// 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); } } } }