// 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 } }