// 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 MongoDbGenericRepository; using MongoDB.Driver; using AspNetCore.Identity.MongoDbCore.Extensions; using AspNetCore.Identity.MongoDbCore.Models; using AspNetCore.Identity.MongoDbCore.Infrastructure; using Microsoft.AspNetCore.Identity; namespace AspNetCore.Identity.MongoDbCore { /// /// Creates a new instance of a persistence store for roles. /// /// The type of the class representing a role public class MongoRoleStore : MongoRoleStore where TRole : MongoIdentityRole { /// /// Constructs a new instance of . /// /// The . /// The . public MongoRoleStore(IMongoDbContext 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 MongoRoleStore : MongoRoleStore where TRole : MongoIdentityRole where TContext : IMongoDbContext { /// /// Constructs a new instance of . /// /// The . /// The . public MongoRoleStore(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 MongoRoleStore : MongoRoleStore, IdentityRoleClaim>, IQueryableRoleStore, IRoleClaimStore where TRole : MongoIdentityRole where TKey : IEquatable where TContext : IMongoDbContext { /// /// Constructs a new instance of . /// /// The . /// The . public MongoRoleStore(IMongoDbContext 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 MongoRoleStore : IQueryableRoleStore, IRoleClaimStore where TRole : MongoIdentityRole where TKey : IEquatable where TContext : IMongoDbContext where TUserRole : IdentityUserRole, new() where TRoleClaim : IdentityRoleClaim, new() { /// /// Constructs a new instance of . /// /// The . /// The . public MongoRoleStore(IMongoDbContext 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. /// private IMongoDbContext Context { get; } private IMongoRepository _mongoRepository; private IMongoRepository MongoRepository { get { if (_mongoRepository == null) { _mongoRepository = new MongoRepository(Context); } return _mongoRepository; } } /// /// A navigation property for the roles the store contains. /// public virtual IQueryable Roles => RolesCollection.AsQueryable(); /// /// A navigation property for the roles the store contains. /// public virtual IMongoCollection RolesCollection => Context.GetCollection(); /// /// 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; /// /// 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)); } await MongoRepository.AddOneAsync(role); 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)); } var oldStamp = role.ConcurrencyStamp; role.ConcurrencyStamp = Guid.NewGuid().ToString(); var updateRes = await RolesCollection.ReplaceOneAsync(x => x.Id.Equals(role.Id) && x.ConcurrencyStamp.Equals(oldStamp), role); if (updateRes.ModifiedCount == 0) { 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)); } var oldStamp = role.ConcurrencyStamp; role.ConcurrencyStamp = Guid.NewGuid().ToString(); var deleteRes = await RolesCollection.DeleteOneAsync(x => x.Id.Equals(role.Id) && x.ConcurrencyStamp.Equals(oldStamp)); if (deleteRes.DeletedCount == 0) { 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)); } if (role.Name != roleName) { role.Name = roleName; return MongoRepository.UpdateOneAsync(role, x => x.Name, role.Name); } 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) { return id.ToTKey(); } /// /// Converts the provided to its string representation. /// /// The id to convert. /// An representation of the provided . public virtual string ConvertIdToString(TKey id) { if (id == null) { return null; } 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 MongoRepository.GetOneAsync(u => u.Id.Equals(roleId)); } /// /// 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 MongoRepository.GetOneAsync(r => r.NormalizedName == normalizedName); } /// /// 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)); } if (role.NormalizedName != normalizedName) { role.NormalizedName = normalizedName; return MongoRepository.UpdateOneAsync(role, x => x.NormalizedName, role.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; #pragma warning disable CS1998 // Cette méthode async n'a pas d'opérateur 'await' et elle s'exécutera de façon synchrone /// /// 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)) #pragma warning restore CS1998 // Cette méthode async n'a pas d'opérateur 'await' et elle s'exécutera de façon synchrone { ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException(nameof(role)); } return role.Claims.Select(e => e.ToClaim()).ToList(); } /// /// 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)); } if (role.AddClaim(claim)) { MongoRepository.UpdateOne>(role, e => e.Claims, role.Claims); } 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)); } if (role.RemoveClaim(claim)) { await MongoRepository.UpdateOneAsync>(role, e => e.Claims, role.Claims); } } /// /// Creates a 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 }; } }