// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Identity { /// /// Exposes role related api which will automatically save changes to the RoleStore /// /// public class RoleManager : IDisposable where TRole : class { private bool _disposed; private HttpContext _context; /// /// Constructor /// /// The IRoleStore commits changes via the UpdateAsync/CreateAsync methods /// public RoleManager(IRoleStore store, IEnumerable> roleValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, ILogger> logger, IHttpContextAccessor contextAccessor) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; KeyNormalizer = keyNormalizer ?? new UpperInvariantLookupNormalizer(); ErrorDescriber = errors ?? new IdentityErrorDescriber(); _context = contextAccessor?.Value; if (roleValidators != null) { foreach (var v in roleValidators) { RoleValidators.Add(v); } } Logger = logger ?? new Logger>(new LoggerFactory()); } /// /// Persistence abstraction that the Manager operates against /// protected IRoleStore Store { get; private set; } /// /// Used to validate roles before persisting changes /// internal IList> RoleValidators { get; } = new List>(); /// /// Used to generate public API error messages /// internal IdentityErrorDescriber ErrorDescriber { get; set; } /// /// Used to log results /// internal ILogger> Logger { get; set; } /// /// Used to normalize user names, role names, emails for uniqueness /// internal ILookupNormalizer KeyNormalizer { get; set; } /// /// Returns an IQueryable of roles if the store is an IQueryableRoleStore /// public virtual IQueryable Roles { get { var queryableStore = Store as IQueryableRoleStore; if (queryableStore == null) { throw new NotSupportedException(Resources.StoreNotIQueryableRoleStore); } return queryableStore.Roles; } } /// /// Returns true if the store is an IQueryableRoleStore /// public virtual bool SupportsQueryableRoles { get { ThrowIfDisposed(); return Store is IQueryableRoleStore; } } /// /// Returns true if the store is an IUserClaimStore /// public virtual bool SupportsRoleClaims { get { ThrowIfDisposed(); return Store is IRoleClaimStore; } } private CancellationToken CancellationToken { get { return _context?.RequestAborted ?? CancellationToken.None; } } /// /// Dispose this object /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } 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); } } return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; } /// /// Create a role /// /// /// public virtual async Task CreateAsync(TRole role) { ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException("role"); } var result = await ValidateRoleInternal(role); if (!result.Succeeded) { return result; } await UpdateNormalizedRoleNameAsync(role); return await LogResultAsync(await Store.CreateAsync(role, CancellationToken), role); } /// /// Update the user's normalized user name /// /// /// public virtual async Task UpdateNormalizedRoleNameAsync(TRole role) { var name = await GetRoleNameAsync(role); await Store.SetNormalizedRoleNameAsync(role, NormalizeKey(name), CancellationToken); } /// /// Update an existing role /// /// /// public virtual async Task UpdateAsync(TRole role) { ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException("role"); } return await LogResultAsync(await UpdateRoleAsync(role), role); } 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); } /// /// Delete a role /// /// /// public virtual async Task DeleteAsync(TRole role) { ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException("role"); } return await LogResultAsync(await Store.DeleteAsync(role, CancellationToken), role); } /// /// Returns true if the role exists /// /// /// public virtual async Task RoleExistsAsync(string roleName) { ThrowIfDisposed(); if (roleName == null) { throw new ArgumentNullException("roleName"); } return await FindByNameAsync(NormalizeKey(roleName)) != null; } /// /// Normalize a key (role name) for uniqueness comparisons /// /// /// public virtual string NormalizeKey(string key) { return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key); } /// /// Find a role by id /// /// /// public virtual async Task FindByIdAsync(string roleId) { ThrowIfDisposed(); return await Store.FindByIdAsync(roleId, CancellationToken); } /// /// Return the name of the role /// /// /// public virtual async Task GetRoleNameAsync(TRole role) { ThrowIfDisposed(); return await Store.GetRoleNameAsync(role, CancellationToken); } /// /// Set the name of the role /// /// /// /// public virtual async Task SetRoleNameAsync(TRole role, string name) { ThrowIfDisposed(); await Store.SetRoleNameAsync(role, name, CancellationToken); await UpdateNormalizedRoleNameAsync(role); return await LogResultAsync(IdentityResult.Success, role); } /// /// Return the role id for a role /// /// /// public virtual async Task GetRoleIdAsync(TRole role) { ThrowIfDisposed(); return await Store.GetRoleIdAsync(role, CancellationToken); } /// /// FindByLoginAsync a role by name /// /// /// public virtual async Task FindByNameAsync(string roleName) { ThrowIfDisposed(); if (roleName == null) { throw new ArgumentNullException("roleName"); } return await Store.FindByNameAsync(NormalizeKey(roleName), CancellationToken); } // IRoleClaimStore methods private IRoleClaimStore GetClaimStore() { var cast = Store as IRoleClaimStore; if (cast == null) { throw new NotSupportedException(Resources.StoreNotIRoleClaimStore); } return cast; } /// /// Add a user claim /// /// /// /// public virtual async Task AddClaimAsync(TRole role, Claim claim) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (claim == null) { throw new ArgumentNullException("claim"); } if (role == null) { throw new ArgumentNullException("role"); } await claimStore.AddClaimAsync(role, claim, CancellationToken); return await LogResultAsync(await UpdateRoleAsync(role), role); } /// /// Remove a user claim /// /// /// /// public virtual async Task RemoveClaimAsync(TRole role, Claim claim) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (role == null) { throw new ArgumentNullException("role"); } await claimStore.RemoveClaimAsync(role, claim, CancellationToken); return await LogResultAsync(await UpdateRoleAsync(role), role); } /// /// Get a role's claims /// /// /// public virtual async Task> GetClaimsAsync(TRole role) { ThrowIfDisposed(); var claimStore = GetClaimStore(); if (role == null) { throw new ArgumentNullException("role"); } return await claimStore.GetClaimsAsync(role, CancellationToken); } /// /// Logs the current Identity Result and returns result object /// /// /// /// /// protected async Task LogResultAsync(IdentityResult result, TRole role, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") { result.Log(Logger, Resources.FormatLoggingResultMessageForRole(methodName, await GetRoleIdAsync(role))); return result; } private void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } /// /// When disposing, actually dipose the store /// /// protected virtual void Dispose(bool disposing) { if (disposing && !_disposed) { Store.Dispose(); } _disposed = true; } } }