104 changed files with 7193 additions and 675 deletions
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -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"); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -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()); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue