466 lines
22 KiB
C#
466 lines
22 KiB
C#
// 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
|
|
{
|
|
/// <summary>
|
|
/// Creates a new instance of a persistence store for roles.
|
|
/// </summary>
|
|
/// <typeparam name="TRole">The type of the class representing a role</typeparam>
|
|
public class MongoRoleStore<TRole> : MongoRoleStore<TRole, MongoDbContext, string>
|
|
where TRole : MongoIdentityRole<string>
|
|
{
|
|
/// <summary>
|
|
/// Constructs a new instance of <see cref="MongoRoleStore{TRole}"/>.
|
|
/// </summary>
|
|
/// <param name="context">The <see cref="IMongoDbContext"/>.</param>
|
|
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
|
|
public MongoRoleStore(IMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of a persistence store for roles.
|
|
/// </summary>
|
|
/// <typeparam name="TRole">The type of the class representing a role.</typeparam>
|
|
/// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
|
|
public class MongoRoleStore<TRole, TContext> : MongoRoleStore<TRole, TContext, string>
|
|
where TRole : MongoIdentityRole<string>
|
|
where TContext : IMongoDbContext
|
|
{
|
|
/// <summary>
|
|
/// Constructs a new instance of <see cref="MongoRoleStore{TRole, TContext}"/>.
|
|
/// </summary>
|
|
/// <param name="context">The <see cref="IMongoDbContext"/>.</param>
|
|
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
|
|
public MongoRoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of a persistence store for roles.
|
|
/// </summary>
|
|
/// <typeparam name="TRole">The type of the class representing a role.</typeparam>
|
|
/// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
|
|
/// <typeparam name="TKey">The type of the primary key for a role.</typeparam>
|
|
public class MongoRoleStore<TRole, TContext, TKey> : MongoRoleStore<TRole, TContext, TKey, IdentityUserRole<TKey>, IdentityRoleClaim<TKey>>,
|
|
IQueryableRoleStore<TRole>,
|
|
IRoleClaimStore<TRole>
|
|
where TRole : MongoIdentityRole<TKey>
|
|
where TKey : IEquatable<TKey>
|
|
where TContext : IMongoDbContext
|
|
{
|
|
/// <summary>
|
|
/// Constructs a new instance of <see cref="MongoRoleStore{TRole, TContext, TKey}"/>.
|
|
/// </summary>
|
|
/// <param name="context">The <see cref="IMongoDbContext"/>.</param>
|
|
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
|
|
public MongoRoleStore(IMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of a persistence store for roles.
|
|
/// </summary>
|
|
/// <typeparam name="TRole">The type of the class representing a role.</typeparam>
|
|
/// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
|
|
/// <typeparam name="TKey">The type of the primary key for a role.</typeparam>
|
|
/// <typeparam name="TUserRole">The type of the class representing a user role.</typeparam>
|
|
/// <typeparam name="TRoleClaim">The type of the class representing a role claim.</typeparam>
|
|
public class MongoRoleStore<TRole, TContext, TKey, TUserRole, TRoleClaim> :
|
|
IQueryableRoleStore<TRole>,
|
|
IRoleClaimStore<TRole>
|
|
where TRole : MongoIdentityRole<TKey>
|
|
where TKey : IEquatable<TKey>
|
|
where TContext : IMongoDbContext
|
|
where TUserRole : IdentityUserRole<TKey>, new()
|
|
where TRoleClaim : IdentityRoleClaim<TKey>, new()
|
|
{
|
|
/// <summary>
|
|
/// Constructs a new instance of <see cref="MongoRoleStore{TRole, TContext, TKey, TUserRole, TRoleClaim}"/>.
|
|
/// </summary>
|
|
/// <param name="context">The <see cref="IMongoDbContext"/>.</param>
|
|
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
|
|
public MongoRoleStore(IMongoDbContext context, IdentityErrorDescriber describer = null)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
Context = context;
|
|
ErrorDescriber = describer ?? new IdentityErrorDescriber();
|
|
}
|
|
|
|
private bool _disposed;
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the database context for this store.
|
|
/// </summary>
|
|
private IMongoDbContext Context { get; }
|
|
|
|
private IMongoRepository _mongoRepository;
|
|
private IMongoRepository MongoRepository
|
|
{
|
|
get
|
|
{
|
|
if (_mongoRepository == null)
|
|
{
|
|
_mongoRepository = new MongoRepository(Context);
|
|
}
|
|
return _mongoRepository;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A navigation property for the roles the store contains.
|
|
/// </summary>
|
|
public virtual IQueryable<TRole> Roles => RolesCollection.AsQueryable();
|
|
|
|
/// <summary>
|
|
/// A navigation property for the roles the store contains.
|
|
/// </summary>
|
|
public virtual IMongoCollection<TRole> RolesCollection => Context.GetCollection<TRole>();
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="IdentityErrorDescriber"/> for any error that occurred with the current operation.
|
|
/// </summary>
|
|
public IdentityErrorDescriber ErrorDescriber { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called.
|
|
/// </summary>
|
|
/// <value>
|
|
/// True if changes should be automatically persisted, otherwise false.
|
|
/// </value>
|
|
public bool AutoSaveChanges { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Creates a new role in a store as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="role">The role to create in the store.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="IdentityResult"/> of the asynchronous query.</returns>
|
|
public async virtual Task<IdentityResult> CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ThrowIfDisposed();
|
|
if (role == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(role));
|
|
}
|
|
await MongoRepository.AddOneAsync<TRole, TKey>(role);
|
|
return IdentityResult.Success;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates a role in a store as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="role">The role to update in the store.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="IdentityResult"/> of the asynchronous query.</returns>
|
|
public async virtual Task<IdentityResult> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes a role from the store as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="role">The role to delete from the store.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="IdentityResult"/> of the asynchronous query.</returns>
|
|
public async virtual Task<IdentityResult> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the ID for a role from the store as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="role">The role whose ID should be returned.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>A <see cref="Task{TResult}"/> that contains the ID of the role.</returns>
|
|
public virtual Task<string> GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ThrowIfDisposed();
|
|
if (role == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(role));
|
|
}
|
|
return Task.FromResult(ConvertIdToString(role.Id));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name of a role from the store as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="role">The role whose name should be returned.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>A <see cref="Task{TResult}"/> that contains the name of the role.</returns>
|
|
public virtual Task<string> GetRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ThrowIfDisposed();
|
|
if (role == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(role));
|
|
}
|
|
return Task.FromResult(role.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the name of a role in the store as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="role">The role whose name should be set.</param>
|
|
/// <param name="roleName">The name of the role.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
|
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<TRole, TKey, string>(role, x => x.Name, role.Name);
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the provided <paramref name="id"/> to a strongly typed key object.
|
|
/// </summary>
|
|
/// <param name="id">The id to convert.</param>
|
|
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided <paramref name="id"/>.</returns>
|
|
public virtual TKey ConvertIdFromString(string id)
|
|
{
|
|
return id.ToTKey<TKey>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the provided <paramref name="id"/> to its string representation.
|
|
/// </summary>
|
|
/// <param name="id">The id to convert.</param>
|
|
/// <returns>An <see cref="string"/> representation of the provided <paramref name="id"/>.</returns>
|
|
public virtual string ConvertIdToString(TKey id)
|
|
{
|
|
if (id == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (id.Equals(default(TKey)))
|
|
{
|
|
return null;
|
|
}
|
|
return id.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the role who has the specified ID as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="id">The role ID to look for.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>A <see cref="Task{TResult}"/> that result of the look up.</returns>
|
|
public virtual Task<TRole> FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ThrowIfDisposed();
|
|
var roleId = ConvertIdFromString(id);
|
|
return MongoRepository.GetOneAsync<TRole, TKey>(u => u.Id.Equals(roleId));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the role who has the specified normalized name as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="normalizedName">The normalized role name to look for.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>A <see cref="Task{TResult}"/> that result of the look up.</returns>
|
|
public virtual Task<TRole> FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ThrowIfDisposed();
|
|
return MongoRepository.GetOneAsync<TRole, TKey>(r => r.NormalizedName == normalizedName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a role's normalized name as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="role">The role whose normalized name should be retrieved.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>A <see cref="Task{TResult}"/> that contains the name of the role.</returns>
|
|
public virtual Task<string> GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
ThrowIfDisposed();
|
|
if (role == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(role));
|
|
}
|
|
return Task.FromResult(role.NormalizedName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set a role's normalized name as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="role">The role whose normalized name should be set.</param>
|
|
/// <param name="normalizedName">The normalized name to set</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
|
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<TRole, TKey, string>(role, x => x.NormalizedName, role.NormalizedName);
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Throws if this class has been disposed.
|
|
/// </summary>
|
|
protected void ThrowIfDisposed()
|
|
{
|
|
if (_disposed)
|
|
{
|
|
throw new ObjectDisposedException(GetType().Name);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose the stores
|
|
/// </summary>
|
|
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
|
|
/// <summary>
|
|
/// Get the claims associated with the specified <paramref name="role"/> as an asynchronous operation.
|
|
/// </summary>
|
|
/// <param name="role">The role whose claims should be retrieved.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>A <see cref="Task{TResult}"/> that contains the claims granted to a role.</returns>
|
|
public async virtual Task<IList<Claim>> 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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the <paramref name="claim"/> given to the specified <paramref name="role"/>.
|
|
/// </summary>
|
|
/// <param name="role">The role to add the claim to.</param>
|
|
/// <param name="claim">The claim to add to the role.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
|
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<TRole, TKey, List<MongoClaim>>(role, e => e.Claims, role.Claims);
|
|
}
|
|
return Task.FromResult(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the <paramref name="claim"/> given from the specified <paramref name="role"/>.
|
|
/// </summary>
|
|
/// <param name="role">The role to remove the claim from.</param>
|
|
/// <param name="claim">The claim to remove from the role.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
|
|
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
|
|
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<TRole, TKey, List<MongoClaim>>(role, e => e.Claims, role.Claims);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Creates a entity representing a role claim.
|
|
/// </summary>
|
|
/// <param name="role">The associated role.</param>
|
|
/// <param name="claim">The associated claim.</param>
|
|
/// <returns>The role claim entity.</returns>
|
|
protected virtual TRoleClaim CreateRoleClaim(TRole role, Claim claim)
|
|
=> new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value };
|
|
}
|
|
} |