22 changed files with 674 additions and 160 deletions
@ -0,0 +1,201 @@ |
|||
/* |
|||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|||
* the license and the contributors participating to this project. |
|||
*/ |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using AspNet.Security.OpenIdConnect.Extensions; |
|||
using AspNet.Security.OpenIdConnect.Server; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace OpenIddict { |
|||
/// <summary>
|
|||
/// Provides methods allowing to manage the users stored in the store.
|
|||
/// </summary>
|
|||
/// <typeparam name="TUser">The type of the User entity.</typeparam>
|
|||
public class OpenIddictUserManager<TUser> : UserManager<TUser> where TUser : class { |
|||
public OpenIddictUserManager( |
|||
[NotNull] IServiceProvider services, |
|||
[NotNull] IOpenIddictUserStore<TUser> store, |
|||
[NotNull] IOptions<IdentityOptions> options, |
|||
[NotNull] ILogger<OpenIddictUserManager<TUser>> logger, |
|||
[NotNull] IPasswordHasher<TUser> hasher, |
|||
[NotNull] IEnumerable<IUserValidator<TUser>> userValidators, |
|||
[NotNull] IEnumerable<IPasswordValidator<TUser>> passwordValidators, |
|||
[NotNull] ILookupNormalizer keyNormalizer, |
|||
[NotNull] IdentityErrorDescriber errors) |
|||
: base(store, options, hasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) { |
|||
Context = services?.GetRequiredService<IHttpContextAccessor>()?.HttpContext; |
|||
Logger = logger; |
|||
Options = options.Value; |
|||
Store = store; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the cancellation token used to abort async operations.
|
|||
/// </summary>
|
|||
protected CancellationToken CancellationToken => Context?.RequestAborted ?? CancellationToken.None; |
|||
|
|||
/// <summary>
|
|||
/// Gets the HTTP context associated with the current manager.
|
|||
/// </summary>
|
|||
protected HttpContext Context { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the identity options.
|
|||
/// </summary>
|
|||
protected IdentityOptions Options { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the store associated with the current manager.
|
|||
/// </summary>
|
|||
protected new IOpenIddictUserStore<TUser> Store { get; } |
|||
|
|||
/// <summary>
|
|||
/// Creates a new <see cref="ClaimsIdentity"/> used to create new tokens.
|
|||
/// </summary>
|
|||
/// <param name="user">The user corresponding to the identity.</param>
|
|||
/// <param name="scopes">The scopes granted by the resource owner.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|||
/// whose result returns the <see cref="ClaimsIdentity"/> corresponding to the user.
|
|||
/// </returns>
|
|||
public virtual async Task<ClaimsIdentity> CreateIdentityAsync(TUser user, IEnumerable<string> scopes) { |
|||
if (user == null) { |
|||
throw new ArgumentNullException(nameof(user)); |
|||
} |
|||
|
|||
if (scopes == null) { |
|||
throw new ArgumentNullException(nameof(scopes)); |
|||
} |
|||
|
|||
var identity = new ClaimsIdentity( |
|||
OpenIdConnectServerDefaults.AuthenticationScheme, |
|||
Options.ClaimsIdentity.UserNameClaimType, |
|||
Options.ClaimsIdentity.RoleClaimType); |
|||
|
|||
// Note: the name identifier is always included in both identity and
|
|||
// access tokens, even if an explicit destination is not specified.
|
|||
identity.AddClaim(ClaimTypes.NameIdentifier, await GetUserIdAsync(user)); |
|||
|
|||
// Resolve the email address associated with the user if the underlying store supports it.
|
|||
var email = SupportsUserEmail ? await GetEmailAsync(user) : null; |
|||
|
|||
// Only add the name claim if the "profile" scope was granted.
|
|||
if (scopes.Contains(OpenIdConnectConstants.Scopes.Profile)) { |
|||
var username = await GetUserNameAsync(user); |
|||
|
|||
// Throw an exception if the username corresponds to the registered
|
|||
// email address and if the "email" scope has not been requested.
|
|||
if (!scopes.Contains(OpenIdConnectConstants.Scopes.Email) && |
|||
!string.IsNullOrEmpty(email) && |
|||
string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) { |
|||
throw new InvalidOperationException("The 'email' scope is required."); |
|||
} |
|||
|
|||
identity.AddClaim(ClaimTypes.Name, username, |
|||
OpenIdConnectConstants.Destinations.AccessToken, |
|||
OpenIdConnectConstants.Destinations.IdentityToken); |
|||
} |
|||
|
|||
// Only add the email address if the "email" scope was granted.
|
|||
if (!string.IsNullOrEmpty(email) && scopes.Contains(OpenIdConnectConstants.Scopes.Email)) { |
|||
identity.AddClaim(ClaimTypes.Email, email, |
|||
OpenIdConnectConstants.Destinations.AccessToken, |
|||
OpenIdConnectConstants.Destinations.IdentityToken); |
|||
} |
|||
|
|||
if (SupportsUserRole && scopes.Contains(OpenIddictConstants.Scopes.Roles)) { |
|||
foreach (var role in await GetRolesAsync(user)) { |
|||
identity.AddClaim(identity.RoleClaimType, role, |
|||
OpenIdConnectConstants.Destinations.AccessToken, |
|||
OpenIdConnectConstants.Destinations.IdentityToken); |
|||
} |
|||
} |
|||
|
|||
if (SupportsUserSecurityStamp) { |
|||
var stamp = await GetSecurityStampAsync(user); |
|||
|
|||
if (!string.IsNullOrEmpty(stamp)) { |
|||
identity.AddClaim(Options.ClaimsIdentity.SecurityStampClaimType, stamp, |
|||
OpenIdConnectConstants.Destinations.AccessToken, |
|||
OpenIdConnectConstants.Destinations.IdentityToken); |
|||
} |
|||
} |
|||
|
|||
return identity; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new token associated with the given user.
|
|||
/// </summary>
|
|||
/// <param name="user">The user.</param>
|
|||
/// <param name="type">The token type.</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> CreateTokenAsync(TUser user, string type) { |
|||
if (user == null) { |
|||
throw new ArgumentNullException(nameof(user)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(type)) { |
|||
throw new ArgumentException("The token type cannot be null or empty.", nameof(type)); |
|||
} |
|||
|
|||
return Store.CreateTokenAsync(user, type, CancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new token associated with the given user and
|
|||
/// attached to the tokens issued to the specified client.
|
|||
/// </summary>
|
|||
/// <param name="user">The user.</param>
|
|||
/// <param name="client">The application.</param>
|
|||
/// <param name="type">The token type.</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> CreateTokenAsync(TUser user, string client, string type) { |
|||
if (user == null) { |
|||
throw new ArgumentNullException(nameof(user)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(type)) { |
|||
throw new ArgumentException("The token type cannot be null or empty.", nameof(type)); |
|||
} |
|||
|
|||
return Store.CreateTokenAsync(user, client, type, CancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the token identifiers associated with a user.
|
|||
/// </summary>
|
|||
/// <param name="user">The user.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|||
/// whose result returns the tokens associated with the user.
|
|||
/// </returns>
|
|||
public virtual Task<IEnumerable<string>> GetTokensAsync(TUser user) { |
|||
if (user == null) { |
|||
throw new ArgumentNullException(nameof(user)); |
|||
} |
|||
|
|||
return Store.GetTokensAsync(user, CancellationToken); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/* |
|||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|||
* the license and the contributors participating to this project. |
|||
*/ |
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Identity; |
|||
|
|||
namespace OpenIddict { |
|||
/// <summary>
|
|||
/// Provides methods allowing to manage the users stored in a database.
|
|||
/// </summary>
|
|||
/// <typeparam name="TUser">The type of the User entity.</typeparam>
|
|||
public interface IOpenIddictUserStore<TUser> : IUserStore<TUser> where TUser : class { |
|||
/// <summary>
|
|||
/// Creates a new token associated with the given user.
|
|||
/// </summary>
|
|||
/// <param name="user">The user associated with the token.</param>
|
|||
/// <param name="type">The token type.</param>
|
|||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|||
/// whose result returns the unique identifier associated with the token.
|
|||
/// </returns>
|
|||
Task<string> CreateTokenAsync(TUser user, string type, CancellationToken cancellationToken); |
|||
|
|||
/// <summary>
|
|||
/// Creates a new token associated with the given user and
|
|||
/// attached to the tokens issued to the specified client.
|
|||
/// </summary>
|
|||
/// <param name="user">The user.</param>
|
|||
/// <param name="client">The application.</param>
|
|||
/// <param name="type">The token type.</param>
|
|||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|||
/// whose result returns the unique identifier associated with the token.
|
|||
/// </returns>
|
|||
Task<string> CreateTokenAsync(TUser user, string client, string type, CancellationToken cancellationToken); |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the token identifiers associated with a user.
|
|||
/// </summary>
|
|||
/// <param name="user">The user.</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 associated with the user.
|
|||
/// </returns>
|
|||
Task<IEnumerable<string>> GetTokensAsync(TUser user, CancellationToken cancellationToken); |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
/* |
|||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|||
* the license and the contributors participating to this project. |
|||
*/ |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore; |
|||
|
|||
namespace OpenIddict { |
|||
/// <summary>
|
|||
/// Provides methods allowing to manage the users stored in a database.
|
|||
/// </summary>
|
|||
/// <typeparam name="TUser">The type of the User entity.</typeparam>
|
|||
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
|
|||
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
|
|||
/// <typeparam name="TRole">The type of the Role 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 OpenIddictUserStore<TUser, TApplication, TAuthorization, TRole, TToken, TContext, TKey> : |
|||
UserStore<TUser, TRole, TContext, TKey>, IOpenIddictUserStore<TUser> |
|||
where TUser : OpenIddictUser<TKey, TAuthorization, TToken>, new() |
|||
where TApplication : OpenIddictApplication<TKey, TToken> |
|||
where TAuthorization : OpenIddictAuthorization<TKey, TToken> |
|||
where TRole : IdentityRole<TKey> |
|||
where TToken : OpenIddictToken<TKey>, new() |
|||
where TContext : DbContext |
|||
where TKey : IEquatable<TKey> { |
|||
public OpenIddictUserStore(TContext context) |
|||
: base(context) { } |
|||
|
|||
/// <summary>
|
|||
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
|
|||
/// </summary>
|
|||
protected DbSet<TApplication> Applications => Context.Set<TApplication>(); |
|||
|
|||
/// <summary>
|
|||
/// Creates a new token associated with the given user and defined by a unique identifier and a token type.
|
|||
/// </summary>
|
|||
/// <param name="user">The user associated with the token.</param>
|
|||
/// <param name="type">The token type.</param>
|
|||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|||
/// whose result returns the unique identifier associated with the token.
|
|||
/// </returns>
|
|||
public virtual async Task<string> CreateTokenAsync(TUser user, string type, CancellationToken cancellationToken) { |
|||
if (user == null) { |
|||
throw new ArgumentNullException(nameof(user)); |
|||
} |
|||
|
|||
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."); |
|||
} |
|||
|
|||
var token = new TToken { Type = type }; |
|||
user.Tokens.Add(token); |
|||
|
|||
Context.Update(user); |
|||
|
|||
await Context.SaveChangesAsync(cancellationToken); |
|||
|
|||
return converter.ConvertToInvariantString(token.Id); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new token associated with the given user and
|
|||
/// attached to the tokens issued to the specified client.
|
|||
/// </summary>
|
|||
/// <param name="user">The user.</param>
|
|||
/// <param name="client">The application.</param>
|
|||
/// <param name="type">The token type.</param>
|
|||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|||
/// whose result returns the unique identifier associated with the token.
|
|||
/// </returns>
|
|||
public virtual async Task<string> CreateTokenAsync(TUser user, string client, string type, CancellationToken cancellationToken) { |
|||
if (user == null) { |
|||
throw new ArgumentNullException(nameof(user)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(client)) { |
|||
throw new ArgumentException("The client identifier cannot be null or empty."); |
|||
} |
|||
|
|||
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)) || !converter.CanConvertFrom(typeof(string))) { |
|||
throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported."); |
|||
} |
|||
|
|||
var key = (TKey) converter.ConvertFromInvariantString(client); |
|||
|
|||
var application = await Applications.FirstOrDefaultAsync(entity => entity.Id.Equals(key), cancellationToken); |
|||
if (application == null) { |
|||
throw new InvalidOperationException("The application cannot be found in the database."); |
|||
} |
|||
|
|||
var token = new TToken { Type = type }; |
|||
|
|||
application.Tokens.Add(token); |
|||
user.Tokens.Add(token); |
|||
|
|||
Context.Update(application); |
|||
Context.Update(user); |
|||
|
|||
await Context.SaveChangesAsync(cancellationToken); |
|||
|
|||
return converter.ConvertToInvariantString(token.Id); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the token identifiers associated with a user.
|
|||
/// </summary>
|
|||
/// <param name="user">The user.</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 associated with the user.
|
|||
/// </returns>
|
|||
public virtual async Task<IEnumerable<string>> GetTokensAsync(TUser user, CancellationToken cancellationToken) { |
|||
if (user == null) { |
|||
throw new ArgumentNullException(nameof(user)); |
|||
} |
|||
|
|||
// 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 Users |
|||
where entity.Id.Equals(user.Id) |
|||
from token in entity.Tokens |
|||
select token.Id; |
|||
|
|||
var tokens = new List<string>(); |
|||
|
|||
foreach (var identifier in await query.ToArrayAsync()) { |
|||
tokens.Add(converter.ConvertToInvariantString(identifier)); |
|||
} |
|||
|
|||
return tokens; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue