Browse Source

Reintroduce token/subject binding and add authorization support

pull/339/head
Kévin Chalet 9 years ago
parent
commit
00e6fac166
  1. 21
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  2. 81
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  3. 149
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  4. 9
      src/OpenIddict.Core/OpenIddictBuilder.cs
  5. 4
      src/OpenIddict.Core/OpenIddictConstants.cs
  6. 16
      src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
  7. 61
      src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
  8. 94
      src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
  9. 4
      src/OpenIddict.Core/project.json
  10. 8
      src/OpenIddict.EntityFrameworkCore/OpenIddictCustomizer.cs
  11. 11
      src/OpenIddict.EntityFrameworkCore/OpenIddictExtension.cs
  12. 77
      src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs
  13. 109
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  14. 158
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
  15. 53
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
  16. 280
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
  17. 11
      src/OpenIddict.Models/OpenIddictApplication.cs
  18. 16
      src/OpenIddict.Models/OpenIddictAuthorization.cs
  19. 26
      src/OpenIddict.Models/OpenIddictToken.cs
  20. 5
      src/OpenIddict.Mvc/OpenIddictModelBinder.cs
  21. 71
      src/OpenIddict/OpenIddictProvider.Serialization.cs
  22. 80
      test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs
  23. 1
      test/OpenIddict.Tests/OpenIddictExtensionsTests.cs
  24. 7
      test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
  25. 306
      test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
  26. 12
      test/OpenIddict.Tests/OpenIddictProviderTests.cs

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

@ -44,7 +44,7 @@ namespace OpenIddict.Core {
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
public virtual async Task<string> CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
public virtual async Task<TApplication> CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
@ -73,7 +73,7 @@ namespace OpenIddict.Core {
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
public virtual async Task<string> CreateAsync(
public virtual async Task<TApplication> CreateAsync(
[NotNull] TApplication application,
[NotNull] string secret, CancellationToken cancellationToken) {
if (application == null) {
@ -196,6 +196,23 @@ namespace OpenIddict.Core {
return Store.GetDisplayNameAsync(application, cancellationToken);
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
/// <param name="application">The application.</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 unique identifier associated with the application.
/// </returns>
public virtual Task<string> GetIdAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.GetIdAsync(application, cancellationToken);
}
/// <summary>
/// Retrieves the token identifiers associated with an application.
/// </summary>

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

@ -4,6 +4,9 @@
* the license and the contributors participating to this project.
*/
using System;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
@ -29,5 +32,83 @@ namespace OpenIddict.Core {
/// Gets the store associated with the current manager.
/// </summary>
protected IOpenIddictAuthorizationStore<TAuthorization> Store { get; }
/// <summary>
/// Creates a new authorization.
/// </summary>
/// <param name="authorization">The application to create.</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.
/// </returns>
public virtual Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) {
if (authorization == null) {
throw new ArgumentNullException(nameof(authorization));
}
return Store.CreateAsync(authorization, cancellationToken);
}
/// <summary>
/// Retrieves an authorization using its associated subject/client.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client 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 authorization corresponding to the subject/client.
/// </returns>
public virtual Task<TAuthorization> FindAsync(string subject, string client, CancellationToken cancellationToken) {
return Store.FindAsync(subject, client, cancellationToken);
}
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier.
/// </returns>
public virtual Task<TAuthorization> FindByIdAsync(string identifier, CancellationToken cancellationToken) {
return Store.FindByIdAsync(identifier, cancellationToken);
}
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
/// <param name="authorization">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 unique identifier associated with the authorization.
/// </returns>
public virtual Task<string> GetIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) {
if (authorization == null) {
throw new ArgumentNullException(nameof(authorization));
}
return Store.GetIdAsync(authorization, cancellationToken);
}
/// <summary>
/// Validates the authorization to ensure it's in a consistent state.
/// </summary>
/// <param name="authorization">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.
/// </returns>
protected virtual async Task ValidateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) {
if (authorization == null) {
throw new ArgumentNullException(nameof(authorization));
}
if (string.IsNullOrEmpty(await Store.GetSubjectAsync(authorization, cancellationToken))) {
throw new ArgumentException("The subject cannot be null or empty.");
}
}
}
}

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

@ -7,6 +7,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Primitives;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
@ -34,20 +35,46 @@ namespace OpenIddict.Core {
protected IOpenIddictTokenStore<TToken> Store { get; }
/// <summary>
/// Creates a new token, which is not associated with a particular user or client.
/// Creates a new token.
/// </summary>
/// <param name="token">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 token.
/// </returns>
public virtual async Task<TToken> CreateAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
await ValidateAsync(token, cancellationToken);
return await Store.CreateAsync(token, cancellationToken);
}
/// <summary>
/// Creates a new token, which is associated with a particular subject.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="subject">The subject 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 unique identifier associated with the token.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns>
public virtual Task<string> CreateAsync(string type, CancellationToken cancellationToken) {
public virtual async Task<TToken> CreateAsync([NotNull] string type, [NotNull] string subject, CancellationToken cancellationToken) {
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The token type cannot be null or empty.", nameof(type));
}
return Store.CreateAsync(type, cancellationToken);
if (!string.Equals(type, OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, OpenIdConnectConstants.TokenTypeHints.RefreshToken, StringComparison.OrdinalIgnoreCase)) {
throw new ArgumentException("The specified token type is not supported by the default token manager.");
}
if (string.IsNullOrEmpty(subject)) {
throw new ArgumentException("The subject cannot be null or empty.");
}
return await Store.CreateAsync(type, subject, cancellationToken);
}
/// <summary>
@ -63,18 +90,128 @@ namespace OpenIddict.Core {
return Store.FindByIdAsync(identifier, cancellationToken);
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the tokens.</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 specified subject.
/// </returns>
public virtual Task<TToken[]> FindBySubjectAsync(string subject, CancellationToken cancellationToken) {
return Store.FindBySubjectAsync(subject, cancellationToken);
}
/// <summary>
/// Retrieves the unique identifier associated with a token.
/// </summary>
/// <param name="token">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 unique identifier associated with the token.
/// </returns>
public virtual Task<string> GetIdAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
return Store.GetIdAsync(token, cancellationToken);
}
/// <summary>
/// Revokes a token.
/// </summary>
/// <param name="token">The token to revoke.</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.</returns>
public virtual Task RevokeAsync(TToken token, CancellationToken cancellationToken) {
public virtual Task RevokeAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
return Store.RevokeAsync(token, cancellationToken);
}
/// <summary>
/// Sets the authorization associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task SetAuthorizationAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
await Store.SetAuthorizationAsync(token, identifier, cancellationToken);
await UpdateAsync(token, cancellationToken);
}
/// <summary>
/// Sets the client application associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The unique identifier associated with the client application.</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.
/// </returns>
public virtual async Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
await Store.SetClientAsync(token, identifier, cancellationToken);
await UpdateAsync(token, cancellationToken);
}
/// <summary>
/// Updates an existing token.
/// </summary>
/// <param name="token">The token to update.</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.
/// </returns>
public virtual Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
return Store.UpdateAsync(token, cancellationToken);
}
/// <summary>
/// Validates the token to ensure it's in a consistent state.
/// </summary>
/// <param name="token">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.
/// </returns>
protected virtual async Task ValidateAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
var type = await Store.GetTokenTypeAsync(token, cancellationToken);
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The token type cannot be null or empty.", nameof(token));
}
if (!string.Equals(type, OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, OpenIdConnectConstants.TokenTypeHints.RefreshToken, StringComparison.OrdinalIgnoreCase)) {
throw new ArgumentException("The specified token type is not supported by the default token manager.");
}
if (string.IsNullOrEmpty(await Store.GetSubjectAsync(token, cancellationToken))) {
throw new ArgumentException("The subject cannot be null or empty.");
}
}
}
}

9
src/OpenIddict.Core/OpenIddictBuilder.cs

@ -8,6 +8,7 @@ using System;
using System.ComponentModel;
using JetBrains.Annotations;
using OpenIddict.Core;
using OpenIddict.Models;
#if NETSTANDARD1_3
using System.Reflection;
@ -34,25 +35,25 @@ namespace Microsoft.Extensions.DependencyInjection {
/// Gets or sets the type corresponding to the Application entity.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Type ApplicationType { get; set; }
public Type ApplicationType { get; set; } = typeof(OpenIddictApplication);
/// <summary>
/// Gets or sets the type corresponding to the Authorization entity.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Type AuthorizationType { get; set; }
public Type AuthorizationType { get; set; } = typeof(OpenIddictAuthorization);
/// <summary>
/// Gets or sets the type corresponding to the Scope entity.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Type ScopeType { get; set; }
public Type ScopeType { get; set; } = typeof(OpenIddictScope);
/// <summary>
/// Gets or sets the type corresponding to the Token entity.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Type TokenType { get; set; }
public Type TokenType { get; set; } = typeof(OpenIddictToken);
/// <summary>
/// Gets the services collection.

4
src/OpenIddict.Core/OpenIddictConstants.cs

@ -24,6 +24,10 @@ namespace OpenIddict.Core {
public const string ExternalProvidersSupported = "external_providers_supported";
}
public static class Properties {
public const string AuthorizationId = ".authorization_id";
}
public static class Scopes {
public const string Roles = "roles";
}

16
src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs

@ -21,10 +21,9 @@ namespace OpenIddict.Core {
/// <param name="application">The application to create.</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 unique identifier associated with the application.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the application.
/// </returns>
Task<string> CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken);
Task<TApplication> CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Removes an existing application.
@ -113,6 +112,17 @@ namespace OpenIddict.Core {
/// </returns>
Task<string> GetHashedSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
/// <param name="application">The application.</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 unique identifier associated with the application.
/// </returns>
Task<string> GetIdAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the logout callback address associated with an application.
/// </summary>

61
src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs

@ -4,10 +4,69 @@
* the license and the contributors participating to this project.
*/
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace OpenIddict.Core {
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
public interface IOpenIddictAuthorizationStore<TAuthorization> where TAuthorization : class { }
public interface IOpenIddictAuthorizationStore<TAuthorization> where TAuthorization : class {
/// <summary>
/// Creates a new authorization.
/// </summary>
/// <param name="authorization">The authorization to create.</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 authorization.
/// </returns>
Task<TAuthorization> CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier.
/// </returns>
Task<TAuthorization> FindByIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an authorization using its associated subject/client.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client 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 authorization corresponding to the subject/client.
/// </returns>
Task<TAuthorization> FindAsync(string subject, string client, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
/// <param name="authorization">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 unique identifier associated with the authorization.
/// </returns>
Task<string> GetIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the subject associated with an authorization.
/// </summary>
/// <param name="authorization">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 subject associated with the specified authorization.
/// </returns>
Task<string> GetSubjectAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
}
}

94
src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs

@ -15,15 +15,25 @@ namespace OpenIddict.Core {
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
public interface IOpenIddictTokenStore<TToken> where TToken : class {
/// <summary>
/// Creates a new token, which is not associated with a particular user or client.
/// Creates a new token.
/// </summary>
/// <param name="token">The token to create.</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 token.
/// </returns>
Task<TToken> CreateAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Creates a new token, which is associated with a particular subject.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="subject">The subject 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 unique identifier associated with the token.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns>
Task<string> CreateAsync([NotNull] string type, CancellationToken cancellationToken);
Task<TToken> CreateAsync([NotNull] string type, [NotNull] string subject, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an token using its unique identifier.
@ -36,6 +46,50 @@ namespace OpenIddict.Core {
/// </returns>
Task<TToken> FindByIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of tokens corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the tokens.</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 specified subject.
/// </returns>
Task<TToken[]> FindBySubjectAsync(string subject, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the unique identifier associated with a token.
/// </summary>
/// <param name="token">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 unique identifier associated with the token.
/// </returns>
Task<string> GetIdAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the token type associated with a token.
/// </summary>
/// <param name="token">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 token type associated with the specified token.
/// </returns>
Task<string> GetTokenTypeAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the subject associated with a token.
/// </summary>
/// <param name="token">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 subject associated with the specified token.
/// </returns>
Task<string> GetSubjectAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Revokes a token.
/// </summary>
@ -43,5 +97,37 @@ namespace OpenIddict.Core {
/// <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.</returns>
Task RevokeAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Sets the authorization associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
Task SetAuthorizationAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken);
/// <summary>
/// Sets the client application associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The unique identifier associated with the client application.</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.
/// </returns>
Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken);
/// <summary>
/// Updates an existing token.
/// </summary>
/// <param name="token">The token to update.</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.
/// </returns>
Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken);
}
}

4
src/OpenIddict.Core/project.json

@ -33,6 +33,7 @@
},
"dependencies": {
"AspNet.Security.OpenIdConnect.Primitives": "1.0.0-rc1-*",
"CryptoHelper": "2.0.0",
"JetBrains.Annotations": { "type": "build", "version": "10.1.4" },
"Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0",
@ -46,7 +47,8 @@
"netstandard1.3": {
"dependencies": {
"System.Reflection.TypeExtensions": "4.1.0"
"System.Reflection.TypeExtensions": "4.1.0",
"System.Security.Claims": "4.0.1"
},
"imports": [

8
src/OpenIddict.EntityFrameworkCore/OpenIddictCustomizer.cs

@ -17,10 +17,10 @@ namespace OpenIddict.EntityFrameworkCore {
/// required by the OpenIddict stack in an Entity Framework context.
/// </summary>
public class OpenIddictCustomizer<TApplication, TAuthorization, TScope, TToken, TKey> : ModelCustomizer
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TScope : OpenIddictScope<TKey>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TKey : IEquatable<TKey> {
public override void Customize([NotNull] ModelBuilder builder, [NotNull] DbContext context) {
if (builder == null) {

11
src/OpenIddict.EntityFrameworkCore/OpenIddictExtension.cs

@ -5,18 +5,19 @@
*/
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Models;
namespace OpenIddict.EntityFrameworkCore {
public class OpenIddictExtension<TApplication, TAuthorization, TScope, TToken, TKey> : IDbContextOptionsExtension
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TScope : OpenIddictScope<TKey>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TKey : IEquatable<TKey> {
public void ApplyServices(IServiceCollection services) {
public void ApplyServices([NotNull] IServiceCollection services) {
if (services == null) {
throw new ArgumentNullException(nameof(services));
}

77
src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs

@ -5,6 +5,7 @@
*/
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using JetBrains.Annotations;
@ -18,7 +19,7 @@ using OpenIddict.Models;
namespace Microsoft.Extensions.DependencyInjection {
public static class OpenIddictExtensions {
/// <summary>
/// Registers the Entity Framework stores. Note: when using the built-in Entity Framework stores,
/// Registers the Entity Framework Core stores. Note: when using the Entity Framework Core stores,
/// the entities MUST be derived from the models contained in the OpenIddict.Models package.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
@ -34,35 +35,42 @@ namespace Microsoft.Extensions.DependencyInjection {
builder.ScopeType != null &&
builder.TokenType != null, "The entity types exposed by OpenIddictBuilder shouldn't be null.");
var application = FindGenericBaseType(builder.ApplicationType, typeof(OpenIddictApplication<,>));
var application = FindGenericBaseType(builder.ApplicationType, typeof(OpenIddictApplication<,,>));
if (application == null) {
throw new InvalidOperationException("The Entity Framework stores can only be used " +
throw new InvalidOperationException("The Entity Framework Core stores can only be used " +
"with the built-in OpenIddictApplication entity.");
}
var authorization = FindGenericBaseType(builder.AuthorizationType, typeof(OpenIddictAuthorization<,>));
var authorization = FindGenericBaseType(builder.AuthorizationType, typeof(OpenIddictAuthorization<,,>));
if (authorization == null) {
throw new InvalidOperationException("The Entity Framework stores can only be used " +
throw new InvalidOperationException("The Entity Framework Core stores can only be used " +
"with the built-in OpenIddictAuthorization entity.");
}
var scope = FindGenericBaseType(builder.ScopeType, typeof(OpenIddictScope<>));
if (scope == null) {
throw new InvalidOperationException("The Entity Framework stores can only be used " +
throw new InvalidOperationException("The Entity Framework Core stores can only be used " +
"with the built-in OpenIddictScope entity.");
}
var token = FindGenericBaseType(builder.TokenType, typeof(OpenIddictToken<>));
var token = FindGenericBaseType(builder.TokenType, typeof(OpenIddictToken<,,>));
if (token == null) {
throw new InvalidOperationException("The Entity Framework stores can only be used " +
throw new InvalidOperationException("The Entity Framework Core stores can only be used " +
"with the built-in OpenIddictToken entity.");
}
var converter = TypeDescriptor.GetConverter(application.GenericTypeArguments[0]);
if (converter == null || !converter.CanConvertFrom(typeof(string)) ||
!converter.CanConvertTo(typeof(string))) {
throw new InvalidOperationException("The specified entity key type is not supported.");
}
// Register the application store in the DI container.
builder.Services.TryAddScoped(
typeof(IOpenIddictApplicationStore<>).MakeGenericType(builder.ApplicationType),
typeof(OpenIddictApplicationStore<,,,>).MakeGenericType(
typeof(OpenIddictApplicationStore<,,,,>).MakeGenericType(
/* TApplication: */ builder.ApplicationType,
/* TAuthorization: */ builder.AuthorizationType,
/* TToken: */ builder.TokenType,
/* TContext: */ typeof(TContext),
/* TKey: */ application.GenericTypeArguments[0]));
@ -70,8 +78,9 @@ namespace Microsoft.Extensions.DependencyInjection {
// Register the authorization store in the DI container.
builder.Services.TryAddScoped(
typeof(IOpenIddictAuthorizationStore<>).MakeGenericType(builder.AuthorizationType),
typeof(OpenIddictAuthorizationStore<,,,>).MakeGenericType(
typeof(OpenIddictAuthorizationStore<,,,,>).MakeGenericType(
/* TAuthorization: */ builder.AuthorizationType,
/* TApplication: */ builder.ApplicationType,
/* TToken: */ builder.TokenType,
/* TContext: */ typeof(TContext),
/* TKey: */ authorization.GenericTypeArguments[0]));
@ -87,8 +96,9 @@ namespace Microsoft.Extensions.DependencyInjection {
// Register the token store in the DI container.
builder.Services.TryAddScoped(
typeof(IOpenIddictTokenStore<>).MakeGenericType(builder.TokenType),
typeof(OpenIddictTokenStore<,,,>).MakeGenericType(
typeof(OpenIddictTokenStore<,,,,>).MakeGenericType(
/* TToken: */ builder.TokenType,
/* TApplication: */ builder.ApplicationType,
/* TAuthorization: */ builder.AuthorizationType,
/* TContext: */ typeof(TContext),
/* TKey: */ token.GenericTypeArguments[0]));
@ -103,7 +113,10 @@ namespace Microsoft.Extensions.DependencyInjection {
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbContextOptionsBuilder UseOpenIddict([NotNull] this DbContextOptionsBuilder builder) {
return builder.UseOpenIddict<OpenIddictApplication, OpenIddictAuthorization, OpenIddictScope, OpenIddictToken, string>();
return builder.UseOpenIddict<OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken, string>();
}
/// <summary>
@ -126,10 +139,10 @@ namespace Microsoft.Extensions.DependencyInjection {
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbContextOptionsBuilder UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey>([NotNull] this DbContextOptionsBuilder builder)
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TScope : OpenIddictScope<TKey>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TKey : IEquatable<TKey> {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
@ -148,7 +161,10 @@ namespace Microsoft.Extensions.DependencyInjection {
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static ModelBuilder UseOpenIddict([NotNull] this ModelBuilder builder) {
return builder.UseOpenIddict<OpenIddictApplication, OpenIddictAuthorization, OpenIddictScope, OpenIddictToken, string>();
return builder.UseOpenIddict<OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken, string>();
}
/// <summary>
@ -171,10 +187,10 @@ namespace Microsoft.Extensions.DependencyInjection {
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static ModelBuilder UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey>([NotNull] this ModelBuilder builder)
where TApplication : OpenIddictApplication<TKey, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TScope : OpenIddictScope<TKey>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TKey : IEquatable<TKey> {
if (builder == null) {
throw new ArgumentNullException(nameof(builder));
@ -188,11 +204,16 @@ namespace Microsoft.Extensions.DependencyInjection {
builder.Entity<TApplication>(entity => {
entity.HasKey(application => application.Id);
entity.HasIndex("ClientId")
entity.HasIndex(application => application.ClientId)
.IsUnique(unique: true);
entity.HasMany(application => application.Authorizations)
.WithOne(authorization => authorization.Application)
.HasForeignKey("ApplicationId")
.IsRequired(required: false);
entity.HasMany(application => application.Tokens)
.WithOne()
.WithOne(token => token.Application)
.HasForeignKey("ApplicationId")
.IsRequired(required: false);
@ -204,7 +225,7 @@ namespace Microsoft.Extensions.DependencyInjection {
entity.HasKey(authorization => authorization.Id);
entity.HasMany(application => application.Tokens)
.WithOne()
.WithOne(token => token.Authorization)
.HasForeignKey("AuthorizationId")
.IsRequired(required: false);
@ -229,6 +250,14 @@ namespace Microsoft.Extensions.DependencyInjection {
}
private static TypeInfo FindGenericBaseType(Type type, Type definition) {
if (type == null) {
throw new ArgumentNullException(nameof(type));
}
if (definition == null) {
throw new ArgumentNullException(nameof(definition));
}
for (var candidate = type.GetTypeInfo(); candidate != null; candidate = candidate.BaseType?.GetTypeInfo()) {
if (candidate.IsGenericType && candidate.GetGenericTypeDefinition() == definition) {
return candidate;

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

@ -16,16 +16,42 @@ using OpenIddict.Core;
using OpenIddict.Models;
namespace OpenIddict.EntityFrameworkCore {
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictApplicationStore<TContext> : OpenIddictApplicationStore<OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictToken, TContext, string>
where TContext : DbContext {
public OpenIddictApplicationStore([NotNull] TContext context) : base(context) { }
}
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictApplicationStore<TContext, TKey> : OpenIddictApplicationStore<OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictToken<TKey>, TContext, TKey>
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictApplicationStore([NotNull] TContext context) : base(context) { }
}
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
/// <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="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictApplicationStore<TApplication, TToken, TContext, TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictApplication<TKey, TToken>
where TToken : OpenIddictToken<TKey>, new()
public class OpenIddictApplicationStore<TApplication, TAuthorization, TToken, TContext, TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictApplicationStore([NotNull] TContext context) {
@ -51,23 +77,19 @@ namespace OpenIddict.EntityFrameworkCore {
/// </summary>
/// <param name="application">The application to create.</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.</returns>
public virtual async Task<string> CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the application.
/// </returns>
public virtual async Task<TApplication> CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
// Ensure that the key type can be serialized.
var converter = TypeDescriptor.GetConverter(typeof(TKey));
if (!converter.CanConvertTo(typeof(string))) {
throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported.");
}
Context.Add(application);
await Context.SaveChangesAsync(cancellationToken);
return converter.ConvertToInvariantString(application.Id);
return application;
}
/// <summary>
@ -102,15 +124,7 @@ namespace OpenIddict.EntityFrameworkCore {
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public virtual Task<TApplication> FindByIdAsync(string identifier, CancellationToken cancellationToken) {
var converter = TypeDescriptor.GetConverter(typeof(TKey));
// If the string key cannot be converted to TKey, return null
// to indicate that the requested application doesn't exist.
if (!converter.CanConvertFrom(typeof(string))) {
return Task.FromResult<TApplication>(null);
}
var key = (TKey) converter.ConvertFromInvariantString(identifier);
var key = ConvertIdentifierFromString(identifier);
return Applications.SingleOrDefaultAsync(application => application.Id.Equals(key), cancellationToken);
}
@ -209,6 +223,23 @@ namespace OpenIddict.EntityFrameworkCore {
return Task.FromResult(application.ClientSecret);
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
/// <param name="application">The application.</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 unique identifier associated with the application.
/// </returns>
public virtual Task<string> GetIdAsync([NotNull] TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Task.FromResult(ConvertIdentifierToString(application.Id));
}
/// <summary>
/// Retrieves the logout callback address associated with an application.
/// </summary>
@ -257,12 +288,6 @@ namespace OpenIddict.EntityFrameworkCore {
throw new ArgumentNullException(nameof(application));
}
// Ensure that the key type can be serialized.
var converter = TypeDescriptor.GetConverter(typeof(TKey));
if (!converter.CanConvertTo(typeof(string))) {
throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported.");
}
var query = from entity in Applications
where entity.Id.Equals(application.Id)
from token in entity.Tokens
@ -271,7 +296,7 @@ namespace OpenIddict.EntityFrameworkCore {
var tokens = new List<string>();
foreach (var identifier in await query.ToArrayAsync()) {
tokens.Add(converter.ConvertToInvariantString(identifier));
tokens.Add(ConvertIdentifierToString(identifier));
}
return tokens;
@ -345,5 +370,33 @@ namespace OpenIddict.EntityFrameworkCore {
catch (DbUpdateConcurrencyException) { }
}
/// <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([CanBeNull] string identifier) {
if (string.IsNullOrEmpty(identifier)) {
return default(TKey);
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey))
.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([CanBeNull] TKey identifier) {
if (Equals(identifier, default(TKey))) {
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey))
.ConvertToInvariantString(identifier);
}
}
}

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

@ -5,22 +5,52 @@
*/
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Core;
using OpenIddict.Models;
namespace OpenIddict.EntityFrameworkCore {
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictAuthorizationStore<TContext> : OpenIddictAuthorizationStore<OpenIddictAuthorization,
OpenIddictApplication,
OpenIddictToken, TContext, string>
where TContext : DbContext {
public OpenIddictAuthorizationStore([NotNull] TContext context) : base(context) { }
}
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictAuthorizationStore<TContext, TKey> : OpenIddictAuthorizationStore<OpenIddictAuthorization<TKey>,
OpenIddictApplication<TKey>,
OpenIddictToken<TKey>, TContext, TKey>
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictAuthorizationStore([NotNull] TContext context) : base(context) { }
}
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictAuthorizationStore<TAuthorization, TToken, TContext, TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
where TToken : OpenIddictToken<TKey>
public class OpenIddictAuthorizationStore<TAuthorization, TApplication, TToken, TContext, TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictAuthorizationStore([NotNull] TContext context) {
@ -36,9 +66,131 @@ namespace OpenIddict.EntityFrameworkCore {
/// </summary>
protected virtual TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
protected DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
protected DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Creates a new authorization.
/// </summary>
/// <param name="authorization">The authorization to create.</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 authorization.
/// </returns>
public virtual async Task<TAuthorization> CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) {
if (authorization == null) {
throw new ArgumentNullException(nameof(authorization));
}
Context.Add(authorization);
await Context.SaveChangesAsync(cancellationToken);
return authorization;
}
/// <summary>
/// Retrieves an authorization using its associated subject/client.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client 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 authorization corresponding to the subject/client.
/// </returns>
public virtual Task<TAuthorization> FindAsync(string subject, string client, CancellationToken cancellationToken) {
var key = ConvertIdentifierFromString(client);
return (from application in Applications
where application.Id.Equals(key)
from authorization in application.Authorizations
where authorization.Subject == subject
select authorization).FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier.
/// </returns>
public virtual Task<TAuthorization> FindByIdAsync(string identifier, CancellationToken cancellationToken) {
var key = ConvertIdentifierFromString(identifier);
return Authorizations.SingleOrDefaultAsync(authorization => authorization.Id.Equals(key), cancellationToken);
}
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
/// <param name="authorization">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 unique identifier associated with the authorization.
/// </returns>
public virtual Task<string> GetIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) {
if (authorization == null) {
throw new ArgumentNullException(nameof(authorization));
}
return Task.FromResult(ConvertIdentifierToString(authorization.Id));
}
/// <summary>
/// Retrieves the subject associated with an authorization.
/// </summary>
/// <param name="authorization">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 subject associated with the specified authorization.
/// </returns>
public virtual Task<string> GetSubjectAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken) {
if (authorization == null) {
throw new ArgumentNullException(nameof(authorization));
}
return Task.FromResult(authorization.Subject);
}
/// <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([CanBeNull] string identifier) {
if (string.IsNullOrEmpty(identifier)) {
return default(TKey);
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey))
.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([CanBeNull] TKey identifier) {
if (Equals(identifier, default(TKey))) {
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey))
.ConvertToInvariantString(identifier);
}
}
}

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

@ -5,12 +5,33 @@
*/
using System;
using System.ComponentModel;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Core;
using OpenIddict.Models;
namespace OpenIddict.EntityFrameworkCore {
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictScopeStore<TContext> : OpenIddictScopeStore<OpenIddictScope, TContext, string>
where TContext : DbContext {
public OpenIddictScopeStore([NotNull] TContext context) : base(context) { }
}
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictScopeStore<TContext, TKey> : OpenIddictScopeStore<OpenIddictScope<TKey>, TContext, TKey>
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictScopeStore([NotNull] TContext context) : base(context) { }
}
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
@ -18,7 +39,7 @@ namespace OpenIddict.EntityFrameworkCore {
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictScopeStore<TScope, TContext, TKey> : IOpenIddictScopeStore<TScope>
where TScope : OpenIddictScope<TKey>
where TScope : OpenIddictScope<TKey>, new()
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictScopeStore([NotNull] TContext context) {
@ -37,6 +58,34 @@ namespace OpenIddict.EntityFrameworkCore {
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TScope"/> entity.
/// </summary>
protected DbSet<TScope> Authorizations => Context.Set<TScope>();
protected DbSet<TScope> Scopes => Context.Set<TScope>();
/// <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([CanBeNull] string identifier) {
if (string.IsNullOrEmpty(identifier)) {
return default(TKey);
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey))
.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([CanBeNull] TKey identifier) {
if (Equals(identifier, default(TKey))) {
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey))
.ConvertToInvariantString(identifier);
}
}
}

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

@ -6,6 +6,7 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -14,16 +15,42 @@ using OpenIddict.Core;
using OpenIddict.Models;
namespace OpenIddict.EntityFrameworkCore {
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictTokenStore<TContext> : OpenIddictTokenStore<OpenIddictToken,
OpenIddictApplication,
OpenIddictAuthorization, TContext, string>
where TContext : DbContext {
public OpenIddictTokenStore([NotNull] TContext context) : base(context) { }
}
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictTokenStore<TContext, TKey> : OpenIddictTokenStore<OpenIddictToken<TKey>,
OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>, TContext, TKey>
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictTokenStore([NotNull] TContext context) : base(context) { }
}
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictTokenStore<TToken, TAuthorization, TContext, TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictToken<TKey>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TToken>
public class OpenIddictTokenStore<TToken, TApplication, TAuthorization, TContext, TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TContext : DbContext
where TKey : IEquatable<TKey> {
public OpenIddictTokenStore([NotNull] TContext context) {
@ -39,37 +66,56 @@ namespace OpenIddict.EntityFrameworkCore {
/// </summary>
protected virtual TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
protected DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
protected DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
protected DbSet<TToken> Tokens => Context.Set<TToken>();
/// <summary>
/// Creates a new token, which is not associated with a particular user or client.
/// Creates a new token.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="token">The token to create.</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 unique identifier associated with the token.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns>
public virtual async Task<string> CreateAsync([NotNull] string type, CancellationToken cancellationToken) {
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The token type cannot be null or empty.");
}
// Ensure that the key type can be serialized.
var converter = TypeDescriptor.GetConverter(typeof(TKey));
if (!converter.CanConvertTo(typeof(string))) {
throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported.");
public virtual async Task<TToken> CreateAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
var token = new TToken { Type = type };
Tokens.Add(token);
Context.Add(token);
await Context.SaveChangesAsync(cancellationToken);
return converter.ConvertToInvariantString(token.Id);
return token;
}
/// <summary>
/// Creates a new token, which is associated with a particular subject.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="subject">The subject 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 token.
/// </returns>
public virtual Task<TToken> CreateAsync([NotNull] string type, [NotNull] string subject, CancellationToken cancellationToken) {
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The token type cannot be null or empty.");
}
return CreateAsync(new TToken { Subject = subject, Type = type }, cancellationToken);
}
/// <summary>
@ -82,16 +128,73 @@ namespace OpenIddict.EntityFrameworkCore {
/// whose result returns the token corresponding to the unique identifier.
/// </returns>
public virtual Task<TToken> FindByIdAsync(string identifier, CancellationToken cancellationToken) {
// If the string key cannot be converted to TKey, return null
// to indicate that the requested token doesn't exist.
var converter = TypeDescriptor.GetConverter(typeof(TKey));
if (!converter.CanConvertFrom(typeof(string))) {
return Task.FromResult<TToken>(null);
var key = ConvertIdentifierFromString(identifier);
return Tokens.SingleOrDefaultAsync(token => token.Id.Equals(key), cancellationToken);
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the tokens.</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 specified subject.
/// </returns>
public virtual Task<TToken[]> FindBySubjectAsync(string subject, CancellationToken cancellationToken) {
return Tokens.Where(token => token.Subject == subject).ToArrayAsync();
}
/// <summary>
/// Retrieves the unique identifier associated with a token.
/// </summary>
/// <param name="token">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 unique identifier associated with the token.
/// </returns>
public virtual Task<string> GetIdAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
var key = (TKey) converter.ConvertFromInvariantString(identifier);
return Task.FromResult(ConvertIdentifierToString(token.Id));
}
return Tokens.SingleOrDefaultAsync(token => token.Id.Equals(key), cancellationToken);
/// <summary>
/// Retrieves the token type associated with a token.
/// </summary>
/// <param name="token">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 token type associated with the specified token.
/// </returns>
public virtual Task<string> GetTokenTypeAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
return Task.FromResult(token.Type);
}
/// <summary>
/// Retrieves the subject associated with a token.
/// </summary>
/// <param name="token">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 subject associated with the specified token.
/// </returns>
public virtual Task<string> GetSubjectAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
return Task.FromResult(token.Subject);
}
/// <summary>
@ -113,5 +216,130 @@ namespace OpenIddict.EntityFrameworkCore {
catch (DbUpdateConcurrencyException) { }
}
/// <summary>
/// Sets the authorization associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task SetAuthorizationAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
if (!string.IsNullOrEmpty(identifier)) {
var key = ConvertIdentifierFromString(identifier);
var authorization = await Authorizations.SingleOrDefaultAsync(element => element.Id.Equals(key));
if (authorization == null) {
throw new InvalidOperationException("The authorization associated with the token cannot be found.");
}
authorization.Tokens.Add(token);
}
else {
var key = await GetIdAsync(token, cancellationToken);
// Try to retrieve the authorization associated with the token.
// If none can be found, assume that no authorization is attached.
var authorization = await Authorizations.SingleOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
if (authorization != null) {
authorization.Tokens.Remove(token);
}
}
}
/// <summary>
/// Sets the client application associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The unique identifier associated with the client application.</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.
/// </returns>
public virtual async Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
if (!string.IsNullOrEmpty(identifier)) {
var key = ConvertIdentifierFromString(identifier);
var application = await Applications.SingleOrDefaultAsync(element => element.Id.Equals(key));
if (application == null) {
throw new InvalidOperationException("The application associated with the token cannot be found.");
}
application.Tokens.Add(token);
}
else {
var key = await GetIdAsync(token, cancellationToken);
// Try to retrieve the application associated with the token.
// If none can be found, assume that no application is attached.
var application = await Applications.SingleOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
if (application != null) {
application.Tokens.Remove(token);
}
}
}
/// <summary>
/// Updates an existing token.
/// </summary>
/// <param name="token">The token to update.</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.
/// </returns>
public virtual async Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken) {
if (token == null) {
throw new ArgumentNullException(nameof(token));
}
Context.Attach(token);
Context.Update(token);
try {
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
}
/// <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([CanBeNull] string identifier) {
if (string.IsNullOrEmpty(identifier)) {
return default(TKey);
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey))
.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([CanBeNull] TKey identifier) {
if (Equals(identifier, default(TKey))) {
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey))
.ConvertToInvariantString(identifier);
}
}
}

11
src/OpenIddict.Models/OpenIddictApplication.cs

@ -11,7 +11,7 @@ namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication : OpenIddictApplication<string, OpenIddictToken> {
public class OpenIddictApplication : OpenIddictApplication<string, OpenIddictAuthorization, OpenIddictToken> {
public OpenIddictApplication() {
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
@ -21,13 +21,18 @@ namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication<TKey> : OpenIddictApplication<TKey, OpenIddictToken<TKey>>
public class OpenIddictApplication<TKey> : OpenIddictApplication<TKey, OpenIddictAuthorization<TKey>, OpenIddictToken<TKey>>
where TKey : IEquatable<TKey> { }
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication<TKey, TToken> where TKey : IEquatable<TKey> {
public class OpenIddictApplication<TKey, TAuthorization, TToken> where TKey : IEquatable<TKey> {
/// <summary>
/// Gets the list of the authorizations associated with this application.
/// </summary>
public virtual IList<TAuthorization> Authorizations { get; } = new List<TAuthorization>();
/// <summary>
/// Gets or sets the client identifier
/// associated with the current application.

16
src/OpenIddict.Models/OpenIddictAuthorization.cs

@ -11,7 +11,7 @@ namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict authorization.
/// </summary>
public class OpenIddictAuthorization : OpenIddictAuthorization<string, OpenIddictToken> {
public class OpenIddictAuthorization : OpenIddictAuthorization<string, OpenIddictApplication, OpenIddictToken> {
public OpenIddictAuthorization() {
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
@ -21,13 +21,18 @@ namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict authorization.
/// </summary>
public class OpenIddictAuthorization<TKey> : OpenIddictAuthorization<TKey, OpenIddictToken<TKey>>
public class OpenIddictAuthorization<TKey> : OpenIddictAuthorization<TKey, OpenIddictApplication<TKey>, OpenIddictToken<TKey>>
where TKey : IEquatable<TKey> { }
/// <summary>
/// Represents an OpenIddict authorization.
/// </summary>
public class OpenIddictAuthorization<TKey, TToken> where TKey : IEquatable<TKey> {
public class OpenIddictAuthorization<TKey, TApplication, TToken> where TKey : IEquatable<TKey> {
/// <summary>
/// Gets or sets the application associated with the current authorization.
/// </summary>
public virtual TApplication Application { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current authorization.
@ -40,6 +45,11 @@ namespace OpenIddict.Models {
/// </summary>
public virtual string Scope { get; set; }
/// <summary>
/// Gets or sets the subject associated with the current authorization.
/// </summary>
public virtual string Subject { get; set; }
/// <summary>
/// Gets or sets the list of tokens
/// associated with the current authorization.

26
src/OpenIddict.Models/OpenIddictToken.cs

@ -10,7 +10,7 @@ namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict token.
/// </summary>
public class OpenIddictToken : OpenIddictToken<string> {
public class OpenIddictToken : OpenIddictToken<string, OpenIddictApplication, OpenIddictAuthorization> {
public OpenIddictToken() {
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
@ -20,13 +20,35 @@ namespace OpenIddict.Models {
/// <summary>
/// Represents an OpenIddict token.
/// </summary>
public class OpenIddictToken<TKey> where TKey : IEquatable<TKey> {
public class OpenIddictToken<TKey> : OpenIddictToken<TKey, OpenIddictApplication<TKey>, OpenIddictAuthorization<TKey>>
where TKey : IEquatable<TKey> {
}
/// <summary>
/// Represents an OpenIddict token.
/// </summary>
public class OpenIddictToken<TKey, TApplication, TAuthorization> where TKey : IEquatable<TKey> {
/// <summary>
/// Gets or sets the application associated with the current token.
/// </summary>
public virtual TApplication Application { get; set; }
/// <summary>
/// Gets or sets the authorization associated with the current token.
/// </summary>
public virtual TAuthorization Authorization { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current token.
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the subject associated with the current token.
/// </summary>
public virtual string Subject { get; set; }
/// <summary>
/// Gets or sets the type of the current token.
/// </summary>

5
src/OpenIddict.Mvc/OpenIddictModelBinder.cs

@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Primitives;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
@ -17,7 +18,7 @@ namespace OpenIddict.Mvc {
/// </summary>
/// <param name="context">The model binding context.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task BindModelAsync(ModelBindingContext context) {
public Task BindModelAsync([NotNull] ModelBindingContext context) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
@ -65,7 +66,7 @@ namespace OpenIddict.Mvc {
/// </summary>
/// <param name="context">The model binding context.</param>
/// <returns>The current instance or <c>null</c> if the model is not supported.</returns>
public IModelBinder GetBinder(ModelBinderProviderContext context) {
public IModelBinder GetBinder([NotNull] ModelBinderProviderContext context) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}

71
src/OpenIddict/OpenIddictProvider.Serialization.cs

@ -5,6 +5,8 @@
*/
using System;
using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
@ -18,11 +20,30 @@ namespace OpenIddict {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context) {
var applications = context.HttpContext.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), "The client identifier shouldn't be null or empty.");
if (!options.Value.DisableTokenRevocation) {
var identifier = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, context.HttpContext.RequestAborted);
// Resolve the subject from the authentication ticket. If it cannot be found, throw an exception.
var subject = context.Ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject) ??
context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier) ??
context.Ticket.Principal.GetClaim(ClaimTypes.Upn);
if (string.IsNullOrEmpty(subject)) {
throw new InvalidOperationException("The subject associated with the authentication ticket cannot be retrieved.");
}
// If a null value was returned by CreateAsync, return immediately.
var token = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, subject, context.HttpContext.RequestAborted);
if (token == null) {
return;
}
// Throw an exception if the token identifier can't be resolved.
var identifier = await tokens.GetIdAsync(token, context.HttpContext.RequestAborted);
if (string.IsNullOrEmpty(identifier)) {
throw new InvalidOperationException("The unique key associated with an authorization code cannot be null or empty.");
}
@ -31,15 +52,45 @@ namespace OpenIddict {
// to the authorization code to override the default GUID
// generated by the OpenID Connect server middleware.
context.Ticket.SetTicketId(identifier);
var application = await applications.FindByClientIdAsync(context.Request.ClientId, context.HttpContext.RequestAborted);
if (application == null) {
throw new InvalidOperationException("The client application cannot be retrieved from the database.");
}
await tokens.SetClientAsync(token, await applications.GetIdAsync(application, context.HttpContext.RequestAborted), context.HttpContext.RequestAborted);
// If an authorization identifier was specified, bind it to the token.
var authorization = context.Ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
if (!string.IsNullOrEmpty(authorization)) {
await tokens.SetAuthorizationAsync(token, authorization, context.HttpContext.RequestAborted);
}
}
}
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) {
var applications = context.HttpContext.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<OpenIddictOptions>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
if (!options.Value.DisableTokenRevocation) {
var identifier = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, context.HttpContext.RequestAborted);
// Resolve the subject from the authentication ticket. If it cannot be found, throw an exception.
var subject = context.Ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject) ??
context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier) ??
context.Ticket.Principal.GetClaim(ClaimTypes.Upn);
if (string.IsNullOrEmpty(subject)) {
throw new InvalidOperationException("The subject associated with the authentication ticket cannot be retrieved.");
}
// If a null value was returned by CreateAsync, return immediately.
var token = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, subject, context.HttpContext.RequestAborted);
if (token == null) {
return;
}
// Throw an exception if the token identifier can't be resolved.
var identifier = await tokens.GetIdAsync(token, context.HttpContext.RequestAborted);
if (string.IsNullOrEmpty(identifier)) {
throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty.");
}
@ -48,6 +99,22 @@ namespace OpenIddict {
// to the refresh token to override the default GUID
// generated by the OpenID Connect server middleware.
context.Ticket.SetTicketId(identifier);
// If the client application is known, associate it with the token.
if (!string.IsNullOrEmpty(context.Request.ClientId)) {
var application = await applications.FindByClientIdAsync(context.Request.ClientId, context.HttpContext.RequestAborted);
if (application == null) {
throw new InvalidOperationException("The client application cannot be retrieved from the database.");
}
await tokens.SetClientAsync(token, await applications.GetIdAsync(application, context.HttpContext.RequestAborted), context.HttpContext.RequestAborted);
}
// If an authorization identifier was specified, bind it to the token.
var authorization = context.Ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
if (!string.IsNullOrEmpty(authorization)) {
await tokens.SetAuthorizationAsync(token, authorization, context.HttpContext.RequestAborted);
}
}
}
}

80
test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs

@ -9,117 +9,129 @@ namespace OpenIddict.EntityFrameworkCore.Tests {
[Fact]
public void AddEntityFrameworkCoreStores_ThrowsAnExceptionForInvalidApplicationEntity() {
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(new ServiceCollection());
builder.ApplicationType = typeof(object);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(delegate {
services.AddOpenIddict<object, OpenIddictAuthorization, OpenIddictScope, OpenIddictToken>()
.AddEntityFrameworkCoreStores<DbContext>();
builder.AddEntityFrameworkCoreStores<DbContext>();
});
Assert.Equal("The Entity Framework stores can only be used " +
Assert.Equal("The Entity Framework Core stores can only be used " +
"with the built-in OpenIddictApplication entity.", exception.Message);
}
[Fact]
public void AddEntityFrameworkCoreStores_ThrowsAnExceptionForInvalidAuthorizationEntity() {
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(new ServiceCollection());
builder.AuthorizationType = typeof(object);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(delegate {
services.AddOpenIddict<OpenIddictApplication, object, OpenIddictScope, OpenIddictToken>()
.AddEntityFrameworkCoreStores<DbContext>();
builder.AddEntityFrameworkCoreStores<DbContext>();
});
Assert.Equal("The Entity Framework stores can only be used " +
Assert.Equal("The Entity Framework Core stores can only be used " +
"with the built-in OpenIddictAuthorization entity.", exception.Message);
}
[Fact]
public void AddEntityFrameworkCoreStores_ThrowsAnExceptionForInvalidScopeEntity() {
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(new ServiceCollection());
builder.ScopeType = typeof(object);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(delegate {
services.AddOpenIddict<OpenIddictApplication, OpenIddictAuthorization, object, OpenIddictToken>()
.AddEntityFrameworkCoreStores<DbContext>();
builder.AddEntityFrameworkCoreStores<DbContext>();
});
Assert.Equal("The Entity Framework stores can only be used " +
Assert.Equal("The Entity Framework Core stores can only be used " +
"with the built-in OpenIddictScope entity.", exception.Message);
}
[Fact]
public void AddEntityFrameworkCoreStores_ThrowsAnExceptionForInvalidTokenEntity() {
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(new ServiceCollection());
builder.TokenType = typeof(object);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(delegate {
services.AddOpenIddict<OpenIddictApplication, OpenIddictAuthorization, OpenIddictScope, object>()
.AddEntityFrameworkCoreStores<DbContext>();
builder.AddEntityFrameworkCoreStores<DbContext>();
});
Assert.Equal("The Entity Framework stores can only be used " +
Assert.Equal("The Entity Framework Core stores can only be used " +
"with the built-in OpenIddictToken entity.", exception.Message);
}
[Theory]
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication, OpenIddictToken, DbContext, string>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization, OpenIddictToken, DbContext, string>))]
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication, OpenIddictAuthorization, OpenIddictToken, DbContext, string>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization, OpenIddictApplication, OpenIddictToken, DbContext, string>))]
[InlineData(typeof(OpenIddictScopeStore<OpenIddictScope, DbContext, string>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken, OpenIddictAuthorization, DbContext, string>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken, OpenIddictApplication, OpenIddictAuthorization, DbContext, string>))]
public void AddEntityFrameworkCoreStores_RegistersEntityFrameworkStores(Type type) {
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services);
// Act
services.AddOpenIddict()
.AddEntityFrameworkCoreStores<DbContext>();
builder.AddEntityFrameworkCoreStores<DbContext>();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
}
[Theory]
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication<Guid>, OpenIddictToken<Guid>, DbContext, Guid>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization<Guid>, OpenIddictToken<Guid>, DbContext, Guid>))]
[InlineData(typeof(OpenIddictApplicationStore<OpenIddictApplication<Guid>, OpenIddictAuthorization<Guid>, OpenIddictToken<Guid>, DbContext, Guid>))]
[InlineData(typeof(OpenIddictAuthorizationStore<OpenIddictAuthorization<Guid>, OpenIddictApplication<Guid>, OpenIddictToken<Guid>, DbContext, Guid>))]
[InlineData(typeof(OpenIddictScopeStore<OpenIddictScope<Guid>, DbContext, Guid>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken<Guid>, OpenIddictAuthorization<Guid>, DbContext, Guid>))]
[InlineData(typeof(OpenIddictTokenStore<OpenIddictToken<Guid>, OpenIddictApplication<Guid>, OpenIddictAuthorization<Guid>, DbContext, Guid>))]
public void AddEntityFrameworkCoreStores_KeyTypeIsInferredFromEntities(Type type) {
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services) {
ApplicationType = typeof(OpenIddictApplication<Guid>),
AuthorizationType = typeof(OpenIddictAuthorization<Guid>),
ScopeType = typeof(OpenIddictScope<Guid>),
TokenType = typeof(OpenIddictToken<Guid>)
};
// Act
services.AddOpenIddict<Guid>()
.AddEntityFrameworkCoreStores<DbContext>();
builder.AddEntityFrameworkCoreStores<DbContext>();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
}
[Theory]
[InlineData(typeof(OpenIddictApplicationStore<CustomApplication, CustomToken, DbContext, long>))]
[InlineData(typeof(OpenIddictAuthorizationStore<CustomAuthorization, CustomToken, DbContext, long>))]
[InlineData(typeof(OpenIddictApplicationStore<CustomApplication, CustomAuthorization, CustomToken, DbContext, long>))]
[InlineData(typeof(OpenIddictAuthorizationStore<CustomAuthorization, CustomApplication, CustomToken, DbContext, long>))]
[InlineData(typeof(OpenIddictScopeStore<CustomScope, DbContext, long>))]
[InlineData(typeof(OpenIddictTokenStore<CustomToken, CustomAuthorization, DbContext, long>))]
[InlineData(typeof(OpenIddictTokenStore<CustomToken, CustomApplication, CustomAuthorization, DbContext, long>))]
public void AddEntityFrameworkCoreStores_DefaultEntitiesCanBeReplaced(Type type) {
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services) {
ApplicationType = typeof(CustomApplication),
AuthorizationType = typeof(CustomAuthorization),
ScopeType = typeof(CustomScope),
TokenType = typeof(CustomToken)
};
// Act
services.AddOpenIddict<CustomApplication, CustomAuthorization, CustomScope, CustomToken>()
.AddEntityFrameworkCoreStores<DbContext>();
builder.AddEntityFrameworkCoreStores<DbContext>();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
}
public class CustomApplication : OpenIddictApplication<long, CustomToken> { }
public class CustomAuthorization : OpenIddictAuthorization<long, CustomToken> { }
public class CustomApplication : OpenIddictApplication<long, CustomAuthorization, CustomToken> { }
public class CustomAuthorization : OpenIddictAuthorization<long, CustomApplication, CustomToken> { }
public class CustomScope : OpenIddictScope<long> { }
public class CustomToken : OpenIddictToken<long> { }
public class CustomToken : OpenIddictToken<long, CustomApplication, CustomAuthorization> { }
}
}

1
test/OpenIddict.Tests/OpenIddictExtensionsTests.cs

@ -18,7 +18,6 @@ namespace OpenIddict.Tests {
public void UseOpenIddict_ThrowsAnExceptionWhenServicesAreNotRegistered() {
// Arrange
var services = new ServiceCollection();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert

7
test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs

@ -503,7 +503,12 @@ namespace OpenIddict.Tests {
}));
builder.Services.AddSingleton(CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, It.IsAny<CancellationToken>()))
var token = new OpenIddictToken();
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
});

306
test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs

@ -2,6 +2,8 @@
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Core;
@ -11,11 +13,112 @@ using Xunit;
namespace OpenIddict.Tests {
public partial class OpenIddictProviderTests {
[Fact]
public async Task SerializeAuthorizationCode_AuthorizationCodeIsAutomaticallyPersisted() {
public async Task SerializeAuthorizationCode_AuthorizationCodeIsNotPersistedWhenRevocationIsDisabled() {
// Arrange
var manager = CreateTokenManager();
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
});
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task SerializeAuthorizationCode_AuthorizationCodeIsCorrectlyPersisted() {
// Arrange
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
});
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task SerializeAuthorizationCode_ClientApplicationIsAutomaticallyAttached() {
// Arrange
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder => {
@ -33,6 +136,9 @@ namespace OpenIddict.Tests {
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
instance.Setup(mock => mock.GetIdAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(manager);
@ -50,18 +156,206 @@ namespace OpenIddict.Tests {
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task SerializeRefreshToken_RefreshTokenIsAutomaticallyPersisted() {
public async Task SerializeAuthorizationCode_AuthorizationIsAutomaticallyAttached() {
// Arrange
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
instance.Setup(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
instance.Setup(mock => mock.GetIdAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(CreateAuthorizationManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization());
}));
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code,
});
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task SerializeRefreshToken_RefreshTokenIsNotPersistedWhenRevocationIsDisabled() {
// Arrange
var manager = CreateTokenManager();
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w",
Scope = OpenIdConnectConstants.Scopes.OfflineAccess
});
// Assert
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task SerializeRefreshToken_RefreshTokenIsCorrectlyPersisted() {
// Arrange
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w",
Scope = OpenIdConnectConstants.Scopes.OfflineAccess
});
// Assert
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task SerializeRefreshToken_ClientApplicationIsAutomaticallyAttached() {
// Arrange
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
instance.Setup(mock => mock.GetIdAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w",
Scope = OpenIdConnectConstants.Scopes.OfflineAccess
});
// Assert
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task SerializeRefreshToken_AuthorizationIsAutomaticallyAttached() {
// Arrange
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateAuthorizationManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization());
}));
builder.Services.AddSingleton(manager);
});
@ -78,7 +372,7 @@ namespace OpenIddict.Tests {
// Assert
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()), Times.Once());
}
}
}

12
test/OpenIddict.Tests/OpenIddictProviderTests.cs

@ -133,6 +133,8 @@ namespace OpenIddict.Tests {
ticket.SetScopes(request.GetScopes());
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70");
return context.Authentication.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, ticket.Properties);
}
@ -166,6 +168,16 @@ namespace OpenIddict.Tests {
return manager.Object;
}
private static OpenIddictAuthorizationManager<OpenIddictAuthorization> CreateAuthorizationManager(Action<Mock<OpenIddictAuthorizationManager<OpenIddictAuthorization>>> setup = null) {
var manager = new Mock<OpenIddictAuthorizationManager<OpenIddictAuthorization>>(
Mock.Of<IOpenIddictAuthorizationStore<OpenIddictAuthorization>>(),
Mock.Of<ILogger<OpenIddictAuthorizationManager<OpenIddictAuthorization>>>());
setup?.Invoke(manager);
return manager.Object;
}
private static OpenIddictTokenManager<OpenIddictToken> CreateTokenManager(Action<Mock<OpenIddictTokenManager<OpenIddictToken>>> setup = null) {
var manager = new Mock<OpenIddictTokenManager<OpenIddictToken>>(
Mock.Of<IOpenIddictTokenStore<OpenIddictToken>>(),

Loading…
Cancel
Save