// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Identity
{
///
/// Provides the APIs for managing roles in a persistence store.
///
/// The type encapsulating a role.
public class RoleManager : IDisposable where TRole : class
{
private bool _disposed;
///
/// The cancellation token used to cancel operations.
///
protected virtual CancellationToken CancellationToken => CancellationToken.None;
///
/// Constructs a new instance of .
///
/// The persistence store the manager will operate over.
/// A collection of validators for roles.
/// The normalizer to use when normalizing role names to keys.
/// The used to provider error messages.
/// The logger used to log messages, warnings and errors.
public RoleManager(IRoleStore store,
IEnumerable> roleValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
ILogger> logger)
{
if (store == null)
{
throw new ArgumentNullException(nameof(store));
}
Store = store;
KeyNormalizer = keyNormalizer;
ErrorDescriber = errors;
Logger = logger;
if (roleValidators != null)
{
foreach (var v in roleValidators)
{
RoleValidators.Add(v);
}
}
}
///
/// Gets the persistence store this instance operates over.
///
/// The persistence store this instance operates over.
protected IRoleStore Store { get; private set; }
///
/// Gets the used to log messages from the manager.
///
///
/// The used to log messages from the manager.
///
public virtual ILogger Logger { get; set; }
///
/// Gets a list of validators for roles to call before persistence.
///
/// A list of validators for roles to call before persistence.
public IList> RoleValidators { get; } = new List>();
///
/// Gets the used to provider error messages.
///
///
/// The used to provider error messages.
///
public IdentityErrorDescriber ErrorDescriber { get; set; }
///
/// Gets the normalizer to use when normalizing role names to keys.
///
///
/// The normalizer to use when normalizing role names to keys.
///
public ILookupNormalizer KeyNormalizer { get; set; }
///
/// Gets an IQueryable collection of Roles if the persistence store is an ,
/// otherwise throws a .
///
/// An IQueryable collection of Roles if the persistence store is an .
/// Thrown if the persistence store is not an .
///
/// Callers to this property should use to ensure the backing role store supports
/// returning an IQueryable list of roles.
///
public virtual IQueryable Roles
{
get
{
var queryableStore = Store as IQueryableRoleStore;
if (queryableStore == null)
{
throw new NotSupportedException(Resources.StoreNotIQueryableRoleStore);
}
return queryableStore.Roles;
}
}
///
/// Gets a flag indicating whether the underlying persistence store supports returning an collection of roles.
///
///
/// true if the underlying persistence store supports returning an collection of roles, otherwise false.
///
public virtual bool SupportsQueryableRoles
{
get
{
ThrowIfDisposed();
return Store is IQueryableRoleStore;
}
}
///
/// Gets a flag indicating whether the underlying persistence store supports s for roles.
///
///
/// true if the underlying persistence store supports s for roles, otherwise false.
///
public virtual bool SupportsRoleClaims
{
get
{
ThrowIfDisposed();
return Store is IRoleClaimStore;
}
}
///
/// Creates the specified in the persistence store.
///
/// The role to create.
///
/// The that represents the asynchronous operation.
///
public virtual async Task CreateAsync(TRole role)
{
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
var result = await ValidateRoleInternal(role);
if (!result.Succeeded)
{
return result;
}
await UpdateNormalizedRoleNameAsync(role);
result = await Store.CreateAsync(role, CancellationToken);
return result;
}
///
/// Updates the normalized name for the specified .
///
/// The role whose normalized name needs to be updated.
///
/// The that represents the asynchronous operation.
///
public virtual async Task UpdateNormalizedRoleNameAsync(TRole role)
{
var name = await GetRoleNameAsync(role);
await Store.SetNormalizedRoleNameAsync(role, NormalizeKey(name), CancellationToken);
}
///
/// Updates the specified .
///
/// The role to updated.
///
/// The that represents the asynchronous operation, containing the for the update.
///
public virtual Task UpdateAsync(TRole role)
{
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
return UpdateRoleAsync(role);
}
///
/// Deletes the specified .
///
/// The role to delete.
///
/// The that represents the asynchronous operation, containing the for the delete.
///
public virtual Task DeleteAsync(TRole role)
{
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
return Store.DeleteAsync(role, CancellationToken);
}
///
/// Gets a flag indicating whether the specified exists.
///
/// The role name whose existence should be checked.
///
/// The that represents the asynchronous operation, containing true if the role name exists, otherwise false.
///
public virtual async Task RoleExistsAsync(string roleName)
{
ThrowIfDisposed();
if (roleName == null)
{
throw new ArgumentNullException(nameof(roleName));
}
return await FindByNameAsync(NormalizeKey(roleName)) != null;
}
///
/// Gets a normalized representation of the specified .
///
/// The value to normalize.
/// A normalized representation of the specified .
public virtual string NormalizeKey(string key)
{
return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key);
}
///
/// Finds the role associated with the specified if any.
///
/// The role ID whose role should be returned.
///
/// The that represents the asynchronous operation, containing the role
/// associated with the specified
///
public virtual Task FindByIdAsync(string roleId)
{
ThrowIfDisposed();
return Store.FindByIdAsync(roleId, CancellationToken);
}
///
/// Gets the name of the specified .
///
/// The role whose name should be retrieved.
///
/// The that represents the asynchronous operation, containing the name of the
/// specified .
///
public virtual Task GetRoleNameAsync(TRole role)
{
ThrowIfDisposed();
return Store.GetRoleNameAsync(role, CancellationToken);
}
///
/// Sets the name of the specified .
///
/// The role whose name should be set.
/// The name to set.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task SetRoleNameAsync(TRole role, string name)
{
ThrowIfDisposed();
await Store.SetRoleNameAsync(role, name, CancellationToken);
await UpdateNormalizedRoleNameAsync(role);
return IdentityResult.Success;
}
///
/// Gets the ID of the specified .
///
/// The role whose ID should be retrieved.
///
/// The that represents the asynchronous operation, containing the ID of the
/// specified .
///
public virtual Task GetRoleIdAsync(TRole role)
{
ThrowIfDisposed();
return Store.GetRoleIdAsync(role, CancellationToken);
}
///
/// Finds the role associated with the specified if any.
///
/// The name of the role to be returned.
///
/// The that represents the asynchronous operation, containing the role
/// associated with the specified
///
public virtual Task FindByNameAsync(string roleName)
{
ThrowIfDisposed();
if (roleName == null)
{
throw new ArgumentNullException(nameof(roleName));
}
return Store.FindByNameAsync(NormalizeKey(roleName), CancellationToken);
}
///
/// Adds a claim to a role.
///
/// The role to add the claim to.
/// The claim to add.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task AddClaimAsync(TRole role, Claim claim)
{
ThrowIfDisposed();
var claimStore = GetClaimStore();
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
await claimStore.AddClaimAsync(role, claim, CancellationToken);
return await UpdateRoleAsync(role);
}
///
/// Removes a claim from a role.
///
/// The role to remove the claim from.
/// The claim to remove.
///
/// The that represents the asynchronous operation, containing the
/// of the operation.
///
public virtual async Task RemoveClaimAsync(TRole role, Claim claim)
{
ThrowIfDisposed();
var claimStore = GetClaimStore();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
await claimStore.RemoveClaimAsync(role, claim, CancellationToken);
return await UpdateRoleAsync(role);
}
///
/// Gets a list of claims associated with the specified .
///
/// The role whose claims should be returned.
///
/// The that represents the asynchronous operation, containing the list of s
/// associated with the specified .
///
public virtual Task> GetClaimsAsync(TRole role)
{
ThrowIfDisposed();
var claimStore = GetClaimStore();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
return claimStore.GetClaimsAsync(role, CancellationToken);
}
///
/// Releases all resources used by the role manager.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the unmanaged resources used by the role manager and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
Store.Dispose();
}
_disposed = true;
}
private async Task ValidateRoleInternal(TRole role)
{
var errors = new List();
foreach (var v in RoleValidators)
{
var result = await v.ValidateAsync(this, role);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
Logger.LogWarning(0, "Role {roleId} validation failed: {errors}.", await GetRoleIdAsync(role), string.Join(";", errors.Select(e => e.Code)));
return IdentityResult.Failed(errors.ToArray());
}
return IdentityResult.Success;
}
private async Task UpdateRoleAsync(TRole role)
{
var result = await ValidateRoleInternal(role);
if (!result.Succeeded)
{
return result;
}
await UpdateNormalizedRoleNameAsync(role);
return await Store.UpdateAsync(role, CancellationToken);
}
// IRoleClaimStore methods
private IRoleClaimStore GetClaimStore()
{
var cast = Store as IRoleClaimStore;
if (cast == null)
{
throw new NotSupportedException(Resources.StoreNotIRoleClaimStore);
}
return cast;
}
///
/// Throws if this class has been disposed.
///
protected void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
}
}