// 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 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 the specified user type.
///
/// The type representing a user.
public class MongoUserOnlyStore : MongoUserOnlyStore
where TUser : MongoIdentityUser, new()
{
///
/// Constructs a new instance of .
///
/// The .
/// The .
public MongoUserOnlyStore(IMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
///
/// Represents a new instance of a persistence store for the specified user and role types.
///
/// The type representing a user.
/// The type of the data context class used to access the store.
public class MongoUserOnlyStore : MongoUserOnlyStore
where TUser : MongoIdentityUser
where TContext : IMongoDbContext
{
///
/// Constructs a new instance of .
///
/// The .
/// The .
public MongoUserOnlyStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
///
/// Represents a new instance of a persistence store for the specified user and role types.
///
/// The type representing a user.
/// The type of the data context class used to access the store.
/// The type of the primary key for a role.
public class MongoUserOnlyStore : MongoUserOnlyStore, IdentityUserLogin, IdentityUserToken>
where TUser : MongoIdentityUser
where TContext : IMongoDbContext
where TKey : IEquatable
{
///
/// Constructs a new instance of .
///
/// The .
/// The .
public MongoUserOnlyStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
///
/// Represents a new instance of a persistence store for the specified user and role types.
///
/// The type representing a user.
/// The type of the data context class used to access the store.
/// The type of the primary key for a role.
/// The type representing a claim.
/// The type representing a user external login.
/// The type representing a user token.
public class MongoUserOnlyStore :
UserStoreBase,
IUserAuthenticationTokenStore
where TUser : MongoIdentityUser
where TContext : IMongoDbContext
where TKey : IEquatable
where TUserClaim : IdentityUserClaim, new()
where TUserLogin : IdentityUserLogin, new()
where TUserToken : IdentityUserToken, new()
{
///
/// Creates a new instance of the store.
///
/// The context used to access the store.
/// The used to describe store errors.
public MongoUserOnlyStore(TContext context, IdentityErrorDescriber describer = null) : base(describer ?? new IdentityErrorDescriber())
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Context = context;
}
///
/// Gets the database context for this store.
///
private static TContext Context { get; set; }
private static IMongoRepository _mongoRepository;
private static IMongoRepository MongoRepository
{
get
{
if (_mongoRepository == null)
{
_mongoRepository = new MongoRepository(Context);
}
return _mongoRepository;
}
}
private IMongoCollection UsersCollection { get { return Context.GetCollection(); } }
///
/// 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.
protected Task SaveChanges(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
///
/// Creates the specified in the user store.
///
/// The user to create.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation, containing the of the creation operation.
public async override Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
await UsersCollection.InsertOneAsync(user);
await SaveChanges(cancellationToken);
return IdentityResult.Success;
}
///
/// Updates the specified in the user store.
///
/// The user to update.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation, containing the of the update operation.
public async override Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var oldStamp = user.ConcurrencyStamp;
user.ConcurrencyStamp = Guid.NewGuid().ToString();
var updateRes = await UsersCollection.ReplaceOneAsync(x => x.Id.Equals(user.Id)
&& x.ConcurrencyStamp.Equals(oldStamp),
user);
if(updateRes.ModifiedCount == 0)
{
return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
}
return IdentityResult.Success;
}
///
/// Deletes the specified from the user store.
///
/// The user to delete.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation, containing the of the update operation.
public async override Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
user.Claims.Clear();
user.Roles.Clear();
user.Logins.Clear();
user.Tokens.Clear();
var oldStamp = user.ConcurrencyStamp;
user.ConcurrencyStamp = Guid.NewGuid().ToString();
var deleteRes = await UsersCollection.DeleteOneAsync(x => x.Id.Equals(user.Id)
&& x.ConcurrencyStamp.Equals(oldStamp));
if (deleteRes.DeletedCount == 0)
{
return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
}
return IdentityResult.Success;
}
///
/// Finds and returns a user, if any, who has the specified .
///
/// The user ID to search for.
/// The used to propagate notifications that the operation should be canceled.
///
/// The that represents the asynchronous operation, containing the user matching the specified if it exists.
///
public override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var id = ConvertIdFromString(userId);
return MongoRepository.GetByIdAsync(id);
}
///
/// Finds and returns a user, if any, who has the specified normalized user name.
///
/// The normalized user name to search for.
/// The used to propagate notifications that the operation should be canceled.
///
/// The that represents the asynchronous operation, containing the user matching the specified if it exists.
///
public override Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
return MongoRepository.GetOneAsync(u => u.NormalizedUserName == normalizedUserName);
}
///
/// A navigation property for the users the store contains.
///
public override IQueryable Users
{
get { return UsersCollection.AsQueryable(); }
}
///
/// Return a user with the matching userId if it exists.
///
/// The user's id.
/// The used to propagate notifications that the operation should be canceled.
/// The user if it exists.
protected override Task FindUserAsync(TKey userId, CancellationToken cancellationToken)
{
return MongoRepository.GetOneAsync(u => u.Id.Equals(userId));
}
///
/// Return a user login with the matching userId, provider, providerKey if it exists.
///
/// The user's id.
/// The login provider name.
/// The key provided by the to identify a user.
/// The used to propagate notifications that the operation should be canceled.
/// The user login if it exists.
protected override Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken)
{
var user = MongoRepository.GetOne(x => x.Id.Equals(userId) && x.Logins.Any(e => e.LoginProvider == loginProvider && e.ProviderKey == providerKey));
if (user != null)
{
return Task.FromResult((TUserLogin)user.GetUserLogin(loginProvider, providerKey));
}
return Task.FromResult(default(TUserLogin));
}
///
/// Return a user login with provider, providerKey if it exists.
///
/// The login provider name.
/// The key provided by the to identify a user.
/// The used to propagate notifications that the operation should be canceled.
/// The user login if it exists.
protected override Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)
{
var user = MongoRepository.GetOne(x => x.Logins.Any(e => e.LoginProvider == loginProvider && e.ProviderKey == providerKey));
if (user != null)
{
return Task.FromResult((TUserLogin)user.GetUserLogin(loginProvider, providerKey));
}
return Task.FromResult(default(TUserLogin));
}
#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 user whose claims should be retrieved.
/// The used to propagate notifications that the operation should be canceled.
/// A that contains the claims granted to a user.
public async override Task> GetClaimsAsync(TUser user, 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 (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return user.Claims.Select(e => e.ToClaim()).ToList();
}
///
/// Adds the given to the specified .
///
/// The user to add the claim to.
/// The claim to add to the user.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (claims == null)
{
throw new ArgumentNullException(nameof(claims));
}
var addedSome = false;
foreach (var claim in claims)
{
if (user.AddClaim(claim))
{
addedSome |= true;
}
}
if (addedSome)
{
var success = MongoRepository.UpdateOne>(user, p => p.Claims, user.Claims);
if (!success)
{
throw new Exception($"Failed to add claims to user {user.Id.ToString()}");
}
}
return Task.FromResult(false);
}
///
/// Replaces the on the specified , with the .
///
/// The user to replace the claim on.
/// The claim replace.
/// The new claim replacing the .
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public async override Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
if (newClaim == null)
{
throw new ArgumentNullException(nameof(newClaim));
}
if (user.ReplaceClaim(claim, newClaim))
{
await MongoRepository.UpdateOneAsync>(user, e => e.Claims, user.Claims);
}
}
///
/// Removes the given from the specified .
///
/// The user to remove the claims from.
/// The claim to remove.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public async override Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (claims == null)
{
throw new ArgumentNullException(nameof(claims));
}
if (user.RemoveClaims(claims))
{
await MongoRepository.UpdateOneAsync>(user, e => e.Claims, user.Claims);
}
}
///
/// Adds the given to the specified .
///
/// The user to add the login to.
/// The login to add to the user.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task AddLoginAsync(TUser user, UserLoginInfo login,
CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (login == null)
{
throw new ArgumentNullException(nameof(login));
}
if (user.AddLogin(login))
{
MongoRepository.UpdateOne>(user, e => e.Logins, user.Logins);
}
return Task.FromResult(false);
}
#pragma warning disable CS1998 // Cette méthode async n'a pas d'opérateur 'await' et elle s'exécutera de façon synchrone
///
/// Removes the given from the specified .
///
/// The user to remove the login from.
/// The login to remove from the user.
/// The key provided by the to identify a user.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey,
#pragma warning restore CS1998 // Cette méthode async n'a pas d'opérateur 'await' et elle s'exécutera de façon synchrone
CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var entry = user.Logins.FirstOrDefault(e => e.LoginProvider == loginProvider && e.ProviderKey == providerKey);
if (entry != null)
{
user.RemoveLogin(entry);
}
}
#pragma warning disable CS1998 // Cette méthode async n'a pas d'opérateur 'await' et elle s'exécutera de façon synchrone
///
/// Retrieves the associated logins for the specified .
///
/// The user whose associated logins to retrieve.
/// The used to propagate notifications that the operation should be canceled.
///
/// The for the asynchronous operation, containing a list of for the specified , if any.
///
public async override Task> GetLoginsAsync(TUser user, 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
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return user.Logins.ToList();
}
///
/// Retrieves the user associated with the specified login provider and login provider key.
///
/// The login provider who provided the .
/// The key provided by the to identify a user.
/// The used to propagate notifications that the operation should be canceled.
///
/// The for the asynchronous operation, containing the user, if any which matched the specified login provider and key.
///
public async override Task FindByLoginAsync(string loginProvider, string providerKey,
CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var userLogin = await FindUserLoginAsync(loginProvider, providerKey, cancellationToken);
if (userLogin != null)
{
return await FindUserAsync(userLogin.UserId, cancellationToken);
}
return null;
}
///
/// Gets the user, if any, associated with the specified, normalized email address.
///
/// The normalized email address to return the user for.
/// The used to propagate notifications that the operation should be canceled.
///
/// The task object containing the results of the asynchronous lookup operation, the user if any associated with the specified normalized email address.
///
public override Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
return MongoRepository.GetOneAsync(u => u.NormalizedEmail == normalizedEmail);
}
///
/// Retrieves all users with the specified claim.
///
/// The claim whose users should be retrieved.
/// The used to propagate notifications that the operation should be canceled.
///
/// The contains a list of users, if any, that contain the specified claim.
///
public async override Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
var filter = Builders.Filter.ElemMatch(x => x.Claims, userClaims => userClaims.Value.Equals(claim.Value)
&& userClaims.Type.Equals(claim.Type));
var cursor = UsersCollection.Find(filter);
var res = await cursor.ToListAsync();
return res;
}
#region Token Management
///
/// Find a user token if it exists.
///
/// The token owner.
/// The login provider for the token.
/// The name of the token.
/// The used to propagate notifications that the operation should be canceled.
/// The user token if it exists.
protected override Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
{
return Task.FromResult((TUserToken)user.GetToken(loginProvider, name));
}
///
/// Add a new user token.
///
/// The token to be added.
///
protected override Task AddUserTokenAsync(TUserToken token)
{
var user = MongoRepository.GetById(token.UserId);
if (user != null)
{
if (user.AddUserToken(token))
{
MongoRepository.UpdateOne>(user, e => e.Tokens, user.Tokens);
}
}
return Task.CompletedTask;
}
///
/// Remove a new user token.
///
/// The token to be removed.
///
protected override Task RemoveUserTokenAsync(TUserToken token)
{
var user = MongoRepository.GetById(token.UserId);
if (user != null)
{
if (user.RemoveUserToken(token))
{
MongoRepository.UpdateOne>(user, e => e.Tokens, user.Tokens);
}
}
return Task.CompletedTask;
}
#endregion Token Management
#region UserStoreBase overrides
///
/// Sets the given for the specified .
///
/// The user whose name should be set.
/// The user name to set.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.UserName != userName)
{
user.UserName = userName;
MongoRepository.UpdateOne(user, e => e.UserName, user.UserName);
}
return Task.CompletedTask;
}
///
/// Sets the given normalized name for the specified .
///
/// The user whose 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 override Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.NormalizedUserName != normalizedName)
{
user.NormalizedUserName = normalizedName;
MongoRepository.UpdateOne(user, e => e.NormalizedUserName, user.NormalizedUserName);
}
return Task.CompletedTask;
}
///
/// Sets the password hash for a user.
///
/// The user to set the password hash for.
/// The password hash to set.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.PasswordHash != passwordHash)
{
user.PasswordHash = passwordHash;
MongoRepository.UpdateOne(user, e => e.PasswordHash, user.PasswordHash);
}
return Task.CompletedTask;
}
///
/// Sets the flag indicating whether the specified 's email address has been confirmed or not.
///
/// The user whose email confirmation status should be set.
/// A flag indicating if the email address has been confirmed, true if the address is confirmed otherwise false.
/// The used to propagate notifications that the operation should be canceled.
/// The task object representing the asynchronous operation.
public override Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.EmailConfirmed != confirmed)
{
user.EmailConfirmed = confirmed;
MongoRepository.UpdateOne(user, e => e.EmailConfirmed, user.EmailConfirmed);
}
return Task.CompletedTask;
}
///
/// Sets the address for a .
///
/// The user whose email should be set.
/// The email to set.
/// The used to propagate notifications that the operation should be canceled.
/// The task object representing the asynchronous operation.
public override Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.Email != email)
{
user.Email = email;
MongoRepository.UpdateOne(user, e => e.Email, user.Email);
}
return Task.CompletedTask;
}
///
/// Sets the normalized email for the specified .
///
/// The user whose email address to set.
/// The normalized email to set for the specified .
/// The used to propagate notifications that the operation should be canceled.
/// The task object representing the asynchronous operation.
public override Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.NormalizedEmail != normalizedEmail)
{
user.NormalizedEmail = normalizedEmail;
MongoRepository.UpdateOne(user, e => e.NormalizedEmail, user.NormalizedEmail);
}
user.NormalizedEmail = normalizedEmail;
return Task.CompletedTask;
}
///
/// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a user.
///
/// The user whose lockout date should be set.
/// The after which the 's lockout should end.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.LockoutEnd != lockoutEnd)
{
user.LockoutEnd = lockoutEnd;
MongoRepository.UpdateOne(user, e => e.LockoutEnd, user.LockoutEnd);
}
return Task.CompletedTask;
}
///
/// Records that a failed access has occurred, incrementing the failed access count.
///
/// The user whose cancellation count should be incremented.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation, containing the incremented failed access count.
public override Task IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
user.AccessFailedCount++;
MongoRepository.UpdateOne(user, e => e.AccessFailedCount, user.AccessFailedCount);
return Task.FromResult(user.AccessFailedCount);
}
///
/// Resets a user's failed access count.
///
/// The user whose failed access count should be reset.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
/// This is typically called after the account is successfully accessed.
public override Task ResetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.AccessFailedCount != 0)
{
user.AccessFailedCount = 0;
MongoRepository.UpdateOne(user, e => e.AccessFailedCount, user.AccessFailedCount);
}
return Task.CompletedTask;
}
///
/// Set the flag indicating if the specified can be locked out..
///
/// The user whose ability to be locked out should be set.
/// A flag indicating if lock out can be enabled for the specified .
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.LockoutEnabled != enabled)
{
user.LockoutEnabled = enabled;
MongoRepository.UpdateOne(user, e => e.LockoutEnabled, user.LockoutEnabled);
}
return Task.CompletedTask;
}
///
/// Sets the telephone number for the specified .
///
/// The user whose telephone number should be set.
/// The telephone number to set.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task SetPhoneNumberAsync(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.PhoneNumber != phoneNumber)
{
user.PhoneNumber = phoneNumber;
MongoRepository.UpdateOne(user, e => e.PhoneNumber, user.PhoneNumber);
}
return Task.CompletedTask;
}
///
/// Sets a flag indicating if the specified 's phone number has been confirmed..
///
/// The user whose telephone number confirmation status should be set.
/// A flag indicating whether the user's telephone number has been confirmed.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.PhoneNumberConfirmed != confirmed)
{
user.PhoneNumberConfirmed = confirmed;
MongoRepository.UpdateOne(user, e => e.PhoneNumberConfirmed, user.PhoneNumberConfirmed);
}
return Task.CompletedTask;
}
///
/// Sets the provided security for the specified .
///
/// The user whose security stamp should be set.
/// The security stamp to set.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task SetSecurityStampAsync(TUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (stamp == null)
{
throw new ArgumentNullException(nameof(stamp));
}
if (user.SecurityStamp != stamp)
{
user.SecurityStamp = stamp;
MongoRepository.UpdateOne(user, e => e.SecurityStamp, user.SecurityStamp);
}
return Task.CompletedTask;
}
///
/// Sets a flag indicating whether the specified has two factor authentication enabled or not,
/// as an asynchronous operation.
///
/// The user whose two factor authentication enabled status should be set.
/// A flag indicating whether the specified has two factor authentication enabled.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task SetTwoFactorEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.TwoFactorEnabled != enabled)
{
user.TwoFactorEnabled = enabled;
MongoRepository.UpdateOne(user, e => e.TwoFactorEnabled, user.TwoFactorEnabled);
}
return Task.CompletedTask;
}
///
/// Sets the token value for a particular user.
///
/// The user.
/// The authentication provider for the token.
/// The name of the token.
/// The value of the token.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override async Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var token = await FindTokenAsync(user, loginProvider, name, cancellationToken);
if (token == null)
{
if (user.AddUserToken(CreateUserToken(user, loginProvider, name, value)))
{
MongoRepository.UpdateOne>(user, e => e.Tokens, user.Tokens);
}
//await AddUserTokenAsync(CreateUserToken(user, loginProvider, name, value));
}
else
{
if (user.SetToken(token, value))
{
MongoRepository.UpdateOne>(user, e => e.Tokens, user.Tokens);
}
}
}
///
/// Deletes a token for a user.
///
/// The user.
/// The authentication provider for the token.
/// The name of the token.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override async Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var entry = await FindTokenAsync(user, loginProvider, name, cancellationToken);
if (entry != null)
{
if (user.RemoveUserToken(entry))
{
MongoRepository.UpdateOne>(user, e => e.Tokens, user.Tokens);
}
}
}
///
/// Returns the token value.
///
/// The user.
/// The authentication provider for the token.
/// The name of the token.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override async Task GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var entry = await FindTokenAsync(user, loginProvider, name, cancellationToken);
return entry?.Value;
}
private const string InternalLoginProvider = "[AspNetUserStore]";
private const string AuthenticatorKeyTokenName = "AuthenticatorKey";
private const string RecoveryCodeTokenName = "RecoveryCodes";
///
/// Sets the authenticator key for the specified .
///
/// The user whose authenticator key should be set.
/// The authenticator key to set.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task SetAuthenticatorKeyAsync(TUser user, string key, CancellationToken cancellationToken)
=> SetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, key, cancellationToken);
///
/// Get the authenticator key for the specified .
///
/// The user whose security stamp should be set.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation, containing the security stamp for the specified .
public override Task GetAuthenticatorKeyAsync(TUser user, CancellationToken cancellationToken)
=> GetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, cancellationToken);
///
/// Returns how many recovery code are still valid for a user.
///
/// The user who owns the recovery code.
/// The used to propagate notifications that the operation should be canceled.
/// The number of valid recovery codes for the user..
public override async Task CountCodesAsync(TUser user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var mergedCodes = await GetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, cancellationToken) ?? "";
if (mergedCodes.Length > 0)
{
return mergedCodes.Split(';').Length;
}
return 0;
}
///
/// Updates the recovery codes for the user while invalidating any previous recovery codes.
///
/// The user to store new recovery codes for.
/// The new recovery codes for the user.
/// The used to propagate notifications that the operation should be canceled.
/// The new recovery codes for the user.
public override Task ReplaceCodesAsync(TUser user, IEnumerable recoveryCodes, CancellationToken cancellationToken)
{
var mergedCodes = string.Join(";", recoveryCodes);
return SetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, mergedCodes, cancellationToken);
}
///
/// Returns whether a recovery code is valid for a user. Note: recovery codes are only valid
/// once, and will be invalid after use.
///
/// The user who owns the recovery code.
/// The recovery code to use.
/// The used to propagate notifications that the operation should be canceled.
/// True if the recovery code was found for the user.
public override async Task RedeemCodeAsync(TUser user, string code, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (code == null)
{
throw new ArgumentNullException(nameof(code));
}
var mergedCodes = await GetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, cancellationToken) ?? "";
var splitCodes = mergedCodes.Split(';');
if (splitCodes.Contains(code))
{
var updatedCodes = new List(splitCodes.Where(s => s != code));
await ReplaceCodesAsync(user, updatedCodes, cancellationToken);
return true;
}
return false;
}
#endregion
}
}