// 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.ComponentModel; using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { /// /// Creates a new instance of a persistence store for roles. /// /// The type of the class representing a role public class RoleStore : RoleStore where TRole : IdentityRole { /// /// Constructs a new instance of . /// /// The . /// The . public RoleStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Creates a new instance of a persistence store for roles. /// /// The type of the class representing a role. /// The type of the data context class used to access the store. public class RoleStore : RoleStore where TRole : IdentityRole where TContext : DbContext { /// /// Constructs a new instance of . /// /// The . /// The . public RoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Creates a new instance of a persistence store for roles. /// /// The type of the class representing a role. /// The type of the data context class used to access the store. /// The type of the primary key for a role. public class RoleStore : RoleStore, IdentityRoleClaim>, IQueryableRoleStore, IRoleClaimStore where TRole : IdentityRole where TKey : IEquatable where TContext : DbContext { /// /// Constructs a new instance of . /// /// The . /// The . public RoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// /// Creates a new instance of a persistence store for roles. /// /// The type of the class representing a role. /// The type of the data context class used to access the store. /// The type of the primary key for a role. /// The type of the class representing a user role. /// The type of the class representing a role claim. public class RoleStore : IQueryableRoleStore, IRoleClaimStore where TRole : IdentityRole where TKey : IEquatable where TContext : DbContext where TUserRole : IdentityUserRole, new() where TRoleClaim : IdentityRoleClaim, new() { /// /// Constructs a new instance of . /// /// The . /// The . public RoleStore(TContext context, IdentityErrorDescriber describer = null) { if (context == null) { throw new ArgumentNullException(nameof(context)); } Context = context; ErrorDescriber = describer ?? new IdentityErrorDescriber(); } private bool _disposed; /// /// Gets the database context for this store. /// public TContext Context { get; private set; } /// /// Gets or sets the for any error that occurred with the current operation. /// public IdentityErrorDescriber ErrorDescriber { get; set; } /// /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. /// /// /// True if changes should be automatically persisted, otherwise false. /// public bool AutoSaveChanges { get; set; } = true; /// Saves the current store. /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. private async Task SaveChanges(CancellationToken cancellationToken) { if (AutoSaveChanges) { await Context.SaveChangesAsync(cancellationToken); } } /// /// Creates a new role in a store as an asynchronous operation. /// /// The role to create in the store. /// The used to propagate notifications that the operation should be canceled. /// A that represents the of the asynchronous query. public async virtual Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } Context.Add(role); await SaveChanges(cancellationToken); return IdentityResult.Success; } /// /// Updates a role in a store as an asynchronous operation. /// /// The role to update in the store. /// The used to propagate notifications that the operation should be canceled. /// A that represents the of the asynchronous query. public async virtual Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } Context.Attach(role); role.ConcurrencyStamp = Guid.NewGuid().ToString(); Context.Update(role); try { await SaveChanges(cancellationToken); } catch (DbUpdateConcurrencyException) { return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); } return IdentityResult.Success; } /// /// Deletes a role from the store as an asynchronous operation. /// /// The role to delete from the store. /// The used to propagate notifications that the operation should be canceled. /// A that represents the of the asynchronous query. public async virtual Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } Context.Remove(role); try { await SaveChanges(cancellationToken); } catch (DbUpdateConcurrencyException) { return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); } return IdentityResult.Success; } /// /// Gets the ID for a role from the store as an asynchronous operation. /// /// The role whose ID should be returned. /// The used to propagate notifications that the operation should be canceled. /// A that contains the ID of the role. public virtual Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } return Task.FromResult(ConvertIdToString(role.Id)); } /// /// Gets the name of a role from the store as an asynchronous operation. /// /// The role whose name should be returned. /// The used to propagate notifications that the operation should be canceled. /// A that contains the name of the role. public virtual Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } return Task.FromResult(role.Name); } /// /// Sets the name of a role in the store as an asynchronous operation. /// /// The role whose name should be set. /// The name of the role. /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. public virtual Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } role.Name = roleName; return Task.CompletedTask; } /// /// Converts the provided to a strongly typed key object. /// /// The id to convert. /// An instance of representing the provided . public virtual TKey ConvertIdFromString(string id) { if (id == null) { return default(TKey); } return (TKey)TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(id); } /// /// Converts the provided to its string representation. /// /// The id to convert. /// An representation of the provided . public virtual string ConvertIdToString(TKey id) { if (id.Equals(default(TKey))) { return null; } return id.ToString(); } /// /// Finds the role who has the specified ID as an asynchronous operation. /// /// The role ID to look for. /// The used to propagate notifications that the operation should be canceled. /// A that result of the look up. public virtual Task FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); var roleId = ConvertIdFromString(id); return Roles.FirstOrDefaultAsync(u => u.Id.Equals(roleId), cancellationToken); } /// /// Finds the role who has the specified normalized name as an asynchronous operation. /// /// The normalized role name to look for. /// The used to propagate notifications that the operation should be canceled. /// A that result of the look up. public virtual Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Roles.FirstOrDefaultAsync(r => r.NormalizedName == normalizedName, cancellationToken); } /// /// Get a role's normalized name as an asynchronous operation. /// /// The role whose normalized name should be retrieved. /// The used to propagate notifications that the operation should be canceled. /// A that contains the name of the role. public virtual Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } return Task.FromResult(role.NormalizedName); } /// /// Set a role's normalized name as an asynchronous operation. /// /// The role whose normalized name should be set. /// The normalized name to set /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. public virtual Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } role.NormalizedName = normalizedName; return Task.CompletedTask; } /// /// Throws if this class has been disposed. /// protected void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } /// /// Dispose the stores /// public void Dispose() => _disposed = true; /// /// Get the claims associated with the specified as an asynchronous operation. /// /// The role whose claims should be retrieved. /// The used to propagate notifications that the operation should be canceled. /// A that contains the claims granted to a role. public async virtual Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } return await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken); } /// /// Adds the given to the specified . /// /// The role to add the claim to. /// The claim to add to the role. /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. public virtual Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } if (claim == null) { throw new ArgumentNullException(nameof(claim)); } RoleClaims.Add(CreateRoleClaim(role, claim)); return Task.FromResult(false); } /// /// Removes the given from the specified . /// /// The role to remove the claim from. /// The claim to remove from the role. /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. public async virtual Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } if (claim == null) { throw new ArgumentNullException(nameof(claim)); } var claims = await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id) && rc.ClaimValue == claim.Value && rc.ClaimType == claim.Type).ToListAsync(cancellationToken); foreach (var c in claims) { RoleClaims.Remove(c); } } /// /// A navigation property for the roles the store contains. /// public virtual IQueryable Roles => Context.Set(); private DbSet RoleClaims { get { return Context.Set(); } } /// /// Creates an entity representing a role claim. /// /// The associated role. /// The associated claim. /// The role claim entity. protected virtual TRoleClaim CreateRoleClaim(TRole role, Claim claim) => new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; } }