From 104f2161915b1a4e7d0013f3de3b8d9a212a5d27 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 23 May 2016 13:05:12 -0700 Subject: [PATCH] Restore support for EF User store generics --- .../Properties/launchSettings.json | 19 ++ .../IdentityDbContext.cs | 58 +++- ...dentityEntityFrameworkBuilderExtensions.cs | 13 +- .../IdentityRole.cs | 20 +- .../IdentityRoleClaim.cs | 12 + .../IdentityUser.cs | 19 +- .../IdentityUserClaim.cs | 20 ++ .../RoleStore.cs | 42 ++- .../UserStore.cs | 155 ++++++--- .../InMemoryContext.cs | 22 +- .../InMemoryEFUserStoreTest.cs | 1 - .../InMemoryStoreWithGenericsTest.cs | 320 ++++++++++++++++++ .../UserStoreTest.cs | 1 - .../UserStoreWithGenericsTest.cs | 316 +++++++++++++++++ 14 files changed, 943 insertions(+), 75 deletions(-) create mode 100644 samples/IdentitySample.Mvc/Properties/launchSettings.json create mode 100644 test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs create mode 100644 test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs diff --git a/samples/IdentitySample.Mvc/Properties/launchSettings.json b/samples/IdentitySample.Mvc/Properties/launchSettings.json new file mode 100644 index 0000000000..cf11afc04f --- /dev/null +++ b/samples/IdentitySample.Mvc/Properties/launchSettings.json @@ -0,0 +1,19 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:28248/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNET_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs index 038587f329..af1b1b24fa 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs @@ -3,7 +3,6 @@ using System; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { @@ -52,7 +51,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// The type of user objects. /// The type of role objects. /// The type of the primary key for users and roles. - public class IdentityDbContext : DbContext + public class IdentityDbContext : IdentityDbContext, IdentityUserRole, IdentityUserLogin, IdentityRoleClaim, IdentityUserToken> where TUser : IdentityUser where TRole : IdentityRole where TKey : IEquatable @@ -69,6 +68,41 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// protected IdentityDbContext() { } + } + + /// + /// Base class for the Entity Framework database context used for identity. + /// + /// The type of user objects. + /// The type of role objects. + /// The type of the primary key for users and roles. + /// The type of the user claim object. + /// The type of the user role object. + /// The type of the user login object. + /// The type of the role claim object. + /// The type of the user token object. + public abstract class IdentityDbContext : DbContext + where TUser : IdentityUser + where TRole : IdentityRole + where TKey : IEquatable + where TUserClaim : IdentityUserClaim + where TUserRole : IdentityUserRole + where TUserLogin : IdentityUserLogin + where TRoleClaim : IdentityRoleClaim + where TUserToken : IdentityUserToken + { + /// + /// Initializes a new instance of . + /// + /// The options to be used by a . + public IdentityDbContext(DbContextOptions options) : base(options) + { } + + /// + /// Initializes a new instance of the class. + /// + protected IdentityDbContext() + { } /// /// Gets or sets the of Users. @@ -78,22 +112,22 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// /// Gets or sets the of User claims. /// - public DbSet> UserClaims { get; set; } + public DbSet UserClaims { get; set; } /// /// Gets or sets the of User logins. /// - public DbSet> UserLogins { get; set; } + public DbSet UserLogins { get; set; } /// /// Gets or sets the of User roles. /// - public DbSet> UserRoles { get; set; } + public DbSet UserRoles { get; set; } /// /// Gets or sets the of User tokens. /// - public DbSet> UserTokens { get; set; } + public DbSet UserTokens { get; set; } /// /// Gets or sets the of roles. @@ -103,7 +137,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// /// Gets or sets the of role claims. /// - public DbSet> RoleClaims { get; set; } + public DbSet RoleClaims { get; set; } /// /// Configures the schema needed for the identity framework. @@ -144,31 +178,31 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore b.HasMany(r => r.Claims).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"); }); - builder.Entity>(b => + builder.Entity(b => { b.HasKey(l => new { l.LoginProvider, l.ProviderKey }); b.ToTable("AspNetUserLogins"); }); - builder.Entity>(b => + builder.Entity(b => { b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name }); b.ToTable("AspNetUserTokens"); diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs index e97780fd31..8a5476d305 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs @@ -43,16 +43,9 @@ namespace Microsoft.Extensions.DependencyInjection { Type userStoreType; Type roleStoreType; - if (keyType != null) - { - userStoreType = typeof(UserStore<,,,>).MakeGenericType(userType, roleType, contextType, keyType); - roleStoreType = typeof(RoleStore<,,>).MakeGenericType(roleType, contextType, keyType); - } - else - { - userStoreType = typeof(UserStore<,,>).MakeGenericType(userType, roleType, contextType); - roleStoreType = typeof(RoleStore<,>).MakeGenericType(roleType, contextType); - } + keyType = keyType ?? typeof(string); + userStoreType = typeof(UserStore<,,,>).MakeGenericType(userType, roleType, contextType, keyType); + roleStoreType = typeof(RoleStore<,,>).MakeGenericType(roleType, contextType, keyType); var services = new ServiceCollection(); services.AddScoped( diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityRole.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityRole.cs index 105a1d5419..7498e8ce4a 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityRole.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityRole.cs @@ -39,7 +39,21 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// Represents a role in the identity system /// /// The type used for the primary key for the role. - public class IdentityRole where TKey : IEquatable + public class IdentityRole : IdentityRole, IdentityRoleClaim> + where TKey : IEquatable + { + } + + /// + /// Represents a role in the identity system + /// + /// The type used for the primary key for the role. + /// The type used for user roles. + /// The type used for role claims. + public class IdentityRole + where TKey : IEquatable + where TUserRole : IdentityUserRole + where TRoleClaim : IdentityRoleClaim { /// /// Initializes a new instance of . @@ -58,12 +72,12 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// /// Navigation property for the users in this role. /// - public virtual ICollection> Users { get; } = new List>(); + public virtual ICollection Users { get; } = new List(); /// /// Navigation property for claims in this role. /// - public virtual ICollection> Claims { get; } = new List>(); + public virtual ICollection Claims { get; } = new List(); /// /// Gets or sets the primary key for this role. diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityRoleClaim.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityRoleClaim.cs index 063abb1e9c..d9a6672b0b 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityRoleClaim.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityRoleClaim.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Security.Claims; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { @@ -30,5 +31,16 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// Gets or sets the claim value for this claim. /// public virtual string ClaimValue { get; set; } + + public virtual Claim ToClaim() + { + return new Claim(ClaimType, ClaimValue); + } + + public virtual void InitializeFromClaim(Claim other) + { + ClaimType = other?.Type; + ClaimValue = other?.Value; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityUser.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityUser.cs index 8dd5ee7123..2341b8a0cb 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityUser.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityUser.cs @@ -39,7 +39,18 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// Represents a user in the identity system /// /// The type used for the primary key for the user. - public class IdentityUser where TKey : IEquatable + public class IdentityUser : IdentityUser, IdentityUserRole, IdentityUserLogin> + where TKey : IEquatable + { } + + /// + /// Represents a user in the identity system + /// + /// The type used for the primary key for the user. + /// The type representing a claim. + /// The type representing a user role. + /// The type representing a user external login. + public class IdentityUser where TKey : IEquatable { /// /// Initializes a new instance of . @@ -140,17 +151,17 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// /// Navigation property for the roles this user belongs to. /// - public virtual ICollection> Roles { get; } = new List>(); + public virtual ICollection Roles { get; } = new List(); /// /// Navigation property for the claims this user possesses. /// - public virtual ICollection> Claims { get; } = new List>(); + public virtual ICollection Claims { get; } = new List(); /// /// Navigation property for this users login accounts. /// - public virtual ICollection> Logins { get; } = new List>(); + public virtual ICollection Logins { get; } = new List(); /// /// Returns the username for this user. diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityUserClaim.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityUserClaim.cs index 26a7c7bbb6..2edeb6b33d 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityUserClaim.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityUserClaim.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Security.Claims; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { @@ -30,5 +31,24 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// Gets or sets the claim value for this claim. /// public virtual string ClaimValue { get; set; } + + /// + /// Converts the entity into a Claim instance. + /// + /// + public virtual Claim ToClaim() + { + return new Claim(ClaimType, ClaimValue); + } + + /// + /// Reads the type and value from the Claim. + /// + /// + public virtual void InitializeFromClaim(Claim claim) + { + ClaimType = claim.Type; + ClaimValue = claim.Value; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs index e69bb94fc7..60a40af476 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs @@ -40,12 +40,39 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// 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 : + public class RoleStore : RoleStore, IdentityRoleClaim>, IQueryableRoleStore, IRoleClaimStore where TRole : IdentityRole where TKey : IEquatable where TContext : DbContext + { + public RoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) + { + } + + protected override IdentityRoleClaim CreateRoleClaim(TRole role, Claim claim) + { + return new IdentityRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; + } + } + + /// + /// 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 abstract class RoleStore : + IQueryableRoleStore, + IRoleClaimStore + where TRole : IdentityRole + where TKey : IEquatable + where TContext : DbContext + where TUserRole : IdentityUserRole + where TRoleClaim : IdentityRoleClaim { public RoleStore(TContext context, IdentityErrorDescriber describer = null) { @@ -358,8 +385,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore throw new ArgumentNullException(nameof(claim)); } - RoleClaims.Add(new IdentityRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }); - + RoleClaims.Add(CreateRoleClaim(role, claim)); return Task.FromResult(false); } @@ -396,6 +422,14 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore get { return Context.Set(); } } - private DbSet> RoleClaims { get { return Context.Set>(); } } + private DbSet RoleClaims { get { return Context.Set(); } } + + /// + /// Creates a entity representing a role claim. + /// + /// + /// + /// + protected abstract TRoleClaim CreateRoleClaim(TRole role, Claim claim); } } diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs index 766ab658de..1300eac368 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs @@ -14,7 +14,7 @@ using Microsoft.EntityFrameworkCore; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { /// - /// Creates a new instance of a persistence store for users, using the default implementation + /// 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> @@ -26,25 +26,78 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// Creates a new instance of a persistence store for the specified user type. /// /// The type representing a user. - public class UserStore : UserStore + public class UserStore : UserStore where TUser : IdentityUser, new() { public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } /// - /// Creates a new instance of a persistence store for the specified user and role types. + /// 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, new() - where TRole : IdentityRole, new() + where TUser : IdentityUser + where TRole : IdentityRole where TContext : DbContext { 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> + where TUser : IdentityUser + where TRole : IdentityRole + where TContext : DbContext + where TKey : IEquatable + { + public UserStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } + + protected override IdentityUserRole CreateUserRole(TUser user, TRole role) + { + return new IdentityUserRole() + { + UserId = user.Id, + RoleId = role.Id + }; + } + + protected override IdentityUserClaim CreateUserClaim(TUser user, Claim claim) + { + var userClaim = new IdentityUserClaim { UserId = user.Id }; + userClaim.InitializeFromClaim(claim); + return userClaim; + } + + protected override IdentityUserLogin CreateUserLogin(TUser user, UserLoginInfo login) + { + return new IdentityUserLogin + { + UserId = user.Id, + ProviderKey = login.ProviderKey, + LoginProvider = login.LoginProvider, + ProviderDisplayName = login.ProviderDisplayName + }; + } + + protected override IdentityUserToken CreateUserToken(TUser user, string loginProvider, string name, string value) + { + return new IdentityUserToken + { + UserId = user.Id, + LoginProvider = loginProvider, + Name = name, + Value = value + }; + } + } /// /// Represents a new instance of a persistence store for the specified user and role types. @@ -53,7 +106,11 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// 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 : + /// The type representing a claim. + /// The type representing a user role. + /// The type representing a user external login. + /// The type representing a user token. + public abstract class UserStore : IUserLoginStore, IUserRoleStore, IUserClaimStore, @@ -65,12 +122,15 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore IQueryableUserStore, IUserTwoFactorStore, IUserAuthenticationTokenStore - where TUser : IdentityUser - where TRole : IdentityRole + where TUser : IdentityUser + where TRole : IdentityRole> where TContext : DbContext where TKey : IEquatable + where TUserClaim : IdentityUserClaim + where TUserRole : IdentityUserRole + where TUserLogin : IdentityUserLogin + where TUserToken : IdentityUserToken { - /// /// Creates a new instance of . /// @@ -98,6 +158,46 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// public IdentityErrorDescriber ErrorDescriber { get; 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(); } } + + /// + /// Creates a new entity to represent a user role. + /// + /// + /// + /// + protected abstract TUserRole CreateUserRole(TUser user, TRole role); + + /// + /// Create a new entity representing a user claim. + /// + /// + /// + /// + protected abstract TUserClaim CreateUserClaim(TUser user, Claim claim); + + /// + /// Create a new entity representing a user login. + /// + /// + /// + /// + protected abstract TUserLogin CreateUserLogin(TUser user, UserLoginInfo login); + + /// + /// Create a new entity representing a user token. + /// + /// + /// + /// + /// + /// + protected abstract TUserToken CreateUserToken(TUser user, string loginProvider, string name, string value); + /// /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. /// @@ -109,7 +209,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// Saves the current store. /// The used to propagate notifications that the operation should be canceled. /// The that represents the asynchronous operation. - private Task SaveChanges(CancellationToken cancellationToken) + protected Task SaveChanges(CancellationToken cancellationToken) { return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.FromResult(0); } @@ -418,8 +518,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.RoleNotFound, normalizedRoleName)); } - var ur = new IdentityUserRole { UserId = user.Id, RoleId = roleEntity.Id }; - UserRoles.Add(ur); + UserRoles.Add(CreateUserRole(user, roleEntity)); } /// @@ -520,12 +619,6 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore _disposed = true; } - 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>(); } } - /// /// Get the claims associated with the specified as an asynchronous operation. /// @@ -540,7 +633,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore throw new ArgumentNullException(nameof(user)); } - return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken); + return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => c.ToClaim()).ToListAsync(cancellationToken); } /// @@ -563,7 +656,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore } foreach (var claim in claims) { - UserClaims.Add(new IdentityUserClaim { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value }); + UserClaims.Add(CreateUserClaim(user, claim)); } return Task.FromResult(false); } @@ -648,14 +741,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { throw new ArgumentNullException(nameof(login)); } - var l = new IdentityUserLogin - { - UserId = user.Id, - ProviderKey = login.ProviderKey, - LoginProvider = login.LoginProvider, - ProviderDisplayName = login.ProviderDisplayName - }; - UserLogins.Add(l); + UserLogins.Add(CreateUserLogin(user, login)); return Task.FromResult(false); } @@ -676,8 +762,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { throw new ArgumentNullException(nameof(user)); } - var userId = user.Id; - var entry = await UserLogins.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.ProviderKey == providerKey, cancellationToken); + var entry = await UserLogins.SingleOrDefaultAsync(userLogin => userLogin.UserId.Equals(user.Id) && userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey, cancellationToken); if (entry != null) { UserLogins.Remove(entry); @@ -1198,7 +1283,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore return new List(); } - private Task> FindToken(TUser user, string loginProvider, string name, CancellationToken cancellationToken) + private Task FindToken(TUser user, string loginProvider, string name, CancellationToken cancellationToken) { var userId = user.Id; return UserTokens.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.Name == name, cancellationToken); @@ -1218,13 +1303,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore var token = await FindToken(user, loginProvider, name, cancellationToken); if (token == null) { - UserTokens.Add(new IdentityUserToken - { - UserId = user.Id, - LoginProvider = loginProvider, - Name = name, - Value = value - }); + UserTokens.Add(CreateUserToken(user, loginProvider, name, value)); } else { diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs index 36b8e30152..bfbf9fdc6c 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs @@ -3,7 +3,6 @@ using System; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test { @@ -22,7 +21,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test { } } - public class InMemoryContext : IdentityDbContext + public class InMemoryContext : IdentityDbContext where TUser : IdentityUser where TRole : IdentityRole where TKey : IEquatable @@ -35,4 +34,23 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test optionsBuilder.UseInMemoryDatabase(); } } + + public abstract class InMemoryContext : 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 InMemoryContext(DbContextOptions options) : base(options) + { } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseInMemoryDatabase(); + } + } } \ 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 3a373cadef..791f734e38 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFUserStoreTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFUserStoreTest.cs @@ -5,7 +5,6 @@ using System; using System.Linq.Expressions; using Microsoft.AspNetCore.Identity.Test; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs new file mode 100644 index 0000000000..3b771f3f56 --- /dev/null +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs @@ -0,0 +1,320 @@ +// 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.Linq.Expressions; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity.Test; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test +{ + public class InMemoryEFUserStoreTestWithGenerics : UserManagerTestBase, IDisposable + { + private readonly InMemoryContextWithGenerics _context; + private UserStoreWithGenerics _store; + + public InMemoryEFUserStoreTestWithGenerics() + { + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddDbContext(options => options.UseInMemoryDatabase()); + _context = services.BuildServiceProvider().GetRequiredService(); + } + + protected override object CreateTestContext() + { + return _context; + } + + protected override void AddUserStore(IServiceCollection services, object context = null) + { + _store = new UserStoreWithGenerics((InMemoryContextWithGenerics)context, "TestContext"); + services.AddSingleton>(_store); + } + + protected override void AddRoleStore(IServiceCollection services, object context = null) + { + services.AddSingleton>(new RoleStoreWithGenerics((InMemoryContextWithGenerics)context, "TestContext")); + } + + protected override IdentityUserWithGenerics CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", + bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false) + { + return new IdentityUserWithGenerics + { + UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()), + Email = email, + PhoneNumber = phoneNumber, + LockoutEnabled = lockoutEnabled, + LockoutEnd = lockoutEnd + }; + } + + protected override MyIdentityRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false) + { + var roleName = useRoleNamePrefixAsRoleName ? roleNamePrefix : string.Format("{0}{1}", roleNamePrefix, Guid.NewGuid()); + return new MyIdentityRole(roleName); + } + + protected override void SetUserPasswordHash(IdentityUserWithGenerics 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); + + [Fact] + public async Task CanAddRemoveUserClaimWithIssuer() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Claim[] claims = { new Claim("c1", "v1", null, "i1"), new Claim("c2", "v2", null, "i2"), new Claim("c2", "v3", null, "i3") }; + foreach (Claim c in claims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); + } + + var userId = await manager.GetUserIdAsync(user); + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(3, userClaims.Count); + Assert.Equal(3, userClaims.Intersect(claims, ClaimEqualityComparer.Default).Count()); + + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(2, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(0, userClaims.Count); + } + + [Fact] + public async Task RemoveClaimWithIssuerOnlyAffectsUser() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + var user2 = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); + Claim[] claims = { new Claim("c", "v", null, "i1"), new Claim("c2", "v2", null, "i2"), new Claim("c2", "v3", null, "i3") }; + foreach (Claim c in claims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user2, c)); + } + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(3, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(2, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(0, userClaims.Count); + var userClaims2 = await manager.GetClaimsAsync(user2); + Assert.Equal(3, userClaims2.Count); + } + + [Fact] + public async Task CanReplaceUserClaimWithIssuer() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("c", "a", "i"))); + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + Claim claim = new Claim("c", "b", "i"); + Claim oldClaim = userClaims.FirstOrDefault(); + IdentityResultAssert.IsSuccess(await manager.ReplaceClaimAsync(user, oldClaim, claim)); + var newUserClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, newUserClaims.Count); + Claim newClaim = newUserClaims.FirstOrDefault(); + Assert.Equal(claim.Type, newClaim.Type); + Assert.Equal(claim.Value, newClaim.Value); + Assert.Equal(claim.Issuer, newClaim.Issuer); + } + + public void Dispose() + { + } + } + + public class ClaimEqualityComparer : IEqualityComparer + { + public static IEqualityComparer Default = new ClaimEqualityComparer(); + + public bool Equals(Claim x, Claim y) + { + return x.Value == y.Value && x.Type == y.Type && x.Issuer == y.Issuer; + } + + public int GetHashCode(Claim obj) + { + return 1; + } + } + + + #region Generic Type defintions + + public class IdentityUserWithGenerics : IdentityUser + { + public IdentityUserWithGenerics() + { + Id = Guid.NewGuid().ToString(); + } + } + + public class UserStoreWithGenerics : UserStore + { + public string LoginContext { get; set; } + + public UserStoreWithGenerics(InMemoryContextWithGenerics context, string loginContext) : base(context) + { + LoginContext = loginContext; + } + + protected override IdentityUserRoleWithDate CreateUserRole(IdentityUserWithGenerics user, MyIdentityRole role) + { + return new IdentityUserRoleWithDate() + { + RoleId = role.Id, + UserId = user.Id, + Created = DateTime.UtcNow + }; + } + + protected override IdentityUserClaimWithIssuer CreateUserClaim(IdentityUserWithGenerics user, Claim claim) + { + return new IdentityUserClaimWithIssuer { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value, Issuer = claim.Issuer }; + } + + protected override IdentityUserLoginWithContext CreateUserLogin(IdentityUserWithGenerics user, UserLoginInfo login) + { + return new IdentityUserLoginWithContext + { + UserId = user.Id, + ProviderKey = login.ProviderKey, + LoginProvider = login.LoginProvider, + ProviderDisplayName = login.ProviderDisplayName, + Context = LoginContext + }; + } + + protected override IdentityUserTokenWithStuff CreateUserToken(IdentityUserWithGenerics user, string loginProvider, string name, string value) + { + return new IdentityUserTokenWithStuff + { + UserId = user.Id, + LoginProvider = loginProvider, + Name = name, + Value = value, + Stuff = "stuff" + }; + } + } + + public class RoleStoreWithGenerics : RoleStore> + { + private string _loginContext; + public RoleStoreWithGenerics(InMemoryContextWithGenerics context, string loginContext) : base(context) + { + _loginContext = loginContext; + } + + protected override IdentityRoleClaim CreateRoleClaim(MyIdentityRole role, Claim claim) + { + return new IdentityRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; + } + } + + public class IdentityUserClaimWithIssuer : IdentityUserClaim + { + public string Issuer { get; set; } + + public override Claim ToClaim() + { + return new Claim(ClaimType, ClaimValue, null, Issuer); + } + + public override void InitializeFromClaim(Claim other) + { + ClaimValue = other.Value; + ClaimType = other.Type; + Issuer = other.Issuer; + } + } + + public class IdentityUserRoleWithDate : IdentityUserRole + { + public DateTime Created { get; set; } + } + + public class MyIdentityRole : IdentityRole> + { + public MyIdentityRole() : base() + { + Id = Guid.NewGuid().ToString(); + } + + public MyIdentityRole(string roleName) : this() + { + Name = roleName; + } + + } + + public class IdentityUserTokenWithStuff : IdentityUserToken + { + public string Stuff { get; set; } + } + + public class IdentityUserLoginWithContext : IdentityUserLogin + { + public string Context { get; set; } + } + + public class InMemoryContextWithGenerics : InMemoryContext, IdentityUserTokenWithStuff> + { + public InMemoryContextWithGenerics(DbContextOptions options) : base(options) + { } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseInMemoryDatabase(); + } + } + + #endregion +} \ 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 798e8c9d27..0d06b7b65a 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreTest.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Identity.Test; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs new file mode 100644 index 0000000000..0e9b53a20b --- /dev/null +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs @@ -0,0 +1,316 @@ +// 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.Linq.Expressions; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity.Test; +using Microsoft.AspNetCore.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test +{ + public class UserStoreWithGenericsTest : UserManagerTestBase, IClassFixture + { + private readonly ScratchDatabaseFixture _fixture; + + public UserStoreWithGenericsTest(ScratchDatabaseFixture fixture) + { + _fixture = fixture; + } + + public ContextWithGenerics CreateContext() + { + var db = DbUtil.Create(_fixture.ConnectionString); + db.Database.EnsureCreated(); + return db; + } + + protected override object CreateTestContext() + { + return CreateContext(); + } + + protected override bool ShouldSkipDbTests() + { + return TestPlatformHelper.IsMono || !TestPlatformHelper.IsWindows; + } + + protected override void AddUserStore(IServiceCollection services, object context = null) + { + services.AddSingleton>(new UserStoreWithGenerics((ContextWithGenerics)context, "TestContext")); + } + + protected override void AddRoleStore(IServiceCollection services, object context = null) + { + services.AddSingleton>(new RoleStoreWithGenerics((ContextWithGenerics)context, "TestContext")); + } + + protected override IdentityUserWithGenerics CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", + bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false) + { + return new IdentityUserWithGenerics + { + UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()), + Email = email, + PhoneNumber = phoneNumber, + LockoutEnabled = lockoutEnabled, + LockoutEnd = lockoutEnd + }; + } + + protected override MyIdentityRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false) + { + var roleName = useRoleNamePrefixAsRoleName ? roleNamePrefix : string.Format("{0}{1}", roleNamePrefix, Guid.NewGuid()); + return new MyIdentityRole(roleName); + } + + protected override void SetUserPasswordHash(IdentityUserWithGenerics 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); + + [Fact] + public async Task CanAddRemoveUserClaimWithIssuer() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Claim[] claims = { new Claim("c1", "v1", null, "i1"), new Claim("c2", "v2", null, "i2"), new Claim("c2", "v3", null, "i3") }; + foreach (Claim c in claims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); + } + + var userId = await manager.GetUserIdAsync(user); + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(3, userClaims.Count); + Assert.Equal(3, userClaims.Intersect(claims, ClaimEqualityComparer.Default).Count()); + + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(2, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(0, userClaims.Count); + } + + [Fact] + public async Task RemoveClaimWithIssuerOnlyAffectsUser() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + var user2 = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); + Claim[] claims = { new Claim("c", "v", null, "i1"), new Claim("c2", "v2", null, "i2"), new Claim("c2", "v3", null, "i3") }; + foreach (Claim c in claims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user2, c)); + } + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(3, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(2, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(0, userClaims.Count); + var userClaims2 = await manager.GetClaimsAsync(user2); + Assert.Equal(3, userClaims2.Count); + } + + [Fact] + public async Task CanReplaceUserClaimWithIssuer() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("c", "a", "i"))); + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + Claim claim = new Claim("c", "b", "i"); + Claim oldClaim = userClaims.FirstOrDefault(); + IdentityResultAssert.IsSuccess(await manager.ReplaceClaimAsync(user, oldClaim, claim)); + var newUserClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, newUserClaims.Count); + Claim newClaim = newUserClaims.FirstOrDefault(); + Assert.Equal(claim.Type, newClaim.Type); + Assert.Equal(claim.Value, newClaim.Value); + Assert.Equal(claim.Issuer, newClaim.Issuer); + } + } + + public class ClaimEqualityComparer : IEqualityComparer + { + public static IEqualityComparer Default = new ClaimEqualityComparer(); + + public bool Equals(Claim x, Claim y) + { + return x.Value == y.Value && x.Type == y.Type && x.Issuer == y.Issuer; + } + + public int GetHashCode(Claim obj) + { + return 1; + } + } + + + #region Generic Type defintions + + public class IdentityUserWithGenerics : IdentityUser + { + public IdentityUserWithGenerics() + { + Id = Guid.NewGuid().ToString(); + } + } + + public class UserStoreWithGenerics : UserStore + { + public string LoginContext { get; set; } + + public UserStoreWithGenerics(ContextWithGenerics context, string loginContext) : base(context) + { + LoginContext = loginContext; + } + + protected override IdentityUserRoleWithDate CreateUserRole(IdentityUserWithGenerics user, MyIdentityRole role) + { + return new IdentityUserRoleWithDate() + { + RoleId = role.Id, + UserId = user.Id, + Created = DateTime.UtcNow + }; + } + + protected override IdentityUserClaimWithIssuer CreateUserClaim(IdentityUserWithGenerics user, Claim claim) + { + return new IdentityUserClaimWithIssuer { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value, Issuer = claim.Issuer }; + } + + protected override IdentityUserLoginWithContext CreateUserLogin(IdentityUserWithGenerics user, UserLoginInfo login) + { + return new IdentityUserLoginWithContext + { + UserId = user.Id, + ProviderKey = login.ProviderKey, + LoginProvider = login.LoginProvider, + ProviderDisplayName = login.ProviderDisplayName, + Context = LoginContext + }; + } + + protected override IdentityUserTokenWithStuff CreateUserToken(IdentityUserWithGenerics user, string loginProvider, string name, string value) + { + return new IdentityUserTokenWithStuff + { + UserId = user.Id, + LoginProvider = loginProvider, + Name = name, + Value = value, + Stuff = "stuff" + }; + } + } + + public class RoleStoreWithGenerics : RoleStore> + { + private string _loginContext; + public RoleStoreWithGenerics(ContextWithGenerics context, string loginContext) : base(context) + { + _loginContext = loginContext; + } + + protected override IdentityRoleClaim CreateRoleClaim(MyIdentityRole role, Claim claim) + { + return new IdentityRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; + } + } + + public class IdentityUserClaimWithIssuer : IdentityUserClaim + { + public string Issuer { get; set; } + + public override Claim ToClaim() + { + return new Claim(ClaimType, ClaimValue, null, Issuer); + } + + public override void InitializeFromClaim(Claim other) + { + ClaimValue = other.Value; + ClaimType = other.Type; + Issuer = other.Issuer; + } + } + + public class IdentityUserRoleWithDate : IdentityUserRole + { + public DateTime Created { get; set; } + } + + public class MyIdentityRole : IdentityRole> + { + public MyIdentityRole() : base() + { + Id = Guid.NewGuid().ToString(); + } + + public MyIdentityRole(string roleName) : this() + { + Name = roleName; + } + } + + public class IdentityUserTokenWithStuff : IdentityUserToken + { + public string Stuff { get; set; } + } + + public class IdentityUserLoginWithContext : IdentityUserLogin + { + public string Context { get; set; } + } + + public class ContextWithGenerics : IdentityDbContext, IdentityUserTokenWithStuff> + { + public ContextWithGenerics(DbContextOptions options) : base(options) { } + } + + #endregion +} \ No newline at end of file