diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs index 0655306595..2fafc38694 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs @@ -2,11 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { @@ -135,35 +131,13 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// public DbSet RoleClaims { get; set; } - private string _version; /// - /// Gets or sets the version to use during . - /// If not set, defaults to the value in the - /// - public string Version { - get => _version ?? - this.GetService() - .Extensions.OfType() - .FirstOrDefault()?.ApplicationServiceProvider - ?.GetService>() - ?.Value?.Version; - set => _version = value; - } - - /// - /// Creates the latest identity model. - /// - /// - protected void OnModelCreatingLatest(ModelBuilder builder) - => OnModelCreatingV2(builder); - - /// - /// Configures the schema needed for the identity framework version . + /// Configures the schema needed for the identity framework. /// /// /// The builder being used to construct the model for this context. /// - protected void OnModelCreatingV2(ModelBuilder builder) + protected override void OnModelCreating(ModelBuilder builder) { builder.Entity(b => { @@ -178,6 +152,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore b.Property(u => u.Email).HasMaxLength(256); b.Property(u => u.NormalizedEmail).HasMaxLength(256); + // Replace with b.HasMany(). b.HasMany().WithOne().HasForeignKey(uc => uc.UserId).IsRequired(); b.HasMany().WithOne().HasForeignKey(ul => ul.UserId).IsRequired(); b.HasMany().WithOne().HasForeignKey(ur => ur.UserId).IsRequired(); @@ -198,19 +173,19 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore b.HasMany().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired(); }); - builder.Entity(b => + builder.Entity(b => { b.HasKey(uc => uc.Id); b.ToTable("AspNetUserClaims"); }); - builder.Entity(b => + builder.Entity(b => { b.HasKey(rc => rc.Id); b.ToTable("AspNetRoleClaims"); }); - builder.Entity(b => + builder.Entity(b => { b.HasKey(r => new { r.UserId, r.RoleId }); b.ToTable("AspNetUserRoles"); @@ -222,53 +197,11 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore b.ToTable("AspNetUserLogins"); }); - builder.Entity(b => + builder.Entity(b => { b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name }); b.ToTable("AspNetUserTokens"); }); } - - /// - /// Configures the schema needed for the identity framework version . - /// - /// - /// The builder being used to construct the model for this context. - /// - protected void OnModelCreatingV1(ModelBuilder builder) - { - - OnModelCreatingV2(builder); - - // Ignore the additional 2.0 properties that were added - builder.Entity(b => - { - b.Ignore(u => u.LastPasswordChangeDate); - b.Ignore(u => u.LastSignInDate); - b.Ignore(u => u.CreateDate); - }); - } - - /// - /// Configures the schema needed for the identity framework. - /// - /// - /// The builder being used to construct the model for this context. - /// - protected override void OnModelCreating(ModelBuilder builder) - { - if (Version == IdentityStoreOptions.Version_Latest) - { - OnModelCreatingLatest(builder); - } - else if (Version == IdentityStoreOptions.Version2_0) - { - OnModelCreatingV2(builder); - } - else // Default is always the v1 schema - { - OnModelCreatingV1(builder); - } - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs index c88e77fc5a..6a25296f53 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs @@ -15,52 +15,6 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class IdentityEntityFrameworkBuilderExtensions { - /// - /// Adds an Entity Framework implementation of identity information stores with the schema/features in - /// . - /// - /// The Entity Framework database context to use. - /// The instance this method extends. - /// The instance this method extends. - public static IdentityBuilder AddEntityFrameworkStoresV1(this IdentityBuilder builder) - where TContext : DbContext - { - builder.Services.Configure(o => o.Version = IdentityStoreOptions.Version1_0); - AddStores(builder.Services, typeof(UserStoreV1<,,,,,,,,>), typeof(RoleStoreV1<,,,,>), builder.UserType, builder.RoleType, typeof(TContext)); - return builder; - } - - /// - /// Adds an Entity Framework implementation of identity information stores with the schema/features in - /// . - /// - /// The Entity Framework database context to use. - /// The instance this method extends. - /// The instance this method extends. - public static IdentityBuilder AddEntityFrameworkStoresV2(this IdentityBuilder builder) - where TContext : DbContext - { - builder.Services.Configure(o => o.Version = IdentityStoreOptions.Version2_0); - // Note: RoleStore was not changed for V2. - AddStores(builder.Services, typeof(UserStoreV2<,,,,,,,,>), typeof(RoleStoreV1<,,,,>), builder.UserType, builder.RoleType, typeof(TContext)); - return builder; - } - - /// - /// Adds an Entity Framework implementation of identity information stores with the latest schema/features in - /// . - /// - /// The Entity Framework database context to use. - /// The instance this method extends. - /// The instance this method extends. - public static IdentityBuilder AddEntityFrameworkStoresLatest(this IdentityBuilder builder) - where TContext : DbContext - { - builder.Services.Configure(o => o.Version = IdentityStoreOptions.Version_Latest); - AddStores(builder.Services, typeof(UserStore<,,,,,,,,>), typeof(RoleStore<,,,,>), builder.UserType, builder.RoleType, typeof(TContext)); - return builder; - } - /// /// Adds an Entity Framework implementation of identity information stores. /// @@ -69,9 +23,12 @@ namespace Microsoft.Extensions.DependencyInjection /// The instance this method extends. public static IdentityBuilder AddEntityFrameworkStores(this IdentityBuilder builder) where TContext : DbContext - => builder.AddEntityFrameworkStoresV1(); + { + AddStores(builder.Services, builder.UserType, builder.RoleType, typeof(TContext)); + return builder; + } - private static void AddStores(IServiceCollection services, Type userStore, Type roleStore, Type userType, Type roleType, Type contextType) + private static void AddStores(IServiceCollection services, Type userType, Type roleType, Type contextType) { var identityUserType = FindGenericBaseType(userType, typeof(IdentityUser<,,,,>)); if (identityUserType == null) @@ -86,7 +43,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddScoped( typeof(IUserStore<>).MakeGenericType(userType), - userStore.MakeGenericType(userType, roleType, contextType, + typeof(UserStore<,,,,,,,,>).MakeGenericType(userType, roleType, contextType, identityUserType.GenericTypeArguments[0], identityUserType.GenericTypeArguments[1], identityUserType.GenericTypeArguments[2], @@ -95,7 +52,7 @@ namespace Microsoft.Extensions.DependencyInjection identityRoleType.GenericTypeArguments[2])); services.TryAddScoped( typeof(IRoleStore<>).MakeGenericType(roleType), - roleStore.MakeGenericType(roleType, contextType, + typeof(RoleStore<,,,,>).MakeGenericType(roleType, contextType, identityRoleType.GenericTypeArguments[0], identityRoleType.GenericTypeArguments[1], identityRoleType.GenericTypeArguments[2])); diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs index f05e9827dd..31212b061f 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs @@ -2,10 +2,70 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { + /// + /// Creates a new instance of a persistence store for roles. + /// + /// The type of the class representing a role + public class RoleStore : RoleStore + where TRole : IdentityRole + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public RoleStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } + } + + /// + /// Creates a new instance of a persistence store for roles. + /// + /// The type of the class representing a role. + /// The type of the data context class used to access the store. + public class RoleStore : RoleStore + where TRole : IdentityRole + where TContext : DbContext + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public RoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } + } + + /// + /// Creates a new instance of a persistence store for roles. + /// + /// The type of the class representing a role. + /// The type of the data context class used to access the store. + /// The type of the primary key for a role. + public class RoleStore : RoleStore, IdentityRoleClaim>, + IQueryableRoleStore, + IRoleClaimStore + where TRole : IdentityRole + where TKey : IEquatable + where TContext : DbContext + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public RoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } + } + /// /// Creates a new instance of a persistence store for roles. /// @@ -15,7 +75,8 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// The type of the class representing a user role. /// The type of the class representing a role claim. public class RoleStore : - RoleStoreV1 + IQueryableRoleStore, + IRoleClaimStore where TRole : IdentityRole where TKey : IEquatable where TContext : DbContext @@ -27,7 +88,368 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// /// The . /// The . - public RoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) - { } + public RoleStore(TContext context, IdentityErrorDescriber describer = null) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + Context = context; + ErrorDescriber = describer ?? new IdentityErrorDescriber(); + } + + private bool _disposed; + + + /// + /// Gets the database context for this store. + /// + public TContext Context { get; private set; } + + /// + /// Gets or sets the for any error that occurred with the current operation. + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + public bool AutoSaveChanges { get; set; } = true; + + /// Saves the current store. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + private async Task SaveChanges(CancellationToken cancellationToken) + { + if (AutoSaveChanges) + { + await Context.SaveChangesAsync(cancellationToken); + } + } + + /// + /// Creates a new role in a store as an asynchronous operation. + /// + /// The role to create in the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public async virtual Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + Context.Add(role); + await SaveChanges(cancellationToken); + return IdentityResult.Success; + } + + /// + /// Updates a role in a store as an asynchronous operation. + /// + /// The role to update in the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public async virtual Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + Context.Attach(role); + role.ConcurrencyStamp = Guid.NewGuid().ToString(); + Context.Update(role); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + return IdentityResult.Success; + } + + /// + /// Deletes a role from the store as an asynchronous operation. + /// + /// The role to delete from the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public async virtual Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + Context.Remove(role); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + return IdentityResult.Success; + } + + /// + /// Gets the ID for a role from the store as an asynchronous operation. + /// + /// The role whose ID should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the ID of the role. + public virtual Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + return Task.FromResult(ConvertIdToString(role.Id)); + } + + /// + /// Gets the name of a role from the store as an asynchronous operation. + /// + /// The role whose name should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public virtual Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + return Task.FromResult(role.Name); + } + + /// + /// Sets the name of a role in the store as an asynchronous operation. + /// + /// The role whose name should be set. + /// The name of the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + role.Name = roleName; + return Task.CompletedTask; + } + + /// + /// Converts the provided to a strongly typed key object. + /// + /// The id to convert. + /// An instance of representing the provided . + public virtual TKey ConvertIdFromString(string id) + { + if (id == null) + { + return default(TKey); + } + return (TKey)TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(id); + } + + /// + /// Converts the provided to its string representation. + /// + /// The id to convert. + /// An representation of the provided . + public virtual string ConvertIdToString(TKey id) + { + if (id.Equals(default(TKey))) + { + return null; + } + return id.ToString(); + } + + /// + /// Finds the role who has the specified ID as an asynchronous operation. + /// + /// The role ID to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public virtual Task FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + var roleId = ConvertIdFromString(id); + return Roles.FirstOrDefaultAsync(u => u.Id.Equals(roleId), cancellationToken); + } + + /// + /// Finds the role who has the specified normalized name as an asynchronous operation. + /// + /// The normalized role name to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public virtual Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return Roles.FirstOrDefaultAsync(r => r.NormalizedName == normalizedName, cancellationToken); + } + + /// + /// Get a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public virtual Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + return Task.FromResult(role.NormalizedName); + } + + /// + /// Set a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be set. + /// The normalized name to set + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + role.NormalizedName = normalizedName; + return Task.CompletedTask; + } + + /// + /// Throws if this class has been disposed. + /// + protected void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Dispose the stores + /// + public void Dispose() + { + _disposed = true; + } + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The role whose claims should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the claims granted to a role. + public async virtual Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + + return await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken); + } + + /// + /// Adds the given to the specified . + /// + /// The role to add the claim to. + /// The claim to add to the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + if (claim == null) + { + throw new ArgumentNullException(nameof(claim)); + } + + RoleClaims.Add(CreateRoleClaim(role, claim)); + return Task.FromResult(false); + } + + /// + /// Removes the given from the specified . + /// + /// The role to remove the claim from. + /// The claim to remove from the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async virtual Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + if (claim == null) + { + throw new ArgumentNullException(nameof(claim)); + } + var claims = await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id) && rc.ClaimValue == claim.Value && rc.ClaimType == claim.Type).ToListAsync(cancellationToken); + foreach (var c in claims) + { + RoleClaims.Remove(c); + } + } + + /// + /// A navigation property for the roles the store contains. + /// + public virtual IQueryable Roles + { + get { return Context.Set(); } + } + + private DbSet RoleClaims { get { return Context.Set(); } } + + /// + /// 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) + { + return new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; + } } } diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStoreV1.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStoreV1.cs deleted file mode 100644 index d3823cc8ea..0000000000 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStoreV1.cs +++ /dev/null @@ -1,254 +0,0 @@ -// 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 Microsoft.EntityFrameworkCore; - -namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore -{ - /// - /// Creates a new instance of a persistence store for roles. - /// - /// The type of the class representing a role. - /// 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 RoleStoreV1 : - RoleStoreBaseV1 - where TRole : IdentityRole - where TKey : IEquatable - where TContext : DbContext - where TUserRole : IdentityUserRole, new() - where TRoleClaim : IdentityRoleClaim, new() - { - /// - /// Constructs a new instance of . - /// - /// The . - /// The . - public RoleStoreV1(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. - /// - public TContext Context { get; private set; } - - /// - /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. - /// - /// - /// True if changes should be automatically persisted, otherwise false. - /// - public bool AutoSaveChanges { get; set; } = true; - - /// Saves the current store. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - private async Task SaveChanges(CancellationToken cancellationToken) - { - if (AutoSaveChanges) - { - await Context.SaveChangesAsync(cancellationToken); - } - } - - /// - /// Creates a new role in a store as an asynchronous operation. - /// - /// The role to create in the store. - /// The used to propagate notifications that the operation should be canceled. - /// A that represents the of the asynchronous query. - public override async Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - Context.Add(role); - await SaveChanges(cancellationToken); - return IdentityResult.Success; - } - - /// - /// Updates a role in a store as an asynchronous operation. - /// - /// The role to update in the store. - /// The used to propagate notifications that the operation should be canceled. - /// A that represents the of the asynchronous query. - public async override Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - Context.Attach(role); - role.ConcurrencyStamp = Guid.NewGuid().ToString(); - Context.Update(role); - try - { - await SaveChanges(cancellationToken); - } - catch (DbUpdateConcurrencyException) - { - return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); - } - return IdentityResult.Success; - } - - /// - /// Deletes a role from the store as an asynchronous operation. - /// - /// The role to delete from the store. - /// The used to propagate notifications that the operation should be canceled. - /// A that represents the of the asynchronous query. - public async override Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - Context.Remove(role); - try - { - await SaveChanges(cancellationToken); - } - catch (DbUpdateConcurrencyException) - { - return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); - } - return IdentityResult.Success; - } - - /// - /// 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 override Task FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - var roleId = ConvertIdFromString(id); - return Roles.FirstOrDefaultAsync(u => u.Id.Equals(roleId), cancellationToken); - } - - /// - /// Finds the role who has the specified normalized name as an asynchronous operation. - /// - /// The normalized role name to look for. - /// The used to propagate notifications that the operation should be canceled. - /// A that result of the look up. - public override Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - return Roles.FirstOrDefaultAsync(r => r.NormalizedName == normalizedName, cancellationToken); - } - - /// - /// 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 override Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - - return await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken); - } - - /// - /// Adds the given to the specified . - /// - /// The role to add the claim to. - /// The claim to add to the role. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public override Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - if (claim == null) - { - throw new ArgumentNullException(nameof(claim)); - } - - RoleClaims.Add(CreateRoleClaim(role, claim)); - return Task.FromResult(false); - } - - /// - /// Removes the given from the specified . - /// - /// The role to remove the claim from. - /// The claim to remove from the role. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public override async Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - if (claim == null) - { - throw new ArgumentNullException(nameof(claim)); - } - var claims = await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id) && rc.ClaimValue == claim.Value && rc.ClaimType == claim.Type).ToListAsync(cancellationToken); - foreach (var c in claims) - { - RoleClaims.Remove(c); - } - } - - /// - /// A navigation property for the roles the store contains. - /// - public override IQueryable Roles - { - get { return Context.Set(); } - } - - private DbSet RoleClaims { get { return Context.Set(); } } - - /// - /// Creates a entity representing a role claim. - /// - /// The associated role. - /// The associated claim. - /// The role claim entity. - public override TRoleClaim CreateRoleClaim(TRole role, Claim claim) - { - return new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; - } - } -} diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs index a0c982bee8..acb74d28c3 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs @@ -2,10 +2,86 @@ // 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.Globalization; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { + /// + /// Represents a new instance of a persistence store for users, using the default implementation + /// of with a string as a primary key. + /// + public class UserStore : UserStore> + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } + } + + /// + /// Creates a new instance of a persistence store for the specified user type. + /// + /// The type representing a user. + public class UserStore : UserStore + where TUser : IdentityUser, new() + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public UserStore(DbContext 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 representing a role. + /// The type of the data context class used to access the store. + public class UserStore : UserStore + where TUser : IdentityUser + where TRole : IdentityRole + where TContext : DbContext + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public UserStore(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 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 UserStore : UserStore, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim> + where TUser : IdentityUser + where TRole : IdentityRole + where TContext : DbContext + where TKey : IEquatable + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public UserStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } + } + /// /// Represents a new instance of a persistence store for the specified user and role types. /// @@ -19,7 +95,20 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// The type representing a user token. /// The type representing a role claim. public class UserStore : - UserStoreV2 + UserStoreBase, + IUserLoginStore, + IUserRoleStore, + IUserClaimStore, + IUserPasswordStore, + IUserSecurityStampStore, + IUserEmailStore, + IUserLockoutStore, + IUserPhoneNumberStore, + IQueryableUserStore, + IUserTwoFactorStore, + IUserAuthenticationTokenStore, + IUserAuthenticatorKeyStore, + IUserTwoFactorRecoveryCodeStore where TUser : IdentityUser where TRole : IdentityRole where TContext : DbContext @@ -35,7 +124,622 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// /// The context used to access the store. /// The used to describe store errors. - public UserStore(TContext context, IdentityErrorDescriber describer) : base(context, describer) - { } + public UserStore(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. + /// + public TContext Context { get; private set; } + + private DbSet UsersSet { get { return Context.Set(); } } + private DbSet Roles { get { return Context.Set(); } } + private DbSet UserClaims { get { return Context.Set(); } } + private DbSet UserRoles { get { return Context.Set(); } } + private DbSet UserLogins { get { return Context.Set(); } } + private DbSet UserTokens { get { return Context.Set(); } } + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + public bool AutoSaveChanges { get; set; } = true; + + /// Saves the current store. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + protected Task SaveChanges(CancellationToken cancellationToken) + { + return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : 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)); + } + Context.Add(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)); + } + + Context.Attach(user); + user.ConcurrencyStamp = Guid.NewGuid().ToString(); + Context.Update(user); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + 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)); + } + + Context.Remove(user); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + 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 UsersSet.FindAsync(new object[] { id }, cancellationToken); + } + + /// + /// 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 Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName, cancellationToken); + } + + /// + /// A navigation property for the users the store contains. + /// + public override IQueryable Users + { + get { return UsersSet; } + } + + /// + /// Return a role with the normalized name if it exists. + /// + /// The normalized role name. + /// The used to propagate notifications that the operation should be canceled. + /// The role if it exists. + protected override Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken) + { + return Roles.SingleOrDefaultAsync(r => r.NormalizedName == normalizedRoleName, cancellationToken); + } + + /// + /// Return a user role for the userId and roleId if it exists. + /// + /// The user's id. + /// The role's id. + /// The used to propagate notifications that the operation should be canceled. + /// The user role if it exists. + protected override Task FindUserRoleAsync(TKey userId, TKey roleId, CancellationToken cancellationToken) + { + return UserRoles.FindAsync(new object[] { userId, roleId }, cancellationToken); + } + + /// + /// 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 Users.SingleOrDefaultAsync(u => u.Id.Equals(userId), cancellationToken); + } + + /// + /// 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) + { + return UserLogins.SingleOrDefaultAsync(userLogin => userLogin.UserId.Equals(userId) && userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey, cancellationToken); + } + + /// + /// 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) + { + return UserLogins.SingleOrDefaultAsync(userLogin => userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey, cancellationToken); + } + + + /// + /// Adds the given to the specified . + /// + /// The user to add the role to. + /// The role to add. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async override Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + if (string.IsNullOrWhiteSpace(normalizedRoleName)) + { + throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, nameof(normalizedRoleName)); + } + var roleEntity = await FindRoleAsync(normalizedRoleName, cancellationToken); + if (roleEntity == null) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.RoleNotFound, normalizedRoleName)); + } + UserRoles.Add(CreateUserRole(user, roleEntity)); + } + + /// + /// Removes the given from the specified . + /// + /// The user to remove the role from. + /// The role to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async override Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + if (string.IsNullOrWhiteSpace(normalizedRoleName)) + { + throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, nameof(normalizedRoleName)); + } + var roleEntity = await FindRoleAsync(normalizedRoleName, cancellationToken); + if (roleEntity != null) + { + var userRole = await FindUserRoleAsync(user.Id, roleEntity.Id, cancellationToken); + if (userRole != null) + { + UserRoles.Remove(userRole); + } + } + } + + /// + /// Retrieves the roles the specified is a member of. + /// + /// The user whose roles should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the roles the user is a member of. + public override async Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + var userId = user.Id; + var query = from userRole in UserRoles + join role in Roles on userRole.RoleId equals role.Id + where userRole.UserId.Equals(userId) + select role.Name; + return await query.ToListAsync(); + } + + /// + /// Returns a flag indicating if the specified user is a member of the give . + /// + /// The user whose role membership should be checked. + /// The role to check membership of + /// The used to propagate notifications that the operation should be canceled. + /// A containing a flag indicating if the specified user is a member of the given group. If the + /// user is a member of the group the returned value with be true, otherwise it will be false. + public override async Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + if (string.IsNullOrWhiteSpace(normalizedRoleName)) + { + throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, nameof(normalizedRoleName)); + } + var role = await FindRoleAsync(normalizedRoleName, cancellationToken); + if (role != null) + { + var userRole = await FindUserRoleAsync(user.Id, role.Id, cancellationToken); + return userRole != null; + } + return false; + } + + /// + /// 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)) + { + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => c.ToClaim()).ToListAsync(cancellationToken); + } + + /// + /// 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)); + } + foreach (var claim in claims) + { + UserClaims.Add(CreateUserClaim(user, claim)); + } + 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)); + } + + var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); + foreach (var matchedClaim in matchedClaims) + { + matchedClaim.ClaimValue = newClaim.Value; + matchedClaim.ClaimType = newClaim.Type; + } + } + + /// + /// 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)); + } + foreach (var claim in claims) + { + var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); + foreach (var c in matchedClaims) + { + UserClaims.Remove(c); + } + } + } + + /// + /// 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)); + } + UserLogins.Add(CreateUserLogin(user, login)); + return Task.FromResult(false); + } + + /// + /// 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, + CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + var entry = await FindUserLoginAsync(user.Id, loginProvider, providerKey, cancellationToken); + if (entry != null) + { + UserLogins.Remove(entry); + } + } + + /// + /// 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)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + var userId = user.Id; + return await UserLogins.Where(l => l.UserId.Equals(userId)) + .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToListAsync(cancellationToken); + } + + /// + /// 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 Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); + } + + /// + /// 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 query = from userclaims in UserClaims + join user in Users on userclaims.UserId equals user.Id + where userclaims.ClaimValue == claim.Value + && userclaims.ClaimType == claim.Type + select user; + + return await query.ToListAsync(cancellationToken); + } + + /// + /// Retrieves all users in the specified role. + /// + /// The role 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 are in the specified role. + /// + public async override Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (string.IsNullOrEmpty(normalizedRoleName)) + { + throw new ArgumentNullException(nameof(normalizedRoleName)); + } + + var role = await FindRoleAsync(normalizedRoleName, cancellationToken); + + if (role != null) + { + var query = from userrole in UserRoles + join user in Users on userrole.UserId equals user.Id + where userrole.RoleId.Equals(role.Id) + select user; + + return await query.ToListAsync(cancellationToken); + } + return new List(); + } + + /// + /// 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) + => UserTokens.FindAsync(new object[] { user.Id, loginProvider, name }, cancellationToken); + + /// + /// Add a new user token. + /// + /// The token to be added. + /// + protected override Task AddUserTokenAsync(TUserToken token) + { + UserTokens.Add(token); + return Task.CompletedTask; + } + + + /// + /// Remove a new user token. + /// + /// The token to be removed. + /// + protected override Task RemoveUserTokenAsync(TUserToken token) + { + UserTokens.Remove(token); + return Task.CompletedTask; + } } } diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStoreCore.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStoreCore.cs deleted file mode 100644 index c0c2ca9288..0000000000 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStoreCore.cs +++ /dev/null @@ -1,649 +0,0 @@ -// 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.Globalization; -using System.Linq; -using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; - -namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore -{ - internal class UserStoreCore : - UserStoreBase - where TUser : IdentityUser - where TRole : IdentityRole - where TContext : DbContext - where TKey : IEquatable - where TUserClaim : IdentityUserClaim, new() - where TUserRole : IdentityUserRole, new() - where TUserLogin : IdentityUserLogin, new() - where TUserToken : IdentityUserToken, new() - where TRoleClaim : IdentityRoleClaim, new() - { - /// - /// Creates a new instance of the store. - /// - /// The context used to access the store. - /// The used to describe store errors. - public UserStoreCore(TContext context, IdentityErrorDescriber describer) : base(describer) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - Context = context; - } - - /// - /// Gets the database context for this store. - /// - public TContext Context { get; private set; } - - public DbSet UsersSet { get { return Context.Set(); } } - public DbSet Roles { get { return Context.Set(); } } - public DbSet UserClaims { get { return Context.Set(); } } - public DbSet UserRoles { get { return Context.Set(); } } - public DbSet UserLogins { get { return Context.Set(); } } - public DbSet UserTokens { get { return Context.Set(); } } - - /// - /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. - /// - /// - /// True if changes should be automatically persisted, otherwise false. - /// - public bool AutoSaveChanges { get; set; } = true; - - /// Saves the current store. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public Task SaveChanges(CancellationToken cancellationToken) - { - return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : 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)); - } - Context.Add(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)); - } - - Context.Attach(user); - user.ConcurrencyStamp = Guid.NewGuid().ToString(); - Context.Update(user); - try - { - await SaveChanges(cancellationToken); - } - catch (DbUpdateConcurrencyException) - { - 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)); - } - - Context.Remove(user); - try - { - await SaveChanges(cancellationToken); - } - catch (DbUpdateConcurrencyException) - { - 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 UsersSet.FindAsync(new object[] { id }, cancellationToken); - } - - /// - /// 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 Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName, cancellationToken); - } - - /// - /// A navigation property for the users the store contains. - /// - public override IQueryable Users - { - get { return UsersSet; } - } - - /// - /// Return a role with the normalized name if it exists. - /// - /// The normalized role name. - /// The used to propagate notifications that the operation should be canceled. - /// The role if it exists. - public override Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken) - { - return Roles.SingleOrDefaultAsync(r => r.NormalizedName == normalizedRoleName, cancellationToken); - } - - /// - /// Return a user role for the userId and roleId if it exists. - /// - /// The user's id. - /// The role's id. - /// The used to propagate notifications that the operation should be canceled. - /// The user role if it exists. - public override Task FindUserRoleAsync(TKey userId, TKey roleId, CancellationToken cancellationToken) - { - return UserRoles.FindAsync(new object[] { userId, roleId }, cancellationToken); - } - - /// - /// 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. - public override Task FindUserAsync(TKey userId, CancellationToken cancellationToken) - { - return Users.SingleOrDefaultAsync(u => u.Id.Equals(userId), cancellationToken); - } - - /// - /// 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. - public override Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken) - { - return UserLogins.SingleOrDefaultAsync(userLogin => userLogin.UserId.Equals(userId) && userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey, cancellationToken); - } - - /// - /// 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. - public override Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) - { - return UserLogins.SingleOrDefaultAsync(userLogin => userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey, cancellationToken); - } - - - /// - /// Adds the given to the specified . - /// - /// The user to add the role to. - /// The role to add. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public async override Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - if (string.IsNullOrWhiteSpace(normalizedRoleName)) - { - throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, nameof(normalizedRoleName)); - } - var roleEntity = await FindRoleAsync(normalizedRoleName, cancellationToken); - if (roleEntity == null) - { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.RoleNotFound, normalizedRoleName)); - } - UserRoles.Add(CreateUserRole(user, roleEntity)); - } - - /// - /// Removes the given from the specified . - /// - /// The user to remove the role from. - /// The role to remove. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public async override Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - if (string.IsNullOrWhiteSpace(normalizedRoleName)) - { - throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, nameof(normalizedRoleName)); - } - var roleEntity = await FindRoleAsync(normalizedRoleName, cancellationToken); - if (roleEntity != null) - { - var userRole = await FindUserRoleAsync(user.Id, roleEntity.Id, cancellationToken); - if (userRole != null) - { - UserRoles.Remove(userRole); - } - } - } - - /// - /// Retrieves the roles the specified is a member of. - /// - /// The user whose roles should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the roles the user is a member of. - public override async Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - var userId = user.Id; - var query = from userRole in UserRoles - join role in Roles on userRole.RoleId equals role.Id - where userRole.UserId.Equals(userId) - select role.Name; - return await query.ToListAsync(); - } - - /// - /// Returns a flag indicating if the specified user is a member of the give . - /// - /// The user whose role membership should be checked. - /// The role to check membership of - /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user is a member of the given group. If the - /// user is a member of the group the returned value with be true, otherwise it will be false. - public override async Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - if (string.IsNullOrWhiteSpace(normalizedRoleName)) - { - throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, nameof(normalizedRoleName)); - } - var role = await FindRoleAsync(normalizedRoleName, cancellationToken); - if (role != null) - { - var userRole = await FindUserRoleAsync(user.Id, role.Id, cancellationToken); - return userRole != null; - } - return false; - } - - /// - /// 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)) - { - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => c.ToClaim()).ToListAsync(cancellationToken); - } - - /// - /// 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)); - } - foreach (var claim in claims) - { - UserClaims.Add(CreateUserClaim(user, claim)); - } - 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)); - } - - var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); - foreach (var matchedClaim in matchedClaims) - { - matchedClaim.ClaimValue = newClaim.Value; - matchedClaim.ClaimType = newClaim.Type; - } - } - - /// - /// 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)); - } - foreach (var claim in claims) - { - var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); - foreach (var c in matchedClaims) - { - UserClaims.Remove(c); - } - } - } - - /// - /// 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)); - } - UserLogins.Add(CreateUserLogin(user, login)); - return Task.FromResult(false); - } - - /// - /// 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, - CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - var entry = await FindUserLoginAsync(user.Id, loginProvider, providerKey, cancellationToken); - if (entry != null) - { - UserLogins.Remove(entry); - } - } - - /// - /// 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)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - var userId = user.Id; - return await UserLogins.Where(l => l.UserId.Equals(userId)) - .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToListAsync(cancellationToken); - } - - /// - /// 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 Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); - } - - /// - /// 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 query = from userclaims in UserClaims - join user in Users on userclaims.UserId equals user.Id - where userclaims.ClaimValue == claim.Value - && userclaims.ClaimType == claim.Type - select user; - - return await query.ToListAsync(cancellationToken); - } - - /// - /// Retrieves all users in the specified role. - /// - /// The role 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 are in the specified role. - /// - public async override Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (string.IsNullOrEmpty(normalizedRoleName)) - { - throw new ArgumentNullException(nameof(normalizedRoleName)); - } - - var role = await FindRoleAsync(normalizedRoleName, cancellationToken); - - if (role != null) - { - var query = from userrole in UserRoles - join user in Users on userrole.UserId equals user.Id - where userrole.RoleId.Equals(role.Id) - select user; - - return await query.ToListAsync(cancellationToken); - } - return new List(); - } - - /// - /// 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. - public override Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) - => UserTokens.FindAsync(new object[] { user.Id, loginProvider, name }, cancellationToken); - - /// - /// Add a new user token. - /// - /// The token to be added. - /// - public override Task AddUserTokenAsync(TUserToken token) - { - UserTokens.Add(token); - return Task.CompletedTask; - } - - /// - /// Remove a new user token. - /// - /// The token to be removed. - /// - public override Task RemoveUserTokenAsync(TUserToken token) - { - UserTokens.Remove(token); - return Task.CompletedTask; - } - } -} diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStoreV1.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStoreV1.cs deleted file mode 100644 index c8a0871aca..0000000000 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStoreV1.cs +++ /dev/null @@ -1,373 +0,0 @@ -// 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 Microsoft.EntityFrameworkCore; - -namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore -{ - /// - /// Represents a new instance of a persistence store for the specified user and role types that supports - /// . - /// - /// The type representing a user. - /// The type 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 representing a claim. - /// The type representing a user role. - /// The type representing a user external login. - /// The type representing a user token. - /// The type representing a role claim. - public class UserStoreV1 : - UserStoreBaseV1 - where TUser : IdentityUser - where TRole : IdentityRole - where TContext : DbContext - where TKey : IEquatable - where TUserClaim : IdentityUserClaim, new() - where TUserRole : IdentityUserRole, new() - where TUserLogin : IdentityUserLogin, new() - where TUserToken : IdentityUserToken, new() - where TRoleClaim : IdentityRoleClaim, new() - { - /// - /// Creates a new instance of the store. - /// - /// The context used to access the store. - /// The used to describe store errors. - public UserStoreV1(TContext context, IdentityErrorDescriber describer = null) : base(describer ?? new IdentityErrorDescriber()) - { - _core = new UserStoreCore(context, describer); - } - - private readonly UserStoreCore _core; - - /// - /// Gets the database context for this store. - /// - public TContext Context => _core.Context; - - /// - /// 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 => _core.AutoSaveChanges; - set => _core.AutoSaveChanges = value; - } - - /// 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) => _core.SaveChanges(cancellationToken); - - /// - /// 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 override Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.CreateAsync(user, cancellationToken); - - /// - /// 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 override Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.UpdateAsync(user, cancellationToken); - - /// - /// 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 override Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.DeleteAsync(user, cancellationToken); - - /// - /// 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)) - => _core.FindByIdAsync(userId, cancellationToken); - - /// - /// 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)) - => _core.FindByNameAsync(normalizedUserName, cancellationToken); - - /// - /// A navigation property for the users the store contains. - /// - public override IQueryable Users => _core.UsersSet; - - /// - /// Return a role with the normalized name if it exists. - /// - /// The normalized role name. - /// The used to propagate notifications that the operation should be canceled. - /// The role if it exists. - public override Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken) - => _core.FindRoleAsync(normalizedRoleName, cancellationToken); - - /// - /// Return a user role for the userId and roleId if it exists. - /// - /// The user's id. - /// The role's id. - /// The used to propagate notifications that the operation should be canceled. - /// The user role if it exists. - public override Task FindUserRoleAsync(TKey userId, TKey roleId, CancellationToken cancellationToken) - => _core.FindUserRoleAsync(userId, roleId, cancellationToken); - - /// - /// 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. - public override Task FindUserAsync(TKey userId, CancellationToken cancellationToken) - => _core.FindUserAsync(userId, cancellationToken); - - /// - /// 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. - public override Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken) - => _core.FindUserLoginAsync(userId, loginProvider, providerKey, cancellationToken); - - /// - /// 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. - public override Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) - => _core.FindUserLoginAsync(loginProvider, providerKey, cancellationToken); - - /// - /// Adds the given to the specified . - /// - /// The user to add the role to. - /// The role to add. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public override Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - => _core.AddToRoleAsync(user, normalizedRoleName, cancellationToken); - - /// - /// Removes the given from the specified . - /// - /// The user to remove the role from. - /// The role to remove. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public override Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - => _core.RemoveFromRoleAsync(user, normalizedRoleName, cancellationToken); - - /// - /// Retrieves the roles the specified is a member of. - /// - /// The user whose roles should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the roles the user is a member of. - public override Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetRolesAsync(user, cancellationToken); - - /// - /// Returns a flag indicating if the specified user is a member of the give . - /// - /// The user whose role membership should be checked. - /// The role to check membership of - /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user is a member of the given group. If the - /// user is a member of the group the returned value with be true, otherwise it will be false. - public override Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - => _core.IsInRoleAsync(user, normalizedRoleName, cancellationToken); - - /// - /// 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 override Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetClaimsAsync(user, cancellationToken); - - /// - /// 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)) - => _core.AddClaimsAsync(user, claims, cancellationToken); - - /// - /// 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 override Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) - => _core.ReplaceClaimAsync(user, claim, newClaim, cancellationToken); - - /// - /// 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 override Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) - => _core.RemoveClaimsAsync(user, claims, cancellationToken); - - /// - /// 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)) - => _core.AddLoginAsync(user, login, cancellationToken); - - /// - /// 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 Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) - => _core.RemoveLoginAsync(user, loginProvider, providerKey, cancellationToken); - - /// - /// 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 override Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetLoginsAsync(user, cancellationToken); - - /// - /// 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 override Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) - => _core.FindByLoginAsync(loginProvider, providerKey, cancellationToken); - - /// - /// 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)) - => _core.FindByEmailAsync(normalizedEmail, cancellationToken); - - /// - /// 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 override Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetUsersForClaimAsync(claim, cancellationToken); - - /// - /// Retrieves all users in the specified role. - /// - /// The role 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 are in the specified role. - /// - public override Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetUsersInRoleAsync(normalizedRoleName, cancellationToken); - - /// - /// 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. - public override Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) - => _core.FindTokenAsync(user, loginProvider, name, cancellationToken); - - /// - /// Add a new user token. - /// - /// The token to be added. - /// - public override Task AddUserTokenAsync(TUserToken token) - => _core.AddUserTokenAsync(token); - - /// - /// Remove a new user token. - /// - /// The token to be removed. - /// - public override Task RemoveUserTokenAsync(TUserToken token) - => _core.RemoveUserTokenAsync(token); - - /// - /// Dispose the store - /// - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - _core.Dispose(); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStoreV2.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStoreV2.cs deleted file mode 100644 index 416e74a909..0000000000 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStoreV2.cs +++ /dev/null @@ -1,373 +0,0 @@ -// 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 Microsoft.EntityFrameworkCore; - -namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore -{ - /// - /// Represents a new instance of a persistence store for the specified user and role types that supports - /// . - /// - /// The type representing a user. - /// The type 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 representing a claim. - /// The type representing a user role. - /// The type representing a user external login. - /// The type representing a user token. - /// The type representing a role claim. - public class UserStoreV2 : - UserStoreBaseV2 - where TUser : IdentityUser - where TRole : IdentityRole - where TContext : DbContext - where TKey : IEquatable - where TUserClaim : IdentityUserClaim, new() - where TUserRole : IdentityUserRole, new() - where TUserLogin : IdentityUserLogin, new() - where TUserToken : IdentityUserToken, new() - where TRoleClaim : IdentityRoleClaim, new() - { - /// - /// Creates a new instance of the store. - /// - /// The context used to access the store. - /// The used to describe store errors. - public UserStoreV2(TContext context, IdentityErrorDescriber describer = null) : base(describer ?? new IdentityErrorDescriber()) - { - _core = new UserStoreCore(context, describer); - } - - private readonly UserStoreCore _core; - - /// - /// Gets the database context for this store. - /// - public TContext Context => _core.Context; - - /// - /// 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 => _core.AutoSaveChanges; - set => _core.AutoSaveChanges = value; - } - - /// 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) => _core.SaveChanges(cancellationToken); - - /// - /// 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 override Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.CreateAsync(user, cancellationToken); - - /// - /// 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 override Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.UpdateAsync(user, cancellationToken); - - /// - /// 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 override Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.DeleteAsync(user, cancellationToken); - - /// - /// 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)) - => _core.FindByIdAsync(userId, cancellationToken); - - /// - /// 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)) - => _core.FindByNameAsync(normalizedUserName, cancellationToken); - - /// - /// A navigation property for the users the store contains. - /// - public override IQueryable Users => _core.UsersSet; - - /// - /// Return a role with the normalized name if it exists. - /// - /// The normalized role name. - /// The used to propagate notifications that the operation should be canceled. - /// The role if it exists. - public override Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken) - => _core.FindRoleAsync(normalizedRoleName, cancellationToken); - - /// - /// Return a user role for the userId and roleId if it exists. - /// - /// The user's id. - /// The role's id. - /// The used to propagate notifications that the operation should be canceled. - /// The user role if it exists. - public override Task FindUserRoleAsync(TKey userId, TKey roleId, CancellationToken cancellationToken) - => _core.FindUserRoleAsync(userId, roleId, cancellationToken); - - /// - /// 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. - public override Task FindUserAsync(TKey userId, CancellationToken cancellationToken) - => _core.FindUserAsync(userId, cancellationToken); - - /// - /// 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. - public override Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken) - => _core.FindUserLoginAsync(userId, loginProvider, providerKey, cancellationToken); - - /// - /// 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. - public override Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) - => _core.FindUserLoginAsync(loginProvider, providerKey, cancellationToken); - - /// - /// Adds the given to the specified . - /// - /// The user to add the role to. - /// The role to add. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public override Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - => _core.AddToRoleAsync(user, normalizedRoleName, cancellationToken); - - /// - /// Removes the given from the specified . - /// - /// The user to remove the role from. - /// The role to remove. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public override Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - => _core.RemoveFromRoleAsync(user, normalizedRoleName, cancellationToken); - - /// - /// Retrieves the roles the specified is a member of. - /// - /// The user whose roles should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the roles the user is a member of. - public override Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetRolesAsync(user, cancellationToken); - - /// - /// Returns a flag indicating if the specified user is a member of the give . - /// - /// The user whose role membership should be checked. - /// The role to check membership of - /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user is a member of the given group. If the - /// user is a member of the group the returned value with be true, otherwise it will be false. - public override Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - => _core.IsInRoleAsync(user, normalizedRoleName, cancellationToken); - - /// - /// 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 override Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetClaimsAsync(user, cancellationToken); - - /// - /// 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)) - => _core.AddClaimsAsync(user, claims, cancellationToken); - - /// - /// 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 override Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) - => _core.ReplaceClaimAsync(user, claim, newClaim, cancellationToken); - - /// - /// 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 override Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) - => _core.RemoveClaimsAsync(user, claims, cancellationToken); - - /// - /// 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)) - => _core.AddLoginAsync(user, login, cancellationToken); - - /// - /// 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 Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) - => _core.RemoveLoginAsync(user, loginProvider, providerKey, cancellationToken); - - /// - /// 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 override Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetLoginsAsync(user, cancellationToken); - - /// - /// 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 override Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) - => _core.FindByLoginAsync(loginProvider, providerKey, cancellationToken); - - /// - /// 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)) - => _core.FindByEmailAsync(normalizedEmail, cancellationToken); - - /// - /// 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 override Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetUsersForClaimAsync(claim, cancellationToken); - - /// - /// Retrieves all users in the specified role. - /// - /// The role 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 are in the specified role. - /// - public override Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) - => _core.GetUsersInRoleAsync(normalizedRoleName, cancellationToken); - - /// - /// 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. - public override Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) - => _core.FindTokenAsync(user, loginProvider, name, cancellationToken); - - /// - /// Add a new user token. - /// - /// The token to be added. - /// - public override Task AddUserTokenAsync(TUserToken token) - => _core.AddUserTokenAsync(token); - - /// - /// Remove a new user token. - /// - /// The token to be removed. - /// - public override Task RemoveUserTokenAsync(TUserToken token) - => _core.RemoveUserTokenAsync(token); - - /// - /// Dispose the store - /// - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - _core.Dispose(); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Identity.Specification.Tests/IdentitySpecificationTestBase.cs b/src/Microsoft.AspNetCore.Identity.Specification.Tests/IdentitySpecificationTestBase.cs index 8e8785766d..220dd11818 100644 --- a/src/Microsoft.AspNetCore.Identity.Specification.Tests/IdentitySpecificationTestBase.cs +++ b/src/Microsoft.AspNetCore.Identity.Specification.Tests/IdentitySpecificationTestBase.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Identity.Test /// /// /// - protected virtual void SetupIdentityServices(IServiceCollection services, object context) + protected virtual void SetupIdentityServices(IServiceCollection services, object context = null) { services.AddSingleton(new ConfigurationBuilder().Build()); services.AddSingleton(); @@ -192,31 +192,6 @@ namespace Microsoft.AspNetCore.Identity.Test /// The query to use. protected abstract Expression> RoleNameStartsWithPredicate(string roleName); - /// - /// Test. - /// - /// Task - [Fact] - public async Task CreateUserWillSetCreateDateOnlyIfSupported() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var userId = await manager.GetUserIdAsync(user); - if (manager.SupportsUserActivity) - { - Assert.NotNull(await manager.GetCreateDateAsync(user)); - } - else - { - await Assert.ThrowsAsync(() => manager.GetCreateDateAsync(user)); - } - } - /// /// Test. /// @@ -515,17 +490,9 @@ namespace Microsoft.AspNetCore.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); manager.PasswordValidators.Clear(); manager.PasswordValidators.Add(new AlwaysBadValidator()); - if (manager.SupportsUserActivity) - { - Assert.Null(await manager.GetLastPasswordChangeDateAsync(user)); - } IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), AlwaysBadValidator.ErrorMessage); IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); - if (manager.SupportsUserActivity) - { - Assert.Null(await manager.GetLastPasswordChangeDateAsync(user)); - } } /// @@ -611,12 +578,6 @@ namespace Microsoft.AspNetCore.Identity.Test var logins = await manager.GetLoginsAsync(user); Assert.NotNull(logins); Assert.Equal(0, logins.Count()); - if (manager.SupportsUserActivity) - { - Assert.NotNull(await manager.GetCreateDateAsync(user)); - Assert.Null(await manager.GetLastPasswordChangeDateAsync(user)); - Assert.Null(await manager.GetLastSignInDateAsync(user)); - } } /// @@ -643,12 +604,6 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.Equal(provider, logins.First().LoginProvider); Assert.Equal(providerKey, logins.First().ProviderKey); Assert.Equal(display, logins.First().ProviderDisplayName); - if (manager.SupportsUserActivity) - { - Assert.NotNull(await manager.GetCreateDateAsync(user)); - Assert.Null(await manager.GetLastPasswordChangeDateAsync(user)); - Assert.Null(await manager.GetLastSignInDateAsync(user)); - } } /// @@ -669,27 +624,13 @@ namespace Microsoft.AspNetCore.Identity.Test var login = new UserLoginInfo("Provider", userId, "display"); IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); Assert.False(await manager.HasPasswordAsync(user)); - if (manager.SupportsUserActivity) - { - Assert.Null(await manager.GetLastPasswordChangeDateAsync(user)); - } IdentityResultAssert.IsSuccess(await manager.AddPasswordAsync(user, "password")); - if (manager.SupportsUserActivity) - { - Assert.NotNull(await manager.GetLastPasswordChangeDateAsync(user)); - } Assert.True(await manager.HasPasswordAsync(user)); var logins = await manager.GetLoginsAsync(user); Assert.NotNull(logins); Assert.Equal(1, logins.Count()); Assert.Equal(user, await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey)); Assert.True(await manager.CheckPasswordAsync(user, "password")); - - if (manager.SupportsUserActivity) - { - Assert.NotNull(await manager.GetCreateDateAsync(user)); - Assert.Null(await manager.GetLastSignInDateAsync(user)); - } } /// @@ -787,57 +728,12 @@ namespace Microsoft.AspNetCore.Identity.Test const string password = "password"; const string newPassword = "newpassword"; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); - DateTimeOffset? createChange = null; - if (manager.SupportsUserActivity) - { - createChange = await manager.GetLastPasswordChangeDateAsync(user); - Assert.NotNull(createChange); - Assert.Null(await manager.GetLastSignInDateAsync(user)); - } - else - { - await Assert.ThrowsAsync(() => manager.GetLastPasswordChangeDateAsync(user)); - await Assert.ThrowsAsync(() => manager.GetLastSignInDateAsync(user)); - } var stamp = await manager.GetSecurityStampAsync(user); Assert.NotNull(stamp); IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, password, newPassword)); Assert.False(await manager.CheckPasswordAsync(user, password)); Assert.True(await manager.CheckPasswordAsync(user, newPassword)); Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - - if (manager.SupportsUserActivity) - { - var changeDate = await manager.GetLastPasswordChangeDateAsync(user); - Assert.NotNull(changeDate); - Assert.True(createChange < changeDate); - } - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanUpdateLastSignInTime() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - if (manager.SupportsUserActivity) - { - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.Null(await manager.GetLastSignInDateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.UpdateLastSignInDateAsync(user)); - Assert.NotNull(await manager.GetLastSignInDateAsync(user)); - } - else - { - await Assert.ThrowsAsync(() => manager.UpdateLastSignInDateAsync(user)); - } } /// @@ -988,20 +884,9 @@ namespace Microsoft.AspNetCore.Identity.Test var manager = CreateManager(); var user = CreateTestUser(); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); - DateTimeOffset? createChange = null; - if (manager.SupportsUserActivity) - { - createChange = await manager.GetLastPasswordChangeDateAsync(user); - Assert.NotNull(createChange); - } var result = await manager.ChangePasswordAsync(user, "bogus", "newpassword"); IdentityResultAssert.IsFailure(result, "Incorrect password."); IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Change password failed for user {await manager.GetUserIdAsync(user)}."); - - if (manager.SupportsUserActivity) - { - Assert.Equal(createChange, await manager.GetLastPasswordChangeDateAsync(user)); - } } /// diff --git a/src/Microsoft.AspNetCore.Identity/SignInManager.cs b/src/Microsoft.AspNetCore.Identity/SignInManager.cs index ace19c2165..47d30b5da9 100644 --- a/src/Microsoft.AspNetCore.Identity/SignInManager.cs +++ b/src/Microsoft.AspNetCore.Identity/SignInManager.cs @@ -162,7 +162,7 @@ namespace Microsoft.AspNetCore.Identity { var auth = await Context.AuthenticateAsync(IdentityConstants.ApplicationScheme); var authenticationMethod = auth?.Principal?.FindFirstValue(ClaimTypes.AuthenticationMethod); - await SignInAsync(user, auth?.Properties, authenticationMethod, updateLastSignIn: false); + await SignInAsync(user, auth?.Properties, authenticationMethod); } /// @@ -171,9 +171,8 @@ namespace Microsoft.AspNetCore.Identity /// The user to sign-in. /// Flag indicating whether the sign-in cookie should persist after the browser is closed. /// Name of the method used to authenticate the user. - /// If true, will update the last sign for the user if supported. /// The task object representing the asynchronous operation. - public virtual Task SignInAsync(TUser user, bool isPersistent, string authenticationMethod = null, bool updateLastSignIn = true) + public virtual Task SignInAsync(TUser user, bool isPersistent, string authenticationMethod = null) { return SignInAsync(user, new AuthenticationProperties { IsPersistent = isPersistent }, authenticationMethod); } @@ -184,9 +183,8 @@ namespace Microsoft.AspNetCore.Identity /// The user to sign-in. /// Properties applied to the login and authentication cookie. /// Name of the method used to authenticate the user. - /// If true, will update the last sign for the user if supported. /// The task object representing the asynchronous operation. - public virtual async Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod = null, bool updateLastSignIn = true) + public virtual async Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod = null) { var userPrincipal = await CreateUserPrincipalAsync(user); // Review: should we guard against CreateUserPrincipal returning null? @@ -197,10 +195,6 @@ namespace Microsoft.AspNetCore.Identity await Context.SignInAsync(IdentityConstants.ApplicationScheme, userPrincipal, authenticationProperties ?? new AuthenticationProperties()); - if (updateLastSignIn && UserManager.SupportsUserActivity) - { - await UserManager.UpdateLastSignInDateAsync(user); - } } /// diff --git a/src/Microsoft.Extensions.Identity.Core/IUserActivityStore.cs b/src/Microsoft.Extensions.Identity.Core/IUserActivityStore.cs deleted file mode 100644 index c7d924b9ee..0000000000 --- a/src/Microsoft.Extensions.Identity.Core/IUserActivityStore.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Identity -{ - /// - /// Provides an abstraction for a store containing users' password expiration data. - /// - /// The type encapsulating a user. - public interface IUserActivityStore : IUserStore where TUser : class - { - /// - /// Sets the last password change date for the specified . - /// - /// The user. - /// The last password change date. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task SetLastPasswordChangeDateAsync(TUser user, DateTimeOffset? changeDate, CancellationToken cancellationToken); - - /// - /// Gets the last password change date for the specified . - /// - /// The user. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, returning the password hash for the specified . - Task GetLastPasswordChangeDateAsync(TUser user, CancellationToken cancellationToken); - - /// - /// Sets the creation date for the specified . - /// - /// The user. - /// The date the user was created. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task SetCreateDateAsync(TUser user, DateTimeOffset? creationDate, CancellationToken cancellationToken); - - /// - /// Gets the creation date for the specified . - /// - /// The user. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, returning the password hash for the specified . - Task GetCreateDateAsync(TUser user, CancellationToken cancellationToken); - - /// - /// Sets the last signin date for the specified . - /// - /// The user. - /// The date the user last signed in. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task SetLastSignInDateAsync(TUser user, DateTimeOffset? lastSignIn, CancellationToken cancellationToken); - - /// - /// Gets the last signin date for the specified . - /// - /// The user. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, returning the password hash for the specified . - Task GetLastSignInDateAsync(TUser user, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/src/Microsoft.Extensions.Identity.Core/Properties/Resources.Designer.cs b/src/Microsoft.Extensions.Identity.Core/Properties/Resources.Designer.cs index e29a08dd06..d39c98abad 100644 --- a/src/Microsoft.Extensions.Identity.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.Extensions.Identity.Core/Properties/Resources.Designer.cs @@ -762,22 +762,6 @@ namespace Microsoft.AspNetCore.Identity return string.Format(CultureInfo.CurrentCulture, GetString("PasswordRequiresUniqueChars"), p0); } - /// - /// Store does not implement IUserActivityStore<User>. - /// - internal static string StoreNotIUserActivityStore - { - get { return GetString("StoreNotIUserActivityStore"); } - } - - /// - /// Store does not implement IUserActivityStore<User>. - /// - internal static string FormatStoreNotIUserActivityStore() - { - return GetString("StoreNotIUserActivityStore"); - } - private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.Extensions.Identity.Core/Resources.resx b/src/Microsoft.Extensions.Identity.Core/Resources.resx index 4df00dfadf..39a1443a4b 100644 --- a/src/Microsoft.Extensions.Identity.Core/Resources.resx +++ b/src/Microsoft.Extensions.Identity.Core/Resources.resx @@ -305,8 +305,4 @@ Passwords must use at least {0} different characters. Error message for passwords that are based on similar characters - - Store does not implement IUserActivityStore<User>. - Error when the store does not implement this interface - \ No newline at end of file diff --git a/src/Microsoft.Extensions.Identity.Core/SignInOptions.cs b/src/Microsoft.Extensions.Identity.Core/SignInOptions.cs index 5f4fae7a71..ac8ddc0571 100644 --- a/src/Microsoft.Extensions.Identity.Core/SignInOptions.cs +++ b/src/Microsoft.Extensions.Identity.Core/SignInOptions.cs @@ -1,8 +1,6 @@ // 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; - namespace Microsoft.AspNetCore.Identity { /// diff --git a/src/Microsoft.Extensions.Identity.Core/SignInResult.cs b/src/Microsoft.Extensions.Identity.Core/SignInResult.cs index 9b2bfcdc64..faae170c3b 100644 --- a/src/Microsoft.Extensions.Identity.Core/SignInResult.cs +++ b/src/Microsoft.Extensions.Identity.Core/SignInResult.cs @@ -38,11 +38,6 @@ namespace Microsoft.AspNetCore.Identity /// True if the user attempting to sign-in requires two factor authentication, otherwise false. public bool RequiresTwoFactor { get; protected set; } - /// - /// Returns a flag indication whether the user attempting to sign-in requires a password change. - /// - public bool RequiresPasswordChange { get; protected set; } - /// /// Returns a that represents a successful sign-in. /// diff --git a/src/Microsoft.Extensions.Identity.Core/UserManager.cs b/src/Microsoft.Extensions.Identity.Core/UserManager.cs index 6f283dab33..6ab877124b 100644 --- a/src/Microsoft.Extensions.Identity.Core/UserManager.cs +++ b/src/Microsoft.Extensions.Identity.Core/UserManager.cs @@ -339,21 +339,6 @@ namespace Microsoft.AspNetCore.Identity } } - /// - /// Gets a flag indicating whether the backing user store supports tracking user activity like creation date. - /// - /// - /// true if the backing user store supports user activity tracking, otherwise false. - /// - public virtual bool SupportsUserActivity - { - get - { - ThrowIfDisposed(); - return Store is IUserActivityStore; - } - } - /// /// Gets a flag indicating whether the backing user store supports returning /// collections of information. @@ -481,11 +466,6 @@ namespace Microsoft.AspNetCore.Identity await UpdateNormalizedUserNameAsync(user); await UpdateNormalizedEmailAsync(user); - if (SupportsUserActivity) - { - await GetUserActivityStore().SetCreateDateAsync(user, DateTimeOffset.UtcNow, CancellationToken); - } - return await Store.CreateAsync(user, CancellationToken); } @@ -2245,79 +2225,6 @@ namespace Microsoft.AspNetCore.Identity return IdentityResult.Failed(ErrorDescriber.RecoveryCodeRedemptionFailed()); } - /// - /// Gets a date representing when a user was created. - /// - /// The user. - /// - /// A that represents the lookup. - /// - public virtual Task GetCreateDateAsync(TUser user) - { - ThrowIfDisposed(); - var store = GetUserActivityStore(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return store.GetCreateDateAsync(user, CancellationToken); - } - - /// - /// Gets a date representing when the user last changed their password. - /// - /// The user. - /// - /// A that represents the lookup. - /// - public virtual Task GetLastPasswordChangeDateAsync(TUser user) - { - ThrowIfDisposed(); - var store = GetUserActivityStore(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return store.GetLastPasswordChangeDateAsync(user, CancellationToken); - } - - /// - /// Gets a date representing when the user last signed in. - /// - /// The user. - /// - /// A that represents the lookup. - /// - public virtual Task GetLastSignInDateAsync(TUser user) - { - ThrowIfDisposed(); - var store = GetUserActivityStore(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return store.GetLastSignInDateAsync(user, CancellationToken); - } - - /// - /// Update the last sign in date to DateTimeOffest.UtcNow. - /// - /// The user. - /// - /// A that represents the lookup. - /// - public virtual async Task UpdateLastSignInDateAsync(TUser user) - { - ThrowIfDisposed(); - var store = GetUserActivityStore(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - await store.SetLastSignInDateAsync(user, DateTimeOffset.UtcNow, CancellationToken); - return await UpdateAsync(user); - } - /// /// Releases the unmanaged resources used by the role manager and optionally releases the managed resources. /// @@ -2341,17 +2248,6 @@ namespace Microsoft.AspNetCore.Identity return cast; } - internal IUserActivityStore GetUserActivityStore() - { - var cast = Store as IUserActivityStore; - if (cast == null) - { - throw new NotSupportedException(Resources.StoreNotIUserActivityStore); - } - return cast; - } - - internal IUserLockoutStore GetUserLockoutStore() { var cast = Store as IUserLockoutStore; @@ -2414,10 +2310,6 @@ namespace Microsoft.AspNetCore.Identity } var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null; await passwordStore.SetPasswordHashAsync(user, hash, CancellationToken); - if (SupportsUserActivity) - { - await GetUserActivityStore().SetLastPasswordChangeDateAsync(user, DateTimeOffset.UtcNow, CancellationToken); - } await UpdateSecurityStampInternal(user); return IdentityResult.Success; } diff --git a/src/Microsoft.Extensions.Identity.Stores/IdentityStoreOptions.cs b/src/Microsoft.Extensions.Identity.Stores/IdentityStoreOptions.cs deleted file mode 100644 index 36bb891aef..0000000000 --- a/src/Microsoft.Extensions.Identity.Stores/IdentityStoreOptions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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. - -namespace Microsoft.AspNetCore.Identity -{ - /// - /// Used for store schema versioning. - /// - public class IdentityStoreOptions - { - /// - /// Matches version 1.x.x - /// - public const string Version1_0 = "v1.0"; - - /// - /// Matches version 2.0.0 - /// - public const string Version2_0 = "v2.0"; - - /// - /// Used to represent the most current version. - /// - public const string Version_Latest = "latest"; - - /// - /// Used to determine what features/schema are supported in the store. - /// - public string Version { get; set; } - } -} \ No newline at end of file diff --git a/src/Microsoft.Extensions.Identity.Stores/IdentityUser.cs b/src/Microsoft.Extensions.Identity.Stores/IdentityUser.cs index 3769ffcb07..efc8a9b713 100644 --- a/src/Microsoft.Extensions.Identity.Stores/IdentityUser.cs +++ b/src/Microsoft.Extensions.Identity.Stores/IdentityUser.cs @@ -149,21 +149,6 @@ namespace Microsoft.AspNetCore.Identity /// public virtual int AccessFailedCount { get; set; } - /// - /// Gets or sets the date and time, in UTC, when the user was created. - /// - public virtual DateTimeOffset? CreateDate { get; set; } - - /// - /// Gets or sets the date and time, in UTC, when the user last signed in. - /// - public virtual DateTimeOffset? LastSignInDate { get; set; } - - /// - /// Gets or sets the date and time, in UTC, when the user last changed his password. - /// - public virtual DateTimeOffset? LastPasswordChangeDate { get; set; } - /// /// Returns the username for this user. /// diff --git a/src/Microsoft.Extensions.Identity.Stores/RoleStoreBase.cs b/src/Microsoft.Extensions.Identity.Stores/RoleStoreBase.cs index 2eeba89c56..b65b5f9937 100644 --- a/src/Microsoft.Extensions.Identity.Stores/RoleStoreBase.cs +++ b/src/Microsoft.Extensions.Identity.Stores/RoleStoreBase.cs @@ -2,18 +2,26 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Identity { /// - /// Creates a new instance of a persistence store for roles that always will match the latest /// Creates a new instance of a persistence store for roles that matches . + /// Creates a new instance of a persistence store for roles. /// /// The type of the class representing a role. /// 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 abstract class RoleStoreBase : - RoleStoreBaseV1 + IQueryableRoleStore, + IRoleClaimStore where TRole : IdentityRole where TKey : IEquatable where TUserRole : IdentityUserRole, new() @@ -23,6 +31,242 @@ namespace Microsoft.AspNetCore.Identity /// Constructs a new instance of . /// /// The . - public RoleStoreBase(IdentityErrorDescriber describer) : base(describer) { } + public RoleStoreBase(IdentityErrorDescriber describer) + { + if (describer == null) + { + throw new ArgumentNullException(nameof(describer)); + } + + ErrorDescriber = describer; + } + + private bool _disposed; + + /// + /// Gets or sets the for any error that occurred with the current operation. + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// 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 abstract Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Gets the ID for a role from the store as an asynchronous operation. + /// + /// The role whose ID should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the ID of the role. + public virtual Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + return Task.FromResult(ConvertIdToString(role.Id)); + } + + /// + /// Gets the name of a role from the store as an asynchronous operation. + /// + /// The role whose name should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public virtual Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + return Task.FromResult(role.Name); + } + + /// + /// Sets the name of a role in the store as an asynchronous operation. + /// + /// The role whose name should be set. + /// The name of the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + role.Name = roleName; + return Task.CompletedTask; + } + + /// + /// Converts the provided to a strongly typed key object. + /// + /// The id to convert. + /// An instance of representing the provided . + public virtual TKey ConvertIdFromString(string id) + { + if (id == null) + { + return default(TKey); + } + return (TKey)TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(id); + } + + /// + /// Converts the provided to its string representation. + /// + /// The id to convert. + /// An representation of the provided . + public virtual string ConvertIdToString(TKey id) + { + if (id.Equals(default(TKey))) + { + return null; + } + return id.ToString(); + } + + /// + /// Finds the role who has the specified ID as an asynchronous operation. + /// + /// The role ID to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public abstract Task FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Finds the role who has the specified normalized name as an asynchronous operation. + /// + /// The normalized role name to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public abstract Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Get a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public virtual Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + return Task.FromResult(role.NormalizedName); + } + + /// + /// Set a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be set. + /// The normalized name to set + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException(nameof(role)); + } + role.NormalizedName = normalizedName; + return Task.CompletedTask; + } + + /// + /// Throws if this class has been disposed. + /// + protected void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Dispose the stores + /// + public void Dispose() + { + _disposed = true; + } + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The role whose claims should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the claims granted to a role. + public abstract Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Adds the given to the specified . + /// + /// The role to add the claim to. + /// The claim to add to the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public abstract Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// A navigation property for the roles the store contains. + /// + public abstract IQueryable Roles + { + get; + } + + /// + /// 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) + { + return new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; + } } } diff --git a/src/Microsoft.Extensions.Identity.Stores/RoleStoreBaseV1.cs b/src/Microsoft.Extensions.Identity.Stores/RoleStoreBaseV1.cs deleted file mode 100644 index b9e1150b4f..0000000000 --- a/src/Microsoft.Extensions.Identity.Stores/RoleStoreBaseV1.cs +++ /dev/null @@ -1,271 +0,0 @@ -// 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; - -namespace Microsoft.AspNetCore.Identity -{ - /// - /// Creates a new instance of a persistence store for roles that matches . - /// - /// The type of the class representing a role. - /// 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 abstract class RoleStoreBaseV1 : - IQueryableRoleStore, - IRoleClaimStore - where TRole : IdentityRole - where TKey : IEquatable - where TUserRole : IdentityUserRole, new() - where TRoleClaim : IdentityRoleClaim, new() - { - /// - /// Constructs a new instance of . - /// - /// The . - public RoleStoreBaseV1(IdentityErrorDescriber describer) - { - if (describer == null) - { - throw new ArgumentNullException(nameof(describer)); - } - - ErrorDescriber = describer; - } - - private bool _disposed; - - /// - /// Gets or sets the for any error that occurred with the current operation. - /// - public IdentityErrorDescriber ErrorDescriber { get; set; } - - /// - /// 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 abstract Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Gets the ID for a role from the store as an asynchronous operation. - /// - /// The role whose ID should be returned. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the ID of the role. - public virtual Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - return Task.FromResult(ConvertIdToString(role.Id)); - } - - /// - /// Gets the name of a role from the store as an asynchronous operation. - /// - /// The role whose name should be returned. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the name of the role. - public virtual Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - return Task.FromResult(role.Name); - } - - /// - /// Sets the name of a role in the store as an asynchronous operation. - /// - /// The role whose name should be set. - /// The name of the role. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public virtual Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - role.Name = roleName; - return Task.CompletedTask; - } - - /// - /// Converts the provided to a strongly typed key object. - /// - /// The id to convert. - /// An instance of representing the provided . - public virtual TKey ConvertIdFromString(string id) - { - if (id == null) - { - return default(TKey); - } - return (TKey)TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(id); - } - - /// - /// Converts the provided to its string representation. - /// - /// The id to convert. - /// An representation of the provided . - public virtual string ConvertIdToString(TKey id) - { - if (id.Equals(default(TKey))) - { - return null; - } - return id.ToString(); - } - - /// - /// Finds the role who has the specified ID as an asynchronous operation. - /// - /// The role ID to look for. - /// The used to propagate notifications that the operation should be canceled. - /// A that result of the look up. - public abstract Task FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Finds the role who has the specified normalized name as an asynchronous operation. - /// - /// The normalized role name to look for. - /// The used to propagate notifications that the operation should be canceled. - /// A that result of the look up. - public abstract Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Get a role's normalized name as an asynchronous operation. - /// - /// The role whose normalized name should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the name of the role. - public virtual Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - return Task.FromResult(role.NormalizedName); - } - - /// - /// Set a role's normalized name as an asynchronous operation. - /// - /// The role whose normalized name should be set. - /// The normalized name to set - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public virtual Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (role == null) - { - throw new ArgumentNullException(nameof(role)); - } - role.NormalizedName = normalizedName; - return Task.CompletedTask; - } - - /// - /// Throws if this class has been disposed. - /// - protected void ThrowIfDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - - /// - /// Dispose the stores - /// - public void Dispose() - { - _disposed = true; - } - - /// - /// Get the claims associated with the specified as an asynchronous operation. - /// - /// The role whose claims should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the claims granted to a role. - public abstract Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Adds the given to the specified . - /// - /// The role to add the claim to. - /// The claim to add to the role. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public abstract Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// A navigation property for the roles the store contains. - /// - public abstract IQueryable Roles - { - get; - } - - /// - /// Creates a entity representing a role claim. - /// - /// The associated role. - /// The associated claim. - /// The role claim entity. - public virtual TRoleClaim CreateRoleClaim(TRole role, Claim claim) - { - return new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; - } - } -} diff --git a/src/Microsoft.Extensions.Identity.Stores/UserStoreBase.cs b/src/Microsoft.Extensions.Identity.Stores/UserStoreBase.cs index 4be6c2f05a..1a72f5bf87 100644 --- a/src/Microsoft.Extensions.Identity.Stores/UserStoreBase.cs +++ b/src/Microsoft.Extensions.Identity.Stores/UserStoreBase.cs @@ -2,12 +2,18 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Identity { /// /// Represents a new instance of a persistence store for the specified user and role types. - /// This type will always support the most current schema. /// /// The type representing a user. /// The type representing a role. @@ -18,7 +24,19 @@ namespace Microsoft.AspNetCore.Identity /// The type representing a user token. /// The type representing a role claim. public abstract class UserStoreBase : - UserStoreBaseV2 + IUserLoginStore, + IUserRoleStore, + IUserClaimStore, + IUserPasswordStore, + IUserSecurityStampStore, + IUserEmailStore, + IUserLockoutStore, + IUserPhoneNumberStore, + IQueryableUserStore, + IUserTwoFactorStore, + IUserAuthenticationTokenStore, + IUserAuthenticatorKeyStore, + IUserTwoFactorRecoveryCodeStore where TUser : IdentityUser where TRole : IdentityRole where TKey : IEquatable @@ -32,7 +50,1093 @@ namespace Microsoft.AspNetCore.Identity /// Creates a new instance. /// /// The used to describe store errors. - public UserStoreBase(IdentityErrorDescriber describer) : base(describer) { } + public UserStoreBase(IdentityErrorDescriber describer) + { + if (describer == null) + { + throw new ArgumentNullException(nameof(describer)); + } + ErrorDescriber = describer; + } + + private bool _disposed; + + /// + /// Gets or sets the for any error that occurred with the current operation. + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// Called to create a new instance of a . + /// + /// The associated user. + /// The associated role. + /// + protected virtual TUserRole CreateUserRole(TUser user, TRole role) + { + return new TUserRole() + { + UserId = user.Id, + RoleId = role.Id + }; + } + + /// + /// Called to create a new instance of a . + /// + /// The associated user. + /// The associated claim. + /// + protected virtual TUserClaim CreateUserClaim(TUser user, Claim claim) + { + var userClaim = new TUserClaim { UserId = user.Id }; + userClaim.InitializeFromClaim(claim); + return userClaim; + } + + /// + /// Called to create a new instance of a . + /// + /// The associated user. + /// The sasociated login. + /// + protected virtual TUserLogin CreateUserLogin(TUser user, UserLoginInfo login) + { + return new TUserLogin + { + UserId = user.Id, + ProviderKey = login.ProviderKey, + LoginProvider = login.LoginProvider, + ProviderDisplayName = login.ProviderDisplayName + }; + } + + /// + /// Called to create a new instance of a . + /// + /// The associated user. + /// The associated login provider. + /// The name of the user token. + /// The value of the user token. + /// + protected virtual TUserToken CreateUserToken(TUser user, string loginProvider, string name, string value) + { + return new TUserToken + { + UserId = user.Id, + LoginProvider = loginProvider, + Name = name, + Value = value + }; + } + + /// + /// Gets the user identifier for the specified . + /// + /// The user whose identifier should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the identifier for the specified . + public virtual Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(ConvertIdToString(user.Id)); + } + + /// + /// Gets the user name for the specified . + /// + /// The user whose name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the name for the specified . + public virtual Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.UserName); + } + + /// + /// 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 virtual Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.UserName = userName; + return Task.CompletedTask; + } + + /// + /// Gets the normalized user name for the specified . + /// + /// The user whose normalized name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the normalized user name for the specified . + public virtual Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.NormalizedUserName); + } + + /// + /// 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 virtual Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.NormalizedUserName = normalizedName; + 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 abstract Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Converts the provided to a strongly typed key object. + /// + /// The id to convert. + /// An instance of representing the provided . + public virtual TKey ConvertIdFromString(string id) + { + if (id == null) + { + return default(TKey); + } + return (TKey)TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(id); + } + + /// + /// Converts the provided to its string representation. + /// + /// The id to convert. + /// An representation of the provided . + public virtual string ConvertIdToString(TKey id) + { + if (object.Equals(id, default(TKey))) + { + return null; + } + return id.ToString(); + } + + /// + /// 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 abstract Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// A navigation property for the users the store contains. + /// + public abstract IQueryable Users + { + get; + } + + /// + /// 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 virtual Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.PasswordHash = passwordHash; + return Task.CompletedTask; + } + + /// + /// Gets the password hash for a user. + /// + /// The user to retrieve the password hash for. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the password hash for the user. + public virtual Task GetPasswordHashAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.PasswordHash); + } + + /// + /// Returns a flag indicating if the specified user has a password. + /// + /// The user to retrieve the password hash for. + /// The used to propagate notifications that the operation should be canceled. + /// A containing a flag indicating if the specified user has a password. If the + /// user has a password the returned value with be true, otherwise it will be false. + public virtual Task HasPasswordAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(user.PasswordHash != null); + } + + /// + /// Return a role with the normalized name if it exists. + /// + /// The normalized role name. + /// The used to propagate notifications that the operation should be canceled. + /// The role if it exists. + protected abstract Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken); + + /// + /// Return a user role for the userId and roleId if it exists. + /// + /// The user's id. + /// The role's id. + /// The used to propagate notifications that the operation should be canceled. + /// The user role if it exists. + protected abstract Task FindUserRoleAsync(TKey userId, TKey roleId, CancellationToken cancellationToken); + + /// + /// 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 abstract Task FindUserAsync(TKey userId, CancellationToken cancellationToken); + + /// + /// 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 abstract Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken); + + /// + /// 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 abstract Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken); + + /// + /// Adds the given to the specified . + /// + /// The user to add the role to. + /// The role to add. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public abstract Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Removes the given from the specified . + /// + /// The user to remove the role from. + /// The role to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public abstract Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Retrieves the roles the specified is a member of. + /// + /// The user whose roles should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the roles the user is a member of. + public abstract Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Returns a flag indicating if the specified user is a member of the give . + /// + /// The user whose role membership should be checked. + /// The role to check membership of + /// The used to propagate notifications that the operation should be canceled. + /// A containing a flag indicating if the specified user is a member of the given group. If the + /// user is a member of the group the returned value with be true, otherwise it will be false. + public abstract Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Throws if this class has been disposed. + /// + protected void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Dispose the store + /// + public void Dispose() + { + _disposed = true; + } + + /// + /// 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 abstract Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task AddLoginAsync(TUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 virtual 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 a flag indicating whether the email address for the specified has been verified, true if the email address is verified otherwise + /// false. + /// + /// The user whose email confirmation status should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified + /// has been confirmed or not. + /// + public virtual Task GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.EmailConfirmed); + } + + /// + /// 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 virtual Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.EmailConfirmed = confirmed; + 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 virtual Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.Email = email; + return Task.CompletedTask; + } + + /// + /// Gets the email address for the specified . + /// + /// The user whose email should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// The task object containing the results of the asynchronous operation, the email address for the specified . + public virtual Task GetEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.Email); + } + + /// + /// Returns the normalized email for the specified . + /// + /// The user whose email address to retrieve. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous lookup operation, the normalized email address if any associated with the specified user. + /// + public virtual Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.NormalizedEmail); + } + + /// + /// 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 virtual Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.NormalizedEmail = normalizedEmail; + return Task.CompletedTask; + } + + /// + /// 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 abstract Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Gets the last a user's last lockout expired, if any. + /// Any time in the past should be indicates a user is not locked out. + /// + /// The user whose lockout date should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// A that represents the result of the asynchronous query, a containing the last time + /// a user's lockout expired, if any. + /// + public virtual Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.LockoutEnd); + } + + /// + /// 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 virtual Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.LockoutEnd = 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 virtual Task IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + 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 virtual Task ResetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.AccessFailedCount = 0; + return Task.CompletedTask; + } + + /// + /// Retrieves the current failed access count for the specified .. + /// + /// The user whose failed access count should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the failed access count. + public virtual Task GetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.AccessFailedCount); + } + + /// + /// Retrieves a flag indicating whether user lockout can enabled for the specified user. + /// + /// The user whose ability to be locked out should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, true if a user can be locked out, otherwise false. + /// + public virtual Task GetLockoutEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.LockoutEnabled); + } + + /// + /// 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 virtual Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.LockoutEnabled = enabled; + 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 virtual Task SetPhoneNumberAsync(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.PhoneNumber = phoneNumber; + return Task.CompletedTask; + } + + /// + /// Gets the telephone number, if any, for the specified . + /// + /// The user whose telephone number should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the user's telephone number, if any. + public virtual Task GetPhoneNumberAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.PhoneNumber); + } + + /// + /// Gets a flag indicating whether the specified 's telephone number has been confirmed. + /// + /// The user to return a flag for, indicating whether their telephone number is confirmed. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, returning true if the specified has a confirmed + /// telephone number otherwise false. + /// + public virtual Task GetPhoneNumberConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.PhoneNumberConfirmed); + } + + /// + /// 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 virtual Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.PhoneNumberConfirmed = confirmed; + 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 virtual 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)); + } + user.SecurityStamp = stamp; + return Task.CompletedTask; + } + + /// + /// Get the security stamp 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 virtual Task GetSecurityStampAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.SecurityStamp); + } + + /// + /// 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 virtual Task SetTwoFactorEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + user.TwoFactorEnabled = enabled; + return Task.CompletedTask; + } + + /// + /// Returns 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. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing a flag indicating whether the specified + /// has two factor authentication enabled or not. + /// + public virtual Task GetTwoFactorEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return Task.FromResult(user.TwoFactorEnabled); + } + + /// + /// 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 abstract Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Retrieves all users in the specified role. + /// + /// The role 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 are in the specified role. + /// + public abstract Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// 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 abstract Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken); + + /// + /// Add a new user token. + /// + /// The token to be added. + /// + protected abstract Task AddUserTokenAsync(TUserToken token); + + /// + /// Remove a new user token. + /// + /// The token to be removed. + /// + protected abstract Task RemoveUserTokenAsync(TUserToken token); + + /// + /// 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 virtual 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) + { + await AddUserTokenAsync(CreateUserToken(user, loginProvider, name, value)); + } + else + { + token.Value = value; + } + } + + /// + /// 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 virtual 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) + { + await RemoveUserTokenAsync(entry); + } + } + + /// + /// 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 virtual 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 virtual Task SetAuthenticatorKeyAsync(TUser user, string key, CancellationToken cancellationToken) + { + return 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 virtual Task GetAuthenticatorKeyAsync(TUser user, CancellationToken cancellationToken) + { + return GetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, cancellationToken); + } + + /// + /// 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 virtual 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 virtual 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; + } } } diff --git a/src/Microsoft.Extensions.Identity.Stores/UserStoreBaseV1.cs b/src/Microsoft.Extensions.Identity.Stores/UserStoreBaseV1.cs deleted file mode 100644 index cf590fe279..0000000000 --- a/src/Microsoft.Extensions.Identity.Stores/UserStoreBaseV1.cs +++ /dev/null @@ -1,1156 +0,0 @@ -// 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; - -namespace Microsoft.AspNetCore.Identity -{ - /// - /// Represents a new instance of a persistence store for the specified user and role types. - /// - /// The type representing a user. - /// The type representing a role. - /// The type of the primary key for a role. - /// The type representing a claim. - /// The type representing a user role. - /// The type representing a user external login. - /// The type representing a user token. - /// The type representing a role claim. - public abstract class UserStoreBaseV1 : - IUserLoginStore, - IUserRoleStore, - IUserClaimStore, - IUserPasswordStore, - IUserSecurityStampStore, - IUserEmailStore, - IUserLockoutStore, - IUserPhoneNumberStore, - IQueryableUserStore, - IUserTwoFactorStore, - IUserAuthenticationTokenStore, - IUserAuthenticatorKeyStore, - IUserTwoFactorRecoveryCodeStore - where TUser : IdentityUser - where TRole : IdentityRole - where TKey : IEquatable - where TUserClaim : IdentityUserClaim, new() - where TUserRole : IdentityUserRole, new() - where TUserLogin : IdentityUserLogin, new() - where TUserToken : IdentityUserToken, new() - where TRoleClaim : IdentityRoleClaim, new() - { - /// - /// Creates a new instance. - /// - /// The used to describe store errors. - public UserStoreBaseV1(IdentityErrorDescriber describer) - { - if (describer == null) - { - throw new ArgumentNullException(nameof(describer)); - } - - ErrorDescriber = describer; - } - - private bool _disposed; - - /// - /// Gets or sets the for any error that occurred with the current operation. - /// - public IdentityErrorDescriber ErrorDescriber { get; set; } - - /// - /// Called to create a new instance of a . - /// - /// The associated user. - /// The associated role. - /// - public virtual TUserRole CreateUserRole(TUser user, TRole role) - { - return new TUserRole() - { - UserId = user.Id, - RoleId = role.Id - }; - } - - /// - /// Called to create a new instance of a . - /// - /// The associated user. - /// The associated claim. - /// - public virtual TUserClaim CreateUserClaim(TUser user, Claim claim) - { - var userClaim = new TUserClaim { UserId = user.Id }; - userClaim.InitializeFromClaim(claim); - return userClaim; - } - - /// - /// Called to create a new instance of a . - /// - /// The associated user. - /// The sasociated login. - /// - public virtual TUserLogin CreateUserLogin(TUser user, UserLoginInfo login) - { - return new TUserLogin - { - UserId = user.Id, - ProviderKey = login.ProviderKey, - LoginProvider = login.LoginProvider, - ProviderDisplayName = login.ProviderDisplayName - }; - } - - /// - /// Called to create a new instance of a . - /// - /// The associated user. - /// The associated login provider. - /// The name of the user token. - /// The value of the user token. - /// - public virtual TUserToken CreateUserToken(TUser user, string loginProvider, string name, string value) - { - return new TUserToken - { - UserId = user.Id, - LoginProvider = loginProvider, - Name = name, - Value = value - }; - } - - /// - /// Gets the user identifier for the specified . - /// - /// The user whose identifier should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, containing the identifier for the specified . - public virtual Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(ConvertIdToString(user.Id)); - } - - /// - /// Gets the user name for the specified . - /// - /// The user whose name should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, containing the name for the specified . - public virtual Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.UserName); - } - - /// - /// 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 virtual Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.UserName = userName; - return Task.CompletedTask; - } - - /// - /// Gets the normalized user name for the specified . - /// - /// The user whose normalized name should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, containing the normalized user name for the specified . - public virtual Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.NormalizedUserName); - } - - /// - /// 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 virtual Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.NormalizedUserName = normalizedName; - 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 abstract Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Converts the provided to a strongly typed key object. - /// - /// The id to convert. - /// An instance of representing the provided . - public virtual TKey ConvertIdFromString(string id) - { - if (id == null) - { - return default(TKey); - } - return (TKey)TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(id); - } - - /// - /// Converts the provided to its string representation. - /// - /// The id to convert. - /// An representation of the provided . - public virtual string ConvertIdToString(TKey id) - { - if (object.Equals(id, default(TKey))) - { - return null; - } - return id.ToString(); - } - - /// - /// 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 abstract Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// A navigation property for the users the store contains. - /// - public abstract IQueryable Users - { - get; - } - - /// - /// 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 virtual Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.PasswordHash = passwordHash; - return Task.CompletedTask; - } - - /// - /// Gets the password hash for a user. - /// - /// The user to retrieve the password hash for. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the password hash for the user. - public virtual Task GetPasswordHashAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.PasswordHash); - } - - /// - /// Returns a flag indicating if the specified user has a password. - /// - /// The user to retrieve the password hash for. - /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user has a password. If the - /// user has a password the returned value with be true, otherwise it will be false. - public virtual Task HasPasswordAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(user.PasswordHash != null); - } - - /// - /// Return a role with the normalized name if it exists. - /// - /// The normalized role name. - /// The used to propagate notifications that the operation should be canceled. - /// The role if it exists. - public abstract Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken); - - /// - /// Return a user role for the userId and roleId if it exists. - /// - /// The user's id. - /// The role's id. - /// The used to propagate notifications that the operation should be canceled. - /// The user role if it exists. - public abstract Task FindUserRoleAsync(TKey userId, TKey roleId, CancellationToken cancellationToken); - - /// - /// 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. - public abstract Task FindUserAsync(TKey userId, CancellationToken cancellationToken); - - /// - /// 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. - public abstract Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken); - - /// - /// 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. - public abstract Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken); - - /// - /// Adds the given to the specified . - /// - /// The user to add the role to. - /// The role to add. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public abstract Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Removes the given from the specified . - /// - /// The user to remove the role from. - /// The role to remove. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public abstract Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Retrieves the roles the specified is a member of. - /// - /// The user whose roles should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the roles the user is a member of. - public abstract Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Returns a flag indicating if the specified user is a member of the give . - /// - /// The user whose role membership should be checked. - /// The role to check membership of - /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user is a member of the given group. If the - /// user is a member of the group the returned value with be true, otherwise it will be false. - public abstract Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// True if this class has been disposed. - /// - protected bool IsDisposed => _disposed; - - /// - /// Throws if this class has been disposed. - /// - protected void ThrowIfDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - - /// - /// Dispose the store - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose the store - /// - /// True when called from Dispose - protected virtual void Dispose(bool disposing) - { - _disposed = true; - } - - /// - /// 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 abstract Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task AddLoginAsync(TUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 abstract Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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 virtual 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 a flag indicating whether the email address for the specified has been verified, true if the email address is verified otherwise - /// false. - /// - /// The user whose email confirmation status should be returned. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified - /// has been confirmed or not. - /// - public virtual Task GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.EmailConfirmed); - } - - /// - /// 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 virtual Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.EmailConfirmed = confirmed; - 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 virtual Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.Email = email; - return Task.CompletedTask; - } - - /// - /// Gets the email address for the specified . - /// - /// The user whose email should be returned. - /// The used to propagate notifications that the operation should be canceled. - /// The task object containing the results of the asynchronous operation, the email address for the specified . - public virtual Task GetEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.Email); - } - - /// - /// Returns the normalized email for the specified . - /// - /// The user whose email address to retrieve. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The task object containing the results of the asynchronous lookup operation, the normalized email address if any associated with the specified user. - /// - public virtual Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.NormalizedEmail); - } - - /// - /// 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 virtual Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.NormalizedEmail = normalizedEmail; - return Task.CompletedTask; - } - - /// - /// 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 abstract Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Gets the last a user's last lockout expired, if any. - /// Any time in the past should be indicates a user is not locked out. - /// - /// The user whose lockout date should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// - /// A that represents the result of the asynchronous query, a containing the last time - /// a user's lockout expired, if any. - /// - public virtual Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.LockoutEnd); - } - - /// - /// 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 virtual Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.LockoutEnd = 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 virtual Task IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - 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 virtual Task ResetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.AccessFailedCount = 0; - return Task.CompletedTask; - } - - /// - /// Retrieves the current failed access count for the specified .. - /// - /// The user whose failed access count should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, containing the failed access count. - public virtual Task GetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.AccessFailedCount); - } - - /// - /// Retrieves a flag indicating whether user lockout can enabled for the specified user. - /// - /// The user whose ability to be locked out should be returned. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The that represents the asynchronous operation, true if a user can be locked out, otherwise false. - /// - public virtual Task GetLockoutEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.LockoutEnabled); - } - - /// - /// 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 virtual Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.LockoutEnabled = enabled; - 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 virtual Task SetPhoneNumberAsync(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.PhoneNumber = phoneNumber; - return Task.CompletedTask; - } - - /// - /// Gets the telephone number, if any, for the specified . - /// - /// The user whose telephone number should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, containing the user's telephone number, if any. - public virtual Task GetPhoneNumberAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.PhoneNumber); - } - - /// - /// Gets a flag indicating whether the specified 's telephone number has been confirmed. - /// - /// The user to return a flag for, indicating whether their telephone number is confirmed. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The that represents the asynchronous operation, returning true if the specified has a confirmed - /// telephone number otherwise false. - /// - public virtual Task GetPhoneNumberConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.PhoneNumberConfirmed); - } - - /// - /// 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 virtual Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.PhoneNumberConfirmed = confirmed; - 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 virtual 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)); - } - user.SecurityStamp = stamp; - return Task.CompletedTask; - } - - /// - /// Get the security stamp 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 virtual Task GetSecurityStampAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.SecurityStamp); - } - - /// - /// 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 virtual Task SetTwoFactorEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.TwoFactorEnabled = enabled; - return Task.CompletedTask; - } - - /// - /// Returns 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. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The that represents the asynchronous operation, containing a flag indicating whether the specified - /// has two factor authentication enabled or not. - /// - public virtual Task GetTwoFactorEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.TwoFactorEnabled); - } - - /// - /// 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 abstract Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Retrieves all users in the specified role. - /// - /// The role 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 are in the specified role. - /// - public abstract Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// 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. - public abstract Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken); - - /// - /// Add a new user token. - /// - /// The token to be added. - /// - public abstract Task AddUserTokenAsync(TUserToken token); - - /// - /// Remove a new user token. - /// - /// The token to be removed. - /// - public abstract Task RemoveUserTokenAsync(TUserToken token); - - /// - /// 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 virtual 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) - { - await AddUserTokenAsync(CreateUserToken(user, loginProvider, name, value)); - } - else - { - token.Value = value; - } - } - - /// - /// 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 virtual 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) - { - await RemoveUserTokenAsync(entry); - } - } - - /// - /// 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 virtual 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 virtual Task SetAuthenticatorKeyAsync(TUser user, string key, CancellationToken cancellationToken) - { - return 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 virtual Task GetAuthenticatorKeyAsync(TUser user, CancellationToken cancellationToken) - { - return GetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, cancellationToken); - } - - /// - /// 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 virtual 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 virtual 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; - } - } -} diff --git a/src/Microsoft.Extensions.Identity.Stores/UserStoreBaseV2.cs b/src/Microsoft.Extensions.Identity.Stores/UserStoreBaseV2.cs deleted file mode 100644 index 25c4f2f29e..0000000000 --- a/src/Microsoft.Extensions.Identity.Stores/UserStoreBaseV2.cs +++ /dev/null @@ -1,151 +0,0 @@ -// 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.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Identity -{ - /// - /// Represents a new instance of a persistence store for the specified user and role types. - /// - /// The type representing a user. - /// The type representing a role. - /// The type of the primary key for a role. - /// The type representing a claim. - /// The type representing a user role. - /// The type representing a user external login. - /// The type representing a user token. - /// The type representing a role claim. - public abstract class UserStoreBaseV2 : - UserStoreBaseV1, - IUserActivityStore - where TUser : IdentityUser - where TRole : IdentityRole - where TKey : IEquatable - where TUserClaim : IdentityUserClaim, new() - where TUserRole : IdentityUserRole, new() - where TUserLogin : IdentityUserLogin, new() - where TUserToken : IdentityUserToken, new() - where TRoleClaim : IdentityRoleClaim, new() - { - /// - /// Creates a new instance. - /// - /// The used to describe store errors. - public UserStoreBaseV2(IdentityErrorDescriber describer) : base(describer) { } - - /// - /// Sets the last password change date for the specified . - /// - /// The user. - /// The last password change date. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public virtual Task SetLastPasswordChangeDateAsync(TUser user, DateTimeOffset? changeDate, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.LastPasswordChangeDate = changeDate; - return Task.CompletedTask; - } - - /// - /// Gets the last password change date for the specified . - /// - /// The user. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, returning the password hash for the specified . - public virtual Task GetLastPasswordChangeDateAsync(TUser user, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.LastPasswordChangeDate); - } - - /// - /// Sets the creation date for the specified . - /// - /// The user. - /// The date the user was created. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public virtual Task SetCreateDateAsync(TUser user, DateTimeOffset? creationDate, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.CreateDate = creationDate; - return Task.CompletedTask; - } - - /// - /// Gets the creation date for the specified . - /// - /// The user. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, returning the password hash for the specified . - public virtual Task GetCreateDateAsync(TUser user, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.CreateDate); - } - - /// - /// Sets the last signin date for the specified . - /// - /// The user. - /// The date the user last signed in. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public virtual Task SetLastSignInDateAsync(TUser user, DateTimeOffset? lastSignIn, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - user.LastSignInDate = lastSignIn; - return Task.CompletedTask; - } - - /// - /// Gets the last signin date for the specified . - /// - /// The user. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, returning the password hash for the specified . - public virtual Task GetLastSignInDateAsync(TUser user, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - return Task.FromResult(user.LastSignInDate); - } - - } -} diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs index 1261747bb3..e1d2e349a0 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs @@ -9,18 +9,16 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test public class InMemoryContext : InMemoryContext { - public InMemoryContext(DbContextOptions options) : base(options) { } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseInMemoryDatabase("Scratch"); + public InMemoryContext(DbContextOptions options) : base(options) + { } } public class InMemoryContext : InMemoryContext where TUser : IdentityUser { - public InMemoryContext(DbContextOptions options) : base(options) { } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseInMemoryDatabase("Scratch"); + public InMemoryContext(DbContextOptions options) : base(options) + { } } public class InMemoryContext : IdentityDbContext @@ -28,10 +26,13 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test where TRole : IdentityRole where TKey : IEquatable { - public InMemoryContext(DbContextOptions options) : base(options) { } + public InMemoryContext(DbContextOptions options) : base(options) + { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseInMemoryDatabase("Scratch"); + { + optionsBuilder.UseInMemoryDatabase("Scratch"); + } } public abstract class InMemoryContext : IdentityDbContext @@ -44,57 +45,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test where TRoleClaim : IdentityRoleClaim where TUserToken : IdentityUserToken { - public InMemoryContext(DbContextOptions options) : base(options) { } + public InMemoryContext(DbContextOptions options) : base(options) + { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseInMemoryDatabase("Scratch"); - } - - public class InMemoryContextV1 : - InMemoryContextV1 - { - public InMemoryContextV1(DbContextOptions options) : base(options) { } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseInMemoryDatabase("ScratchV1"); - } - - public class InMemoryContextV1 : - InMemoryContext - where TUser : IdentityUser - { - public InMemoryContextV1(DbContextOptions options) : base(options) { } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseInMemoryDatabase("ScratchV1"); - } - - public class InMemoryContextV1 : IdentityDbContext - where TUser : IdentityUser - where TRole : IdentityRole - where TKey : IEquatable - { - public InMemoryContextV1(DbContextOptions options) : base(options) { } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseInMemoryDatabase("ScratchV1"); - } - - public abstract class InMemoryContextV1 : IdentityDbContext - where TUser : IdentityUser - where TRole : IdentityRole - where TKey : IEquatable - where TUserClaim : IdentityUserClaim - where TUserRole : IdentityUserRole - where TUserLogin : IdentityUserLogin - where TRoleClaim : IdentityRoleClaim - where TUserToken : IdentityUserToken - { - public InMemoryContextV1(DbContextOptions options) : base(options) { } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseInMemoryDatabase("ScratchV1"); - - // TODO: remove once we fix the base - protected override void OnModelCreating(ModelBuilder builder) - => base.OnModelCreatingV1(builder); + { + optionsBuilder.UseInMemoryDatabase("Scratch"); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFUserStoreTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFUserStoreTest.cs index f8ddd77528..5a4d0fdc20 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFUserStoreTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFUserStoreTest.cs @@ -3,25 +3,27 @@ using System; using System.Linq.Expressions; -using System.Threading.Tasks; using Microsoft.AspNetCore.Identity.Test; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Xunit; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test { public class InMemoryEFUserStoreTest : IdentitySpecificationTestBase { protected override object CreateTestContext() - => new InMemoryContext(new DbContextOptionsBuilder().Options); + { + return new InMemoryContext(new DbContextOptionsBuilder().Options); + } protected override void AddUserStore(IServiceCollection services, object context = null) - => services.AddSingleton>(new UserStore, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim>((InMemoryContext)context, new IdentityErrorDescriber())); + { + services.AddSingleton>(new UserStore((InMemoryContext)context)); + } protected override void AddRoleStore(IServiceCollection services, object context = null) { - var store = new RoleStore, IdentityRoleClaim>((InMemoryContext)context); + var store = new RoleStore((InMemoryContext)context); services.AddSingleton>(store); } diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFUserStoreV1Test.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFUserStoreV1Test.cs deleted file mode 100644 index c1c8f5cf25..0000000000 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFUserStoreV1Test.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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.Linq.Expressions; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity.Test; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test -{ - public class InMemoryEFUserStoreV1Test : IdentitySpecificationTestBase - { - protected override object CreateTestContext() - => new InMemoryContextV1(new DbContextOptionsBuilder().Options); - - protected override void AddUserStore(IServiceCollection services, object context = null) - { - services.AddSingleton>(new UserStoreV1, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim>((InMemoryContextV1)context, new IdentityErrorDescriber())); - } - - protected override void AddRoleStore(IServiceCollection services, object context = null) - { - var store = new RoleStoreV1, IdentityRoleClaim>((InMemoryContextV1)context); - services.AddSingleton>(store); - } - - protected override IdentityUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", - bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false) - { - return new IdentityUser - { - UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()), - Email = email, - PhoneNumber = phoneNumber, - LockoutEnabled = lockoutEnabled, - LockoutEnd = lockoutEnd - }; - } - - protected override IdentityRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false) - { - var roleName = useRoleNamePrefixAsRoleName ? roleNamePrefix : string.Format("{0}{1}", roleNamePrefix, Guid.NewGuid()); - return new IdentityRole(roleName); - } - - protected override void SetUserPasswordHash(IdentityUser user, string hashedPassword) - { - user.PasswordHash = hashedPassword; - } - - protected override Expression> UserNameEqualsPredicate(string userName) => u => u.UserName == userName; - - protected override Expression> RoleNameEqualsPredicate(string roleName) => r => r.Name == roleName; - - protected override Expression> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName); - - protected override Expression> RoleNameStartsWithPredicate(string roleName) => r => r.Name.StartsWith(roleName); - } -} diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs index a7e4b01afd..679221e048 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs @@ -200,12 +200,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test { public string LoginContext { get; set; } - public UserStoreWithGenerics(InMemoryContextWithGenerics context, string loginContext) : base(context, new IdentityErrorDescriber()) + public UserStoreWithGenerics(InMemoryContextWithGenerics context, string loginContext) : base(context) { LoginContext = loginContext; } - public override IdentityUserRoleWithDate CreateUserRole(IdentityUserWithGenerics user, MyIdentityRole role) + protected override IdentityUserRoleWithDate CreateUserRole(IdentityUserWithGenerics user, MyIdentityRole role) { return new IdentityUserRoleWithDate() { @@ -215,12 +215,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test }; } - public override IdentityUserClaimWithIssuer CreateUserClaim(IdentityUserWithGenerics user, Claim claim) + protected override IdentityUserClaimWithIssuer CreateUserClaim(IdentityUserWithGenerics user, Claim claim) { return new IdentityUserClaimWithIssuer { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value, Issuer = claim.Issuer }; } - public override IdentityUserLoginWithContext CreateUserLogin(IdentityUserWithGenerics user, UserLoginInfo login) + protected override IdentityUserLoginWithContext CreateUserLogin(IdentityUserWithGenerics user, UserLoginInfo login) { return new IdentityUserLoginWithContext { @@ -232,7 +232,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test }; } - public override IdentityUserTokenWithStuff CreateUserToken(IdentityUserWithGenerics user, string loginProvider, string name, string value) + protected override IdentityUserTokenWithStuff CreateUserToken(IdentityUserWithGenerics user, string loginProvider, string name, string value) { return new IdentityUserTokenWithStuff { diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/RoleStoreTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/RoleStoreTest.cs index 84d82a14fd..a356787781 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/RoleStoreTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/RoleStoreTest.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test var services = TestIdentityFactory.CreateTestServices(); services.AddEntityFrameworkInMemoryDatabase(); services.AddSingleton(new InMemoryContext(new DbContextOptionsBuilder().Options)); - services.AddTransient, RoleStore, IdentityRoleClaim>>(); + services.AddTransient, RoleStore>(); services.AddSingleton>(); var provider = services.BuildServiceProvider(); var manager = provider.GetRequiredService>(); @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test [Fact] public async Task RoleStoreMethodsThrowWhenDisposedTest() { - var store = new RoleStore, IdentityRoleClaim>(new InMemoryContext(new DbContextOptionsBuilder().Options)); + var store = new RoleStore(new InMemoryContext(new DbContextOptionsBuilder().Options)); store.Dispose(); await Assert.ThrowsAsync(async () => await store.FindByIdAsync(null)); await Assert.ThrowsAsync(async () => await store.FindByNameAsync(null)); @@ -53,8 +53,8 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test [Fact] public async Task RoleStorePublicNullCheckTest() { - Assert.Throws("context", () => new RoleStore, IdentityRoleClaim>(null)); - var store = new RoleStore, IdentityRoleClaim>(new InMemoryContext(new DbContextOptionsBuilder().Options)); + Assert.Throws("context", () => new RoleStore(null)); + var store = new RoleStore(new InMemoryContext(new DbContextOptionsBuilder().Options)); await Assert.ThrowsAsync("role", async () => await store.GetRoleIdAsync(null)); await Assert.ThrowsAsync("role", async () => await store.GetRoleNameAsync(null)); await Assert.ThrowsAsync("role", async () => await store.SetRoleNameAsync(null, null)); diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/TestIdentityFactory.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/TestIdentityFactory.cs index 203de9b14c..c8ba6c922f 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/TestIdentityFactory.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/TestIdentityFactory.cs @@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test public static RoleManager CreateRoleManager(InMemoryContext context) { var services = CreateTestServices(); - services.AddSingleton>(new RoleStore, IdentityRoleClaim>(context)); + services.AddSingleton>(new RoleStore(context)); return services.BuildServiceProvider().GetRequiredService>(); } diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreTestBase.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreTestBase.cs index 3f87134a8e..5a78f6c50d 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreTestBase.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreTestBase.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Xunit; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test @@ -22,7 +21,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test where TRole : IdentityRole, new() where TKey : IEquatable { - protected readonly ScratchDatabaseFixture _fixture; + private readonly ScratchDatabaseFixture _fixture; protected SqlStoreTestBase(ScratchDatabaseFixture fixture) { @@ -65,7 +64,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test protected override Expression> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName); - public virtual TestDbContext CreateContext() + public TestDbContext CreateContext() { var db = DbUtil.Create(_fixture.ConnectionString); db.Database.EnsureCreated(); @@ -79,12 +78,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test protected override void AddUserStore(IServiceCollection services, object context = null) { - services.AddSingleton>(new UserStore, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim>((TestDbContext)context, new IdentityErrorDescriber())); + services.AddSingleton>(new UserStore((TestDbContext)context)); } protected override void AddRoleStore(IServiceCollection services, object context = null) { - services.AddSingleton>(new RoleStore, IdentityRoleClaim>((TestDbContext)context)); + services.AddSingleton>(new RoleStore((TestDbContext)context)); } protected override void SetUserPasswordHash(TUser user, string hashedPassword) @@ -96,36 +95,26 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] - public virtual void EnsureDefaultSchema() + public void EnsureDefaultSchema() { - var db = DbUtil.Create(_fixture.ConnectionString); - var services = new ServiceCollection().AddSingleton(db); - services.AddIdentity().AddEntityFrameworkStoresLatest(); - var sp = services.BuildServiceProvider(); - var version = sp.GetRequiredService>().Value.Version; - Assert.Equal(IdentityStoreOptions.Version_Latest, version); - db.Version = version; - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); - VerifyDefaultSchema(db); + VerifyDefaultSchema(CreateContext()); } - protected virtual void VerifyDefaultSchema(TestDbContext dbContext) + internal static void VerifyDefaultSchema(TestDbContext dbContext) { var sqlConn = dbContext.Database.GetDbConnection(); using (var db = new SqlConnection(sqlConn.ConnectionString)) { db.Open(); - VerifyColumns(db, "AspNetUsers", "Id", "UserName", "Email", "PasswordHash", "SecurityStamp", + Assert.True(VerifyColumns(db, "AspNetUsers", "Id", "UserName", "Email", "PasswordHash", "SecurityStamp", "EmailConfirmed", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnabled", - "LockoutEnd", "AccessFailedCount", "ConcurrencyStamp", "NormalizedUserName", "NormalizedEmail", - "CreateDate", "LastSignInDate", "LastPasswordChangeDate"); - VerifyColumns(db, "AspNetRoles", "Id", "Name", "NormalizedName", "ConcurrencyStamp"); - VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId"); - VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue"); - VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName"); - VerifyColumns(db, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value"); + "LockoutEnd", "AccessFailedCount", "ConcurrencyStamp", "NormalizedUserName", "NormalizedEmail")); + Assert.True(VerifyColumns(db, "AspNetRoles", "Id", "Name", "NormalizedName", "ConcurrencyStamp")); + Assert.True(VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId")); + Assert.True(VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue")); + Assert.True(VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName")); + Assert.True(VerifyColumns(db, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value")); VerifyIndex(db, "AspNetRoles", "RoleNameIndex", isUnique: true); VerifyIndex(db, "AspNetUsers", "UserNameIndex", isUnique: true); @@ -134,7 +123,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test } } - internal static void VerifyColumns(SqlConnection conn, string table, params string[] columns) + internal static bool VerifyColumns(SqlConnection conn, string table, params string[] columns) { var count = 0; using ( @@ -147,10 +136,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test while (reader.Read()) { count++; - Assert.True(columns.Contains(reader.GetString(0)), - "Unexpected column: " + reader.GetString(0)); + if (!columns.Contains(reader.GetString(0))) + { + return false; + } } - Assert.Equal(count, columns.Length); + return count == columns.Length; } } } @@ -372,67 +363,6 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test Assert.Equal(1, (await manager.GetLoginsAsync(userByEmail)).Count); Assert.Equal(2, (await manager.GetRolesAsync(userByEmail)).Count); } - } - public abstract class SqlStoreTestBaseV1 : SqlStoreTestBase, IClassFixture - where TUser : IdentityUser, new() - where TRole : IdentityRole, new() - where TKey : IEquatable - { - protected SqlStoreTestBaseV1(ScratchDatabaseFixture fixture) : base(fixture) { } - - public override TestDbContext CreateContext() - { - var db = base.CreateContext(); - db.Version = IdentityStoreOptions.Version1_0; - return db; - } - - protected override void AddUserStore(IServiceCollection services, object context = null) - => services.AddSingleton>(new UserStoreV1, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim>((TestDbContext)context, new IdentityErrorDescriber())); - - protected override void AddRoleStore(IServiceCollection services, object context = null) - => services.AddSingleton>(new RoleStoreV1, IdentityRoleClaim>((TestDbContext)context)); - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public override void EnsureDefaultSchema() - { - var db = DbUtil.Create(_fixture.ConnectionString); - var services = new ServiceCollection().AddSingleton(db); - services.AddIdentity().AddEntityFrameworkStoresV1(); - var sp = services.BuildServiceProvider(); - var version = sp.GetRequiredService>().Value.Version; - Assert.Equal(IdentityStoreOptions.Version1_0, version); - db.Version = version; - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); - VerifyDefaultSchema(db); - } - - protected override void VerifyDefaultSchema(TestDbContext dbContext) - { - var sqlConn = dbContext.Database.GetDbConnection(); - - using (var db = new SqlConnection(sqlConn.ConnectionString)) - { - db.Open(); - VerifyColumns(db, "AspNetUsers", "Id", "UserName", "Email", "PasswordHash", "SecurityStamp", - "EmailConfirmed", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnabled", - "LockoutEnd", "AccessFailedCount", "ConcurrencyStamp", "NormalizedUserName", "NormalizedEmail"); - VerifyColumns(db, "AspNetRoles", "Id", "Name", "NormalizedName", "ConcurrencyStamp"); - VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId"); - VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue"); - VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName"); - VerifyColumns(db, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value"); - - VerifyIndex(db, "AspNetRoles", "RoleNameIndex", isUnique: true); - VerifyIndex(db, "AspNetUsers", "UserNameIndex", isUnique: true); - VerifyIndex(db, "AspNetUsers", "EmailIndex"); - db.Close(); - } - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidKeyTest.cs similarity index 50% rename from test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidTest.cs rename to test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidKeyTest.cs index 892b2ab669..9887736d89 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreGuidKeyTest.cs @@ -25,55 +25,21 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test } } - public class UserStoreGuidV1Test : SqlStoreTestBaseV1 - { - public UserStoreGuidV1Test(ScratchDatabaseFixture fixture) - : base(fixture) - { } - - public class ApplicationUserStore : UserStoreV1, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim> - { - public ApplicationUserStore(TestDbContext context) : base(context, new IdentityErrorDescriber()) { } - } - - public class ApplicationRoleStore : RoleStoreV1, IdentityRoleClaim> - { - public ApplicationRoleStore(TestDbContext context) : base(context, new IdentityErrorDescriber()) { } - } - - protected override void AddUserStore(IServiceCollection services, object context = null) - { - services.AddSingleton>(new ApplicationUserStore((TestDbContext)context)); - } - - protected override void AddRoleStore(IServiceCollection services, object context = null) - { - services.AddSingleton>(new ApplicationRoleStore((TestDbContext)context)); - } - - [Fact] - public void AddEntityFrameworkStoresCanInferKey() - { - var services = new ServiceCollection(); - // This used to throw - var builder = services.AddIdentity().AddEntityFrameworkStores(); - } - } - public class UserStoreGuidTest : SqlStoreTestBase { public UserStoreGuidTest(ScratchDatabaseFixture fixture) : base(fixture) - { } - - public class ApplicationUserStore : UserStore, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim> { - public ApplicationUserStore(TestDbContext context) : base(context, new IdentityErrorDescriber()) { } } - public class ApplicationRoleStore : RoleStore, IdentityRoleClaim> + public class ApplicationUserStore : UserStore { - public ApplicationRoleStore(TestDbContext context) : base(context, new IdentityErrorDescriber()) { } + public ApplicationUserStore(TestDbContext context) : base(context) { } + } + + public class ApplicationRoleStore : RoleStore + { + public ApplicationRoleStore(TestDbContext context) : base(context) { } } protected override void AddUserStore(IServiceCollection services, object context = null) @@ -94,5 +60,4 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test var builder = services.AddIdentity().AddEntityFrameworkStores(); } } - } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntKeyTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntKeyTest.cs new file mode 100644 index 0000000000..265a467b93 --- /dev/null +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntKeyTest.cs @@ -0,0 +1,41 @@ +// 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 Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test +{ + public class IntUser : IdentityUser + { + public IntUser() + { + UserName = Guid.NewGuid().ToString(); + } + } + + public class IntRole : IdentityRole + { + public IntRole() + { + Name = Guid.NewGuid().ToString(); + } + } + + public class UserStoreIntTest : SqlStoreTestBase + { + public UserStoreIntTest(ScratchDatabaseFixture fixture) + : base(fixture) + { + } + + [Fact] + public void AddEntityFrameworkStoresCanInferKey() + { + var services = new ServiceCollection(); + // This used to throw + var builder = services.AddIdentity().AddEntityFrameworkStores(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntTest.cs deleted file mode 100644 index cb461a6920..0000000000 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreIntTest.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 Microsoft.AspNetCore.Testing.xunit; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Xunit; - -namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test -{ - public class IntUser : IdentityUser - { - public IntUser() => UserName = Guid.NewGuid().ToString(); - } - - public class IntRole : IdentityRole - { - public IntRole() => Name = Guid.NewGuid().ToString(); - } - - public class UserStoreIntTest : SqlStoreTestBase - { - public UserStoreIntTest(ScratchDatabaseFixture fixture) : base(fixture) { } - - [Fact] - public void AddEntityFrameworkStoresCanInferKey() - { - // This used to throw - var builder = new ServiceCollection().AddIdentity().AddEntityFrameworkStores(); - } - } - - public class UserStoreIntV1Test : SqlStoreTestBaseV1 - { - public UserStoreIntV1Test(ScratchDatabaseFixture fixture) : base(fixture) { } - - [Fact] - public void AddEntityFrameworkStoresCanInferKey() - { - // This used to throw - var builder = new ServiceCollection().AddIdentity().AddEntityFrameworkStores(); - } - } - -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreTest.cs index 878d2f57f3..34a824d62e 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreTest.cs @@ -14,11 +14,6 @@ using Xunit; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test { - internal class UserStore : UserStore, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim> - { - public UserStore(IdentityDbContext context, IdentityErrorDescriber describer = null) : base(context, describer ?? new IdentityErrorDescriber()) { } - } - public class UserStoreTest : IdentitySpecificationTestBase, IClassFixture { private readonly ScratchDatabaseFixture _fixture; @@ -31,6 +26,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test protected override bool ShouldSkipDbTests() => TestPlatformHelper.IsMono || !TestPlatformHelper.IsWindows; + public class ApplicationDbContext : IdentityDbContext + { + public ApplicationDbContext(DbContextOptions options) : base(options) + { } + } + [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] @@ -63,14 +64,21 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test return CreateContext(); } + public ApplicationDbContext CreateAppContext() + { + var db = DbUtil.Create(_fixture.ConnectionString); + db.Database.EnsureCreated(); + return db; + } + protected override void AddUserStore(IServiceCollection services, object context = null) { - services.AddSingleton>(new UserStore((IdentityDbContext)context)); + services.AddSingleton>(new UserStore((IdentityDbContext)context)); } protected override void AddRoleStore(IServiceCollection services, object context = null) { - services.AddSingleton>(new RoleStore, IdentityRoleClaim>((IdentityDbContext)context)); + services.AddSingleton>(new RoleStore((IdentityDbContext)context)); } [Fact] @@ -185,20 +193,6 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); } - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task CreateUserSetsCreateDate() - { - var manager = CreateManager(); - var guid = Guid.NewGuid().ToString(); - var user = new IdentityUser { UserName = "New" + guid }; - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.NotNull(await manager.GetCreateDateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); - } - [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] [OSSkipCondition(OperatingSystems.Linux)] @@ -428,4 +422,6 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test protected override Expression> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName); } + + public class ApplicationUser : IdentityUser { } } diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreV1Test.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreV1Test.cs deleted file mode 100644 index 33a97f944c..0000000000 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreV1Test.cs +++ /dev/null @@ -1,418 +0,0 @@ -// 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.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity.Test; -using Microsoft.AspNetCore.Testing.xunit; -using Microsoft.AspNetCore.Testing; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test -{ - internal class UserStoreV1 : UserStoreV1, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim> - { - public UserStoreV1(IdentityDbContext context, IdentityErrorDescriber describer = null) : base(context, describer ?? new IdentityErrorDescriber()) { } - } - - public class UserStoreV1Test : IdentitySpecificationTestBase, IClassFixture - { - private readonly ScratchDatabaseFixture _fixture; - - public UserStoreV1Test(ScratchDatabaseFixture fixture) - { - _fixture = fixture; - } - - protected override bool ShouldSkipDbTests() - => TestPlatformHelper.IsMono || !TestPlatformHelper.IsWindows; - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public void CanCreateUserUsingEF() - { - using (var db = CreateContext()) - { - var guid = Guid.NewGuid().ToString(); - db.Users.Add(new IdentityUser { Id = guid, UserName = guid }); - db.SaveChanges(); - Assert.True(db.Users.Any(u => u.UserName == guid)); - Assert.NotNull(db.Users.FirstOrDefault(u => u.UserName == guid)); - } - } - - public IdentityDbContext CreateContext(bool delete = false) - { - var db = DbUtil.Create(_fixture.ConnectionString); - db.Version = IdentityStoreOptions.Version1_0; - if (delete) - { - db.Database.EnsureDeleted(); - } - db.Database.EnsureCreated(); - return db; - } - - protected override object CreateTestContext() - { - return CreateContext(); - } - - protected override void AddUserStore(IServiceCollection services, object context = null) - { - services.AddSingleton>(new UserStoreV1((IdentityDbContext)context)); - } - - protected override void AddRoleStore(IServiceCollection services, object context = null) - { - services.AddSingleton>(new RoleStoreV1, IdentityRoleClaim>((IdentityDbContext)context)); - } - - [Fact] - public async Task SqlUserStoreMethodsThrowWhenDisposedTest() - { - var store = new UserStore(new IdentityDbContext(new DbContextOptionsBuilder().Options)); - store.Dispose(); - await Assert.ThrowsAsync(async () => await store.AddClaimsAsync(null, null)); - await Assert.ThrowsAsync(async () => await store.AddLoginAsync(null, null)); - await Assert.ThrowsAsync(async () => await store.AddToRoleAsync(null, null)); - await Assert.ThrowsAsync(async () => await store.GetClaimsAsync(null)); - await Assert.ThrowsAsync(async () => await store.GetLoginsAsync(null)); - await Assert.ThrowsAsync(async () => await store.GetRolesAsync(null)); - await Assert.ThrowsAsync(async () => await store.IsInRoleAsync(null, null)); - await Assert.ThrowsAsync(async () => await store.RemoveClaimsAsync(null, null)); - await Assert.ThrowsAsync(async () => await store.RemoveLoginAsync(null, null, null)); - await Assert.ThrowsAsync( - async () => await store.RemoveFromRoleAsync(null, null)); - await Assert.ThrowsAsync(async () => await store.RemoveClaimsAsync(null, null)); - await Assert.ThrowsAsync(async () => await store.ReplaceClaimAsync(null, null, null)); - await Assert.ThrowsAsync(async () => await store.FindByLoginAsync(null, null)); - await Assert.ThrowsAsync(async () => await store.FindByIdAsync(null)); - await Assert.ThrowsAsync(async () => await store.FindByNameAsync(null)); - await Assert.ThrowsAsync(async () => await store.CreateAsync(null)); - await Assert.ThrowsAsync(async () => await store.UpdateAsync(null)); - await Assert.ThrowsAsync(async () => await store.DeleteAsync(null)); - await Assert.ThrowsAsync( - async () => await store.SetEmailConfirmedAsync(null, true)); - await Assert.ThrowsAsync(async () => await store.GetEmailConfirmedAsync(null)); - await Assert.ThrowsAsync( - async () => await store.SetPhoneNumberConfirmedAsync(null, true)); - await Assert.ThrowsAsync( - async () => await store.GetPhoneNumberConfirmedAsync(null)); - } - - [Fact] - public async Task UserStorePublicNullCheckTest() - { - Assert.Throws("context", () => new UserStore(null)); - var store = new UserStore(new IdentityDbContext(new DbContextOptionsBuilder().Options)); - await Assert.ThrowsAsync("user", async () => await store.GetUserIdAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.GetUserNameAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.SetUserNameAsync(null, null)); - await Assert.ThrowsAsync("user", async () => await store.CreateAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.UpdateAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.DeleteAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.AddClaimsAsync(null, null)); - await Assert.ThrowsAsync("user", async () => await store.ReplaceClaimAsync(null, null, null)); - await Assert.ThrowsAsync("user", async () => await store.RemoveClaimsAsync(null, null)); - await Assert.ThrowsAsync("user", async () => await store.GetClaimsAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.GetLoginsAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.GetRolesAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.AddLoginAsync(null, null)); - await - Assert.ThrowsAsync("user", async () => await store.RemoveLoginAsync(null, null, null)); - await Assert.ThrowsAsync("user", async () => await store.AddToRoleAsync(null, null)); - await - Assert.ThrowsAsync("user", - async () => await store.RemoveFromRoleAsync(null, null)); - await Assert.ThrowsAsync("user", async () => await store.IsInRoleAsync(null, null)); - await Assert.ThrowsAsync("user", async () => await store.GetPasswordHashAsync(null)); - await - Assert.ThrowsAsync("user", - async () => await store.SetPasswordHashAsync(null, null)); - await Assert.ThrowsAsync("user", async () => await store.GetSecurityStampAsync(null)); - await Assert.ThrowsAsync("user", - async () => await store.SetSecurityStampAsync(null, null)); - await Assert.ThrowsAsync("login", async () => await store.AddLoginAsync(new IdentityUser("fake"), null)); - await Assert.ThrowsAsync("claims", - async () => await store.AddClaimsAsync(new IdentityUser("fake"), null)); - await Assert.ThrowsAsync("claims", - async () => await store.RemoveClaimsAsync(new IdentityUser("fake"), null)); - await Assert.ThrowsAsync("user", async () => await store.GetEmailConfirmedAsync(null)); - await Assert.ThrowsAsync("user", - async () => await store.SetEmailConfirmedAsync(null, true)); - await Assert.ThrowsAsync("user", async () => await store.GetEmailAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.SetEmailAsync(null, null)); - await Assert.ThrowsAsync("user", async () => await store.GetPhoneNumberAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.SetPhoneNumberAsync(null, null)); - await Assert.ThrowsAsync("user", - async () => await store.GetPhoneNumberConfirmedAsync(null)); - await Assert.ThrowsAsync("user", - async () => await store.SetPhoneNumberConfirmedAsync(null, true)); - await Assert.ThrowsAsync("user", async () => await store.GetTwoFactorEnabledAsync(null)); - await Assert.ThrowsAsync("user", - async () => await store.SetTwoFactorEnabledAsync(null, true)); - await Assert.ThrowsAsync("user", async () => await store.GetAccessFailedCountAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.GetLockoutEnabledAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.SetLockoutEnabledAsync(null, false)); - await Assert.ThrowsAsync("user", async () => await store.GetLockoutEndDateAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.SetLockoutEndDateAsync(null, new DateTimeOffset())); - await Assert.ThrowsAsync("user", async () => await store.ResetAccessFailedCountAsync(null)); - await Assert.ThrowsAsync("user", async () => await store.IncrementAccessFailedCountAsync(null)); - await Assert.ThrowsAsync("normalizedRoleName", async () => await store.AddToRoleAsync(new IdentityUser("fake"), null)); - await Assert.ThrowsAsync("normalizedRoleName", async () => await store.RemoveFromRoleAsync(new IdentityUser("fake"), null)); - await Assert.ThrowsAsync("normalizedRoleName", async () => await store.IsInRoleAsync(new IdentityUser("fake"), null)); - await Assert.ThrowsAsync("normalizedRoleName", async () => await store.AddToRoleAsync(new IdentityUser("fake"), "")); - await Assert.ThrowsAsync("normalizedRoleName", async () => await store.RemoveFromRoleAsync(new IdentityUser("fake"), "")); - await Assert.ThrowsAsync("normalizedRoleName", async () => await store.IsInRoleAsync(new IdentityUser("fake"), "")); - } - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task CanCreateUsingManager() - { - var manager = CreateManager(); - var guid = Guid.NewGuid().ToString(); - var user = new IdentityUser { UserName = "New" + guid }; - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); - } - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task TwoUsersSamePasswordDifferentHash() - { - var manager = CreateManager(); - var userA = new IdentityUser(Guid.NewGuid().ToString()); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(userA, "password")); - var userB = new IdentityUser(Guid.NewGuid().ToString()); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(userB, "password")); - - Assert.NotEqual(userA.PasswordHash, userB.PasswordHash); - } - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task AddUserToUnknownRoleFails() - { - var manager = CreateManager(); - var u = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(u)); - await Assert.ThrowsAsync( - async () => await manager.AddToRoleAsync(u, "bogus")); - } - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task ConcurrentUpdatesWillFail() - { - var user = CreateTestUser(); - using (var db = CreateContext()) - { - var manager = CreateManager(db); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - } - using (var db = CreateContext()) - using (var db2 = CreateContext()) - { - var manager1 = CreateManager(db); - var manager2 = CreateManager(db2); - var user1 = await manager1.FindByIdAsync(user.Id); - var user2 = await manager2.FindByIdAsync(user.Id); - Assert.NotNull(user1); - Assert.NotNull(user2); - Assert.NotSame(user1, user2); - user1.UserName = Guid.NewGuid().ToString(); - user2.UserName = Guid.NewGuid().ToString(); - IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(user1)); - IdentityResultAssert.IsFailure(await manager2.UpdateAsync(user2), new IdentityErrorDescriber().ConcurrencyFailure()); - } - } - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task ConcurrentUpdatesWillFailWithDetachedUser() - { - var user = CreateTestUser(); - using (var db = CreateContext()) - { - var manager = CreateManager(db); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - } - using (var db = CreateContext()) - using (var db2 = CreateContext()) - { - var manager1 = CreateManager(db); - var manager2 = CreateManager(db2); - var user2 = await manager2.FindByIdAsync(user.Id); - Assert.NotNull(user2); - Assert.NotSame(user, user2); - user.UserName = Guid.NewGuid().ToString(); - user2.UserName = Guid.NewGuid().ToString(); - IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(user)); - IdentityResultAssert.IsFailure(await manager2.UpdateAsync(user2), new IdentityErrorDescriber().ConcurrencyFailure()); - } - } - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task DeleteAModifiedUserWillFail() - { - var user = CreateTestUser(); - using (var db = CreateContext()) - { - var manager = CreateManager(db); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - } - using (var db = CreateContext()) - using (var db2 = CreateContext()) - { - var manager1 = CreateManager(db); - var manager2 = CreateManager(db2); - var user1 = await manager1.FindByIdAsync(user.Id); - var user2 = await manager2.FindByIdAsync(user.Id); - Assert.NotNull(user1); - Assert.NotNull(user2); - Assert.NotSame(user1, user2); - user1.UserName = Guid.NewGuid().ToString(); - IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(user1)); - IdentityResultAssert.IsFailure(await manager2.DeleteAsync(user2), new IdentityErrorDescriber().ConcurrencyFailure()); - } - } - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task ConcurrentRoleUpdatesWillFail() - { - var role = new IdentityRole(Guid.NewGuid().ToString()); - using (var db = CreateContext()) - { - var manager = CreateRoleManager(db); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - } - using (var db = CreateContext()) - using (var db2 = CreateContext()) - { - var manager1 = CreateRoleManager(db); - var manager2 = CreateRoleManager(db2); - var role1 = await manager1.FindByIdAsync(role.Id); - var role2 = await manager2.FindByIdAsync(role.Id); - Assert.NotNull(role1); - Assert.NotNull(role2); - Assert.NotSame(role1, role2); - role1.Name = Guid.NewGuid().ToString(); - role2.Name = Guid.NewGuid().ToString(); - IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(role1)); - IdentityResultAssert.IsFailure(await manager2.UpdateAsync(role2), new IdentityErrorDescriber().ConcurrencyFailure()); - } - } - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task ConcurrentRoleUpdatesWillFailWithDetachedRole() - { - var role = new IdentityRole(Guid.NewGuid().ToString()); - using (var db = CreateContext()) - { - var manager = CreateRoleManager(db); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - } - using (var db = CreateContext()) - using (var db2 = CreateContext()) - { - var manager1 = CreateRoleManager(db); - var manager2 = CreateRoleManager(db2); - var role2 = await manager2.FindByIdAsync(role.Id); - Assert.NotNull(role); - Assert.NotNull(role2); - Assert.NotSame(role, role2); - role.Name = Guid.NewGuid().ToString(); - role2.Name = Guid.NewGuid().ToString(); - IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(role)); - IdentityResultAssert.IsFailure(await manager2.UpdateAsync(role2), new IdentityErrorDescriber().ConcurrencyFailure()); - } - } - - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - [OSSkipCondition(OperatingSystems.Linux)] - [OSSkipCondition(OperatingSystems.MacOSX)] - public async Task DeleteAModifiedRoleWillFail() - { - var role = new IdentityRole(Guid.NewGuid().ToString()); - using (var db = CreateContext()) - { - var manager = CreateRoleManager(db); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - } - using (var db = CreateContext()) - using (var db2 = CreateContext()) - { - var manager1 = CreateRoleManager(db); - var manager2 = CreateRoleManager(db2); - var role1 = await manager1.FindByIdAsync(role.Id); - var role2 = await manager2.FindByIdAsync(role.Id); - Assert.NotNull(role1); - Assert.NotNull(role2); - Assert.NotSame(role1, role2); - role1.Name = Guid.NewGuid().ToString(); - IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(role1)); - IdentityResultAssert.IsFailure(await manager2.DeleteAsync(role2), new IdentityErrorDescriber().ConcurrencyFailure()); - } - } - - protected override IdentityUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", - bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false) - { - return new IdentityUser - { - UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()), - Email = email, - PhoneNumber = phoneNumber, - LockoutEnabled = lockoutEnabled, - LockoutEnd = lockoutEnd - }; - } - - protected override IdentityRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false) - { - var roleName = useRoleNamePrefixAsRoleName ? roleNamePrefix : string.Format("{0}{1}", roleNamePrefix, Guid.NewGuid()); - return new IdentityRole(roleName); - } - - protected override void SetUserPasswordHash(IdentityUser user, string hashedPassword) - { - user.PasswordHash = hashedPassword; - } - - protected override Expression> UserNameEqualsPredicate(string userName) => u => u.UserName == userName; - - protected override Expression> RoleNameEqualsPredicate(string roleName) => r => r.Name == roleName; - - protected override Expression> RoleNameStartsWithPredicate(string roleName) => r => r.Name.StartsWith(roleName); - - protected override Expression> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName); - } -} diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs index c9466abee7..89ba53590b 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs @@ -230,12 +230,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test { public string LoginContext { get; set; } - public UserStoreWithGenerics(ContextWithGenerics context, string loginContext) : base(context, new IdentityErrorDescriber()) + public UserStoreWithGenerics(ContextWithGenerics context, string loginContext) : base(context) { LoginContext = loginContext; } - public override IdentityUserRoleWithDate CreateUserRole(IdentityUserWithGenerics user, MyIdentityRole role) + protected override IdentityUserRoleWithDate CreateUserRole(IdentityUserWithGenerics user, MyIdentityRole role) { return new IdentityUserRoleWithDate() { @@ -245,12 +245,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test }; } - public override IdentityUserClaimWithIssuer CreateUserClaim(IdentityUserWithGenerics user, Claim claim) + protected override IdentityUserClaimWithIssuer CreateUserClaim(IdentityUserWithGenerics user, Claim claim) { return new IdentityUserClaimWithIssuer { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value, Issuer = claim.Issuer }; } - public override IdentityUserLoginWithContext CreateUserLogin(IdentityUserWithGenerics user, UserLoginInfo login) + protected override IdentityUserLoginWithContext CreateUserLogin(IdentityUserWithGenerics user, UserLoginInfo login) { return new IdentityUserLoginWithContext { @@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test }; } - public override IdentityUserTokenWithStuff CreateUserToken(IdentityUserWithGenerics user, string loginProvider, string name, string value) + protected override IdentityUserTokenWithStuff CreateUserToken(IdentityUserWithGenerics user, string loginProvider, string name, string value) { return new IdentityUserTokenWithStuff { @@ -283,7 +283,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test _loginContext = loginContext; } - public override IdentityRoleClaimWithIssuer CreateRoleClaim(MyIdentityRole role, Claim claim) + protected override IdentityRoleClaimWithIssuer CreateRoleClaim(MyIdentityRole role, Claim claim) { return new IdentityRoleClaimWithIssuer { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value, Issuer = claim.Issuer }; } diff --git a/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryStore.cs b/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryStore.cs index 83d4106c61..ea7424fbf3 100644 --- a/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryStore.cs +++ b/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryStore.cs @@ -26,8 +26,7 @@ namespace Microsoft.AspNetCore.Identity.InMemory IRoleClaimStore, IUserAuthenticationTokenStore, IUserAuthenticatorKeyStore, - IUserTwoFactorRecoveryCodeStore, - IUserActivityStore + IUserTwoFactorRecoveryCodeStore where TRole : TestRole where TUser : TestUser { @@ -584,39 +583,6 @@ namespace Microsoft.AspNetCore.Identity.InMemory return false; } - public Task SetLastPasswordChangeDateAsync(TUser user, DateTimeOffset? changeDate, CancellationToken cancellationToken) - { - user.LastPasswordChangeDate = changeDate; - return Task.FromResult(0); - } - - public Task GetLastPasswordChangeDateAsync(TUser user, CancellationToken cancellationToken) - { - return Task.FromResult(user.LastPasswordChangeDate); - } - - public Task SetCreateDateAsync(TUser user, DateTimeOffset? creationDate, CancellationToken cancellationToken) - { - user.CreateDate = creationDate; - return Task.FromResult(0); - } - - public Task GetCreateDateAsync(TUser user, CancellationToken cancellationToken) - { - return Task.FromResult(user.CreateDate); - } - - public Task SetLastSignInDateAsync(TUser user, DateTimeOffset? lastSignIn, CancellationToken cancellationToken) - { - user.LastSignInDate = lastSignIn; - return Task.FromResult(0); - } - - public Task GetLastSignInDateAsync(TUser user, CancellationToken cancellationToken) - { - return Task.FromResult(user.LastSignInDate); - } - public IQueryable Roles { get { return _roles.Values.AsQueryable(); } diff --git a/test/Microsoft.AspNetCore.Identity.InMemory.Test/Microsoft.AspNetCore.Identity.InMemory.Test.csproj b/test/Microsoft.AspNetCore.Identity.InMemory.Test/Microsoft.AspNetCore.Identity.InMemory.Test.csproj index cab1aad3ea..f48a04e335 100644 --- a/test/Microsoft.AspNetCore.Identity.InMemory.Test/Microsoft.AspNetCore.Identity.InMemory.Test.csproj +++ b/test/Microsoft.AspNetCore.Identity.InMemory.Test/Microsoft.AspNetCore.Identity.InMemory.Test.csproj @@ -27,7 +27,4 @@ - - - diff --git a/test/Microsoft.AspNetCore.Identity.Test/SecurityStampValidatorTest.cs b/test/Microsoft.AspNetCore.Identity.Test/SecurityStampValidatorTest.cs index 5c568b2be8..8f56000d37 100644 --- a/test/Microsoft.AspNetCore.Identity.Test/SecurityStampValidatorTest.cs +++ b/test/Microsoft.AspNetCore.Identity.Test/SecurityStampValidatorTest.cs @@ -197,7 +197,7 @@ namespace Microsoft.AspNetCore.Identity.Test var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock().Object); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny())).Throws(new Exception("Shouldn't be called")); - signInManager.Setup(s => s.SignInAsync(user, false, null, It.IsAny())).Throws(new Exception("Shouldn't be called")); + signInManager.Setup(s => s.SignInAsync(user, false, null)).Throws(new Exception("Shouldn't be called")); var services = new ServiceCollection(); services.AddSingleton(options.Object); services.AddSingleton(signInManager.Object); diff --git a/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs index 3da9d31d78..a5bf8a134f 100644 --- a/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNetCore.Identity.Test/SignInManagerTest.cs @@ -202,8 +202,6 @@ namespace Microsoft.AspNetCore.Identity.Test manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable(); manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable(); - manager.Setup(m => m.SupportsUserActivity).Returns(true).Verifiable(); - manager.Setup(m => m.UpdateLastSignInDateAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); var context = new DefaultHttpContext(); var auth = MockAuth(context); @@ -228,8 +226,6 @@ namespace Microsoft.AspNetCore.Identity.Test manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable(); manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable(); - manager.Setup(m => m.SupportsUserActivity).Returns(true).Verifiable(); - manager.Setup(m => m.UpdateLastSignInDateAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); var context = new DefaultHttpContext(); var auth = MockAuth(context); @@ -256,8 +252,6 @@ namespace Microsoft.AspNetCore.Identity.Test manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); - manager.Setup(m => m.SupportsUserActivity).Returns(true).Verifiable(); - manager.Setup(m => m.UpdateLastSignInDateAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); var context = new DefaultHttpContext(); var auth = MockAuth(context); @@ -381,8 +375,6 @@ namespace Microsoft.AspNetCore.Identity.Test manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable(); manager.Setup(m => m.VerifyTwoFactorTokenAsync(user, providerName ?? TokenOptions.DefaultAuthenticatorProvider, code)).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); - manager.Setup(m => m.SupportsUserActivity).Returns(true).Verifiable(); - manager.Setup(m => m.UpdateLastSignInDateAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); var context = new DefaultHttpContext(); var auth = MockAuth(context); @@ -427,8 +419,6 @@ namespace Microsoft.AspNetCore.Identity.Test var manager = SetupUserManager(user); manager.Setup(m => m.SupportsUserLockout).Returns(supportsLockout).Verifiable(); manager.Setup(m => m.RedeemTwoFactorRecoveryCodeAsync(user, bypassCode)).ReturnsAsync(IdentityResult.Success).Verifiable(); - manager.Setup(m => m.SupportsUserActivity).Returns(true).Verifiable(); - manager.Setup(m => m.UpdateLastSignInDateAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); if (supportsLockout) { manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); @@ -466,15 +456,11 @@ namespace Microsoft.AspNetCore.Identity.Test } [Theory] - [InlineData(true, true, true)] - [InlineData(true, false, true)] - [InlineData(false, true, true)] - [InlineData(false, false, true)] - [InlineData(true, true, false)] - [InlineData(true, false, false)] - [InlineData(false, true, false)] - [InlineData(false, false, false)] - public async Task CanExternalSignIn(bool isPersistent, bool supportsLockout, bool supportsActivity) + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task CanExternalSignIn(bool isPersistent, bool supportsLockout) { // Setup var user = new TestUser { UserName = "Foo" }; @@ -487,11 +473,6 @@ namespace Microsoft.AspNetCore.Identity.Test manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable(); } manager.Setup(m => m.FindByLoginAsync(loginProvider, providerKey)).ReturnsAsync(user).Verifiable(); - if (supportsActivity) - { - manager.Setup(m => m.SupportsUserActivity).Returns(true).Verifiable(); - manager.Setup(m => m.UpdateLastSignInDateAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); - } var context = new DefaultHttpContext(); var auth = MockAuth(context); @@ -537,7 +518,8 @@ namespace Microsoft.AspNetCore.Identity.Test { CallBase = true }; //signInManager.Setup(s => s.SignInAsync(user, It.Is(p => p.IsPersistent == isPersistent), //externalLogin? loginProvider : null)).Returns(Task.FromResult(0)).Verifiable(); - signInManager.Setup(s => s.SignInAsync(user, It.IsAny(), /*authmethod*/null, /*updateLastSignIn*/false)).Returns(Task.FromResult(0)).Verifiable(); + signInManager.Setup(s => s.SignInAsync(user, It.IsAny(), null)).Returns(Task.FromResult(0)).Verifiable(); + signInManager.Object.Context = context; // Act await signInManager.Object.RefreshSignInAsync(user); @@ -548,39 +530,23 @@ namespace Microsoft.AspNetCore.Identity.Test } [Theory] - [InlineData(true, true, true, true, true)] - [InlineData(true, true, false, true, true)] - [InlineData(true, false, true, true, true)] - [InlineData(true, false, false, true, true)] - [InlineData(false, true, true, true, true)] - [InlineData(false, true, false, true, true)] - [InlineData(false, false, true, true, true)] - [InlineData(false, false, false, true, true)] - [InlineData(true, true, true, false, true)] - [InlineData(true, true, false, false, true)] - [InlineData(true, false, true, false, true)] - [InlineData(true, false, false, false, true)] - [InlineData(false, true, true, false, true)] - [InlineData(false, true, false, false, true)] - [InlineData(false, false, true, false, true)] - [InlineData(false, false, false, false, true)] - [InlineData(true, true, true, true, false)] - [InlineData(true, true, false, true, false)] - [InlineData(true, false, true, true, false)] - [InlineData(true, false, false, true, false)] - [InlineData(false, true, true, true, false)] - [InlineData(false, true, false, true, false)] - [InlineData(false, false, true, true, false)] - [InlineData(false, false, false, true, false)] - [InlineData(true, true, true, false, false)] - [InlineData(true, true, false, false, false)] - [InlineData(true, false, true, false, false)] - [InlineData(true, false, false, false, false)] - [InlineData(false, true, true, false, false)] - [InlineData(false, true, false, false, false)] - [InlineData(false, false, true, false, false)] - [InlineData(false, false, false, false, false)] - public async Task CanTwoFactorSignIn(bool isPersistent, bool supportsLockout, bool externalLogin, bool rememberClient, bool supportsUserActivity) + [InlineData(true, true, true, true)] + [InlineData(true, true, false, true)] + [InlineData(true, false, true, true)] + [InlineData(true, false, false, true)] + [InlineData(false, true, true, true)] + [InlineData(false, true, false, true)] + [InlineData(false, false, true, true)] + [InlineData(false, false, false, true)] + [InlineData(true, true, true, false)] + [InlineData(true, true, false, false)] + [InlineData(true, false, true, false)] + [InlineData(true, false, false, false)] + [InlineData(false, true, true, false)] + [InlineData(false, true, false, false)] + [InlineData(false, false, true, false)] + [InlineData(false, false, false, false)] + public async Task CanTwoFactorSignIn(bool isPersistent, bool supportsLockout, bool externalLogin, bool rememberClient) { // Setup var user = new TestUser { UserName = "Foo" }; @@ -593,11 +559,6 @@ namespace Microsoft.AspNetCore.Identity.Test manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable(); manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); } - if (supportsUserActivity) - { - manager.Setup(m => m.SupportsUserActivity).Returns(true).Verifiable(); - manager.Setup(m => m.UpdateLastSignInDateAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable(); - } manager.Setup(m => m.VerifyTwoFactorTokenAsync(user, provider, code)).ReturnsAsync(true).Verifiable(); var context = new DefaultHttpContext(); var auth = MockAuth(context); diff --git a/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs index 9768362c14..92ff64558f 100644 --- a/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs @@ -580,17 +580,6 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.Throws(() => manager.Users.Count()); } - [Fact] - public async Task UsersActivityMethodsFailWhenStoreNotImplemented() - { - var manager = MockHelpers.TestUserManager(new NoopUserStore()); - Assert.False(manager.SupportsUserActivity); - await Assert.ThrowsAsync(() => manager.UpdateLastSignInDateAsync(null)); - await Assert.ThrowsAsync(() => manager.GetLastSignInDateAsync(null)); - await Assert.ThrowsAsync(() => manager.GetLastPasswordChangeDateAsync(null)); - await Assert.ThrowsAsync(() => manager.GetCreateDateAsync(null)); - } - [Fact] public async Task UsersEmailMethodsFailWhenStoreNotImplemented() { @@ -709,6 +698,146 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.ThrowsAsync(() => manager.GenerateUserTokenAsync(new TestUser(), "A", "purpose")); } + [Fact] + public void TOTPTest() + { + //var verify = new TotpAuthenticatorVerification(); + //var secret = "abcdefghij"; + //var secret = Base32.FromBase32(authKey); + +// Assert.Equal(bytes, secret); + + //var code = verify.VerifyCode(secret, -1); + //Assert.Equal(code, 287004); + + + //var bytes = new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)'!', (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF }; + //var base32 = Base32.ToBase32(bytes); + // var code = Rfc6238AuthenticationService.GenerateCode(bytes); + // Assert.Equal(Rfc6238AuthenticationService.GenerateCode(bytes), Rfc6238AuthenticationService.CalculateOneTimePassword(new HMACSHA1(bytes))); + //Assert.True(Rfc6238AuthenticationService.ValidateCode(bytes, code)); + } + + public static byte[] ToBytes(string input) + { + if (string.IsNullOrEmpty(input)) + { + throw new ArgumentNullException("input"); + } + + input = input.TrimEnd('='); //remove padding characters + int byteCount = input.Length * 5 / 8; //this must be TRUNCATED + byte[] returnArray = new byte[byteCount]; + + byte curByte = 0, bitsRemaining = 8; + int mask = 0, arrayIndex = 0; + + foreach (char c in input) + { + int cValue = CharToValue(c); + + if (bitsRemaining > 5) + { + mask = cValue << (bitsRemaining - 5); + curByte = (byte)(curByte | mask); + bitsRemaining -= 5; + } + else + { + mask = cValue >> (5 - bitsRemaining); + curByte = (byte)(curByte | mask); + returnArray[arrayIndex++] = curByte; + curByte = (byte)(cValue << (3 + bitsRemaining)); + bitsRemaining += 3; + } + } + + //if we didn't end with a full byte + if (arrayIndex != byteCount) + { + returnArray[arrayIndex] = curByte; + } + + return returnArray; + } + + public static string ToString(byte[] input) + { + if (input == null || input.Length == 0) + { + throw new ArgumentNullException("input"); + } + + int charCount = (int)Math.Ceiling(input.Length / 5d) * 8; + char[] returnArray = new char[charCount]; + + byte nextChar = 0, bitsRemaining = 5; + int arrayIndex = 0; + + foreach (byte b in input) + { + nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining))); + returnArray[arrayIndex++] = ValueToChar(nextChar); + + if (bitsRemaining < 4) + { + nextChar = (byte)((b >> (3 - bitsRemaining)) & 31); + returnArray[arrayIndex++] = ValueToChar(nextChar); + bitsRemaining += 5; + } + + bitsRemaining -= 3; + nextChar = (byte)((b << bitsRemaining) & 31); + } + + //if we didn't end with a full char + if (arrayIndex != charCount) + { + returnArray[arrayIndex++] = ValueToChar(nextChar); + while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding + } + + return new string(returnArray); + } + + private static int CharToValue(char c) + { + var value = (int)c; + + //65-90 == uppercase letters + if (value < 91 && value > 64) + { + return value - 65; + } + //50-55 == numbers 2-7 + if (value < 56 && value > 49) + { + return value - 24; + } + //97-122 == lowercase letters + if (value < 123 && value > 96) + { + return value - 97; + } + + throw new ArgumentException("Character is not a Base32 character.", "c"); + } + + private static char ValueToChar(byte b) + { + if (b < 26) + { + return (char)(b + 65); + } + + if (b < 32) + { + return (char)(b + 24); + } + + throw new ArgumentException("Byte is not a value Base32 value.", "b"); + } + [Fact] public void UserManagerWillUseTokenProviderInstanceOverDefaults() { diff --git a/test/Shared/TestUser.cs b/test/Shared/TestUser.cs index 1e13a3cb93..d08e747a28 100644 --- a/test/Shared/TestUser.cs +++ b/test/Shared/TestUser.cs @@ -124,10 +124,6 @@ namespace Microsoft.AspNetCore.Identity.Test /// public virtual int AccessFailedCount { get; set; } - public virtual DateTimeOffset? CreateDate { get; set; } - public virtual DateTimeOffset? LastSignInDate { get; set; } - public virtual DateTimeOffset? LastPasswordChangeDate { get; set; } - /// /// Navigation property ///