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