Browse Source

Introduce a new session entity and store the session identifier in the tokens

pull/2521/head
Kévin Chalet 2 months ago
parent
commit
4f6882bc0a
  1. 118
      sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/IdentityConfig.cs
  2. 123
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
  3. 21
      sandbox/OpenIddict.Sandbox.AspNet.Server/Models/IdentityModels.cs
  4. 5
      sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs
  5. 136
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs
  6. 16
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Program.cs
  7. 36
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Services/UserClaimsPrincipalFactory.cs
  8. 12
      src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs
  9. 82
      src/OpenIddict.Abstractions/Caches/IOpenIddictSessionCache.cs
  10. 16
      src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs
  11. 36
      src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs
  12. 22
      src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs
  13. 12
      src/OpenIddict.Abstractions/Descriptors/OpenIddictResourceDescriptor.cs
  14. 14
      src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs
  15. 53
      src/OpenIddict.Abstractions/Descriptors/OpenIddictSessionDescriptor.cs
  16. 32
      src/OpenIddict.Abstractions/Descriptors/OpenIddictTokenDescriptor.cs
  17. 26
      src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs
  18. 59
      src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
  19. 16
      src/OpenIddict.Abstractions/Managers/IOpenIddictResourceManager.cs
  20. 16
      src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs
  21. 346
      src/OpenIddict.Abstractions/Managers/IOpenIddictSessionManager.cs
  22. 33
      src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
  23. 2
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  24. 10
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  25. 34
      src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
  26. 10
      src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs
  27. 21
      src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
  28. 10
      src/OpenIddict.Abstractions/Stores/IOpenIddictResourceStore.cs
  29. 10
      src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs
  30. 319
      src/OpenIddict.Abstractions/Stores/IOpenIddictSessionStore.cs
  31. 26
      src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs
  32. 2
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionFormatter.cs
  33. 2
      src/OpenIddict.Client/IOpenIddictClientHandlerFilter.cs
  34. 6
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  35. 2
      src/OpenIddict.Core/Caches/OpenIddictApplicationCache.cs
  36. 9
      src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs
  37. 2
      src/OpenIddict.Core/Caches/OpenIddictResourceCache.cs
  38. 2
      src/OpenIddict.Core/Caches/OpenIddictScopeCache.cs
  39. 383
      src/OpenIddict.Core/Caches/OpenIddictSessionCache.cs
  40. 8
      src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs
  41. 28
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  42. 105
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  43. 16
      src/OpenIddict.Core/Managers/OpenIddictResourceManager.cs
  44. 16
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  45. 919
      src/OpenIddict.Core/Managers/OpenIddictSessionManager.cs
  46. 50
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  47. 93
      src/OpenIddict.Core/OpenIddictCoreBuilder.cs
  48. 4
      src/OpenIddict.Core/OpenIddictCoreExtensions.cs
  49. 5
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs
  50. 5
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs
  51. 3
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkResource.cs
  52. 3
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs
  53. 77
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkSession.cs
  54. 7
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs
  55. 74
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkSessionConfiguration.cs
  56. 9
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs
  57. 2
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs
  58. 8
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs
  59. 51
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs
  60. 706
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkSessionStore.cs
  61. 61
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs
  62. 10
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs
  63. 10
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs
  64. 3
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreResource.cs
  65. 3
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs
  66. 85
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreSession.cs
  67. 14
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs
  68. 85
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreSessionConfiguration.cs
  69. 7
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs
  70. 4
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs
  71. 2
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs
  72. 21
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
  73. 54
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
  74. 6
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreResourceStore.cs
  75. 6
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs
  76. 668
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreSessionStore.cs
  77. 72
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
  78. 2
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs
  79. 70
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbSession.cs
  80. 2
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs
  81. 29
      src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs
  82. 2
      src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs
  83. 5
      src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs
  84. 41
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
  85. 499
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbSessionStore.cs
  86. 44
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs
  87. 1
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs
  88. 5
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs
  89. 2
      src/OpenIddict.Server/IOpenIddictServerHandlerFilter.cs
  90. 17
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  91. 1
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs
  92. 1
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs
  93. 2
      src/OpenIddict.Validation/IOpenIddictValidationHandlerFilter.cs
  94. 148
      test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
  95. 549
      test/OpenIddict.Core.Tests/Caches/OpenIddictSessionCacheTests.cs
  96. 20
      test/OpenIddict.Core.Tests/Managers/OpenIddictAuthorizationManagerTests.cs
  97. 861
      test/OpenIddict.Core.Tests/Managers/OpenIddictSessionManagerTests.cs
  98. 88
      test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs
  99. 20
      test/OpenIddict.Core.Tests/OpenIddictCoreExtensionsTests.cs
  100. 7
      test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkBuilderTests.cs

118
sandbox/OpenIddict.Sandbox.AspNet.Server/App_Start/IdentityConfig.cs

@ -1,4 +1,6 @@
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity;
@ -6,29 +8,22 @@ using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin; using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin; using Microsoft.Owin;
using Microsoft.Owin.Security; using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using OpenIddict.Abstractions;
using OpenIddict.Sandbox.AspNet.Server.Models; using OpenIddict.Sandbox.AspNet.Server.Models;
namespace OpenIddict.Sandbox.AspNet.Server; namespace OpenIddict.Sandbox.AspNet.Server;
public class EmailService : IIdentityMessageService public class EmailService : IIdentityMessageService
{ {
public Task SendAsync(IdentityMessage message) public Task SendAsync(IdentityMessage message) => Task.CompletedTask;
{
// Connectez votre service e-mail ici pour envoyer un e-mail.
return Task.FromResult(0);
}
} }
public class SmsService : IIdentityMessageService public class SmsService : IIdentityMessageService
{ {
public Task SendAsync(IdentityMessage message) public Task SendAsync(IdentityMessage message) => Task.CompletedTask;
{
// Connectez votre service SMS ici pour envoyer un message texte.
return Task.FromResult(0);
}
} }
// Configurer l'application que le gestionnaire des utilisateurs a utilisée dans cette application. UserManager est défini dans ASP.NET Identity et est utilisé par l'application.
public class ApplicationUserManager : UserManager<ApplicationUser> public class ApplicationUserManager : UserManager<ApplicationUser>
{ {
public ApplicationUserManager(IUserStore<ApplicationUser> store) public ApplicationUserManager(IUserStore<ApplicationUser> store)
@ -39,14 +34,13 @@ public class ApplicationUserManager : UserManager<ApplicationUser>
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{ {
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>())); var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
// Configurer la logique de validation pour les noms d'utilisateur
manager.UserValidator = new UserValidator<ApplicationUser>(manager) manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{ {
AllowOnlyAlphanumericUserNames = false, AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true RequireUniqueEmail = true
}; };
// Configurer la logique de validation pour les mots de passe
manager.PasswordValidator = new PasswordValidator manager.PasswordValidator = new PasswordValidator
{ {
RequiredLength = 6, RequiredLength = 6,
@ -56,13 +50,10 @@ public class ApplicationUserManager : UserManager<ApplicationUser>
RequireUppercase = true, RequireUppercase = true,
}; };
// Configurer les valeurs par défaut du verrouillage de l'utilisateur
manager.UserLockoutEnabledByDefault = true; manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5; manager.MaxFailedAccessAttemptsBeforeLockout = 5;
// Inscrire les fournisseurs d'authentification à 2 facteurs. Cette application utilise le téléphone et l'e-mail comme procédure de réception d'un code de vérification de l'utilisateur
// Vous pouvez écrire votre propre fournisseur et le connecter ici.
manager.RegisterTwoFactorProvider("Code téléphonique ", new PhoneNumberTokenProvider<ApplicationUser> manager.RegisterTwoFactorProvider("Code téléphonique ", new PhoneNumberTokenProvider<ApplicationUser>
{ {
MessageFormat = "Votre code de sécurité est {0}" MessageFormat = "Votre code de sécurité est {0}"
@ -84,7 +75,6 @@ public class ApplicationUserManager : UserManager<ApplicationUser>
} }
} }
// Configurer le gestionnaire de connexion d'application qui est utilisé dans cette application.
public class ApplicationSignInManager : SignInManager<ApplicationUser, string> public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{ {
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
@ -101,4 +91,98 @@ public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{ {
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication); return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
} }
public static async Task OnValidateIdentity(CookieValidateIdentityContext context)
{
// Note: the logic implemented here is equivalent to the default security stamp validation logic used
// by ASP.NET Identity but allows overriding the login identifier claim to ensure it is preserved
// when the identity is regenerated and the new application cookie is returned to the user agent.
//
// Unlike the default implementation, this method also uses a time-constant comparison
// to prevent leaking information about the security stamp value through timing attacks.
if ((context.Options.SystemClock.UtcNow - context.Properties.IssuedUtc) < TimeSpan.FromMinutes(30))
{
return;
}
var manager = context.OwinContext.GetUserManager<ApplicationUserManager>()
?? throw new InvalidOperationException("The user manager cannot be resolved from the context.");
if (!manager.SupportsUserSecurityStamp)
{
throw new InvalidOperationException("The user manager does not support security stamp-based validation.");
}
if (string.IsNullOrEmpty(context.Identity.GetUserId()))
{
throw new InvalidOperationException("The user ID cannot be resolved from the user identity.");
}
if (string.IsNullOrEmpty(context.Identity.FindFirstValue(Constants.DefaultSecurityStampClaimType)))
{
throw new InvalidOperationException("The security stamp cannot be resolved from the user identity.");
}
var user = await manager.FindByIdAsync(context.Identity.GetUserId());
if (user is null || await manager.GetSecurityStampAsync(user.Id) is not { Length: > 0 } value)
{
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
return;
}
if (!FixedTimeEquals(
left : MemoryMarshal.AsBytes<char>(value),
right: MemoryMarshal.AsBytes<char>(context.Identity.FindFirstValue(Constants.DefaultSecurityStampClaimType))))
{
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
return;
}
if (await user.GenerateUserIdentityAsync(manager) is not ClaimsIdentity identity)
{
throw new InvalidOperationException("The user identity cannot be generated for the specified user.");
}
var identifier = context.Identity.GetClaim("login_id");
if (!string.IsNullOrEmpty(identifier))
{
identity.SetClaim("login_id", identifier);
}
context.Properties.IssuedUtc = null;
context.Properties.ExpiresUtc = null;
context.OwinContext.Authentication.SignIn(context.Properties, identity);
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
static bool FixedTimeEquals(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
{
// Note: the logic used here is directly taken from the official implementation of
// the CryptographicOperations.FixedTimeEquals() method introduced in .NET Core 2.1.
//
// See https://github.com/dotnet/corefx/pull/27103 for more information.
// Note: these null checks can be theoretically considered as early checks
// (which would defeat the purpose of a time-constant comparison method),
// but the expected string length is the only information an attacker
// could get at this stage, which is not critical where this method is used.
if (left.Length != right.Length)
{
return false;
}
var length = left.Length;
var accumulator = 0;
for (var index = 0; index < length; index++)
{
accumulator |= left[index] - right[index];
}
return accumulator is 0;
}
}
} }

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

@ -31,17 +31,20 @@ public class AuthorizationController : Controller
private readonly IOpenIddictAuthorizationManager _authorizationManager; private readonly IOpenIddictAuthorizationManager _authorizationManager;
private readonly OpenIddictClientService _clientService; private readonly OpenIddictClientService _clientService;
private readonly IOpenIddictScopeManager _scopeManager; private readonly IOpenIddictScopeManager _scopeManager;
private readonly IOpenIddictSessionManager _sessionManager;
public AuthorizationController( public AuthorizationController(
IOpenIddictApplicationManager applicationManager, IOpenIddictApplicationManager applicationManager,
IOpenIddictAuthorizationManager authorizationManager, IOpenIddictAuthorizationManager authorizationManager,
OpenIddictClientService clientService, OpenIddictClientService clientService,
IOpenIddictScopeManager scopeManager) IOpenIddictScopeManager scopeManager,
IOpenIddictSessionManager sessionManager)
{ {
_applicationManager = applicationManager; _applicationManager = applicationManager;
_authorizationManager = authorizationManager; _authorizationManager = authorizationManager;
_clientService = clientService; _clientService = clientService;
_scopeManager = scopeManager; _scopeManager = scopeManager;
_sessionManager = sessionManager;
} }
[HttpGet, Route("~/connect/authorize")] [HttpGet, Route("~/connect/authorize")]
@ -124,8 +127,12 @@ public class AuthorizationController : Controller
} }
// Retrieve the profile of the logged in user. // Retrieve the profile of the logged in user.
var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(result.Identity.GetUserId()) var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(result.Identity.GetUserId());
?? throw new InvalidOperationException("The user details cannot be retrieved."); if (user is null)
{
context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie);
return new EmptyResult();
}
// Retrieve the application details from the database. // Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId) var application = await _applicationManager.FindByClientIdAsync(request.ClientId)
@ -133,11 +140,12 @@ public class AuthorizationController : Controller
// Retrieve the permanent authorizations associated with the user and the calling client application. // Retrieve the permanent authorizations associated with the user and the calling client application.
var authorizations = await _authorizationManager.FindAsync( var authorizations = await _authorizationManager.FindAsync(
subject: user.Id, query: (
client : await _applicationManager.GetIdAsync(application), Subject : user.Id,
status : Statuses.Valid, ApplicationId : await _applicationManager.GetIdAsync(application),
type : AuthorizationTypes.Permanent, Status : Statuses.Valid,
scopes : request.GetScopes()).ToListAsync(); Type : AuthorizationTypes.Permanent,
RequiredScopes: request.GetScopes())).ToListAsync();
switch (await _applicationManager.GetConsentTypeAsync(application)) switch (await _applicationManager.GetConsentTypeAsync(application))
{ {
@ -181,13 +189,34 @@ public class AuthorizationController : Controller
// Automatically create a permanent authorization to avoid requiring explicit consent // Automatically create a permanent authorization to avoid requiring explicit consent
// for future authorization or token requests containing the same scopes. // for future authorization or token requests containing the same scopes.
var authorization = authorizations.LastOrDefault(); var authorization = authorizations.LastOrDefault() ?? await _authorizationManager.CreateAsync(new()
authorization ??= await _authorizationManager.CreateAsync( {
identity: identity, ApplicationId = await _applicationManager.GetIdAsync(application),
subject : user.Id, Principal = new ClaimsPrincipal(identity),
client : await _applicationManager.GetIdAsync(application), Scopes = [.. request.GetScopes()],
type : AuthorizationTypes.Permanent, Subject = user.Id,
scopes : identity.GetScopes()); Type = AuthorizationTypes.Permanent,
});
// If available, resolve the latest session corresponding to the login identifier stored
// in the authentication cookie or create a new one if no valid session can be found.
if (result.Identity.HasClaim("login_id"))
{
var sessions = await _sessionManager.FindAsync(
query: (
Subject : user.Id,
LoginId : result.Identity.GetClaim("login_id"),
ApplicationId: await _applicationManager.GetIdAsync(application),
Status : Statuses.Valid)).ToListAsync();
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{
ApplicationId = await _applicationManager.GetIdAsync(application),
Subject = user.Id
});
identity.SetSessionId(await _sessionManager.GetIdAsync(session));
}
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
identity.SetDestinations(GetDestinations); identity.SetDestinations(GetDestinations);
@ -235,7 +264,7 @@ public class AuthorizationController : Controller
// Retrieve the user principal stored in the authentication cookie. // Retrieve the user principal stored in the authentication cookie.
var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie);
if (result == null || result.Identity == null) if (result is not { Identity.IsAuthenticated: true })
{ {
context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie); context.Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie);
@ -243,8 +272,20 @@ public class AuthorizationController : Controller
} }
// Retrieve the profile of the logged in user. // Retrieve the profile of the logged in user.
var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(result.Identity.GetUserId()) var user = await context.GetUserManager<ApplicationUserManager>().FindByIdAsync(result.Identity.GetUserId());
?? throw new InvalidOperationException("The user details cannot be retrieved."); if (user is null)
{
context.Authentication.Challenge(
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerOwinConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerOwinConstants.Properties.ErrorDescription] =
"The account associated with the logged in user was removed."
}));
return new EmptyResult();
}
// Retrieve the application details from the database. // Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId) var application = await _applicationManager.FindByClientIdAsync(request.ClientId)
@ -252,11 +293,12 @@ public class AuthorizationController : Controller
// Retrieve the permanent authorizations associated with the user and the calling client application. // Retrieve the permanent authorizations associated with the user and the calling client application.
var authorizations = await _authorizationManager.FindAsync( var authorizations = await _authorizationManager.FindAsync(
subject: user.Id, query: (
client : await _applicationManager.GetIdAsync(application), Subject : user.Id,
status : Statuses.Valid, ApplicationId : await _applicationManager.GetIdAsync(application),
type : AuthorizationTypes.Permanent, Status : Statuses.Valid,
scopes : request.GetScopes()).ToListAsync(); Type : AuthorizationTypes.Permanent,
RequiredScopes: request.GetScopes())).ToListAsync();
// Note: the same check is already made in the other action but is repeated // Note: the same check is already made in the other action but is repeated
// here to ensure a malicious user can't abuse this POST-only endpoint and // here to ensure a malicious user can't abuse this POST-only endpoint and
@ -296,13 +338,34 @@ public class AuthorizationController : Controller
// Automatically create a permanent authorization to avoid requiring explicit consent // Automatically create a permanent authorization to avoid requiring explicit consent
// for future authorization or token requests containing the same scopes. // for future authorization or token requests containing the same scopes.
var authorization = authorizations.LastOrDefault(); var authorization = authorizations.LastOrDefault() ?? await _authorizationManager.CreateAsync(new()
authorization ??= await _authorizationManager.CreateAsync( {
identity: identity, ApplicationId = await _applicationManager.GetIdAsync(application),
subject : user.Id, Principal = new ClaimsPrincipal(identity),
client : await _applicationManager.GetIdAsync(application), Scopes = [.. request.GetScopes()],
type : AuthorizationTypes.Permanent, Subject = user.Id,
scopes : identity.GetScopes()); Type = AuthorizationTypes.Permanent,
});
// If available, resolve the latest session corresponding to the login identifier stored
// in the authentication cookie or create a new one if no valid session can be found.
if (result.Identity.HasClaim("login_id"))
{
var sessions = await _sessionManager.FindAsync(
query: (
Subject : user.Id,
LoginId : result.Identity.GetClaim("login_id"),
ApplicationId: await _applicationManager.GetIdAsync(application),
Status : Statuses.Valid)).ToListAsync();
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{
ApplicationId = await _applicationManager.GetIdAsync(application),
Subject = user.Id
});
identity.SetSessionId(await _sessionManager.GetIdAsync(session));
}
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
identity.SetDestinations(GetDestinations); identity.SetDestinations(GetDestinations);

21
sandbox/OpenIddict.Sandbox.AspNet.Server/Models/IdentityModels.cs

@ -1,20 +1,29 @@
using System.Data.Entity; using System.Buffers.Text;
using System.Data.Entity;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.EntityFramework;
namespace OpenIddict.Sandbox.AspNet.Server.Models; namespace OpenIddict.Sandbox.AspNet.Server.Models;
// Vous pouvez ajouter des données de profil pour l'utilisateur en ajoutant d'autres propriétés à votre classe ApplicationUser. Pour en savoir plus, consultez https://go.microsoft.com/fwlink/?LinkID=317594.
public class ApplicationUser : IdentityUser public class ApplicationUser : IdentityUser
{ {
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{ {
// Notez que l'authenticationType doit correspondre à celui défini dans CookieAuthenticationOptions.AuthenticationType var identity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Ajouter des revendications utilisateur personnalisées ici // Generate and attach a unique login identifier to the claims identity: this value will
return userIdentity; // be used by the authorization controller to infer a unique identifier representing the
// current user session and bind the tokens issued by OpenIddict to a specific session.
//
// Note: this method is also called when the application cookie is refreshed: to ensure
// the login identifier is preserved, a custom OnRefreshingPrincipal event handler is used
// to copy the login identifier from the existing principal to the refreshed instance.
identity.AddClaim(new Claim("login_id", Base64Url.EncodeToString(RandomNumberGenerator.GetBytes(256 / 8))));
return identity;
} }
} }

5
sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs

@ -7,7 +7,6 @@ using Autofac.Extensions.DependencyInjection;
using Autofac.Integration.Mvc; using Autofac.Integration.Mvc;
using Autofac.Integration.WebApi; using Autofac.Integration.WebApi;
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Owin; using Microsoft.Owin;
using Microsoft.Owin.Host.SystemWeb; using Microsoft.Owin.Host.SystemWeb;
@ -150,9 +149,7 @@ public class Startup
LoginPath = new PathString("/Account/Login"), LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider Provider = new CookieAuthenticationProvider
{ {
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>( OnValidateIdentity = ApplicationSignInManager.OnValidateIdentity
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
} }
}); });

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

@ -29,6 +29,7 @@ public class AuthorizationController : Controller
private readonly IOpenIddictAuthorizationManager _authorizationManager; private readonly IOpenIddictAuthorizationManager _authorizationManager;
private readonly OpenIddictClientService _clientService; private readonly OpenIddictClientService _clientService;
private readonly IOpenIddictScopeManager _scopeManager; private readonly IOpenIddictScopeManager _scopeManager;
private readonly IOpenIddictSessionManager _sessionManager;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
@ -37,6 +38,7 @@ public class AuthorizationController : Controller
IOpenIddictAuthorizationManager authorizationManager, IOpenIddictAuthorizationManager authorizationManager,
OpenIddictClientService clientService, OpenIddictClientService clientService,
IOpenIddictScopeManager scopeManager, IOpenIddictScopeManager scopeManager,
IOpenIddictSessionManager sessionManager,
SignInManager<ApplicationUser> signInManager, SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager) UserManager<ApplicationUser> userManager)
{ {
@ -44,6 +46,7 @@ public class AuthorizationController : Controller
_authorizationManager = authorizationManager; _authorizationManager = authorizationManager;
_clientService = clientService; _clientService = clientService;
_scopeManager = scopeManager; _scopeManager = scopeManager;
_sessionManager = sessionManager;
_signInManager = signInManager; _signInManager = signInManager;
_userManager = userManager; _userManager = userManager;
} }
@ -148,8 +151,15 @@ public class AuthorizationController : Controller
} }
// Retrieve the profile of the logged in user. // Retrieve the profile of the logged in user.
var user = await _userManager.GetUserAsync(result.Principal) var user = await _userManager.GetUserAsync(result.Principal);
?? throw new InvalidOperationException("The user details cannot be retrieved."); if (user is null)
{
return Challenge(new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form : Request.Query)
});
}
// Retrieve the application details from the database. // Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!) var application = await _applicationManager.FindByClientIdAsync(request.ClientId!)
@ -157,11 +167,12 @@ public class AuthorizationController : Controller
// Retrieve the permanent authorizations associated with the user and the calling client application. // Retrieve the permanent authorizations associated with the user and the calling client application.
var authorizations = await _authorizationManager.FindAsync( var authorizations = await _authorizationManager.FindAsync(
subject: await _userManager.GetUserIdAsync(user), query: (
client : await _applicationManager.GetIdAsync(application), Subject : await _userManager.GetUserIdAsync(user),
status : Statuses.Valid, ApplicationId : await _applicationManager.GetIdAsync(application),
type : AuthorizationTypes.Permanent, Status : Statuses.Valid,
scopes : request.GetScopes()).ToListAsync(); Type : AuthorizationTypes.Permanent,
RequiredScopes: request.GetScopes())).ToListAsync();
switch (await _applicationManager.GetConsentTypeAsync(application)) switch (await _applicationManager.GetConsentTypeAsync(application))
{ {
@ -203,17 +214,39 @@ public class AuthorizationController : Controller
// Automatically create a permanent authorization to avoid requiring explicit consent // Automatically create a permanent authorization to avoid requiring explicit consent
// for future authorization or token requests containing the same scopes. // for future authorization or token requests containing the same scopes.
var authorization = authorizations.LastOrDefault(); var authorization = authorizations.LastOrDefault() ?? await _authorizationManager.CreateAsync(new()
authorization ??= await _authorizationManager.CreateAsync( {
identity: identity, ApplicationId = await _applicationManager.GetIdAsync(application),
subject : await _userManager.GetUserIdAsync(user), Principal = new ClaimsPrincipal(identity),
client : (await _applicationManager.GetIdAsync(application))!, Scopes = [.. request.GetScopes()],
type : AuthorizationTypes.Permanent, Subject = await _userManager.GetUserIdAsync(user),
scopes : identity.GetScopes()); Type = AuthorizationTypes.Permanent,
});
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
identity.SetDestinations(GetDestinations); identity.SetDestinations(GetDestinations);
// If available, resolve the latest session corresponding to the login identifier stored
// in the authentication cookie or create a new one if no valid session can be found.
if (result.Principal.HasClaim("login_id"))
{
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();
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{
ApplicationId = await _applicationManager.GetIdAsync(application),
LoginId = result.Principal.GetClaim("login_id"),
Subject = await _userManager.GetUserIdAsync(user)
});
identity.SetSessionId(await _sessionManager.GetIdAsync(session));
}
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// At this point, no authorization was found in the database and an error must be returned // At this point, no authorization was found in the database and an error must be returned
@ -251,8 +284,18 @@ public class AuthorizationController : Controller
?? throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); ?? throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Retrieve the profile of the logged in user. // Retrieve the profile of the logged in user.
var user = await _userManager.GetUserAsync(User) var user = await _userManager.GetUserAsync(User);
?? throw new InvalidOperationException("The user details cannot be retrieved."); if (user is null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The account associated with the logged in user was removed."
}));
}
// Retrieve the application details from the database. // Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!) var application = await _applicationManager.FindByClientIdAsync(request.ClientId!)
@ -260,11 +303,12 @@ public class AuthorizationController : Controller
// Retrieve the permanent authorizations associated with the user and the calling client application. // Retrieve the permanent authorizations associated with the user and the calling client application.
var authorizations = await _authorizationManager.FindAsync( var authorizations = await _authorizationManager.FindAsync(
subject: await _userManager.GetUserIdAsync(user), query: (
client : await _applicationManager.GetIdAsync(application), Subject : await _userManager.GetUserIdAsync(user),
status : Statuses.Valid, ApplicationId : await _applicationManager.GetIdAsync(application),
type : AuthorizationTypes.Permanent, Status : Statuses.Valid,
scopes : request.GetScopes()).ToListAsync(); Type : AuthorizationTypes.Permanent,
RequiredScopes: request.GetScopes())).ToListAsync();
// Note: the same check is already made in the other action but is repeated // Note: the same check is already made in the other action but is repeated
// here to ensure a malicious user can't abuse this POST-only endpoint and // here to ensure a malicious user can't abuse this POST-only endpoint and
@ -302,17 +346,39 @@ public class AuthorizationController : Controller
// Automatically create a permanent authorization to avoid requiring explicit consent // Automatically create a permanent authorization to avoid requiring explicit consent
// for future authorization or token requests containing the same scopes. // for future authorization or token requests containing the same scopes.
var authorization = authorizations.LastOrDefault(); var authorization = authorizations.LastOrDefault() ?? await _authorizationManager.CreateAsync(new()
authorization ??= await _authorizationManager.CreateAsync( {
identity: identity, ApplicationId = await _applicationManager.GetIdAsync(application),
subject : await _userManager.GetUserIdAsync(user), Principal = new ClaimsPrincipal(identity),
client : (await _applicationManager.GetIdAsync(application))!, Scopes = [.. request.GetScopes()],
type : AuthorizationTypes.Permanent, Subject = await _userManager.GetUserIdAsync(user),
scopes : identity.GetScopes()); Type = AuthorizationTypes.Permanent,
});
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
identity.SetDestinations(GetDestinations); identity.SetDestinations(GetDestinations);
// If available, resolve the latest session corresponding to the login identifier stored
// in the authentication cookie or create a new one if no valid session can be found.
if (User.HasClaim("login_id"))
{
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();
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{
ApplicationId = await _applicationManager.GetIdAsync(application),
LoginId = User.GetClaim("login_id"),
Subject = await _userManager.GetUserIdAsync(user)
});
identity.SetSessionId(await _sessionManager.GetIdAsync(session));
}
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
} }
@ -366,8 +432,18 @@ public class AuthorizationController : Controller
public async Task<IActionResult> VerifyAccept() public async Task<IActionResult> VerifyAccept()
{ {
// Retrieve the profile of the logged in user. // Retrieve the profile of the logged in user.
var user = await _userManager.GetUserAsync(User) var user = await _userManager.GetUserAsync(User);
?? throw new InvalidOperationException("The user details cannot be retrieved."); if (user is null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The account associated with the logged in user was removed."
}));
}
// Retrieve the claims principal associated with the user code. // Retrieve the claims principal associated with the user code.
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

16
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Program.cs

@ -31,9 +31,25 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
// Register the Identity builder.Services. // Register the Identity builder.Services.
builder.Services.AddIdentity<ApplicationUser, IdentityRole>() builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddClaimsPrincipalFactory<UserClaimsPrincipalFactory>()
.AddEntityFrameworkStores<ApplicationDbContext>() .AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders(); .AddDefaultTokenProviders();
// Note: ASP.NET Core Identity doesn't store a unique identifier representing user sessions.
//
// To work around that, a custom IUserClaimsPrincipalFactory<TUser> is used to attach a unique identifier and a
// custom OnRefreshingPrincipal event handler is used to restore that identifier when the cookie is refreshed.
builder.Services.Configure<SecurityStampValidatorOptions>(options => options.OnRefreshingPrincipal = static context =>
{
var identifier = context.CurrentPrincipal?.GetClaim("login_id");
if (!string.IsNullOrEmpty(identifier))
{
context.NewPrincipal?.SetClaim("login_id", identifier);
}
return Task.CompletedTask;
});
// OpenIddict offers native integration with Quartz.NET to perform scheduled tasks // OpenIddict offers native integration with Quartz.NET to perform scheduled tasks
// (like pruning orphaned authorizations/tokens from the database) at regular intervals. // (like pruning orphaned authorizations/tokens from the database) at regular intervals.
builder.Services.AddQuartz(options => builder.Services.AddQuartz(options =>

36
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Services/UserClaimsPrincipalFactory.cs

@ -0,0 +1,36 @@
using System.Buffers.Text;
using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using OpenIddict.Sandbox.AspNetCore.Server.Models;
namespace OpenIddict.Sandbox.AspNetCore.Server.Services;
public sealed class UserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
public UserClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, optionsAccessor)
{
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
{
ArgumentNullException.ThrowIfNull(user);
var identity = await base.GenerateClaimsAsync(user);
// Generate and attach a unique login identifier to the claims identity: this value will
// be used by the authorization controller to infer a unique identifier representing the
// current user session and bind the tokens issued by OpenIddict to a specific session.
//
// Note: this method is also called when the application cookie is refreshed: to ensure
// the login identifier is preserved, a custom OnValidateIdentity event handler is used
// to copy the login identifier from the existing principal to the refreshed instance.
identity.AddClaim(new Claim("login_id", Base64Url.EncodeToString(RandomNumberGenerator.GetBytes(256 / 8))));
return identity;
}
}

12
src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs

@ -23,18 +23,14 @@ public interface IOpenIddictAuthorizationCache<TAuthorization> where TAuthorizat
ValueTask AddAsync(TAuthorization authorization, CancellationToken cancellationToken); ValueTask AddAsync(TAuthorization authorization, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the authorizations matching the specified parameters. /// Retrieves the authorizations matching the specified query.
/// </summary> /// </summary>
/// <param name="subject">The subject associated with the authorization, or <see langword="null"/> not to filter out specific subjects.</param> /// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <param name="client">The client associated with the authorization, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The authorization status, or <see langword="null"/> not to filter out specific authorization statuses.</param>
/// <param name="type">The authorization type, or <see langword="null"/> not to filter out specific authorization types.</param>
/// <param name="scopes">The minimal scopes associated with the authorization, or <see langword="null"/> not to filter out scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The authorizations corresponding to the criteria.</returns> /// <returns>The authorizations corresponding to the criteria.</returns>
IAsyncEnumerable<TAuthorization> FindAsync( IAsyncEnumerable<TAuthorization> FindAsync(
string? subject, string? client, string? status, (string? Subject, string? ApplicationId, string? Status,
string? type, ImmutableArray<string>? scopes, CancellationToken cancellationToken); string? Type, ImmutableArray<string>? RequiredScopes) query, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the list of authorizations corresponding to the specified application identifier. /// Retrieves the list of authorizations corresponding to the specified application identifier.

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

@ -0,0 +1,82 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
namespace OpenIddict.Abstractions;
/// <summary>
/// Provides methods allowing to cache sessions after retrieving them from the store.
/// </summary>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
public interface IOpenIddictSessionCache<TSession> where TSession : class
{
/// <summary>
/// Add the specified session to the cache.
/// </summary>
/// <param name="session">The session to add to the cache.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask AddAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the sessions matching the specified query.
/// </summary>
/// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <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);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified application identifier.
/// </summary>
/// <param name="identifier">The application identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified application.</returns>
IAsyncEnumerable<TSession> FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified authorization identifier.
/// </summary>
/// <param name="identifier">The authorization identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified authorization.</returns>
IAsyncEnumerable<TSession> FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified login identifier.
/// </summary>
/// <param name="identifier">The login identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified login identifier.</returns>
IAsyncEnumerable<TSession> FindByLoginIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves a session using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the session corresponding to the identifier.
/// </returns>
ValueTask<TSession?> FindByIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified subject.</returns>
IAsyncEnumerable<TSession> FindBySubjectAsync(string subject, CancellationToken cancellationToken);
/// <summary>
/// Removes the specified session from the cache.
/// </summary>
/// <param name="session">The session to remove from the cache.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask RemoveAsync(TSession session, CancellationToken cancellationToken);
}

16
src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs

@ -21,17 +21,13 @@ public interface IOpenIddictTokenCache<TToken> where TToken : class
ValueTask AddAsync(TToken token, CancellationToken cancellationToken); ValueTask AddAsync(TToken token, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the tokens matching the specified parameters. /// Retrieves the tokens matching the specified query.
/// </summary> /// </summary>
/// <param name="subject">The subject associated with the token, or <see langword="null"/> not to filter out specific subjects.</param> /// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <param name="client">The client associated with the token, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The token status, or <see langword="null"/> not to filter out specific token statuses.</param>
/// <param name="type">The token type, or <see langword="null"/> not to filter out specific token types.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The tokens corresponding to the criteria.</returns> /// <returns>The tokens corresponding to the criteria.</returns>
IAsyncEnumerable<TToken> FindAsync( IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status, string? Type) query, CancellationToken cancellationToken);
string? status, string? type, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier. /// Retrieves the list of tokens corresponding to the specified application identifier.
@ -61,9 +57,11 @@ public interface IOpenIddictTokenCache<TToken> where TToken : class
ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken); ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified reference identifier. /// Retrieves a token using its unique reference identifier.
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks>
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </remarks>
/// <param name="identifier">The reference identifier associated with the tokens.</param> /// <param name="identifier">The reference identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>

36
src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs

@ -10,80 +10,84 @@ namespace OpenIddict.Abstractions;
public class OpenIddictApplicationDescriptor public class OpenIddictApplicationDescriptor
{ {
/// <summary> /// <summary>
/// Gets or sets the application type associated with the application. /// Gets or sets the application type of the application.
/// </summary> /// </summary>
public string? ApplicationType { get; set; } public string? ApplicationType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client identifier associated with the application. /// Gets or sets the client identifier of the application.
/// </summary> /// </summary>
public string? ClientId { get; set; } public string? ClientId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client secret associated with the application. /// Gets or sets the client secret of the application.
/// Note: depending on the application manager used when creating it,
/// this property may be hashed or encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para>
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </para>
/// <para>
/// Note: client authentication based on shared secrets is not recommended and should /// Note: client authentication based on shared secrets is not recommended and should
/// only be used for backward compatibility with legacy applications that only support /// only be used for backward compatibility with legacy applications that only support
/// client secrets. When possible, consider using public/private key pairs or TLS client /// client secrets. When possible, consider using public/private key pairs or TLS client
/// certificates instead, as these client authentication methods are significantly safer. /// certificates instead, as these client authentication methods are significantly safer.
/// </para>
/// </remarks> /// </remarks>
public string? ClientSecret { get; set; } public string? ClientSecret { get; set; }
/// <summary> /// <summary>
/// Gets or sets the client type associated with the application. /// Gets or sets the client type of the application.
/// </summary> /// </summary>
public string? ClientType { get; set; } public string? ClientType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the consent type associated with the application. /// Gets or sets the consent type of the application.
/// </summary> /// </summary>
public string? ConsentType { get; set; } public string? ConsentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the display name associated with the application. /// Gets or sets the display name of the application.
/// </summary> /// </summary>
public string? DisplayName { get; set; } public string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets the localized display names associated with the application. /// Gets the localized display names of the application.
/// </summary> /// </summary>
public Dictionary<CultureInfo, string> DisplayNames { get; } = []; public Dictionary<CultureInfo, string> DisplayNames { get; } = [];
/// <summary> /// <summary>
/// Gets or sets the JSON Web Key Set associated with the application. /// Gets or sets the JSON Web Key Set of the application.
/// </summary> /// </summary>
public JsonWebKeySet? JsonWebKeySet { get; set; } public JsonWebKeySet? JsonWebKeySet { get; set; }
/// <summary> /// <summary>
/// Gets the permissions associated with the application. /// Gets the permissions of the application.
/// </summary> /// </summary>
public HashSet<string> Permissions { get; } = new(StringComparer.Ordinal); public HashSet<string> Permissions { get; } = new(StringComparer.Ordinal);
/// <summary> /// <summary>
/// Gets the post-logout redirect URIs associated with the application. /// Gets the post-logout redirect URIs of the application.
/// </summary> /// </summary>
public HashSet<Uri> PostLogoutRedirectUris { get; } = []; public HashSet<Uri> PostLogoutRedirectUris { get; } = [];
/// <summary> /// <summary>
/// Gets the additional properties associated with the application. /// Gets the additional properties of the application.
/// </summary> /// </summary>
public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal); public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal);
/// <summary> /// <summary>
/// Gets the redirect URIs associated with the application. /// Gets the redirect URIs of the application.
/// </summary> /// </summary>
public HashSet<Uri> RedirectUris { get; } = []; public HashSet<Uri> RedirectUris { get; } = [];
/// <summary> /// <summary>
/// Gets the requirements associated with the application. /// Gets the requirements of the application.
/// </summary> /// </summary>
public HashSet<string> Requirements { get; } = new(StringComparer.Ordinal); public HashSet<string> Requirements { get; } = new(StringComparer.Ordinal);
/// <summary> /// <summary>
/// Gets the settings associated with the application. /// Gets the settings of the application.
/// </summary> /// </summary>
public Dictionary<string, string> Settings { get; } = new(StringComparer.Ordinal); public Dictionary<string, string> Settings { get; } = new(StringComparer.Ordinal);

22
src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs

@ -9,38 +9,40 @@ namespace OpenIddict.Abstractions;
public class OpenIddictAuthorizationDescriptor public class OpenIddictAuthorizationDescriptor
{ {
/// <summary> /// <summary>
/// Gets or sets the application identifier associated with the authorization. /// Gets or sets the identifier of the application associated with the authorization.
/// </summary> /// </summary>
public string? ApplicationId { get; set; } public string? ApplicationId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the creation date associated with the authorization. /// Gets or sets the creation date of the authorization.
/// </summary> /// </summary>
public DateTimeOffset? CreationDate { get; set; } public DateTimeOffset? CreationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the optional principal associated with the authorization. /// Gets or sets the optional principal specified by the caller.
/// Note: this property is not stored by the default authorization stores.
/// </summary> /// </summary>
/// <remarks>
/// Note: this property is not stored by the default stores.
/// </remarks>
public ClaimsPrincipal? Principal { get; set; } public ClaimsPrincipal? Principal { get; set; }
/// <summary> /// <summary>
/// Gets the additional properties associated with the authorization. /// Gets or sets the additional properties of the authorization.
/// </summary> /// </summary>
public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal); public Dictionary<string, JsonElement> Properties { get; set; } = new(StringComparer.Ordinal);
/// <summary> /// <summary>
/// Gets the scopes associated with the authorization. /// Gets or sets the scopes of the authorization.
/// </summary> /// </summary>
public HashSet<string> Scopes { get; } = new(StringComparer.Ordinal); public HashSet<string> Scopes { get; set; } = [];
/// <summary> /// <summary>
/// Gets or sets the status associated with the authorization. /// Gets or sets the status of the authorization.
/// </summary> /// </summary>
public string? Status { get; set; } public string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the authorization. /// Gets or sets the subject of the authorization.
/// </summary> /// </summary>
public string? Subject { get; set; } public string? Subject { get; set; }

12
src/OpenIddict.Abstractions/Descriptors/OpenIddictResourceDescriptor.cs

@ -9,32 +9,32 @@ namespace OpenIddict.Abstractions;
public class OpenIddictResourceDescriptor public class OpenIddictResourceDescriptor
{ {
/// <summary> /// <summary>
/// Gets or sets the description associated with the resource. /// Gets or sets the description of the resource.
/// </summary> /// </summary>
public string? Description { get; set; } public string? Description { get; set; }
/// <summary> /// <summary>
/// Gets the localized descriptions associated with the resource. /// Gets the localized descriptions of the resource.
/// </summary> /// </summary>
public Dictionary<CultureInfo, string> Descriptions { get; } = []; public Dictionary<CultureInfo, string> Descriptions { get; } = [];
/// <summary> /// <summary>
/// Gets or sets the display name associated with the resource. /// Gets or sets the display name of the resource.
/// </summary> /// </summary>
public string? DisplayName { get; set; } public string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets the localized display names associated with the resource. /// Gets the localized display names of the resource.
/// </summary> /// </summary>
public Dictionary<CultureInfo, string> DisplayNames { get; } = []; public Dictionary<CultureInfo, string> DisplayNames { get; } = [];
/// <summary> /// <summary>
/// Gets or sets the unique name associated with the resource. /// Gets or sets the unique name of the resource.
/// </summary> /// </summary>
public string? Name { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// Gets the additional properties associated with the resource. /// Gets the additional properties of the resource.
/// </summary> /// </summary>
public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal); public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal);
} }

14
src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs

@ -9,37 +9,37 @@ namespace OpenIddict.Abstractions;
public class OpenIddictScopeDescriptor public class OpenIddictScopeDescriptor
{ {
/// <summary> /// <summary>
/// Gets or sets the description associated with the scope. /// Gets or sets the description of the scope.
/// </summary> /// </summary>
public string? Description { get; set; } public string? Description { get; set; }
/// <summary> /// <summary>
/// Gets the localized descriptions associated with the scope. /// Gets the localized descriptions of the scope.
/// </summary> /// </summary>
public Dictionary<CultureInfo, string> Descriptions { get; } = []; public Dictionary<CultureInfo, string> Descriptions { get; } = [];
/// <summary> /// <summary>
/// Gets or sets the display name associated with the scope. /// Gets or sets the display name of the scope.
/// </summary> /// </summary>
public string? DisplayName { get; set; } public string? DisplayName { get; set; }
/// <summary> /// <summary>
/// Gets the localized display names associated with the scope. /// Gets the localized display names of the scope.
/// </summary> /// </summary>
public Dictionary<CultureInfo, string> DisplayNames { get; } = []; public Dictionary<CultureInfo, string> DisplayNames { get; } = [];
/// <summary> /// <summary>
/// Gets or sets the unique name associated with the scope. /// Gets or sets the unique name of the scope.
/// </summary> /// </summary>
public string? Name { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// Gets the additional properties associated with the scope. /// Gets the additional properties of the scope.
/// </summary> /// </summary>
public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal); public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal);
/// <summary> /// <summary>
/// Gets the resources associated with the scope. /// Gets the resources of the scope.
/// </summary> /// </summary>
public HashSet<string> Resources { get; } = new(StringComparer.Ordinal); public HashSet<string> Resources { get; } = new(StringComparer.Ordinal);
} }

53
src/OpenIddict.Abstractions/Descriptors/OpenIddictSessionDescriptor.cs

@ -0,0 +1,53 @@
using System.Security.Claims;
using System.Text.Json;
namespace OpenIddict.Abstractions;
/// <summary>
/// Represents an OpenIddict session descriptor.
/// </summary>
public class OpenIddictSessionDescriptor
{
/// <summary>
/// Gets or sets the identifier of the application associated with the session.
/// </summary>
public string? ApplicationId { get; set; }
/// <summary>
/// Gets or sets the identifier of the authorization associated with the session.
/// </summary>
public string? AuthorizationId { get; set; }
/// <summary>
/// Gets or sets the creation date of the session.
/// </summary>
public DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the login identifier of the session.
/// </summary>
public string? LoginId { get; set; }
/// <summary>
/// Gets or sets the optional principal specified by the caller.
/// </summary>
/// <remarks>
/// Note: this property is not stored by the default stores.
/// </remarks>
public ClaimsPrincipal? Principal { get; set; }
/// <summary>
/// Gets the additional properties of the session.
/// </summary>
public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets or sets the status of the session.
/// </summary>
public string? Status { get; set; }
/// <summary>
/// Gets or sets the subject of the session.
/// </summary>
public string? Subject { get; set; }
}

32
src/OpenIddict.Abstractions/Descriptors/OpenIddictTokenDescriptor.cs

@ -9,65 +9,69 @@ namespace OpenIddict.Abstractions;
public class OpenIddictTokenDescriptor public class OpenIddictTokenDescriptor
{ {
/// <summary> /// <summary>
/// Gets or sets the application identifier associated with the token. /// Gets or sets the identifier of the application associated with the token.
/// </summary> /// </summary>
public string? ApplicationId { get; set; } public string? ApplicationId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the authorization identifier associated with the token. /// Gets or sets the identifier of the authorization associated with the token.
/// </summary> /// </summary>
public string? AuthorizationId { get; set; } public string? AuthorizationId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the creation date associated with the token. /// Gets or sets the creation date of the token.
/// </summary> /// </summary>
public DateTimeOffset? CreationDate { get; set; } public DateTimeOffset? CreationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the expiration date associated with the token. /// Gets or sets the expiration date of the token.
/// </summary> /// </summary>
public DateTimeOffset? ExpirationDate { get; set; } public DateTimeOffset? ExpirationDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the payload associated with the token. /// Gets or sets the payload of the token.
/// </summary> /// </summary>
public string? Payload { get; set; } public string? Payload { get; set; }
/// <summary> /// <summary>
/// Gets or sets the optional principal associated with the token. /// Gets or sets the optional principal specified by the caller.
/// Note: this property is not stored by the default token stores.
/// </summary> /// </summary>
/// <remarks>
/// Note: this property is not stored by the default stores.
/// </remarks>
public ClaimsPrincipal? Principal { get; set; } public ClaimsPrincipal? Principal { get; set; }
/// <summary> /// <summary>
/// Gets the additional properties associated with the token. /// Gets the additional properties of the token.
/// </summary> /// </summary>
public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal); public Dictionary<string, JsonElement> Properties { get; } = new(StringComparer.Ordinal);
/// <summary> /// <summary>
/// Gets or sets the redemption date associated with the token. /// Gets or sets the redemption date of the token.
/// </summary> /// </summary>
public DateTimeOffset? RedemptionDate { get; set; } public DateTimeOffset? RedemptionDate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the reference identifier associated with the token. /// Gets or sets the reference identifier of the token.
/// </summary>
/// <remarks>
/// Note: depending on the application manager used when creating it, /// Note: depending on the application manager used when creating it,
/// this property may be hashed or encrypted for security reasons. /// this property may be hashed or encrypted for security reasons.
/// </summary> /// </remarks>
public string? ReferenceId { get; set; } public string? ReferenceId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the status associated with the token. /// Gets or sets the status of the token.
/// </summary> /// </summary>
public string? Status { get; set; } public string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the token. /// Gets or sets the subject of the token.
/// </summary> /// </summary>
public string? Subject { get; set; } public string? Subject { get; set; }
/// <summary> /// <summary>
/// Gets or sets the token type. /// Gets or sets the type of the token.
/// </summary> /// </summary>
public string? Type { get; set; } public string? Type { get; set; }
} }

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

@ -31,7 +31,7 @@ public interface IOpenIddictApplicationManager
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database. /// whose result returns the number of applications in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken = default); ValueTask<long> CountAsync(CancellationToken cancellationToken = default);
@ -43,7 +43,7 @@ public interface IOpenIddictApplicationManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications that match the specified query. /// whose result returns the number of applications that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default); ValueTask<long> CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default);
@ -57,7 +57,7 @@ public interface IOpenIddictApplicationManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications that match the specified query. /// whose result returns the number of applications that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
@ -72,7 +72,7 @@ public interface IOpenIddictApplicationManager
/// <param name="descriptor">The application descriptor.</param> /// <param name="descriptor">The application descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application. /// whose result returns the unique identifier associated with the application.
/// </returns> /// </returns>
ValueTask<object> CreateAsync(OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default); ValueTask<object> CreateAsync(OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default);
@ -122,7 +122,7 @@ public interface IOpenIddictApplicationManager
/// <param name="identifier">The client identifier associated with the application.</param> /// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier. /// whose result returns the client application corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<object?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken = default); ValueTask<object?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken = default);
@ -133,7 +133,7 @@ public interface IOpenIddictApplicationManager
/// <param name="identifier">The unique identifier associated with the application.</param> /// <param name="identifier">The unique identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier. /// whose result returns the client application corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default); ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default);
@ -174,7 +174,7 @@ public interface IOpenIddictApplicationManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TResult>( ValueTask<TResult?> GetAsync<TResult>(
@ -189,7 +189,7 @@ public interface IOpenIddictApplicationManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(
@ -554,7 +554,7 @@ public interface IOpenIddictApplicationManager
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns> /// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client secret was valid. /// whose result returns a boolean indicating whether the client secret was valid.
/// </returns> /// </returns>
ValueTask<bool> ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken = default); ValueTask<bool> ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken = default);
@ -567,7 +567,7 @@ public interface IOpenIddictApplicationManager
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <remarks>Note: if no client_id parameter is specified in end session requests, this method may not be called.</remarks> /// <remarks>Note: if no client_id parameter is specified in end session requests, this method may not be called.</remarks>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the post_logout_redirect_uri was valid. /// whose result returns a boolean indicating whether the post_logout_redirect_uri was valid.
/// </returns> /// </returns>
ValueTask<bool> ValidatePostLogoutRedirectUriAsync(object application, ValueTask<bool> ValidatePostLogoutRedirectUriAsync(object application,
@ -582,7 +582,7 @@ public interface IOpenIddictApplicationManager
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns> /// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client certificate was valid. /// whose result returns a boolean indicating whether the client certificate was valid.
/// </returns> /// </returns>
ValueTask<bool> ValidatePublicKeyInfrastructureTlsClientCertificateAsync(object application, ValueTask<bool> ValidatePublicKeyInfrastructureTlsClientCertificateAsync(object application,
@ -595,7 +595,7 @@ public interface IOpenIddictApplicationManager
/// <param name="uri">The URI that should be compared to one of the redirect_uri stored in the database.</param> /// <param name="uri">The URI that should be compared to one of the redirect_uri stored in the database.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the redirect_uri was valid. /// whose result returns a boolean indicating whether the redirect_uri was valid.
/// </returns> /// </returns>
ValueTask<bool> ValidateRedirectUriAsync(object application, ValueTask<bool> ValidateRedirectUriAsync(object application,
@ -610,7 +610,7 @@ public interface IOpenIddictApplicationManager
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns> /// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose
/// result returns a boolean indicating whether the self-signed client certificate was valid. /// result returns a boolean indicating whether the self-signed client certificate was valid.
/// </returns> /// </returns>
ValueTask<bool> ValidateSelfSignedTlsClientCertificateAsync( ValueTask<bool> ValidateSelfSignedTlsClientCertificateAsync(

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

@ -6,7 +6,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using System.Text.Json; using System.Text.Json;
namespace OpenIddict.Abstractions; namespace OpenIddict.Abstractions;
@ -28,7 +27,7 @@ public interface IOpenIddictAuthorizationManager
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations in the database. /// whose result returns the number of authorizations in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken = default); ValueTask<long> CountAsync(CancellationToken cancellationToken = default);
@ -40,7 +39,7 @@ public interface IOpenIddictAuthorizationManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations that match the specified query. /// whose result returns the number of authorizations that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TResult>( ValueTask<long> CountAsync<TResult>(
@ -55,52 +54,20 @@ public interface IOpenIddictAuthorizationManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations that match the specified query. /// whose result returns the number of authorizations that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
Func<IQueryable<object>, TState, IQueryable<TResult>> query, Func<IQueryable<object>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken = default); TState state, CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new permanent authorization based on the specified parameters.
/// </summary>
/// <param name="identity">The identity associated with the authorization.</param>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="type">The authorization type.</param>
/// <param name="scopes">The minimal scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the authorization.
/// </returns>
ValueTask<object> CreateAsync(
ClaimsIdentity identity, string subject, string client,
string type, ImmutableArray<string> scopes, CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new permanent authorization based on the specified parameters.
/// </summary>
/// <param name="principal">The principal associated with the authorization.</param>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="type">The authorization type.</param>
/// <param name="scopes">The minimal scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the authorization.
/// </returns>
ValueTask<object> CreateAsync(
ClaimsPrincipal principal, string subject, string client,
string type, ImmutableArray<string> scopes, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Creates a new authorization based on the specified descriptor. /// Creates a new authorization based on the specified descriptor.
/// </summary> /// </summary>
/// <param name="descriptor">The authorization descriptor.</param> /// <param name="descriptor">The authorization descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the authorization. /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the authorization.
/// </returns> /// </returns>
ValueTask<object> CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default); ValueTask<object> CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default);
@ -125,18 +92,14 @@ public interface IOpenIddictAuthorizationManager
ValueTask DeleteAsync(object authorization, CancellationToken cancellationToken = default); ValueTask DeleteAsync(object authorization, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Retrieves the authorizations matching the specified parameters. /// Retrieves the authorizations matching the specified query.
/// </summary> /// </summary>
/// <param name="subject">The subject associated with the authorization, or <see langword="null"/> not to filter out specific subjects.</param> /// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <param name="client">The client associated with the authorization, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The authorization status, or <see langword="null"/> not to filter out specific authorization statuses.</param>
/// <param name="type">The authorization type, or <see langword="null"/> not to filter out specific authorization types.</param>
/// <param name="scopes">The minimal scopes associated with the authorization, or <see langword="null"/> not to filter out scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The authorizations corresponding to the criteria.</returns> /// <returns>The authorizations corresponding to the criteria.</returns>
IAsyncEnumerable<object> FindAsync( IAsyncEnumerable<object> FindAsync(
string? subject, string? client, string? status, (string? Subject, string? ApplicationId, string? Status,
string? type, ImmutableArray<string>? scopes, CancellationToken cancellationToken = default); string? Type, ImmutableArray<string>? RequiredScopes) query, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Retrieves the list of authorizations corresponding to the specified application identifier. /// Retrieves the list of authorizations corresponding to the specified application identifier.
@ -152,7 +115,7 @@ public interface IOpenIddictAuthorizationManager
/// <param name="identifier">The unique identifier associated with the authorization.</param> /// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier. /// whose result returns the authorization corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default); ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default);
@ -183,7 +146,7 @@ public interface IOpenIddictAuthorizationManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TResult>( ValueTask<TResult?> GetAsync<TResult>(
@ -198,7 +161,7 @@ public interface IOpenIddictAuthorizationManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(

16
src/OpenIddict.Abstractions/Managers/IOpenIddictResourceManager.cs

@ -28,7 +28,7 @@ public interface IOpenIddictResourceManager
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of resources in the database. /// whose result returns the number of resources in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken = default); ValueTask<long> CountAsync(CancellationToken cancellationToken = default);
@ -40,7 +40,7 @@ public interface IOpenIddictResourceManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of resources that match the specified query. /// whose result returns the number of resources that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default); ValueTask<long> CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default);
@ -54,7 +54,7 @@ public interface IOpenIddictResourceManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of resources that match the specified query. /// whose result returns the number of resources that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
@ -67,7 +67,7 @@ public interface IOpenIddictResourceManager
/// <param name="descriptor">The resource descriptor.</param> /// <param name="descriptor">The resource descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the resource. /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the resource.
/// </returns> /// </returns>
ValueTask<object> CreateAsync(OpenIddictResourceDescriptor descriptor, CancellationToken cancellationToken = default); ValueTask<object> CreateAsync(OpenIddictResourceDescriptor descriptor, CancellationToken cancellationToken = default);
@ -97,7 +97,7 @@ public interface IOpenIddictResourceManager
/// <param name="identifier">The unique identifier associated with the resource.</param> /// <param name="identifier">The unique identifier associated with the resource.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the resource corresponding to the identifier. /// whose result returns the resource corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default); ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default);
@ -108,7 +108,7 @@ public interface IOpenIddictResourceManager
/// <param name="name">The name associated with the resource.</param> /// <param name="name">The name associated with the resource.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the resource corresponding to the specified name. /// whose result returns the resource corresponding to the specified name.
/// </returns> /// </returns>
ValueTask<object?> FindByNameAsync(string name, CancellationToken cancellationToken = default); ValueTask<object?> FindByNameAsync(string name, CancellationToken cancellationToken = default);
@ -128,7 +128,7 @@ public interface IOpenIddictResourceManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TResult>( ValueTask<TResult?> GetAsync<TResult>(
@ -143,7 +143,7 @@ public interface IOpenIddictResourceManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(

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

@ -28,7 +28,7 @@ public interface IOpenIddictScopeManager
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes in the database. /// whose result returns the number of scopes in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken = default); ValueTask<long> CountAsync(CancellationToken cancellationToken = default);
@ -40,7 +40,7 @@ public interface IOpenIddictScopeManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes that match the specified query. /// whose result returns the number of scopes that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default); ValueTask<long> CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default);
@ -54,7 +54,7 @@ public interface IOpenIddictScopeManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes that match the specified query. /// whose result returns the number of scopes that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
@ -67,7 +67,7 @@ public interface IOpenIddictScopeManager
/// <param name="descriptor">The scope descriptor.</param> /// <param name="descriptor">The scope descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the scope. /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the scope.
/// </returns> /// </returns>
ValueTask<object> CreateAsync(OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default); ValueTask<object> CreateAsync(OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default);
@ -97,7 +97,7 @@ public interface IOpenIddictScopeManager
/// <param name="identifier">The unique identifier associated with the scope.</param> /// <param name="identifier">The unique identifier associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the identifier. /// whose result returns the scope corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default); ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default);
@ -108,7 +108,7 @@ public interface IOpenIddictScopeManager
/// <param name="name">The name associated with the scope.</param> /// <param name="name">The name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the specified name. /// whose result returns the scope corresponding to the specified name.
/// </returns> /// </returns>
ValueTask<object?> FindByNameAsync(string name, CancellationToken cancellationToken = default); ValueTask<object?> FindByNameAsync(string name, CancellationToken cancellationToken = default);
@ -136,7 +136,7 @@ public interface IOpenIddictScopeManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TResult>( ValueTask<TResult?> GetAsync<TResult>(
@ -151,7 +151,7 @@ public interface IOpenIddictScopeManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(

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

@ -0,0 +1,346 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
namespace OpenIddict.Abstractions;
/// <summary>
/// Provides methods allowing to manage the Sessions stored in the store.
/// </summary>
/// <remarks>
/// Note: this interface is not meant to be implemented by custom managers,
/// that should inherit from the generic OpenIddictSessionManager class.
/// It is primarily intended to be used by services that cannot easily
/// depend on the generic session manager. The actual session entity type is
/// automatically determined at runtime based on the OpenIddict core options.
/// </remarks>
public interface IOpenIddictSessionManager
{
/// <summary>
/// Determines the number of sessions that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of sessions in the database.
/// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Determines the number of sessions that match the specified query.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of sessions that match the specified query.
/// </returns>
ValueTask<long> CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default);
/// <summary>
/// Determines the number of sessions that match the specified query.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of sessions that match the specified query.
/// </returns>
ValueTask<long> CountAsync<TState, TResult>(
Func<IQueryable<object>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new session based on the specified descriptor.
/// </summary>
/// <param name="descriptor">The session descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the session.
/// </returns>
ValueTask<object> CreateAsync(OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new session.
/// </summary>
/// <param name="session">The session to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
ValueTask CreateAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Removes an existing session.
/// </summary>
/// <param name="session">The session to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
ValueTask DeleteAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the sessions matching the specified query.
/// </summary>
/// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <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,
CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified application identifier.
/// </summary>
/// <param name="identifier">The application identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified application.</returns>
IAsyncEnumerable<object> FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified authorization identifier.
/// </summary>
/// <param name="identifier">The authorization identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified authorization.</returns>
IAsyncEnumerable<object> FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified login identifier.
/// </summary>
/// <param name="identifier">The login identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified login identifier.</returns>
IAsyncEnumerable<object> FindByLoginIdAsync(string identifier, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves a session using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the session corresponding to the identifier.
/// </returns>
ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified subject.</returns>
IAsyncEnumerable<object> FindBySubjectAsync(string subject, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the optional application identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the session.
/// </returns>
ValueTask<string?> GetApplicationIdAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
ValueTask<TResult?> GetAsync<TResult>(
Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>(
Func<IQueryable<object>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the optional authorization identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization identifier associated with the session.
/// </returns>
ValueTask<string?> GetAuthorizationIdAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the creation date associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified session.
/// </returns>
ValueTask<DateTimeOffset?> GetCreationDateAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the unique identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the session.
/// </returns>
ValueTask<string?> GetIdAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the login identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the login identifier associated with the specified session.
/// </returns>
ValueTask<string?> GetLoginIdAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the additional properties associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the session.
/// </returns>
ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the status associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified session.
/// </returns>
ValueTask<string?> GetStatusAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the subject associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified session.
/// </returns>
ValueTask<string?> GetSubjectAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>All the elements returned when executing the specified query.</returns>
IAsyncEnumerable<object> ListAsync(
int? count = null, int? offset = null, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>All the elements returned when executing the specified query.</returns>
IAsyncEnumerable<TResult> ListAsync<TResult>(
Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>All the elements returned when executing the specified query.</returns>
IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
Func<IQueryable<object>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken = default);
/// <summary>
/// Populates the specified descriptor using the properties exposed by the session.
/// </summary>
/// <param name="descriptor">The descriptor.</param>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
ValueTask PopulateAsync(OpenIddictSessionDescriptor descriptor, object session, CancellationToken cancellationToken = default);
/// <summary>
/// Populates the session using the specified descriptor.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="descriptor">The descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
ValueTask PopulateAsync(object session, OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken = default);
/// <summary>
/// Updates an existing session.
/// </summary>
/// <param name="session">The session to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
ValueTask UpdateAsync(object session, CancellationToken cancellationToken = default);
/// <summary>
/// Updates an existing session.
/// </summary>
/// <param name="session">The session to update.</param>
/// <param name="descriptor">The descriptor used to update the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
ValueTask UpdateAsync(object session, OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken = default);
/// <summary>
/// Validates the session to ensure it's in a consistent state.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The validation error encountered when validating the session.</returns>
IAsyncEnumerable<ValidationResult> ValidateAsync(object session, CancellationToken cancellationToken = default);
}

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

@ -27,7 +27,7 @@ public interface IOpenIddictTokenManager
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens in the database. /// whose result returns the number of tokens in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken = default); ValueTask<long> CountAsync(CancellationToken cancellationToken = default);
@ -39,7 +39,7 @@ public interface IOpenIddictTokenManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens that match the specified query. /// whose result returns the number of tokens that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default); ValueTask<long> CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken = default);
@ -53,7 +53,7 @@ public interface IOpenIddictTokenManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens that match the specified query. /// whose result returns the number of tokens that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
@ -66,7 +66,7 @@ public interface IOpenIddictTokenManager
/// <param name="descriptor">The token descriptor.</param> /// <param name="descriptor">The token descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the token. /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns> /// </returns>
ValueTask<object> CreateAsync(OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default); ValueTask<object> CreateAsync(OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default);
@ -91,17 +91,14 @@ public interface IOpenIddictTokenManager
ValueTask DeleteAsync(object token, CancellationToken cancellationToken = default); ValueTask DeleteAsync(object token, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Retrieves the tokens matching the specified parameters. /// Retrieves the tokens matching the specified query.
/// </summary> /// </summary>
/// <param name="subject">The subject associated with the token, or <see langword="null"/> not to filter out specific subjects.</param> /// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <param name="client">The client associated with the token, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The token status, or <see langword="null"/> not to filter out specific token statuses.</param>
/// <param name="type">The token type, or <see langword="null"/> not to filter out specific token types.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The tokens corresponding to the criteria.</returns> /// <returns>The tokens corresponding to the criteria.</returns>
IAsyncEnumerable<object> FindAsync( IAsyncEnumerable<object> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status, string? Type) query,
string? status, string? type, CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier. /// Retrieves the list of tokens corresponding to the specified application identifier.
@ -125,19 +122,21 @@ public interface IOpenIddictTokenManager
/// <param name="identifier">The unique identifier associated with the token.</param> /// <param name="identifier">The unique identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier. /// whose result returns the token corresponding to the unique identifier.
/// </returns> /// </returns>
ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default); ValueTask<object?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified reference identifier. /// Retrieves a token using its unique reference identifier.
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks>
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </remarks>
/// <param name="identifier">The reference identifier associated with the tokens.</param> /// <param name="identifier">The reference identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified reference identifier. /// whose result returns the tokens corresponding to the specified reference identifier.
/// </returns> /// </returns>
ValueTask<object?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken = default); ValueTask<object?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken = default);
@ -168,7 +167,7 @@ public interface IOpenIddictTokenManager
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TResult>( ValueTask<TResult?> GetAsync<TResult>(
@ -183,7 +182,7 @@ public interface IOpenIddictTokenManager
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(

2
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -106,6 +106,7 @@ public static class OpenIddictConstants
public const string RequestForgeryProtection = "rfp"; public const string RequestForgeryProtection = "rfp";
public const string Role = "role"; public const string Role = "role";
public const string Scope = "scope"; public const string Scope = "scope";
public const string SessionId = "sid";
public const string StreetAddress = "street_address"; public const string StreetAddress = "street_address";
public const string Subject = "sub"; public const string Subject = "sub";
public const string TargetLinkUri = "target_link_uri"; public const string TargetLinkUri = "target_link_uri";
@ -154,6 +155,7 @@ public static class OpenIddictConstants
public const string RequestTokenType = "oi_reqt_typ"; public const string RequestTokenType = "oi_reqt_typ";
public const string Resource = "oi_rsrc"; public const string Resource = "oi_rsrc";
public const string ResponseType = "oi_rsp_typ"; public const string ResponseType = "oi_rsp_typ";
public const string SessionId = "oi_ses_id";
public const string SigningAlgorithm = "oi_sign_alg"; public const string SigningAlgorithm = "oi_sign_alg";
public const string Scope = "oi_scp"; public const string Scope = "oi_scp";
public const string StateTokenLifetime = "oi_stet_lft"; public const string StateTokenLifetime = "oi_stet_lft";

10
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -867,16 +867,13 @@ Make sure that the entity is not abstract and has a public parameterless constru
<value>An error occurred while pruning authorizations.</value> <value>An error occurred while pruning authorizations.</value>
</data> </data>
<data name="ID0244" xml:space="preserve"> <data name="ID0244" xml:space="preserve">
<value>The application associated with the authorization cannot be found.</value> <value>The application matching the specified identifier cannot be found in the change tracker or in the database.</value>
</data> </data>
<data name="ID0249" xml:space="preserve"> <data name="ID0249" xml:space="preserve">
<value>An error occurred while pruning tokens.</value> <value>An error occurred while pruning tokens.</value>
</data> </data>
<data name="ID0250" xml:space="preserve">
<value>The application associated with the token cannot be found.</value>
</data>
<data name="ID0251" xml:space="preserve"> <data name="ID0251" xml:space="preserve">
<value>The authorization associated with the token cannot be found.</value> <value>The authorization matching the specified identifier cannot be found in the change tracker or in the database.</value>
</data> </data>
<data name="ID0253" xml:space="preserve"> <data name="ID0253" xml:space="preserve">
<value>No Entity Framework Core context was configured to be used with OpenIddict. <value>No Entity Framework Core context was configured to be used with OpenIddict.
@ -2448,6 +2445,9 @@ To use a custom policy relying on the system store, set 'OpenIddictServerOptions
<data name="ID2208" xml:space="preserve"> <data name="ID2208" xml:space="preserve">
<value>A resource with the same name already exists.</value> <value>A resource with the same name already exists.</value>
</data> </data>
<data name="ID2209" xml:space="preserve">
<value>The login identifier cannot be null or empty and must match the value used to represent the user session.</value>
</data>
<data name="ID4000" xml:space="preserve"> <data name="ID4000" xml:space="preserve">
<value>The '{0}' parameter shouldn't be null or empty at this point.</value> <value>The '{0}' parameter shouldn't be null or empty at this point.</value>
</data> </data>

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

@ -2692,6 +2692,22 @@ public static class OpenIddictExtensions
public static string? GetAuthorizationId(this ClaimsPrincipal principal) public static string? GetAuthorizationId(this ClaimsPrincipal principal)
=> principal.GetClaim(Claims.Private.AuthorizationId); => principal.GetClaim(Claims.Private.AuthorizationId);
/// <summary>
/// Gets the internal session identifier associated with the claims identity.
/// </summary>
/// <param name="identity">The claims identity.</param>
/// <returns>The unique identifier or <see langword="null"/> if the claim cannot be found.</returns>
public static string? GetSessionId(this ClaimsIdentity identity)
=> identity.GetClaim(Claims.Private.SessionId);
/// <summary>
/// Gets the internal session identifier associated with the claims principal.
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <returns>The unique identifier or <see langword="null"/> if the claim cannot be found.</returns>
public static string? GetSessionId(this ClaimsPrincipal principal)
=> principal.GetClaim(Claims.Private.SessionId);
/// <summary> /// <summary>
/// Gets the internal token identifier associated with the claims identity. /// Gets the internal token identifier associated with the claims identity.
/// </summary> /// </summary>
@ -3344,6 +3360,24 @@ public static class OpenIddictExtensions
public static ClaimsPrincipal SetAuthorizationId(this ClaimsPrincipal principal, string? identifier) public static ClaimsPrincipal SetAuthorizationId(this ClaimsPrincipal principal, string? identifier)
=> principal.SetClaim(Claims.Private.AuthorizationId, identifier); => principal.SetClaim(Claims.Private.AuthorizationId, identifier);
/// <summary>
/// Sets the internal session identifier associated with the claims identity.
/// </summary>
/// <param name="identity">The claims identity.</param>
/// <param name="identifier">The unique identifier to store.</param>
/// <returns>The claims identity.</returns>
public static ClaimsIdentity SetSessionId(this ClaimsIdentity identity, string? identifier)
=> identity.SetClaim(Claims.Private.SessionId, identifier);
/// <summary>
/// Sets the internal session identifier associated with the claims principal.
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <param name="identifier">The unique identifier to store.</param>
/// <returns>The claims principal.</returns>
public static ClaimsPrincipal SetSessionId(this ClaimsPrincipal principal, string? identifier)
=> principal.SetClaim(Claims.Private.SessionId, identifier);
/// <summary> /// <summary>
/// Sets the internal token identifier associated with the claims identity. /// Sets the internal token identifier associated with the claims identity.
/// </summary> /// </summary>

10
src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs

@ -23,7 +23,7 @@ public interface IOpenIddictApplicationStore<TApplication> where TApplication :
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database. /// whose result returns the number of applications in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken); ValueTask<long> CountAsync(CancellationToken cancellationToken);
@ -37,7 +37,7 @@ public interface IOpenIddictApplicationStore<TApplication> where TApplication :
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications that match the specified query. /// whose result returns the number of applications that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
@ -66,7 +66,7 @@ public interface IOpenIddictApplicationStore<TApplication> where TApplication :
/// <param name="identifier">The unique identifier associated with the application.</param> /// <param name="identifier">The unique identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier. /// whose result returns the client application corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<TApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken); ValueTask<TApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken);
@ -77,7 +77,7 @@ public interface IOpenIddictApplicationStore<TApplication> where TApplication :
/// <param name="identifier">The client identifier associated with the application.</param> /// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier. /// whose result returns the client application corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<TApplication?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken); ValueTask<TApplication?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken);
@ -120,7 +120,7 @@ public interface IOpenIddictApplicationStore<TApplication> where TApplication :
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(

21
src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs

@ -20,7 +20,7 @@ public interface IOpenIddictAuthorizationStore<TAuthorization> where TAuthorizat
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations in the database. /// whose result returns the number of authorizations in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken); ValueTask<long> CountAsync(CancellationToken cancellationToken);
@ -34,7 +34,7 @@ public interface IOpenIddictAuthorizationStore<TAuthorization> where TAuthorizat
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations that match the specified query. /// whose result returns the number of authorizations that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
@ -58,19 +58,14 @@ public interface IOpenIddictAuthorizationStore<TAuthorization> where TAuthorizat
ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken); ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the authorizations matching the specified parameters. /// Retrieves the authorizations matching the specified query.
/// </summary> /// </summary>
/// <param name="subject">The subject associated with the authorization, or <see langword="null"/> not to filter out specific subjects.</param> /// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <param name="client">The client associated with the authorization, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The authorization status, or <see langword="null"/> not to filter out specific authorization statuses.</param>
/// <param name="type">The authorization type, or <see langword="null"/> not to filter out specific authorization types.</param>
/// <param name="scopes">The minimal scopes associated with the authorization, or <see langword="null"/> not to filter out scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The authorizations corresponding to the criteria.</returns> /// <returns>The authorizations corresponding to the criteria.</returns>
IAsyncEnumerable<TAuthorization> FindAsync( IAsyncEnumerable<TAuthorization> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status,
string? status, string? type, string? Type, ImmutableArray<string>? RequiredScopes) query, CancellationToken cancellationToken);
ImmutableArray<string>? scopes, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the list of authorizations corresponding to the specified application identifier. /// Retrieves the list of authorizations corresponding to the specified application identifier.
@ -86,7 +81,7 @@ public interface IOpenIddictAuthorizationStore<TAuthorization> where TAuthorizat
/// <param name="identifier">The unique identifier associated with the authorization.</param> /// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier. /// whose result returns the authorization corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<TAuthorization?> FindByIdAsync(string identifier, CancellationToken cancellationToken); ValueTask<TAuthorization?> FindByIdAsync(string identifier, CancellationToken cancellationToken);
@ -119,7 +114,7 @@ public interface IOpenIddictAuthorizationStore<TAuthorization> where TAuthorizat
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(

10
src/OpenIddict.Abstractions/Stores/IOpenIddictResourceStore.cs

@ -21,7 +21,7 @@ public interface IOpenIddictResourceStore<TResource> where TResource : class
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of resources in the database. /// whose result returns the number of resources in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken); ValueTask<long> CountAsync(CancellationToken cancellationToken);
@ -35,7 +35,7 @@ public interface IOpenIddictResourceStore<TResource> where TResource : class
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of resources that match the specified query. /// whose result returns the number of resources that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
@ -64,7 +64,7 @@ public interface IOpenIddictResourceStore<TResource> where TResource : class
/// <param name="identifier">The unique identifier associated with the resource.</param> /// <param name="identifier">The unique identifier associated with the resource.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the resource corresponding to the identifier. /// whose result returns the resource corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<TResource?> FindByIdAsync(string identifier, CancellationToken cancellationToken); ValueTask<TResource?> FindByIdAsync(string identifier, CancellationToken cancellationToken);
@ -75,7 +75,7 @@ public interface IOpenIddictResourceStore<TResource> where TResource : class
/// <param name="name">The name associated with the resource.</param> /// <param name="name">The name associated with the resource.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the resource corresponding to the specified name. /// whose result returns the resource corresponding to the specified name.
/// </returns> /// </returns>
ValueTask<TResource?> FindByNameAsync(string name, CancellationToken cancellationToken); ValueTask<TResource?> FindByNameAsync(string name, CancellationToken cancellationToken);
@ -97,7 +97,7 @@ public interface IOpenIddictResourceStore<TResource> where TResource : class
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(

10
src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs

@ -21,7 +21,7 @@ public interface IOpenIddictScopeStore<TScope> where TScope : class
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes in the database. /// whose result returns the number of scopes in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken); ValueTask<long> CountAsync(CancellationToken cancellationToken);
@ -35,7 +35,7 @@ public interface IOpenIddictScopeStore<TScope> where TScope : class
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes that match the specified query. /// whose result returns the number of scopes that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
@ -64,7 +64,7 @@ public interface IOpenIddictScopeStore<TScope> where TScope : class
/// <param name="identifier">The unique identifier associated with the scope.</param> /// <param name="identifier">The unique identifier associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the identifier. /// whose result returns the scope corresponding to the identifier.
/// </returns> /// </returns>
ValueTask<TScope?> FindByIdAsync(string identifier, CancellationToken cancellationToken); ValueTask<TScope?> FindByIdAsync(string identifier, CancellationToken cancellationToken);
@ -75,7 +75,7 @@ public interface IOpenIddictScopeStore<TScope> where TScope : class
/// <param name="name">The name associated with the scope.</param> /// <param name="name">The name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the specified name. /// whose result returns the scope corresponding to the specified name.
/// </returns> /// </returns>
ValueTask<TScope?> FindByNameAsync(string name, CancellationToken cancellationToken); ValueTask<TScope?> FindByNameAsync(string name, CancellationToken cancellationToken);
@ -105,7 +105,7 @@ public interface IOpenIddictScopeStore<TScope> where TScope : class
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(

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

@ -0,0 +1,319 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.Text.Json;
namespace OpenIddict.Abstractions;
/// <summary>
/// Provides methods allowing to manage the sessions stored in a database.
/// </summary>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
public interface IOpenIddictSessionStore<TSession> where TSession : class
{
/// <summary>
/// Determines the number of sessions that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of sessions in the database.
/// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken);
/// <summary>
/// Determines the number of sessions that match the specified query.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of sessions that match the specified query.
/// </returns>
ValueTask<long> CountAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken);
/// <summary>
/// Creates a new session.
/// </summary>
/// <param name="session">The session to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask CreateAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Removes an existing session.
/// </summary>
/// <param name="session">The session to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask DeleteAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the sessions matching the specified query.
/// </summary>
/// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <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);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified application identifier.
/// </summary>
/// <param name="identifier">The application identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified application.</returns>
IAsyncEnumerable<TSession> FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified authorization identifier.
/// </summary>
/// <param name="identifier">The authorization identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified authorization.</returns>
IAsyncEnumerable<TSession> FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified login identifier.
/// </summary>
/// <param name="identifier">The login identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified login identifier.</returns>
IAsyncEnumerable<TSession> FindByLoginIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves a session using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the session corresponding to the identifier.
/// </returns>
ValueTask<TSession?> FindByIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified subject.</returns>
IAsyncEnumerable<TSession> FindBySubjectAsync(string subject, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the optional application identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the session.
/// </returns>
ValueTask<string?> GetApplicationIdAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the optional authorization identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization identifier associated with the session.
/// </returns>
ValueTask<string?> GetAuthorizationIdAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the creation date associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified session.
/// </returns>
ValueTask<DateTimeOffset?> GetCreationDateAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the unique identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the session.
/// </returns>
ValueTask<string?> GetIdAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the login identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the login identifier associated with the specified session.
/// </returns>
ValueTask<string?> GetLoginIdAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the additional properties associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose
/// result returns all the additional properties associated with the session.
/// </returns>
ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the status associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified session.
/// </returns>
ValueTask<string?> GetStatusAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the subject associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified session.
/// </returns>
ValueTask<string?> GetSubjectAsync(TSession session, CancellationToken cancellationToken);
/// <summary>
/// Instantiates a new session.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated session, that can be persisted in the database.
/// </returns>
ValueTask<TSession> InstantiateAsync(CancellationToken cancellationToken);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>All the elements returned when executing the specified query.</returns>
IAsyncEnumerable<TSession> ListAsync(int? count, int? offset, CancellationToken cancellationToken);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>All the elements returned when executing the specified query.</returns>
IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken);
/// <summary>
/// Sets the application identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="identifier">The unique identifier associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetApplicationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken);
/// <summary>
/// Sets the authorization identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="identifier">The unique identifier associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetAuthorizationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken);
/// <summary>
/// Sets the creation date associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="date">The creation date.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetCreationDateAsync(TSession session, DateTimeOffset? date, CancellationToken cancellationToken);
/// <summary>
/// Sets the login identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="identifier">The login identifier associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetLoginIdAsync(TSession session, string? identifier, CancellationToken cancellationToken);
/// <summary>
/// Sets the additional properties associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="properties">The additional properties associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetPropertiesAsync(TSession session,
ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken);
/// <summary>
/// Sets the status associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="status">The status associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetStatusAsync(TSession session, string? status, CancellationToken cancellationToken);
/// <summary>
/// Sets the subject associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="subject">The subject associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask SetSubjectAsync(TSession session, string? subject, CancellationToken cancellationToken);
/// <summary>
/// Updates an existing session.
/// </summary>
/// <param name="session">The session to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
ValueTask UpdateAsync(TSession session, CancellationToken cancellationToken);
}

26
src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs

@ -20,7 +20,7 @@ public interface IOpenIddictTokenStore<TToken> where TToken : class
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database. /// whose result returns the number of applications in the database.
/// </returns> /// </returns>
ValueTask<long> CountAsync(CancellationToken cancellationToken); ValueTask<long> CountAsync(CancellationToken cancellationToken);
@ -34,7 +34,7 @@ public interface IOpenIddictTokenStore<TToken> where TToken : class
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens that match the specified query. /// whose result returns the number of tokens that match the specified query.
/// </returns> /// </returns>
ValueTask<long> CountAsync<TState, TResult>( ValueTask<long> CountAsync<TState, TResult>(
@ -58,17 +58,13 @@ public interface IOpenIddictTokenStore<TToken> where TToken : class
ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken); ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the tokens matching the specified parameters. /// Retrieves the tokens matching the specified query.
/// </summary> /// </summary>
/// <param name="subject">The subject associated with the token, or <see langword="null"/> not to filter out specific subjects.</param> /// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <param name="client">The client associated with the token, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The token status, or <see langword="null"/> not to filter out specific token statuses.</param>
/// <param name="type">The token type, or <see langword="null"/> not to filter out specific token types.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The tokens corresponding to the criteria.</returns> /// <returns>The tokens corresponding to the criteria.</returns>
IAsyncEnumerable<TToken> FindAsync( IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status, string? Type) query, CancellationToken cancellationToken);
string? status, string? type, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier. /// Retrieves the list of tokens corresponding to the specified application identifier.
@ -92,19 +88,21 @@ public interface IOpenIddictTokenStore<TToken> where TToken : class
/// <param name="identifier">The unique identifier associated with the token.</param> /// <param name="identifier">The unique identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier. /// whose result returns the token corresponding to the unique identifier.
/// </returns> /// </returns>
ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken); ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified reference identifier. /// Retrieves a token using its unique reference identifier.
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks>
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </remarks>
/// <param name="identifier">The reference identifier associated with the tokens.</param> /// <param name="identifier">The reference identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified reference identifier. /// whose result returns the tokens corresponding to the specified reference identifier.
/// </returns> /// </returns>
ValueTask<TToken?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken); ValueTask<TToken?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken);
@ -137,7 +135,7 @@ public interface IOpenIddictTokenStore<TToken> where TToken : class
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
ValueTask<TResult?> GetAsync<TState, TResult>( ValueTask<TResult?> GetAsync<TState, TResult>(

2
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionFormatter.cs

@ -206,7 +206,7 @@ public sealed class OpenIddictClientDataProtectionFormatter : IOpenIddictClientD
SetArrayProperty(properties, Properties.Scopes, principal.GetScopes()); SetArrayProperty(properties, Properties.Scopes, principal.GetScopes());
// Copy the principal and exclude the claim that were mapped to authentication properties. // Copy the principal and exclude the claim that were mapped to authentication properties.
principal = principal.Clone(claim => claim.Type is not ( principal = principal.Clone(static claim => claim.Type is not (
Claims.Private.Audience or Claims.Private.Audience or
Claims.Private.CodeVerifier or Claims.Private.CodeVerifier or
Claims.Private.CreationDate or Claims.Private.CreationDate or

2
src/OpenIddict.Client/IOpenIddictClientHandlerFilter.cs

@ -19,7 +19,7 @@ public interface IOpenIddictClientHandlerFilter<in TContext> where TContext : Ba
/// </summary> /// </summary>
/// <param name="context">The context associated with the event to process.</param> /// <param name="context">The context associated with the event to process.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose boolean result determines whether the handler will be invoked or not. /// whose boolean result determines whether the handler will be invoked or not.
/// </returns> /// </returns>
ValueTask<bool> IsActiveAsync(TContext context); ValueTask<bool> IsActiveAsync(TContext context);

6
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -1752,7 +1752,8 @@ public static partial class OpenIddictClientHandlers
{ {
// The following claims MUST be represented as unique strings. // The following claims MUST be represented as unique strings.
Claims.AuthenticationContextReference or Claims.AuthorizedParty or Claims.AuthenticationContextReference or Claims.AuthorizedParty or
Claims.Issuer or Claims.Nonce or Claims.Subject Claims.Issuer or Claims.Nonce or
Claims.SessionId or Claims.Subject
=> values is [{ ValueType: ClaimValueTypes.String }], => values is [{ ValueType: ClaimValueTypes.String }],
// The following claims MUST be represented as unique strings or array of strings. // The following claims MUST be represented as unique strings or array of strings.
@ -3488,7 +3489,8 @@ public static partial class OpenIddictClientHandlers
{ {
// The following claims MUST be represented as unique strings. // The following claims MUST be represented as unique strings.
Claims.AuthenticationContextReference or Claims.AuthorizedParty or Claims.AuthenticationContextReference or Claims.AuthorizedParty or
Claims.Issuer or Claims.Nonce or Claims.Subject Claims.Issuer or Claims.Nonce or
Claims.SessionId or Claims.Subject
=> values is [{ ValueType: ClaimValueTypes.String }], => values is [{ ValueType: ClaimValueTypes.String }],
// The following claims MUST be represented as unique strings or array of strings. // The following claims MUST be represented as unique strings or array of strings.

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

@ -312,7 +312,7 @@ public sealed class OpenIddictApplicationCache<TApplication> : IOpenIddictApplic
/// <param name="application">The application associated with the expiration signal.</param> /// <param name="application">The application associated with the expiration signal.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns an expiration signal for the specified application. /// whose result returns an expiration signal for the specified application.
/// </returns> /// </returns>
private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TApplication application, CancellationToken cancellationToken) private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TApplication application, CancellationToken cancellationToken)

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

@ -84,13 +84,12 @@ public sealed class OpenIddictAuthorizationCache<TAuthorization> : IOpenIddictAu
/// <inheritdoc/> /// <inheritdoc/>
public async IAsyncEnumerable<TAuthorization> FindAsync( public async IAsyncEnumerable<TAuthorization> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status,
string? status, string? type, string? Type, ImmutableArray<string>? RequiredScopes) query, [EnumeratorCancellation] CancellationToken cancellationToken)
ImmutableArray<string>? scopes, [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
// Note: this method is only partially cached. // Note: this method is only partially cached.
await foreach (var authorization in _store.FindAsync(subject, client, status, type, scopes, cancellationToken)) await foreach (var authorization in _store.FindAsync(query, cancellationToken))
{ {
await AddAsync(authorization, cancellationToken); await AddAsync(authorization, cancellationToken);
@ -276,7 +275,7 @@ public sealed class OpenIddictAuthorizationCache<TAuthorization> : IOpenIddictAu
/// <param name="authorization">The authorization associated with the expiration signal.</param> /// <param name="authorization">The authorization associated with the expiration signal.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns an expiration signal for the specified authorization. /// whose result returns an expiration signal for the specified authorization.
/// </returns> /// </returns>
private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TAuthorization authorization, CancellationToken cancellationToken) private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TAuthorization authorization, CancellationToken cancellationToken)

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

@ -238,7 +238,7 @@ public sealed class OpenIddictResourceCache<TResource> : IOpenIddictResourceCach
/// <param name="resource">The resource associated with the expiration signal.</param> /// <param name="resource">The resource associated with the expiration signal.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns an expiration signal for the specified resource. /// whose result returns an expiration signal for the specified resource.
/// </returns> /// </returns>
private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TResource resource, CancellationToken cancellationToken) private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TResource resource, CancellationToken cancellationToken)

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

@ -285,7 +285,7 @@ public sealed class OpenIddictScopeCache<TScope> : IOpenIddictScopeCache<TScope>
/// <param name="scope">The scope associated with the expiration signal.</param> /// <param name="scope">The scope associated with the expiration signal.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns an expiration signal for the specified scope. /// whose result returns an expiration signal for the specified scope.
/// </returns> /// </returns>
private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TScope scope, CancellationToken cancellationToken) private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TScope scope, CancellationToken cancellationToken)

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

@ -0,0 +1,383 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace OpenIddict.Core;
/// <summary>
/// Provides methods allowing to cache sessions after retrieving them from the store.
/// </summary>
/// <typeparam name="TSession">The type of the Session entity.</typeparam>
public sealed class OpenIddictSessionCache<TSession> : IOpenIddictSessionCache<TSession>, IDisposable where TSession : class
{
private readonly MemoryCache _cache;
private readonly ConcurrentDictionary<string, CancellationTokenSource> _signals;
private readonly IOpenIddictSessionStore<TSession> _store;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictSessionCache{TSession}"/> class.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictSessionCache(
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictSessionStore<TSession> store)
{
_cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = (options ?? throw new ArgumentNullException(nameof(options))).CurrentValue.EntityCacheLimit
});
_signals = new ConcurrentDictionary<string, CancellationTokenSource>(StringComparer.Ordinal);
_store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <inheritdoc/>
public async ValueTask AddAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
_cache.Remove(new
{
Method = nameof(FindByApplicationIdAsync),
Identifier = await _store.GetApplicationIdAsync(session, cancellationToken)
});
_cache.Remove(new
{
Method = nameof(FindByAuthorizationIdAsync),
Identifier = await _store.GetAuthorizationIdAsync(session, cancellationToken)
});
_cache.Remove(new
{
Method = nameof(FindByIdAsync),
Identifier = await _store.GetIdAsync(session, cancellationToken)
});
_cache.Remove(new
{
Method = nameof(FindByLoginIdAsync),
Identifier = await _store.GetLoginIdAsync(session, cancellationToken)
});
_cache.Remove(new
{
Method = nameof(FindBySubjectAsync),
Subject = await _store.GetSubjectAsync(session, cancellationToken)
});
await CreateEntryAsync(new
{
Method = nameof(FindByIdAsync),
Identifier = await _store.GetIdAsync(session, cancellationToken)
}, session, cancellationToken);
}
/// <inheritdoc/>
public void Dispose()
{
foreach (var signal in _signals)
{
signal.Value.Dispose();
}
_cache.Dispose();
}
/// <inheritdoc/>
public async IAsyncEnumerable<TSession> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
// Note: this method is only partially cached.
await foreach (var session in _store.FindAsync(query, cancellationToken))
{
await AddAsync(session, cancellationToken);
yield return session;
}
}
/// <inheritdoc/>
public IAsyncEnumerable<TSession> FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var parameters = new
{
Method = nameof(FindByApplicationIdAsync),
Identifier = identifier
};
if (!_cache.TryGetValue(parameters, out ImmutableArray<TSession> sessions))
{
var builder = ImmutableArray.CreateBuilder<TSession>();
await foreach (var session in _store.FindByApplicationIdAsync(identifier, cancellationToken))
{
builder.Add(session);
await AddAsync(session, cancellationToken);
}
sessions = builder.ToImmutable();
await CreateEntryAsync(parameters, sessions, cancellationToken);
}
foreach (var session in sessions)
{
yield return session;
}
}
}
/// <inheritdoc/>
public IAsyncEnumerable<TSession> FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var parameters = new
{
Method = nameof(FindByAuthorizationIdAsync),
Identifier = identifier
};
if (!_cache.TryGetValue(parameters, out ImmutableArray<TSession> sessions))
{
var builder = ImmutableArray.CreateBuilder<TSession>();
await foreach (var session in _store.FindByAuthorizationIdAsync(identifier, cancellationToken))
{
builder.Add(session);
await AddAsync(session, cancellationToken);
}
sessions = builder.ToImmutable();
await CreateEntryAsync(parameters, sessions, cancellationToken);
}
foreach (var session in sessions)
{
yield return session;
}
}
}
/// <inheritdoc/>
public ValueTask<TSession?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
var parameters = new
{
Method = nameof(FindByIdAsync),
Identifier = identifier
};
if (_cache.TryGetValue(parameters, out TSession? session))
{
return new(session);
}
return new(ExecuteAsync());
async Task<TSession?> ExecuteAsync()
{
if ((session = await _store.FindByIdAsync(identifier, cancellationToken)) is not null)
{
await AddAsync(session, cancellationToken);
}
await CreateEntryAsync(parameters, session, cancellationToken);
return session;
}
}
/// <inheritdoc/>
public IAsyncEnumerable<TSession> FindByLoginIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var parameters = new
{
Method = nameof(FindByLoginIdAsync),
Identifier = identifier
};
if (!_cache.TryGetValue(parameters, out ImmutableArray<TSession> sessions))
{
var builder = ImmutableArray.CreateBuilder<TSession>();
await foreach (var session in _store.FindByLoginIdAsync(identifier, cancellationToken))
{
builder.Add(session);
await AddAsync(session, cancellationToken);
}
sessions = builder.ToImmutable();
await CreateEntryAsync(parameters, sessions, cancellationToken);
}
foreach (var session in sessions)
{
yield return session;
}
}
}
/// <inheritdoc/>
public IAsyncEnumerable<TSession> FindBySubjectAsync(string subject, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(subject);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var parameters = new
{
Method = nameof(FindBySubjectAsync),
Identifier = subject
};
if (!_cache.TryGetValue(parameters, out ImmutableArray<TSession> sessions))
{
var builder = ImmutableArray.CreateBuilder<TSession>();
await foreach (var session in _store.FindBySubjectAsync(subject, cancellationToken))
{
builder.Add(session);
await AddAsync(session, cancellationToken);
}
sessions = builder.ToImmutable();
await CreateEntryAsync(parameters, sessions, cancellationToken);
}
foreach (var session in sessions)
{
yield return session;
}
}
}
/// <inheritdoc/>
public async ValueTask RemoveAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var identifier = await _store.GetIdAsync(session, cancellationToken);
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0196));
}
if (_signals.TryRemove(identifier, out CancellationTokenSource? signal))
{
signal.Cancel();
signal.Dispose();
}
}
/// <summary>
/// Creates a cache entry for the specified key.
/// </summary>
/// <param name="key">The cache key.</param>
/// <param name="session">The session to store in the cache entry, if applicable.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
private async ValueTask CreateEntryAsync(object key, TSession? session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(key);
using var entry = _cache.CreateEntry(key);
if (session is not null)
{
entry.AddExpirationToken(await CreateExpirationSignalAsync(session, cancellationToken)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)));
}
entry.Size = 1L;
entry.Value = session;
}
/// <summary>
/// Creates a cache entry for the specified key.
/// </summary>
/// <param name="key">The cache key.</param>
/// <param name="sessions">The sessions to store in the cache entry.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
private async ValueTask CreateEntryAsync(object key, ImmutableArray<TSession> sessions, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(key);
using var entry = _cache.CreateEntry(key);
foreach (var session in sessions)
{
entry.AddExpirationToken(await CreateExpirationSignalAsync(session, cancellationToken)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)));
}
entry.Size = sessions.Length;
entry.Value = sessions;
}
/// <summary>
/// Creates an expiration signal allowing to invalidate all the
/// cache entries associated with the specified session.
/// </summary>
/// <param name="session">The session associated with the expiration signal.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns an expiration signal for the specified session.
/// </returns>
private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var identifier = await _store.GetIdAsync(session, cancellationToken);
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0204));
}
var signal = _signals.GetOrAdd(identifier, _ => new CancellationTokenSource());
return new CancellationChangeToken(signal.Token);
}
}

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

@ -102,12 +102,12 @@ public sealed class OpenIddictTokenCache<TToken> : IOpenIddictTokenCache<TToken>
/// <inheritdoc/> /// <inheritdoc/>
public async IAsyncEnumerable<TToken> FindAsync( public async IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status, string? Type) query,
string? status, string? type, [EnumeratorCancellation] CancellationToken cancellationToken) [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
// Note: this method is only partially cached. // Note: this method is only partially cached.
await foreach (var token in _store.FindAsync(subject, client, status, type, cancellationToken)) await foreach (var token in _store.FindAsync(query, cancellationToken))
{ {
await AddAsync(token, cancellationToken); await AddAsync(token, cancellationToken);
@ -362,7 +362,7 @@ public sealed class OpenIddictTokenCache<TToken> : IOpenIddictTokenCache<TToken>
/// <param name="token">The token associated with the expiration signal.</param> /// <param name="token">The token associated with the expiration signal.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns an expiration signal for the specified token. /// whose result returns an expiration signal for the specified token.
/// </returns> /// </returns>
private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TToken token, CancellationToken cancellationToken) private async ValueTask<IChangeToken> CreateExpirationSignalAsync(TToken token, CancellationToken cancellationToken)

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

@ -77,7 +77,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database. /// whose result returns the number of applications in the database.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default) public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default)
@ -90,7 +90,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications that match the specified query. /// whose result returns the number of applications that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TResult>( public virtual ValueTask<long> CountAsync<TResult>(
@ -110,7 +110,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications that match the specified query. /// whose result returns the number of applications that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TState, TResult>( public virtual ValueTask<long> CountAsync<TState, TResult>(
@ -238,7 +238,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="descriptor">The application descriptor.</param> /// <param name="descriptor">The application descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application. /// whose result returns the unique identifier associated with the application.
/// </returns> /// </returns>
public virtual async ValueTask<TApplication> CreateAsync( public virtual async ValueTask<TApplication> CreateAsync(
@ -291,7 +291,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="identifier">The client identifier associated with the application.</param> /// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier. /// whose result returns the client application corresponding to the identifier.
/// </returns> /// </returns>
public virtual async ValueTask<TApplication?> FindByClientIdAsync( public virtual async ValueTask<TApplication?> FindByClientIdAsync(
@ -326,7 +326,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="identifier">The unique identifier associated with the application.</param> /// <param name="identifier">The unique identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier. /// whose result returns the client application corresponding to the identifier.
/// </returns> /// </returns>
public virtual async ValueTask<TApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default) public virtual async ValueTask<TApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default)
@ -462,7 +462,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TResult>( public virtual ValueTask<TResult?> GetAsync<TResult>(
@ -482,7 +482,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TState, TResult>( public virtual ValueTask<TResult?> GetAsync<TState, TResult>(
@ -1373,7 +1373,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns> /// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client secret was valid. /// whose result returns a boolean indicating whether the client secret was valid.
/// </returns> /// </returns>
public virtual async ValueTask<bool> ValidateClientSecretAsync( public virtual async ValueTask<bool> ValidateClientSecretAsync(
@ -1437,7 +1437,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <remarks>Note: if no client_id parameter is specified in end session requests, this method may not be called.</remarks> /// <remarks>Note: if no client_id parameter is specified in end session requests, this method may not be called.</remarks>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the post_logout_redirect_uri was valid. /// whose result returns a boolean indicating whether the post_logout_redirect_uri was valid.
/// </returns> /// </returns>
public virtual async ValueTask<bool> ValidatePostLogoutRedirectUriAsync(TApplication application, public virtual async ValueTask<bool> ValidatePostLogoutRedirectUriAsync(TApplication application,
@ -1503,7 +1503,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns> /// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client certificate was valid. /// whose result returns a boolean indicating whether the client certificate was valid.
/// </returns> /// </returns>
public virtual async ValueTask<bool> ValidatePublicKeyInfrastructureTlsClientCertificateAsync( public virtual async ValueTask<bool> ValidatePublicKeyInfrastructureTlsClientCertificateAsync(
@ -1602,7 +1602,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="uri">The URI that should be compared to one of the redirect_uri stored in the database.</param> /// <param name="uri">The URI that should be compared to one of the redirect_uri stored in the database.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the redirect_uri was valid. /// whose result returns a boolean indicating whether the redirect_uri was valid.
/// </returns> /// </returns>
public virtual async ValueTask<bool> ValidateRedirectUriAsync(TApplication application, public virtual async ValueTask<bool> ValidateRedirectUriAsync(TApplication application,
@ -1670,7 +1670,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns> /// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose
/// result returns a boolean indicating whether the self-signed client certificate was valid. /// result returns a boolean indicating whether the self-signed client certificate was valid.
/// </returns> /// </returns>
public virtual async ValueTask<bool> ValidateSelfSignedTlsClientCertificateAsync( public virtual async ValueTask<bool> ValidateSelfSignedTlsClientCertificateAsync(
@ -1823,7 +1823,7 @@ public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplication
/// <param name="comparand">The value stored in the database, which is usually a hashed representation of the secret.</param> /// <param name="comparand">The value stored in the database, which is usually a hashed representation of the secret.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns
/// a tuple indicating whether the client secret was valid and whether the client secret should be re-hashed. /// a tuple indicating whether the client secret was valid and whether the client secret should be re-hashed.
/// </returns> /// </returns>
protected virtual ValueTask<(bool IsValid, bool IsRehashRequired)> ValidateClientSecretAsync( protected virtual ValueTask<(bool IsValid, bool IsRehashRequired)> ValidateClientSecretAsync(

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

@ -7,7 +7,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -71,7 +70,7 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations in the database. /// whose result returns the number of authorizations in the database.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default) public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default)
@ -84,7 +83,7 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations that match the specified query. /// whose result returns the number of authorizations that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TResult>( public virtual ValueTask<long> CountAsync<TResult>(
@ -104,7 +103,7 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations that match the specified query. /// whose result returns the number of authorizations that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TState, TResult>( public virtual ValueTask<long> CountAsync<TState, TResult>(
@ -176,7 +175,7 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
/// <param name="descriptor">The authorization descriptor.</param> /// <param name="descriptor">The authorization descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the authorization. /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the authorization.
/// </returns> /// </returns>
public virtual async ValueTask<TAuthorization> CreateAsync( public virtual async ValueTask<TAuthorization> CreateAsync(
OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default)
@ -192,60 +191,6 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
return authorization; return authorization;
} }
/// <summary>
/// Creates a new permanent authorization based on the specified parameters.
/// </summary>
/// <param name="identity">The identity associated with the authorization.</param>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="type">The authorization type.</param>
/// <param name="scopes">The minimal scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the authorization.
/// </returns>
public virtual ValueTask<TAuthorization> CreateAsync(
ClaimsIdentity identity, string subject, string client,
string type, ImmutableArray<string> scopes, CancellationToken cancellationToken = default)
=> CreateAsync(new ClaimsPrincipal(identity ?? throw new ArgumentNullException(nameof(identity))),
subject, client, type, scopes, cancellationToken);
/// <summary>
/// Creates a new permanent authorization based on the specified parameters.
/// </summary>
/// <param name="principal">The principal associated with the authorization.</param>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="type">The authorization type.</param>
/// <param name="scopes">The minimal scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the authorization.
/// </returns>
public virtual ValueTask<TAuthorization> CreateAsync(
ClaimsPrincipal principal, string subject, string client,
string type, ImmutableArray<string> scopes, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(principal);
ArgumentException.ThrowIfNullOrEmpty(subject);
ArgumentException.ThrowIfNullOrEmpty(client);
ArgumentException.ThrowIfNullOrEmpty(type);
var descriptor = new OpenIddictAuthorizationDescriptor
{
ApplicationId = client,
CreationDate = Options.CurrentValue.TimeProvider.GetUtcNow(),
Principal = principal,
Status = Statuses.Valid,
Subject = subject,
Type = type
};
descriptor.Scopes.UnionWith(scopes);
return CreateAsync(descriptor, cancellationToken);
}
/// <summary> /// <summary>
/// Removes an existing authorization. /// Removes an existing authorization.
/// </summary> /// </summary>
@ -267,23 +212,18 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
} }
/// <summary> /// <summary>
/// Retrieves the authorizations matching the specified parameters. /// Retrieves the authorizations matching the specified query.
/// </summary> /// </summary>
/// <param name="subject">The subject associated with the authorization, or <see langword="null"/> not to filter out specific subjects.</param> /// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <param name="client">The client associated with the authorization, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The authorization status, or <see langword="null"/> not to filter out specific authorization statuses.</param>
/// <param name="type">The authorization type, or <see langword="null"/> not to filter out specific authorization types.</param>
/// <param name="scopes">The minimal scopes associated with the authorization, or <see langword="null"/> not to filter out scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The authorizations corresponding to the criteria.</returns> /// <returns>The authorizations corresponding to the criteria.</returns>
public virtual IAsyncEnumerable<TAuthorization> FindAsync( public virtual IAsyncEnumerable<TAuthorization> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status,
string? status, string? type, string? Type, ImmutableArray<string>? RequiredScopes) query, CancellationToken cancellationToken = default)
ImmutableArray<string>? scopes, CancellationToken cancellationToken = default)
{ {
var authorizations = Options.CurrentValue.DisableEntityCaching var authorizations = Options.CurrentValue.DisableEntityCaching
? Store.FindAsync(subject, client, status, type, scopes, cancellationToken) ? Store.FindAsync(query, cancellationToken)
: Cache.FindAsync(subject, client, status, type, scopes, cancellationToken); : Cache.FindAsync(query, cancellationToken);
if (Options.CurrentValue.DisableAdditionalFiltering) if (Options.CurrentValue.DisableAdditionalFiltering)
{ {
@ -300,13 +240,14 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
{ {
await foreach (var authorization in authorizations) await foreach (var authorization in authorizations)
{ {
if (!string.IsNullOrEmpty(subject) && if (!string.IsNullOrEmpty(query.Subject) &&
!string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) !string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), query.Subject, StringComparison.Ordinal))
{ {
continue; continue;
} }
if (scopes is not null && !await HasScopesAsync(authorization, scopes.Value, cancellationToken)) if (query.RequiredScopes is { IsDefaultOrEmpty: false } scopes &&
!await HasScopesAsync(authorization, scopes, cancellationToken))
{ {
continue; continue;
} }
@ -360,7 +301,7 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
/// <param name="identifier">The unique identifier associated with the authorization.</param> /// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier. /// whose result returns the authorization corresponding to the identifier.
/// </returns> /// </returns>
public virtual async ValueTask<TAuthorization?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default) public virtual async ValueTask<TAuthorization?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default)
@ -450,7 +391,7 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TResult>( public virtual ValueTask<TResult?> GetAsync<TResult>(
@ -470,7 +411,7 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TState, TResult>( public virtual ValueTask<TResult?> GetAsync<TState, TResult>(
@ -977,14 +918,6 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
ValueTask<long> IOpenIddictAuthorizationManager.CountAsync<TState, TResult>(Func<IQueryable<object>, TState, IQueryable<TResult>> query, TState state, CancellationToken cancellationToken) ValueTask<long> IOpenIddictAuthorizationManager.CountAsync<TState, TResult>(Func<IQueryable<object>, TState, IQueryable<TResult>> query, TState state, CancellationToken cancellationToken)
=> CountAsync(query, state, cancellationToken); => CountAsync(query, state, cancellationToken);
/// <inheritdoc/>
async ValueTask<object> IOpenIddictAuthorizationManager.CreateAsync(ClaimsIdentity identity, string subject, string client, string type, ImmutableArray<string> scopes, CancellationToken cancellationToken)
=> await CreateAsync(identity, subject, client, type, scopes, cancellationToken);
/// <inheritdoc/>
async ValueTask<object> IOpenIddictAuthorizationManager.CreateAsync(ClaimsPrincipal principal, string subject, string client, string type, ImmutableArray<string> scopes, CancellationToken cancellationToken)
=> await CreateAsync(principal, subject, client, type, scopes, cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
async ValueTask<object> IOpenIddictAuthorizationManager.CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) async ValueTask<object> IOpenIddictAuthorizationManager.CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken)
=> await CreateAsync(descriptor, cancellationToken); => await CreateAsync(descriptor, cancellationToken);
@ -998,8 +931,8 @@ public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthori
=> DeleteAsync((TAuthorization) authorization, cancellationToken); => DeleteAsync((TAuthorization) authorization, cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictAuthorizationManager.FindAsync(string? subject, string? client, string? status, string? type, ImmutableArray<string>? scopes, CancellationToken cancellationToken) IAsyncEnumerable<object> IOpenIddictAuthorizationManager.FindAsync((string? Subject, string? ApplicationId, string? Status, string? Type, ImmutableArray<string>? RequiredScopes) query, CancellationToken cancellationToken)
=> FindAsync(subject, client, status, type, scopes, cancellationToken); => FindAsync(query, cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictAuthorizationManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) IAsyncEnumerable<object> IOpenIddictAuthorizationManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)

16
src/OpenIddict.Core/Managers/OpenIddictResourceManager.cs

@ -70,7 +70,7 @@ public class OpenIddictResourceManager<TResource> : IOpenIddictResourceManager w
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of resources in the database. /// whose result returns the number of resources in the database.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default) public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default)
@ -83,7 +83,7 @@ public class OpenIddictResourceManager<TResource> : IOpenIddictResourceManager w
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of resources that match the specified query. /// whose result returns the number of resources that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TResult>( public virtual ValueTask<long> CountAsync<TResult>(
@ -103,7 +103,7 @@ public class OpenIddictResourceManager<TResource> : IOpenIddictResourceManager w
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of resources that match the specified query. /// whose result returns the number of resources that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TState, TResult>( public virtual ValueTask<long> CountAsync<TState, TResult>(
@ -169,7 +169,7 @@ public class OpenIddictResourceManager<TResource> : IOpenIddictResourceManager w
/// <param name="descriptor">The resource descriptor.</param> /// <param name="descriptor">The resource descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the resource. /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the resource.
/// </returns> /// </returns>
public virtual async ValueTask<TResource> CreateAsync( public virtual async ValueTask<TResource> CreateAsync(
OpenIddictResourceDescriptor descriptor, CancellationToken cancellationToken = default) OpenIddictResourceDescriptor descriptor, CancellationToken cancellationToken = default)
@ -211,7 +211,7 @@ public class OpenIddictResourceManager<TResource> : IOpenIddictResourceManager w
/// <param name="identifier">The unique identifier associated with the resource.</param> /// <param name="identifier">The unique identifier associated with the resource.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the resource corresponding to the identifier. /// whose result returns the resource corresponding to the identifier.
/// </returns> /// </returns>
public virtual async ValueTask<TResource?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default) public virtual async ValueTask<TResource?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default)
@ -245,7 +245,7 @@ public class OpenIddictResourceManager<TResource> : IOpenIddictResourceManager w
/// <param name="name">The name associated with the resource.</param> /// <param name="name">The name associated with the resource.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the resource corresponding to the specified name. /// whose result returns the resource corresponding to the specified name.
/// </returns> /// </returns>
public virtual async ValueTask<TResource?> FindByNameAsync(string name, CancellationToken cancellationToken = default) public virtual async ValueTask<TResource?> FindByNameAsync(string name, CancellationToken cancellationToken = default)
@ -323,7 +323,7 @@ public class OpenIddictResourceManager<TResource> : IOpenIddictResourceManager w
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TResult>( public virtual ValueTask<TResult?> GetAsync<TResult>(
@ -343,7 +343,7 @@ public class OpenIddictResourceManager<TResource> : IOpenIddictResourceManager w
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TState, TResult>( public virtual ValueTask<TResult?> GetAsync<TState, TResult>(

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

@ -70,7 +70,7 @@ public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TSco
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes in the database. /// whose result returns the number of scopes in the database.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default) public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default)
@ -83,7 +83,7 @@ public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TSco
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes that match the specified query. /// whose result returns the number of scopes that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TResult>( public virtual ValueTask<long> CountAsync<TResult>(
@ -103,7 +103,7 @@ public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TSco
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes that match the specified query. /// whose result returns the number of scopes that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TState, TResult>( public virtual ValueTask<long> CountAsync<TState, TResult>(
@ -169,7 +169,7 @@ public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TSco
/// <param name="descriptor">The scope descriptor.</param> /// <param name="descriptor">The scope descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the scope. /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the scope.
/// </returns> /// </returns>
public virtual async ValueTask<TScope> CreateAsync( public virtual async ValueTask<TScope> CreateAsync(
OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default) OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default)
@ -211,7 +211,7 @@ public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TSco
/// <param name="identifier">The unique identifier associated with the scope.</param> /// <param name="identifier">The unique identifier associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the identifier. /// whose result returns the scope corresponding to the identifier.
/// </returns> /// </returns>
public virtual async ValueTask<TScope?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default) public virtual async ValueTask<TScope?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default)
@ -245,7 +245,7 @@ public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TSco
/// <param name="name">The name associated with the scope.</param> /// <param name="name">The name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the specified name. /// whose result returns the scope corresponding to the specified name.
/// </returns> /// </returns>
public virtual async ValueTask<TScope?> FindByNameAsync(string name, CancellationToken cancellationToken = default) public virtual async ValueTask<TScope?> FindByNameAsync(string name, CancellationToken cancellationToken = default)
@ -362,7 +362,7 @@ public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TSco
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TResult>( public virtual ValueTask<TResult?> GetAsync<TResult>(
@ -382,7 +382,7 @@ public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TSco
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TState, TResult>( public virtual ValueTask<TResult?> GetAsync<TState, TResult>(

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

@ -0,0 +1,919 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ValidationException = OpenIddict.Abstractions.OpenIddictExceptions.ValidationException;
namespace OpenIddict.Core;
/// <summary>
/// Provides methods allowing to manage the sessions stored in the store.
/// </summary>
/// <remarks>
/// Applications that do not want to depend on a specific entity type can use the non-generic
/// <see cref="IOpenIddictSessionManager"/> instead, for which the actual entity type is resolved at runtime.
/// </remarks>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
public class OpenIddictSessionManager<TSession> : IOpenIddictSessionManager where TSession : class
{
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictSessionManager{TSession}"/> class.
/// </summary>
/// <param name="cache">The cache.</param>
/// <param name="logger">The logger.</param>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictSessionManager(
IOpenIddictSessionCache<TSession> cache,
ILogger<OpenIddictSessionManager<TSession>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictSessionStore<TSession> store)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Options = options ?? throw new ArgumentNullException(nameof(options));
Store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <summary>
/// Gets the cache associated with the current manager.
/// </summary>
protected IOpenIddictSessionCache<TSession> Cache { get; }
/// <summary>
/// Gets the logger associated with the current manager.
/// </summary>
protected ILogger Logger { get; }
/// <summary>
/// Gets the options associated with the current manager.
/// </summary>
protected IOptionsMonitor<OpenIddictCoreOptions> Options { get; }
/// <summary>
/// Gets the store associated with the current manager.
/// </summary>
protected IOpenIddictSessionStore<TSession> Store { get; }
/// <summary>
/// Determines the number of sessions that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of sessions in the database.
/// </returns>
public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default)
=> Store.CountAsync(cancellationToken);
/// <summary>
/// Determines the number of sessions that match the specified query.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of sessions that match the specified query.
/// </returns>
public virtual ValueTask<long> CountAsync<TResult>(
Func<IQueryable<TSession>, IQueryable<TResult>> query, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(query);
return CountAsync(static (sessions, query) => query(sessions), query, cancellationToken);
}
/// <summary>
/// Determines the number of sessions that match the specified query.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of sessions that match the specified query.
/// </returns>
public virtual ValueTask<long> CountAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(query);
return Store.CountAsync(query, state, cancellationToken);
}
/// <summary>
/// Creates a new session.
/// </summary>
/// <param name="session">The session to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async ValueTask CreateAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
// If no status was explicitly specified, assume that the session is valid.
if (string.IsNullOrEmpty(await Store.GetStatusAsync(session, cancellationToken)))
{
await Store.SetStatusAsync(session, Statuses.Valid, cancellationToken);
}
var results = await GetValidationResultsAsync(session, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
{
var builder = new StringBuilder();
builder.AppendLine(SR.GetResourceString(SR.ID0207));
builder.AppendLine();
foreach (var result in results)
{
builder.AppendLine(result.ErrorMessage);
}
throw new ValidationException(builder.ToString(), results);
}
await Store.CreateAsync(session, cancellationToken);
if (!Options.CurrentValue.DisableEntityCaching)
{
await Cache.AddAsync(session, cancellationToken);
}
async Task<ImmutableArray<ValidationResult>> GetValidationResultsAsync(
TSession session, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder<ValidationResult>();
await foreach (var result in ValidateAsync(session, cancellationToken))
{
builder.Add(result);
}
return builder.ToImmutable();
}
}
/// <summary>
/// Creates a new session based on the specified descriptor.
/// </summary>
/// <param name="descriptor">The session descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the session.
/// </returns>
public virtual async ValueTask<TSession> CreateAsync(
OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(descriptor);
var session = await Store.InstantiateAsync(cancellationToken)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0208));
await PopulateAsync(session, descriptor, cancellationToken);
await CreateAsync(session, cancellationToken);
return session;
}
/// <summary>
/// Removes an existing session.
/// </summary>
/// <param name="session">The session to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async ValueTask DeleteAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
if (!Options.CurrentValue.DisableEntityCaching)
{
await Cache.RemoveAsync(session, cancellationToken);
}
await Store.DeleteAsync(session, cancellationToken);
}
/// <summary>
/// Retrieves the sessions matching the specified query.
/// </summary>
/// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <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,
CancellationToken cancellationToken = default)
{
var sessions = Options.CurrentValue.DisableEntityCaching
? Store.FindAsync(query, cancellationToken)
: Cache.FindAsync(query, cancellationToken);
if (Options.CurrentValue.DisableAdditionalFiltering)
{
return sessions;
}
// SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is enforced independently of the database/table/query collation
// used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here.
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var session in sessions)
{
if (string.IsNullOrEmpty(query.Subject) ||
string.Equals(await Store.GetSubjectAsync(session, cancellationToken), query.Subject, StringComparison.Ordinal))
{
yield return session;
}
}
}
}
/// <summary>
/// Retrieves the list of sessions corresponding to the specified application identifier.
/// </summary>
/// <param name="identifier">The application identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified application.</returns>
public virtual IAsyncEnumerable<TSession> FindByApplicationIdAsync(
string identifier, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
var sessions = Options.CurrentValue.DisableEntityCaching
? Store.FindByApplicationIdAsync(identifier, cancellationToken)
: Cache.FindByApplicationIdAsync(identifier, cancellationToken);
if (Options.CurrentValue.DisableAdditionalFiltering)
{
return sessions;
}
// SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is enforced independently of the database/table/query collation
// used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here.
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var session in sessions)
{
if (string.Equals(await Store.GetApplicationIdAsync(session, cancellationToken), identifier, StringComparison.Ordinal))
{
yield return session;
}
}
}
}
/// <summary>
/// Retrieves the list of sessions corresponding to the specified authorization identifier.
/// </summary>
/// <param name="identifier">The authorization identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified authorization.</returns>
public virtual IAsyncEnumerable<TSession> FindByAuthorizationIdAsync(
string identifier, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
var sessions = Options.CurrentValue.DisableEntityCaching
? Store.FindByAuthorizationIdAsync(identifier, cancellationToken)
: Cache.FindByAuthorizationIdAsync(identifier, cancellationToken);
if (Options.CurrentValue.DisableAdditionalFiltering)
{
return sessions;
}
// SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is enforced independently of the database/table/query collation
// used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here.
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var session in sessions)
{
if (string.Equals(await Store.GetAuthorizationIdAsync(session, cancellationToken), identifier, StringComparison.Ordinal))
{
yield return session;
}
}
}
}
/// <summary>
/// Retrieves a session using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the session corresponding to the identifier.
/// </returns>
public virtual async ValueTask<TSession?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
var session = Options.CurrentValue.DisableEntityCaching
? await Store.FindByIdAsync(identifier, cancellationToken)
: await Cache.FindByIdAsync(identifier, cancellationToken);
if (session is null)
{
return null;
}
// SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is enforced independently of the database/table/query collation
// used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here.
if (!Options.CurrentValue.DisableAdditionalFiltering &&
!string.Equals(await Store.GetIdAsync(session, cancellationToken), identifier, StringComparison.Ordinal))
{
return null;
}
return session;
}
/// <summary>
/// Retrieves the list of sessions corresponding to the specified login identifier.
/// </summary>
/// <param name="identifier">The login identifier associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified login identifier.</returns>
public virtual IAsyncEnumerable<TSession> FindByLoginIdAsync(string identifier, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
var sessions = Options.CurrentValue.DisableEntityCaching
? Store.FindByLoginIdAsync(identifier, cancellationToken)
: Cache.FindByLoginIdAsync(identifier, cancellationToken);
if (Options.CurrentValue.DisableAdditionalFiltering)
{
return sessions;
}
// SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is enforced independently of the database/table/query collation
// used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here.
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var session in sessions)
{
if (string.Equals(await Store.GetLoginIdAsync(session, cancellationToken), identifier, StringComparison.Ordinal))
{
yield return session;
}
}
}
}
/// <summary>
/// Retrieves the list of sessions corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the sessions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the specified subject.</returns>
public virtual IAsyncEnumerable<TSession> FindBySubjectAsync(
string subject, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(subject);
var sessions = Options.CurrentValue.DisableEntityCaching
? Store.FindBySubjectAsync(subject, cancellationToken)
: Cache.FindBySubjectAsync(subject, cancellationToken);
if (Options.CurrentValue.DisableAdditionalFiltering)
{
return sessions;
}
// SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is enforced independently of the database/table/query collation
// used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here.
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var session in sessions)
{
if (string.Equals(await Store.GetSubjectAsync(session, cancellationToken), subject, StringComparison.Ordinal))
{
yield return session;
}
}
}
}
/// <summary>
/// Retrieves the optional application identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the session.
/// </returns>
public virtual ValueTask<string?> GetApplicationIdAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
return Store.GetApplicationIdAsync(session, cancellationToken);
}
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public virtual ValueTask<TResult?> GetAsync<TResult>(
Func<IQueryable<TSession>, IQueryable<TResult>> query, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(query);
return GetAsync(static (sessions, query) => query(sessions), query, cancellationToken);
}
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public virtual ValueTask<TResult?> GetAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(query);
return Store.GetAsync(query, state, cancellationToken);
}
/// <summary>
/// Retrieves the optional authorization identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization identifier associated with the session.
/// </returns>
public virtual ValueTask<string?> GetAuthorizationIdAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
return Store.GetAuthorizationIdAsync(session, cancellationToken);
}
/// <summary>
/// Retrieves the creation date associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified session.
/// </returns>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
return Store.GetCreationDateAsync(session, cancellationToken);
}
/// <summary>
/// Retrieves the unique identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the session.
/// </returns>
public virtual ValueTask<string?> GetIdAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
return Store.GetIdAsync(session, cancellationToken);
}
/// <summary>
/// Retrieves the login identifier associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the login identifier associated with the specified session.
/// </returns>
public virtual ValueTask<string?> GetLoginIdAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
return Store.GetLoginIdAsync(session, cancellationToken);
}
/// <summary>
/// Retrieves the additional properties associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the session.
/// </returns>
public virtual ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(
TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
return Store.GetPropertiesAsync(session, cancellationToken);
}
/// <summary>
/// Retrieves the status associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified session.
/// </returns>
public virtual ValueTask<string?> GetStatusAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
return Store.GetStatusAsync(session, cancellationToken);
}
/// <summary>
/// Retrieves the subject associated with a session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified session.
/// </returns>
public virtual ValueTask<string?> GetSubjectAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
return Store.GetSubjectAsync(session, cancellationToken);
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>All the elements returned when executing the specified query.</returns>
public virtual IAsyncEnumerable<TSession> ListAsync(
int? count = null, int? offset = null, CancellationToken cancellationToken = default)
=> Store.ListAsync(count, offset, cancellationToken);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>All the elements returned when executing the specified query.</returns>
public virtual IAsyncEnumerable<TResult> ListAsync<TResult>(
Func<IQueryable<TSession>, IQueryable<TResult>> query, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(query);
return ListAsync(static (sessions, query) => query(sessions), query, cancellationToken);
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>All the elements returned when executing the specified query.</returns>
public virtual IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(query);
return Store.ListAsync(query, state, cancellationToken);
}
/// <summary>
/// Populates the session using the specified descriptor.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="descriptor">The descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async ValueTask PopulateAsync(TSession session,
OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
ArgumentNullException.ThrowIfNull(descriptor);
await Store.SetApplicationIdAsync(session, descriptor.ApplicationId, cancellationToken);
await Store.SetAuthorizationIdAsync(session, descriptor.AuthorizationId, cancellationToken);
await Store.SetCreationDateAsync(session, descriptor.CreationDate, cancellationToken);
await Store.SetLoginIdAsync(session, descriptor.LoginId, cancellationToken);
await Store.SetPropertiesAsync(session, [.. descriptor.Properties], cancellationToken);
await Store.SetStatusAsync(session, descriptor.Status, cancellationToken);
await Store.SetSubjectAsync(session, descriptor.Subject, cancellationToken);
}
/// <summary>
/// Populates the specified descriptor using the properties exposed by the session.
/// </summary>
/// <param name="descriptor">The descriptor.</param>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async ValueTask PopulateAsync(
OpenIddictSessionDescriptor descriptor,
TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(descriptor);
ArgumentNullException.ThrowIfNull(session);
descriptor.ApplicationId = await Store.GetApplicationIdAsync(session, cancellationToken);
descriptor.AuthorizationId = await Store.GetAuthorizationIdAsync(session, cancellationToken);
descriptor.CreationDate = await Store.GetCreationDateAsync(session, cancellationToken);
descriptor.LoginId = await Store.GetLoginIdAsync(session, cancellationToken);
descriptor.Status = await Store.GetStatusAsync(session, cancellationToken);
descriptor.Subject = await Store.GetSubjectAsync(session, cancellationToken);
descriptor.Properties.Clear();
foreach (var pair in await Store.GetPropertiesAsync(session, cancellationToken))
{
descriptor.Properties.Add(pair.Key, pair.Value);
}
}
/// <summary>
/// Updates an existing session.
/// </summary>
/// <param name="session">The session to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async ValueTask UpdateAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
var results = await GetValidationResultsAsync(session, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
{
var builder = new StringBuilder();
builder.AppendLine(SR.GetResourceString(SR.ID0215));
builder.AppendLine();
foreach (var result in results)
{
builder.AppendLine(result.ErrorMessage);
}
throw new ValidationException(builder.ToString(), results);
}
if (!Options.CurrentValue.DisableEntityCaching)
{
await Cache.RemoveAsync(session, cancellationToken);
}
await Store.UpdateAsync(session, cancellationToken);
if (!Options.CurrentValue.DisableEntityCaching)
{
await Cache.AddAsync(session, cancellationToken);
}
async Task<ImmutableArray<ValidationResult>> GetValidationResultsAsync(
TSession session, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder<ValidationResult>();
await foreach (var result in ValidateAsync(session, cancellationToken))
{
builder.Add(result);
}
return builder.ToImmutable();
}
}
/// <summary>
/// Updates an existing session.
/// </summary>
/// <param name="session">The session to update.</param>
/// <param name="descriptor">The descriptor used to update the session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async ValueTask UpdateAsync(TSession session,
OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
ArgumentNullException.ThrowIfNull(descriptor);
await PopulateAsync(session, descriptor, cancellationToken);
await UpdateAsync(session, cancellationToken);
}
/// <summary>
/// Validates the session to ensure it's in a consistent state.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The validation error encountered when validating the session.</returns>
public virtual IAsyncEnumerable<ValidationResult> ValidateAsync(TSession session, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(session);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<ValidationResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(await Store.GetStatusAsync(session, cancellationToken)))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2038));
}
if (string.IsNullOrEmpty(await Store.GetLoginIdAsync(session, cancellationToken)))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2209));
}
}
}
/// <inheritdoc/>
ValueTask<long> IOpenIddictSessionManager.CountAsync(CancellationToken cancellationToken)
=> CountAsync(cancellationToken);
/// <inheritdoc/>
ValueTask<long> IOpenIddictSessionManager.CountAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken)
=> CountAsync(query, cancellationToken);
/// <inheritdoc/>
ValueTask<long> IOpenIddictSessionManager.CountAsync<TState, TResult>(Func<IQueryable<object>, TState, IQueryable<TResult>> query, TState state, CancellationToken cancellationToken)
=> CountAsync(query, state, cancellationToken);
/// <inheritdoc/>
async ValueTask<object> IOpenIddictSessionManager.CreateAsync(OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken)
=> await CreateAsync(descriptor, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictSessionManager.CreateAsync(object session, CancellationToken cancellationToken)
=> CreateAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictSessionManager.DeleteAsync(object session, CancellationToken cancellationToken)
=> DeleteAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictSessionManager.FindAsync((string? Subject, string? LoginId, string? ApplicationId, string? Status) query, CancellationToken cancellationToken)
=> FindAsync(query, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictSessionManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
=> FindByApplicationIdAsync(identifier, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictSessionManager.FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
=> FindByAuthorizationIdAsync(identifier, cancellationToken);
/// <inheritdoc/>
async ValueTask<object?> IOpenIddictSessionManager.FindByIdAsync(string identifier, CancellationToken cancellationToken)
=> await FindByIdAsync(identifier, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictSessionManager.FindByLoginIdAsync(string identifier, CancellationToken cancellationToken)
=> FindByLoginIdAsync(identifier, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictSessionManager.FindBySubjectAsync(string subject, CancellationToken cancellationToken)
=> FindBySubjectAsync(subject, cancellationToken);
/// <inheritdoc/>
ValueTask<string?> IOpenIddictSessionManager.GetApplicationIdAsync(object session, CancellationToken cancellationToken)
=> GetApplicationIdAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask<TResult?> IOpenIddictSessionManager.GetAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken) where TResult : default
=> GetAsync(query, cancellationToken);
/// <inheritdoc/>
ValueTask<TResult?> IOpenIddictSessionManager.GetAsync<TState, TResult>(Func<IQueryable<object>, TState, IQueryable<TResult>> query, TState state, CancellationToken cancellationToken) where TResult : default
=> GetAsync(query, state, cancellationToken);
/// <inheritdoc/>
ValueTask<string?> IOpenIddictSessionManager.GetAuthorizationIdAsync(object session, CancellationToken cancellationToken)
=> GetAuthorizationIdAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask<DateTimeOffset?> IOpenIddictSessionManager.GetCreationDateAsync(object session, CancellationToken cancellationToken)
=> GetCreationDateAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask<string?> IOpenIddictSessionManager.GetIdAsync(object session, CancellationToken cancellationToken)
=> GetIdAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask<string?> IOpenIddictSessionManager.GetLoginIdAsync(object session, CancellationToken cancellationToken)
=> GetLoginIdAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask<ImmutableDictionary<string, JsonElement>> IOpenIddictSessionManager.GetPropertiesAsync(object session, CancellationToken cancellationToken)
=> GetPropertiesAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask<string?> IOpenIddictSessionManager.GetStatusAsync(object session, CancellationToken cancellationToken)
=> GetStatusAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask<string?> IOpenIddictSessionManager.GetSubjectAsync(object session, CancellationToken cancellationToken)
=> GetSubjectAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictSessionManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken)
=> ListAsync(count, offset, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<TResult> IOpenIddictSessionManager.ListAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken)
=> ListAsync(query, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<TResult> IOpenIddictSessionManager.ListAsync<TState, TResult>(Func<IQueryable<object>, TState, IQueryable<TResult>> query, TState state, CancellationToken cancellationToken)
=> ListAsync(query, state, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictSessionManager.PopulateAsync(OpenIddictSessionDescriptor descriptor, object session, CancellationToken cancellationToken)
=> PopulateAsync(descriptor, (TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictSessionManager.PopulateAsync(object session, OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken)
=> PopulateAsync((TSession) session, descriptor, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictSessionManager.UpdateAsync(object session, CancellationToken cancellationToken)
=> UpdateAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictSessionManager.UpdateAsync(object session, OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken)
=> UpdateAsync((TSession) session, descriptor, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<ValidationResult> IOpenIddictSessionManager.ValidateAsync(object session, CancellationToken cancellationToken)
=> ValidateAsync((TSession) session, cancellationToken);
}

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

@ -71,7 +71,7 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
/// </summary> /// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens in the database. /// whose result returns the number of tokens in the database.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default) public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken = default)
@ -84,7 +84,7 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens that match the specified query. /// whose result returns the number of tokens that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TResult>( public virtual ValueTask<long> CountAsync<TResult>(
@ -104,7 +104,7 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens that match the specified query. /// whose result returns the number of tokens that match the specified query.
/// </returns> /// </returns>
public virtual ValueTask<long> CountAsync<TState, TResult>( public virtual ValueTask<long> CountAsync<TState, TResult>(
@ -184,7 +184,7 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
/// <param name="descriptor">The token descriptor.</param> /// <param name="descriptor">The token descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, whose result returns the token. /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns> /// </returns>
public virtual async ValueTask<TToken> CreateAsync( public virtual async ValueTask<TToken> CreateAsync(
OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default) OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default)
@ -221,21 +221,18 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
} }
/// <summary> /// <summary>
/// Retrieves the tokens matching the specified parameters. /// Retrieves the tokens matching the specified query.
/// </summary> /// </summary>
/// <param name="subject">The subject associated with the token, or <see langword="null"/> not to filter out specific subjects.</param> /// <param name="query">The query parameters: if a parameter is <see langword="null"/>, it will not be used to filter the results.</param>
/// <param name="client">The client associated with the token, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The token status, or <see langword="null"/> not to filter out specific token statuses.</param>
/// <param name="type">The token type, or <see langword="null"/> not to filter out specific token types.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>Tokens corresponding to the criteria.</returns> /// <returns>The tokens corresponding to the criteria.</returns>
public virtual IAsyncEnumerable<TToken> FindAsync( public virtual IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status, string? Type) query,
string? status, string? type, CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var tokens = Options.CurrentValue.DisableEntityCaching var tokens = Options.CurrentValue.DisableEntityCaching
? Store.FindAsync(subject, client, status, type, cancellationToken) ? Store.FindAsync(query, cancellationToken)
: Cache.FindAsync(subject, client, status, type, cancellationToken); : Cache.FindAsync(query, cancellationToken);
if (Options.CurrentValue.DisableAdditionalFiltering) if (Options.CurrentValue.DisableAdditionalFiltering)
{ {
@ -252,8 +249,8 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
{ {
await foreach (var token in tokens) await foreach (var token in tokens)
{ {
if (string.IsNullOrEmpty(subject) || if (string.IsNullOrEmpty(query.Subject) ||
string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) string.Equals(await Store.GetSubjectAsync(token, cancellationToken), query.Subject, StringComparison.Ordinal))
{ {
yield return token; yield return token;
} }
@ -343,7 +340,7 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
/// <param name="identifier">The unique identifier associated with the token.</param> /// <param name="identifier">The unique identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier. /// whose result returns the token corresponding to the unique identifier.
/// </returns> /// </returns>
public virtual async ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default) public virtual async ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken = default)
@ -372,13 +369,15 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
} }
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified reference identifier. /// Retrieves a token using its unique reference identifier.
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </summary> /// </summary>
/// <remarks>
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </remarks>
/// <param name="identifier">The reference identifier associated with the tokens.</param> /// <param name="identifier">The reference identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified reference identifier. /// whose result returns the tokens corresponding to the specified reference identifier.
/// </returns> /// </returns>
public virtual async ValueTask<TToken?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken = default) public virtual async ValueTask<TToken?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken = default)
@ -470,7 +469,7 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
/// <param name="query">The query to execute.</param> /// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TResult>( public virtual ValueTask<TResult?> GetAsync<TResult>(
@ -490,7 +489,7 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
/// <param name="state">The optional state.</param> /// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query. /// whose result returns the first element returned when executing the query.
/// </returns> /// </returns>
public virtual ValueTask<TResult?> GetAsync<TState, TResult>( public virtual ValueTask<TResult?> GetAsync<TState, TResult>(
@ -1151,8 +1150,7 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
} }
} }
var type = await Store.GetTypeAsync(token, cancellationToken); if (string.IsNullOrEmpty(await Store.GetTypeAsync(token, cancellationToken)))
if (string.IsNullOrEmpty(type))
{ {
yield return new ValidationResult(SR.GetResourceString(SR.ID2086)); yield return new ValidationResult(SR.GetResourceString(SR.ID2086));
} }
@ -1207,8 +1205,8 @@ public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TTok
=> DeleteAsync((TToken) token, cancellationToken); => DeleteAsync((TToken) token, cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictTokenManager.FindAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken) IAsyncEnumerable<object> IOpenIddictTokenManager.FindAsync((string? Subject, string? ApplicationId, string? Status, string? Type) query, CancellationToken cancellationToken)
=> FindAsync(subject, client, status, type, cancellationToken); => FindAsync(query, cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictTokenManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) IAsyncEnumerable<object> IOpenIddictTokenManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)

93
src/OpenIddict.Core/OpenIddictCoreBuilder.cs

@ -377,6 +377,86 @@ public sealed class OpenIddictCoreBuilder
return this; return this;
} }
/// <summary>
/// Replaces the session manager by the specified type.
/// </summary>
/// <typeparam name="TSession">The type of the entity.</typeparam>
/// <typeparam name="TManager">The type of the manager.</typeparam>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceSessionManager<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TManager>()
where TSession : class
where TManager : OpenIddictSessionManager<TSession>
{
Services.Replace(ServiceDescriptor.Scoped<OpenIddictSessionManager<TSession>, TManager>());
return this;
}
/// <summary>
/// Replaces the session manager by the specified type.
/// </summary>
/// <remarks>
/// Note: the specified type MUST be an open generic type definition containing exactly one generic argument.
/// </remarks>
/// <param name="type">The type of the manager.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceSessionManager(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
{
if (!type.IsGenericTypeDefinition || type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictSessionManager<>), type));
return this;
}
/// <summary>
/// Replaces the session store by the specified type.
/// </summary>
/// <typeparam name="TSession">The type of the entity.</typeparam>
/// <typeparam name="TStore">The type of the store.</typeparam>
/// <param name="lifetime">The lifetime of the store.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceSessionStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TStore>(
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TSession : class
where TStore : IOpenIddictSessionStore<TSession>
{
Services.Replace(ServiceDescriptor.Describe(typeof(IOpenIddictSessionStore<TSession>), typeof(TStore), lifetime));
return this;
}
/// <summary>
/// Replaces the session store by the specified type.
/// </summary>
/// <remarks>
/// Note: the specified type MUST be an open generic type definition containing exactly one generic argument.
/// </remarks>
/// <param name="type">The type of the store.</param>
/// <param name="lifetime">The lifetime of the store.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceSessionStore(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (!type.IsGenericTypeDefinition || type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Describe(typeof(IOpenIddictSessionStore<>), type, lifetime));
return this;
}
/// <summary> /// <summary>
/// Replaces the token manager by the specified type. /// Replaces the token manager by the specified type.
/// </summary> /// </summary>
@ -600,6 +680,19 @@ public sealed class OpenIddictCoreBuilder
return this; return this;
} }
/// <summary>
/// Configures OpenIddict to use the specified entity as the default session entity.
/// </summary>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder SetDefaultSessionEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession>() where TSession : class
{
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictSessionManager>(static provider =>
provider.GetRequiredService<OpenIddictSessionManager<TSession>>()));
return this;
}
/// <summary> /// <summary>
/// Configures OpenIddict to use the specified entity as the default token entity. /// Configures OpenIddict to use the specified entity as the default token entity.
/// </summary> /// </summary>

4
src/OpenIddict.Core/OpenIddictCoreExtensions.cs

@ -33,12 +33,14 @@ public static class OpenIddictCoreExtensions
builder.Services.TryAddScoped(typeof(IOpenIddictAuthorizationCache<>), typeof(OpenIddictAuthorizationCache<>)); builder.Services.TryAddScoped(typeof(IOpenIddictAuthorizationCache<>), typeof(OpenIddictAuthorizationCache<>));
builder.Services.TryAddScoped(typeof(IOpenIddictResourceCache<>), typeof(OpenIddictResourceCache<>)); builder.Services.TryAddScoped(typeof(IOpenIddictResourceCache<>), typeof(OpenIddictResourceCache<>));
builder.Services.TryAddScoped(typeof(IOpenIddictScopeCache<>), typeof(OpenIddictScopeCache<>)); builder.Services.TryAddScoped(typeof(IOpenIddictScopeCache<>), typeof(OpenIddictScopeCache<>));
builder.Services.TryAddScoped(typeof(IOpenIddictSessionCache<>), typeof(OpenIddictSessionCache<>));
builder.Services.TryAddScoped(typeof(IOpenIddictTokenCache<>), typeof(OpenIddictTokenCache<>)); builder.Services.TryAddScoped(typeof(IOpenIddictTokenCache<>), typeof(OpenIddictTokenCache<>));
builder.Services.TryAddScoped(typeof(OpenIddictApplicationManager<>)); builder.Services.TryAddScoped(typeof(OpenIddictApplicationManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationManager<>)); builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictResourceManager<>)); builder.Services.TryAddScoped(typeof(OpenIddictResourceManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictScopeManager<>)); builder.Services.TryAddScoped(typeof(OpenIddictScopeManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictSessionManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictTokenManager<>)); builder.Services.TryAddScoped(typeof(OpenIddictTokenManager<>));
// Note: default factories for the untyped managers are always registered to make debugging // Note: default factories for the untyped managers are always registered to make debugging
@ -52,6 +54,8 @@ public static class OpenIddictCoreExtensions
throw new InvalidOperationException(SR.GetResourceString(SR.ID0472))); throw new InvalidOperationException(SR.GetResourceString(SR.ID0472)));
builder.Services.TryAddScoped<IOpenIddictScopeManager>(static provider => builder.Services.TryAddScoped<IOpenIddictScopeManager>(static provider =>
throw new InvalidOperationException(SR.GetResourceString(SR.ID0472))); throw new InvalidOperationException(SR.GetResourceString(SR.ID0472)));
builder.Services.TryAddScoped<IOpenIddictSessionManager>(static provider =>
throw new InvalidOperationException(SR.GetResourceString(SR.ID0472)));
builder.Services.TryAddScoped<IOpenIddictTokenManager>(static provider => builder.Services.TryAddScoped<IOpenIddictTokenManager>(static provider =>
throw new InvalidOperationException(SR.GetResourceString(SR.ID0472))); throw new InvalidOperationException(SR.GetResourceString(SR.ID0472)));

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

@ -12,7 +12,10 @@ namespace OpenIddict.EntityFramework.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict application. /// Represents an OpenIddict application.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkApplication : OpenIddictEntityFrameworkApplication<string, OpenIddictEntityFrameworkAuthorization, OpenIddictEntityFrameworkToken> public class OpenIddictEntityFrameworkApplication :
OpenIddictEntityFrameworkApplication<string,
OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkToken>
{ {
public OpenIddictEntityFrameworkApplication() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkApplication() => Id = Guid.NewGuid().ToString();
} }

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

@ -12,7 +12,10 @@ namespace OpenIddict.EntityFramework.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict authorization. /// Represents an OpenIddict authorization.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkAuthorization : OpenIddictEntityFrameworkAuthorization<string, OpenIddictEntityFrameworkApplication, OpenIddictEntityFrameworkToken> public class OpenIddictEntityFrameworkAuthorization :
OpenIddictEntityFrameworkAuthorization<string,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkToken>
{ {
public OpenIddictEntityFrameworkAuthorization() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkAuthorization() => Id = Guid.NewGuid().ToString();
} }

3
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkResource.cs

@ -12,7 +12,8 @@ namespace OpenIddict.EntityFramework.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict resource. /// Represents an OpenIddict resource.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkResource : OpenIddictEntityFrameworkResource<string> public class OpenIddictEntityFrameworkResource :
OpenIddictEntityFrameworkResource<string>
{ {
public OpenIddictEntityFrameworkResource() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkResource() => Id = Guid.NewGuid().ToString();
} }

3
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs

@ -12,7 +12,8 @@ namespace OpenIddict.EntityFramework.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict scope. /// Represents an OpenIddict scope.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkScope : OpenIddictEntityFrameworkScope<string> public class OpenIddictEntityFrameworkScope :
OpenIddictEntityFrameworkScope<string>
{ {
public OpenIddictEntityFrameworkScope() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkScope() => Id = Guid.NewGuid().ToString();
} }

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

@ -0,0 +1,77 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace OpenIddict.EntityFramework.Models;
/// <summary>
/// Represents an OpenIddict session.
/// </summary>
public class OpenIddictEntityFrameworkSession :
OpenIddictEntityFrameworkSession<string,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkAuthorization>
{
public OpenIddictEntityFrameworkSession() => Id = Guid.NewGuid().ToString();
}
/// <summary>
/// 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>
where TKey : notnull, IEquatable<TKey>
where TApplication : class
where TAuthorization : class
{
/// <summary>
/// Gets or sets the application associated with the session.
/// </summary>
public virtual TApplication? Application { get; set; }
/// <summary>
/// Gets or sets the authorization associated with the session.
/// </summary>
public virtual TAuthorization? Authorization { get; set; }
/// <summary>
/// Gets or sets the concurrency token of the session.
/// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the UTC creation date of the session.
/// </summary>
public virtual DateTime? CreationDate { get; set; }
/// <summary>
/// Gets or sets the unique identifier of the session.
/// </summary>
public virtual TKey? Id { get; set; }
/// <summary>
/// Gets or sets the login identifier of the session.
/// </summary>
public virtual string? LoginId { get; set; }
/// <summary>
/// Gets or sets the additional properties of the session, serialized as a JSON object.
/// </summary>
[StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Properties { get; set; }
/// <summary>
/// Gets or sets the status of the session.
/// </summary>
public virtual string? Status { get; set; }
/// <summary>
/// Gets or sets the subject of the session.
/// </summary>
public virtual string? Subject { get; set; }
}

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

@ -12,7 +12,10 @@ namespace OpenIddict.EntityFramework.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict token. /// Represents an OpenIddict token.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkToken : OpenIddictEntityFrameworkToken<string, OpenIddictEntityFrameworkApplication, OpenIddictEntityFrameworkAuthorization> public class OpenIddictEntityFrameworkToken :
OpenIddictEntityFrameworkToken<string,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkAuthorization>
{ {
public OpenIddictEntityFrameworkToken() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkToken() => Id = Guid.NewGuid().ToString();
} }
@ -91,7 +94,7 @@ public class OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
public virtual string? Status { get; set; } public virtual string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the token. /// Gets or sets the subject of the token.
/// </summary> /// </summary>
public virtual string? Subject { get; set; } public virtual string? Subject { get; set; }

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

@ -0,0 +1,74 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
using System.Data.Entity.ModelConfiguration;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework;
/// <summary>
/// Defines a relational mapping for the session entity.
/// </summary>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkSessionConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[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 TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkSessionConfiguration()
{
// Warning: optional foreign keys MUST NOT be added as CLR properties because
// Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid.
Property(static session => session.ConcurrencyToken)
.HasMaxLength(50)
.IsConcurrencyToken();
HasKey(static session => session.Id);
if (typeof(TKey) == typeof(string))
{
var parameter = Expression.Parameter(typeof(TSession), "session");
var property = Expression.Property(parameter,
typeof(TSession).GetProperty(nameof(OpenIddictEntityFrameworkSession.Id))!);
var lambda = Expression.Lambda<Func<TSession, string>>(property, parameter);
Property(lambda).HasMaxLength(100);
}
Property(static session => session.LoginId)
.HasMaxLength(100);
// Warning: the index on the LoginId property MUST NOT be declared as
// a unique index, as Entity Framework 6.x doesn't support creating indexes
// with null-friendly WHERE conditions, unlike Entity Framework Core.
HasIndex(static session => session.LoginId);
Property(static session => session.Status)
.HasMaxLength(50);
Property(static session => session.Subject)
.HasMaxLength(400);
ToTable("OpenIddictSessions");
}
}

9
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs

@ -68,12 +68,14 @@ public sealed class OpenIddictEntityFrameworkBuilder
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>() [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>()
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken> where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken> where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TResource : OpenIddictEntityFrameworkResource<TKey> where TResource : OpenIddictEntityFrameworkResource<TKey>
where TScope : OpenIddictEntityFrameworkScope<TKey> where TScope : OpenIddictEntityFrameworkScope<TKey>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization> where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
@ -82,7 +84,8 @@ public sealed class OpenIddictEntityFrameworkBuilder
// //
// To ensure a better exception is thrown, a manual check is made here. // To ensure a better exception is thrown, a manual check is made here.
if (typeof(TApplication).IsGenericType || typeof(TAuthorization).IsGenericType || if (typeof(TApplication).IsGenericType || typeof(TAuthorization).IsGenericType ||
typeof(TResource).IsGenericType || typeof(TScope).IsGenericType || typeof(TToken).IsGenericType) typeof(TResource).IsGenericType || typeof(TScope).IsGenericType ||
typeof(TSession).IsGenericType || typeof(TToken).IsGenericType)
{ {
throw new InvalidOperationException(SR.GetResourceString(SR.ID0277)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0277));
} }
@ -104,6 +107,8 @@ public sealed class OpenIddictEntityFrameworkBuilder
provider.GetRequiredService<OpenIddictResourceManager<TResource>>())); provider.GetRequiredService<OpenIddictResourceManager<TResource>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeManager>(static provider => Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeManager>(static provider =>
provider.GetRequiredService<OpenIddictScopeManager<TScope>>())); provider.GetRequiredService<OpenIddictScopeManager<TScope>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictSessionManager>(static provider =>
provider.GetRequiredService<OpenIddictSessionManager<TSession>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenManager>(static provider => Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenManager>(static provider =>
provider.GetRequiredService<OpenIddictTokenManager<TToken>>())); provider.GetRequiredService<OpenIddictTokenManager<TToken>>()));
@ -115,6 +120,8 @@ public sealed class OpenIddictEntityFrameworkBuilder
OpenIddictEntityFrameworkResourceStore<TResource, TKey>>()); OpenIddictEntityFrameworkResourceStore<TResource, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeStore<TScope>, Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeStore<TScope>,
OpenIddictEntityFrameworkScopeStore<TScope, TKey>>()); OpenIddictEntityFrameworkScopeStore<TScope, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictSessionStore<TSession>,
OpenIddictEntityFrameworkSessionStore<TSession, TApplication, TAuthorization, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenStore<TToken>, Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenStore<TToken>,
OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthorization, TKey>>()); OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthorization, TKey>>());

2
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs

@ -35,12 +35,14 @@ public static class OpenIddictEntityFrameworkExtensions
.SetDefaultAuthorizationEntity<OpenIddictEntityFrameworkAuthorization>() .SetDefaultAuthorizationEntity<OpenIddictEntityFrameworkAuthorization>()
.SetDefaultResourceEntity<OpenIddictEntityFrameworkResource>() .SetDefaultResourceEntity<OpenIddictEntityFrameworkResource>()
.SetDefaultScopeEntity<OpenIddictEntityFrameworkScope>() .SetDefaultScopeEntity<OpenIddictEntityFrameworkScope>()
.SetDefaultSessionEntity<OpenIddictEntityFrameworkSession>()
.SetDefaultTokenEntity<OpenIddictEntityFrameworkToken>(); .SetDefaultTokenEntity<OpenIddictEntityFrameworkToken>();
builder.ReplaceApplicationStore<OpenIddictEntityFrameworkApplication, OpenIddictEntityFrameworkApplicationStore>() builder.ReplaceApplicationStore<OpenIddictEntityFrameworkApplication, OpenIddictEntityFrameworkApplicationStore>()
.ReplaceAuthorizationStore<OpenIddictEntityFrameworkAuthorization, OpenIddictEntityFrameworkAuthorizationStore>() .ReplaceAuthorizationStore<OpenIddictEntityFrameworkAuthorization, OpenIddictEntityFrameworkAuthorizationStore>()
.ReplaceResourceStore<OpenIddictEntityFrameworkResource, OpenIddictEntityFrameworkResourceStore>() .ReplaceResourceStore<OpenIddictEntityFrameworkResource, OpenIddictEntityFrameworkResourceStore>()
.ReplaceScopeStore<OpenIddictEntityFrameworkScope, OpenIddictEntityFrameworkScopeStore>() .ReplaceScopeStore<OpenIddictEntityFrameworkScope, OpenIddictEntityFrameworkScopeStore>()
.ReplaceSessionStore<OpenIddictEntityFrameworkSession, OpenIddictEntityFrameworkSessionStore>()
.ReplaceTokenStore<OpenIddictEntityFrameworkToken, OpenIddictEntityFrameworkTokenStore>(); .ReplaceTokenStore<OpenIddictEntityFrameworkToken, OpenIddictEntityFrameworkTokenStore>();
// Note: a default context factory is always registered to make debugging easier when // Note: a default context factory is always registered to make debugging easier when

8
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs

@ -29,6 +29,7 @@ public static class OpenIddictEntityFrameworkHelpers
OpenIddictEntityFrameworkAuthorization, OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkResource, OpenIddictEntityFrameworkResource,
OpenIddictEntityFrameworkScope, OpenIddictEntityFrameworkScope,
OpenIddictEntityFrameworkSession,
OpenIddictEntityFrameworkToken, string>(); OpenIddictEntityFrameworkToken, string>();
/// <summary> /// <summary>
@ -37,7 +38,7 @@ public static class OpenIddictEntityFrameworkHelpers
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Note: when using custom entities, the new entities MUST be registered by calling /// Note: when using custom entities, the new entities MUST be registered by calling
/// <see cref="OpenIddictEntityFrameworkBuilder.ReplaceDefaultEntities{TApplication, TAuthorization, TResource, TScope, TToken, TKey}"/>. /// <see cref="OpenIddictEntityFrameworkBuilder.ReplaceDefaultEntities{TApplication, TAuthorization, TResource, TScope, TSession, TToken, TKey}"/>.
/// </remarks> /// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework context.</param> /// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns> /// <returns>The Entity Framework context builder.</returns>
@ -46,12 +47,14 @@ public static class OpenIddictEntityFrameworkHelpers
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this DbModelBuilder builder) [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this DbModelBuilder builder)
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken> where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken> where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TResource : OpenIddictEntityFrameworkResource<TKey> where TResource : OpenIddictEntityFrameworkResource<TKey>
where TScope : OpenIddictEntityFrameworkScope<TKey> where TScope : OpenIddictEntityFrameworkScope<TKey>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization> where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
@ -62,6 +65,7 @@ public static class OpenIddictEntityFrameworkHelpers
.Add(new OpenIddictEntityFrameworkAuthorizationConfiguration<TAuthorization, TApplication, TToken, TKey>()) .Add(new OpenIddictEntityFrameworkAuthorizationConfiguration<TAuthorization, TApplication, TToken, TKey>())
.Add(new OpenIddictEntityFrameworkResourceConfiguration<TResource, TKey>()) .Add(new OpenIddictEntityFrameworkResourceConfiguration<TResource, TKey>())
.Add(new OpenIddictEntityFrameworkScopeConfiguration<TScope, 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, TKey>());
return builder; return builder;
@ -82,7 +86,7 @@ public static class OpenIddictEntityFrameworkHelpers
static async IAsyncEnumerable<T> ExecuteAsync(IQueryable<T> source, [EnumeratorCancellation] CancellationToken cancellationToken) static async IAsyncEnumerable<T> ExecuteAsync(IQueryable<T> source, [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
using var enumerator = ((IDbAsyncEnumerable<T>)source).GetAsyncEnumerator(); using var enumerator = ((IDbAsyncEnumerable<T>) source).GetAsyncEnumerator();
while (await enumerator.MoveNextAsync(cancellationToken)) while (await enumerator.MoveNextAsync(cancellationToken))
{ {

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

@ -159,44 +159,47 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
/// <inheritdoc/> /// <inheritdoc/>
public virtual async IAsyncEnumerable<TAuthorization> FindAsync( public virtual async IAsyncEnumerable<TAuthorization> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status,
string? status, string? type, string? Type, ImmutableArray<string>? RequiredScopes) query,
ImmutableArray<string>? scopes, [EnumeratorCancellation] CancellationToken cancellationToken) [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var context = await Context.GetDbContextAsync(cancellationToken); var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TAuthorization> query = context.Set<TAuthorization>().Include(authorization => authorization.Application); IQueryable<TAuthorization> authorizations = context.Set<TAuthorization>().Include(authorization => authorization.Application);
if (!string.IsNullOrEmpty(subject)) if (!string.IsNullOrEmpty(query.Subject))
{ {
query = query.Where(authorization => authorization.Subject == subject); authorizations = authorizations.Where(authorization => authorization.Subject == query.Subject);
} }
if (!string.IsNullOrEmpty(client)) if (!string.IsNullOrEmpty(query.ApplicationId))
{ {
var key = ConvertIdentifierFromString(client); var key = ConvertIdentifierFromString(query.ApplicationId);
authorizations = authorizations.Where(authorization => authorization.Application!.Id!.Equals(key));
query = query.Where(authorization => authorization.Application!.Id!.Equals(key));
} }
if (!string.IsNullOrEmpty(status)) if (!string.IsNullOrEmpty(query.Status))
{ {
query = query.Where(authorization => authorization.Status == status); authorizations = authorizations.Where(authorization => authorization.Status == query.Status);
} }
if (!string.IsNullOrEmpty(type)) if (!string.IsNullOrEmpty(query.Type))
{ {
query = query.Where(authorization => authorization.Type == type); authorizations = authorizations.Where(authorization => authorization.Type == query.Type);
} }
await foreach (var authorization in query.AsAsyncEnumerable(cancellationToken)) // Note: Entity Framework Core cannot translate the logic used to filter authorizations by scopes in a
// SQL query so the filtering is done manually after the results have been retrieved from the database.
await foreach (var authorization in authorizations.AsAsyncEnumerable(cancellationToken))
{ {
if (scopes is null || (await GetScopesAsync(authorization, cancellationToken)) if (query.RequiredScopes is { IsDefaultOrEmpty: false } scopes && !(await GetScopesAsync(authorization, cancellationToken))
.ToHashSet(StringComparer.Ordinal) .ToHashSet(StringComparer.Ordinal)
.IsSupersetOf(scopes)) .IsSupersetOf(scopes))
{ {
yield return authorization; continue;
} }
yield return authorization;
} }
} }
@ -259,12 +262,12 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
// If the application is not attached to the authorization, try to load it manually. // If the application is not attached to the authorization, try to load it manually.
if (authorization.Application is null) if (authorization.Application is null)
{ {
var reference = context.Entry(authorization).Reference(entry => entry.Application); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(authorization).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return null; return null;
@ -703,10 +706,10 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier)) if (!string.IsNullOrEmpty(identifier))
{ {
var context = await Context.GetDbContextAsync(cancellationToken);
authorization.Application = await context.Set<TApplication>().FindAsync( authorization.Application = await context.Set<TApplication>().FindAsync(
cancellationToken, ConvertIdentifierFromString(identifier)) cancellationToken, ConvertIdentifierFromString(identifier))
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0244)); ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0244));
@ -717,7 +720,9 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
// If the application is not attached to the authorization, try to load it manually. // If the application is not attached to the authorization, try to load it manually.
if (authorization.Application is null) if (authorization.Application is null)
{ {
var reference = context.Entry(authorization).Reference(entry => entry.Application); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(authorization).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return; return;

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

@ -0,0 +1,706 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data.Entity.Infrastructure;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFramework.Models;
using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.EntityFramework;
/// <summary>
/// Provides methods allowing to manage the sessions stored in a database.
/// </summary>
public class OpenIddictEntityFrameworkSessionStore :
OpenIddictEntityFrameworkSessionStore<OpenIddictEntityFrameworkSession,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkToken, string>
{
public OpenIddictEntityFrameworkSessionStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
: base(cache, context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the sessions stored in a database.
/// </summary>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkSessionStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[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 TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkSessionStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Context = context ?? throw new ArgumentNullException(nameof(context));
Options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <summary>
/// Gets the memory cache associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected IOpenIddictEntityFrameworkContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkOptions> Options { get; }
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TSession>().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TSession>(), state).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask CreateAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TSession>().Add(session);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask DeleteAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TSession>().Remove(session);
try
{
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
context.Entry(session).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,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TSession> sessions = context.Set<TSession>()
.Include(session => session.Application)
.Include(session => session.Authorization);
if (!string.IsNullOrEmpty(query.Subject))
{
sessions = sessions.Where(session => session.Subject == query.Subject);
}
if (!string.IsNullOrEmpty(query.ApplicationId))
{
var key = ConvertIdentifierFromString(query.ApplicationId);
sessions = sessions.Where(session => session.Application!.Id!.Equals(key));
}
if (!string.IsNullOrEmpty(query.LoginId))
{
sessions = sessions.Where(session => session.LoginId == query.LoginId);
}
if (!string.IsNullOrEmpty(query.Status))
{
sessions = sessions.Where(session => session.Status == query.Status);
}
await foreach (var session in sessions.AsAsyncEnumerable(cancellationToken))
{
yield return session;
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
await foreach (var session in
(from session in context.Set<TSession>().Include(session => session.Application).Include(session => session.Authorization)
where session.Application!.Id!.Equals(key)
select session).AsAsyncEnumerable(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
await foreach (var session in
(from session in context.Set<TSession>().Include(session => session.Application).Include(session => session.Authorization)
where session.Authorization!.Id!.Equals(key)
select session).AsAsyncEnumerable(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask<TSession?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return await context.Set<TSession>().FindAsync(cancellationToken, [key]);
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindByLoginIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var session in
(from session in context.Set<TSession>().Include(session => session.Application).Include(session => session.Authorization)
where session.LoginId == identifier
select session).AsAsyncEnumerable(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindBySubjectAsync(string subject, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(subject);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var session in
(from session in context.Set<TSession>().Include(session => session.Application).Include(session => session.Authorization)
where session.Subject == subject
select session).AsAsyncEnumerable(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask<string?> GetApplicationIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
// If the application is not attached to the session, try to load it manually.
if (session.Application is null)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(session).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
}
await reference.LoadAsync(cancellationToken);
}
if (session.Application is null)
{
return null;
}
return ConvertIdentifierToString(session.Application.Id);
}
/// <inheritdoc/>
public virtual async ValueTask<TResult?> GetAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TSession>(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<string?> GetAuthorizationIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
// If the application is not attached to the session, try to load it manually.
if (session.Application is null)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(session).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
}
await reference.LoadAsync(cancellationToken);
}
if (session.Application is null)
{
return null;
}
return ConvertIdentifierToString(session.Application.Id);
}
/// <inheritdoc/>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.CreationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(ConvertIdentifierToString(session.Id));
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetLoginIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.LoginId);
}
/// <inheritdoc/>
public virtual ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
if (string.IsNullOrEmpty(session.Properties))
{
return new(ImmutableDictionary.Create<string, JsonElement>());
}
// Note: parsing the stringified properties is an expensive operation.
// To mitigate that, the resulting object is stored in the memory cache.
var key = string.Concat("7c674699-92a2-4607-a11b-a4d4edf9df46", "\x1e", session.Properties);
var properties = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
using var document = JsonDocument.Parse(session.Properties);
var builder = ImmutableDictionary.CreateBuilder<string, JsonElement>();
foreach (var property in document.RootElement.EnumerateObject())
{
builder[property.Name] = property.Value.Clone();
}
return builder.ToImmutable();
})!;
return new(properties);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetStatusAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.Status);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetSubjectAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.Subject);
}
/// <inheritdoc/>
public virtual ValueTask<TSession> InstantiateAsync(CancellationToken cancellationToken)
{
try
{
return new(Activator.CreateInstance<TSession>());
}
catch (MemberAccessException exception)
{
return new(Task.FromException<TSession>(
new InvalidOperationException(SR.GetResourceString(SR.ID0240), exception)));
}
}
/// <inheritdoc/>
public virtual async IAsyncEnumerable<TSession> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TSession> query = context.Set<TSession>().OrderBy(session => session.Id!);
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
await foreach (var session in query.AsAsyncEnumerable(cancellationToken))
{
yield return session;
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var session in query(context.Set<TSession>(), state).AsAsyncEnumerable(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask SetApplicationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
if (!string.IsNullOrEmpty(identifier))
{
var context = await Context.GetDbContextAsync(cancellationToken);
session.Application = await context.Set<TApplication>().FindAsync(
cancellationToken, ConvertIdentifierFromString(identifier))
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0244));
}
else
{
// If the application is not attached to the session, try to load it manually.
if (session.Application is null)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(session).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
}
await reference.LoadAsync(cancellationToken);
}
session.Application = null;
}
}
/// <inheritdoc/>
public virtual async ValueTask SetAuthorizationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
if (!string.IsNullOrEmpty(identifier))
{
var context = await Context.GetDbContextAsync(cancellationToken);
session.Authorization = await context.Set<TAuthorization>().FindAsync(
cancellationToken, ConvertIdentifierFromString(identifier))
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0251));
}
else
{
// If the authorization is not attached to the session, try to load it manually.
if (session.Authorization is null)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(session).Reference(static entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
}
await reference.LoadAsync(cancellationToken);
}
session.Authorization = null;
}
}
/// <inheritdoc/>
public virtual ValueTask SetCreationDateAsync(TSession session, DateTimeOffset? date, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.CreationDate = date?.UtcDateTime;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetPropertiesAsync(TSession session,
ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
if (properties is not { IsEmpty: false })
{
session.Properties = null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var property in properties)
{
writer.WritePropertyName(property.Key);
property.Value.WriteTo(writer);
}
writer.WriteEndObject();
writer.Flush();
session.Properties = Encoding.UTF8.GetString(stream.ToArray());
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetLoginIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.LoginId = identifier;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetStatusAsync(TSession session, string? status, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.Status = status;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetSubjectAsync(TSession session, string? subject, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.Subject = subject;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual async ValueTask UpdateAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TSession>().Attach(session);
// Generate a new concurrency token and attach it
// to the session before persisting the changes.
session.ConcurrencyToken = Guid.NewGuid().ToString();
context.Entry(session).State = EntityState.Modified;
try
{
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
context.Entry(session).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey? ConvertIdentifierFromString(string? identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
// Optimization: if the key is a string, directly return it as-is.
if (typeof(TKey) == typeof(string))
{
return (TKey?) (object?) identifier;
}
else
{
var converter =
#if NET
TypeDescriptor.GetConverterFromRegisteredType(typeof(TKey));
#else
TypeDescriptor.GetConverter(typeof(TKey));
#endif
return (TKey?) converter.ConvertFromInvariantString(identifier);
}
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string? ConvertIdentifierToString(TKey? identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
// Optimization: if the key is a string, directly return it as-is.
if (identifier is string value)
{
return value;
}
else
{
var converter =
#if NET
TypeDescriptor.GetConverterFromRegisteredType(typeof(TKey));
#else
TypeDescriptor.GetConverter(typeof(TKey));
#endif
return converter.ConvertToInvariantString(identifier);
}
}
}

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

@ -136,38 +136,37 @@ public class OpenIddictEntityFrameworkTokenStore<
/// <inheritdoc/> /// <inheritdoc/>
public virtual async IAsyncEnumerable<TToken> FindAsync( public virtual async IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status, string? Type) query,
string? status, string? type, [EnumeratorCancellation] CancellationToken cancellationToken) [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var context = await Context.GetDbContextAsync(cancellationToken); var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TToken> query = context.Set<TToken>() IQueryable<TToken> tokens = context.Set<TToken>()
.Include(token => token.Application) .Include(token => token.Application)
.Include(token => token.Authorization); .Include(token => token.Authorization);
if (!string.IsNullOrEmpty(subject)) if (!string.IsNullOrEmpty(query.Subject))
{ {
query = query.Where(token => token.Subject == subject); tokens = tokens.Where(token => token.Subject == query.Subject);
} }
if (!string.IsNullOrEmpty(client)) if (!string.IsNullOrEmpty(query.ApplicationId))
{ {
var key = ConvertIdentifierFromString(client); var key = ConvertIdentifierFromString(query.ApplicationId);
tokens = tokens.Where(token => token.Application!.Id!.Equals(key));
query = query.Where(token => token.Application!.Id!.Equals(key));
} }
if (!string.IsNullOrEmpty(status)) if (!string.IsNullOrEmpty(query.Status))
{ {
query = query.Where(token => token.Status == status); tokens = tokens.Where(token => token.Status == query.Status);
} }
if (!string.IsNullOrEmpty(type)) if (!string.IsNullOrEmpty(query.Type))
{ {
query = query.Where(token => token.Type == type); tokens = tokens.Where(token => token.Type == query.Type);
} }
await foreach (var token in query.AsAsyncEnumerable(cancellationToken)) await foreach (var token in tokens.AsAsyncEnumerable(cancellationToken))
{ {
yield return token; yield return token;
} }
@ -274,12 +273,12 @@ public class OpenIddictEntityFrameworkTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
var context = await Context.GetDbContextAsync(cancellationToken);
// If the application is not attached to the token, try to load it manually. // If the application is not attached to the token, try to load it manually.
if (token.Application is null) if (token.Application is null)
{ {
var reference = context.Entry(token).Reference(entry => entry.Application); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(token).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return null; return null;
@ -316,12 +315,12 @@ public class OpenIddictEntityFrameworkTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
var context = await Context.GetDbContextAsync(cancellationToken);
// If the authorization is not attached to the token, try to load it manually. // If the authorization is not attached to the token, try to load it manually.
if (token.Authorization is null) if (token.Authorization is null)
{ {
var reference = context.Entry(token).Reference(entry => entry.Authorization); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(token).Reference(static entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return null; return null;
@ -791,13 +790,13 @@ public class OpenIddictEntityFrameworkTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier)) if (!string.IsNullOrEmpty(identifier))
{ {
var context = await Context.GetDbContextAsync(cancellationToken);
token.Application = await context.Set<TApplication>().FindAsync( token.Application = await context.Set<TApplication>().FindAsync(
cancellationToken, ConvertIdentifierFromString(identifier)) cancellationToken, ConvertIdentifierFromString(identifier))
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0250)); ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0244));
} }
else else
@ -805,7 +804,9 @@ public class OpenIddictEntityFrameworkTokenStore<
// If the application is not attached to the token, try to load it manually. // If the application is not attached to the token, try to load it manually.
if (token.Application is null) if (token.Application is null)
{ {
var reference = context.Entry(token).Reference(entry => entry.Application); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(token).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return; return;
@ -823,10 +824,10 @@ public class OpenIddictEntityFrameworkTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier)) if (!string.IsNullOrEmpty(identifier))
{ {
var context = await Context.GetDbContextAsync(cancellationToken);
token.Authorization = await context.Set<TAuthorization>().FindAsync( token.Authorization = await context.Set<TAuthorization>().FindAsync(
cancellationToken, ConvertIdentifierFromString(identifier)) cancellationToken, ConvertIdentifierFromString(identifier))
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0251)); ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0251));
@ -837,7 +838,9 @@ public class OpenIddictEntityFrameworkTokenStore<
// If the authorization is not attached to the token, try to load it manually. // If the authorization is not attached to the token, try to load it manually.
if (token.Authorization is null) if (token.Authorization is null)
{ {
var reference = context.Entry(token).Reference(entry => entry.Authorization); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(token).Reference(static entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return; return;

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

@ -13,7 +13,10 @@ namespace OpenIddict.EntityFrameworkCore.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict application. /// Represents an OpenIddict application.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreApplication : OpenIddictEntityFrameworkCoreApplication<string, OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreToken> public class OpenIddictEntityFrameworkCoreApplication :
OpenIddictEntityFrameworkCoreApplication<string,
OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreToken>
{ {
public OpenIddictEntityFrameworkCoreApplication() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkCoreApplication() => Id = Guid.NewGuid().ToString();
} }
@ -21,7 +24,10 @@ public class OpenIddictEntityFrameworkCoreApplication : OpenIddictEntityFramewor
/// <summary> /// <summary>
/// Represents an OpenIddict application. /// Represents an OpenIddict application.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreApplication<TKey> : OpenIddictEntityFrameworkCoreApplication<TKey, OpenIddictEntityFrameworkCoreAuthorization<TKey>, OpenIddictEntityFrameworkCoreToken<TKey>> public class OpenIddictEntityFrameworkCoreApplication<TKey> :
OpenIddictEntityFrameworkCoreApplication<TKey,
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>>
where TKey : notnull, IEquatable<TKey>; where TKey : notnull, IEquatable<TKey>;
/// <summary> /// <summary>

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

@ -12,7 +12,10 @@ namespace OpenIddict.EntityFrameworkCore.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict authorization. /// Represents an OpenIddict authorization.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreAuthorization : OpenIddictEntityFrameworkCoreAuthorization<string, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreToken> public class OpenIddictEntityFrameworkCoreAuthorization :
OpenIddictEntityFrameworkCoreAuthorization<string,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreToken>
{ {
public OpenIddictEntityFrameworkCoreAuthorization() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkCoreAuthorization() => Id = Guid.NewGuid().ToString();
} }
@ -20,7 +23,10 @@ public class OpenIddictEntityFrameworkCoreAuthorization : OpenIddictEntityFramew
/// <summary> /// <summary>
/// Represents an OpenIddict authorization. /// Represents an OpenIddict authorization.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreAuthorization<TKey> : OpenIddictEntityFrameworkCoreAuthorization<TKey, OpenIddictEntityFrameworkCoreApplication<TKey>, OpenIddictEntityFrameworkCoreToken<TKey>> public class OpenIddictEntityFrameworkCoreAuthorization<TKey> :
OpenIddictEntityFrameworkCoreAuthorization<TKey,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>>
where TKey : notnull, IEquatable<TKey>; where TKey : notnull, IEquatable<TKey>;
/// <summary> /// <summary>

3
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreResource.cs

@ -12,7 +12,8 @@ namespace OpenIddict.EntityFrameworkCore.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict resource. /// Represents an OpenIddict resource.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreResource : OpenIddictEntityFrameworkCoreResource<string> public class OpenIddictEntityFrameworkCoreResource :
OpenIddictEntityFrameworkCoreResource<string>
{ {
public OpenIddictEntityFrameworkCoreResource() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkCoreResource() => Id = Guid.NewGuid().ToString();
} }

3
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs

@ -12,7 +12,8 @@ namespace OpenIddict.EntityFrameworkCore.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict scope. /// Represents an OpenIddict scope.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreScope : OpenIddictEntityFrameworkCoreScope<string> public class OpenIddictEntityFrameworkCoreScope :
OpenIddictEntityFrameworkCoreScope<string>
{ {
public OpenIddictEntityFrameworkCoreScope() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkCoreScope() => Id = Guid.NewGuid().ToString();
} }

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

@ -0,0 +1,85 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Diagnostics;
using System.Text.Json;
namespace OpenIddict.EntityFrameworkCore.Models;
/// <summary>
/// Represents an OpenIddict session.
/// </summary>
public class OpenIddictEntityFrameworkCoreSession :
OpenIddictEntityFrameworkCoreSession<string,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreAuthorization>
{
public OpenIddictEntityFrameworkCoreSession() => Id = Guid.NewGuid().ToString();
}
/// <summary>
/// Represents an OpenIddict session.
/// </summary>
public class OpenIddictEntityFrameworkCoreSession<TKey> :
OpenIddictEntityFrameworkCoreSession<TKey,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<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>
where TKey : notnull, IEquatable<TKey>
where TApplication : class
where TAuthorization : class
{
/// <summary>
/// Gets or sets the application associated with the session.
/// </summary>
public virtual TApplication? Application { get; set; }
/// <summary>
/// Gets or sets the authorization associated with the session.
/// </summary>
public virtual TAuthorization? Authorization { get; set; }
/// <summary>
/// Gets or sets the concurrency token of the session.
/// </summary>
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the UTC creation date of the session.
/// </summary>
public virtual DateTime? CreationDate { get; set; }
/// <summary>
/// Gets or sets the unique identifier of the session.
/// </summary>
public virtual TKey? Id { get; set; }
/// <summary>
/// Gets or sets the login identifier of the session.
/// </summary>
public virtual string? LoginId { get; set; }
/// <summary>
/// Gets or sets the additional properties of the session.
/// </summary>
public virtual IDictionary<string, JsonElement>? Properties { get; set; }
/// <summary>
/// Gets or sets the status of the session.
/// </summary>
public virtual string? Status { get; set; }
/// <summary>
/// Gets or sets the subject of the session.
/// </summary>
public virtual string? Subject { get; set; }
}

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

@ -12,7 +12,10 @@ namespace OpenIddict.EntityFrameworkCore.Models;
/// <summary> /// <summary>
/// Represents an OpenIddict token. /// Represents an OpenIddict token.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreToken : OpenIddictEntityFrameworkCoreToken<string, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreAuthorization> public class OpenIddictEntityFrameworkCoreToken :
OpenIddictEntityFrameworkCoreToken<string,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreAuthorization>
{ {
public OpenIddictEntityFrameworkCoreToken() => Id = Guid.NewGuid().ToString(); public OpenIddictEntityFrameworkCoreToken() => Id = Guid.NewGuid().ToString();
} }
@ -20,7 +23,10 @@ public class OpenIddictEntityFrameworkCoreToken : OpenIddictEntityFrameworkCoreT
/// <summary> /// <summary>
/// Represents an OpenIddict token. /// Represents an OpenIddict token.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreToken<TKey> : OpenIddictEntityFrameworkCoreToken<TKey, OpenIddictEntityFrameworkCoreApplication<TKey>, OpenIddictEntityFrameworkCoreAuthorization<TKey>> public class OpenIddictEntityFrameworkCoreToken<TKey> :
OpenIddictEntityFrameworkCoreToken<TKey,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>>
where TKey : notnull, IEquatable<TKey>; where TKey : notnull, IEquatable<TKey>;
/// <summary> /// <summary>
@ -33,12 +39,12 @@ public class OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorizati
where TAuthorization : class where TAuthorization : class
{ {
/// <summary> /// <summary>
/// Gets or sets the application associated with the current token. /// Gets or sets the application associated with the token.
/// </summary> /// </summary>
public virtual TApplication? Application { get; set; } public virtual TApplication? Application { get; set; }
/// <summary> /// <summary>
/// Gets or sets the authorization associated with the current token. /// Gets or sets the authorization associated with the token.
/// </summary> /// </summary>
public virtual TAuthorization? Authorization { get; set; } public virtual TAuthorization? Authorization { get; set; }

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

@ -0,0 +1,85 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Defines a relational mapping for the session entity.
/// </summary>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkCoreSessionConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[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 TKey : notnull, IEquatable<TKey>
{
public void Configure(EntityTypeBuilder<TSession> builder)
{
ArgumentNullException.ThrowIfNull(builder);
// Warning: optional foreign keys MUST NOT be added as CLR properties because
// Entity Framework would throw an exception due to the TKey generic parameter
// being non-nullable when using value types like short, int, long or Guid.
builder.Property(static session => session.ConcurrencyToken)
.HasMaxLength(50)
.IsConcurrencyToken();
builder.HasKey(static session => session.Id);
builder.Property(static session => session.Id)
.ValueGeneratedOnAdd();
if (typeof(TKey) == typeof(string))
{
builder.Property(static session => session.Id)
.HasMaxLength(100);
}
builder.Property(static session => session.LoginId)
.HasMaxLength(100);
builder.HasIndex(static session => session.LoginId)
.IsUnique();
builder.Property(static session => session.Status)
.HasMaxLength(50);
builder.Property(static session => session.Subject)
.HasMaxLength(400);
builder.Property(static session => session.Properties)
.HasConversion(
static value => JsonSerializer.Serialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement),
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement),
CreateDictionaryComparer<JsonElement>());
builder.ToTable("OpenIddictSessions");
static ValueComparer CreateDictionaryComparer<TValue>() => new ValueComparer<IDictionary<string, TValue>>(
static (left, right) => ReferenceEquals(left, right) || (left != null && right != null && left.SequenceEqual(right)),
static value => value.Aggregate(0, static (hash, value) => HashCode.Combine(hash, value)),
static value => value.ToDictionary());
}
}

7
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs

@ -77,6 +77,7 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
OpenIddictEntityFrameworkCoreAuthorization<TKey>, OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreResource<TKey>, OpenIddictEntityFrameworkCoreResource<TKey>,
OpenIddictEntityFrameworkCoreScope<TKey>, OpenIddictEntityFrameworkCoreScope<TKey>,
OpenIddictEntityFrameworkCoreSession<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>, TKey>(); OpenIddictEntityFrameworkCoreToken<TKey>, TKey>();
/// <summary> /// <summary>
@ -89,12 +90,14 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>() [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>()
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken> where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken> where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey> where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey> where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization> where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
@ -114,6 +117,8 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
provider.GetRequiredService<OpenIddictResourceManager<TResource>>())); provider.GetRequiredService<OpenIddictResourceManager<TResource>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeManager>(static provider => Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeManager>(static provider =>
provider.GetRequiredService<OpenIddictScopeManager<TScope>>())); provider.GetRequiredService<OpenIddictScopeManager<TScope>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictSessionManager>(static provider =>
provider.GetRequiredService<OpenIddictSessionManager<TSession>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenManager>(static provider => Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenManager>(static provider =>
provider.GetRequiredService<OpenIddictTokenManager<TToken>>())); provider.GetRequiredService<OpenIddictTokenManager<TToken>>()));
@ -125,6 +130,8 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
OpenIddictEntityFrameworkCoreResourceStore<TResource, TKey>>()); OpenIddictEntityFrameworkCoreResourceStore<TResource, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeStore<TScope>, Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeStore<TScope>,
OpenIddictEntityFrameworkCoreScopeStore<TScope, TKey>>()); OpenIddictEntityFrameworkCoreScopeStore<TScope, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictSessionStore<TSession>,
OpenIddictEntityFrameworkCoreSessionStore<TSession, TApplication, TAuthorization, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenStore<TToken>, Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenStore<TToken>,
OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuthorization, TKey>>()); OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuthorization, TKey>>());

4
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs

@ -20,12 +20,14 @@ public sealed class OpenIddictEntityFrameworkCoreCustomizer<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : RelationalModelCustomizer [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : RelationalModelCustomizer
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken> where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken> where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey> where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey> where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization> where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
@ -41,7 +43,7 @@ public sealed class OpenIddictEntityFrameworkCoreCustomizer<
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);
// Register the OpenIddict entity sets. // Register the OpenIddict entity sets.
modelBuilder.UseOpenIddict<TApplication, TAuthorization, TResource, TScope, TToken, TKey>(); modelBuilder.UseOpenIddict<TApplication, TAuthorization, TResource, TScope, TSession, TToken, TKey>();
base.Customize(modelBuilder, context); base.Customize(modelBuilder, context);
} }

2
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs

@ -35,12 +35,14 @@ public static class OpenIddictEntityFrameworkCoreExtensions
.SetDefaultAuthorizationEntity<OpenIddictEntityFrameworkCoreAuthorization>() .SetDefaultAuthorizationEntity<OpenIddictEntityFrameworkCoreAuthorization>()
.SetDefaultResourceEntity<OpenIddictEntityFrameworkCoreResource>() .SetDefaultResourceEntity<OpenIddictEntityFrameworkCoreResource>()
.SetDefaultScopeEntity<OpenIddictEntityFrameworkCoreScope>() .SetDefaultScopeEntity<OpenIddictEntityFrameworkCoreScope>()
.SetDefaultSessionEntity<OpenIddictEntityFrameworkCoreSession>()
.SetDefaultTokenEntity<OpenIddictEntityFrameworkCoreToken>(); .SetDefaultTokenEntity<OpenIddictEntityFrameworkCoreToken>();
builder.ReplaceApplicationStore<OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreApplicationStore>() builder.ReplaceApplicationStore<OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreApplicationStore>()
.ReplaceAuthorizationStore<OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreAuthorizationStore>() .ReplaceAuthorizationStore<OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreAuthorizationStore>()
.ReplaceResourceStore<OpenIddictEntityFrameworkCoreResource, OpenIddictEntityFrameworkCoreResourceStore>() .ReplaceResourceStore<OpenIddictEntityFrameworkCoreResource, OpenIddictEntityFrameworkCoreResourceStore>()
.ReplaceScopeStore<OpenIddictEntityFrameworkCoreScope, OpenIddictEntityFrameworkCoreScopeStore>() .ReplaceScopeStore<OpenIddictEntityFrameworkCoreScope, OpenIddictEntityFrameworkCoreScopeStore>()
.ReplaceSessionStore<OpenIddictEntityFrameworkCoreSession, OpenIddictEntityFrameworkCoreSessionStore>()
.ReplaceTokenStore<OpenIddictEntityFrameworkCoreToken, OpenIddictEntityFrameworkCoreTokenStore>(); .ReplaceTokenStore<OpenIddictEntityFrameworkCoreToken, OpenIddictEntityFrameworkCoreTokenStore>();
// Note: a default context factory is always registered to make debugging easier when // Note: a default context factory is always registered to make debugging easier when

21
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs

@ -27,6 +27,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreResource, OpenIddictEntityFrameworkCoreResource,
OpenIddictEntityFrameworkCoreScope, OpenIddictEntityFrameworkCoreScope,
OpenIddictEntityFrameworkCoreSession,
OpenIddictEntityFrameworkCoreToken, string>(); OpenIddictEntityFrameworkCoreToken, string>();
/// <summary> /// <summary>
@ -59,6 +60,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
OpenIddictEntityFrameworkCoreAuthorization<TKey>, OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreResource<TKey>, OpenIddictEntityFrameworkCoreResource<TKey>,
OpenIddictEntityFrameworkCoreScope<TKey>, OpenIddictEntityFrameworkCoreScope<TKey>,
OpenIddictEntityFrameworkCoreSession<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>, TKey>(); OpenIddictEntityFrameworkCoreToken<TKey>, TKey>();
/// <summary> /// <summary>
@ -87,7 +89,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Note: when using custom entities, the new entities MUST be registered by calling /// Note: when using custom entities, the new entities MUST be registered by calling
/// <see cref="OpenIddictEntityFrameworkCoreBuilder.ReplaceDefaultEntities{TApplication, TAuthorization, TResource, TScope, TToken, TKey}"/>. /// <see cref="OpenIddictEntityFrameworkCoreBuilder.ReplaceDefaultEntities{TApplication, TAuthorization, TResource, TScope, TSession, TToken, TKey}"/>.
/// </remarks> /// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework Core context.</param> /// <param name="builder">The builder used to configure the Entity Framework Core context.</param>
/// <returns>The Entity Framework Core context builder.</returns> /// <returns>The Entity Framework Core context builder.</returns>
@ -96,6 +98,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(
this DbContextOptionsBuilder builder) this DbContextOptionsBuilder builder)
@ -103,13 +106,14 @@ public static class OpenIddictEntityFrameworkCoreHelpers
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken> where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey> where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey> where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization> where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(builder);
return ReplaceService<IModelCustomizer, return ReplaceService<IModelCustomizer,
OpenIddictEntityFrameworkCoreCustomizer<TApplication, TAuthorization, TResource, TScope, TToken, TKey>>(builder); OpenIddictEntityFrameworkCoreCustomizer<TApplication, TAuthorization, TResource, TScope, TSession, TToken, TKey>>(builder);
static DbContextOptionsBuilder ReplaceService< static DbContextOptionsBuilder ReplaceService<
TService, TService,
@ -124,7 +128,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Note: when using custom entities, the new entities MUST be registered by calling /// Note: when using custom entities, the new entities MUST be registered by calling
/// <see cref="OpenIddictEntityFrameworkCoreBuilder.ReplaceDefaultEntities{TApplication, TAuthorization, TResource, TScope, TToken, TKey}"/>. /// <see cref="OpenIddictEntityFrameworkCoreBuilder.ReplaceDefaultEntities{TApplication, TAuthorization, TResource, TScope, TSession, TToken, TKey}"/>.
/// </remarks> /// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework Core context.</param> /// <param name="builder">The builder used to configure the Entity Framework Core context.</param>
/// <returns>The Entity Framework Core context builder.</returns> /// <returns>The Entity Framework Core context builder.</returns>
@ -133,6 +137,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey,
TContext>( TContext>(
@ -141,11 +146,12 @@ public static class OpenIddictEntityFrameworkCoreHelpers
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken> where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey> where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey> where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization> where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
where TContext : DbContext where TContext : DbContext
{ {
builder.UseOpenIddict<TApplication, TAuthorization, TResource, TScope, TToken, TKey>(); builder.UseOpenIddict<TApplication, TAuthorization, TResource, TScope, TSession, TToken, TKey>();
return builder; return builder;
} }
@ -160,6 +166,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreResource, OpenIddictEntityFrameworkCoreResource,
OpenIddictEntityFrameworkCoreScope, OpenIddictEntityFrameworkCoreScope,
OpenIddictEntityFrameworkCoreSession,
OpenIddictEntityFrameworkCoreToken, string>(); OpenIddictEntityFrameworkCoreToken, string>();
/// <summary> /// <summary>
@ -178,6 +185,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
OpenIddictEntityFrameworkCoreAuthorization<TKey>, OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreResource<TKey>, OpenIddictEntityFrameworkCoreResource<TKey>,
OpenIddictEntityFrameworkCoreScope<TKey>, OpenIddictEntityFrameworkCoreScope<TKey>,
OpenIddictEntityFrameworkCoreSession<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>, TKey>(); OpenIddictEntityFrameworkCoreToken<TKey>, TKey>();
/// <summary> /// <summary>
@ -186,7 +194,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Note: when using custom entities, the new entities MUST be registered by calling /// Note: when using custom entities, the new entities MUST be registered by calling
/// <see cref="OpenIddictEntityFrameworkCoreBuilder.ReplaceDefaultEntities{TApplication, TAuthorization, TResource, TScope, TToken, TKey}"/>. /// <see cref="OpenIddictEntityFrameworkCoreBuilder.ReplaceDefaultEntities{TApplication, TAuthorization, TResource, TScope, TSession, TToken, TKey}"/>.
/// </remarks> /// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework Core context.</param> /// <param name="builder">The builder used to configure the Entity Framework Core context.</param>
/// <returns>The Entity Framework Core context builder.</returns> /// <returns>The Entity Framework Core context builder.</returns>
@ -195,12 +203,14 @@ public static class OpenIddictEntityFrameworkCoreHelpers
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TResource,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this ModelBuilder builder) [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this ModelBuilder builder)
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken> where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken> where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey> where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey> where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization> where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
@ -211,6 +221,7 @@ public static class OpenIddictEntityFrameworkCoreHelpers
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreAuthorizationConfiguration<TAuthorization, TApplication, TToken, TKey>()) .ApplyConfiguration(new OpenIddictEntityFrameworkCoreAuthorizationConfiguration<TAuthorization, TApplication, TToken, TKey>())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreResourceConfiguration<TResource, TKey>()) .ApplyConfiguration(new OpenIddictEntityFrameworkCoreResourceConfiguration<TResource, TKey>())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreScopeConfiguration<TScope, 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, TKey>());
} }
} }

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

@ -217,44 +217,47 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
/// <inheritdoc/> /// <inheritdoc/>
public virtual async IAsyncEnumerable<TAuthorization> FindAsync( public virtual async IAsyncEnumerable<TAuthorization> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status,
string? status, string? type, string? Type, ImmutableArray<string>? RequiredScopes) query,
ImmutableArray<string>? scopes, [EnumeratorCancellation] CancellationToken cancellationToken) [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var context = await Context.GetDbContextAsync(cancellationToken); var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TAuthorization> query = context.Set<TAuthorization>().Include(authorization => authorization.Application).AsTracking(); IQueryable<TAuthorization> authorizations = context.Set<TAuthorization>().Include(authorization => authorization.Application).AsTracking();
if (!string.IsNullOrEmpty(subject)) if (!string.IsNullOrEmpty(query.Subject))
{ {
query = query.Where(authorization => authorization.Subject == subject); authorizations = authorizations.Where(authorization => authorization.Subject == query.Subject);
} }
if (!string.IsNullOrEmpty(client)) if (!string.IsNullOrEmpty(query.ApplicationId))
{ {
var key = ConvertIdentifierFromString(client); var key = ConvertIdentifierFromString(query.ApplicationId);
authorizations = authorizations.Where(authorization => authorization.Application!.Id!.Equals(key));
query = query.Where(authorization => authorization.Application!.Id!.Equals(key));
} }
if (!string.IsNullOrEmpty(status)) if (!string.IsNullOrEmpty(query.Status))
{ {
query = query.Where(authorization => authorization.Status == status); authorizations = authorizations.Where(authorization => authorization.Status == query.Status);
} }
if (!string.IsNullOrEmpty(type)) if (!string.IsNullOrEmpty(query.Type))
{ {
query = query.Where(authorization => authorization.Type == type); authorizations = authorizations.Where(authorization => authorization.Type == query.Type);
} }
await foreach (var authorization in query.AsAsyncEnumerable().WithCancellation(cancellationToken)) // Note: Entity Framework 6.x cannot translate the logic used to filter authorizations by scopes in a
// SQL query so the filtering is done manually after the results have been retrieved from the database.
await foreach (var authorization in authorizations.AsAsyncEnumerable().WithCancellation(cancellationToken))
{ {
if (scopes is null || (await GetScopesAsync(authorization, cancellationToken)) if (query.RequiredScopes is { IsDefaultOrEmpty: false } scopes && !(await GetScopesAsync(authorization, cancellationToken))
.ToHashSet(StringComparer.Ordinal) .ToHashSet(StringComparer.Ordinal)
.IsSupersetOf(scopes)) .IsSupersetOf(scopes))
{ {
yield return authorization; continue;
} }
yield return authorization;
} }
} }
@ -317,12 +320,12 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
// If the application is not attached to the authorization, try to load it manually. // If the application is not attached to the authorization, try to load it manually.
if (authorization.Application is null) if (authorization.Application is null)
{ {
var reference = context.Entry(authorization).Reference(entry => entry.Application); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(authorization).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return null; return null;
@ -786,12 +789,13 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier)) if (!string.IsNullOrEmpty(identifier))
{ {
var context = await Context.GetDbContextAsync(cancellationToken);
authorization.Application = await context.Set<TApplication>() authorization.Application = await context.Set<TApplication>()
.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken); .FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0244));
} }
else else
@ -799,7 +803,9 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
// If the application is not attached to the authorization, try to load it manually. // If the application is not attached to the authorization, try to load it manually.
if (authorization.Application is null) if (authorization.Application is null)
{ {
var reference = context.Entry(authorization).Reference(entry => entry.Application); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(authorization).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return; return;

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

@ -19,7 +19,8 @@ namespace OpenIddict.EntityFrameworkCore;
/// <summary> /// <summary>
/// Provides methods allowing to manage the resources stored in a database. /// Provides methods allowing to manage the resources stored in a database.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreResourceStore : OpenIddictEntityFrameworkCoreResourceStore<OpenIddictEntityFrameworkCoreResource, string> public class OpenIddictEntityFrameworkCoreResourceStore :
OpenIddictEntityFrameworkCoreResourceStore<OpenIddictEntityFrameworkCoreResource, string>
{ {
public OpenIddictEntityFrameworkCoreResourceStore( public OpenIddictEntityFrameworkCoreResourceStore(
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
@ -34,7 +35,8 @@ public class OpenIddictEntityFrameworkCoreResourceStore : OpenIddictEntityFramew
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam> /// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreResourceStore< public class OpenIddictEntityFrameworkCoreResourceStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : OpenIddictEntityFrameworkCoreResourceStore<OpenIddictEntityFrameworkCoreResource<TKey>, TKey> [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> :
OpenIddictEntityFrameworkCoreResourceStore<OpenIddictEntityFrameworkCoreResource<TKey>, TKey>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreResourceStore( public OpenIddictEntityFrameworkCoreResourceStore(

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

@ -19,7 +19,8 @@ namespace OpenIddict.EntityFrameworkCore;
/// <summary> /// <summary>
/// Provides methods allowing to manage the scopes stored in a database. /// Provides methods allowing to manage the scopes stored in a database.
/// </summary> /// </summary>
public class OpenIddictEntityFrameworkCoreScopeStore : OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope, string> public class OpenIddictEntityFrameworkCoreScopeStore :
OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope, string>
{ {
public OpenIddictEntityFrameworkCoreScopeStore( public OpenIddictEntityFrameworkCoreScopeStore(
IOpenIddictEntityFrameworkCoreContext context, IOpenIddictEntityFrameworkCoreContext context,
@ -34,7 +35,8 @@ public class OpenIddictEntityFrameworkCoreScopeStore : OpenIddictEntityFramework
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam> /// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreScopeStore< public class OpenIddictEntityFrameworkCoreScopeStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope<TKey>, TKey> [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> :
OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope<TKey>, TKey>
where TKey : notnull, IEquatable<TKey> where TKey : notnull, IEquatable<TKey>
{ {
public OpenIddictEntityFrameworkCoreScopeStore( public OpenIddictEntityFrameworkCoreScopeStore(

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

@ -0,0 +1,668 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFrameworkCore.Models;
using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Provides methods allowing to manage the sessions stored in a database.
/// </summary>
public class OpenIddictEntityFrameworkCoreSessionStore :
OpenIddictEntityFrameworkCoreSessionStore<OpenIddictEntityFrameworkCoreSession,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreToken, string>
{
public OpenIddictEntityFrameworkCoreSessionStore(
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the sessions stored in a database.
/// </summary>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreSessionStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> :
OpenIddictEntityFrameworkCoreSessionStore<OpenIddictEntityFrameworkCoreSession<TKey>,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>, TKey>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreSessionStore(
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the sessions stored in a database.
/// </summary>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreSessionStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[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 TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreSessionStore(
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
Options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected IOpenIddictEntityFrameworkCoreContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> Options { get; }
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TSession>().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TSession>(), state).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask CreateAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Add(session);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask DeleteAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Remove(session);
try
{
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
context.Entry(session).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,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TSession> sessions = context.Set<TSession>()
.Include(session => session.Application)
.Include(session => session.Authorization)
.AsTracking();
if (!string.IsNullOrEmpty(query.Subject))
{
sessions = sessions.Where(session => session.Subject == query.Subject);
}
if (!string.IsNullOrEmpty(query.ApplicationId))
{
var key = ConvertIdentifierFromString(query.ApplicationId);
sessions = sessions.Where(session => session.Application!.Id!.Equals(key));
}
if (!string.IsNullOrEmpty(query.LoginId))
{
sessions = sessions.Where(session => session.LoginId == query.LoginId);
}
if (!string.IsNullOrEmpty(query.Status))
{
sessions = sessions.Where(session => session.Status == query.Status);
}
await foreach (var session in sessions.AsAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
await foreach (var session in
(from session in context.Set<TSession>()
.Include(session => session.Application)
.Include(session => session.Authorization)
.AsTracking()
where session.Application!.Id!.Equals(key)
select session).AsAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
await foreach (var session in
(from session in context.Set<TSession>()
.Include(session => session.Application)
.Include(session => session.Authorization)
.AsTracking()
where session.Authorization!.Id!.Equals(key)
select session).AsAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask<TSession?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return await context.Set<TSession>().FindAsync([key], cancellationToken);
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindByLoginIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var session in
(from session in context.Set<TSession>()
.Include(session => session.Application)
.Include(session => session.Authorization)
.AsTracking()
where session.LoginId == identifier
select session).AsAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindBySubjectAsync(string subject, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(subject);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var session in
(from session in context.Set<TSession>()
.Include(session => session.Application)
.Include(session => session.Authorization)
.AsTracking()
where session.Subject == subject
select session).AsAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask<string?> GetApplicationIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
// If the application is not attached to the session, try to load it manually.
if (session.Application is null)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(session).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
}
await reference.LoadAsync(cancellationToken);
}
if (session.Application is null)
{
return null;
}
return ConvertIdentifierToString(session.Application.Id);
}
/// <inheritdoc/>
public virtual async ValueTask<TResult?> GetAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TSession>().AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<string?> GetAuthorizationIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
// If the authorization is not attached to the session, try to load it manually.
if (session.Authorization is null)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(session).Reference(static entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
}
await reference.LoadAsync(cancellationToken);
}
if (session.Authorization is null)
{
return null;
}
return ConvertIdentifierToString(session.Authorization.Id);
}
/// <inheritdoc/>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.CreationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(ConvertIdentifierToString(session.Id));
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetLoginIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.LoginId);
}
/// <inheritdoc/>
public virtual ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.Properties is { Count: > 0 } properties ? [.. properties] : []);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetStatusAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.Status);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetSubjectAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.Subject);
}
/// <inheritdoc/>
public virtual ValueTask<TSession> InstantiateAsync(CancellationToken cancellationToken)
{
try
{
return new(Activator.CreateInstance<TSession>());
}
catch (MemberAccessException exception)
{
return new(Task.FromException<TSession>(
new InvalidOperationException(SR.GetResourceString(SR.ID0240), exception)));
}
}
/// <inheritdoc/>
public virtual async IAsyncEnumerable<TSession> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var query = context.Set<TSession>().OrderBy(session => session.Id!).AsTracking();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
await foreach (var session in query.AsAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var session in query(context.Set<TSession>().AsTracking(), state).AsAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask SetApplicationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
if (!string.IsNullOrEmpty(identifier))
{
var context = await Context.GetDbContextAsync(cancellationToken);
session.Application = await context.Set<TApplication>()
.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0244));
}
else
{
// If the application is not attached to the session, try to load it manually.
if (session.Application is null)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(session).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
}
await reference.LoadAsync(cancellationToken);
}
session.Application = null;
}
}
/// <inheritdoc/>
public virtual async ValueTask SetAuthorizationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
if (!string.IsNullOrEmpty(identifier))
{
var context = await Context.GetDbContextAsync(cancellationToken);
session.Authorization = await context.Set<TAuthorization>()
.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0251));
}
else
{
// If the authorization is not attached to the session, try to load it manually.
if (session.Authorization is null)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(session).Reference(static entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
}
await reference.LoadAsync(cancellationToken);
}
session.Authorization = null;
}
}
/// <inheritdoc/>
public virtual ValueTask SetCreationDateAsync(TSession session, DateTimeOffset? date, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.CreationDate = date?.UtcDateTime;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetPropertiesAsync(TSession session,
ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.Properties = properties;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetLoginIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.LoginId = identifier;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetStatusAsync(TSession session, string? status, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.Status = status;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetSubjectAsync(TSession session, string? subject, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.Subject = subject;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual async ValueTask UpdateAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Attach(session);
// Generate a new concurrency token and attach it
// to the session before persisting the changes.
session.ConcurrencyToken = Guid.NewGuid().ToString();
context.Update(session);
try
{
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
context.Entry(session).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey? ConvertIdentifierFromString(string? identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
// Optimization: if the key is a string, directly return it as-is.
if (typeof(TKey) == typeof(string))
{
return (TKey?) (object?) identifier;
}
else
{
var converter = TypeDescriptor.GetConverterFromRegisteredType(typeof(TKey));
return (TKey?) converter.ConvertFromInvariantString(identifier);
}
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string? ConvertIdentifierToString(TKey? identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
// Optimization: if the key is a string, directly return it as-is.
if (identifier is string value)
{
return value;
}
else
{
var converter = TypeDescriptor.GetConverterFromRegisteredType(typeof(TKey));
return converter.ConvertToInvariantString(identifier);
}
}
}

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

@ -143,39 +143,38 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
/// <inheritdoc/> /// <inheritdoc/>
public virtual async IAsyncEnumerable<TToken> FindAsync( public virtual async IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status, string? Type) query,
string? status, string? type, [EnumeratorCancellation] CancellationToken cancellationToken) [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var context = await Context.GetDbContextAsync(cancellationToken); var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TToken> query = context.Set<TToken>() IQueryable<TToken> tokens = context.Set<TToken>()
.Include(token => token.Application) .Include(token => token.Application)
.Include(token => token.Authorization) .Include(token => token.Authorization)
.AsTracking(); .AsTracking();
if (!string.IsNullOrEmpty(subject)) if (!string.IsNullOrEmpty(query.Subject))
{ {
query = query.Where(token => token.Subject == subject); tokens = tokens.Where(token => token.Subject == query.Subject);
} }
if (!string.IsNullOrEmpty(client)) if (!string.IsNullOrEmpty(query.ApplicationId))
{ {
var key = ConvertIdentifierFromString(client); var key = ConvertIdentifierFromString(query.ApplicationId);
tokens = tokens.Where(token => token.Application!.Id!.Equals(key));
query = query.Where(token => token.Application!.Id!.Equals(key));
} }
if (!string.IsNullOrEmpty(status)) if (!string.IsNullOrEmpty(query.Status))
{ {
query = query.Where(token => token.Status == status); tokens = tokens.Where(token => token.Status == query.Status);
} }
if (!string.IsNullOrEmpty(type)) if (!string.IsNullOrEmpty(query.Type))
{ {
query = query.Where(token => token.Type == type); tokens = tokens.Where(token => token.Type == query.Type);
} }
await foreach (var token in query.AsAsyncEnumerable().WithCancellation(cancellationToken)) await foreach (var token in tokens.AsAsyncEnumerable().WithCancellation(cancellationToken))
{ {
yield return token; yield return token;
} }
@ -274,7 +273,10 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
var context = await Context.GetDbContextAsync(cancellationToken); var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var token in await foreach (var token in
(from token in context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization).AsTracking() (from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
where token.Subject == subject where token.Subject == subject
select token).AsAsyncEnumerable().WithCancellation(cancellationToken)) select token).AsAsyncEnumerable().WithCancellation(cancellationToken))
{ {
@ -288,12 +290,12 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
var context = await Context.GetDbContextAsync(cancellationToken);
// If the application is not attached to the token, try to load it manually. // If the application is not attached to the token, try to load it manually.
if (token.Application is null) if (token.Application is null)
{ {
var reference = context.Entry(token).Reference(entry => entry.Application); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(token).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return null; return null;
@ -329,12 +331,12 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
var context = await Context.GetDbContextAsync(cancellationToken);
// If the authorization is not attached to the token, try to load it manually. // If the authorization is not attached to the token, try to load it manually.
if (token.Authorization is null) if (token.Authorization is null)
{ {
var reference = context.Entry(token).Reference(entry => entry.Authorization); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(token).Reference(static entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return null; return null;
@ -877,12 +879,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier)) if (!string.IsNullOrEmpty(identifier))
{ {
var context = await Context.GetDbContextAsync(cancellationToken);
token.Application = await context.Set<TApplication>() token.Application = await context.Set<TApplication>()
.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken); .FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0244));
} }
else else
@ -890,7 +893,9 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
// If the application is not attached to the token, try to load it manually. // If the application is not attached to the token, try to load it manually.
if (token.Application is null) if (token.Application is null)
{ {
var reference = context.Entry(token).Reference(entry => entry.Application); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(token).Reference(static entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return; return;
@ -908,12 +913,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier)) if (!string.IsNullOrEmpty(identifier))
{ {
var context = await Context.GetDbContextAsync(cancellationToken);
token.Authorization = await context.Set<TAuthorization>() token.Authorization = await context.Set<TAuthorization>()
.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken); .FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken)
?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0251));
} }
else else
@ -921,7 +927,9 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
// If the authorization is not attached to the token, try to load it manually. // If the authorization is not attached to the token, try to load it manually.
if (token.Authorization is null) if (token.Authorization is null)
{ {
var reference = context.Entry(token).Reference(entry => entry.Authorization); var context = await Context.GetDbContextAsync(cancellationToken);
var reference = context.Entry(token).Reference(static entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached) if (reference.EntityEntry.State is EntityState.Detached)
{ {
return; return;

2
src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs

@ -58,7 +58,7 @@ public class OpenIddictMongoDbAuthorization
public virtual string? Status { get; set; } public virtual string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the authorization. /// Gets or sets the subject of the authorization.
/// </summary> /// </summary>
[BsonElement("subject"), BsonIgnoreIfNull] [BsonElement("subject"), BsonIgnoreIfNull]
public virtual string? Subject { get; set; } public virtual string? Subject { get; set; }

70
src/OpenIddict.MongoDb.Models/OpenIddictMongoDbSession.cs

@ -0,0 +1,70 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Diagnostics;
namespace OpenIddict.MongoDb.Models;
/// <summary>
/// Represents an OpenIddict session.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Name = {Name,nq}")]
public class OpenIddictMongoDbSession
{
/// <summary>
/// Gets or sets the identifier of the application associated with the session.
/// </summary>
[BsonElement("application_id"), BsonIgnoreIfDefault]
public virtual ObjectId ApplicationId { get; set; }
/// <summary>
/// Gets or sets the identifier of the authorization associated with the session.
/// </summary>
[BsonElement("authorization_id"), BsonIgnoreIfDefault]
public virtual ObjectId AuthorizationId { get; set; }
/// <summary>
/// Gets or sets the concurrency token of the session.
/// </summary>
[BsonElement("concurrency_token"), BsonIgnoreIfNull]
public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the UTC creation date of the session.
/// </summary>
[BsonElement("creation_date"), BsonIgnoreIfNull]
public virtual DateTime? CreationDate { get; set; }
/// <summary>
/// Gets or sets the unique identifier of the session.
/// </summary>
[BsonId, BsonRequired]
public virtual ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the login identifier of the session.
/// </summary>
[BsonElement("login_id"), BsonIgnoreIfNull]
public virtual string? LoginId { get; set; }
/// <summary>
/// Gets or sets the additional properties of the session.
/// </summary>
[BsonElement("properties"), BsonIgnoreIfNull]
public virtual BsonDocument? Properties { get; set; }
/// <summary>
/// Gets or sets the status of the session.
/// </summary>
[BsonElement("status"), BsonIgnoreIfNull]
public virtual string? Status { get; set; }
/// <summary>
/// Gets or sets the subject of the session.
/// </summary>
[BsonElement("subject"), BsonIgnoreIfNull]
public virtual string? Subject { get; set; }
}

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

@ -89,7 +89,7 @@ public class OpenIddictMongoDbToken
public virtual string? Status { get; set; } public virtual string? Status { get; set; }
/// <summary> /// <summary>
/// Gets or sets the subject associated with the token. /// Gets or sets the subject of the token.
/// </summary> /// </summary>
[BsonElement("subject"), BsonIgnoreIfNull] [BsonElement("subject"), BsonIgnoreIfNull]
public virtual string? Subject { get; set; } public virtual string? Subject { get; set; }

29
src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs

@ -126,6 +126,23 @@ public sealed class OpenIddictMongoDbBuilder
return this; return this;
} }
/// <summary>
/// Configures OpenIddict to use the specified entity as the default session entity.
/// </summary>
/// <returns>The <see cref="OpenIddictMongoDbBuilder"/> instance.</returns>
public OpenIddictMongoDbBuilder ReplaceDefaultSessionEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession>()
where TSession : OpenIddictMongoDbSession
{
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictSessionManager>(static provider =>
provider.GetRequiredService<OpenIddictSessionManager<TSession>>()));
Services.Replace(ServiceDescriptor.Scoped<
IOpenIddictSessionStore<TSession>, OpenIddictMongoDbSessionStore<TSession>>());
return this;
}
/// <summary> /// <summary>
/// Configures OpenIddict to use the specified entity as the default token entity. /// Configures OpenIddict to use the specified entity as the default token entity.
/// </summary> /// </summary>
@ -191,6 +208,18 @@ public sealed class OpenIddictMongoDbBuilder
return Configure(options => options.ScopesCollectionName = name); return Configure(options => options.ScopesCollectionName = name);
} }
/// <summary>
/// Replaces the default sessions collection name (by default, openiddict.sessions).
/// </summary>
/// <param name="name">The collection name</param>
/// <returns>The <see cref="OpenIddictMongoDbBuilder"/> instance.</returns>
public OpenIddictMongoDbBuilder SetSessionsCollectionName(string name)
{
ArgumentException.ThrowIfNullOrEmpty(name);
return Configure(options => options.SessionsCollectionName = name);
}
/// <summary> /// <summary>
/// Replaces the default tokens collection name (by default, openiddict.tokens). /// Replaces the default tokens collection name (by default, openiddict.tokens).
/// </summary> /// </summary>

2
src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs

@ -34,6 +34,7 @@ public static class OpenIddictMongoDbExtensions
.SetDefaultAuthorizationEntity<OpenIddictMongoDbAuthorization>() .SetDefaultAuthorizationEntity<OpenIddictMongoDbAuthorization>()
.SetDefaultResourceEntity<OpenIddictMongoDbResource>() .SetDefaultResourceEntity<OpenIddictMongoDbResource>()
.SetDefaultScopeEntity<OpenIddictMongoDbScope>() .SetDefaultScopeEntity<OpenIddictMongoDbScope>()
.SetDefaultSessionEntity<OpenIddictMongoDbSession>()
.SetDefaultTokenEntity<OpenIddictMongoDbToken>(); .SetDefaultTokenEntity<OpenIddictMongoDbToken>();
// Note: the Mongo stores don't depend on scoped/transient services and thus can // Note: the Mongo stores don't depend on scoped/transient services and thus can
@ -42,6 +43,7 @@ public static class OpenIddictMongoDbExtensions
.ReplaceAuthorizationStore<OpenIddictMongoDbAuthorization, OpenIddictMongoDbAuthorizationStore>(ServiceLifetime.Singleton) .ReplaceAuthorizationStore<OpenIddictMongoDbAuthorization, OpenIddictMongoDbAuthorizationStore>(ServiceLifetime.Singleton)
.ReplaceResourceStore<OpenIddictMongoDbResource, OpenIddictMongoDbResourceStore>(ServiceLifetime.Singleton) .ReplaceResourceStore<OpenIddictMongoDbResource, OpenIddictMongoDbResourceStore>(ServiceLifetime.Singleton)
.ReplaceScopeStore<OpenIddictMongoDbScope, OpenIddictMongoDbScopeStore>(ServiceLifetime.Singleton) .ReplaceScopeStore<OpenIddictMongoDbScope, OpenIddictMongoDbScopeStore>(ServiceLifetime.Singleton)
.ReplaceSessionStore<OpenIddictMongoDbSession, OpenIddictMongoDbSessionStore>(ServiceLifetime.Singleton)
.ReplaceTokenStore<OpenIddictMongoDbToken, OpenIddictMongoDbTokenStore>(ServiceLifetime.Singleton); .ReplaceTokenStore<OpenIddictMongoDbToken, OpenIddictMongoDbTokenStore>(ServiceLifetime.Singleton);
builder.Services.TryAddSingleton<IOpenIddictMongoDbContext, OpenIddictMongoDbContext>(); builder.Services.TryAddSingleton<IOpenIddictMongoDbContext, OpenIddictMongoDbContext>();

5
src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs

@ -37,6 +37,11 @@ public sealed class OpenIddictMongoDbOptions
/// </summary> /// </summary>
public string ScopesCollectionName { get; set; } = "openiddict.scopes"; public string ScopesCollectionName { get; set; } = "openiddict.scopes";
/// <summary>
/// Gets or sets the name of the sessions collection (by default, openiddict.sessions).
/// </summary>
public string SessionsCollectionName { get; set; } = "openiddict.sessions";
/// <summary> /// <summary>
/// Gets or sets the name of the tokens collection (by default, openiddict.tokens). /// Gets or sets the name of the tokens collection (by default, openiddict.tokens).
/// </summary> /// </summary>

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

@ -110,43 +110,40 @@ public class OpenIddictMongoDbAuthorizationStore<
/// <inheritdoc/> /// <inheritdoc/>
public virtual async IAsyncEnumerable<TAuthorization> FindAsync( public virtual async IAsyncEnumerable<TAuthorization> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status,
string? status, string? type, string? Type, ImmutableArray<string>? RequiredScopes) query, [EnumeratorCancellation] CancellationToken cancellationToken)
ImmutableArray<string>? scopes, [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var database = await Context.GetDatabaseAsync(cancellationToken); var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TAuthorization>(Options.CurrentValue.AuthorizationsCollectionName); var collection = database.GetCollection<TAuthorization>(Options.CurrentValue.AuthorizationsCollectionName);
IQueryable<TAuthorization> query = collection.AsQueryable(); IQueryable<TAuthorization> authorizations = collection.AsQueryable();
if (!string.IsNullOrEmpty(subject)) if (!string.IsNullOrEmpty(query.Subject))
{ {
query = query.Where(authorization => authorization.Subject == subject); authorizations = authorizations.Where(authorization => authorization.Subject == query.Subject);
} }
if (!string.IsNullOrEmpty(client)) if (!string.IsNullOrEmpty(query.ApplicationId))
{ {
query = query.Where(authorization => authorization.ApplicationId == ObjectId.Parse(client)); authorizations = authorizations.Where(authorization => authorization.ApplicationId == ObjectId.Parse(query.ApplicationId));
} }
if (!string.IsNullOrEmpty(status)) if (!string.IsNullOrEmpty(query.Status))
{ {
query = query.Where(authorization => authorization.Status == status); authorizations = authorizations.Where(authorization => authorization.Status == query.Status);
} }
if (!string.IsNullOrEmpty(type)) if (!string.IsNullOrEmpty(query.Type))
{ {
query = query.Where(authorization => authorization.Type == type); authorizations = authorizations.Where(authorization => authorization.Type == query.Type);
} }
if (scopes is ImmutableArray<string> values) if (query.RequiredScopes is { IsDefaultOrEmpty: false } scopes)
{ {
// Note: Enumerable.All() is deliberately used without the extension method syntax to ensure authorizations = authorizations.Where(authorization => scopes.All(scope => authorization.Scopes!.Contains(scope)));
// ImmutableArrayExtensions.All() (which is not supported by MongoDB) is not used instead.
query = query.Where(authorization => Enumerable.All(values, scope => authorization.Scopes!.Contains(scope)));
} }
await foreach (var authorization in query.ToAsyncEnumerable().WithCancellation(cancellationToken)) await foreach (var authorization in authorizations.ToAsyncEnumerable().WithCancellation(cancellationToken))
{ {
yield return authorization; yield return authorization;
} }
@ -459,15 +456,7 @@ public class OpenIddictMongoDbAuthorizationStore<
{ {
ArgumentNullException.ThrowIfNull(authorization); ArgumentNullException.ThrowIfNull(authorization);
if (!string.IsNullOrEmpty(identifier)) authorization.ApplicationId = !string.IsNullOrEmpty(identifier) ? ObjectId.Parse(identifier) : ObjectId.Empty;
{
authorization.ApplicationId = ObjectId.Parse(identifier);
}
else
{
authorization.ApplicationId = ObjectId.Empty;
}
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }

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

@ -0,0 +1,499 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using Microsoft.Extensions.Options;
using OpenIddict.MongoDb.Models;
using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.MongoDb;
/// <summary>
/// Provides methods allowing to manage the sessions stored in a database.
/// </summary>
public class OpenIddictMongoDbSessionStore : OpenIddictMongoDbSessionStore<OpenIddictMongoDbSession>
{
public OpenIddictMongoDbSessionStore(
IOpenIddictMongoDbContext context,
IOptionsMonitor<OpenIddictMongoDbOptions> options)
: base(context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the sessions stored in a database.
/// </summary>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
public class OpenIddictMongoDbSessionStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession> : IOpenIddictSessionStore<TSession>
where TSession : OpenIddictMongoDbSession
{
public OpenIddictMongoDbSessionStore(
IOpenIddictMongoDbContext context,
IOptionsMonitor<OpenIddictMongoDbOptions> options)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
Options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected IOpenIddictMongoDbContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictMongoDbOptions> Options { get; }
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
return await collection.CountDocumentsAsync(FilterDefinition<TSession>.Empty, null, cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
return await query(collection.AsQueryable(), state).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask CreateAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
await collection.InsertOneAsync(session, null, cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask DeleteAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
if ((await collection.DeleteOneAsync(entity =>
entity.Id == session.Id &&
entity.ConcurrencyToken == session.ConcurrencyToken, cancellationToken)).DeletedCount is 0)
{
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239));
}
}
/// <inheritdoc/>
public virtual async IAsyncEnumerable<TSession> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
IQueryable<TSession> sessions = collection.AsQueryable();
if (!string.IsNullOrEmpty(query.Subject))
{
sessions = sessions.Where(session => session.Subject == query.Subject);
}
if (!string.IsNullOrEmpty(query.ApplicationId))
{
sessions = sessions.Where(session => session.ApplicationId == ObjectId.Parse(query.ApplicationId));
}
if (!string.IsNullOrEmpty(query.LoginId))
{
sessions = sessions.Where(session => session.LoginId == query.LoginId);
}
if (!string.IsNullOrEmpty(query.Status))
{
sessions = sessions.Where(session => session.Status == query.Status);
}
await foreach (var session in sessions.ToAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
await foreach (var session in collection.Find(session =>
session.ApplicationId == ObjectId.Parse(identifier)).ToAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
await foreach (var session in collection.Find(session =>
session.AuthorizationId == ObjectId.Parse(identifier)).ToAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask<TSession?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
return await collection.Find(session => session.Id == ObjectId.Parse(identifier)).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindByLoginIdAsync(string identifier, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
await foreach (var session in collection.Find(session => session.LoginId == identifier).ToAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TSession> FindBySubjectAsync(string subject, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(subject);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TSession> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
await foreach (var session in collection.Find(session => session.Subject == subject).ToAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetApplicationIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.ApplicationId != ObjectId.Empty ? session.ApplicationId.ToString() : null);
}
/// <inheritdoc/>
public virtual async ValueTask<TResult?> GetAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
return await query(collection.AsQueryable(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetAuthorizationIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.AuthorizationId != ObjectId.Empty ? session.AuthorizationId.ToString() : null);
}
/// <inheritdoc/>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.CreationDate is DateTime date ? DateTime.SpecifyKind(date, DateTimeKind.Utc) : null);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.Id.ToString());
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetLoginIdAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.LoginId);
}
/// <inheritdoc/>
public virtual ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
if (session.Properties is null)
{
return new(ImmutableDictionary.Create<string, JsonElement>());
}
using var document = JsonDocument.Parse(session.Properties.ToJson());
var builder = ImmutableDictionary.CreateBuilder<string, JsonElement>();
foreach (var property in document.RootElement.EnumerateObject())
{
builder[property.Name] = property.Value.Clone();
}
return new(builder.ToImmutable());
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetStatusAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.Status);
}
/// <inheritdoc/>
public virtual ValueTask<string?> GetSubjectAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
return new(session.Subject);
}
/// <inheritdoc/>
public virtual ValueTask<TSession> InstantiateAsync(CancellationToken cancellationToken)
{
try
{
return new(Activator.CreateInstance<TSession>());
}
catch (MemberAccessException exception)
{
return new(Task.FromException<TSession>(
new InvalidOperationException(SR.GetResourceString(SR.ID0240), exception)));
}
}
/// <inheritdoc/>
public virtual async IAsyncEnumerable<TSession> ListAsync(
int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
var query = (IQueryable<TSession>) collection.AsQueryable().OrderBy(session => session.Id);
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
await foreach (var session in ((IAsyncCursorSource<TSession>) query).ToAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return session;
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable().WithCancellation(cancellationToken))
{
yield return element;
}
}
}
/// <inheritdoc/>
public virtual ValueTask SetApplicationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.ApplicationId = !string.IsNullOrEmpty(identifier) ? ObjectId.Parse(identifier) : ObjectId.Empty;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetAuthorizationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.AuthorizationId = !string.IsNullOrEmpty(identifier) ? ObjectId.Parse(identifier) : ObjectId.Empty;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetCreationDateAsync(TSession session, DateTimeOffset? date, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.CreationDate = date?.UtcDateTime;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetPropertiesAsync(TSession session,
ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
if (properties is not { IsEmpty: false })
{
session.Properties = null;
return ValueTask.CompletedTask;
}
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartObject();
foreach (var property in properties)
{
writer.WritePropertyName(property.Key);
property.Value.WriteTo(writer);
}
writer.WriteEndObject();
writer.Flush();
session.Properties = BsonDocument.Parse(Encoding.UTF8.GetString(stream.ToArray()));
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetLoginIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.LoginId = identifier;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetStatusAsync(TSession session, string? status, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.Status = status;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual ValueTask SetSubjectAsync(TSession session, string? subject, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
session.Subject = subject;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public virtual async ValueTask UpdateAsync(TSession session, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(session);
// Generate a new concurrency token and attach it
// to the session before persisting the changes.
var timestamp = session.ConcurrencyToken;
session.ConcurrencyToken = Guid.NewGuid().ToString();
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
if ((await collection.ReplaceOneAsync(entity =>
entity.Id == session.Id &&
entity.ConcurrencyToken == timestamp, session, null as ReplaceOptions, cancellationToken)).MatchedCount is 0)
{
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239));
}
}
}

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

@ -106,35 +106,35 @@ public class OpenIddictMongoDbTokenStore<
/// <inheritdoc/> /// <inheritdoc/>
public virtual async IAsyncEnumerable<TToken> FindAsync( public virtual async IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client, (string? Subject, string? ApplicationId, string? Status, string? Type) query,
string? status, string? type, [EnumeratorCancellation] CancellationToken cancellationToken) [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var database = await Context.GetDatabaseAsync(cancellationToken); var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TToken>(Options.CurrentValue.TokensCollectionName); var collection = database.GetCollection<TToken>(Options.CurrentValue.TokensCollectionName);
IQueryable<TToken> query = collection.AsQueryable(); IQueryable<TToken> tokens = collection.AsQueryable();
if (!string.IsNullOrEmpty(subject)) if (!string.IsNullOrEmpty(query.Subject))
{ {
query = query.Where(token => token.Subject == subject); tokens = tokens.Where(token => token.Subject == query.Subject);
} }
if (!string.IsNullOrEmpty(client)) if (!string.IsNullOrEmpty(query.ApplicationId))
{ {
query = query.Where(token => token.ApplicationId == ObjectId.Parse(client)); tokens = tokens.Where(token => token.ApplicationId == ObjectId.Parse(query.ApplicationId));
} }
if (!string.IsNullOrEmpty(status)) if (!string.IsNullOrEmpty(query.Status))
{ {
query = query.Where(token => token.Status == status); tokens = tokens.Where(token => token.Status == query.Status);
} }
if (!string.IsNullOrEmpty(type)) if (!string.IsNullOrEmpty(query.Type))
{ {
query = query.Where(token => token.Type == type); tokens = tokens.Where(token => token.Type == query.Type);
} }
await foreach (var token in query.ToAsyncEnumerable().WithCancellation(cancellationToken)) await foreach (var token in tokens.ToAsyncEnumerable().WithCancellation(cancellationToken))
{ {
yield return token; yield return token;
} }
@ -521,15 +521,7 @@ public class OpenIddictMongoDbTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (!string.IsNullOrEmpty(identifier)) token.ApplicationId = !string.IsNullOrEmpty(identifier) ? ObjectId.Parse(identifier) : ObjectId.Empty;
{
token.ApplicationId = ObjectId.Parse(identifier);
}
else
{
token.ApplicationId = ObjectId.Empty;
}
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }
@ -539,15 +531,7 @@ public class OpenIddictMongoDbTokenStore<
{ {
ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(token);
if (!string.IsNullOrEmpty(identifier)) token.AuthorizationId = !string.IsNullOrEmpty(identifier) ? ObjectId.Parse(identifier) : ObjectId.Empty;
{
token.AuthorizationId = ObjectId.Parse(identifier);
}
else
{
token.AuthorizationId = ObjectId.Empty;
}
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }

1
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs

@ -21,6 +21,7 @@ public static class OpenIddictServerDataProtectionConstants
public const string Expires = ".expires"; public const string Expires = ".expires";
public const string IdentityTokenLifetime = ".identity_token_lifetime"; public const string IdentityTokenLifetime = ".identity_token_lifetime";
public const string InternalAuthorizationId = ".internal_authorization_id"; public const string InternalAuthorizationId = ".internal_authorization_id";
public const string InternalSessionId = ".internal_session_id";
public const string InternalTokenId = ".internal_token_id"; public const string InternalTokenId = ".internal_token_id";
public const string Issued = ".issued"; public const string Issued = ".issued";
public const string Nonce = ".nonce"; public const string Nonce = ".nonce";

5
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs

@ -52,6 +52,7 @@ public sealed class OpenIddictServerDataProtectionFormatter : IOpenIddictServerD
.SetClaim(Claims.Private.ExpirationDate, GetStringProperty(properties, Properties.Expires)) .SetClaim(Claims.Private.ExpirationDate, GetStringProperty(properties, Properties.Expires))
.SetClaim(Claims.Private.Nonce, GetStringProperty(properties, Properties.Nonce)) .SetClaim(Claims.Private.Nonce, GetStringProperty(properties, Properties.Nonce))
.SetClaim(Claims.Private.RedirectUri, GetStringProperty(properties, Properties.OriginalRedirectUri)) .SetClaim(Claims.Private.RedirectUri, GetStringProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.SessionId, GetStringProperty(properties, Properties.InternalSessionId))
.SetClaim(Claims.Private.TokenId, GetStringProperty(properties, Properties.InternalTokenId)); .SetClaim(Claims.Private.TokenId, GetStringProperty(properties, Properties.InternalTokenId));
static (ClaimsPrincipal principal, IReadOnlyDictionary<string, string> properties) Read(BinaryReader reader) static (ClaimsPrincipal principal, IReadOnlyDictionary<string, string> properties) Read(BinaryReader reader)
@ -213,6 +214,7 @@ public sealed class OpenIddictServerDataProtectionFormatter : IOpenIddictServerD
SetProperty(properties, Properties.HostProperties, principal.GetClaim(Claims.Private.HostProperties)); SetProperty(properties, Properties.HostProperties, principal.GetClaim(Claims.Private.HostProperties));
SetProperty(properties, Properties.InternalAuthorizationId, principal.GetAuthorizationId()); SetProperty(properties, Properties.InternalAuthorizationId, principal.GetAuthorizationId());
SetProperty(properties, Properties.InternalSessionId, principal.GetSessionId());
SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId()); SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId());
SetProperty(properties, Properties.DeviceCodeId, principal.GetClaim(Claims.Private.DeviceCodeId)); SetProperty(properties, Properties.DeviceCodeId, principal.GetClaim(Claims.Private.DeviceCodeId));
@ -225,7 +227,7 @@ public sealed class OpenIddictServerDataProtectionFormatter : IOpenIddictServerD
SetArrayProperty(properties, Properties.Scopes, principal.GetScopes()); SetArrayProperty(properties, Properties.Scopes, principal.GetScopes());
// Copy the principal and exclude the claim that were mapped to authentication properties. // Copy the principal and exclude the claim that were mapped to authentication properties.
principal = principal.Clone(claim => claim.Type is not ( principal = principal.Clone(static claim => claim.Type is not (
Claims.Private.AccessTokenLifetime or Claims.Private.AccessTokenLifetime or
Claims.Private.Audience or Claims.Private.Audience or
Claims.Private.AuthorizationCodeLifetime or Claims.Private.AuthorizationCodeLifetime or
@ -244,6 +246,7 @@ public sealed class OpenIddictServerDataProtectionFormatter : IOpenIddictServerD
Claims.Private.RequestTokenLifetime or Claims.Private.RequestTokenLifetime or
Claims.Private.Resource or Claims.Private.Resource or
Claims.Private.Scope or Claims.Private.Scope or
Claims.Private.SessionId or
Claims.Private.TokenId or Claims.Private.TokenId or
Claims.Private.UserCodeLifetime)); Claims.Private.UserCodeLifetime));

2
src/OpenIddict.Server/IOpenIddictServerHandlerFilter.cs

@ -19,7 +19,7 @@ public interface IOpenIddictServerHandlerFilter<in TContext> where TContext : Ba
/// </summary> /// </summary>
/// <param name="context">The context associated with the event to process.</param> /// <param name="context">The context associated with the event to process.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose boolean result determines whether the handler will be invoked or not. /// whose boolean result determines whether the handler will be invoked or not.
/// </returns> /// </returns>
ValueTask<bool> IsActiveAsync(TContext context); ValueTask<bool> IsActiveAsync(TContext context);

17
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -2855,7 +2855,7 @@ public static partial class OpenIddictServerHandlers
Claims.AuthenticationContextReference or Claims.Subject or Claims.AuthenticationContextReference or Claims.Subject or
Claims.Private.AuthorizationId or Claims.Private.CreationDate or Claims.Private.AuthorizationId or Claims.Private.CreationDate or
Claims.Private.DeviceCodeId or Claims.Private.ExpirationDate or Claims.Private.DeviceCodeId or Claims.Private.ExpirationDate or
Claims.Private.TokenId Claims.Private.SessionId or Claims.Private.TokenId
=> values is [{ ValueType: ClaimValueTypes.String }], => values is [{ ValueType: ClaimValueTypes.String }],
// The following claims MUST be represented as unique strings or array of strings. // The following claims MUST be represented as unique strings or array of strings.
@ -3557,7 +3557,8 @@ public static partial class OpenIddictServerHandlers
if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase)) string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.SessionId, StringComparison.OrdinalIgnoreCase))
{ {
return true; return true;
} }
@ -3963,7 +3964,8 @@ public static partial class OpenIddictServerHandlers
if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase)) string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.SessionId, StringComparison.OrdinalIgnoreCase))
{ {
return true; return true;
} }
@ -4020,7 +4022,8 @@ public static partial class OpenIddictServerHandlers
if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase)) string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.SessionId, StringComparison.OrdinalIgnoreCase))
{ {
return true; return true;
} }
@ -4541,7 +4544,8 @@ public static partial class OpenIddictServerHandlers
{ {
// Always include the following claims: // Always include the following claims:
if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase)) string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase) ||
string.Equals(claim.Type, Claims.Private.SessionId, StringComparison.OrdinalIgnoreCase))
{ {
return true; return true;
} }
@ -4643,6 +4647,9 @@ public static partial class OpenIddictServerHandlers
_ => null _ => null
}); });
// If available, use the internal session identifier for the standard "sid" claim.
principal.SetClaim(Claims.SessionId, context.Principal.GetClaim(Claims.Private.SessionId));
context.IdentityTokenPrincipal = principal; context.IdentityTokenPrincipal = principal;
} }
} }

1
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs

@ -21,6 +21,7 @@ public static class OpenIddictValidationDataProtectionConstants
public const string HostProperties = ".host_properties"; public const string HostProperties = ".host_properties";
public const string IdentityTokenLifetime = ".identity_token_lifetime"; public const string IdentityTokenLifetime = ".identity_token_lifetime";
public const string InternalAuthorizationId = ".internal_authorization_id"; public const string InternalAuthorizationId = ".internal_authorization_id";
public const string InternalSessionId = ".internal_session_id";
public const string InternalTokenId = ".internal_token_id"; public const string InternalTokenId = ".internal_token_id";
public const string Issued = ".issued"; public const string Issued = ".issued";
public const string Nonce = ".nonce"; public const string Nonce = ".nonce";

1
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs

@ -35,6 +35,7 @@ public sealed class OpenIddictValidationDataProtectionFormatter : IOpenIddictVal
.SetClaim(Claims.Private.AuthorizationId, GetStringProperty(properties, Properties.InternalAuthorizationId)) .SetClaim(Claims.Private.AuthorizationId, GetStringProperty(properties, Properties.InternalAuthorizationId))
.SetClaim(Claims.Private.CreationDate, GetStringProperty(properties, Properties.Issued)) .SetClaim(Claims.Private.CreationDate, GetStringProperty(properties, Properties.Issued))
.SetClaim(Claims.Private.ExpirationDate, GetStringProperty(properties, Properties.Expires)) .SetClaim(Claims.Private.ExpirationDate, GetStringProperty(properties, Properties.Expires))
.SetClaim(Claims.Private.SessionId, GetStringProperty(properties, Properties.InternalSessionId))
.SetClaim(Claims.Private.TokenId, GetStringProperty(properties, Properties.InternalTokenId)); .SetClaim(Claims.Private.TokenId, GetStringProperty(properties, Properties.InternalTokenId));
static (ClaimsPrincipal principal, IReadOnlyDictionary<string, string> properties) Read(BinaryReader reader) static (ClaimsPrincipal principal, IReadOnlyDictionary<string, string> properties) Read(BinaryReader reader)

2
src/OpenIddict.Validation/IOpenIddictValidationHandlerFilter.cs

@ -18,7 +18,7 @@ public interface IOpenIddictValidationHandlerFilter<in TContext> where TContext
/// </summary> /// </summary>
/// <param name="context">The context associated with the event to process.</param> /// <param name="context">The context associated with the event to process.</param>
/// <returns> /// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose boolean result determines whether the handler will be invoked or not. /// whose boolean result determines whether the handler will be invoked or not.
/// </returns> /// </returns>
ValueTask<bool> IsActiveAsync(TContext context); ValueTask<bool> IsActiveAsync(TContext context);

148
test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs

@ -5780,6 +5780,72 @@ public class OpenIddictExtensionsTests
Assert.Equal("42", principal.GetAuthorizationId()); Assert.Equal("42", principal.GetAuthorizationId());
} }
[Fact]
public void ClaimsIdentity_GetSessionId_ThrowsAnExceptionForNullIdentity()
{
// Arrange
var identity = (ClaimsIdentity) null!;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(identity.GetSessionId);
Assert.Equal("identity", exception.ParamName);
}
[Fact]
public void ClaimsPrincipal_GetSessionId_ThrowsAnExceptionForNullPrincipal()
{
// Arrange
var principal = (ClaimsPrincipal) null!;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(principal.GetSessionId);
Assert.Equal("principal", exception.ParamName);
}
[Fact]
public void ClaimsIdentity_GetSessionId_ReturnsNullForMissingClaim()
{
// Arrange
var identity = new ClaimsIdentity();
// Act and assert
Assert.Null(identity.GetSessionId());
}
[Fact]
public void ClaimsPrincipal_GetSessionId_ReturnsNullForMissingClaim()
{
// Arrange
var principal = new ClaimsPrincipal(new ClaimsIdentity());
// Act and assert
Assert.Null(principal.GetSessionId());
}
[Fact]
public void ClaimsIdentity_GetSessionId_ReturnsExpectedResult()
{
// Arrange
var identity = new ClaimsIdentity();
identity.SetClaim(Claims.Private.SessionId, "42");
// Act and assert
Assert.Equal("42", identity.GetSessionId());
}
[Fact]
public void ClaimsPrincipal_GetSessionId_ReturnsExpectedResult()
{
// Arrange
var principal = new ClaimsPrincipal(new ClaimsIdentity());
principal.SetClaim(Claims.Private.SessionId, "42");
// Act and assert
Assert.Equal("42", principal.GetSessionId());
}
[Fact] [Fact]
public void ClaimsIdentity_HasAudience_ThrowsAnExceptionForNullIdentity() public void ClaimsIdentity_HasAudience_ThrowsAnExceptionForNullIdentity()
{ {
@ -7278,6 +7344,88 @@ public class OpenIddictExtensionsTests
Assert.Equal("42", principal.GetClaim(Claims.Private.AuthorizationId)); Assert.Equal("42", principal.GetClaim(Claims.Private.AuthorizationId));
} }
[Fact]
public void ClaimsIdentity_SetSessionId_ThrowsAnExceptionForNullIdentity()
{
// Arrange
var identity = (ClaimsIdentity) null!;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => identity.SetSessionId(null));
Assert.Equal("identity", exception.ParamName);
}
[Fact]
public void ClaimsPrincipal_SetSessionId_ThrowsAnExceptionForNullPrincipal()
{
// Arrange
var principal = (ClaimsPrincipal) null!;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => principal.SetSessionId(null));
Assert.Equal("principal", exception.ParamName);
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void ClaimsIdentity_SetSessionId_RemovesClaimForNullOrEmptyValue(string? value)
{
// Arrange
var identity = new ClaimsIdentity();
identity.AddClaim(Claims.Private.SessionId, 2520);
// Act
identity.SetSessionId(value);
// Assert
Assert.Empty(identity.Claims);
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void ClaimsPrincipal_SetSessionId_RemovesClaimForNullOrEmptyValue(string? value)
{
// Arrange
var principal = new ClaimsPrincipal(new ClaimsIdentity());
principal.AddClaim(Claims.Private.SessionId, 2520);
// Act
principal.SetSessionId(value);
// Assert
Assert.Empty(principal.Claims);
}
[Fact]
public void ClaimsIdentity_SetSessionId_AddsClaim()
{
// Arrange
var identity = new ClaimsIdentity();
// Act
identity.SetSessionId("42");
// Assert
Assert.Equal("42", identity.GetClaim(Claims.Private.SessionId));
}
[Fact]
public void ClaimsPrincipal_SetSessionId_AddsClaim()
{
// Arrange
var principal = new ClaimsPrincipal(new ClaimsIdentity());
// Act
principal.SetSessionId("42");
// Assert
Assert.Equal("42", principal.GetClaim(Claims.Private.SessionId));
}
[Fact] [Fact]
public void ClaimsIdentity_SetTokenId_ThrowsAnExceptionForNullIdentity() public void ClaimsIdentity_SetTokenId_ThrowsAnExceptionForNullIdentity()
{ {

549
test/OpenIddict.Core.Tests/Caches/OpenIddictSessionCacheTests.cs

@ -0,0 +1,549 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests;
public class OpenIddictSessionCacheTests
{
[Fact]
public void Constructor_ThrowsAnExceptionForNullOptions()
{
// Arrange
var options = (IOptionsMonitor<OpenIddictCoreOptions>) null!;
var store = Mock.Of<IOpenIddictSessionStore<object>>();
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => new OpenIddictSessionCache<object>(options, store));
Assert.Equal("options", exception.ParamName);
}
[Fact]
public void Constructor_ThrowsAnExceptionForNullStore()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = (IOpenIddictSessionStore<object>) null!;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => new OpenIddictSessionCache<object>(options, store));
Assert.Equal("store", exception.ParamName);
}
[Fact]
public async Task AddAsync_ThrowsAnExceptionForNullsession()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => cache.AddAsync(session: null!, CancellationToken.None).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public void Dispose_CanBeCalledMultipleTimes()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
cache.Dispose();
cache.Dispose();
}
[Fact]
public async Task FindByApplicationIdAsync_QueriesStoreOnCacheMiss()
{
// Arrange
var sessions = new[]
{
new OpenIddictSession(),
new OpenIddictSession()
};
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<OpenIddictSession>>();
store.Setup(store => store.GetIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id-1");
store.Setup(store => store.GetApplicationIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("application-id");
store.Setup(store => store.GetAuthorizationIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetSubjectAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id-2");
store.Setup(store => store.GetApplicationIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync("application-id");
store.Setup(store => store.GetAuthorizationIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetSubjectAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.FindByApplicationIdAsync("application-id", It.IsAny<CancellationToken>()))
.Returns(sessions.ToAsyncEnumerable());
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store.Object);
// Act
var results = await cache.FindByApplicationIdAsync("application-id", CancellationToken.None).ToListAsync();
// Assert
Assert.Equal(2, results.Count);
Assert.Contains(sessions[0], results);
Assert.Contains(sessions[1], results);
store.Verify(store => store.FindByApplicationIdAsync("application-id", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task FindByApplicationIdAsync_ThrowsAnExceptionForNullIdentifier()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(
() => cache.FindByApplicationIdAsync(identifier: null!, CancellationToken.None));
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindByApplicationIdAsync_ThrowsAnExceptionForEmptyIdentifier()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = Assert.Throws<ArgumentException>(
() => cache.FindByApplicationIdAsync(identifier: string.Empty, CancellationToken.None));
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindByAuthorizationIdAsync_QueriesStoreOnCacheMiss()
{
// Arrange
var sessions = new[]
{
new OpenIddictSession(),
new OpenIddictSession()
};
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<OpenIddictSession>>();
store.Setup(store => store.GetIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id-1");
store.Setup(store => store.GetApplicationIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetAuthorizationIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("authorization-id");
store.Setup(store => store.GetSubjectAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id-2");
store.Setup(store => store.GetApplicationIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetAuthorizationIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync("authorization-id");
store.Setup(store => store.GetSubjectAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.FindByAuthorizationIdAsync("authorization-id", It.IsAny<CancellationToken>()))
.Returns(sessions.ToAsyncEnumerable());
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store.Object);
// Act
var results = await cache.FindByAuthorizationIdAsync("authorization-id", CancellationToken.None).ToListAsync();
// Assert
Assert.Equal(2, results.Count);
Assert.Contains(sessions[0], results);
Assert.Contains(sessions[1], results);
store.Verify(store => store.FindByAuthorizationIdAsync("authorization-id", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task FindByAuthorizationIdAsync_ThrowsAnExceptionForNullIdentifier()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(
() => cache.FindByAuthorizationIdAsync(identifier: null!, CancellationToken.None));
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindByAuthorizationIdAsync_ThrowsAnExceptionForEmptyIdentifier()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = Assert.Throws<ArgumentException>(
() => cache.FindByAuthorizationIdAsync(identifier: string.Empty, CancellationToken.None));
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindByIdAsync_ThrowsAnExceptionForNullIdentifier()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => cache.FindByIdAsync(identifier: null!, CancellationToken.None).AsTask());
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindByIdAsync_ThrowsAnExceptionForEmptyIdentifier()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentException>(
() => cache.FindByIdAsync(identifier: string.Empty, CancellationToken.None).AsTask());
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindByIdAsync_ReturnsCachedsessionOnCacheHit()
{
// Arrange
var session = new OpenIddictSession();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<OpenIddictSession>>();
store.Setup(store => store.GetIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id");
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store.Object);
await cache.AddAsync(session, CancellationToken.None);
// Act
var result = await cache.FindByIdAsync("session-id", CancellationToken.None);
// Assert
Assert.Same(session, result);
store.Verify(store => store.FindByIdAsync("session-id", It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task FindByIdAsync_QueriesStoreOnCacheMiss()
{
// Arrange
var session = new OpenIddictSession();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<OpenIddictSession>>();
store.Setup(store => store.FindByIdAsync("session-id", It.IsAny<CancellationToken>()))
.ReturnsAsync(session);
store.Setup(store => store.GetIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id");
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store.Object);
// Act
var result = await cache.FindByIdAsync("session-id", CancellationToken.None);
// Assert
Assert.Same(session, result);
store.Verify(store => store.FindByIdAsync("session-id", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task FindByIdAsync_ReturnsNullWhensessionNotFound()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<OpenIddictSession>>();
store.Setup(store => store.FindByIdAsync("session-id", It.IsAny<CancellationToken>()))
.ReturnsAsync((OpenIddictSession?) null);
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store.Object);
// Act
var result = await cache.FindByIdAsync("session-id", CancellationToken.None);
// Assert
Assert.Null(result);
}
[Fact]
public async Task FindByLoginIdAsync_QueriesStoreOnCacheMiss()
{
// Arrange
var sessions = new[]
{
new OpenIddictSession(),
new OpenIddictSession()
};
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<OpenIddictSession>>();
store.Setup(store => store.GetIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id-1");
store.Setup(store => store.GetApplicationIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetAuthorizationIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetLoginIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("login-id");
store.Setup(store => store.GetSubjectAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("subject");
store.Setup(store => store.GetIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id-2");
store.Setup(store => store.GetApplicationIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetAuthorizationIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetLoginIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync("login-id");
store.Setup(store => store.GetSubjectAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync("subject");
store.Setup(store => store.FindByLoginIdAsync("login-id", It.IsAny<CancellationToken>()))
.Returns(sessions.ToAsyncEnumerable());
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store.Object);
// Act
var results = await cache.FindByLoginIdAsync("login-id", CancellationToken.None).ToListAsync();
// Assert
Assert.Equal(2, results.Count);
Assert.Contains(sessions[0], results);
Assert.Contains(sessions[1], results);
store.Verify(store => store.FindByLoginIdAsync("login-id", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task FindByLoginIdAsync_ThrowsAnExceptionForNullIdentifier()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(
() => cache.FindByLoginIdAsync(identifier: null!, CancellationToken.None));
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindByLoginIdAsync_ThrowsAnExceptionForEmptyIdentifier()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = Assert.Throws<ArgumentException>(
() => cache.FindByLoginIdAsync(identifier: string.Empty, CancellationToken.None));
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindBySubjectAsync_QueriesStoreOnCacheMiss()
{
// Arrange
var sessions = new[]
{
new OpenIddictSession(),
new OpenIddictSession()
};
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<OpenIddictSession>>();
store.Setup(store => store.GetIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id-1");
store.Setup(store => store.GetApplicationIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetAuthorizationIdAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetSubjectAsync(sessions[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("subject");
store.Setup(store => store.GetIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id-2");
store.Setup(store => store.GetApplicationIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetAuthorizationIdAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
store.Setup(store => store.GetSubjectAsync(sessions[1], It.IsAny<CancellationToken>()))
.ReturnsAsync("subject");
store.Setup(store => store.FindBySubjectAsync("subject", It.IsAny<CancellationToken>()))
.Returns(sessions.ToAsyncEnumerable());
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store.Object);
// Act
var results = await cache.FindBySubjectAsync("subject", CancellationToken.None).ToListAsync();
// Assert
Assert.Equal(2, results.Count);
Assert.Contains(sessions[0], results);
Assert.Contains(sessions[1], results);
store.Verify(store => store.FindBySubjectAsync("subject", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task FindBySubjectAsync_ThrowsAnExceptionForNullSubject()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(
() => cache.FindBySubjectAsync(subject: null!, CancellationToken.None));
Assert.Equal("subject", exception.ParamName);
}
[Fact]
public async Task FindBySubjectAsync_ThrowsAnExceptionForEmptySubject()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = Assert.Throws<ArgumentException>(
() => cache.FindBySubjectAsync(subject: string.Empty, CancellationToken.None));
Assert.Equal("subject", exception.ParamName);
}
[Fact]
public async Task RemoveAsync_ThrowsAnExceptionForNullsession()
{
// Arrange
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<OpenIddictSession>>();
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => cache.RemoveAsync(session: null!, CancellationToken.None).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task RemoveAsync_InvalidatesCachedEntries()
{
// Arrange
var session = new OpenIddictSession();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<OpenIddictSession>>();
store.Setup(store => store.GetIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("session-id");
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store.Object);
await cache.AddAsync(session, CancellationToken.None);
// Act
await cache.RemoveAsync(session, CancellationToken.None);
var result = await cache.FindByIdAsync("session-id", CancellationToken.None);
// Assert
Assert.Null(result);
}
[Fact]
public async Task RemoveAsync_ThrowsForsessionWithoutId()
{
// Arrange
var session = new OpenIddictSession();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<OpenIddictSession>>();
store.Setup(store => store.GetIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync((string?) null);
var cache = new OpenIddictSessionCache<OpenIddictSession>(options, store.Object);
// Act and assert
await Assert.ThrowsAsync<InvalidOperationException>(
() => cache.RemoveAsync(session, CancellationToken.None).AsTask());
}
public sealed class OpenIddictSession;
}

20
test/OpenIddict.Core.Tests/Managers/OpenIddictAuthorizationManagerTests.cs

@ -211,7 +211,7 @@ public class OpenIddictAuthorizationManagerTests
mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true }); mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true });
var store = new Mock<IOpenIddictAuthorizationStore<CustomAuthorization>>(); var store = new Mock<IOpenIddictAuthorizationStore<CustomAuthorization>>();
store.Setup(store => store.FindAsync("alice", null, null, null, null, It.IsAny<CancellationToken>())) store.Setup(store => store.FindAsync(It.Is<(string?, string?, string?, string?, ImmutableArray<string>?)>(query => query.Item1 == "alice"), It.IsAny<CancellationToken>()))
.Returns(authorizations.ToAsyncEnumerable()); .Returns(authorizations.ToAsyncEnumerable());
store.Setup(store => store.GetSubjectAsync(authorizations[0], It.IsAny<CancellationToken>())) store.Setup(store => store.GetSubjectAsync(authorizations[0], It.IsAny<CancellationToken>()))
@ -223,7 +223,7 @@ public class OpenIddictAuthorizationManagerTests
var manager = new OpenIddictAuthorizationManager<CustomAuthorization>(cache, logger, options, store.Object); var manager = new OpenIddictAuthorizationManager<CustomAuthorization>(cache, logger, options, store.Object);
// Act // Act
var results = await manager.FindAsync("alice", null, null, null, null).ToListAsync(); var results = await manager.FindAsync(("alice", null, null, null, null)).ToListAsync();
// Assert // Assert
Assert.Single(results); Assert.Single(results);
@ -240,8 +240,9 @@ public class OpenIddictAuthorizationManagerTests
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>( var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true }); mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true });
var store = new Mock<IOpenIddictAuthorizationStore<CustomAuthorization>>(); var store = new Mock<IOpenIddictAuthorizationStore<CustomAuthorization>>();
var scopes = ImmutableArray.Create("openid");
store.Setup(store => store.FindAsync(null, null, null, null, ImmutableArray.Create("openid"), It.IsAny<CancellationToken>())) store.Setup(store => store.FindAsync(It.Is<(string?, string?, string?, string?, ImmutableArray<string>?)>(query => query.Item5 == scopes), It.IsAny<CancellationToken>()))
.Returns(authorizations.ToAsyncEnumerable()); .Returns(authorizations.ToAsyncEnumerable());
store.Setup(store => store.GetSubjectAsync(It.IsAny<CustomAuthorization>(), It.IsAny<CancellationToken>())) store.Setup(store => store.GetSubjectAsync(It.IsAny<CustomAuthorization>(), It.IsAny<CancellationToken>()))
@ -256,7 +257,7 @@ public class OpenIddictAuthorizationManagerTests
var manager = new OpenIddictAuthorizationManager<CustomAuthorization>(cache, logger, options, store.Object); var manager = new OpenIddictAuthorizationManager<CustomAuthorization>(cache, logger, options, store.Object);
// Act // Act
var results = await manager.FindAsync(null, null, null, null, ImmutableArray.Create("openid")).ToListAsync(); var results = await manager.FindAsync((null, null, null, null, scopes)).ToListAsync();
// Assert // Assert
Assert.Single(results); Assert.Single(results);
@ -896,16 +897,9 @@ public class OpenIddictAuthorizationManagerTests
var store = new Mock<IOpenIddictAuthorizationStore<CustomAuthorization>>(); var store = new Mock<IOpenIddictAuthorizationStore<CustomAuthorization>>();
store.Setup(store => store.FindAsync( store.Setup(store => store.FindAsync(
It.IsAny<string?>(), It.IsAny<(string?, string?, string?, string?, ImmutableArray<string>?)>(),
It.IsAny<string?>(),
It.IsAny<string?>(),
It.IsAny<string?>(),
It.IsAny<ImmutableArray<string>?>(),
It.IsAny<CancellationToken>())) It.IsAny<CancellationToken>()))
.Returns((string? subject, string? client, string? status, string? type, ImmutableArray<string>? scopes, CancellationToken cancellationToken) => .Returns(Enumerable.Empty<CustomAuthorization>().ToAsyncEnumerable());
{
return Enumerable.Empty<CustomAuthorization>().ToAsyncEnumerable();
});
var manager = new OpenIddictAuthorizationManager<CustomAuthorization>(cache, logger, options, store.Object); var manager = new OpenIddictAuthorizationManager<CustomAuthorization>(cache, logger, options, store.Object);

861
test/OpenIddict.Core.Tests/Managers/OpenIddictSessionManagerTests.cs

@ -0,0 +1,861 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests;
public class OpenIddictSessionManagerTests
{
[Fact]
public void Constructor_ThrowsAnExceptionForNullCache()
{
// Arrange
var cache = (IOpenIddictSessionCache<CustomSession>) null!;
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(
() => new OpenIddictSessionManager<CustomSession>(cache, logger, options, store));
Assert.Equal("cache", exception.ParamName);
}
[Fact]
public void Constructor_ThrowsAnExceptionForNullLogger()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = (ILogger<OpenIddictSessionManager<CustomSession>>) null!;
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(
() => new OpenIddictSessionManager<CustomSession>(cache, logger, options, store));
Assert.Equal("logger", exception.ParamName);
}
[Fact]
public void Constructor_ThrowsAnExceptionForNullOptions()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = (IOptionsMonitor<OpenIddictCoreOptions>) null!;
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(
() => new OpenIddictSessionManager<CustomSession>(cache, logger, options, store));
Assert.Equal("options", exception.ParamName);
}
[Fact]
public void Constructor_ThrowsAnExceptionForNullStore()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = (IOpenIddictSessionStore<CustomSession>) null!;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(
() => new OpenIddictSessionManager<CustomSession>(cache, logger, options, store));
Assert.Equal("store", exception.ParamName);
}
[Fact]
public async Task CountAsync_CallsStoreMethod()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.CountAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(42);
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var count = await manager.CountAsync();
// Assert
Assert.Equal(42, count);
store.Verify(store => store.CountAsync(It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task CountAsync_WithQuery_ThrowsAnExceptionForNullQuery()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.CountAsync<CustomSession>(query: null!).AsTask());
Assert.Equal("query", exception.ParamName);
}
[Fact]
public async Task CreateAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.CreateAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task DeleteAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.DeleteAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task DeleteAsync_RemovessessionFromCache_WhenCachingIsEnabled()
{
// Arrange
var cache = new Mock<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = false });
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
var session = new CustomSession();
var manager = new OpenIddictSessionManager<CustomSession>(cache.Object, logger, options, store.Object);
// Act
await manager.DeleteAsync(session);
// Assert
cache.Verify(cache => cache.RemoveAsync(session, It.IsAny<CancellationToken>()), Times.Once());
store.Verify(store => store.DeleteAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task DeleteAsync_DoesNotRemoveFromCache_WhenCachingIsDisabled()
{
// Arrange
var cache = new Mock<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true });
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
var session = new CustomSession();
var manager = new OpenIddictSessionManager<CustomSession>(cache.Object, logger, options, store.Object);
// Act
await manager.DeleteAsync(session);
// Assert
cache.Verify(cache => cache.RemoveAsync(It.IsAny<CustomSession>(), It.IsAny<CancellationToken>()), Times.Never());
store.Verify(store => store.DeleteAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task FindByIdAsync_ThrowsAnExceptionForNullIdentifier()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.FindByIdAsync(identifier: null!).AsTask());
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindByIdAsync_ThrowsAnExceptionForEmptyIdentifier()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentException>(
() => manager.FindByIdAsync(identifier: string.Empty).AsTask());
Assert.Equal("identifier", exception.ParamName);
}
[Fact]
public async Task FindByIdAsync_UsesCache_WhenCachingIsEnabled()
{
// Arrange
var session = new CustomSession();
var cache = new Mock<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = false });
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
cache.Setup(cache => cache.FindByIdAsync("id", It.IsAny<CancellationToken>()))
.ReturnsAsync(session);
store.Setup(store => store.GetIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("id");
var manager = new OpenIddictSessionManager<CustomSession>(cache.Object, logger, options, store.Object);
// Act
var result = await manager.FindByIdAsync("id");
// Assert
Assert.Same(session, result);
cache.Verify(cache => cache.FindByIdAsync("id", It.IsAny<CancellationToken>()), Times.Once());
store.Verify(store => store.FindByIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task FindByIdAsync_UsesStore_WhenCachingIsDisabled()
{
// Arrange
var session = new CustomSession();
var cache = new Mock<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true });
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.FindByIdAsync("id", It.IsAny<CancellationToken>()))
.ReturnsAsync(session);
store.Setup(store => store.GetIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("id");
var manager = new OpenIddictSessionManager<CustomSession>(cache.Object, logger, options, store.Object);
// Act
var result = await manager.FindByIdAsync("id");
// Assert
Assert.Same(session, result);
cache.Verify(cache => cache.FindByIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never());
store.Verify(store => store.FindByIdAsync("id", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task GetAsync_WithQuery_ThrowsAnExceptionForNullQuery()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetAsync<CustomSession>(query: null!).AsTask());
Assert.Equal("query", exception.ParamName);
}
[Fact]
public async Task GetAsync_WithQueryAndState_ThrowsAnExceptionForNullQuery()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetAsync<object, CustomSession>(query: null!, state: null!).AsTask());
Assert.Equal("query", exception.ParamName);
}
[Fact]
public async Task GetIdAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetIdAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task GetIdAsync_ReturnsIdentifierFromStore()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("unique-session-id");
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var id = await manager.GetIdAsync(session);
// Assert
Assert.Equal("unique-session-id", id);
store.Verify(store => store.GetIdAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task GetApplicationIdAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetApplicationIdAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task GetApplicationIdAsync_ReturnsIdentifierFromStore()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetApplicationIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("application-id");
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var applicationId = await manager.GetApplicationIdAsync(session);
// Assert
Assert.Equal("application-id", applicationId);
store.Verify(store => store.GetApplicationIdAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task GetAuthorizationIdAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetAuthorizationIdAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task GetAuthorizationIdAsync_ReturnsIdentifierFromStore()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetAuthorizationIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("authorization-id");
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var authorizationId = await manager.GetAuthorizationIdAsync(session);
// Assert
Assert.Equal("authorization-id", authorizationId);
store.Verify(store => store.GetAuthorizationIdAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task GetCreationDateAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetCreationDateAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task GetCreationDateAsync_ReturnsCreationDateFromStore()
{
// Arrange
var session = new CustomSession();
var creationDate = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetCreationDateAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync(creationDate);
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var result = await manager.GetCreationDateAsync(session);
// Assert
Assert.Equal(creationDate, result);
store.Verify(store => store.GetCreationDateAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task GetLoginIdAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetLoginIdAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task GetLoginIdAsync_ReturnsIdentifierFromStore()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetLoginIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("login-id");
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var loginId = await manager.GetLoginIdAsync(session);
// Assert
Assert.Equal("login-id", loginId);
store.Verify(store => store.GetLoginIdAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task GetPropertiesAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetPropertiesAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task GetPropertiesAsync_ReturnsPropertiesFromStore()
{
// Arrange
var session = new CustomSession();
var properties = ImmutableDictionary<string, JsonElement>.Empty;
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetPropertiesAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync(properties);
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var result = await manager.GetPropertiesAsync(session);
// Assert
Assert.Same(properties, result);
store.Verify(store => store.GetPropertiesAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task GetStatusAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetStatusAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task GetStatusAsync_ReturnsStatusFromStore()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetStatusAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync(Statuses.Valid);
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var status = await manager.GetStatusAsync(session);
// Assert
Assert.Equal(Statuses.Valid, status);
store.Verify(store => store.GetStatusAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task GetSubjectAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.GetSubjectAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task GetSubjectAsync_ReturnsSubjectFromStore()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetSubjectAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("subject");
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var subject = await manager.GetSubjectAsync(session);
// Assert
Assert.Equal("subject", subject);
store.Verify(store => store.GetSubjectAsync(session, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ListAsync_ReturnsAllSessions()
{
// Arrange
var sessions = new[] { new CustomSession(), new CustomSession() };
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.ListAsync(It.IsAny<int?>(), It.IsAny<int?>(), It.IsAny<CancellationToken>()))
.Returns(sessions.ToAsyncEnumerable());
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var results = new List<CustomSession>();
await foreach (var scp in manager.ListAsync())
{
results.Add(scp);
}
// Assert
Assert.Equal(2, results.Count);
store.Verify(store => store.ListAsync(It.IsAny<int?>(), It.IsAny<int?>(), It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task PopulateAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
var descriptor = new OpenIddictSessionDescriptor();
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.PopulateAsync(session: null!, descriptor).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task PopulateAsync_ThrowsAnExceptionForNullDescriptor()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.PopulateAsync(session, descriptor: null!).AsTask());
Assert.Equal("descriptor", exception.ParamName);
}
[Fact]
public async Task UpdateAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.UpdateAsync(session: null!).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task UpdateAsync_WithDescriptor_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
var descriptor = new OpenIddictSessionDescriptor();
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
() => manager.UpdateAsync(session: null!, descriptor).AsTask());
Assert.Equal("session", exception.ParamName);
}
[Fact]
public void ValidateAsync_ThrowsAnExceptionForNullSession()
{
// Arrange
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>();
var store = Mock.Of<IOpenIddictSessionStore<CustomSession>>();
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(
() => manager.ValidateAsync(session: null!));
Assert.Equal("session", exception.ParamName);
}
[Fact]
public async Task ValidateAsync_ReturnsErrorWhenStatusIsEmpty()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetStatusAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync(string.Empty);
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var results = await manager.ValidateAsync(session).ToListAsync();
// Assert
Assert.Contains(results, result => result.ErrorMessage == SR.GetResourceString(SR.ID2038));
}
[Fact]
public async Task ValidateAsync_ReturnsErrorWhenLoginIdIsEmpty()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetStatusAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync(Statuses.Valid);
store.Setup(store => store.GetLoginIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync(string.Empty);
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var results = await manager.ValidateAsync(session).ToListAsync();
// Assert
Assert.Contains(results, result => result.ErrorMessage == SR.GetResourceString(SR.ID2209));
}
[Fact]
public async Task ValidateAsync_ReturnsNoErrorsForValidSession()
{
// Arrange
var session = new CustomSession();
var cache = Mock.Of<IOpenIddictSessionCache<CustomSession>>();
var logger = Mock.Of<ILogger<OpenIddictSessionManager<CustomSession>>>();
var options = Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>(
mock => mock.CurrentValue == new OpenIddictCoreOptions());
var store = new Mock<IOpenIddictSessionStore<CustomSession>>();
store.Setup(store => store.GetStatusAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync(Statuses.Valid);
store.Setup(store => store.GetLoginIdAsync(session, It.IsAny<CancellationToken>()))
.ReturnsAsync("login-id");
var manager = new OpenIddictSessionManager<CustomSession>(cache, logger, options, store.Object);
// Act
var results = await manager.ValidateAsync(session).ToListAsync();
// Assert
Assert.DoesNotContain(results, static result => result != ValidationResult.Success);
}
public class CustomSession;
}

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

@ -205,6 +205,51 @@ public class OpenIddictCoreBuilderTests
Assert.Equal(typeof(OpenGenericScopeManager<>), descriptor.ImplementationType); Assert.Equal(typeof(OpenGenericScopeManager<>), descriptor.ImplementationType);
} }
[Fact]
public void ReplaceSessionManager_ThrowsAnExceptionForClosedSourceManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceSessionManager(typeof(ClosedGenericSessionManager)));
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
}
[Fact]
public void ReplaceSessionManager_ThrowsAnExceptionForInvalidManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceSessionManager(typeof(object)));
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
}
[Fact]
public void ReplaceSessionManager_OverridesDefaultOpenGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceSessionManager(typeof(OpenGenericSessionManager<>));
// Assert
var descriptor = Assert.Single(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(OpenIddictSessionManager<>));
Assert.Equal(typeof(OpenGenericSessionManager<>), descriptor.ImplementationType);
}
[Fact] [Fact]
public void ReplaceTokenManager_ThrowsAnExceptionForClosedSourceManager() public void ReplaceTokenManager_ThrowsAnExceptionForClosedSourceManager()
{ {
@ -551,6 +596,23 @@ public class OpenIddictCoreBuilderTests
service.ImplementationFactory is not null); service.ImplementationFactory is not null);
} }
[Fact]
public void SetDefaultSessionEntity_ReplacesUntypedManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetDefaultSessionEntity<CustomSession>();
// Assert
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictSessionManager) &&
service.ImplementationFactory is not null);
}
[Fact] [Fact]
public void SetDefaultTokenEntity_ReplacesUntypedManager() public void SetDefaultTokenEntity_ReplacesUntypedManager()
{ {
@ -582,6 +644,7 @@ private static OpenIddictCoreBuilder CreateBuilder(IServiceCollection services)
private class CustomAuthorization; private class CustomAuthorization;
private class CustomResource; private class CustomResource;
private class CustomScope; private class CustomScope;
private class CustomSession;
private class CustomToken; private class CustomToken;
private class ClosedGenericApplicationManager : OpenIddictApplicationManager<CustomApplication> private class ClosedGenericApplicationManager : OpenIddictApplicationManager<CustomApplication>
@ -684,6 +747,31 @@ private static OpenIddictCoreBuilder CreateBuilder(IServiceCollection services)
} }
} }
private class ClosedGenericSessionManager : OpenIddictSessionManager<CustomSession>
{
public ClosedGenericSessionManager(
IOpenIddictSessionCache<CustomSession> cache,
ILogger<OpenIddictSessionManager<CustomSession>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictSessionStore<CustomSession> store)
: base(cache, logger, options, store)
{
}
}
private class OpenGenericSessionManager<TSession> : OpenIddictSessionManager<TSession>
where TSession : class
{
public OpenGenericSessionManager(
IOpenIddictSessionCache<TSession> cache,
ILogger<OpenIddictSessionManager<TSession>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictSessionStore<TSession> store)
: base(cache, logger, options, store)
{
}
}
private class ClosedGenericTokenManager : OpenIddictTokenManager<CustomToken> private class ClosedGenericTokenManager : OpenIddictTokenManager<CustomToken>
{ {
public ClosedGenericTokenManager( public ClosedGenericTokenManager(

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

@ -71,6 +71,7 @@ public class OpenIddictCoreExtensionsTests
[InlineData(typeof(OpenIddictAuthorizationManager<>))] [InlineData(typeof(OpenIddictAuthorizationManager<>))]
[InlineData(typeof(OpenIddictResourceManager<>))] [InlineData(typeof(OpenIddictResourceManager<>))]
[InlineData(typeof(OpenIddictScopeManager<>))] [InlineData(typeof(OpenIddictScopeManager<>))]
[InlineData(typeof(OpenIddictSessionManager<>))]
[InlineData(typeof(OpenIddictTokenManager<>))] [InlineData(typeof(OpenIddictTokenManager<>))]
public void AddCore_RegistersDefaultManagers(Type type) public void AddCore_RegistersDefaultManagers(Type type)
{ {
@ -90,6 +91,7 @@ public class OpenIddictCoreExtensionsTests
[InlineData(typeof(IOpenIddictAuthorizationManager))] [InlineData(typeof(IOpenIddictAuthorizationManager))]
[InlineData(typeof(IOpenIddictResourceManager))] [InlineData(typeof(IOpenIddictResourceManager))]
[InlineData(typeof(IOpenIddictScopeManager))] [InlineData(typeof(IOpenIddictScopeManager))]
[InlineData(typeof(IOpenIddictSessionManager))]
[InlineData(typeof(IOpenIddictTokenManager))] [InlineData(typeof(IOpenIddictTokenManager))]
public void AddCore_RegistersUntypedProxies(Type type) public void AddCore_RegistersUntypedProxies(Type type)
{ {
@ -176,6 +178,24 @@ public class OpenIddictCoreExtensionsTests
Assert.Equal(SR.GetResourceString(SR.ID0472), exception.Message); Assert.Equal(SR.GetResourceString(SR.ID0472), exception.Message);
} }
[Fact]
public void AddCore_ResolvingUntypedSessionManagerThrowsAnException()
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddCore();
// Assert
var provider = services.BuildServiceProvider();
var exception = Assert.Throws<InvalidOperationException>(provider.GetRequiredService<IOpenIddictSessionManager>);
Assert.Equal(SR.GetResourceString(SR.ID0472), exception.Message);
}
[Fact] [Fact]
public void AddCore_ResolvingUntypedTokenManagerThrowsAnException() public void AddCore_ResolvingUntypedTokenManagerThrowsAnException()
{ {

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

@ -33,7 +33,7 @@ public class OpenIddictEntityFrameworkBuilderTests
var builder = CreateBuilder(services); var builder = CreateBuilder(services);
// Act // Act
builder.ReplaceDefaultEntities<CustomApplication, CustomAuthorization, CustomResource, CustomScope, CustomToken, long>(); builder.ReplaceDefaultEntities<CustomApplication, CustomAuthorization, CustomResource, CustomScope, CustomSession, CustomToken, long>();
// Assert // Assert
Assert.Contains(services, service => Assert.Contains(services, service =>
@ -52,6 +52,10 @@ public class OpenIddictEntityFrameworkBuilderTests
service.Lifetime == ServiceLifetime.Scoped && service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictScopeStore<CustomScope>) && service.ServiceType == typeof(IOpenIddictScopeStore<CustomScope>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkScopeStore<CustomScope, long>)); service.ImplementationType == typeof(OpenIddictEntityFrameworkScopeStore<CustomScope, long>));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictSessionStore<CustomSession>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkSessionStore<CustomSession, CustomApplication, CustomAuthorization, CustomToken, long>));
Assert.Contains(services, service => Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped && service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenStore<CustomToken>) && service.ServiceType == typeof(IOpenIddictTokenStore<CustomToken>) &&
@ -90,6 +94,7 @@ public class OpenIddictEntityFrameworkBuilderTests
public class CustomAuthorization : OpenIddictEntityFrameworkAuthorization<long, CustomApplication, CustomToken>; public class CustomAuthorization : OpenIddictEntityFrameworkAuthorization<long, CustomApplication, CustomToken>;
public class CustomResource : OpenIddictEntityFrameworkResource<long>; public class CustomResource : OpenIddictEntityFrameworkResource<long>;
public class CustomScope : OpenIddictEntityFrameworkScope<long>; public class CustomScope : OpenIddictEntityFrameworkScope<long>;
public class CustomSession : OpenIddictEntityFrameworkSession<long, CustomApplication, CustomAuthorization>;
public class CustomToken : OpenIddictEntityFrameworkToken<long, CustomApplication, CustomAuthorization>; public class CustomToken : OpenIddictEntityFrameworkToken<long, CustomApplication, CustomAuthorization>;
public class CustomDbContext : DbContext public class CustomDbContext : DbContext

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

Loading…
Cancel
Save