Browse Source

Backport the stores changes to OpenIddict 1.x

pull/670/head
Kévin Chalet 8 years ago
parent
commit
0ff8535506
  1. 45
      src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
  2. 18
      src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
  3. 44
      src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs
  4. 6
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  5. 40
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  6. 174
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  7. 44
      src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
  8. 123
      src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
  9. 6
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictAuthorizationConfiguration.cs
  10. 5
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictTokenConfiguration.cs
  11. 10
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  12. 52
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
  13. 10
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
  14. 145
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
  15. 74
      src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs
  16. 3
      src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs
  17. 49
      src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs
  18. 117
      src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs
  19. 2
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs
  20. 18
      test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs

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

@ -81,6 +81,51 @@ namespace OpenIddict.Abstractions
/// </returns>
Task ExtendAsync([NotNull] object token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the tokens corresponding to the specified
/// subject and associated with the application identifier.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the subject/client.
/// </returns>
Task<ImmutableArray<object>> FindAsync([NotNull] string subject,
[NotNull] string client, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
Task<ImmutableArray<object>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
Task<ImmutableArray<object>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier.
/// </summary>

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

@ -107,6 +107,24 @@ namespace OpenIddict.Abstractions
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="type">The authorization type.</param>
/// <param name="scopes">The minimal scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type,
ImmutableArray<string> scopes, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>

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

@ -60,6 +60,50 @@ namespace OpenIddict.Abstractions
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the tokens corresponding to the specified
/// subject and associated with the application identifier.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the subject/client.
/// </returns>
Task<ImmutableArray<TToken>> FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier.
/// </summary>

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

@ -876,12 +876,6 @@ namespace OpenIddict.Core
await Store.SetClientSecretAsync(application, secret, cancellationToken);
}
var results = await ValidateAsync(application, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
{
throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, application);
}
await UpdateAsync(application, cancellationToken);
}

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

@ -279,7 +279,7 @@ namespace OpenIddict.Core
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken)
[NotNull] string status, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
@ -400,17 +400,42 @@ namespace OpenIddict.Core
[NotNull] string status, [NotNull] string type,
ImmutableArray<string> scopes, CancellationToken cancellationToken = default)
{
var authorizations = await FindAsync(subject, client, status, type, cancellationToken);
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
// 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.
var authorizations = await Store.FindAsync(subject, client, status, type, scopes, cancellationToken);
if (authorizations.IsEmpty)
{
return ImmutableArray.Create<TAuthorization>();
}
var builder = ImmutableArray.CreateBuilder<TAuthorization>(authorizations.Length);
foreach (var authorization in authorizations)
{
if (await HasScopesAsync(authorization, scopes, cancellationToken))
if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)
&& await HasScopesAsync(authorization, scopes, cancellationToken))
{
builder.Add(authorization);
}
@ -888,13 +913,6 @@ namespace OpenIddict.Core
if (!string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase))
{
await Store.SetStatusAsync(authorization, OpenIddictConstants.Statuses.Revoked, cancellationToken);
var results = await ValidateAsync(authorization, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
{
throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, authorization);
}
await UpdateAsync(authorization, cancellationToken);
}
}

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

@ -179,6 +179,171 @@ namespace OpenIddict.Core
await UpdateAsync(token, cancellationToken);
}
/// <summary>
/// Retrieves the tokens corresponding to the specified
/// subject and associated with the application identifier.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the subject/client.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync([NotNull] string subject,
[NotNull] string client, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
// 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.
var tokens = await Store.FindAsync(subject, client, cancellationToken);
if (tokens.IsEmpty)
{
return ImmutableArray.Create<TToken>();
}
var builder = ImmutableArray.CreateBuilder<TToken>(tokens.Length);
foreach (var token in tokens)
{
if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal))
{
builder.Add(token);
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
// 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.
var tokens = await Store.FindAsync(subject, client, status, cancellationToken);
if (tokens.IsEmpty)
{
return ImmutableArray.Create<TToken>();
}
var builder = ImmutableArray.CreateBuilder<TToken>(tokens.Length);
foreach (var token in tokens)
{
if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal))
{
builder.Add(token);
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
// 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.
var tokens = await Store.FindAsync(subject, client, status, type, cancellationToken);
if (tokens.IsEmpty)
{
return ImmutableArray.Create<TToken>();
}
var builder = ImmutableArray.CreateBuilder<TToken>(tokens.Length);
foreach (var token in tokens)
{
if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal))
{
builder.Add(token);
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier.
/// </summary>
@ -985,6 +1150,15 @@ namespace OpenIddict.Core
Task IOpenIddictTokenManager.ExtendAsync(object token, DateTimeOffset? date, CancellationToken cancellationToken)
=> ExtendAsync((TToken) token, date, cancellationToken);
async Task<ImmutableArray<object>> IOpenIddictTokenManager.FindAsync(string subject, string client, CancellationToken cancellationToken)
=> (await FindAsync(subject, client, cancellationToken)).CastArray<object>();
async Task<ImmutableArray<object>> IOpenIddictTokenManager.FindAsync(string subject, string client, string status, CancellationToken cancellationToken)
=> (await FindAsync(subject, client, status, cancellationToken)).CastArray<object>();
async Task<ImmutableArray<object>> IOpenIddictTokenManager.FindAsync(string subject, string client, string status, string type, CancellationToken cancellationToken)
=> (await FindAsync(subject, client, status, type, cancellationToken)).CastArray<object>();
async Task<ImmutableArray<object>> IOpenIddictTokenManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
=> (await FindByApplicationIdAsync(identifier, cancellationToken)).CastArray<object>();

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

@ -334,6 +334,50 @@ namespace OpenIddict.EntityFramework
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="type">The authorization type.</param>
/// <param name="scopes">The minimal scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type,
ImmutableArray<string> scopes, CancellationToken cancellationToken)
{
var authorizations = await FindAsync(subject, client, status, type, cancellationToken);
if (authorizations.IsEmpty)
{
return ImmutableArray.Create<TAuthorization>();
}
var builder = ImmutableArray.CreateBuilder<TAuthorization>(authorizations.Length);
foreach (var authorization in authorizations)
{
async Task<bool> HasScopesAsync()
=> (await GetScopesAsync(authorization, cancellationToken))
.ToImmutableHashSet(StringComparer.Ordinal)
.IsSupersetOf(scopes);
if (await HasScopesAsync())
{
builder.Add(authorization);
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>

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

@ -180,6 +180,129 @@ namespace OpenIddict.EntityFramework
}
}
/// <summary>
/// Retrieves the tokens corresponding to the specified
/// subject and associated with the application identifier.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the subject/client.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync([NotNull] string subject,
[NotNull] string client, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
var key = ConvertIdentifierFromString(client);
return ImmutableArray.CreateRange(
await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Application != null &&
token.Application.Id.Equals(key) &&
token.Subject == subject
select token).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
var key = ConvertIdentifierFromString(client);
return ImmutableArray.CreateRange(
await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Application != null &&
token.Application.Id.Equals(key) &&
token.Subject == subject &&
token.Status == status
select token).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
var key = ConvertIdentifierFromString(client);
return ImmutableArray.CreateRange(
await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Application != null &&
token.Application.Id.Equals(key) &&
token.Subject == subject &&
token.Status == status &&
token.Type == type
select token).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier.
/// </summary>

6
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictAuthorizationConfiguration.cs

@ -39,6 +39,12 @@ namespace OpenIddict.EntityFrameworkCore
builder.HasKey(authorization => authorization.Id);
builder.HasIndex("ApplicationId",
nameof(OpenIddictAuthorization.Scopes),
nameof(OpenIddictAuthorization.Status),
nameof(OpenIddictAuthorization.Subject),
nameof(OpenIddictAuthorization.Type));
builder.Property(authorization => authorization.ConcurrencyToken)
.IsConcurrencyToken();

5
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictTokenConfiguration.cs

@ -42,6 +42,11 @@ namespace OpenIddict.EntityFrameworkCore
builder.HasIndex(token => token.ReferenceId)
.IsUnique();
builder.HasIndex("ApplicationId",
nameof(OpenIddictToken.Status),
nameof(OpenIddictToken.Subject),
nameof(OpenIddictToken.Type));
builder.Property(token => token.ConcurrencyToken)
.IsConcurrencyToken();

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

@ -290,7 +290,7 @@ namespace OpenIddict.EntityFrameworkCore
var key = ConvertIdentifierFromString(identifier);
return (from application in Applications
return (from application in Applications.AsTracking()
where application.Id.Equals(key)
select application).FirstOrDefaultAsync();
}
@ -311,7 +311,7 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
return (from application in Applications
return (from application in Applications.AsTracking()
where application.ClientId == identifier
select application).FirstOrDefaultAsync();
}
@ -337,7 +337,7 @@ namespace OpenIddict.EntityFrameworkCore
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var applications = await (from application in Applications
var applications = await (from application in Applications.AsTracking()
where application.PostLogoutRedirectUris.Contains(address)
select application).ToListAsync(cancellationToken);
@ -384,7 +384,7 @@ namespace OpenIddict.EntityFrameworkCore
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var applications = await (from application in Applications
var applications = await (from application in Applications.AsTracking()
where application.RedirectUris.Contains(address)
select application).ToListAsync(cancellationToken);
@ -726,7 +726,7 @@ namespace OpenIddict.EntityFrameworkCore
public virtual async Task<ImmutableArray<TApplication>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Applications.OrderBy(application => application.Id).AsQueryable();
var query = Applications.OrderBy(application => application.Id).AsTracking();
if (offset.HasValue)
{

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

@ -391,6 +391,50 @@ namespace OpenIddict.EntityFrameworkCore
Authorizations, Applications, ConvertIdentifierFromString(client), subject, status, type).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="type">The authorization type.</param>
/// <param name="scopes">The minimal scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type,
ImmutableArray<string> scopes, CancellationToken cancellationToken)
{
var authorizations = await FindAsync(subject, client, status, type, cancellationToken);
if (authorizations.IsEmpty)
{
return ImmutableArray.Create<TAuthorization>();
}
var builder = ImmutableArray.CreateBuilder<TAuthorization>(authorizations.Length);
foreach (var authorization in authorizations)
{
async Task<bool> HasScopesAsync()
=> (await GetScopesAsync(authorization, cancellationToken))
.ToImmutableHashSet(StringComparer.Ordinal)
.IsSupersetOf(scopes);
if (await HasScopesAsync())
{
builder.Add(authorization);
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>
@ -409,7 +453,7 @@ namespace OpenIddict.EntityFrameworkCore
var key = ConvertIdentifierFromString(identifier);
return (from authorization in Authorizations.Include(authorization => authorization.Application)
return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
where authorization.Id.Equals(key)
select authorization).FirstOrDefaultAsync(cancellationToken);
}
@ -432,7 +476,7 @@ namespace OpenIddict.EntityFrameworkCore
}
return ImmutableArray.CreateRange(
await (from authorization in Authorizations.Include(authorization => authorization.Application)
await (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
where authorization.Subject == subject
select authorization).ToListAsync(cancellationToken));
}
@ -461,7 +505,7 @@ namespace OpenIddict.EntityFrameworkCore
async Task<string> RetrieveApplicationIdAsync()
{
IQueryable<TKey> Query(IQueryable<TAuthorization> authorizations, TKey key)
=> from element in authorizations
=> from element in authorizations.AsTracking()
where element.Id.Equals(key) &&
element.Application != null
select element.Application.Id;
@ -666,7 +710,7 @@ namespace OpenIddict.EntityFrameworkCore
{
var query = Authorizations.Include(authorization => authorization.Application)
.OrderBy(authorization => authorization.Id)
.AsQueryable();
.AsTracking();
if (offset.HasValue)
{

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

@ -197,7 +197,7 @@ namespace OpenIddict.EntityFrameworkCore
var key = ConvertIdentifierFromString(identifier);
return (from scope in Scopes
return (from scope in Scopes.AsTracking()
where scope.Id.Equals(key)
select scope).FirstOrDefaultAsync(cancellationToken);
}
@ -218,7 +218,7 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The scope name cannot be null or empty.", nameof(name));
}
return (from scope in Scopes
return (from scope in Scopes.AsTracking()
where scope.Name == name
select scope).FirstOrDefaultAsync(cancellationToken);
}
@ -241,7 +241,7 @@ namespace OpenIddict.EntityFrameworkCore
}
return ImmutableArray.CreateRange(
await (from scope in Scopes
await (from scope in Scopes.AsTracking()
where names.Contains(scope.Name)
select scope).ToListAsync(cancellationToken));
}
@ -268,7 +268,7 @@ namespace OpenIddict.EntityFrameworkCore
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var scopes = await (from scope in Scopes
var scopes = await (from scope in Scopes.AsTracking()
where scope.Resources.Contains(resource)
select scope).ToListAsync(cancellationToken);
@ -488,7 +488,7 @@ namespace OpenIddict.EntityFrameworkCore
public virtual async Task<ImmutableArray<TScope>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Scopes.OrderBy(scope => scope.Id).AsQueryable();
var query = Scopes.OrderBy(scope => scope.Id).AsTracking();
if (offset.HasValue)
{

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

@ -201,6 +201,139 @@ namespace OpenIddict.EntityFrameworkCore
}
}
/// <summary>
/// Retrieves the tokens corresponding to the specified
/// subject and associated with the application identifier.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the subject/client.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync([NotNull] string subject,
[NotNull] string client, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
IQueryable<TToken> Query(IQueryable<TApplication> applications, IQueryable<TToken> tokens, TKey key, string principal)
=> from token in tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.Subject == subject
join application in applications.AsTracking() on token.Application.Id equals application.Id
where application.Id.Equals(key)
select token;
return ImmutableArray.CreateRange(await Query(Applications, Tokens,
ConvertIdentifierFromString(client), subject).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
IQueryable<TToken> Query(IQueryable<TApplication> applications,
IQueryable<TToken> tokens, TKey key, string principal, string state)
=> from token in tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.Subject == subject &&
token.Status == status
join application in applications.AsTracking() on token.Application.Id equals application.Id
where application.Id.Equals(key)
select token;
return ImmutableArray.CreateRange(await Query(Applications, Tokens,
ConvertIdentifierFromString(client), subject, status).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
IQueryable<TToken> Query(IQueryable<TApplication> applications,
IQueryable<TToken> tokens, TKey key, string principal, string state, string kind)
=> from token in tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.Subject == subject &&
token.Status == status &&
token.Type == type
join application in applications.AsTracking() on token.Application.Id equals application.Id
where application.Id.Equals(key)
select token;
return ImmutableArray.CreateRange(await Query(Applications, Tokens,
ConvertIdentifierFromString(client), subject, status, type).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier.
/// </summary>
@ -281,7 +414,7 @@ namespace OpenIddict.EntityFrameworkCore
var key = ConvertIdentifierFromString(identifier);
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.Id.Equals(key)
select token).FirstOrDefaultAsync(cancellationToken);
}
@ -303,7 +436,7 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.ReferenceId == identifier
select token).FirstOrDefaultAsync(cancellationToken);
}
@ -325,7 +458,7 @@ namespace OpenIddict.EntityFrameworkCore
}
return ImmutableArray.CreateRange(
await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.Subject == subject
select token).ToListAsync(cancellationToken));
}
@ -354,7 +487,7 @@ namespace OpenIddict.EntityFrameworkCore
async Task<string> RetrieveApplicationIdAsync()
{
IQueryable<TKey> Query(IQueryable<TToken> tokens, TKey key)
=> from element in tokens
=> from element in tokens.AsTracking()
where element.Id.Equals(key) &&
element.Application != null
select element.Application.Id;
@ -416,7 +549,7 @@ namespace OpenIddict.EntityFrameworkCore
async Task<string> RetrieveAuthorizationIdAsync()
{
IQueryable<TKey> Query(IQueryable<TToken> tokens, TKey key)
=> from element in tokens
=> from element in tokens.AsTracking()
where element.Id.Equals(key) &&
element.Authorization != null
select element.Authorization.Id;
@ -649,7 +782,7 @@ namespace OpenIddict.EntityFrameworkCore
var query = Tokens.Include(token => token.Application)
.Include(token => token.Authorization)
.OrderBy(token => token.Id)
.AsQueryable();
.AsTracking();
if (offset.HasValue)
{

74
src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs

@ -61,8 +61,8 @@ namespace OpenIddict.MongoDb
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The MongoDB database couldn't be initialized within a reasonable timeframe.")
.Append("Make sure that the MongoDB server is ready and accepts connections from this machine ")
.Append("or use 'options.UseMongoDb().SetInitializationTimeout()' to manually adjust the timeout.")
.Append("Make sure that the MongoDB server is ready and accepts connections from this machine or use ")
.Append("'services.AddOpenIddict().AddCore().UseMongoDb().SetInitializationTimeout()' to adjust the timeout.")
.ToString());
}
@ -89,19 +89,43 @@ namespace OpenIddict.MongoDb
// Note: the cancellation token passed as a parameter is deliberately not used here to ensure
// the cancellation of a single store operation doesn't prevent the indexes from being created.
var applications = database.GetCollection<OpenIddictApplication>(options.ApplicationsCollectionName);
await applications.Indexes.CreateOneAsync(new CreateIndexModel<OpenIddictApplication>(
Builders<OpenIddictApplication>.IndexKeys.Ascending(application => application.ClientId),
await applications.Indexes.CreateManyAsync(new[]
{
new CreateIndexModel<OpenIddictApplication>(
Builders<OpenIddictApplication>.IndexKeys.Ascending(application => application.ClientId),
new CreateIndexOptions
{
Unique = true
}),
new CreateIndexModel<OpenIddictApplication>(
Builders<OpenIddictApplication>.IndexKeys.Ascending(application => application.PostLogoutRedirectUris),
new CreateIndexOptions
{
Background = true
}),
new CreateIndexModel<OpenIddictApplication>(
Builders<OpenIddictApplication>.IndexKeys.Ascending(application => application.RedirectUris),
new CreateIndexOptions
{
Background = true
})
});
var authorizations = database.GetCollection<OpenIddictAuthorization>(options.AuthorizationsCollectionName);
await authorizations.Indexes.CreateOneAsync(new CreateIndexModel<OpenIddictAuthorization>(
Builders<OpenIddictAuthorization>.IndexKeys
.Ascending(authorization => authorization.ApplicationId)
.Ascending(authorization => authorization.Scopes)
.Ascending(authorization => authorization.Status)
.Ascending(authorization => authorization.Subject)
.Ascending(authorization => authorization.Type),
new CreateIndexOptions
{
Unique = true
Background = true
}));
await applications.Indexes.CreateOneAsync(new CreateIndexModel<OpenIddictApplication>(
Builders<OpenIddictApplication>.IndexKeys.Ascending(application => application.PostLogoutRedirectUris)));
await applications.Indexes.CreateOneAsync(new CreateIndexModel<OpenIddictApplication>(
Builders<OpenIddictApplication>.IndexKeys.Ascending(application => application.RedirectUris)));
var scopes = database.GetCollection<OpenIddictScope>(options.ScopesCollectionName);
await scopes.Indexes.CreateOneAsync(new CreateIndexModel<OpenIddictScope>(
Builders<OpenIddictScope>.IndexKeys.Ascending(scope => scope.Name),
@ -111,13 +135,27 @@ namespace OpenIddict.MongoDb
}));
var tokens = database.GetCollection<OpenIddictToken>(options.TokensCollectionName);
await tokens.Indexes.CreateOneAsync(new CreateIndexModel<OpenIddictToken>(
Builders<OpenIddictToken>.IndexKeys.Ascending(token => token.ReferenceId),
new CreateIndexOptions<OpenIddictToken>
{
PartialFilterExpression = Builders<OpenIddictToken>.Filter.Exists(token => token.ReferenceId),
Unique = true
}));
await tokens.Indexes.CreateManyAsync(new[]
{
new CreateIndexModel<OpenIddictToken>(
Builders<OpenIddictToken>.IndexKeys.Ascending(token => token.ReferenceId),
new CreateIndexOptions<OpenIddictToken>
{
PartialFilterExpression = Builders<OpenIddictToken>.Filter.Exists(token => token.ReferenceId),
Unique = true
}),
new CreateIndexModel<OpenIddictToken>(
Builders<OpenIddictToken>.IndexKeys
.Ascending(token => token.ApplicationId)
.Ascending(token => token.Status)
.Ascending(token => token.Subject)
.Ascending(token => token.Type),
new CreateIndexOptions
{
Background = true
})
});
}
return _database = database;

3
src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs

@ -7,7 +7,6 @@
using System;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using OpenIddict.MongoDb;
using OpenIddict.MongoDb.Models;
@ -83,4 +82,4 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
}
}
}

49
src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs

@ -265,6 +265,55 @@ namespace OpenIddict.MongoDb
authorization.Type == type).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="type">The authorization type.</param>
/// <param name="scopes">The minimal scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type,
ImmutableArray<string> scopes, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TAuthorization>(Options.Value.AuthorizationsCollectionName);
return ImmutableArray.CreateRange(await collection.Find(authorization =>
authorization.Subject == subject &&
authorization.ApplicationId == ObjectId.Parse(client) &&
authorization.Status == status &&
authorization.Type == type &&
Enumerable.All(scopes, scope => authorization.Scopes.Contains(scope))).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>

117
src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs

@ -144,6 +144,123 @@ namespace OpenIddict.MongoDb
}
}
/// <summary>
/// Retrieves the tokens corresponding to the specified
/// subject and associated with the application identifier.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the subject/client.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync([NotNull] string subject,
[NotNull] string client, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TToken>(Options.Value.TokensCollectionName);
return ImmutableArray.CreateRange(await collection.Find(token =>
token.ApplicationId == ObjectId.Parse(client) &&
token.Subject == subject).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TToken>(Options.Value.TokensCollectionName);
return ImmutableArray.CreateRange(await collection.Find(token =>
token.ApplicationId == ObjectId.Parse(client) &&
token.Subject == subject &&
token.Status == status).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="client">The client associated with the token.</param>
/// <param name="status">The token status.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TToken>(Options.Value.TokensCollectionName);
return ImmutableArray.CreateRange(await collection.Find(token =>
token.ApplicationId == ObjectId.Parse(client) &&
token.Subject == subject &&
token.Status == status &&
token.Type == type).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier.
/// </summary>

2
src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs

@ -184,7 +184,7 @@ namespace OpenIddict.Server.Internal
// Note: make sure the foreach statement iterates on a copy of the ticket
// as the property collection is modified when the property is removed.
var parameters = GetParameters(context.HttpContext, context.Request, context.Ticket.Properties);
foreach (var parameter in parameters.ToArray())
foreach (var parameter in parameters.ToList())
{
context.Response.AddParameter(parameter.Item2, parameter.Item3);
context.Ticket.RemoveProperty(parameter.Item1);

18
test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs

@ -5,6 +5,7 @@
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -48,11 +49,11 @@ namespace OpenIddict.MongoDb.Tests
var provider = services.BuildServiceProvider();
var manager = new Mock<IMongoIndexManager<OpenIddictApplication>>();
manager.Setup(mock => mock.CreateOneAsync(It.IsAny<CreateIndexModel<OpenIddictApplication>>(), It.IsAny<CreateOneIndexOptions>(), It.IsAny<CancellationToken>()))
manager.Setup(mock => mock.CreateManyAsync(It.IsAny<IEnumerable<CreateIndexModel<OpenIddictApplication>>>(), It.IsAny<CancellationToken>()))
.Returns(async delegate
{
await Task.Delay(TimeSpan.FromMilliseconds(1000));
return nameof(OpenIddictMongoDbContextTests);
return new[] { string.Empty };
});
var collection = new Mock<IMongoCollection<OpenIddictApplication>>();
@ -83,8 +84,8 @@ namespace OpenIddict.MongoDb.Tests
Assert.Equal(new StringBuilder()
.AppendLine("The MongoDB database couldn't be initialized within a reasonable timeframe.")
.Append("Make sure that the MongoDB server is ready and accepts connections from this machine ")
.Append("or use 'options.UseMongoDb().SetInitializationTimeout()' to manually adjust the timeout.")
.Append("Make sure that the MongoDB server is ready and accepts connections from this machine or use ")
.Append("'services.AddOpenIddict().AddCore().UseMongoDb().SetInitializationTimeout()' to adjust the timeout.")
.ToString(), exception.Message);
}
@ -182,6 +183,7 @@ namespace OpenIddict.MongoDb.Tests
// Assert
database.Verify(mock => mock.GetCollection<OpenIddictApplication>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Never());
database.Verify(mock => mock.GetCollection<OpenIddictAuthorization>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Never());
database.Verify(mock => mock.GetCollection<OpenIddictScope>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Never());
database.Verify(mock => mock.GetCollection<OpenIddictToken>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Never());
}
@ -206,6 +208,7 @@ namespace OpenIddict.MongoDb.Tests
Assert.Same(database.Object, await context.GetDatabaseAsync(CancellationToken.None));
database.Verify(mock => mock.GetCollection<OpenIddictApplication>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Once());
database.Verify(mock => mock.GetCollection<OpenIddictAuthorization>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Once());
database.Verify(mock => mock.GetCollection<OpenIddictScope>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Once());
database.Verify(mock => mock.GetCollection<OpenIddictToken>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Once());
}
@ -248,6 +251,7 @@ namespace OpenIddict.MongoDb.Tests
Assert.Same(database.Object, await context.GetDatabaseAsync(CancellationToken.None));
database.Verify(mock => mock.GetCollection<OpenIddictApplication>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Exactly(2));
database.Verify(mock => mock.GetCollection<OpenIddictAuthorization>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Once());
database.Verify(mock => mock.GetCollection<OpenIddictScope>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Once());
database.Verify(mock => mock.GetCollection<OpenIddictToken>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()), Times.Once());
}
@ -258,6 +262,10 @@ namespace OpenIddict.MongoDb.Tests
applications.SetupGet(mock => mock.Indexes)
.Returns(Mock.Of<IMongoIndexManager<OpenIddictApplication>>());
var authorizations = new Mock<IMongoCollection<OpenIddictAuthorization>>();
authorizations.SetupGet(mock => mock.Indexes)
.Returns(Mock.Of<IMongoIndexManager<OpenIddictAuthorization>>());
var scopes = new Mock<IMongoCollection<OpenIddictScope>>();
scopes.SetupGet(mock => mock.Indexes)
.Returns(Mock.Of<IMongoIndexManager<OpenIddictScope>>());
@ -269,6 +277,8 @@ namespace OpenIddict.MongoDb.Tests
var database = new Mock<IMongoDatabase>();
database.Setup(mock => mock.GetCollection<OpenIddictApplication>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()))
.Returns(applications.Object);
database.Setup(mock => mock.GetCollection<OpenIddictAuthorization>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()))
.Returns(authorizations.Object);
database.Setup(mock => mock.GetCollection<OpenIddictScope>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()))
.Returns(scopes.Object);
database.Setup(mock => mock.GetCollection<OpenIddictToken>(It.IsAny<string>(), It.IsAny<MongoCollectionSettings>()))

Loading…
Cancel
Save