From 350902db07ae9dd1a4458ba75d23be1ffc2ed1f9 Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Mon, 8 Dec 2014 15:24:45 -0800 Subject: [PATCH 01/31] Updating to dev NuGet.config --- NuGet.Config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGet.Config b/NuGet.Config index 2d3b0cb857..f41e9c631d 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,7 +1,7 @@  - + From f69f46c330159038b532df33a6689bfbb251fbb5 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 15 Dec 2014 14:23:04 -0800 Subject: [PATCH 02/31] Switch to new xunit --- .../project.json | 4 ++-- .../project.json | 4 ++-- test/Microsoft.AspNet.Identity.InMemory.Test/project.json | 4 ++-- test/Microsoft.AspNet.Identity.Test/project.json | 4 ++-- test/Shared/PriorityOrderer.cs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/project.json b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/project.json index 38d3deabe7..1ee5fbe41a 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/project.json +++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/project.json @@ -11,7 +11,7 @@ "EntityFramework.InMemory": "7.0.0-*", "Microsoft.Framework.OptionsModel" : "1.0.0-*", "System.Security.Claims": "1.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "code": "**\\*.cs;..\\Shared\\*.cs", "frameworks": { @@ -22,6 +22,6 @@ } }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" } } diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/project.json b/test/Microsoft.AspNet.Identity.EntityFramework.Test/project.json index b4f14912d0..03296b2fcb 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/project.json +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/project.json @@ -12,7 +12,7 @@ "EntityFramework.SqlServer": "7.0.0-*", "Microsoft.Framework.OptionsModel" : "1.0.0-*", "System.Security.Claims": "1.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "code": "**\\*.cs;..\\Shared\\*.cs", "frameworks": { @@ -23,6 +23,6 @@ } }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" } } diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/project.json b/test/Microsoft.AspNet.Identity.InMemory.Test/project.json index 470267ba51..fdf436789e 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/project.json +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/project.json @@ -11,7 +11,7 @@ "Microsoft.Framework.ConfigurationModel": "1.0.0-*", "Microsoft.Framework.DependencyInjection" : "1.0.0-*", "Microsoft.Framework.OptionsModel" : "1.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "code": "**\\*.cs;..\\Shared\\*.cs", "frameworks": { @@ -22,6 +22,6 @@ } }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" } } diff --git a/test/Microsoft.AspNet.Identity.Test/project.json b/test/Microsoft.AspNet.Identity.Test/project.json index 75d6ba15c1..8223b94bad 100644 --- a/test/Microsoft.AspNet.Identity.Test/project.json +++ b/test/Microsoft.AspNet.Identity.Test/project.json @@ -9,7 +9,7 @@ "Microsoft.Framework.ConfigurationModel" : "1.0.0-*", "Microsoft.Framework.DependencyInjection" : "1.0.0-*", "Microsoft.Framework.OptionsModel" : "1.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "code": "**\\*.cs;..\\Shared\\*.cs", "frameworks": { @@ -20,6 +20,6 @@ } }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" } } diff --git a/test/Shared/PriorityOrderer.cs b/test/Shared/PriorityOrderer.cs index d9161de20f..8702e415d8 100644 --- a/test/Shared/PriorityOrderer.cs +++ b/test/Shared/PriorityOrderer.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Identity.Test { int priority = 0; - foreach (IAttributeInfo attr in testCase.Method.GetCustomAttributes((typeof(TestPriorityAttribute)))) + foreach (IAttributeInfo attr in testCase.TestMethod.Method.GetCustomAttributes((typeof(TestPriorityAttribute)))) priority = attr.GetNamedArgument("Priority"); GetOrCreate(sortedMethods, priority).Add(testCase); @@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Identity.Test foreach (var list in sortedMethods.Keys.Select(priority => sortedMethods[priority])) { - list.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.Method.Name, y.Method.Name)); + list.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name)); foreach (XunitTestCase testCase in list) yield return testCase; } From 4af03693bf402d68dfd2f1c894400521493b2d1a Mon Sep 17 00:00:00 2001 From: Stuart Leeks Date: Thu, 11 Dec 2014 08:51:45 +0000 Subject: [PATCH 03/31] Fix typos in Views\Home\Index.cshtml --- samples/IdentitySample.Mvc/Views/Home/Index.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/IdentitySample.Mvc/Views/Home/Index.cshtml b/samples/IdentitySample.Mvc/Views/Home/Index.cshtml index 419bedfccb..605e094dcc 100644 --- a/samples/IdentitySample.Mvc/Views/Home/Index.cshtml +++ b/samples/IdentitySample.Mvc/Views/Home/Index.cshtml @@ -72,7 +72,7 @@ Add Social Logins
  • - Get more data about the user when they login suing Facebook + Get more data about the user when they login using Facebook
  • @@ -102,7 +102,7 @@
    Account Confirmation
    When you register a new account, you will be sent an email confirmation. - You can use an email service such as SendGrid which integrate nicely with Windows Azure and requires no configuration or + You can use an email service such as SendGrid which integrates nicely with Windows Azure and requires no configuration or set up an SMTP server to send email. You can send email using the EmailService which is registered in App_Start\IdentityConfig.cs
    From e3602fac9867b5a1dc5bb40b7689bb343956d6a5 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Tue, 16 Dec 2014 14:02:22 -0800 Subject: [PATCH 04/31] Login -> log in --- samples/IdentitySample.Mvc/Views/Home/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/IdentitySample.Mvc/Views/Home/Index.cshtml b/samples/IdentitySample.Mvc/Views/Home/Index.cshtml index 605e094dcc..65e438ad78 100644 --- a/samples/IdentitySample.Mvc/Views/Home/Index.cshtml +++ b/samples/IdentitySample.Mvc/Views/Home/Index.cshtml @@ -72,7 +72,7 @@ Add Social Logins
  • - Get more data about the user when they login using Facebook + Get more data about the user when they log in using Facebook
  • From ac86d94eb682356c5191c516218e4a4fd4049b60 Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Wed, 17 Dec 2014 15:54:17 -0800 Subject: [PATCH 05/31] Added new methods on IdentityBuilder --- .../IdentityBuilder.cs | 10 ++++ .../IdentityBuilderTest.cs | 47 ++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs index 8c9d51332a..71b2011cdb 100644 --- a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs +++ b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs @@ -76,5 +76,15 @@ namespace Microsoft.AspNet.Identity .AddTokenProvider(typeof(PhoneNumberTokenProvider<>).MakeGenericType(UserType)) .AddTokenProvider(typeof(EmailTokenProvider<>).MakeGenericType(UserType)); } + + public IdentityBuilder AddUserManager() where TUserManager : class + { + return AddScoped(typeof(UserManager<>).MakeGenericType(UserType), typeof(TUserManager)); + } + + public IdentityBuilder AddRoleManager() where TRoleManager : class + { + return AddScoped(typeof(RoleManager<>).MakeGenericType(RoleType), typeof(TRoleManager)); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs index 0bbf0f7f11..7843b95019 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs @@ -10,7 +10,6 @@ using System; using System.Threading; using System.Threading.Tasks; using System.Linq; -using Microsoft.AspNet.Security.DataProtection; namespace Microsoft.AspNet.Identity.Test { @@ -62,6 +61,28 @@ namespace Microsoft.AspNet.Identity.Test Assert.NotNull(thingy); } + [Fact] + public void CanOverrideUserManager() + { + var services = new ServiceCollection(); + services.AddIdentity() + .AddUserStore() + .AddUserManager(); + var myUserManager = services.BuildServiceProvider().GetRequiredService(typeof(UserManager)) as MyUserManager; + Assert.NotNull(myUserManager); + } + + [Fact] + public void CanOverrideRoleManager() + { + var services = new ServiceCollection(); + services.AddIdentity() + .AddRoleStore() + .AddRoleManager(); + var myRoleManager = services.BuildServiceProvider().GetRequiredService>() as MyRoleManager; + Assert.NotNull(myRoleManager); + } + [Fact] public void EnsureDefaultServices() { @@ -203,5 +224,29 @@ namespace Microsoft.AspNet.Identity.Test } } + private class MyUserManager : UserManager + { + public MyUserManager(IUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + IUserNameNormalizer userNameNormalizer, + IEnumerable> tokenProviders, + IEnumerable msgProviders) : + base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, userNameNormalizer, tokenProviders, msgProviders) + { + + } + } + + private class MyRoleManager : RoleManager + { + public MyRoleManager(IRoleStore store, + IEnumerable> roleValidators) : base(store, roleValidators) + { + + } + } } } \ No newline at end of file From 5d29d11ccb8b0973c4448903ad9ab985234b957c Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Fri, 19 Dec 2014 13:26:54 -0800 Subject: [PATCH 06/31] Remove all navigation property usage from EF Store --- .../IdentityDbContext.cs | 17 ---- .../RoleStore.cs | 9 +- .../UserStore.cs | 45 +++------- .../SqlStoreTestBase.cs | 90 +++++++++++++++++-- .../UserStoreTest.cs | 12 --- 5 files changed, 95 insertions(+), 78 deletions(-) diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs b/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs index add648bd0b..baf7a99e6e 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs @@ -52,23 +52,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework b.ForRelational().Table("AspNetRoleClaims"); }); - var userType = builder.Model.GetEntityType(typeof(TUser)); - var roleType = builder.Model.GetEntityType(typeof(TRole)); - var userClaimType = builder.Model.GetEntityType(typeof(IdentityUserClaim)); - var roleClaimType = builder.Model.GetEntityType(typeof(IdentityRoleClaim)); - var userRoleType = builder.Model.GetEntityType(typeof(IdentityUserRole)); - //var ucfk = userClaimType.GetOrAddForeignKey(userType.GetPrimaryKey(), new[] { userClaimType.GetProperty("UserId") }); - //userType.AddNavigation(new Navigation(ucfk, "Claims", false)); - //userClaimType.AddNavigation(new Navigation(ucfk, "User", true)); - //var urfk = userRoleType.GetOrAddForeignKey(userType.GetPrimaryKey(), new[] { userRoleType.GetProperty("UserId") }); - //userType.AddNavigation(new Navigation(urfk, "Roles", false)); - - //var urfk2 = userRoleType.GetOrAddForeignKey(roleType.GetPrimaryKey(), new[] { userRoleType.GetProperty("RoleId") }); - //roleType.AddNavigation(new Navigation(urfk2, "Users", false)); - - var rcfk = roleClaimType.GetOrAddForeignKey(new[] { roleClaimType.GetProperty("RoleId") }, roleType.GetPrimaryKey()); - roleType.AddNavigation("Claims", rcfk, false); - builder.Entity>(b => { b.Key(r => new { r.UserId, r.RoleId }); diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs index 53cab96a6a..7b14e0aa21 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs @@ -59,11 +59,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework } } - public virtual Task GetRoleAggregate(Expression> filter, CancellationToken cancellationToken = default(CancellationToken)) - { - return Roles.FirstOrDefaultAsync(filter); - } - public async virtual Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -163,7 +158,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); var roleId = ConvertIdFromString(id); - return GetRoleAggregate(u => u.Id.Equals(roleId), cancellationToken); + return Roles.FirstOrDefaultAsync(u => u.Id.Equals(roleId), cancellationToken); } /// @@ -176,7 +171,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - return GetRoleAggregate(u => u.Name.ToUpper() == name.ToUpper(), cancellationToken); + return Roles.FirstOrDefaultAsync(u => u.Name.ToUpper() == name.ToUpper(), cancellationToken); } private void ThrowIfDisposed() diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs index 18e9b9d87f..591013e8b6 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs @@ -72,14 +72,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.FromResult(0); } - protected virtual Task GetUserAggregate(Expression> filter, CancellationToken cancellationToken = default(CancellationToken)) - { - return Users.FirstOrDefaultAsync(filter, cancellationToken); - // TODO: .Include(u => u.Roles) - //.Include(u => u.Claims) - //.Include(u => u.Logins); - } - public Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); @@ -184,7 +176,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); var id = ConvertIdFromString(userId); - return GetUserAggregate(u => u.Id.Equals(id), cancellationToken); + return Users.FirstOrDefaultAsync(u => u.Id.Equals(id), cancellationToken); } public virtual TKey ConvertIdFromString(string id) @@ -215,7 +207,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - return GetUserAggregate(u => u.NormalizedUserName == normalizedUserName, cancellationToken); + return Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName, cancellationToken); } public IQueryable Users @@ -296,10 +288,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.RoleNotFound, roleName)); } var ur = new IdentityUserRole { UserId = user.Id, RoleId = roleEntity.Id }; - // TODO: rely on fixup? await UserRoles.AddAsync(ur); - user.Roles.Add(ur); - roleEntity.Users.Add(ur); } /// @@ -328,7 +317,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework if (userRole != null) { UserRoles.Remove(userRole); - user.Roles.Remove(userRole); } } } @@ -339,7 +327,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework /// /// /// - public virtual Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public virtual async Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -348,12 +336,11 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new ArgumentNullException("user"); } var userId = user.Id; -// TODO: var query = from userRole in UserRoles - var query = from userRole in user.Roles + 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(); - return Task.FromResult>(query.ToList()); + return await query.ToListAsync(); } /// @@ -371,7 +358,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentNullException("user"); } - if (String.IsNullOrWhiteSpace(roleName)) + if (string.IsNullOrWhiteSpace(roleName)) { throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, "roleName"); } @@ -380,9 +367,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework { var userId = user.Id; var roleId = role.Id; - return user.Roles.Any(ur => ur.RoleId.Equals(roleId)); - //return await UserRoles.AnyAsync(ur => ur.RoleId.Equals(roleId) && ur.UserId.Equals(userId)); - //return UserRoles.Any(ur => ur.RoleId.Equals(roleId) && ur.UserId.Equals(userId)); + return await UserRoles.AnyAsync(ur => ur.RoleId.Equals(roleId) && ur.UserId.Equals(userId)); } return false; } @@ -502,7 +487,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework }; // TODO: fixup so we don't have to update both await UserLogins.AddAsync(l); - user.Logins.Add(l); } public virtual async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, @@ -515,12 +499,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new ArgumentNullException("user"); } var userId = user.Id; - // todo: ensure logins loaded var entry = await UserLogins.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.ProviderKey == providerKey); if (entry != null) { UserLogins.Remove(entry); - user.Logins.Remove(entry); } } @@ -532,11 +514,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentNullException("user"); } - // todo: ensure logins loaded - //IList result = user.Logins - // .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToList(); var userId = user.Id; - return await UserLogins.Where(l => l.UserId.Equals(userId)) .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToListAsync(); } @@ -546,12 +524,11 @@ namespace Microsoft.AspNet.Identity.EntityFramework { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - // todo: ensure logins loaded var userLogin = await UserLogins.FirstOrDefaultAsync(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); if (userLogin != null) { - return await GetUserAggregate(u => u.Id.Equals(userLogin.UserId), cancellationToken); + return await Users.FirstOrDefaultAsync(u => u.Id.Equals(userLogin.UserId), cancellationToken); } return null; } @@ -638,9 +615,9 @@ namespace Microsoft.AspNet.Identity.EntityFramework { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - return GetUserAggregate(u => u.Email == email, cancellationToken); + return Users.FirstOrDefaultAsync(u => u.Email == email, cancellationToken); // todo: ToUpper blows up with Null Ref - //return GetUserAggregate(u => u.Email.ToUpper() == email.ToUpper(), cancellationToken); + //return Users.FirstOrDefaultAsync(u => u.Email.ToUpper() == email.ToUpper(), cancellationToken); } /// diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs b/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs index a5d0f07889..b7e52134aa 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs @@ -156,16 +156,90 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); } - [Fact] - public async Task EnsureRoleClaimNavigationProperty() + private async Task LazyLoadTestSetup(TestDbContext db, TUser user) { var context = CreateContext(); - var roleManager = CreateRoleManager(context); - var r = CreateRole(); - IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r)); - var c = new Claim("a", "b"); - IdentityResultAssert.IsSuccess(await roleManager.AddClaimAsync(r, c)); - Assert.NotNull(r.Claims.Single(cl => cl.ClaimValue == c.Value && cl.ClaimType == c.Type)); + var manager = CreateManager(context); + var role = CreateRoleManager(context); + var admin = CreateRole("Admin"); + var local = CreateRole("Local"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, new UserLoginInfo("provider", user.Id.ToString(), "display"))); + IdentityResultAssert.IsSuccess(await role.CreateAsync(admin)); + IdentityResultAssert.IsSuccess(await role.CreateAsync(local)); + IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, admin.Name)); + IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, local.Name)); + Claim[] userClaims = + { + new Claim("Whatever", "Value"), + new Claim("Whatever2", "Value2") + }; + foreach (var c in userClaims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); + } + } + + [Fact] + public async Task LoadFromDbFindByIdTest() + { + var db = CreateContext(); + var user = CreateTestUser(); + await LazyLoadTestSetup(db, user); + + db = CreateContext(); + var manager = CreateManager(db); + + var userById = await manager.FindByIdAsync(user.Id.ToString()); + Assert.Equal(2, (await manager.GetClaimsAsync(userById)).Count); + Assert.Equal(1, (await manager.GetLoginsAsync(userById)).Count); + Assert.Equal(2, (await manager.GetRolesAsync(userById)).Count); + } + + [Fact] + public async Task LoadFromDbFindByNameTest() + { + var db = CreateContext(); + var user = CreateTestUser(); + await LazyLoadTestSetup(db, user); + + db = CreateContext(); + var manager = CreateManager(db); + var userByName = await manager.FindByNameAsync(user.UserName); + Assert.Equal(2, (await manager.GetClaimsAsync(userByName)).Count); + Assert.Equal(1, (await manager.GetLoginsAsync(userByName)).Count); + Assert.Equal(2, (await manager.GetRolesAsync(userByName)).Count); + } + + [Fact] + public async Task LoadFromDbFindByLoginTest() + { + var db = CreateContext(); + var user = CreateTestUser(); + await LazyLoadTestSetup(db, user); + + db = CreateContext(); + var manager = CreateManager(db); + var userByLogin = await manager.FindByLoginAsync("provider", user.Id.ToString()); + Assert.Equal(2, (await manager.GetClaimsAsync(userByLogin)).Count); + Assert.Equal(1, (await manager.GetLoginsAsync(userByLogin)).Count); + Assert.Equal(2, (await manager.GetRolesAsync(userByLogin)).Count); + } + + [Fact] + public async Task LoadFromDbFindByEmailTest() + { + var db = CreateContext(); + var user = CreateTestUser(); + user.Email = "fooz@fizzy.pop"; + await LazyLoadTestSetup(db, user); + + db = CreateContext(); + var manager = CreateManager(db); + var userByEmail = await manager.FindByEmailAsync(user.Email); + Assert.Equal(2, (await manager.GetClaimsAsync(userByEmail)).Count); + Assert.Equal(1, (await manager.GetLoginsAsync(userByEmail)).Count); + Assert.Equal(2, (await manager.GetRolesAsync(userByEmail)).Count); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs index 431d8cd1a3..6e9c937274 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs @@ -265,18 +265,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); } - [Fact] - public async Task EnsureRoleClaimNavigationProperty() - { - var context = CreateContext(); - var roleManager = CreateRoleManager(context); - var r = new IdentityRole("EnsureRoleClaimNavigationProperty"); - IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r)); - var c = new Claim("a", "b"); - IdentityResultAssert.IsSuccess(await roleManager.AddClaimAsync(r, c)); - Assert.NotNull(r.Claims.Single(cl => cl.ClaimValue == c.Value && cl.ClaimType == c.Type)); - } - [Fact] public async Task AddUserToUnknownRoleFails() { From 625b270924c6ea343701ee098bbe718bb7299f69 Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Mon, 1 Dec 2014 10:29:15 -0800 Subject: [PATCH 07/31] Added new apis to query users and fixed EF.Inmemory --- .../IdentitySample.Mvc.kproj | 5 +- .../RoleStore.cs | 10 +- .../UserStore.cs | 80 +- .../IUserClaimStore.cs | 8 + .../IUserRoleStore.cs | 8 + src/Microsoft.AspNet.Identity/UserManager.cs | 31 + ...InMemoryTestServiceCollectionExtensions.cs | 31 - .../InMemoryContext.cs | 67 +- .../InMemoryEFUserStoreTest.cs | 2 +- .../InMemoryUserStore.cs | 929 ------------------ .../TestIdentityFactory.cs | 2 +- .../InMemoryUserStore.cs | 27 +- .../UserManagerTest.cs | 20 + test/Shared/UserManagerTestBase.cs | 53 + 14 files changed, 226 insertions(+), 1047 deletions(-) delete mode 100644 test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/EntityInMemoryTestServiceCollectionExtensions.cs delete mode 100644 test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryUserStore.cs diff --git a/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj b/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj index 0c84b42be0..3cdababc47 100644 --- a/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj +++ b/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 5131 - + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs index 7b14e0aa21..b3d7988dc5 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs @@ -25,8 +25,8 @@ namespace Microsoft.AspNet.Identity.EntityFramework public RoleStore(TContext context) : base(context) { } } - public class RoleStore : - IQueryableRoleStore, + public class RoleStore : + IQueryableRoleStore, IRoleClaimStore where TRole : IdentityRole where TKey : IEquatable @@ -198,7 +198,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new ArgumentNullException("role"); } - return await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(); + return await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken); } public Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) @@ -213,7 +213,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new ArgumentNullException("claim"); } - return RoleClaims.AddAsync(new IdentityRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }); + return RoleClaims.AddAsync(new IdentityRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }, cancellationToken); } public async Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) @@ -227,7 +227,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentNullException("claim"); } - var claims = await RoleClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(); + var claims = await RoleClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); foreach (var c in claims) { RoleClaims.Remove(c); diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs index 591013e8b6..f1cf95a2c4 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs @@ -282,7 +282,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, "roleName"); } - var roleEntity = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper()); + var roleEntity = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper(), cancellationToken); if (roleEntity == null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.RoleNotFound, roleName)); @@ -310,10 +310,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, "roleName"); } - var roleEntity = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper()); + var roleEntity = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper(), cancellationToken); if (roleEntity != null) { - var userRole = await UserRoles.FirstOrDefaultAsync(r => roleEntity.Id.Equals(r.RoleId) && r.UserId.Equals(user.Id)); + var userRole = await UserRoles.FirstOrDefaultAsync(r => roleEntity.Id.Equals(r.RoleId) && r.UserId.Equals(user.Id), cancellationToken); if (userRole != null) { UserRoles.Remove(userRole); @@ -362,7 +362,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, "roleName"); } - var role = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper()); + var role = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper(), cancellationToken); if (role != null) { var userId = user.Id; @@ -401,7 +401,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new ArgumentNullException("user"); } - return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(); + return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken); } public async Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) @@ -417,7 +417,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework } foreach (var claim in claims) { - await UserClaims.AddAsync(new IdentityUserClaim { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value }); + await UserClaims.AddAsync(new IdentityUserClaim { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value }, cancellationToken); } } @@ -437,8 +437,8 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new ArgumentNullException("newClaim"); } - var matchedClaims = await UserClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(); - foreach(var matchedClaim in matchedClaims) + var matchedClaims = await UserClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); + foreach (var matchedClaim in matchedClaims) { matchedClaim.ClaimValue = newClaim.Value; matchedClaim.ClaimType = newClaim.Type; @@ -456,8 +456,9 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentNullException("claims"); } - foreach (var claim in claims) { - var matchedClaims = await UserClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(); + foreach (var claim in claims) + { + var matchedClaims = await UserClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); foreach (var c in matchedClaims) { UserClaims.Remove(c); @@ -499,7 +500,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new ArgumentNullException("user"); } var userId = user.Id; - var entry = await UserLogins.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.ProviderKey == providerKey); + var entry = await UserLogins.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.ProviderKey == providerKey, cancellationToken); if (entry != null) { UserLogins.Remove(entry); @@ -516,7 +517,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework } var userId = user.Id; return await UserLogins.Where(l => l.UserId.Equals(userId)) - .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToListAsync(); + .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToListAsync(cancellationToken); } public async virtual Task FindByLoginAsync(string loginProvider, string providerKey, @@ -525,7 +526,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); var userLogin = await - UserLogins.FirstOrDefaultAsync(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); + UserLogins.FirstOrDefaultAsync(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey, cancellationToken); if (userLogin != null) { return await Users.FirstOrDefaultAsync(u => u.Id.Equals(userLogin.UserId), cancellationToken); @@ -890,5 +891,58 @@ namespace Microsoft.AspNet.Identity.EntityFramework } return Task.FromResult(user.TwoFactorEnabled); } + + /// + /// Get all users with given claim + /// + /// + /// + /// + public async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (claim == null) + { + throw new ArgumentNullException("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 (IList)await query.ToListAsync(cancellationToken); + } + + /// + /// Get all users in given role + /// + /// + /// + /// + public async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (String.IsNullOrEmpty(roleName)) + { + throw new ArgumentNullException("role"); + } + + var role = await Roles.Where(x => x.Name.Equals(roleName)).FirstOrDefaultAsync(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 (IList) await query.ToListAsync(cancellationToken); + } + return new List(); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IUserClaimStore.cs b/src/Microsoft.AspNet.Identity/IUserClaimStore.cs index eb7d0d559f..825c289b0e 100644 --- a/src/Microsoft.AspNet.Identity/IUserClaimStore.cs +++ b/src/Microsoft.AspNet.Identity/IUserClaimStore.cs @@ -51,5 +51,13 @@ namespace Microsoft.AspNet.Identity /// Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Get users having a specific claim + /// + /// Claim to look up + /// + /// + Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IUserRoleStore.cs b/src/Microsoft.AspNet.Identity/IUserRoleStore.cs index e74ca87fe6..690eeeb45a 100644 --- a/src/Microsoft.AspNet.Identity/IUserRoleStore.cs +++ b/src/Microsoft.AspNet.Identity/IUserRoleStore.cs @@ -51,5 +51,13 @@ namespace Microsoft.AspNet.Identity /// Task IsInRoleAsync(TUser user, string roleName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Returns all users in given role + /// + /// + /// + /// + Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index 3b18d534fd..787f91ebeb 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -1962,6 +1962,37 @@ namespace Microsoft.AspNet.Identity return await store.GetAccessFailedCountAsync(user, cancellationToken); } + public virtual Task> GetUsersForClaimAsync(Claim claim, + CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + var store = GetClaimStore(); + if (claim == null) + { + throw new ArgumentNullException("claim"); + } + return store.GetUsersForClaimAsync(claim, cancellationToken); + } + + /// + /// Get all the users in a role + /// + /// + /// + /// + public virtual Task> GetUsersInRoleAsync(string roleName, + CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + var store = GetUserRoleStore(); + if (roleName == null) + { + throw new ArgumentNullException("role"); + } + + return store.GetUsersInRoleAsync(roleName, cancellationToken); + } + private void ThrowIfDisposed() { if (_disposed) diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/EntityInMemoryTestServiceCollectionExtensions.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/EntityInMemoryTestServiceCollectionExtensions.cs deleted file mode 100644 index 98c6e6506c..0000000000 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/EntityInMemoryTestServiceCollectionExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNet.Identity.EntityFramework; -using Microsoft.AspNet.Identity.EntityFramework.InMemory.Test; -using Microsoft.Data.Entity; -using Microsoft.Framework.DependencyInjection; - -namespace Microsoft.AspNet.Identity -{ - public static class EntityInMemoryTestServiceCollectionExtensions - { - public static IdentityBuilder AddIdentityInMemory(this ServiceCollection services, InMemoryContext context) - { - return services.AddIdentityInMemory(context); - } - - public static IdentityBuilder AddIdentityInMemory(this ServiceCollection services, TDbContext context) - where TUser : IdentityUser - where TRole : IdentityRole - where TDbContext : DbContext - { - var builder = services.AddIdentity(); - builder.AddDefaultTokenProviders(); - services.AddInstance>(new InMemoryUserStore(context)); - var store = new RoleStore(context); - services.AddInstance>(store); - return builder; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryContext.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryContext.cs index 375f24319d..16455fa25a 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryContext.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryContext.cs @@ -3,41 +3,25 @@ using System; using Microsoft.Data.Entity; -using Microsoft.Data.Entity.Metadata; namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test { public class InMemoryContext : - InMemoryContext + InMemoryContext { - public InMemoryContext() { } - public InMemoryContext(IServiceProvider serviceProvider) : base(serviceProvider) { } } public class InMemoryContext : - InMemoryContext + InMemoryContext where TUser : IdentityUser { - public InMemoryContext() { } - public InMemoryContext(IServiceProvider serviceProvider) : base(serviceProvider) { } } - public class InMemoryContext : DbContext + public class InMemoryContext : IdentityDbContext where TUser : IdentityUser where TRole : IdentityRole - where TUserLogin : IdentityUserLogin - where TUserRole : IdentityUserRole - where TUserClaim : IdentityUserClaim where TKey : IEquatable { - - public DbSet Users { get; set; } - public DbSet Roles { get; set; } - public DbSet RoleClaims { get; set; } - - public InMemoryContext(IServiceProvider serviceProvider) - : base(serviceProvider) { } - public InMemoryContext() { } protected override void OnConfiguring(DbContextOptions builder) @@ -45,50 +29,5 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test // Want fresh in memory store for tests always for now builder.UseInMemoryStore(persist: false); } - - protected override void OnModelCreating(ModelBuilder builder) - { - builder.Entity(b => - { - b.Key(u => u.Id); - b.Property(u => u.UserName); - b.ForRelational().Table("AspNetUsers"); - }); - - builder.Entity(b => - { - b.Key(r => r.Id); - b.ForRelational().Table("AspNetRoles"); - }); - - builder.Entity(b => - { - b.Key(r => new { r.UserId, r.RoleId }); - b.ForeignKey(f => f.UserId); - b.ForeignKey(f => f.RoleId); - b.ForRelational().Table("AspNetUserRoles"); - }); - - builder.Entity(b => - { - b.Key(l => new { l.LoginProvider, l.ProviderKey, l.UserId }); - b.ForeignKey(f => f.UserId); - b.ForRelational().Table("AspNetUserLogins"); - }); - - builder.Entity(b => - { - b.Key(c => c.Id); - b.ForeignKey(f => f.UserId); - b.ForRelational().Table("AspNetUserClaims"); - }); - - builder.Entity>(b => - { - b.Key(c => c.Id); - b.ForeignKey(f => f.RoleId); - b.ForRelational().Table("AspNetRoleClaims"); - }); - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs index e8bd3397b6..66c01e5998 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test protected override void AddUserStore(IServiceCollection services, object context = null) { - services.AddInstance>(new InMemoryUserStore((InMemoryContext)context)); + services.AddInstance>(new UserStore((InMemoryContext)context)); } protected override void AddRoleStore(IServiceCollection services, object context = null) diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryUserStore.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryUserStore.cs deleted file mode 100644 index e2e9f6e43d..0000000000 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryUserStore.cs +++ /dev/null @@ -1,929 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq.Expressions; -using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Data.Entity; - -namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test -{ - public class InMemoryUserStore : InMemoryUserStore - { - public InMemoryUserStore(InMemoryContext context) : base(context) { } - } - - public class InMemoryUserStore : InMemoryUserStore - where TUser : IdentityUser - { - public InMemoryUserStore(InMemoryContext context) : base(context) { } - } - - public class InMemoryUserStore : InMemoryUserStore - where TUser:IdentityUser - where TContext : DbContext - { - public InMemoryUserStore(TContext context) : base(context) { } - } - - public class InMemoryUserStore : - IUserLoginStore, - IUserClaimStore, - IUserRoleStore, - IUserPasswordStore, - IUserSecurityStampStore, - IQueryableUserStore, - IUserEmailStore, - IUserPhoneNumberStore, - IUserTwoFactorStore, - IUserLockoutStore - where TKey : IEquatable - where TUser : IdentityUser - where TRole : IdentityRole - where TUserLogin : IdentityUserLogin, new() - where TUserRole : IdentityUserRole, new() - where TUserClaim : IdentityUserClaim, new() - where TContext : DbContext - { - private bool _disposed; - - public InMemoryUserStore(TContext context) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - Context = context; - AutoSaveChanges = true; - } - - public TContext Context { get; private set; } - - /// - /// If true will call SaveChanges after CreateAsync/UpdateAsync/DeleteAsync - /// - public bool AutoSaveChanges { get; set; } - - private Task SaveChanges(CancellationToken cancellationToken) - { - return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.FromResult(0); - } - - protected virtual Task GetUserAggregate(Expression> filter, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(Users.SingleOrDefault(filter)); - // TODO: return Users.SingleOrDefaultAsync(filter, cancellationToken); - //Include(u => u.Roles) - //.Include(u => u.Claims) - //.Include(u => u.Logins) - } - - public Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(Convert.ToString(user.Id, CultureInfo.InvariantCulture)); - } - - public Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.UserName); - } - - public Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.UserName = userName; - return Task.FromResult(0); - } - - public Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.NormalizedUserName); - } - - public Task SetNormalizedUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.NormalizedUserName = userName; - return Task.FromResult(0); - } - - public async virtual Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - await Context.AddAsync(user, cancellationToken); - await SaveChanges(cancellationToken); - } - - public async virtual Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - Context.Update(user); - await SaveChanges(cancellationToken); - } - - public async virtual Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - Context.Remove(user); - await SaveChanges(cancellationToken); - } - - public virtual TKey ConvertUserId(string userId) - { - return (TKey)Convert.ChangeType(userId, typeof(TKey)); - } - - /// - /// Find a user by id - /// - /// - /// - /// - public virtual Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - var id = ConvertUserId(userId); - return GetUserAggregate(u => u.Id.Equals(id), cancellationToken); - } - - /// - /// Find a user by name - /// - /// - /// - /// - public virtual Task FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - return GetUserAggregate(u => u.UserName.ToUpper() == userName.ToUpper(), cancellationToken); - } - - public IQueryable Users - { - get { return Context.Set(); } - } - - public async virtual Task AddLoginAsync(TUser user, UserLoginInfo login, - CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - var l = new TUserLogin - { - UserId = user.Id, - ProviderKey = login.ProviderKey, - LoginProvider = login.LoginProvider, - ProviderDisplayName = login.ProviderDisplayName - }; - await Context.Set().AddAsync(l, cancellationToken); - user.Logins.Add(l); - } - - public virtual Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, - CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - var entry = user.Logins.SingleOrDefault(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); - if (entry != null) - { - user.Logins.Remove(entry); - Context.Set>().Remove(entry); - } - return Task.FromResult(0); - } - - public virtual Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - IList result = user.Logins.Select( - l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)) - .ToList(); - return Task.FromResult(result); - } - - public async virtual Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - var userLogin = await Context.Set() - .FirstOrDefaultAsync(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); - if (userLogin != null) - { - return await GetUserAggregate(u => u.Id.Equals(userLogin.UserId), cancellationToken); - } - return null; - } - - /// - /// Set the password hash for a user - /// - /// - /// - /// - /// - public virtual Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.PasswordHash = passwordHash; - return Task.FromResult(0); - } - - /// - /// Get the password hash for a user - /// - /// - /// - /// - public virtual Task GetPasswordHashAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.PasswordHash); - } - - /// - /// Returns true if the user has a password set - /// - /// - /// - /// - public virtual Task HasPasswordAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(user.PasswordHash != null); - } - - /// - /// Return the claims for a user - /// - /// - /// - /// - public virtual Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - IList result = user.Claims.Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToList(); - return Task.FromResult(result); - } - - /// - /// Add claims to a user - /// - /// - /// - /// - /// - public virtual Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - if (claims == null) - { - throw new ArgumentNullException("claims"); - } - foreach (var claim in claims) - { - user.Claims.Add(new TUserClaim { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value }); - } - return Task.FromResult(0); - } - - /// - /// Updates the give claim with the new one. - /// - /// - /// - /// - /// - /// - public Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - if (claim == null) - { - throw new ArgumentNullException("claim"); - } - if (newClaim == null) - { - throw new ArgumentNullException("newClaim"); - } - - var matchedClaim = user.Claims.FirstOrDefault(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type); - if(matchedClaim != null) - { - matchedClaim.ClaimValue = newClaim.Value; - matchedClaim.ClaimType = newClaim.Type; - } - return Task.FromResult(0); - } - - /// - /// Remove claims from a user - /// - /// - /// - /// - /// - public virtual Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - if (claims == null) - { - throw new ArgumentNullException("claims"); - } - foreach (var claim in claims) - { - var matchingClaims = - user.Claims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToList(); - foreach (var c in matchingClaims) - { - user.Claims.Remove(c); - } - } - // TODO:these claims might not exist in the dbset - //var query = - // _userClaims.Where( - // uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type); - //foreach (var c in query) - //{ - // _userClaims.Remove(c); - //} - return Task.FromResult(0); - } - - /// - /// Returns whether the user email is confirmed - /// - /// - /// - /// - public virtual Task GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.EmailConfirmed); - } - - /// - /// Set IsConfirmed on the user - /// - /// - /// - /// - /// - public virtual Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.EmailConfirmed = confirmed; - return Task.FromResult(0); - } - - /// - /// Set the user email - /// - /// - /// - /// - /// - public virtual Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.Email = email; - return Task.FromResult(0); - } - - /// - /// Get the user's email - /// - /// - /// - /// - public virtual Task GetEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.Email); - } - - /// - /// FindByLoginAsync a user by email - /// - /// - /// - /// - public virtual Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - return Task.FromResult(Users.SingleOrDefault(u => u.Email.ToUpper() == email.ToUpper())); - //return GetUserAggregate(u => u.Email.ToUpper() == email.ToUpper(), cancellationToken); - } - - /// - /// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered - /// not locked out. - /// - /// - /// - /// - public virtual Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.LockoutEnd); - } - - /// - /// Locks a user out until the specified end date (set to a past date, to unlock a user) - /// - /// - /// - /// - /// - public virtual Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.LockoutEnd = lockoutEnd; - return Task.FromResult(0); - } - - /// - /// Used to record when an attempt to access the user has failed - /// - /// - /// - /// - public virtual Task IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.AccessFailedCount++; - return Task.FromResult(user.AccessFailedCount); - } - - /// - /// Used to reset the account access count, typically 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("user"); - } - user.AccessFailedCount = 0; - return Task.FromResult(0); - } - - /// - /// Returns the current number of failed access attempts. This number usually will be reset whenever the password is - /// verified or the account is locked out. - /// - /// - /// - /// - public virtual Task GetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.AccessFailedCount); - } - - /// - /// Returns whether the user can be locked out. - /// - /// - /// - /// - public virtual Task GetLockoutEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.LockoutEnabled); - } - - /// - /// Sets whether the user can be locked out. - /// - /// - /// - /// - /// - public virtual Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.LockoutEnabled = enabled; - return Task.FromResult(0); - } - - /// - /// Set the user's phone number - /// - /// - /// - /// - /// - public virtual Task SetPhoneNumberAsync(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.PhoneNumber = phoneNumber; - return Task.FromResult(0); - } - - /// - /// Get a user's phone number - /// - /// - /// - /// - public virtual Task GetPhoneNumberAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.PhoneNumber); - } - - /// - /// Returns whether the user phoneNumber is confirmed - /// - /// - /// - /// - public virtual Task GetPhoneNumberConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.PhoneNumberConfirmed); - } - - /// - /// Set PhoneNumberConfirmed on the user - /// - /// - /// - /// - /// - public virtual Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.PhoneNumberConfirmed = confirmed; - return Task.FromResult(0); - } - - /// - /// Add a user to a role - /// - /// - /// - /// - /// - public virtual Task AddToRoleAsync(TUser user, string roleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - // TODO: - //if (String.IsNullOrWhiteSpace(roleName)) - //{ - // throw new ArgumentException(IdentityResources.ValueCannotBeNullOrEmpty, "roleName"); - //} - var roleEntity = Context.Set().SingleOrDefault(r => r.Name.ToUpper() == roleName.ToUpper()); - if (roleEntity == null) - { - throw new InvalidOperationException("Role Not Found"); - //TODO: String.Format(CultureInfo.CurrentCulture, IdentityResources.RoleNotFound, roleName)); - } - var ur = new TUserRole { UserId = user.Id, RoleId = roleEntity.Id }; - user.Roles.Add(ur); - roleEntity.Users.Add(ur); - return Task.FromResult(0); - } - - /// - /// Remove a user from a role - /// - /// - /// - /// - /// - public virtual Task RemoveFromRoleAsync(TUser user, string roleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - //if (String.IsNullOrWhiteSpace(roleName)) - //{ - // throw new ArgumentException(IdentityResources.ValueCannotBeNullOrEmpty, "roleName"); - //} - var roleEntity = Context.Set().SingleOrDefault(r => r.Name.ToUpper() == roleName.ToUpper()); - if (roleEntity != null) - { - var userRole = user.Roles.FirstOrDefault(r => roleEntity.Id.Equals(r.RoleId)); - if (userRole != null) - { - user.Roles.Remove(userRole); - roleEntity.Users.Remove(userRole); - } - } - return Task.FromResult(0); - } - - /// - /// Get the names of the roles a user is a member of - /// - /// - /// - /// - public virtual Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - var query = from userRoles in user.Roles - join roles in Context.Set() - on userRoles.RoleId equals roles.Id - select roles.Name; - return Task.FromResult>(query.ToList()); - } - - /// - /// Returns true if the user is in the named role - /// - /// - /// - /// - /// - public virtual Task IsInRoleAsync(TUser user, string roleName, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - //if (String.IsNullOrWhiteSpace(roleName)) - //{ - // throw new ArgumentException(IdentityResources.ValueCannotBeNullOrEmpty, "roleName"); - //} - var any = - Context.Set().Where(r => r.Name.ToUpper() == roleName.ToUpper()) - .Where(r => r.Users.Any(ur => ur.UserId.Equals(user.Id))) - .Count() > 0; - return Task.FromResult(any); - } - - /// - /// Set the security stamp for the user - /// - /// - /// - /// - /// - public virtual Task SetSecurityStampAsync(TUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.SecurityStamp = stamp; - return Task.FromResult(0); - } - - /// - /// Get the security stamp for a user - /// - /// - /// - /// - public virtual Task GetSecurityStampAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.SecurityStamp); - } - - /// - /// Set whether two factor authentication is enabled for the user - /// - /// - /// - /// - /// - public virtual Task SetTwoFactorEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - user.TwoFactorEnabled = enabled; - return Task.FromResult(0); - } - - /// - /// Gets whether two factor authentication is enabled for the user - /// - /// - /// - /// - public virtual Task GetTwoFactorEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - if (user == null) - { - throw new ArgumentNullException("user"); - } - return Task.FromResult(user.TwoFactorEnabled); - } - - private void ThrowIfDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - - /// - /// Dispose the store - /// - public void Dispose() - { - _disposed = true; - } - } -} diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/TestIdentityFactory.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/TestIdentityFactory.cs index 3112c72c58..5e4a8ef137 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/TestIdentityFactory.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/TestIdentityFactory.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test services.AddEntityFramework().AddInMemoryStore(); var serviceProvider = services.BuildServiceProvider(); - var db = new InMemoryContext(serviceProvider); + var db = new InMemoryContext(); db.Database.EnsureCreated(); return db; diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs index e056b3f048..2590eccba7 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs @@ -50,7 +50,7 @@ namespace Microsoft.AspNet.Identity.InMemory public Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) { var matchedClaims = user.Claims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToList(); - foreach(var matchedClaim in matchedClaims) + foreach (var matchedClaim in matchedClaims) { matchedClaim.ClaimValue = newClaim.Value; matchedClaim.ClaimType = newClaim.Type; @@ -349,5 +349,30 @@ namespace Microsoft.AspNet.Identity.InMemory user.NormalizedUserName = userName; return Task.FromResult(0); } + + // RoleId == rolename for inmemory store tests + public Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + if (String.IsNullOrEmpty(roleName)) + { + throw new ArgumentNullException("role"); + } + + return Task.FromResult>(Users.Where(u => (u.Roles.Where(x => x.RoleId == roleName).Count() > 0)).Select(x => x).ToList()); + } + + public Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + if (claim == null) + { + throw new ArgumentNullException("claim"); + } + + var query = from user in Users + where user.Claims.Where(x => x.ClaimType == claim.Type && x.ClaimValue == claim.Value).FirstOrDefault() != null + select user; + + return Task.FromResult>(query.ToList()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index f9068d6e30..de496f8ff7 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -961,6 +961,16 @@ namespace Microsoft.AspNet.Identity.Test { return Task.FromResult(0); } + + public Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult>(new List()); + } + + public Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult>(new List()); + } } private class NoOpTokenProvider : IUserTokenProvider @@ -1218,6 +1228,16 @@ namespace Microsoft.AspNet.Identity.Test { throw new NotImplementedException(); } + + public Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } + + public Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } } } } \ No newline at end of file diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index 2ba65a828a..8997be452f 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -1579,6 +1579,59 @@ namespace Microsoft.AspNet.Identity.Test // set to a valid value await userMgr.SetLockoutEndDateAsync(user, DateTimeOffset.Parse("01/01/2014")); Assert.Equal(DateTimeOffset.Parse("01/01/2014"), await userMgr.GetLockoutEndDateAsync(user)); + } + + [Fact] + public async Task CanGetUsersWithClaims() + { + var manager = CreateManager(); + + for (int i = 0; i < 6; i++) + { + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + + if ((i % 2) == 0) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("foo", "bar"))); + } + } + + Assert.Equal(3, (await manager.GetUsersForClaimAsync(new Claim("foo", "bar"))).Count); + + Assert.Equal(0, (await manager.GetUsersForClaimAsync(new Claim("123", "456"))).Count); + } + + [Fact] + public async Task CanGetUsersInRole() + { + var context = CreateTestContext(); + var manager = CreateManager(context); + var roleManager = CreateRoleManager(context); + var roles = GenerateRoles("UsersInRole", 4); + + foreach (var role in roles) + { + IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(role)); + } + + for (int i = 0; i < 6; i++) + { + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + + if ((i % 2) == 0) + { + IdentityResultAssert.IsSuccess(await manager.AddToRolesAsync(user, roles.Select(x=>x.Name).AsEnumerable())); + } + } + + foreach (var role in roles) + { + Assert.Equal(3, (await manager.GetUsersInRoleAsync(role.Name)).Count); + } + + Assert.Equal(0, (await manager.GetUsersInRoleAsync("123456")).Count); } public List GenerateUsers(string userNamePrefix, int count) From c9d27e27e6dd6c15b4228de6e5de3c1a22c15a6d Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Tue, 30 Dec 2014 14:51:52 -0800 Subject: [PATCH 08/31] Introduce SignInResult/IdentityError/Describer Follows Resource pattern (IdentityErrorDescriber.StringName, or FormatStringName(arg1, arg2) Also cleaned up optional services, by allowing null in constructor SignInStatus -> SignInFailure and introduced SignInResult to make SignInManager APIs consistent with IdentityResult (but no strings needed for SignIn) Fixes: #86, #176, #287 and #177 --- .../Controllers/AccountController.cs | 93 +++++---- .../Controllers/ManageController.cs | 2 +- .../IdentityBuilder.cs | 6 + .../{SignInStatus.cs => IdentityError.cs} | 9 +- .../IdentityErrorDescriber.cs | 181 ++++++++++++++++++ .../IdentityResult.cs | 43 ++--- .../IdentityServiceCollectionExtensions.cs | 2 + .../PasswordHasher.cs | 13 +- .../PasswordValidator.cs | 23 ++- .../Properties/Resources.Designer.cs | 136 ++++++++----- src/Microsoft.AspNet.Identity/Resources.resx | 46 +++-- src/Microsoft.AspNet.Identity/RoleManager.cs | 12 +- .../RoleValidator.cs | 18 +- .../SignInManager.cs | 65 ++++--- src/Microsoft.AspNet.Identity/SignInResult.cs | 84 ++++++++ src/Microsoft.AspNet.Identity/UserManager.cs | 63 +++--- .../UserValidator.cs | 26 ++- .../HttpSignInTest.cs | 4 +- .../IdentityBuilderTest.cs | 13 +- .../IdentityResultTest.cs | 10 +- .../PasswordValidatorTest.cs | 10 +- .../RoleValidatorTest.cs | 2 +- .../SignInManagerTest.cs | 42 ++-- .../UserManagerTest.cs | 86 ++++++--- .../UserValidatorTest.cs | 2 +- test/Shared/IdentityResultAssert.cs | 11 +- test/Shared/MockHelpers.cs | 26 +-- test/Shared/TestUser.cs | 1 + test/Shared/UserManagerTestBase.cs | 16 +- 29 files changed, 708 insertions(+), 337 deletions(-) rename src/Microsoft.AspNet.Identity/{SignInStatus.cs => IdentityError.cs} (64%) create mode 100644 src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs create mode 100644 src/Microsoft.AspNet.Identity/SignInResult.cs diff --git a/samples/IdentitySample.Mvc/Controllers/AccountController.cs b/samples/IdentitySample.Mvc/Controllers/AccountController.cs index 728328eed1..48ad2824f4 100644 --- a/samples/IdentitySample.Mvc/Controllers/AccountController.cs +++ b/samples/IdentitySample.Mvc/Controllers/AccountController.cs @@ -1,11 +1,10 @@ -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Mvc.Rendering; -using System.Linq; +using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; +using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Mvc.Rendering; namespace IdentitySample.Models { @@ -43,20 +42,23 @@ namespace IdentitySample.Models ViewBag.ReturnUrl = returnUrl; if (ModelState.IsValid) { - var signInStatus = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false); - switch (signInStatus) + var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false); + if (result.Succeeded) { - case SignInStatus.Success: - return RedirectToLocal(returnUrl); - case SignInStatus.LockedOut: - ModelState.AddModelError("", "User is locked out, try again later."); - return View(model); - case SignInStatus.RequiresVerification: - return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); - case SignInStatus.Failure: - default: - ModelState.AddModelError("", "Invalid username or password."); - return View(model); + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + ModelState.AddModelError("", "Invalid username or password."); + return View(model); } } @@ -143,22 +145,26 @@ namespace IdentitySample.Models // Sign in the user with this external login provider if the user already has a login var result = await SignInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); - switch (result) + if (result.Succeeded) { - case SignInStatus.Success: - return RedirectToLocal(returnUrl); - case SignInStatus.LockedOut: - return View("Lockout"); - case SignInStatus.RequiresVerification: - return RedirectToAction("SendCode", new { ReturnUrl = returnUrl }); - case SignInStatus.Failure: - default: - // If the user does not have an account, then prompt the user to create an account - ViewBag.ReturnUrl = returnUrl; - ViewBag.LoginProvider = info.LoginProvider; - // REVIEW: handle case where email not in claims? - var email = info.ExternalIdentity.FindFirstValue(ClaimTypes.Email); - return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction("SendCode", new { ReturnUrl = returnUrl }); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + // If the user does not have an account, then prompt the user to create an account + ViewBag.ReturnUrl = returnUrl; + ViewBag.LoginProvider = info.LoginProvider; + // REVIEW: handle case where email not in claims? + var email = info.ExternalIdentity.FindFirstValue(ClaimTypes.Email); + return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); } } @@ -380,15 +386,18 @@ namespace IdentitySample.Models } var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser); - switch (result) + if (result.Succeeded) { - case SignInStatus.Success: - return RedirectToLocal(model.ReturnUrl); - case SignInStatus.LockedOut: - return View("Lockout"); - default: - ModelState.AddModelError("", "Invalid code."); - return View(model); + return RedirectToLocal(model.ReturnUrl); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + ModelState.AddModelError("", "Invalid code."); + return View(model); } } @@ -398,7 +407,7 @@ namespace IdentitySample.Models { foreach (var error in result.Errors) { - ModelState.AddModelError("", error); + ModelState.AddModelError("", error.Description); } } diff --git a/samples/IdentitySample.Mvc/Controllers/ManageController.cs b/samples/IdentitySample.Mvc/Controllers/ManageController.cs index e67342fb0a..5a4359dd11 100644 --- a/samples/IdentitySample.Mvc/Controllers/ManageController.cs +++ b/samples/IdentitySample.Mvc/Controllers/ManageController.cs @@ -346,7 +346,7 @@ namespace IdentitySample { foreach (var error in result.Errors) { - ModelState.AddModelError("", error); + ModelState.AddModelError("", error.Description); } } diff --git a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs index 71b2011cdb..4f78d0731d 100644 --- a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs +++ b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs @@ -35,6 +35,12 @@ namespace Microsoft.AspNet.Identity return AddScoped(typeof(IRoleValidator<>).MakeGenericType(RoleType), typeof(T)); } + public IdentityBuilder AddErrorDescriber() where TDescriber : IdentityErrorDescriber + { + Services.AddScoped(); + return this; + } + public IdentityBuilder AddPasswordValidator() where T : class { return AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(T)); diff --git a/src/Microsoft.AspNet.Identity/SignInStatus.cs b/src/Microsoft.AspNet.Identity/IdentityError.cs similarity index 64% rename from src/Microsoft.AspNet.Identity/SignInStatus.cs rename to src/Microsoft.AspNet.Identity/IdentityError.cs index 3247a7dd39..0db1958a8f 100644 --- a/src/Microsoft.AspNet.Identity/SignInStatus.cs +++ b/src/Microsoft.AspNet.Identity/IdentityError.cs @@ -3,12 +3,9 @@ namespace Microsoft.AspNet.Identity { - public enum SignInStatus + public class IdentityError { - Success, - LockedOut, - RequiresVerification, - NotAllowed, - Failure + public string Code { get; set; } + public string Description { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs new file mode 100644 index 0000000000..05fcef7fe0 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Identity +{ + public class IdentityErrorDescriber + { + public static IdentityErrorDescriber Default = new IdentityErrorDescriber(); + + public virtual IdentityError DefaultError() + { + return new IdentityError + { + Code = nameof(DefaultError), + Description = Resources.DefaultError + }; + } + + public virtual IdentityError PasswordMismatch() + { + return new IdentityError + { + Code = nameof(PasswordMismatch), + Description = Resources.PasswordMismatch + }; + } + + public virtual IdentityError InvalidToken() + { + return new IdentityError + { + Code = nameof(InvalidToken), + Description = Resources.InvalidToken + }; + } + + public virtual IdentityError LoginAlreadyAssociated() + { + return new IdentityError + { + Code = nameof(LoginAlreadyAssociated), + Description = Resources.LoginAlreadyAssociated + }; + } + + public virtual IdentityError InvalidUserName(string name) + { + return new IdentityError + { + Code = nameof(InvalidUserName), + Description = Resources.FormatInvalidUserName(name) + }; + } + + public virtual IdentityError InvalidEmail(string email) + { + return new IdentityError + { + Code = nameof(InvalidEmail), + Description = Resources.FormatInvalidEmail(email) + }; + } + + public virtual IdentityError DuplicateUserName(string name) + { + return new IdentityError + { + Code = nameof(DuplicateUserName), + Description = Resources.FormatDuplicateUserName(name) + }; + } + + public virtual IdentityError DuplicateEmail(string email) + { + return new IdentityError + { + Code = nameof(DuplicateEmail), + Description = Resources.FormatDuplicateEmail(email) + }; + } + + public virtual IdentityError InvalidRoleName(string name) + { + return new IdentityError + { + Code = nameof(InvalidRoleName), + Description = Resources.FormatInvalidRoleName(name) + }; + } + + public virtual IdentityError DuplicateRoleName(string name) + { + return new IdentityError + { + Code = nameof(DuplicateRoleName), + Description = Resources.FormatDuplicateRoleName(name) + }; + } + + public virtual IdentityError UserAlreadyHasPassword() + { + return new IdentityError + { + Code = nameof(UserAlreadyHasPassword), + Description = Resources.UserAlreadyHasPassword + }; + } + + public virtual IdentityError UserLockoutNotEnabled() + { + return new IdentityError + { + Code = nameof(UserLockoutNotEnabled), + Description = Resources.UserLockoutNotEnabled + }; + } + + public virtual IdentityError UserAlreadyInRole(string role) + { + return new IdentityError + { + Code = nameof(UserAlreadyInRole), + Description = Resources.FormatUserAlreadyInRole(role) + }; + } + + public virtual IdentityError UserNotInRole(string role) + { + return new IdentityError + { + Code = nameof(UserNotInRole), + Description = Resources.FormatUserNotInRole(role) + }; + } + + public virtual IdentityError PasswordTooShort(int length) + { + return new IdentityError + { + Code = nameof(PasswordTooShort), + Description = Resources.FormatPasswordTooShort(length) + }; + } + + public virtual IdentityError PasswordRequiresNonLetterAndDigit() + { + return new IdentityError + { + Code = nameof(PasswordRequiresNonLetterAndDigit), + Description = Resources.PasswordRequiresNonLetterAndDigit + }; + } + + public virtual IdentityError PasswordRequiresDigit() + { + return new IdentityError + { + Code = nameof(PasswordRequiresDigit), + Description = Resources.PasswordRequiresDigit + }; + } + + public virtual IdentityError PasswordRequiresLower() + { + return new IdentityError + { + Code = nameof(PasswordRequiresLower), + Description = Resources.PasswordRequiresLower + }; + } + + public virtual IdentityError PasswordRequiresUpper() + { + return new IdentityError + { + Code = nameof(PasswordRequiresUpper), + Description = Resources.PasswordRequiresUpper + }; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityResult.cs b/src/Microsoft.AspNet.Identity/IdentityResult.cs index dbe60c4756..dd3fe07de6 100644 --- a/src/Microsoft.AspNet.Identity/IdentityResult.cs +++ b/src/Microsoft.AspNet.Identity/IdentityResult.cs @@ -11,45 +11,19 @@ namespace Microsoft.AspNet.Identity /// public class IdentityResult { - private static readonly IdentityResult _success = new IdentityResult(true); + private static readonly IdentityResult _success = new IdentityResult { Succeeded = true }; - /// - /// Failure constructor that takes error messages - /// - /// - public IdentityResult(params string[] errors) : this((IEnumerable)errors) - { - } - - /// - /// Failure constructor that takes error messages - /// - /// - public IdentityResult(IEnumerable errors) - { - if (errors == null || !errors.Any()) - { - errors = new[] { Resources.DefaultError }; - } - Succeeded = false; - Errors = errors; - } - - protected IdentityResult(bool success) - { - Succeeded = success; - Errors = new string[0]; - } + private List _errors = new List(); /// /// True if the operation was successful /// - public bool Succeeded { get; private set; } + public bool Succeeded { get; protected set; } /// /// List of errors /// - public IEnumerable Errors { get; private set; } + public IEnumerable Errors { get { return _errors; } } /// /// Static success result @@ -65,9 +39,14 @@ namespace Microsoft.AspNet.Identity /// /// /// - public static IdentityResult Failed(params string[] errors) + public static IdentityResult Failed(params IdentityError[] errors) { - return new IdentityResult(errors); + var result = new IdentityResult { Succeeded = false }; + if (errors != null) + { + result._errors.AddRange(errors); + } + return result; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs index 86fd009b9e..33b4d5e8f6 100644 --- a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs @@ -59,6 +59,8 @@ namespace Microsoft.Framework.DependencyInjection services.TryAdd(describe.Transient, PasswordHasher>()); services.TryAdd(describe.Transient()); services.TryAdd(describe.Transient, RoleValidator>()); + // No interface for the error describer so we can add errors without rev'ing the interface + services.TryAdd(describe.Transient()); services.TryAdd(describe.Scoped>()); services.TryAdd(describe.Scoped, ClaimsIdentityFactory>()); services.TryAdd(describe.Scoped, UserManager>()); diff --git a/src/Microsoft.AspNet.Identity/PasswordHasher.cs b/src/Microsoft.AspNet.Identity/PasswordHasher.cs index 09557ae983..611ce4a04f 100644 --- a/src/Microsoft.AspNet.Identity/PasswordHasher.cs +++ b/src/Microsoft.AspNet.Identity/PasswordHasher.cs @@ -37,14 +37,11 @@ namespace Microsoft.AspNet.Identity /// Constructs a PasswordHasher using the specified options /// /// - public PasswordHasher(IOptions options) + public PasswordHasher(IOptions optionsAccessor = null) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } + var options = optionsAccessor?.Options ?? new PasswordHasherOptions(); - _compatibilityMode = options.Options.CompatibilityMode; + _compatibilityMode = options.CompatibilityMode; switch (_compatibilityMode) { case PasswordHasherCompatibilityMode.IdentityV2: @@ -52,7 +49,7 @@ namespace Microsoft.AspNet.Identity break; case PasswordHasherCompatibilityMode.IdentityV3: - _iterCount = options.Options.IterationCount; + _iterCount = options.IterationCount; if (_iterCount < 1) { throw new InvalidOperationException(Resources.InvalidPasswordHasherIterationCount); @@ -63,7 +60,7 @@ namespace Microsoft.AspNet.Identity throw new InvalidOperationException(Resources.InvalidPasswordHasherCompatibilityMode); } - _rng = options.Options.Rng; + _rng = options.Rng; } // Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized. diff --git a/src/Microsoft.AspNet.Identity/PasswordValidator.cs b/src/Microsoft.AspNet.Identity/PasswordValidator.cs index 2a8bcf730f..1726e26694 100644 --- a/src/Microsoft.AspNet.Identity/PasswordValidator.cs +++ b/src/Microsoft.AspNet.Identity/PasswordValidator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,6 +14,13 @@ namespace Microsoft.AspNet.Identity /// public class PasswordValidator : IPasswordValidator where TUser : class { + public PasswordValidator(IdentityErrorDescriber errors = null) + { + Describer = errors ?? new IdentityErrorDescriber(); + } + + public IdentityErrorDescriber Describer { get; private set; } + /// /// Ensures that the password is of the required length and meets the configured requirements /// @@ -33,33 +39,32 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("manager"); } - var errors = new List(); + var errors = new List(); var options = manager.Options.Password; if (string.IsNullOrWhiteSpace(password) || password.Length < options.RequiredLength) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PasswordTooShort, - options.RequiredLength)); + errors.Add(Describer.PasswordTooShort(options.RequiredLength)); } if (options.RequireNonLetterOrDigit && password.All(IsLetterOrDigit)) { - errors.Add(Resources.PasswordRequireNonLetterOrDigit); + errors.Add(Describer.PasswordRequiresNonLetterAndDigit()); } if (options.RequireDigit && !password.Any(IsDigit)) { - errors.Add(Resources.PasswordRequireDigit); + errors.Add(Describer.PasswordRequiresDigit()); } if (options.RequireLowercase && !password.Any(IsLower)) { - errors.Add(Resources.PasswordRequireLower); + errors.Add(Describer.PasswordRequiresLower()); } if (options.RequireUppercase && !password.Any(IsUpper)) { - errors.Add(Resources.PasswordRequireUpper); + errors.Add(Describer.PasswordRequiresUpper()); } return Task.FromResult(errors.Count == 0 ? IdentityResult.Success - : IdentityResult.Failed(String.Join(" ", errors))); + : IdentityResult.Failed(errors.ToArray())); } /// diff --git a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs index 9c91fd4bd5..8caeda20b2 100644 --- a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs @@ -139,35 +139,35 @@ namespace Microsoft.AspNet.Identity } /// - /// Name {0} is already taken. + /// Role Name '{0}' is already taken. /// - internal static string DuplicateName + internal static string DuplicateRoleName { - get { return GetString("DuplicateName"); } + get { return GetString("DuplicateRoleName"); } } /// - /// Name {0} is already taken. + /// Role Name '{0}' is already taken. /// - internal static string FormatDuplicateName(object p0) + internal static string FormatDuplicateRoleName(object p0) { - return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateName"), p0); + return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateRoleName"), p0); } /// - /// A user with that external login already exists. + /// UserName '{0}' is already taken. /// - internal static string ExternalLoginExists + internal static string DuplicateUserName { - get { return GetString("ExternalLoginExists"); } + get { return GetString("DuplicateUserName"); } } /// - /// A user with that external login already exists. + /// UserName '{0}' is already taken. /// - internal static string FormatExternalLoginExists() + internal static string FormatDuplicateUserName(object p0) { - return GetString("ExternalLoginExists"); + return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateUserName"), p0); } /// @@ -218,6 +218,22 @@ namespace Microsoft.AspNet.Identity return GetString("InvalidPasswordHasherIterationCount"); } + /// + /// Role name '{0}' is invalid. + /// + internal static string InvalidRoleName + { + get { return GetString("InvalidRoleName"); } + } + + /// + /// Role name '{0}' is invalid. + /// + internal static string FormatInvalidRoleName(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("InvalidRoleName"), p0); + } + /// /// Invalid token. /// @@ -235,7 +251,7 @@ namespace Microsoft.AspNet.Identity } /// - /// User name {0} is invalid, can only contain letters or digits. + /// User name '{0}' is invalid, can only contain letters or digits. /// internal static string InvalidUserName { @@ -243,7 +259,7 @@ namespace Microsoft.AspNet.Identity } /// - /// User name {0} is invalid, can only contain letters or digits. + /// User name '{0}' is invalid, can only contain letters or digits. /// internal static string FormatInvalidUserName(object p0) { @@ -251,19 +267,19 @@ namespace Microsoft.AspNet.Identity } /// - /// Lockout is not enabled for this user. + /// A user with this login already exists. /// - internal static string LockoutNotEnabled + internal static string LoginAlreadyAssociated { - get { return GetString("LockoutNotEnabled"); } + get { return GetString("LoginAlreadyAssociated"); } } /// - /// Lockout is not enabled for this user. + /// A user with this login already exists. /// - internal static string FormatLockoutNotEnabled() + internal static string FormatLoginAlreadyAssociated() { - return GetString("LockoutNotEnabled"); + return GetString("LoginAlreadyAssociated"); } /// @@ -317,65 +333,65 @@ namespace Microsoft.AspNet.Identity /// /// Passwords must have at least one digit ('0'-'9'). /// - internal static string PasswordRequireDigit + internal static string PasswordRequiresDigit { - get { return GetString("PasswordRequireDigit"); } + get { return GetString("PasswordRequiresDigit"); } } /// /// Passwords must have at least one digit ('0'-'9'). /// - internal static string FormatPasswordRequireDigit() + internal static string FormatPasswordRequiresDigit() { - return GetString("PasswordRequireDigit"); + return GetString("PasswordRequiresDigit"); } /// /// Passwords must have at least one lowercase ('a'-'z'). /// - internal static string PasswordRequireLower + internal static string PasswordRequiresLower { - get { return GetString("PasswordRequireLower"); } + get { return GetString("PasswordRequiresLower"); } } /// /// Passwords must have at least one lowercase ('a'-'z'). /// - internal static string FormatPasswordRequireLower() + internal static string FormatPasswordRequiresLower() { - return GetString("PasswordRequireLower"); + return GetString("PasswordRequiresLower"); } /// /// Passwords must have at least one non letter and non digit character. /// - internal static string PasswordRequireNonLetterOrDigit + internal static string PasswordRequiresNonLetterAndDigit { - get { return GetString("PasswordRequireNonLetterOrDigit"); } + get { return GetString("PasswordRequiresNonLetterAndDigit"); } } /// /// Passwords must have at least one non letter and non digit character. /// - internal static string FormatPasswordRequireNonLetterOrDigit() + internal static string FormatPasswordRequiresNonLetterAndDigit() { - return GetString("PasswordRequireNonLetterOrDigit"); + return GetString("PasswordRequiresNonLetterAndDigit"); } /// /// Passwords must have at least one uppercase ('A'-'Z'). /// - internal static string PasswordRequireUpper + internal static string PasswordRequiresUpper { - get { return GetString("PasswordRequireUpper"); } + get { return GetString("PasswordRequiresUpper"); } } /// /// Passwords must have at least one uppercase ('A'-'Z'). /// - internal static string FormatPasswordRequireUpper() + internal static string FormatPasswordRequiresUpper() { - return GetString("PasswordRequireUpper"); + return GetString("PasswordRequiresUpper"); } /// @@ -651,7 +667,7 @@ namespace Microsoft.AspNet.Identity } /// - /// User already in role. + /// User already in role '{0}'. /// internal static string UserAlreadyInRole { @@ -659,11 +675,43 @@ namespace Microsoft.AspNet.Identity } /// - /// User already in role. + /// User already in role '{0}'. /// - internal static string FormatUserAlreadyInRole() + internal static string FormatUserAlreadyInRole(object p0) { - return GetString("UserAlreadyInRole"); + return string.Format(CultureInfo.CurrentCulture, GetString("UserAlreadyInRole"), p0); + } + + /// + /// User is locked out. + /// + internal static string UserLockedOut + { + get { return GetString("UserLockedOut"); } + } + + /// + /// User is locked out. + /// + internal static string FormatUserLockedOut() + { + return GetString("UserLockedOut"); + } + + /// + /// Lockout is not enabled for this user. + /// + internal static string UserLockoutNotEnabled + { + get { return GetString("UserLockoutNotEnabled"); } + } + + /// + /// Lockout is not enabled for this user. + /// + internal static string FormatUserLockoutNotEnabled() + { + return GetString("UserLockoutNotEnabled"); } /// @@ -683,7 +731,7 @@ namespace Microsoft.AspNet.Identity } /// - /// User is not in role. + /// User is not in role '{0}'. /// internal static string UserNotInRole { @@ -691,11 +739,11 @@ namespace Microsoft.AspNet.Identity } /// - /// User is not in role. + /// User is not in role '{0}'. /// - internal static string FormatUserNotInRole() + internal static string FormatUserNotInRole(object p0) { - return GetString("UserNotInRole"); + return string.Format(CultureInfo.CurrentCulture, GetString("UserNotInRole"), p0); } private static string GetString(string name, params string[] formatterNames) diff --git a/src/Microsoft.AspNet.Identity/Resources.resx b/src/Microsoft.AspNet.Identity/Resources.resx index 674ae53538..79f668637f 100644 --- a/src/Microsoft.AspNet.Identity/Resources.resx +++ b/src/Microsoft.AspNet.Identity/Resources.resx @@ -149,13 +149,13 @@ Email '{0}' is already taken. error for duplicate emails - - Name {0} is already taken. + + Role Name '{0}' is already taken. error for duplicate usernames - - A user with that external login already exists. - Error when a login already linked + + UserName '{0}' is already taken. + error for duplicate usernames Email '{0}' is invalid. @@ -169,17 +169,21 @@ The iteration count must be a positive integer. Error when the iteration count is < 1. + + Role name '{0}' is invalid. + error for invalid role names + Invalid token. Error when a token is not recognized - User name {0} is invalid, can only contain letters or digits. + User name '{0}' is invalid, can only contain letters or digits. usernames can only contain letters or digits - - Lockout is not enabled for this user. - error when lockout is not enabled + + A user with this login already exists. + Error when a login already linked No IUserMessageProvider named '{0}' is registered. @@ -193,19 +197,19 @@ Incorrect password. Error when a password doesn't match - + Passwords must have at least one digit ('0'-'9'). Error when passwords do not have a digit - + Passwords must have at least one lowercase ('a'-'z'). Error when passwords do not have a lowercase letter - + Passwords must have at least one non letter and non digit character. Error when password does not have enough letter or digit characters - + Passwords must have at least one uppercase ('A'-'Z'). Error when passwords do not have an uppercase letter @@ -213,10 +217,6 @@ Passwords must be at least {0} characters. Error message for passwords that are too short - - {0} cannot be null or empty. - error for empty or null usernames - Role {0} does not exist. error when a role does not exist @@ -278,15 +278,23 @@ error when AddPasswordAsync called when a user already has a password - User already in role. + User already in role '{0}'. Error when a user is already in a role + + User is locked out. + Error when a user is locked out + + + Lockout is not enabled for this user. + error when lockout is not enabled + User {0} does not exist. error when a user does not exist - User is not in role. + User is not in role '{0}'. Error when a user is not in the role \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs index 6b30e437fc..3189f253a4 100644 --- a/src/Microsoft.AspNet.Identity/RoleManager.cs +++ b/src/Microsoft.AspNet.Identity/RoleManager.cs @@ -24,13 +24,16 @@ namespace Microsoft.AspNet.Identity /// /// The IRoleStore commits changes via the UpdateAsync/CreateAsync methods /// - public RoleManager(IRoleStore store, IEnumerable> roleValidators) + public RoleManager(IRoleStore store, + IEnumerable> roleValidators = null, + IdentityErrorDescriber errors = null) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; + ErrorDescriber = errors ?? new IdentityErrorDescriber(); if (roleValidators != null) { @@ -51,6 +54,11 @@ namespace Microsoft.AspNet.Identity /// public IList> RoleValidators { get; } = new List>(); + /// + /// Used to generate public API error messages + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + /// /// Returns an IQueryable of roles if the store is an IQueryableRoleStore /// @@ -102,7 +110,7 @@ namespace Microsoft.AspNet.Identity private async Task ValidateRoleInternal(TRole role, CancellationToken cancellationToken) { - var errors = new List(); + var errors = new List(); foreach (var v in RoleValidators) { var result = await v.ValidateAsync(this, role, cancellationToken); diff --git a/src/Microsoft.AspNet.Identity/RoleValidator.cs b/src/Microsoft.AspNet.Identity/RoleValidator.cs index 244b7136f0..fc20865ae9 100644 --- a/src/Microsoft.AspNet.Identity/RoleValidator.cs +++ b/src/Microsoft.AspNet.Identity/RoleValidator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Threading; using System.Threading.Tasks; @@ -15,6 +14,13 @@ namespace Microsoft.AspNet.Identity /// public class RoleValidator : IRoleValidator where TRole : class { + public RoleValidator(IdentityErrorDescriber errors = null) + { + Describer = errors ?? new IdentityErrorDescriber(); + } + + private IdentityErrorDescriber Describer { get; set; } + /// /// Validates a role before saving /// @@ -33,7 +39,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("role"); } - var errors = new List(); + var errors = new List(); await ValidateRoleName(manager, role, errors); if (errors.Count > 0) { @@ -42,13 +48,13 @@ namespace Microsoft.AspNet.Identity return IdentityResult.Success; } - private static async Task ValidateRoleName(RoleManager manager, TRole role, - ICollection errors) + private async Task ValidateRoleName(RoleManager manager, TRole role, + ICollection errors) { var roleName = await manager.GetRoleNameAsync(role); if (string.IsNullOrWhiteSpace(roleName)) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "Name")); + errors.Add(Describer.InvalidRoleName(roleName)); } else { @@ -56,7 +62,7 @@ namespace Microsoft.AspNet.Identity if (owner != null && !string.Equals(await manager.GetRoleIdAsync(owner), await manager.GetRoleIdAsync(role))) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.DuplicateName, roleName)); + errors.Add(Describer.DuplicateRoleName(roleName)); } } } diff --git a/src/Microsoft.AspNet.Identity/SignInManager.cs b/src/Microsoft.AspNet.Identity/SignInManager.cs index 846fd81540..95ca0c1851 100644 --- a/src/Microsoft.AspNet.Identity/SignInManager.cs +++ b/src/Microsoft.AspNet.Identity/SignInManager.cs @@ -21,8 +21,10 @@ namespace Microsoft.AspNet.Identity /// public class SignInManager where TUser : class { - public SignInManager(UserManager userManager, IContextAccessor contextAccessor, - IClaimsIdentityFactory claimsFactory, IOptions optionsAccessor) + public SignInManager(UserManager userManager, + IContextAccessor contextAccessor, + IClaimsIdentityFactory claimsFactory, + IOptions optionsAccessor = null) { if (userManager == null) { @@ -36,14 +38,10 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException(nameof(claimsFactory)); } - if (optionsAccessor == null || optionsAccessor.Options == null) - { - throw new ArgumentNullException(nameof(optionsAccessor)); - } UserManager = userManager; Context = contextAccessor.Value; ClaimsFactory = claimsFactory; - Options = optionsAccessor.Options; + Options = optionsAccessor?.Options ?? new IdentityOptions(); } public UserManager UserManager { get; private set; } @@ -96,15 +94,15 @@ namespace Microsoft.AspNet.Identity return UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user, token); } - private async Task PreSignInCheck(TUser user, CancellationToken token) + private async Task PreSignInCheck(TUser user, CancellationToken token) { if (!await CanSignInAsync(user, token)) { - return SignInStatus.NotAllowed; + return SignInResult.NotAllowed; } if (await IsLockedOut(user, token)) { - return SignInStatus.LockedOut; + return SignInResult.LockedOut; } return null; } @@ -141,13 +139,21 @@ namespace Microsoft.AspNet.Identity return null; } - public virtual async Task PasswordSignInAsync(TUser user, string password, + public virtual async Task PasswordSignInAsync(TUser user, string password, bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken)) { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error.Value; + return error; + } + if (await IsLockedOut(user, cancellationToken)) + { + return SignInResult.LockedOut; } if (await UserManager.CheckPasswordAsync(user, password, cancellationToken)) { @@ -160,19 +166,19 @@ namespace Microsoft.AspNet.Identity await UserManager.AccessFailedAsync(user, cancellationToken); if (await UserManager.IsLockedOutAsync(user, cancellationToken)) { - return SignInStatus.LockedOut; + return SignInResult.LockedOut; } } - return SignInStatus.Failure; + return SignInResult.Failed; } - public virtual async Task PasswordSignInAsync(string userName, string password, + public virtual async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken)) { var user = await UserManager.FindByNameAsync(userName, cancellationToken); if (user == null) { - return SignInStatus.Failure; + return SignInResult.Failed; } return await PasswordSignInAsync(user, password, isPersistent, shouldLockout, cancellationToken); } @@ -207,7 +213,6 @@ namespace Microsoft.AspNet.Identity return false; } var token = await UserManager.GenerateTwoFactorTokenAsync(user, provider, cancellationToken); - // See IdentityConfig.cs to plug in Email/SMS services to actually send the code await UserManager.NotifyTwoFactorTokenAsync(user, provider, token, cancellationToken); return true; } @@ -236,23 +241,23 @@ namespace Microsoft.AspNet.Identity return Task.FromResult(0); } - public virtual async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, + public virtual async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient, CancellationToken cancellationToken = default(CancellationToken)) { var twoFactorInfo = await RetrieveTwoFactorInfoAsync(cancellationToken); if (twoFactorInfo == null || twoFactorInfo.UserId == null) { - return SignInStatus.Failure; + return SignInResult.Failed; } var user = await UserManager.FindByIdAsync(twoFactorInfo.UserId, cancellationToken); if (user == null) { - return SignInStatus.Failure; + return SignInResult.Failed; } var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error.Value; + return error; } if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code, cancellationToken)) { @@ -268,11 +273,13 @@ namespace Microsoft.AspNet.Identity { await RememberTwoFactorClientAsync(user, cancellationToken); } - return SignInStatus.Success; + await UserManager.ResetAccessFailedCountAsync(user, cancellationToken); + await SignInAsync(user, isPersistent); + return SignInResult.Success; } // If the token is incorrect, record the failure which also may cause the user to be locked out await UserManager.AccessFailedAsync(user, cancellationToken); - return SignInStatus.Failure; + return SignInResult.Failed; } /// @@ -292,18 +299,18 @@ namespace Microsoft.AspNet.Identity return await UserManager.FindByIdAsync(info.UserId, cancellationToken); } - public virtual async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, + public async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, CancellationToken cancellationToken = default(CancellationToken)) { var user = await UserManager.FindByLoginAsync(loginProvider, providerKey, cancellationToken); if (user == null) { - return SignInStatus.Failure; + return SignInResult.Failed; } var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error.Value; + return error; } return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider); } @@ -358,7 +365,7 @@ namespace Microsoft.AspNet.Identity return properties; } - private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, + private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, CancellationToken cancellationToken, string loginProvider = null) { if (UserManager.SupportsUserTwoFactor && @@ -370,7 +377,7 @@ namespace Microsoft.AspNet.Identity // Store the userId for use after two factor check var userId = await UserManager.GetUserIdAsync(user, cancellationToken); Context.Response.SignIn(StoreTwoFactorInfo(userId, loginProvider)); - return SignInStatus.RequiresVerification; + return SignInResult.TwoFactorRequired; } } // Cleanup external cookie @@ -379,7 +386,7 @@ namespace Microsoft.AspNet.Identity Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationType); } await SignInAsync(user, isPersistent, loginProvider, cancellationToken); - return SignInStatus.Success; + return SignInResult.Success; } private async Task RetrieveTwoFactorInfoAsync(CancellationToken cancellationToken) diff --git a/src/Microsoft.AspNet.Identity/SignInResult.cs b/src/Microsoft.AspNet.Identity/SignInResult.cs new file mode 100644 index 0000000000..e125690fe7 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/SignInResult.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Identity +{ + /// + /// Represents the result of an sign in operation + /// + public class SignInResult + { + private static readonly SignInResult _success = new SignInResult { Succeeded = true }; + private static readonly SignInResult _failed = new SignInResult(); + private static readonly SignInResult _lockedOut = new SignInResult { IsLockedOut = true }; + private static readonly SignInResult _notAllowed = new SignInResult { IsNotAllowed = true }; + private static readonly SignInResult _twoFactorRequired = new SignInResult { RequiresTwoFactor = true }; + + /// + /// True if the operation was successful + /// + public bool Succeeded { get; protected set; } + + /// + /// True if the user is locked out + /// + public bool IsLockedOut { get; protected set; } + + /// + /// True if the user is not allowed to sign in + /// + public bool IsNotAllowed { get; protected set; } + + /// + /// True if the sign in requires two factor + /// + public bool RequiresTwoFactor { get; protected set; } + + /// + /// Static success result + /// + /// + public static SignInResult Success + { + get { return _success; } + } + + /// + /// Static failure result + /// + /// + public static SignInResult Failed + { + get { return _failed; } + } + + /// + /// Static locked out result + /// + /// + public static SignInResult LockedOut + { + get { return _lockedOut; } + } + + /// + /// Static not allowed result + /// + /// + public static SignInResult NotAllowed + { + get { return _notAllowed; } + } + + /// + /// Static two factor required result + /// + /// + public static SignInResult TwoFactorRequired + { + get { return _twoFactorRequired; } + } + + + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index 787f91ebeb..6caed539c2 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -39,30 +39,24 @@ namespace Microsoft.AspNet.Identity /// /// public UserManager(IUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - IUserNameNormalizer userNameNormalizer, - IEnumerable> tokenProviders, - IEnumerable msgProviders) + IOptions optionsAccessor = null, + IPasswordHasher passwordHasher = null, + IEnumerable> userValidators = null, + IEnumerable> passwordValidators = null, + IUserNameNormalizer userNameNormalizer = null, + IdentityErrorDescriber errors = null, + IEnumerable> tokenProviders = null, + IEnumerable msgProviders = null) { if (store == null) { throw new ArgumentNullException(nameof(store)); } - if (optionsAccessor == null || optionsAccessor.Options == null) - { - throw new ArgumentNullException(nameof(optionsAccessor)); - } - if (passwordHasher == null) - { - throw new ArgumentNullException(nameof(passwordHasher)); - } Store = store; - Options = optionsAccessor.Options; - PasswordHasher = passwordHasher; - UserNameNormalizer = userNameNormalizer; + Options = optionsAccessor?.Options ?? new IdentityOptions(); + PasswordHasher = passwordHasher ?? new PasswordHasher(); + UserNameNormalizer = userNameNormalizer ?? new UpperInvariantUserNameNormalizer(); + ErrorDescriber = errors ?? new IdentityErrorDescriber(); if (userValidators != null) { foreach (var v in userValidators) @@ -136,6 +130,11 @@ namespace Microsoft.AspNet.Identity /// public IUserNameNormalizer UserNameNormalizer { get; set; } + /// + /// Used to generate public API error messages + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + public IdentityOptions Options { get @@ -301,7 +300,7 @@ namespace Microsoft.AspNet.Identity private async Task ValidateUserInternal(TUser user, CancellationToken cancellationToken) { - var errors = new List(); + var errors = new List(); foreach (var v in UserValidators) { var result = await v.ValidateAsync(this, user, cancellationToken); @@ -315,7 +314,7 @@ namespace Microsoft.AspNet.Identity private async Task ValidatePasswordInternal(TUser user, string password, CancellationToken cancellationToken) { - var errors = new List(); + var errors = new List(); foreach (var v in PasswordValidators) { var result = await v.ValidateAsync(this, user, password, cancellationToken); @@ -598,7 +597,7 @@ namespace Microsoft.AspNet.Identity var hash = await passwordStore.GetPasswordHashAsync(user, cancellationToken); if (hash != null) { - return new IdentityResult(Resources.UserAlreadyHasPassword); + return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword()); } var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken); if (!result.Succeeded) @@ -634,7 +633,7 @@ namespace Microsoft.AspNet.Identity } return await UpdateAsync(user, cancellationToken); } - return IdentityResult.Failed(Resources.PasswordMismatch); + return IdentityResult.Failed(ErrorDescriber.PasswordMismatch()); } /// @@ -766,7 +765,7 @@ namespace Microsoft.AspNet.Identity // Make sure the token is valid and the stamp matches if (!await VerifyUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", token, cancellationToken)) { - return IdentityResult.Failed(Resources.InvalidToken); + return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } var passwordStore = GetPasswordStore(); var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken); @@ -877,7 +876,7 @@ namespace Microsoft.AspNet.Identity var existingUser = await FindByLoginAsync(login.LoginProvider, login.ProviderKey, cancellationToken); if (existingUser != null) { - return IdentityResult.Failed(Resources.ExternalLoginExists); + return IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated()); } await loginStore.AddLoginAsync(user, login, cancellationToken); return await UpdateAsync(user, cancellationToken); @@ -1082,7 +1081,7 @@ namespace Microsoft.AspNet.Identity var userRoles = await userRoleStore.GetRolesAsync(user, cancellationToken); if (userRoles.Contains(role)) { - return new IdentityResult(Resources.UserAlreadyInRole); + return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); return await UpdateAsync(user, cancellationToken); @@ -1113,7 +1112,7 @@ namespace Microsoft.AspNet.Identity { if (userRoles.Contains(role)) { - return new IdentityResult(Resources.UserAlreadyInRole); + return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); } @@ -1138,7 +1137,7 @@ namespace Microsoft.AspNet.Identity } if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { - return new IdentityResult(Resources.UserNotInRole); + return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); return await UpdateAsync(user, cancellationToken); @@ -1168,7 +1167,7 @@ namespace Microsoft.AspNet.Identity { if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { - return new IdentityResult(Resources.UserNotInRole); + return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); } @@ -1313,7 +1312,7 @@ namespace Microsoft.AspNet.Identity } if (!await VerifyUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", token, cancellationToken)) { - return IdentityResult.Failed(Resources.InvalidToken); + return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } await store.SetEmailConfirmedAsync(user, true, cancellationToken); return await UpdateAsync(user, cancellationToken); @@ -1374,7 +1373,7 @@ namespace Microsoft.AspNet.Identity // Make sure the token is valid and the stamp matches if (!await VerifyUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), token, cancellationToken)) { - return IdentityResult.Failed(Resources.InvalidToken); + return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } var store = GetEmailStore(); await store.SetEmailAsync(user, newEmail, cancellationToken); @@ -1453,7 +1452,7 @@ namespace Microsoft.AspNet.Identity } if (!await VerifyChangePhoneNumberTokenAsync(user, token, phoneNumber, cancellationToken)) { - return IdentityResult.Failed(Resources.InvalidToken); + return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken); await store.SetPhoneNumberConfirmedAsync(user, true, cancellationToken); @@ -1890,7 +1889,7 @@ namespace Microsoft.AspNet.Identity } if (!await store.GetLockoutEnabledAsync(user, cancellationToken).ConfigureAwait((false))) { - return IdentityResult.Failed(Resources.LockoutNotEnabled); + return IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled()); } await store.SetLockoutEndDateAsync(user, lockoutEnd, cancellationToken); return await UpdateAsync(user, cancellationToken); diff --git a/src/Microsoft.AspNet.Identity/UserValidator.cs b/src/Microsoft.AspNet.Identity/UserValidator.cs index 749f3a9f3f..06ab306683 100644 --- a/src/Microsoft.AspNet.Identity/UserValidator.cs +++ b/src/Microsoft.AspNet.Identity/UserValidator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; #if ASPNET50 using System.Net.Mail; #endif @@ -19,6 +18,13 @@ namespace Microsoft.AspNet.Identity /// public class UserValidator : IUserValidator where TUser : class { + public UserValidator(IdentityErrorDescriber errors = null) + { + Describer = errors ?? new IdentityErrorDescriber(); + } + + public IdentityErrorDescriber Describer { get; private set; } + /// /// Validates a user before saving /// @@ -37,7 +43,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - var errors = new List(); + var errors = new List(); await ValidateUserName(manager, user, errors); if (manager.Options.User.RequireUniqueEmail) { @@ -46,16 +52,16 @@ namespace Microsoft.AspNet.Identity return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; } - private async Task ValidateUserName(UserManager manager, TUser user, ICollection errors) + private async Task ValidateUserName(UserManager manager, TUser user, ICollection errors) { var userName = await manager.GetUserNameAsync(user); if (string.IsNullOrWhiteSpace(userName)) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "UserName")); + errors.Add(Describer.InvalidUserName(userName)); } else if (manager.Options.User.UserNameValidationRegex != null && !Regex.IsMatch(userName, manager.Options.User.UserNameValidationRegex)) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.InvalidUserName, userName)); + errors.Add(Describer.InvalidUserName(userName)); } else { @@ -63,18 +69,18 @@ namespace Microsoft.AspNet.Identity if (owner != null && !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user))) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.DuplicateName, userName)); + errors.Add(Describer.DuplicateUserName(userName)); } } } // make sure email is not empty, valid, and unique - private static async Task ValidateEmail(UserManager manager, TUser user, List errors) + private async Task ValidateEmail(UserManager manager, TUser user, List errors) { var email = await manager.GetEmailAsync(user); if (string.IsNullOrWhiteSpace(email)) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "Email")); + errors.Add(Describer.InvalidEmail(email)); return; } #if ASPNET50 @@ -84,7 +90,7 @@ namespace Microsoft.AspNet.Identity } catch (FormatException) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.InvalidEmail, email)); + errors.Add(Describer.InvalidEmail(email)); return; } #endif @@ -92,7 +98,7 @@ namespace Microsoft.AspNet.Identity if (owner != null && !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user))) { - errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.DuplicateEmail, email)); + errors.Add(Describer.DuplicateEmail(email)); } } } diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs index ade7e69e73..f9f871fdc8 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs @@ -50,10 +50,10 @@ namespace Microsoft.AspNet.Identity.InMemory.Test var signInManager = app.ApplicationServices.GetRequiredService>(); IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); - var result = await signInManager.PasswordSignInAsync(user.UserName, password, isPersistent, false); + var result = await signInManager.PasswordSignInAsync(user, password, isPersistent, false); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); context.VerifyAll(); response.VerifyAll(); contextAccessor.VerifyAll(); diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs index 7843b95019..1d0b2fcc90 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs @@ -226,18 +226,7 @@ namespace Microsoft.AspNet.Identity.Test private class MyUserManager : UserManager { - public MyUserManager(IUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - IUserNameNormalizer userNameNormalizer, - IEnumerable> tokenProviders, - IEnumerable msgProviders) : - base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, userNameNormalizer, tokenProviders, msgProviders) - { - - } + public MyUserManager(IUserStore store) : base(store) { } } private class MyRoleManager : RoleManager diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs index 0fd8e3bddd..7d24b52bce 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs @@ -13,17 +13,15 @@ namespace Microsoft.AspNet.Identity.Test { var result = new IdentityResult(); Assert.False(result.Succeeded); - Assert.Equal(1, result.Errors.Count()); - Assert.Equal("An unknown failure has occured.", result.Errors.First()); + Assert.Equal(0, result.Errors.Count()); } [Fact] - public void NullErrorListUsesDefaultError() + public void NullFailedUsesEmptyErrors() { - var result = new IdentityResult(null); + var result = IdentityResult.Failed(); Assert.False(result.Succeeded); - Assert.Equal(1, result.Errors.Count()); - Assert.Equal("An unknown failure has occured.", result.Errors.First()); + Assert.Equal(0, result.Errors.Count()); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs index 220fb2125b..f8c37de273 100644 --- a/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/PasswordValidatorTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Xunit; @@ -133,13 +134,18 @@ namespace Microsoft.AspNet.Identity.Test { errors.Add(upperError); } + var result = await valid.ValidateAsync(manager, null, input); if (errors.Count == 0) { - IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input)); + IdentityResultAssert.IsSuccess(result); } else { - IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input), string.Join(" ", errors)); + IdentityResultAssert.IsFailure(result); + foreach (var error in errors) + { + Assert.True(result.Errors.Any(e => e.Description == error)); + } } } } diff --git a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs index c2f47f6870..75a84c24cb 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await validator.ValidateAsync(manager, user); // Assert - IdentityResultAssert.IsFailure(result, "Name cannot be null or empty."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.InvalidRoleName(input)); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs index 629311bf23..60876733cc 100644 --- a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs @@ -1,17 +1,17 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Http.Security; -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.OptionsModel; -using Moq; using System; using System.Collections.Generic; using System.Security.Claims; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Security; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.OptionsModel; +using Moq; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -75,8 +75,6 @@ namespace Microsoft.AspNet.Identity.Test var context = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); Assert.Throws("claimsFactory", () => new SignInManager(userManager, contextAccessor.Object, null, null)); - var claimsFactory = new Mock>().Object; - Assert.Throws("optionsAccessor", () => new SignInManager(userManager, contextAccessor.Object, claimsFactory, null)); } //TODO: Mock fails in K (this works fine in net45) @@ -132,7 +130,8 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); // Assert - Assert.Equal(SignInStatus.LockedOut, result); + Assert.False(result.Succeeded); + Assert.True(result.IsLockedOut); manager.VerifyAll(); } @@ -167,7 +166,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -203,7 +202,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -251,7 +250,8 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); // Assert - Assert.Equal(SignInStatus.RequiresVerification, result); + Assert.False(result.Succeeded); + Assert.True(result.RequiresTwoFactor); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -297,7 +297,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -382,7 +382,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.TwoFactorSignInAsync(provider, code, isPersistent, rememberClient); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -466,7 +466,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); // Assert - Assert.Equal(SignInStatus.Success, result); + Assert.True(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -530,7 +530,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); // Assert - Assert.Equal(SignInStatus.Failure, result); + Assert.False(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); contextAccessor.VerifyAll(); @@ -556,7 +556,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync("bogus", "bogus", false, false); // Assert - Assert.Equal(SignInStatus.Failure, result); + Assert.False(result.Succeeded); manager.VerifyAll(); context.VerifyAll(); contextAccessor.VerifyAll(); @@ -592,7 +592,8 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, true); // Assert - Assert.Equal(SignInStatus.LockedOut, result); + Assert.False(result.Succeeded); + Assert.True(result.IsLockedOut); manager.VerifyAll(); } @@ -631,7 +632,9 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user, "password", false, false); // Assert - Assert.Equal(confirmed ? SignInStatus.Success : SignInStatus.NotAllowed, result); + + Assert.Equal(confirmed, result.Succeeded); + Assert.NotEqual(confirmed, result.IsNotAllowed); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -670,7 +673,8 @@ namespace Microsoft.AspNet.Identity.Test var result = await helper.PasswordSignInAsync(user, "password", false, false); // Assert - Assert.Equal(confirmed ? SignInStatus.Success : SignInStatus.NotAllowed, result); + Assert.Equal(confirmed, result.Succeeded); + Assert.NotEqual(confirmed, result.IsNotAllowed); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index de496f8ff7..21cdf3d03d 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -1,16 +1,15 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; -using Microsoft.Framework.OptionsModel; -using Moq; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; +using Moq; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -21,10 +20,7 @@ namespace Microsoft.AspNet.Identity.Test { public IUserStore StorePublic { get { return Store; } } - public TestManager(IUserStore store, IOptions optionsAccessor, - IPasswordHasher passwordHasher, IEnumerable> userValidator, - IEnumerable> passwordValidator) - : base(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null, null) { } + public TestManager(IUserStore store) : base(store) { } } [Fact] @@ -36,8 +32,6 @@ namespace Microsoft.AspNet.Identity.Test services.AddIdentity(); var manager = services.BuildServiceProvider().GetRequiredService(); Assert.NotNull(manager.PasswordHasher); - Assert.Equal(1, manager.PasswordValidators.Count); - Assert.Equal(1, manager.UserValidators.Count); Assert.NotNull(manager.StorePublic); Assert.NotNull(manager.Options); } @@ -66,7 +60,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; store.Setup(s => s.DeleteAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.DeleteAsync(user); @@ -100,7 +94,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser(); store.Setup(s => s.SetUserNameAsync(user, It.IsAny(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.SetUserNameAsync(user, "foo"); @@ -207,7 +201,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await userManager.AddToRolesAsync(user, roles); // Assert - IdentityResultAssert.IsFailure(result, "User already in role."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserAlreadyInRole("B")); store.VerifyAll(); } @@ -269,7 +263,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await userManager.RemoveFromRolesAsync(user, roles); // Assert - IdentityResultAssert.IsFailure(result, "User is not in role."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserNotInRole("B")); store.VerifyAll(); } @@ -284,7 +278,7 @@ namespace Microsoft.AspNet.Identity.Test .Returns(Task.FromResult(0)) .Verifiable(); store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.AddClaimsAsync(user, claims); @@ -305,7 +299,7 @@ namespace Microsoft.AspNet.Identity.Test .Returns(Task.FromResult(0)) .Verifiable(); store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.AddClaimAsync(user, claim); @@ -326,7 +320,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.ReplaceClaimAsync(user, It.IsAny(), It.IsAny(), CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.ReplaceClaimAsync(user, claim, newClaim); @@ -533,17 +527,11 @@ namespace Microsoft.AspNet.Identity.Test public async Task ManagerPublicNullChecks() { var store = new NotImplementedStore(); - var optionsAccessor = new OptionsManager(null); - var passwordHasher = new PasswordHasher(new PasswordHasherOptionsAccessor()); Assert.Throws("store", - () => new UserManager(null, null, null, null, null, null, null, null)); - Assert.Throws("optionsAccessor", - () => new UserManager(store, null, null, null, null, null, null, null)); - Assert.Throws("passwordHasher", - () => new UserManager(store, optionsAccessor, null, null, null, null, null, null)); + () => new UserManager(null)); - var manager = new UserManager(store, optionsAccessor, passwordHasher, null, null, null, null, null); + var manager = new UserManager(store); Assert.Throws("value", () => manager.PasswordHasher = null); Assert.Throws("value", () => manager.Options = null); @@ -714,7 +702,7 @@ namespace Microsoft.AspNet.Identity.Test private class BadPasswordValidator : IPasswordValidator where TUser : class { - public const string ErrorMessage = "I'm Bad."; + public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad." }; public Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { @@ -1239,5 +1227,51 @@ namespace Microsoft.AspNet.Identity.Test throw new NotImplementedException(); } } + + [Fact] + public async Task CanCustomizeUserValidatorErrors() + { + var services = new ServiceCollection(); + var store = new Mock>(); + var describer = new TestErrorDescriber(); + services.AddInstance(describer) + .AddInstance>(store.Object) + .AddIdentity(); + + var manager = services.BuildServiceProvider().GetRequiredService>(); + + manager.Options.User.RequireUniqueEmail = true; + var user = new TestUser() { UserName = "dupeEmail", Email = "dupe@email.com" }; + var user2 = new TestUser() { UserName = "dupeEmail2", Email = "dupe@email.com" }; + store.Setup(s => s.FindByEmailAsync(user.Email, CancellationToken.None)) + .Returns(Task.FromResult(user2)) + .Verifiable(); + store.Setup(s => s.GetUserIdAsync(user2, CancellationToken.None)) + .Returns(Task.FromResult(user2.Id)) + .Verifiable(); + store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)) + .Returns(Task.FromResult(user.UserName)) + .Verifiable(); + store.Setup(s => s.GetEmailAsync(user, CancellationToken.None)) + .Returns(Task.FromResult(user.Email)) + .Verifiable(); + + Assert.Same(describer, manager.ErrorDescriber); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), describer.DuplicateEmail(user.Email)); + + store.VerifyAll(); + } + + public class TestErrorDescriber : IdentityErrorDescriber + { + public static string Code = "Error"; + public static string FormatError = "FormatError {0}"; + + public override IdentityError DuplicateEmail(string email) + { + return new IdentityError { Code = Code, Description = string.Format(FormatError, email) }; + } + } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs index 793dd36598..093ecb5131 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Identity.Test var result = await validator.ValidateAsync(manager, user); // Assert - IdentityResultAssert.IsFailure(result, "UserName cannot be null or empty."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.InvalidUserName(input)); } [Theory] diff --git a/test/Shared/IdentityResultAssert.cs b/test/Shared/IdentityResultAssert.cs index eb92499bf1..617e5c0c6b 100644 --- a/test/Shared/IdentityResultAssert.cs +++ b/test/Shared/IdentityResultAssert.cs @@ -24,7 +24,16 @@ namespace Microsoft.AspNet.Identity.Test { Assert.NotNull(result); Assert.False(result.Succeeded); - Assert.Equal(error, result.Errors.First()); + Assert.Equal(error, result.Errors.First().Description); } + + public static void IsFailure(IdentityResult result, IdentityError error) + { + Assert.NotNull(result); + Assert.False(result.Succeeded); + Assert.Equal(error.Description, result.Errors.First().Description); + Assert.Equal(error.Code, result.Errors.First().Code); + } + } } \ No newline at end of file diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs index 87d113c96a..4a73bba084 100644 --- a/test/Shared/MockHelpers.cs +++ b/test/Shared/MockHelpers.cs @@ -14,20 +14,10 @@ namespace Microsoft.AspNet.Identity.Test public static Mock> MockUserManager() where TUser : class { var store = new Mock>(); - var options = new OptionsManager(null); - var userValidators = new List>(); - userValidators.Add(new UserValidator()); - var pwdValidators = new List>(); - pwdValidators.Add(new PasswordValidator()); - return new Mock>( - store.Object, - options, - new PasswordHasher(new PasswordHasherOptionsAccessor()), - userValidators, - pwdValidators, - new UpperInvariantUserNameNormalizer(), - new List>(), - new List()); + var mgr = new Mock>(store.Object, null, null, null, null, null, null, null, null); + mgr.Object.UserValidators.Add(new UserValidator()); + mgr.Object.PasswordValidators.Add(new PasswordValidator()); + return mgr; } public static Mock> MockRoleManager() where TRole : class @@ -35,7 +25,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var roles = new List>(); roles.Add(new RoleValidator()); - return new Mock>(store.Object, roles); + return new Mock>(store.Object, roles, null); } public static UserManager TestUserManager() where TUser : class @@ -45,10 +35,8 @@ namespace Microsoft.AspNet.Identity.Test public static UserManager TestUserManager(IUserStore store) where TUser : class { - var options = new OptionsManager(null); - var validator = new Mock>(); - var userManager = new UserManager(store, options, new PasswordHasher(new PasswordHasherOptionsAccessor()), - null, null, new UpperInvariantUserNameNormalizer(), null, null); + var validator = new Mock>(); + var userManager = new UserManager(store); userManager.UserValidators.Add(validator.Object); userManager.PasswordValidators.Add(new PasswordValidator()); validator.Setup(v => v.ValidateAsync(userManager, It.IsAny(), CancellationToken.None)) diff --git a/test/Shared/TestUser.cs b/test/Shared/TestUser.cs index 19b7e391f4..40436a8361 100644 --- a/test/Shared/TestUser.cs +++ b/test/Shared/TestUser.cs @@ -14,5 +14,6 @@ namespace Microsoft.AspNet.Identity.Test public string Id { get; private set; } public string UserName { get; set; } + public string Email { get; set; } } } \ No newline at end of file diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index 8997be452f..3b2dd143f6 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -182,7 +182,7 @@ namespace Microsoft.AspNet.Identity.Test var manager = CreateManager(); var user = CreateTestUser(); manager.Options.User.RequireUniqueEmail = true; - IdentityResultAssert.IsFailure(await manager.CreateAsync(user), "Email cannot be null or empty."); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), IdentityErrorDescriber.Default.InvalidEmail(email)); } #if ASPNET50 @@ -194,7 +194,7 @@ namespace Microsoft.AspNet.Identity.Test var manager = CreateManager(); var user = new TUser() { UserName = "UpdateBlocked", Email = email }; manager.Options.User.RequireUniqueEmail = true; - IdentityResultAssert.IsFailure(await manager.CreateAsync(user), "Email '" + email + "' is invalid."); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), IdentityErrorDescriber.Default.InvalidEmail(email)); } #endif @@ -414,7 +414,7 @@ namespace Microsoft.AspNet.Identity.Test var user = CreateTestUser(); var user2 = new TUser() { UserName = user.UserName }; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), "Name "+user.UserName+" is already taken."); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), IdentityErrorDescriber.Default.DuplicateUserName(user.UserName)); } [Fact] @@ -440,7 +440,7 @@ namespace Microsoft.AspNet.Identity.Test user.Email = email; user2.Email = email; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), "Email '"+email+"' is already taken."); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), IdentityErrorDescriber.Default.DuplicateEmail(user.Email)); } [Fact] @@ -465,7 +465,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); var result = await manager.AddLoginAsync(user, login); - IdentityResultAssert.IsFailure(result, "A user with that external login already exists."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.LoginAlreadyAssociated()); } // Email tests @@ -842,7 +842,7 @@ namespace Microsoft.AspNet.Identity.Test private class AlwaysBadValidator : IUserValidator, IRoleValidator, IPasswordValidator { - public const string ErrorMessage = "I'm Bad."; + public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad." }; public Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { @@ -1142,7 +1142,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); var result = await userMgr.RemoveFromRoleAsync(user, role.Name); - IdentityResultAssert.IsFailure(result, "User is not in role."); + IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserNotInRole(role.Name)); } [Fact] @@ -1157,7 +1157,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); Assert.True(await userMgr.IsInRoleAsync(user, role.Name)); - IdentityResultAssert.IsFailure(await userMgr.AddToRoleAsync(user, role.Name), "User already in role."); + IdentityResultAssert.IsFailure(await userMgr.AddToRoleAsync(user, role.Name), IdentityErrorDescriber.Default.UserAlreadyInRole(role.Name)); } [Fact] From fd6eb9af672ea9e3b8aa42ff8ddb2a25633f0abc Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Fri, 2 Jan 2015 11:57:01 -0800 Subject: [PATCH 09/31] Fix issue with Remove/Replace claims Fixes #304 --- .../UserStore.cs | 4 +- test/Shared/UserManagerTestBase.cs | 58 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs index f1cf95a2c4..8dfe8ba828 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs @@ -437,7 +437,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new ArgumentNullException("newClaim"); } - var matchedClaims = await UserClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); + 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; @@ -458,7 +458,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework } foreach (var claim in claims) { - var matchedClaims = await UserClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); + 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); diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index 3b2dd143f6..2b27a389b1 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -378,6 +378,35 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(0, userClaims.Count); } + [Fact] + public async Task RemoveClaimOnlyAffectsUser() + { + 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"), new Claim("c2", "v2"), new Claim("c2", "v3") }; + 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 CanReplaceUserClaim() { @@ -397,6 +426,35 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(claim.Value, newClaim.Value); } + [Fact] + public async Task ReplaceUserClaimOnlyAffectsUser() + { + var manager = CreateManager(); + var user = CreateTestUser(); + var user2 = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("c", "a"))); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user2, new Claim("c", "a"))); + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + var userClaims2 = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims2.Count); + Claim claim = new Claim("c", "b"); + 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); + userClaims2 = await manager.GetClaimsAsync(user2); + Assert.Equal(1, userClaims2.Count); + Claim oldClaim2 = userClaims2.FirstOrDefault(); + Assert.Equal("c", oldClaim2.Type); + Assert.Equal("a", oldClaim2.Value); + } + [Fact] public async Task ChangePasswordFallsIfPasswordWrong() { From 309fce19d7da136e527d6088ad51762975899835 Mon Sep 17 00:00:00 2001 From: Barry Dorrans Date: Tue, 6 Jan 2015 15:40:08 -0800 Subject: [PATCH 10/31] XML Doc Update (one by one for ease of eventual merging) --- src/Microsoft.AspNet.Identity/BuilderExtensions.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNet.Identity/BuilderExtensions.cs b/src/Microsoft.AspNet.Identity/BuilderExtensions.cs index 4049f04ffd..13b2df4b91 100644 --- a/src/Microsoft.AspNet.Identity/BuilderExtensions.cs +++ b/src/Microsoft.AspNet.Identity/BuilderExtensions.cs @@ -7,10 +7,15 @@ using Microsoft.AspNet.Identity; namespace Microsoft.AspNet.Builder { /// - /// Startup extensions + /// Identity extensions for . /// public static class BuilderExtensions { + /// + /// Enables the ASP.NET identity for the current application. + /// + /// The instance this method extends. + /// The instance this method extends. public static IApplicationBuilder UseIdentity(this IApplicationBuilder app) { if (app == null) From cf68fac658f40da174243c93e6b7ef59a7e9dcc6 Mon Sep 17 00:00:00 2001 From: Barry Dorrans Date: Tue, 6 Jan 2015 15:40:24 -0800 Subject: [PATCH 11/31] XML Doc Update (one by one for ease of eventual merging) --- .../ClaimsIdentityExtensions.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.AspNet.Identity/ClaimsIdentityExtensions.cs b/src/Microsoft.AspNet.Identity/ClaimsIdentityExtensions.cs index 63cd7b03f3..23655ddcff 100644 --- a/src/Microsoft.AspNet.Identity/ClaimsIdentityExtensions.cs +++ b/src/Microsoft.AspNet.Identity/ClaimsIdentityExtensions.cs @@ -6,15 +6,16 @@ using System.Security.Claims; namespace System.Security.Principal { /// - /// Extensions making it easier to get the user name/user id claims off of an identity + /// Claims related extensions for . /// public static class ClaimsIdentityExtensions { /// - /// Return the user name using the UserNameClaimType + /// Returns the Name claim value if present otherwise returns null. /// - /// - /// + /// The instance this method extends. + /// The Name claim value, or null if the claim is not present. + /// The name claim is identified by . public static string GetUserName(this IIdentity identity) { if (identity == null) @@ -26,10 +27,11 @@ namespace System.Security.Principal } /// - /// Return the user id using the UserIdClaimType + /// Returns the User ID claim value if present otherwise returns null. /// - /// - /// + /// The instance this method extends. + /// The User ID claim value, or null if the claim is not present. + /// The name claim is identified by . public static string GetUserId(this IIdentity identity) { if (identity == null) @@ -41,11 +43,11 @@ namespace System.Security.Principal } /// - /// Return the claim value for the first claim with the specified type if it exists, null otherwise + /// Returns the value for the first claim of the specified type otherwise null the claim is not present. /// - /// - /// - /// + /// The instance this method extends. + /// The claim type whose first value should be returned. + /// The value of the first instance of the specifed claim type, or null if the claim is not present. public static string FindFirstValue(this ClaimsIdentity identity, string claimType) { if (identity == null) From e76d1c280964af429e61181c0ab75f337da21e1c Mon Sep 17 00:00:00 2001 From: Barry Dorrans Date: Wed, 7 Jan 2015 12:28:35 -0800 Subject: [PATCH 12/31] XMLDoc updates. --- .../BuilderExtensions.cs | 1 + .../ClaimsIdentityFactory.cs | 49 +++++++++++++++---- .../ClaimsIdentityOptions.cs | 26 ++++++++-- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.AspNet.Identity/BuilderExtensions.cs b/src/Microsoft.AspNet.Identity/BuilderExtensions.cs index 13b2df4b91..d4d184e759 100644 --- a/src/Microsoft.AspNet.Identity/BuilderExtensions.cs +++ b/src/Microsoft.AspNet.Identity/BuilderExtensions.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 Microsoft.AspNet.Identity; namespace Microsoft.AspNet.Builder diff --git a/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs b/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs index 9fc5cb760f..bda8839ac8 100644 --- a/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs +++ b/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs @@ -5,19 +5,29 @@ using System; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; + using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Identity { /// - /// Creates a ClaimsIdentity from a User + /// Provides methods to create a claims identity for a given user. /// - /// + /// The type used to represent a user. + /// The type used to represent a role. public class ClaimsIdentityFactory : IClaimsIdentityFactory where TUser : class where TRole : class { - public ClaimsIdentityFactory(UserManager userManager, RoleManager roleManager, + /// + /// Initializes a new instance of the class. + /// + /// The to retrieve user information from. + /// The to retrieve a user's roles from. + /// The configured . + public ClaimsIdentityFactory( + UserManager userManager, + RoleManager roleManager, IOptions optionsAccessor) { if (userManager == null) @@ -37,19 +47,38 @@ namespace Microsoft.AspNet.Identity Options = optionsAccessor.Options; } + /// + /// Gets the for this factory. + /// + /// + /// The current for this factory instance. + /// public UserManager UserManager { get; private set; } + + /// + /// Gets the for this factory. + /// + /// + /// The current for this factory instance. + /// public RoleManager RoleManager { get; private set; } + + /// + /// Gets the for this factory. + /// + /// + /// The current for this factory instance. + /// public IdentityOptions Options { get; private set; } /// - /// CreateAsync a ClaimsIdentity from a user + /// Creates a populated for the specified . /// - /// - /// - /// - /// - /// - public virtual async Task CreateAsync(TUser user, + /// The user instance to create claims on. + /// A to observe while waiting for the tasks to complete. + /// A that represents the started task. + public virtual async Task CreateAsync( + TUser user, CancellationToken cancellationToken = default(CancellationToken)) { if (user == null) diff --git a/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs b/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs index 9b8dd3c24f..be0df88958 100644 --- a/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs +++ b/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs @@ -5,28 +5,46 @@ using System.Security.Claims; namespace Microsoft.AspNet.Identity { + /// + /// Options for ClaimType names. + /// public class ClaimsIdentityOptions { + /// + /// The ClaimType used for the identity security stamp. + /// public static readonly string DefaultSecurityStampClaimType = "AspNet.Identity.SecurityStamp"; /// - /// Claim type used for role claims + /// Gets the ClaimType used for a Role claim. /// + /// + /// This defaults to . + /// public string RoleClaimType { get; set; } = ClaimTypes.Role; /// - /// Claim type used for the user name + /// Gets the ClaimType used for the user name claim. /// + /// + /// This defaults to . + /// public string UserNameClaimType { get; set; } = ClaimTypes.Name; /// - /// Claim type used for the user id + /// Gets the ClaimType used for the user identifier claim. /// + /// + /// This defaults to . + /// public string UserIdClaimType { get; set; } = ClaimTypes.NameIdentifier; /// - /// Claim type used for the user security stamp + /// Gets the ClaimType used for the security stamp claim.. /// + /// + /// This defaults to "AspNet.Identity.SecurityStamp". + /// public string SecurityStampClaimType { get; set; } = DefaultSecurityStampClaimType; } } \ No newline at end of file From a8c872aba97e641d38ba2cc098a4ac191a666b6a Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Wed, 7 Jan 2015 13:17:36 -0800 Subject: [PATCH 13/31] Concurrency support for EF Store - CRUD operations on IUserStore now return IdentityResult - Fixes: https://github.com/aspnet/Identity/issues/296, https://github.com/aspnet/Identity/issues/245 --- .../IdentityDbContext.cs | 2 + .../RoleStore.cs | 42 ++++++-- .../UserStore.cs | 47 ++++++-- src/Microsoft.AspNet.Identity/IRoleStore.cs | 6 +- src/Microsoft.AspNet.Identity/IUserStore.cs | 6 +- .../IdentityErrorDescriber.cs | 9 ++ src/Microsoft.AspNet.Identity/IdentityRole.cs | 5 + src/Microsoft.AspNet.Identity/IdentityUser.cs | 5 + .../Properties/Resources.Designer.cs | 32 +++--- .../Properties/debugSettings.json | 3 + src/Microsoft.AspNet.Identity/Resources.resx | 4 + src/Microsoft.AspNet.Identity/RoleManager.cs | 11 +- src/Microsoft.AspNet.Identity/UserManager.cs | 15 +-- .../UserStoreTest.cs | 102 ++++++++++++++++++ .../InMemoryRoleStore.cs | 12 +-- .../InMemoryUserStore.cs | 12 +-- .../IdentityBuilderTest.cs | 12 +-- .../NoopRoleStore.cs | 12 +-- .../NoopUserStore.cs | 12 +-- .../RoleManagerTest.cs | 6 +- .../UserManagerTest.cs | 66 ++++++------ 21 files changed, 302 insertions(+), 119 deletions(-) create mode 100644 src/Microsoft.AspNet.Identity/Properties/debugSettings.json diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs b/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs index baf7a99e6e..65f8e2fe2c 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs @@ -30,12 +30,14 @@ namespace Microsoft.AspNet.Identity.EntityFramework { b.Key(u => u.Id); b.ForRelational().Table("AspNetUsers"); + b.Property(u => u.ConcurrencyStamp).ConcurrencyToken(); }); builder.Entity(b => { b.Key(r => r.Id); b.ForRelational().Table("AspNetRoles"); + b.Property(r => r.ConcurrencyStamp).ConcurrencyToken(); }); builder.Entity>(b => diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs index b3d7988dc5..884b7a5b30 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs @@ -9,20 +9,21 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Entity; +using Microsoft.Data.Entity.Update; namespace Microsoft.AspNet.Identity.EntityFramework { public class RoleStore : RoleStore where TRole : IdentityRole { - public RoleStore(DbContext context) : base(context) { } + public RoleStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } public class RoleStore : RoleStore where TRole : IdentityRole where TContext : DbContext { - public RoleStore(TContext context) : base(context) { } + public RoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } public class RoleStore : @@ -32,13 +33,14 @@ namespace Microsoft.AspNet.Identity.EntityFramework where TKey : IEquatable where TContext : DbContext { - public RoleStore(TContext context) + public RoleStore(TContext context, IdentityErrorDescriber describer = null) { if (context == null) { throw new ArgumentNullException("context"); } Context = context; + ErrorDescriber = describer ?? new IdentityErrorDescriber(); } private bool _disposed; @@ -46,6 +48,11 @@ namespace Microsoft.AspNet.Identity.EntityFramework public TContext Context { get; private set; } + /// + /// Used to generate public API error messages + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + /// /// If true will call SaveChanges after CreateAsync/UpdateAsync/DeleteAsync /// @@ -59,7 +66,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework } } - public async virtual Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -69,9 +76,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework } await Context.AddAsync(role, cancellationToken); await SaveChanges(cancellationToken); + return IdentityResult.Success; } - public async virtual Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -79,11 +87,21 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentNullException("role"); } + Context.Attach(role); + role.ConcurrencyStamp = Guid.NewGuid().ToString(); Context.Update(role); - await SaveChanges(cancellationToken); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + return IdentityResult.Success; } - public async virtual Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -92,7 +110,15 @@ namespace Microsoft.AspNet.Identity.EntityFramework throw new ArgumentNullException("role"); } Context.Remove(role); - await SaveChanges(cancellationToken); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + return IdentityResult.Success; } public Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs index 8dfe8ba828..c21198dcbf 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs @@ -5,23 +5,23 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Linq.Expressions; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Entity; +using Microsoft.Data.Entity.Update; namespace Microsoft.AspNet.Identity.EntityFramework { public class UserStore : UserStore { - public UserStore(DbContext context) : base(context) { } + public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } public class UserStore : UserStore where TUser : IdentityUser, new() { - public UserStore(DbContext context) : base(context) { } + public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } public class UserStore : UserStore @@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework where TRole : IdentityRole, new() where TContext : DbContext { - public UserStore(TContext context) : base(context) { } + public UserStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } } public class UserStore : @@ -49,19 +49,25 @@ namespace Microsoft.AspNet.Identity.EntityFramework where TKey : IEquatable { - public UserStore(TContext context) + public UserStore(TContext context, IdentityErrorDescriber describer = null) { if (context == null) { throw new ArgumentNullException("context"); } Context = context; + ErrorDescriber = describer ?? new IdentityErrorDescriber(); } private bool _disposed; public TContext Context { get; private set; } + /// + /// Used to generate public API error messages + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + /// /// If true will call SaveChanges after CreateAsync/UpdateAsync/DeleteAsync /// @@ -129,7 +135,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework return Task.FromResult(0); } - public async virtual Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -139,9 +145,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework } await Context.AddAsync(user, cancellationToken); await SaveChanges(cancellationToken); + return IdentityResult.Success; } - public async virtual Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -149,11 +156,22 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentNullException("user"); } + + Context.Attach(user); + user.ConcurrencyStamp = Guid.NewGuid().ToString(); Context.Update(user); - await SaveChanges(cancellationToken); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + return IdentityResult.Success; } - public async virtual Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -161,8 +179,17 @@ namespace Microsoft.AspNet.Identity.EntityFramework { throw new ArgumentNullException("user"); } + Context.Remove(user); - await SaveChanges(cancellationToken); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + return IdentityResult.Success; } /// diff --git a/src/Microsoft.AspNet.Identity/IRoleStore.cs b/src/Microsoft.AspNet.Identity/IRoleStore.cs index 4c4fc16f46..e4fb97e532 100644 --- a/src/Microsoft.AspNet.Identity/IRoleStore.cs +++ b/src/Microsoft.AspNet.Identity/IRoleStore.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); + Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); /// /// Update a role @@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); + Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); /// /// DeleteAsync a role @@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); + Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)); /// /// Returns a role's id diff --git a/src/Microsoft.AspNet.Identity/IUserStore.cs b/src/Microsoft.AspNet.Identity/IUserStore.cs index 93a8fcae36..d6cd313733 100644 --- a/src/Microsoft.AspNet.Identity/IUserStore.cs +++ b/src/Microsoft.AspNet.Identity/IUserStore.cs @@ -63,7 +63,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); /// /// UpdateAsync a user @@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); /// /// DeleteAsync a user @@ -79,7 +79,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); /// /// Finds a user diff --git a/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs index 05fcef7fe0..9c998bc6a4 100644 --- a/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs +++ b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs @@ -15,6 +15,15 @@ namespace Microsoft.AspNet.Identity Description = Resources.DefaultError }; } + public virtual IdentityError ConcurrencyFailure() + { + return new IdentityError + { + Code = nameof(ConcurrencyFailure), + Description = Resources.ConcurrencyFailure + }; + } + public virtual IdentityError PasswordMismatch() { diff --git a/src/Microsoft.AspNet.Identity/IdentityRole.cs b/src/Microsoft.AspNet.Identity/IdentityRole.cs index e9206c92ad..711ff58f32 100644 --- a/src/Microsoft.AspNet.Identity/IdentityRole.cs +++ b/src/Microsoft.AspNet.Identity/IdentityRole.cs @@ -65,5 +65,10 @@ namespace Microsoft.AspNet.Identity /// Role name /// public virtual string Name { get; set; } + + /// + /// A random value that should change whenever a role is persisted to the store + /// + public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString(); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityUser.cs b/src/Microsoft.AspNet.Identity/IdentityUser.cs index 210791c26f..ce273deaae 100644 --- a/src/Microsoft.AspNet.Identity/IdentityUser.cs +++ b/src/Microsoft.AspNet.Identity/IdentityUser.cs @@ -52,6 +52,11 @@ namespace Microsoft.AspNet.Identity /// public virtual string SecurityStamp { get; set; } + /// + /// A random value that should change whenever a user is persisted to the store + /// + public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString(); + /// /// PhoneNumber for the user /// diff --git a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs index 8caeda20b2..11169b63ea 100644 --- a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs @@ -10,6 +10,22 @@ namespace Microsoft.AspNet.Identity private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.AspNet.Identity.Resources", typeof(Resources).GetTypeInfo().Assembly); + /// + /// Optimistic concurrency failure, object has been modified. + /// + internal static string ConcurrencyFailure + { + get { return GetString("ConcurrencyFailure"); } + } + + /// + /// Optimistic concurrency failure, object has been modified. + /// + internal static string FormatConcurrencyFailure() + { + return GetString("ConcurrencyFailure"); + } + /// /// Your security code is: {0} /// @@ -410,22 +426,6 @@ namespace Microsoft.AspNet.Identity return string.Format(CultureInfo.CurrentCulture, GetString("PasswordTooShort"), p0); } - /// - /// {0} cannot be null or empty. - /// - internal static string PropertyTooShort - { - get { return GetString("PropertyTooShort"); } - } - - /// - /// {0} cannot be null or empty. - /// - internal static string FormatPropertyTooShort(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("PropertyTooShort"), p0); - } - /// /// Role {0} does not exist. /// diff --git a/src/Microsoft.AspNet.Identity/Properties/debugSettings.json b/src/Microsoft.AspNet.Identity/Properties/debugSettings.json new file mode 100644 index 0000000000..a44fad34a3 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/Properties/debugSettings.json @@ -0,0 +1,3 @@ +{ + "Profiles": [] +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/Resources.resx b/src/Microsoft.AspNet.Identity/Resources.resx index 79f668637f..2592527afb 100644 --- a/src/Microsoft.AspNet.Identity/Resources.resx +++ b/src/Microsoft.AspNet.Identity/Resources.resx @@ -117,6 +117,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Optimistic concurrency failure, object has been modified. + Error when optimistic concurrency fails + Your security code is: {0} Default body format for the email diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs index 3189f253a4..cf13cee60a 100644 --- a/src/Microsoft.AspNet.Identity/RoleManager.cs +++ b/src/Microsoft.AspNet.Identity/RoleManager.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; -using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Identity { @@ -142,8 +141,7 @@ namespace Microsoft.AspNet.Identity { return result; } - await Store.CreateAsync(role, cancellationToken); - return IdentityResult.Success; + return await Store.CreateAsync(role, cancellationToken); } /// @@ -166,8 +164,7 @@ namespace Microsoft.AspNet.Identity { return result; } - await Store.UpdateAsync(role, cancellationToken); - return IdentityResult.Success; + return await Store.UpdateAsync(role, cancellationToken); } /// @@ -184,9 +181,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("role"); } - - await Store.DeleteAsync(role, cancellationToken); - return IdentityResult.Success; + return await Store.DeleteAsync(role, cancellationToken); } /// diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index 6caed539c2..d57b654505 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -326,6 +326,12 @@ namespace Microsoft.AspNet.Identity return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; } + public virtual Task GenerateConcurrencyStampAsync(TUser user, + CancellationToken token = default(CancellationToken)) + { + return Task.FromResult(Guid.NewGuid().ToString()); + } + /// /// Create a user with no password /// @@ -347,8 +353,7 @@ namespace Microsoft.AspNet.Identity await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, cancellationToken); } await UpdateNormalizedUserNameAsync(user, cancellationToken); - await Store.CreateAsync(user, cancellationToken); - return IdentityResult.Success; + return await Store.CreateAsync(user, cancellationToken); } /// @@ -371,8 +376,7 @@ namespace Microsoft.AspNet.Identity return result; } await UpdateNormalizedUserNameAsync(user, cancellationToken); - await Store.UpdateAsync(user, cancellationToken); - return IdentityResult.Success; + return await Store.UpdateAsync(user, cancellationToken); } /// @@ -389,8 +393,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - await Store.DeleteAsync(user, cancellationToken); - return IdentityResult.Success; + return await Store.DeleteAsync(user, cancellationToken); } /// diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs index 6e9c937274..950189dfb5 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs @@ -275,6 +275,108 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test async () => await manager.AddToRoleAsync(u, "bogus")); } + [Fact] + 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), IdentityErrorDescriber.Default.ConcurrencyFailure()); + } + } + + [Fact] + 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), IdentityErrorDescriber.Default.ConcurrencyFailure()); + } + } + + [Fact] + 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), IdentityErrorDescriber.Default.ConcurrencyFailure()); + } + } + + [Fact] + 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), IdentityErrorDescriber.Default.ConcurrencyFailure()); + } + } + // TODO: can we move these to UserManagerTestBase? [Fact] public async Task DeleteRoleNonEmptySucceedsTest() diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs index 0404ef3939..352311b56c 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs @@ -15,20 +15,20 @@ namespace Microsoft.AspNet.Identity.InMemory { private readonly Dictionary _roles = new Dictionary(); - public Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { _roles[role.Id] = role; - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } - public Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { if (role == null || !_roles.ContainsKey(role.Id)) { throw new InvalidOperationException("Unknown role"); } _roles.Remove(role.Id); - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } public Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) @@ -47,10 +47,10 @@ namespace Microsoft.AspNet.Identity.InMemory return Task.FromResult(0); } - public Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { _roles[role.Id] = role; - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } public Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs index 2590eccba7..3f6ef090d5 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs @@ -209,16 +209,16 @@ namespace Microsoft.AspNet.Identity.InMemory return Task.FromResult(0); } - public Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { _users[user.Id] = user; - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } - public Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { _users[user.Id] = user; - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } public Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) @@ -241,14 +241,14 @@ namespace Microsoft.AspNet.Identity.InMemory Users.FirstOrDefault(u => String.Equals(u.UserName, userName, StringComparison.OrdinalIgnoreCase))); } - public Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { if (user == null || !_users.ContainsKey(user.Id)) { throw new InvalidOperationException("Unknown user"); } _users.Remove(user.Id); - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } public Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs index 1d0b2fcc90..01c7f8281e 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs @@ -113,22 +113,22 @@ namespace Microsoft.AspNet.Identity.Test private class MyUberThingy : IUserValidator, IPasswordValidator, IRoleValidator, IUserStore, IRoleStore { - public Task CreateAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task CreateAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task DeleteAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task DeleteAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } @@ -188,12 +188,12 @@ namespace Microsoft.AspNet.Identity.Test throw new NotImplementedException(); } - public Task UpdateAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task UpdateAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } diff --git a/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs b/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs index 004d960841..a3c576ca51 100644 --- a/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs +++ b/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs @@ -8,14 +8,14 @@ namespace Microsoft.AspNet.Identity.Test { public class NoopRoleStore : IRoleStore { - public Task CreateAsync(TestRole user, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(TestRole user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } - public Task UpdateAsync(TestRole user, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(TestRole user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } public Task GetRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) @@ -42,9 +42,9 @@ namespace Microsoft.AspNet.Identity.Test { } - public Task DeleteAsync(TestRole user, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteAsync(TestRole user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } public Task GetRoleIdAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/test/Microsoft.AspNet.Identity.Test/NoopUserStore.cs b/test/Microsoft.AspNet.Identity.Test/NoopUserStore.cs index 026784e489..86078e2c0e 100644 --- a/test/Microsoft.AspNet.Identity.Test/NoopUserStore.cs +++ b/test/Microsoft.AspNet.Identity.Test/NoopUserStore.cs @@ -24,14 +24,14 @@ namespace Microsoft.AspNet.Identity.Test return Task.FromResult(0); } - public Task CreateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } - public Task UpdateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } public Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) @@ -48,9 +48,9 @@ namespace Microsoft.AspNet.Identity.Test { } - public Task DeleteAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } public Task GetNormalizedUserNameAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs index 31fa379c79..d57c4f2c25 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs @@ -63,17 +63,17 @@ namespace Microsoft.AspNet.Identity.Test private class NotImplementedStore : IRoleStore { - public Task CreateAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task UpdateAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task DeleteAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index 21cdf3d03d..cb3530fb25 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; - store.Setup(s => s.CreateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.CreateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); // Act @@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; - store.Setup(s => s.DeleteAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.DeleteAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); // Act @@ -76,8 +76,8 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; - store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.UpdateAsync(user); @@ -94,6 +94,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser(); store.Setup(s => s.SetUserNameAsync(user, It.IsAny(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); // Act @@ -172,7 +173,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.AddToRoleAsync(user, "C", CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); - store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); store.Setup(s => s.GetRolesAsync(user, CancellationToken.None)).ReturnsAsync(new List()).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); @@ -221,7 +222,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.RemoveFromRoleAsync(user, "C", CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); - store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); store.Setup(s => s.IsInRoleAsync(user, "A", CancellationToken.None)) .Returns(Task.FromResult(true)) .Verifiable(); @@ -277,7 +278,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.AddClaimsAsync(user, claims, CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); - store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); // Act @@ -298,7 +299,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.AddClaimsAsync(user, It.IsAny>(), CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); - store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); // Act @@ -320,6 +321,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.ReplaceClaimAsync(user, It.IsAny(), It.IsAny(), CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); // Act @@ -340,8 +342,8 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.RemoveClaimsAsync(user, claims, CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); - store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + var userManager = MockHelpers.TestUserManager(store.Object); // Act var result = await userManager.RemoveClaimsAsync(user, claims); @@ -361,7 +363,7 @@ namespace Microsoft.AspNet.Identity.Test store.Setup(s => s.RemoveClaimsAsync(user, It.IsAny>(), CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); - store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); // Act @@ -830,19 +832,19 @@ namespace Microsoft.AspNet.Identity.Test return Task.FromResult(0); } - public Task CreateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } - public Task UpdateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } - public Task DeleteAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } public Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) @@ -1117,21 +1119,6 @@ namespace Microsoft.AspNet.Identity.Test throw new NotImplementedException(); } - public Task CreateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - throw new NotImplementedException(); - } - - public Task UpdateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - throw new NotImplementedException(); - } - - public Task DeleteAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - throw new NotImplementedException(); - } - public Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); @@ -1226,6 +1213,21 @@ namespace Microsoft.AspNet.Identity.Test { throw new NotImplementedException(); } + + Task IUserStore.CreateAsync(TestUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + Task IUserStore.UpdateAsync(TestUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + Task IUserStore.DeleteAsync(TestUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } [Fact] From c558fe8d20586ac8d7bc30e3bec930f8e1b83714 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Wed, 7 Jan 2015 13:24:18 -0800 Subject: [PATCH 14/31] Remove DefaultSecurityClaimType constant Fixes https://github.com/aspnet/Identity/issues/316 --- src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs | 7 +------ test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs b/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs index be0df88958..45e4739bcc 100644 --- a/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs +++ b/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs @@ -10,11 +10,6 @@ namespace Microsoft.AspNet.Identity /// public class ClaimsIdentityOptions { - /// - /// The ClaimType used for the identity security stamp. - /// - public static readonly string DefaultSecurityStampClaimType = "AspNet.Identity.SecurityStamp"; - /// /// Gets the ClaimType used for a Role claim. /// @@ -45,6 +40,6 @@ namespace Microsoft.AspNet.Identity /// /// This defaults to "AspNet.Identity.SecurityStamp". /// - public string SecurityStampClaimType { get; set; } = DefaultSecurityStampClaimType; + public string SecurityStampClaimType { get; set; } = "AspNet.Identity.SecurityStamp"; } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs index 908c73ff82..823d0c639c 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs @@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(ClaimTypes.Role, options.ClaimsIdentity.RoleClaimType); Assert.Equal(ClaimTypes.Name, options.ClaimsIdentity.UserNameClaimType); Assert.Equal(ClaimTypes.NameIdentifier, options.ClaimsIdentity.UserIdClaimType); - Assert.Equal(ClaimsIdentityOptions.DefaultSecurityStampClaimType, options.ClaimsIdentity.SecurityStampClaimType); + Assert.Equal("AspNet.Identity.SecurityStamp", options.ClaimsIdentity.SecurityStampClaimType); } [Theory] From 0e7755ab795c9a397a6bca462d1777701405eb49 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 5 Jan 2015 15:48:47 -0800 Subject: [PATCH 15/31] Remove navigation properties InMemory test was the only one left using them, moved to a specific user subclass which had them for that test project --- src/Microsoft.AspNet.Identity/IdentityUser.cs | 16 ---------- .../HttpSignInTest.cs | 2 +- .../InMemoryStoreTest.cs | 4 +-- .../InMemoryUserStore.cs | 32 ++++++++++++++++--- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.AspNet.Identity/IdentityUser.cs b/src/Microsoft.AspNet.Identity/IdentityUser.cs index ce273deaae..1589199a77 100644 --- a/src/Microsoft.AspNet.Identity/IdentityUser.cs +++ b/src/Microsoft.AspNet.Identity/IdentityUser.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; namespace Microsoft.AspNet.Identity { @@ -86,20 +85,5 @@ namespace Microsoft.AspNet.Identity /// Used to record failures for the purposes of lockout /// public virtual int AccessFailedCount { get; set; } - - /// - /// Roles for the user - /// - public virtual ICollection> Roles { get; } = new List>(); - - /// - /// Claims for the user - /// - public virtual ICollection> Claims { get; } = new List>(); - - /// - /// Associated logins for the user - /// - public virtual ICollection> Logins { get; } = new List>(); } } diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs index f9f871fdc8..81495de872 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs @@ -14,7 +14,7 @@ using Xunit; namespace Microsoft.AspNet.Identity.InMemory.Test { - public class ApplicationUser : IdentityUser { } + public class ApplicationUser : InMemoryUser { } public class HttpSignInTest { diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs index e3de60ca21..9fbc2536dc 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs @@ -6,7 +6,7 @@ using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Identity.InMemory.Test { - public class InMemoryStoreTest : UserManagerTestBase + public class InMemoryStoreTest : UserManagerTestBase { protected override object CreateTestContext() { @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test protected override void AddUserStore(IServiceCollection services, object context = null) { - services.AddSingleton, InMemoryUserStore>(); + services.AddSingleton, InMemoryUserStore>(); } protected override void AddRoleStore(IServiceCollection services, object context = null) diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs index 3f6ef090d5..8d04d1ae4a 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs @@ -10,6 +10,30 @@ using System.Threading.Tasks; namespace Microsoft.AspNet.Identity.InMemory { + public class InMemoryUser : IdentityUser + { + public InMemoryUser() { } + + public InMemoryUser(string userName) : base(userName) { } + + + /// + /// Roles for the user + /// + public virtual ICollection Roles { get; } = new List(); + + /// + /// Claims for the user + /// + public virtual ICollection Claims { get; } = new List(); + + /// + /// Associated logins for the user + /// + public virtual ICollection Logins { get; } = new List(); + + } + public class InMemoryUserStore : IUserLoginStore, IUserRoleStore, @@ -21,7 +45,7 @@ namespace Microsoft.AspNet.Identity.InMemory IUserPhoneNumberStore, IQueryableUserStore, IUserTwoFactorStore - where TUser : IdentityUser + where TUser : InMemoryUser { private readonly Dictionary _logins = new Dictionary(); @@ -42,7 +66,7 @@ namespace Microsoft.AspNet.Identity.InMemory { foreach (var claim in claims) { - user.Claims.Add(new IdentityUserClaim { ClaimType = claim.Type, ClaimValue = claim.Value, UserId = user.Id }); + user.Claims.Add(new IdentityUserClaim { ClaimType = claim.Type, ClaimValue = claim.Value, UserId = user.Id }); } return Task.FromResult(0); } @@ -149,7 +173,7 @@ namespace Microsoft.AspNet.Identity.InMemory public virtual Task AddLoginAsync(TUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) { - user.Logins.Add(new IdentityUserLogin + user.Logins.Add(new IdentityUserLogin { UserId = user.Id, ProviderKey = login.ProviderKey, @@ -292,7 +316,7 @@ namespace Microsoft.AspNet.Identity.InMemory // RoleId == roleName for InMemory public Task AddToRoleAsync(TUser user, string role, CancellationToken cancellationToken = default(CancellationToken)) { - user.Roles.Add(new IdentityUserRole { RoleId = role, UserId = user.Id }); + user.Roles.Add(new IdentityUserRole { RoleId = role, UserId = user.Id }); return Task.FromResult(0); } From 53b994da19c512b1267d796a566b621d2229f42e Mon Sep 17 00:00:00 2001 From: Barry Dorrans Date: Thu, 8 Jan 2015 11:09:08 -0800 Subject: [PATCH 16/31] XMLDoc updates. Seperation of classes. --- .../DataProtectionTokenProvider.cs | 90 ++++++++++++------- .../DataProtectionTokenProviderOptions.cs | 29 ++++++ .../Microsoft.AspNet.Identity.kproj | 9 +- 3 files changed, 96 insertions(+), 32 deletions(-) create mode 100644 src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs diff --git a/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs b/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs index ae7bdfc960..b50ad1ecaa 100644 --- a/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs +++ b/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs @@ -1,24 +1,28 @@ -using Microsoft.AspNet.Security.DataProtection; -using Microsoft.Framework.OptionsModel; +// Copyright (c) Microsoft Open Technologies, Inc. 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.IO; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.Security.DataProtection; +using Microsoft.Framework.OptionsModel; + namespace Microsoft.AspNet.Identity { - public class DataProtectionTokenProviderOptions - { - public string Name { get; set; } = "DataProtection"; - public TimeSpan TokenLifespan { get; set; } = TimeSpan.FromDays(1); - } - /// - /// Token provider that uses an IDataProtector to generate encrypted tokens based off of the security stamp + /// Provides protection and validation of identity tokens. /// + /// The type used to represent a user. public class DataProtectorTokenProvider : IUserTokenProvider where TUser : class { + /// + /// Initializes a new instance of the class. + /// + /// The system data protection provider. + /// The configured . public DataProtectorTokenProvider(IDataProtectionProvider dataProtectionProvider, IOptions options) { if (options == null || options.Options == null) @@ -34,18 +38,38 @@ namespace Microsoft.AspNet.Identity Protector = dataProtectionProvider.CreateProtector(Name ?? "DataProtectorTokenProvider"); } + /// + /// Gets the for this instance. + /// + /// + /// The for this instance. + /// public DataProtectionTokenProviderOptions Options { get; private set; } + + /// + /// Gets the for this instance. + /// + /// + /// The for this instance. + /// public IDataProtector Protector { get; private set; } + /// + /// Gets the name of this instance. + /// + /// + /// The name of this instance. + /// public string Name { get { return Options.Name; } } /// - /// Generate a protected string for a user + /// Generates a protected token for the specified . /// - /// - /// - /// - /// + /// The purpose the token will be used for. + /// The to retrieve user properties from. + /// The the token will be generated from. + /// A to observe while waiting for the tasks to complete. + /// A that contains the protected token. public async Task GenerateAsync(string purpose, UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) { @@ -72,13 +96,14 @@ namespace Microsoft.AspNet.Identity } /// - /// Return false if the token is not valid + /// Validates the protected for the specified and . /// - /// - /// - /// - /// - /// + /// The purpose the token was be used for. + /// The token to validate. + /// The to retrieve user properties from. + /// The the token was generated for. + /// A to observe while waiting for the tasks to complete. + /// A that is true if the token is valid, otherwise false. public async Task ValidateAsync(string purpose, string token, UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) { @@ -128,11 +153,13 @@ namespace Microsoft.AspNet.Identity } /// - /// Returns false because tokens are two long to be used for two factor + /// Returns a indicating whether a token generated by this instance + /// can be used as a Two Factor Authentication token. /// - /// - /// - /// + /// The to retrieve user properties from. + /// The the token was generated for. + /// True if a token generated by this instance can be used as a Two Factor Authentication token, otherwise false. + /// This method will always return false for instances of . public Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) { @@ -140,12 +167,13 @@ namespace Microsoft.AspNet.Identity } /// - /// This provider no-ops by default when asked to notify a user + /// Creates a notification task for A based on the supplied . /// - /// - /// - /// - /// + /// The token to generate notifications for.. + /// The to retrieve user properties from. + /// The the token was generated for. + /// A to observe while waiting for the tasks to complete. + /// A that represents the started task. public Task NotifyAsync(string token, UserManager manager, TUser user, CancellationToken cancellationToken = default(CancellationToken)) { @@ -153,7 +181,9 @@ namespace Microsoft.AspNet.Identity } } - // Based on Levi's authentication sample + /// + /// Utility extensions to streams + /// internal static class StreamExtensions { internal static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true); diff --git a/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs b/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs new file mode 100644 index 0000000000..e7a6ab7170 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Identity +{ + /// + /// Contains options for the . + /// + public class DataProtectionTokenProviderOptions + { + /// + /// Gets or sets the name of the . + /// + /// + /// The name of the . + /// + public string Name { get; set; } = "DataProtection"; + + /// + /// Gets or sets the amount of time a generated token remains valid. + /// + /// + /// The amount of time a generated token remains valid. + /// + public TimeSpan TokenLifespan { get; set; } = TimeSpan.FromDays(1); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj index 318f83366a..2c8bf644b8 100644 --- a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj +++ b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -14,4 +14,9 @@ 2.0 - + + + + + + \ No newline at end of file From 06de635149594a17fc008dad57d6dc05a4cf6ed6 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Sat, 10 Jan 2015 02:34:00 -0800 Subject: [PATCH 17/31] Add attach tests for disconnected user/role --- .../UserStoreTest.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs index 950189dfb5..c3a4ee18f0 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/UserStoreTest.cs @@ -301,6 +301,30 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test } } + [Fact] + 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), IdentityErrorDescriber.Default.ConcurrencyFailure()); + } + } + [Fact] public async Task DeleteAModifiedUserWillFail() { @@ -352,6 +376,31 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test } } + [Fact] + 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), IdentityErrorDescriber.Default.ConcurrencyFailure()); + } + } + [Fact] public async Task DeleteAModifiedRoleWillFail() { From b59440d95ff01994a077be806046151c64197d6c Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 12 Jan 2015 12:12:53 -0800 Subject: [PATCH 18/31] Normalize all lookups with one service --- samples/IdentitySample.Mvc/LocalConfig.json | 2 +- .../RoleStore.cs | 32 ++++- .../UserStore.cs | 55 ++++++--- ...NameNormalizer.cs => ILookupNormalizer.cs} | 10 +- src/Microsoft.AspNet.Identity/IRoleStore.cs | 26 +++- .../IUserEmailStore.cs | 23 +++- .../IdentityErrorDescriber.cs | 1 - src/Microsoft.AspNet.Identity/IdentityRole.cs | 1 + .../IdentityServiceCollectionExtensions.cs | 2 +- src/Microsoft.AspNet.Identity/IdentityUser.cs | 2 + .../PasswordHasherOptions.cs | 1 - src/Microsoft.AspNet.Identity/RoleManager.cs | 39 +++++- .../SignInManager.cs | 4 +- src/Microsoft.AspNet.Identity/SignInResult.cs | 2 - ...r.cs => UpperInvariantLookupNormalizer.cs} | 14 +-- src/Microsoft.AspNet.Identity/UserManager.cs | 57 ++++++--- .../RoleStoreTest.cs | 15 +-- .../HttpSignInTest.cs | 3 +- .../InMemoryRoleStore.cs | 11 ++ .../InMemoryUserStore.cs | 16 ++- .../IHttpContextAccessor.cs | 6 + .../IdentityBuilderTest.cs | 10 ++ .../NoopRoleStore.cs | 10 ++ .../RoleManagerTest.cs | 102 ++++++++++++++++ .../SecurityStampValidatorTest.cs | 9 +- .../SignInManagerTest.cs | 35 +++--- .../UserManagerTest.cs | 111 +++++++++++++++++- test/Shared/MockHelpers.cs | 23 ++-- test/Shared/UserManagerTestBase.cs | 4 +- 29 files changed, 507 insertions(+), 119 deletions(-) rename src/Microsoft.AspNet.Identity/{IUserNameNormalizer.cs => ILookupNormalizer.cs} (61%) rename src/Microsoft.AspNet.Identity/{UpperInvariantUserNameNormalizer.cs => UpperInvariantLookupNormalizer.cs} (54%) create mode 100644 test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs diff --git a/samples/IdentitySample.Mvc/LocalConfig.json b/samples/IdentitySample.Mvc/LocalConfig.json index 06b6876a05..49b1ac00a4 100644 --- a/samples/IdentitySample.Mvc/LocalConfig.json +++ b/samples/IdentitySample.Mvc/LocalConfig.json @@ -3,7 +3,7 @@ "DefaultAdminPassword": "YouShouldChangeThisPassword1!", "Data": { "IdentityConnection": { - "Connectionstring": "Server=(localdb)\\mssqllocaldb;Database=IdentityMvc-11-24-14;Trusted_Connection=True;MultipleActiveResultSets=true" + "Connectionstring": "Server=(localdb)\\mssqllocaldb;Database=IdentityMvc-1-7-15;Trusted_Connection=True;MultipleActiveResultSets=true" } }, "Identity": { diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs index 884b7a5b30..89cf656d5a 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -188,16 +187,39 @@ namespace Microsoft.AspNet.Identity.EntityFramework } /// - /// Find a role by name + /// Find a role by normalized name /// - /// + /// /// /// - public virtual Task FindByNameAsync(string name, CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - return Roles.FirstOrDefaultAsync(u => u.Name.ToUpper() == name.ToUpper(), cancellationToken); + return Roles.FirstOrDefaultAsync(r => r.NormalizedName == normalizedName, cancellationToken); + } + + 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); + } + + 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.FromResult(0); } private void ThrowIfDisposed() diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs index c21198dcbf..74e6ddb234 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.FromResult(0); } - public Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -89,7 +89,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework return Task.FromResult(ConvertIdToString(user.Id)); } - public Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework return Task.FromResult(user.UserName); } - public Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -112,7 +112,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework return Task.FromResult(0); } - public Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -123,7 +123,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework return Task.FromResult(user.NormalizedUserName); } - public Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -420,7 +420,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework private DbSet> UserRoles { get { return Context.Set>(); } } private DbSet> UserLogins { get { return Context.Set>(); } } - public async Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) @@ -431,7 +431,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken); } - public async Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) @@ -448,7 +448,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework } } - public async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) @@ -472,7 +472,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework } } - public async Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) @@ -633,19 +633,40 @@ namespace Microsoft.AspNet.Identity.EntityFramework return Task.FromResult(user.Email); } + public virtual Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + return Task.FromResult(user.NormalizedEmail); + } + + public virtual Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.NormalizedEmail = normalizedEmail; + return Task.FromResult(0); + } + /// /// Find an user by email /// /// /// /// - public virtual Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - return Users.FirstOrDefaultAsync(u => u.Email == email, cancellationToken); - // todo: ToUpper blows up with Null Ref - //return Users.FirstOrDefaultAsync(u => u.Email.ToUpper() == email.ToUpper(), cancellationToken); + return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); } /// @@ -925,7 +946,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework /// /// /// - public async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -940,7 +961,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework && userclaims.ClaimType == claim.Type select user; - return (IList)await query.ToListAsync(cancellationToken); + return await query.ToListAsync(cancellationToken); } /// @@ -949,7 +970,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework /// /// /// - public async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) + public async virtual Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -967,7 +988,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework where userrole.RoleId.Equals(role.Id) select user; - return (IList) await query.ToListAsync(cancellationToken); + return await query.ToListAsync(cancellationToken); } return new List(); } diff --git a/src/Microsoft.AspNet.Identity/IUserNameNormalizer.cs b/src/Microsoft.AspNet.Identity/ILookupNormalizer.cs similarity index 61% rename from src/Microsoft.AspNet.Identity/IUserNameNormalizer.cs rename to src/Microsoft.AspNet.Identity/ILookupNormalizer.cs index b8d1bfcbf2..159f222ffa 100644 --- a/src/Microsoft.AspNet.Identity/IUserNameNormalizer.cs +++ b/src/Microsoft.AspNet.Identity/ILookupNormalizer.cs @@ -4,15 +4,15 @@ namespace Microsoft.AspNet.Identity { /// - /// Used to normalize a user name + /// Used to normalize keys for consistent lookups /// - public interface IUserNameNormalizer + public interface ILookupNormalizer { /// - /// Returns the normalized user name + /// Returns the normalized key /// - /// + /// /// - string Normalize(string userName); + string Normalize(string key); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IRoleStore.cs b/src/Microsoft.AspNet.Identity/IRoleStore.cs index e4fb97e532..405a47ce31 100644 --- a/src/Microsoft.AspNet.Identity/IRoleStore.cs +++ b/src/Microsoft.AspNet.Identity/IRoleStore.cs @@ -63,6 +63,26 @@ namespace Microsoft.AspNet.Identity Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Get a role's normalized name + /// + /// + /// + /// + Task GetNormalizedRoleNameAsync(TRole role, + CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Set a role's normalized name + /// + /// + /// + /// + /// + Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, + CancellationToken cancellationToken = default(CancellationToken)); + + /// /// Finds a role by id /// @@ -72,11 +92,11 @@ namespace Microsoft.AspNet.Identity Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default(CancellationToken)); /// - /// Find a role by name + /// Find a role by normalized name /// - /// + /// /// /// - Task FindByNameAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)); + Task FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IUserEmailStore.cs b/src/Microsoft.AspNet.Identity/IUserEmailStore.cs index 6569548ffe..816f9383eb 100644 --- a/src/Microsoft.AspNet.Identity/IUserEmailStore.cs +++ b/src/Microsoft.AspNet.Identity/IUserEmailStore.cs @@ -51,9 +51,28 @@ namespace Microsoft.AspNet.Identity /// /// Returns the user associated with this email /// - /// + /// /// /// - Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)); + Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Returns the normalized email + /// + /// + /// + /// + Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Set the normalized email + /// + /// + /// + /// + /// + Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, + CancellationToken cancellationToken = default(CancellationToken)); + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs index 9c998bc6a4..39d1677713 100644 --- a/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs +++ b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs @@ -24,7 +24,6 @@ namespace Microsoft.AspNet.Identity }; } - public virtual IdentityError PasswordMismatch() { return new IdentityError diff --git a/src/Microsoft.AspNet.Identity/IdentityRole.cs b/src/Microsoft.AspNet.Identity/IdentityRole.cs index 711ff58f32..0127acb84d 100644 --- a/src/Microsoft.AspNet.Identity/IdentityRole.cs +++ b/src/Microsoft.AspNet.Identity/IdentityRole.cs @@ -65,6 +65,7 @@ namespace Microsoft.AspNet.Identity /// Role name /// public virtual string Name { get; set; } + public virtual string NormalizedName { get; set; } /// /// A random value that should change whenever a role is persisted to the store diff --git a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs index 33b4d5e8f6..6ccc4ce6c5 100644 --- a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs @@ -57,7 +57,7 @@ namespace Microsoft.Framework.DependencyInjection services.TryAdd(describe.Transient, UserValidator>()); services.TryAdd(describe.Transient, PasswordValidator>()); services.TryAdd(describe.Transient, PasswordHasher>()); - services.TryAdd(describe.Transient()); + services.TryAdd(describe.Transient()); services.TryAdd(describe.Transient, RoleValidator>()); // No interface for the error describer so we can add errors without rev'ing the interface services.TryAdd(describe.Transient()); diff --git a/src/Microsoft.AspNet.Identity/IdentityUser.cs b/src/Microsoft.AspNet.Identity/IdentityUser.cs index 1589199a77..4392bf7db4 100644 --- a/src/Microsoft.AspNet.Identity/IdentityUser.cs +++ b/src/Microsoft.AspNet.Identity/IdentityUser.cs @@ -36,6 +36,8 @@ namespace Microsoft.AspNet.Identity /// public virtual string Email { get; set; } + public virtual string NormalizedEmail { get; set; } + /// /// True if the email is confirmed, default is false /// diff --git a/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs b/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs index e441a7bee0..a52e476f15 100644 --- a/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs +++ b/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Security.Cryptography; -using Microsoft.AspNet.Security.DataProtection; namespace Microsoft.AspNet.Identity { diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs index cf13cee60a..7b23ca8ab2 100644 --- a/src/Microsoft.AspNet.Identity/RoleManager.cs +++ b/src/Microsoft.AspNet.Identity/RoleManager.cs @@ -25,6 +25,7 @@ namespace Microsoft.AspNet.Identity /// public RoleManager(IRoleStore store, IEnumerable> roleValidators = null, + ILookupNormalizer keyNormalizer = null, IdentityErrorDescriber errors = null) { if (store == null) @@ -32,6 +33,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("store"); } Store = store; + KeyNormalizer = keyNormalizer ?? new UpperInvariantLookupNormalizer(); ErrorDescriber = errors ?? new IdentityErrorDescriber(); if (roleValidators != null) @@ -58,6 +60,11 @@ namespace Microsoft.AspNet.Identity /// public IdentityErrorDescriber ErrorDescriber { get; set; } + /// + /// Used to normalize user names, role names, emails for uniqueness + /// + public ILookupNormalizer KeyNormalizer { get; set; } + /// /// Returns an IQueryable of roles if the store is an IQueryableRoleStore /// @@ -141,9 +148,24 @@ namespace Microsoft.AspNet.Identity { return result; } + await UpdateNormalizedRoleNameAsync(role, cancellationToken); return await Store.CreateAsync(role, cancellationToken); } + /// + /// Update the user's normalized user name + /// + /// + /// + /// + public virtual async Task UpdateNormalizedRoleNameAsync(TRole role, + CancellationToken cancellationToken = default(CancellationToken)) + { + var name = await GetRoleNameAsync(role, cancellationToken); + await Store.SetNormalizedRoleNameAsync(role, NormalizeKey(name), cancellationToken); + } + + /// /// UpdateAsync an existing role /// @@ -164,6 +186,7 @@ namespace Microsoft.AspNet.Identity { return result; } + await UpdateNormalizedRoleNameAsync(role, cancellationToken); return await Store.UpdateAsync(role, cancellationToken); } @@ -199,9 +222,20 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("roleName"); } - return await FindByNameAsync(roleName, cancellationToken) != null; + return await FindByNameAsync(NormalizeKey(roleName), cancellationToken) != null; } + /// + /// Normalize a key (role name) for uniqueness comparisons + /// + /// + /// + public virtual string NormalizeKey(string key) + { + return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key); + } + + /// /// FindByLoginAsync a role by id /// @@ -240,6 +274,7 @@ namespace Microsoft.AspNet.Identity { ThrowIfDisposed(); await Store.SetRoleNameAsync(role, name, cancellationToken); + await UpdateNormalizedRoleNameAsync(role, cancellationToken); return IdentityResult.Success; } @@ -271,7 +306,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("roleName"); } - return await Store.FindByNameAsync(roleName, cancellationToken); + return await Store.FindByNameAsync(NormalizeKey(roleName), cancellationToken); } // IRoleClaimStore methods diff --git a/src/Microsoft.AspNet.Identity/SignInManager.cs b/src/Microsoft.AspNet.Identity/SignInManager.cs index 95ca0c1851..fb170b95e7 100644 --- a/src/Microsoft.AspNet.Identity/SignInManager.cs +++ b/src/Microsoft.AspNet.Identity/SignInManager.cs @@ -8,9 +8,9 @@ using System.Security.Claims; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Security; -using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Identity @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Identity public class SignInManager where TUser : class { public SignInManager(UserManager userManager, - IContextAccessor contextAccessor, + IHttpContextAccessor contextAccessor, IClaimsIdentityFactory claimsFactory, IOptions optionsAccessor = null) { diff --git a/src/Microsoft.AspNet.Identity/SignInResult.cs b/src/Microsoft.AspNet.Identity/SignInResult.cs index e125690fe7..9c88817de2 100644 --- a/src/Microsoft.AspNet.Identity/SignInResult.cs +++ b/src/Microsoft.AspNet.Identity/SignInResult.cs @@ -78,7 +78,5 @@ namespace Microsoft.AspNet.Identity { get { return _twoFactorRequired; } } - - } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UpperInvariantUserNameNormalizer.cs b/src/Microsoft.AspNet.Identity/UpperInvariantLookupNormalizer.cs similarity index 54% rename from src/Microsoft.AspNet.Identity/UpperInvariantUserNameNormalizer.cs rename to src/Microsoft.AspNet.Identity/UpperInvariantLookupNormalizer.cs index 1564ecd3d4..9bfa9454a3 100644 --- a/src/Microsoft.AspNet.Identity/UpperInvariantUserNameNormalizer.cs +++ b/src/Microsoft.AspNet.Identity/UpperInvariantLookupNormalizer.cs @@ -6,22 +6,22 @@ using System; namespace Microsoft.AspNet.Identity { /// - /// Normalizes user names via ToUpperInvariant() + /// Normalizes via ToUpperInvariant() /// - public class UpperInvariantUserNameNormalizer : IUserNameNormalizer + public class UpperInvariantLookupNormalizer : ILookupNormalizer { /// - /// Normalizes user names via ToUpperInvariant() + /// Normalizes via ToUpperInvariant() /// - /// + /// /// - public string Normalize(string userName) + public string Normalize(string key) { - if (userName == null) + if (key == null) { return null; } - return userName.Normalize().ToUpperInvariant(); + return key.Normalize().ToUpperInvariant(); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index d57b654505..1f8f4b663a 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -30,20 +30,23 @@ namespace Microsoft.AspNet.Identity private IdentityOptions _options; /// - /// Constructor which takes a service provider and user store + /// Constructor /// /// /// /// - /// - /// - /// + /// + /// + /// + /// + /// + /// public UserManager(IUserStore store, IOptions optionsAccessor = null, IPasswordHasher passwordHasher = null, IEnumerable> userValidators = null, IEnumerable> passwordValidators = null, - IUserNameNormalizer userNameNormalizer = null, + ILookupNormalizer keyNormalizer = null, IdentityErrorDescriber errors = null, IEnumerable> tokenProviders = null, IEnumerable msgProviders = null) @@ -55,7 +58,7 @@ namespace Microsoft.AspNet.Identity Store = store; Options = optionsAccessor?.Options ?? new IdentityOptions(); PasswordHasher = passwordHasher ?? new PasswordHasher(); - UserNameNormalizer = userNameNormalizer ?? new UpperInvariantUserNameNormalizer(); + KeyNormalizer = keyNormalizer ?? new UpperInvariantLookupNormalizer(); ErrorDescriber = errors ?? new IdentityErrorDescriber(); if (userValidators != null) { @@ -126,9 +129,9 @@ namespace Microsoft.AspNet.Identity public IList> PasswordValidators { get; } = new List>(); /// - /// Used to normalize user names for uniqueness + /// Used to normalize user names and emails for uniqueness /// - public IUserNameNormalizer UserNameNormalizer { get; set; } + public ILookupNormalizer KeyNormalizer { get; set; } /// /// Used to generate public API error messages @@ -353,6 +356,7 @@ namespace Microsoft.AspNet.Identity await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, cancellationToken); } await UpdateNormalizedUserNameAsync(user, cancellationToken); + await UpdateNormalizedEmailAsync(user, cancellationToken); return await Store.CreateAsync(user, cancellationToken); } @@ -376,6 +380,7 @@ namespace Microsoft.AspNet.Identity return result; } await UpdateNormalizedUserNameAsync(user, cancellationToken); + await UpdateNormalizedEmailAsync(user, cancellationToken); return await Store.UpdateAsync(user, cancellationToken); } @@ -423,7 +428,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("userName"); } - userName = NormalizeUserName(userName); + userName = NormalizeKey(userName); return Store.FindByNameAsync(userName, cancellationToken); } @@ -467,13 +472,13 @@ namespace Microsoft.AspNet.Identity } /// - /// Normalize a user name for uniqueness comparisons + /// Normalize a key (user name, email) for uniqueness comparisons /// /// /// - public virtual string NormalizeUserName(string userName) + public virtual string NormalizeKey(string key) { - return (UserNameNormalizer == null) ? userName : UserNameNormalizer.Normalize(userName); + return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key); } /// @@ -485,8 +490,8 @@ namespace Microsoft.AspNet.Identity public virtual async Task UpdateNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { - var userName = await GetUserNameAsync(user, cancellationToken); - await Store.SetNormalizedUserNameAsync(user, NormalizeUserName(userName), cancellationToken); + var normalizedName = NormalizeKey(await GetUserNameAsync(user, cancellationToken)); + await Store.SetNormalizedUserNameAsync(user, normalizedName, cancellationToken); } /// @@ -1215,10 +1220,10 @@ namespace Microsoft.AspNet.Identity } // IUserEmailStore methods - internal IUserEmailStore GetEmailStore() + internal IUserEmailStore GetEmailStore(bool throwOnFail = true) { var cast = Store as IUserEmailStore; - if (cast == null) + if (throwOnFail && cast == null) { throw new NotSupportedException(Resources.StoreNotIUserEmailStore); } @@ -1281,9 +1286,27 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("email"); } - return store.FindByEmailAsync(email, cancellationToken); + return store.FindByEmailAsync(NormalizeKey(email), cancellationToken); } + /// + /// Update the user's normalized email + /// + /// + /// + /// + public virtual async Task UpdateNormalizedEmailAsync(TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + var store = GetEmailStore(throwOnFail: false); + if (store != null) + { + var email = await GetEmailAsync(user, cancellationToken); + await store.SetNormalizedEmailAsync(user, NormalizeKey(email), cancellationToken); + } + } + + /// /// Get the confirmation token for the user /// diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs index 2efc785d71..9299a59ceb 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs @@ -76,23 +76,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test var role = new IdentityRole("UpdateRoleName"); IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); Assert.Null(await manager.FindByNameAsync("New")); - role.Name = "New"; + IdentityResultAssert.IsSuccess(await manager.SetRoleNameAsync(role, "New")); IdentityResultAssert.IsSuccess(await manager.UpdateAsync(role)); Assert.NotNull(await manager.FindByNameAsync("New")); Assert.Null(await manager.FindByNameAsync("UpdateAsync")); } - - [Fact] - public async Task CanSetUserName() - { - var manager = TestIdentityFactory.CreateRoleManager(); - var role = new IdentityRole("UpdateRoleName"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); - Assert.Null(await manager.FindByNameAsync("New")); - IdentityResultAssert.IsSuccess(await manager.SetRoleNameAsync(role, "New")); - Assert.NotNull(await manager.FindByNameAsync("New")); - Assert.Null(await manager.FindByNameAsync("UpdateAsync")); - } - } } diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs index 81495de872..9f922d9a3b 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/HttpSignInTest.cs @@ -4,6 +4,7 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Security; using Microsoft.AspNet.Identity.Test; @@ -30,7 +31,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test var response = new Mock(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == isPersistent), It.IsAny())).Verifiable(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); app.UseServices(services => { diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs index 352311b56c..0f250c4615 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryRoleStore.cs @@ -97,6 +97,17 @@ namespace Microsoft.AspNet.Identity.InMemory return Task.FromResult(0); } + public Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(role.NormalizedName); + } + + public Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + role.NormalizedName = normalizedName; + return Task.FromResult(0); + } + public IQueryable Roles { get { return _roles.Values.AsQueryable(); } diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs index 8d04d1ae4a..ab3521f6b4 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStore.cs @@ -108,6 +108,18 @@ namespace Microsoft.AspNet.Identity.InMemory return Task.FromResult(user.Email); } + public Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.NormalizedEmail); + } + + public Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) + { + user.NormalizedEmail = normalizedEmail; + return Task.FromResult(0); + } + + public Task GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { return Task.FromResult(user.EmailConfirmed); @@ -123,7 +135,7 @@ namespace Microsoft.AspNet.Identity.InMemory { return Task.FromResult( - Users.FirstOrDefault(u => String.Equals(u.Email, email, StringComparison.OrdinalIgnoreCase))); + Users.FirstOrDefault(u => u.NormalizedEmail == email)); } public Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) @@ -262,7 +274,7 @@ namespace Microsoft.AspNet.Identity.InMemory { return Task.FromResult( - Users.FirstOrDefault(u => String.Equals(u.UserName, userName, StringComparison.OrdinalIgnoreCase))); + Users.FirstOrDefault(u => u.NormalizedUserName == userName)); } public Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs b/test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs new file mode 100644 index 0000000000..b812ed6e8b --- /dev/null +++ b/test/Microsoft.AspNet.Identity.Test/IHttpContextAccessor.cs @@ -0,0 +1,6 @@ +namespace Microsoft.AspNet.Identity.Test +{ + internal interface IHttpContextAccessor + { + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs index 01c7f8281e..488fcc7545 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs @@ -148,6 +148,11 @@ namespace Microsoft.AspNet.Identity.Test throw new NotImplementedException(); } + public Task GetNormalizedRoleNameAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } + public Task GetNormalizedUserNameAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); @@ -173,6 +178,11 @@ namespace Microsoft.AspNet.Identity.Test throw new NotImplementedException(); } + public Task SetNormalizedRoleNameAsync(IdentityRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } + public Task SetNormalizedUserNameAsync(IdentityUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); diff --git a/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs b/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs index a3c576ca51..4d62b972bf 100644 --- a/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs +++ b/test/Microsoft.AspNet.Identity.Test/NoopRoleStore.cs @@ -51,5 +51,15 @@ namespace Microsoft.AspNet.Identity.Test { return Task.FromResult(null); } + + public Task GetNormalizedRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(null); + } + + public Task SetNormalizedRoleNameAsync(TestRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(0); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs index d57c4f2c25..cab11a83df 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs @@ -6,12 +6,51 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Moq; using Xunit; namespace Microsoft.AspNet.Identity.Test { public class RoleManagerTest { + [Fact] + public async Task CreateCallsStore() + { + // Setup + var store = new Mock>(); + var role = new TestRole { Name = "Foo" }; + store.Setup(s => s.CreateAsync(role, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + store.Setup(s => s.GetRoleNameAsync(role, CancellationToken.None)).Returns(Task.FromResult(role.Name)).Verifiable(); + store.Setup(s => s.SetNormalizedRoleNameAsync(role, role.Name.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + var roleManager = MockHelpers.TestRoleManager(store.Object); + + // Act + var result = await roleManager.CreateAsync(role); + + // Assert + Assert.True(result.Succeeded); + store.VerifyAll(); + } + + [Fact] + public async Task UpdateCallsStore() + { + // Setup + var store = new Mock>(); + var role = new TestRole { Name = "Foo" }; + store.Setup(s => s.UpdateAsync(role, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + store.Setup(s => s.GetRoleNameAsync(role, CancellationToken.None)).Returns(Task.FromResult(role.Name)).Verifiable(); + store.Setup(s => s.SetNormalizedRoleNameAsync(role, role.Name.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + var roleManager = MockHelpers.TestRoleManager(store.Object); + + // Act + var result = await roleManager.UpdateAsync(role); + + // Assert + Assert.True(result.Succeeded); + store.VerifyAll(); + } + [Fact] public void RolesQueryableFailWhenStoreNotImplemented() { @@ -20,6 +59,59 @@ namespace Microsoft.AspNet.Identity.Test Assert.Throws(() => manager.Roles.Count()); } + [Fact] + public async Task FindByNameCallsStoreWithNormalizedName() + { + // Setup + var store = new Mock>(); + var role = new TestRole { Name = "Foo" }; + store.Setup(s => s.FindByNameAsync("FOO", CancellationToken.None)).Returns(Task.FromResult(role)).Verifiable(); + var manager = MockHelpers.TestRoleManager(store.Object); + + // Act + var result = await manager.FindByNameAsync(role.Name); + + // Assert + Assert.Equal(role, result); + store.VerifyAll(); + } + + [Fact] + public async Task CanFindByNameCallsStoreWithoutNormalizedName() + { + // Setup + var store = new Mock>(); + var role = new TestRole { Name = "Foo" }; + store.Setup(s => s.FindByNameAsync(role.Name, CancellationToken.None)).Returns(Task.FromResult(role)).Verifiable(); + var manager = MockHelpers.TestRoleManager(store.Object); + manager.KeyNormalizer = null; + + // Act + var result = await manager.FindByNameAsync(role.Name); + + // Assert + Assert.Equal(role, result); + store.VerifyAll(); + } + + [Fact] + public async Task RoleExistsCallsStoreWithNormalizedName() + { + // Setup + var store = new Mock>(); + var role = new TestRole { Name = "Foo" }; + store.Setup(s => s.FindByNameAsync("FOO", CancellationToken.None)).Returns(Task.FromResult(role)).Verifiable(); + var manager = MockHelpers.TestRoleManager(store.Object); + + // Act + var result = await manager.RoleExistsAsync(role.Name); + + // Assert + Assert.True(result); + store.VerifyAll(); + } + + [Fact] public void DisposeAfterDisposeDoesNotThrow() { @@ -107,6 +199,16 @@ namespace Microsoft.AspNet.Identity.Test { throw new NotImplementedException(); } + + public Task GetNormalizedRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } + + public Task SetNormalizedRoleNameAsync(TestRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs index 5ae623e01d..1528665dde 100644 --- a/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs @@ -5,6 +5,7 @@ using System; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Security; using Microsoft.AspNet.Security; @@ -43,7 +44,7 @@ namespace Microsoft.AspNet.Identity.Test var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var httpContext = new Mock(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, options.Object); @@ -78,7 +79,7 @@ namespace Microsoft.AspNet.Identity.Test var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var httpContext = new Mock(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, options.Object); @@ -112,7 +113,7 @@ namespace Microsoft.AspNet.Identity.Test var identityOptions = new IdentityOptions { SecurityStampValidationInterval = TimeSpan.Zero }; var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, options.Object); @@ -146,7 +147,7 @@ namespace Microsoft.AspNet.Identity.Test var identityOptions = new IdentityOptions { SecurityStampValidationInterval = TimeSpan.FromDays(1) }; var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, contextAccessor.Object, claimsManager.Object, options.Object); diff --git a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs index 60876733cc..7d1d1d6c90 100644 --- a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs @@ -7,6 +7,7 @@ using System.Security.Claims; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Security; using Microsoft.Framework.DependencyInjection; @@ -31,7 +32,7 @@ namespace Microsoft.AspNet.Identity.Test // TODO: how to functionally test context? // var context = new DefaultHttpContext(new FeatureCollection()); - // var contextAccessor = new Mock>(); + // var contextAccessor = new Mock(); // contextAccessor.Setup(a => a.Value).Returns(context); // app.UseServices(services => // { @@ -70,7 +71,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Throws("userManager", () => new SignInManager(null, null, null, null)); var userManager = MockHelpers.MockUserManager().Object; Assert.Throws("contextAccessor", () => new SignInManager(userManager, null, null, null)); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); Assert.Throws("contextAccessor", () => new SignInManager(userManager, contextAccessor.Object, null, null)); var context = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); @@ -93,7 +94,7 @@ namespace Microsoft.AspNet.Identity.Test // var response = new Mock(); // context.Setup(c => c.Response).Returns(response.Object).Verifiable(); // response.Setup(r => r.SignIn(testIdentity, It.IsAny())).Verifiable(); - // var contextAccessor = new Mock>(); + // var contextAccessor = new Mock(); // contextAccessor.Setup(a => a.Value).Returns(context.Object); // var helper = new HttpAuthenticationManager(contextAccessor.Object); @@ -117,7 +118,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); var context = new Mock(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -152,7 +153,7 @@ namespace Microsoft.AspNet.Identity.Test var response = new Mock(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == isPersistent), It.IsAny())).Verifiable(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -189,7 +190,7 @@ namespace Microsoft.AspNet.Identity.Test var response = new Mock(); response.Setup(r => r.SignIn(It.IsAny(), It.IsAny())).Verifiable(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -238,7 +239,7 @@ namespace Microsoft.AspNet.Identity.Test var response = new Mock(); response.Setup(r => r.SignIn(It.Is(id => id.Name == user.Id))).Verifiable(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -282,7 +283,7 @@ namespace Microsoft.AspNet.Identity.Test response.Setup(r => r.SignIn( It.Is(v => v.IsPersistent == isPersistent), It.Is(i => i.FindFirstValue(ClaimTypes.AuthenticationMethod) == loginProvider))).Verifiable(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -342,7 +343,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.GetUserNameAsync(user, CancellationToken.None)).ReturnsAsync(user.UserName).Verifiable(); var context = new Mock(); var response = new Mock(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); var twoFactorInfo = new SignInManager.TwoFactorAuthenticationInfo { UserId = user.Id }; var loginProvider = "loginprovider"; var id = SignInManager.StoreTwoFactorInfo(user.Id, externalLogin ? loginProvider : null); @@ -397,7 +398,7 @@ namespace Microsoft.AspNet.Identity.Test var manager = MockHelpers.MockUserManager(); var context = new Mock(); var response = new Mock(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); var options = new Mock>(); @@ -452,7 +453,7 @@ namespace Microsoft.AspNet.Identity.Test id.AddClaim(new Claim(ClaimTypes.Name, user.Id)); var authResult = new AuthenticationResult(id, new AuthenticationProperties(), new AuthenticationDescription()); context.Setup(c => c.AuthenticateAsync(IdentityOptions.TwoFactorRememberMeCookieAuthenticationType)).ReturnsAsync(authResult).Verifiable(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -487,7 +488,7 @@ namespace Microsoft.AspNet.Identity.Test response.Setup(r => r.SignOut(authenticationType)).Verifiable(); response.Setup(r => r.SignOut(IdentityOptions.TwoFactorUserIdCookieAuthenticationType)).Verifiable(); response.Setup(r => r.SignOut(IdentityOptions.ExternalCookieAuthenticationType)).Verifiable(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -518,7 +519,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "bogus", CancellationToken.None)).ReturnsAsync(false).Verifiable(); var context = new Mock(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -543,7 +544,7 @@ namespace Microsoft.AspNet.Identity.Test var manager = MockHelpers.MockUserManager(); manager.Setup(m => m.FindByNameAsync("bogus", CancellationToken.None)).ReturnsAsync(null).Verifiable(); var context = new Mock(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -579,7 +580,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "bogus", CancellationToken.None)).ReturnsAsync(false).Verifiable(); var context = new Mock(); - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -618,7 +619,7 @@ namespace Microsoft.AspNet.Identity.Test context.Setup(c => c.Response).Returns(response.Object).Verifiable(); response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == false), It.IsAny())).Verifiable(); } - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); @@ -659,7 +660,7 @@ namespace Microsoft.AspNet.Identity.Test response.Setup(r => r.SignIn(It.Is(v => v.IsPersistent == false), It.IsAny())).Verifiable(); } - var contextAccessor = new Mock>(); + var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index cb3530fb25..b340caa63f 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -43,6 +43,29 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; store.Setup(s => s.CreateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)).Returns(Task.FromResult(user.UserName)).Verifiable(); + store.Setup(s => s.SetNormalizedUserNameAsync(user, user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + var userManager = MockHelpers.TestUserManager(store.Object); + + // Act + var result = await userManager.CreateAsync(user); + + // Assert + Assert.True(result.Succeeded); + store.VerifyAll(); + } + + [Fact] + public async Task CreateCallsUpdateEmailStore() + { + // Setup + var store = new Mock>(); + var user = new TestUser { UserName = "Foo", Email = "Foo@foo.com" }; + store.Setup(s => s.CreateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)).Returns(Task.FromResult(user.UserName)).Verifiable(); + store.Setup(s => s.GetEmailAsync(user, CancellationToken.None)).Returns(Task.FromResult(user.Email)).Verifiable(); + store.Setup(s => s.SetNormalizedEmailAsync(user, user.Email.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.SetNormalizedUserNameAsync(user, user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); // Act @@ -76,6 +99,29 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; + store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)).Returns(Task.FromResult(user.UserName)).Verifiable(); + store.Setup(s => s.SetNormalizedUserNameAsync(user, user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + var userManager = MockHelpers.TestUserManager(store.Object); + + // Act + var result = await userManager.UpdateAsync(user); + + // Assert + Assert.True(result.Succeeded); + store.VerifyAll(); + } + + [Fact] + public async Task UpdateWillUpdateNormalizedEmail() + { + // Setup + var store = new Mock>(); + var user = new TestUser { UserName = "Foo", Email = "email" }; + store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)).Returns(Task.FromResult(user.UserName)).Verifiable(); + store.Setup(s => s.GetEmailAsync(user, CancellationToken.None)).Returns(Task.FromResult(user.Email)).Verifiable(); + store.Setup(s => s.SetNormalizedUserNameAsync(user, user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.SetNormalizedEmailAsync(user, user.Email.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); @@ -93,7 +139,9 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser(); - store.Setup(s => s.SetUserNameAsync(user, It.IsAny(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.SetUserNameAsync(user, "foo", CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); + store.Setup(s => s.GetUserNameAsync(user, CancellationToken.None)).Returns(Task.FromResult("foo")).Verifiable(); + store.Setup(s => s.SetNormalizedUserNameAsync(user, "FOO", CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); store.Setup(s => s.UpdateAsync(user, CancellationToken.None)).Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); @@ -146,8 +194,8 @@ namespace Microsoft.AspNet.Identity.Test var store = new Mock>(); var user = new TestUser {UserName="Foo"}; store.Setup(s => s.FindByNameAsync(user.UserName, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); - var userManager = MockHelpers.TestUserManager(store.Object); - userManager.UserNameNormalizer = null; + var userManager = MockHelpers.TestUserManager(store.Object); + userManager.KeyNormalizer = null; // Act var result = await userManager.FindByNameAsync(user.UserName); @@ -157,6 +205,41 @@ namespace Microsoft.AspNet.Identity.Test store.VerifyAll(); } + [Fact] + public async Task FindByEmailCallsStoreWithNormalizedEmail() + { + // Setup + var store = new Mock>(); + var user = new TestUser { Email = "Foo" }; + store.Setup(s => s.FindByEmailAsync(user.Email.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); + var userManager = MockHelpers.TestUserManager(store.Object); + + // Act + var result = await userManager.FindByEmailAsync(user.Email); + + // Assert + Assert.Equal(user, result); + store.VerifyAll(); + } + + [Fact] + public async Task CanFindByEmailCallsStoreWithoutNormalizedEmail() + { + // Setup + var store = new Mock>(); + var user = new TestUser { Email = "Foo" }; + store.Setup(s => s.FindByEmailAsync(user.Email, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); + var userManager = MockHelpers.TestUserManager(store.Object); + userManager.KeyNormalizer = null; + + // Act + var result = await userManager.FindByEmailAsync(user.Email); + + // Assert + Assert.Equal(user, result); + store.VerifyAll(); + } + [Fact] public async Task AddToRolesCallsStore() { @@ -961,6 +1044,16 @@ namespace Microsoft.AspNet.Identity.Test { return Task.FromResult>(new List()); } + + public Task GetNormalizedEmailAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(""); + } + + public Task SetNormalizedEmailAsync(TestUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(0); + } } private class NoOpTokenProvider : IUserTokenProvider @@ -1228,6 +1321,16 @@ namespace Microsoft.AspNet.Identity.Test { throw new NotImplementedException(); } + + public Task GetNormalizedEmailAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } + + public Task SetNormalizedEmailAsync(TestUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } } [Fact] @@ -1245,7 +1348,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Options.User.RequireUniqueEmail = true; var user = new TestUser() { UserName = "dupeEmail", Email = "dupe@email.com" }; var user2 = new TestUser() { UserName = "dupeEmail2", Email = "dupe@email.com" }; - store.Setup(s => s.FindByEmailAsync(user.Email, CancellationToken.None)) + store.Setup(s => s.FindByEmailAsync("DUPE@EMAIL.COM", CancellationToken.None)) .Returns(Task.FromResult(user2)) .Verifiable(); store.Setup(s => s.GetUserIdAsync(user2, CancellationToken.None)) diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs index 4a73bba084..f70541bec0 100644 --- a/test/Shared/MockHelpers.cs +++ b/test/Shared/MockHelpers.cs @@ -20,21 +20,17 @@ namespace Microsoft.AspNet.Identity.Test return mgr; } - public static Mock> MockRoleManager() where TRole : class + public static Mock> MockRoleManager(IRoleStore store = null) where TRole : class { - var store = new Mock>(); + store = store ?? new Mock>().Object; var roles = new List>(); roles.Add(new RoleValidator()); - return new Mock>(store.Object, roles, null); + return new Mock>(store, roles, null, null); } - public static UserManager TestUserManager() where TUser : class - { - return TestUserManager(new Mock>().Object); - } - - public static UserManager TestUserManager(IUserStore store) where TUser : class + public static UserManager TestUserManager(IUserStore store = null) where TUser : class { + store = store ?? new Mock>().Object; var validator = new Mock>(); var userManager = new UserManager(store); userManager.UserValidators.Add(validator.Object); @@ -43,5 +39,14 @@ namespace Microsoft.AspNet.Identity.Test .Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); return userManager; } + + public static RoleManager TestRoleManager(IRoleStore store = null) where TRole : class + { + store = store ?? new Mock>().Object; + var roles = new List>(); + roles.Add(new RoleValidator()); + return new RoleManager(store, roles); + } + } } \ No newline at end of file diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index 2b27a389b1..1be1741852 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -1016,10 +1016,10 @@ namespace Microsoft.AspNet.Identity.Test Assert.False(await manager.RoleExistsAsync(role.Name)); IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); Assert.True(await manager.RoleExistsAsync(role.Name)); - role.Name = "Changed"; + IdentityResultAssert.IsSuccess(await manager.SetRoleNameAsync(role, "Changed")); IdentityResultAssert.IsSuccess(await manager.UpdateAsync(role)); Assert.False(await manager.RoleExistsAsync("update")); - Assert.Equal(role, await manager.FindByNameAsync(role.Name)); + Assert.Equal(role, await manager.FindByNameAsync("Changed")); } [Fact] From 4eff17c9b29d5535d7fa72653ad2e64ad4a8d263 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 12 Jan 2015 12:34:16 -0800 Subject: [PATCH 19/31] Cleanup DataProtectionTokenProvider --- .../DataProtectionTokenProvider.cs | 10 +++------- .../DataProtectionTokenProviderOptions.cs | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs b/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs index b50ad1ecaa..988b569e32 100644 --- a/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs +++ b/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs @@ -25,15 +25,11 @@ namespace Microsoft.AspNet.Identity /// The configured . public DataProtectorTokenProvider(IDataProtectionProvider dataProtectionProvider, IOptions options) { - if (options == null || options.Options == null) - { - throw new ArgumentNullException(nameof(options)); - } if (dataProtectionProvider == null) { throw new ArgumentNullException(nameof(dataProtectionProvider)); } - Options = options.Options; + Options = options?.Options ?? new DataProtectionTokenProviderOptions(); // Use the Name as the purpose which should usually be distinct from others Protector = dataProtectionProvider.CreateProtector(Name ?? "DataProtectorTokenProvider"); } @@ -44,7 +40,7 @@ namespace Microsoft.AspNet.Identity /// /// The for this instance. /// - public DataProtectionTokenProviderOptions Options { get; private set; } + protected DataProtectionTokenProviderOptions Options { get; private set; } /// /// Gets the for this instance. @@ -52,7 +48,7 @@ namespace Microsoft.AspNet.Identity /// /// The for this instance. /// - public IDataProtector Protector { get; private set; } + protected IDataProtector Protector { get; private set; } /// /// Gets the name of this instance. diff --git a/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs b/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs index e7a6ab7170..57d589440a 100644 --- a/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs +++ b/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Identity /// /// The name of the . /// - public string Name { get; set; } = "DataProtection"; + public string Name { get; set; } = "DataProtectorTokenProvider"; /// /// Gets or sets the amount of time a generated token remains valid. From 69ac9abcc5bdd831026bb31983b9e4f2979915e5 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 12 Jan 2015 12:47:08 -0800 Subject: [PATCH 20/31] Rehash passwords when needed Fixes https://github.com/aspnet/Identity/issues/17 --- src/Microsoft.AspNet.Identity/UserManager.cs | 40 +++++++++++-------- .../UserManagerTest.cs | 28 +++++++++++++ 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index 1f8f4b663a..e7897a7e1a 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -463,7 +463,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("password"); } - var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken); + var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken); if (!result.Succeeded) { return result; @@ -565,7 +565,12 @@ namespace Microsoft.AspNet.Identity { return false; } - return await VerifyPasswordAsync(passwordStore, user, password, cancellationToken); + var result = await VerifyPasswordAsync(passwordStore, user, password, cancellationToken); + if (result == PasswordVerificationResult.SuccessRehashNeeded) { + await UpdatePasswordHash(passwordStore, user, password, cancellationToken, validatePassword: false); + await UpdateAsync(user, cancellationToken); + } + return result != PasswordVerificationResult.Failed; } /// @@ -607,7 +612,7 @@ namespace Microsoft.AspNet.Identity { return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword()); } - var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken); + var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken); if (!result.Succeeded) { return result; @@ -632,9 +637,9 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - if (await VerifyPasswordAsync(passwordStore, user, currentPassword, cancellationToken)) + if (await VerifyPasswordAsync(passwordStore, user, currentPassword, cancellationToken) != PasswordVerificationResult.Failed) { - var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken); + var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken); if (!result.Succeeded) { return result; @@ -659,21 +664,24 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - await passwordStore.SetPasswordHashAsync(user, null, cancellationToken); - await UpdateSecurityStampInternal(user, cancellationToken); + await UpdatePasswordHash(passwordStore, user, null, cancellationToken, validatePassword: false); return await UpdateAsync(user, cancellationToken); } - internal async Task UpdatePasswordInternal(IUserPasswordStore passwordStore, - TUser user, string newPassword, CancellationToken cancellationToken) + internal async Task UpdatePasswordHash(IUserPasswordStore passwordStore, + TUser user, string newPassword, CancellationToken cancellationToken, bool validatePassword = true) { - var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken); - if (!validate.Succeeded) + if (validatePassword) { - return validate; + var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken); + if (!validate.Succeeded) + { + return validate; + } } + var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null; await - passwordStore.SetPasswordHashAsync(user, PasswordHasher.HashPassword(user, newPassword), cancellationToken); + passwordStore.SetPasswordHashAsync(user, hash, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); return IdentityResult.Success; } @@ -686,11 +694,11 @@ namespace Microsoft.AspNet.Identity /// /// /// - protected virtual async Task VerifyPasswordAsync(IUserPasswordStore store, TUser user, + protected virtual async Task VerifyPasswordAsync(IUserPasswordStore store, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { var hash = await store.GetPasswordHashAsync(user, cancellationToken); - return PasswordHasher.VerifyHashedPassword(user, hash, password) != PasswordVerificationResult.Failed; + return PasswordHasher.VerifyHashedPassword(user, hash, password); } // IUserSecurityStampStore methods @@ -776,7 +784,7 @@ namespace Microsoft.AspNet.Identity return IdentityResult.Failed(ErrorDescriber.InvalidToken()); } var passwordStore = GetPasswordStore(); - var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken); + var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken); if (!result.Succeeded) { return result; diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index b340caa63f..e4cf94ecd5 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -415,6 +415,34 @@ namespace Microsoft.AspNet.Identity.Test store.VerifyAll(); } + [Fact] + public async Task CheckPasswordWillRehashPasswordWhenNeeded() + { + // Setup + var store = new Mock>(); + var hasher = new Mock>(); + var user = new TestUser { UserName = "Foo" }; + var pwd = "password"; + var hashed = "hashed"; + var rehashed = "rehashed"; + + store.Setup(s => s.GetPasswordHashAsync(user, CancellationToken.None)) + .ReturnsAsync(hashed) + .Verifiable(); + hasher.Setup(s => s.VerifyHashedPassword(user, hashed, pwd)).Returns(PasswordVerificationResult.SuccessRehashNeeded).Verifiable(); + hasher.Setup(s => s.HashPassword(user, pwd)).Returns(rehashed).Verifiable(); + var userManager = MockHelpers.TestUserManager(store.Object); + userManager.PasswordHasher = hasher.Object; + + // Act + var result = await userManager.CheckPasswordAsync(user, pwd); + + // Assert + Assert.True(result); + store.VerifyAll(); + hasher.VerifyAll(); + } + [Fact] public async Task RemoveClaimsCallsStore() { From 6e294035a521df8f7da6a1a5c0fad14a6e82fe26 Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Wed, 19 Nov 2014 16:23:20 -0800 Subject: [PATCH 21/31] Added logging to code and updated tests --- .../IdentityResult.cs | 19 ++ .../Microsoft.AspNet.Identity.kproj | 3 + .../Properties/Resources.Designer.cs | 96 ++++++- src/Microsoft.AspNet.Identity/Resources.resx | 74 +++-- src/Microsoft.AspNet.Identity/RoleManager.cs | 69 +++-- .../SignInManager.cs | 79 ++++-- src/Microsoft.AspNet.Identity/SignInResult.cs | 31 +++ src/Microsoft.AspNet.Identity/UserManager.cs | 259 +++++++++++++----- src/Microsoft.AspNet.Identity/project.json | 3 +- .../InMemoryEFUserStoreTest.cs | 8 +- .../SqlStoreTestBase.cs | 4 +- .../InMemoryStoreTest.cs | 8 +- .../IdentityBuilderTest.cs | 5 +- .../IdentityResultTest.cs | 25 ++ .../Microsoft.AspNet.Identity.Test.kproj | 9 +- .../RoleManagerTest.cs | 4 +- .../RoleValidatorTest.cs | 4 +- .../SecurityStampValidatorTest.cs | 8 +- .../SignInManagerTest.cs | 73 ++++- .../SignInResultTest.cs | 71 +++++ .../UserManagerTest.cs | 10 +- test/Shared/IdentityResultAssert.cs | 66 +++++ test/Shared/MockHelpers.cs | 40 ++- test/Shared/TestFileLogger.cs | 45 +++ test/Shared/TestFileLoggerFactory.cs | 48 ++++ test/Shared/UserManagerTestBase.cs | 116 +++++++- 26 files changed, 975 insertions(+), 202 deletions(-) create mode 100644 test/Microsoft.AspNet.Identity.Test/SignInResultTest.cs create mode 100644 test/Shared/TestFileLogger.cs create mode 100644 test/Shared/TestFileLoggerFactory.cs diff --git a/src/Microsoft.AspNet.Identity/IdentityResult.cs b/src/Microsoft.AspNet.Identity/IdentityResult.cs index dd3fe07de6..043c605fef 100644 --- a/src/Microsoft.AspNet.Identity/IdentityResult.cs +++ b/src/Microsoft.AspNet.Identity/IdentityResult.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Identity { @@ -48,5 +49,23 @@ namespace Microsoft.AspNet.Identity } return result; } + + /// + /// Log Identity result + /// + /// + /// + public virtual void Log(ILogger logger, string message) + { + // TODO: Take logging level as a parameter + if (Succeeded) + { + logger.WriteInformation(Resources.FormatLogIdentityResultSuccess(message)); + } + else + { + logger.WriteWarning(Resources.FormatLogIdentityResultFailure(message, string.Join(",", Errors.Select(x => x.Code).ToList()))); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj index 2c8bf644b8..61c02b9a59 100644 --- a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj +++ b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj @@ -13,6 +13,9 @@ 2.0 + + True + diff --git a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs index 11169b63ea..2aa5369096 100644 --- a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Identity } /// - /// Security Code + /// Security code /// internal static string DefaultEmailTokenProviderSubject { @@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Identity } /// - /// Security Code + /// Security code /// internal static string FormatDefaultEmailTokenProviderSubject() { @@ -123,7 +123,7 @@ namespace Microsoft.AspNet.Identity } /// - /// DefaultTokenProvider + /// Default Token Provider /// internal static string DefaultTokenProvider { @@ -131,7 +131,7 @@ namespace Microsoft.AspNet.Identity } /// - /// DefaultTokenProvider + /// Default Token Provider /// internal static string FormatDefaultTokenProvider() { @@ -155,7 +155,7 @@ namespace Microsoft.AspNet.Identity } /// - /// Role Name '{0}' is already taken. + /// Role name '{0}' is already taken. /// internal static string DuplicateRoleName { @@ -163,7 +163,7 @@ namespace Microsoft.AspNet.Identity } /// - /// Role Name '{0}' is already taken. + /// Role name '{0}' is already taken. /// internal static string FormatDuplicateRoleName(object p0) { @@ -171,7 +171,7 @@ namespace Microsoft.AspNet.Identity } /// - /// UserName '{0}' is already taken. + /// User name '{0}' is already taken. /// internal static string DuplicateUserName { @@ -179,7 +179,7 @@ namespace Microsoft.AspNet.Identity } /// - /// UserName '{0}' is already taken. + /// User name '{0}' is already taken. /// internal static string FormatDuplicateUserName(object p0) { @@ -746,6 +746,86 @@ namespace Microsoft.AspNet.Identity return string.Format(CultureInfo.CurrentCulture, GetString("UserNotInRole"), p0); } + /// + /// {0} : Failed : {1} + /// + internal static string LogIdentityResultFailure + { + get { return GetString("LogIdentityResultFailure"); } + } + + /// + /// {0} : Failed : {1} + /// + internal static string FormatLogIdentityResultFailure(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LogIdentityResultFailure"), p0, p1); + } + + /// + /// {0} : Success + /// + internal static string LogIdentityResultSuccess + { + get { return GetString("LogIdentityResultSuccess"); } + } + + /// + /// {0} : Success + /// + internal static string FormatLogIdentityResultSuccess(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LogIdentityResultSuccess"), p0); + } + + /// + /// {0} : Result : {1} + /// + internal static string LoggingSigninResult + { + get { return GetString("LoggingSigninResult"); } + } + + /// + /// {0} : Result : {1} + /// + internal static string FormatLoggingSigninResult(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LoggingSigninResult"), p0, p1); + } + + /// + /// {0} for user: {1} + /// + internal static string LoggingResultMessage + { + get { return GetString("LoggingResultMessage"); } + } + + /// + /// {0} for user: {1} + /// + internal static string FormatLoggingResultMessage(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LoggingResultMessage"), p0, p1); + } + + /// + /// {0} for role: {1} + /// + internal static string LoggingResultMessageForRole + { + get { return GetString("LoggingResultMessageForRole"); } + } + + /// + /// {0} for role: {1} + /// + internal static string FormatLoggingResultMessageForRole(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LoggingResultMessageForRole"), p0, p1); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Identity/Resources.resx b/src/Microsoft.AspNet.Identity/Resources.resx index 2592527afb..2351b1e7aa 100644 --- a/src/Microsoft.AspNet.Identity/Resources.resx +++ b/src/Microsoft.AspNet.Identity/Resources.resx @@ -130,7 +130,7 @@ Default name for the email token provider - Security Code + Security code Default subject for the email @@ -146,24 +146,24 @@ Default name for the phone number token provider - DefaultTokenProvider + Default Token Provider Name of the default token provider Email '{0}' is already taken. - error for duplicate emails + Error for duplicate emails - Role Name '{0}' is already taken. - error for duplicate usernames + Role name '{0}' is already taken. + Error for duplicate user names - UserName '{0}' is already taken. - error for duplicate usernames + User name '{0}' is already taken. + Error for duplicate user names Email '{0}' is invalid. - invalid email + Invalid email The provided PasswordHasherCompatibilityMode is invalid. @@ -175,7 +175,7 @@ Role name '{0}' is invalid. - error for invalid role names + Error for invalid role names Invalid token. @@ -183,7 +183,7 @@ User name '{0}' is invalid, can only contain letters or digits. - usernames can only contain letters or digits + User names can only contain letters or digits A user with this login already exists. @@ -223,63 +223,63 @@ Role {0} does not exist. - error when a role does not exist + Error when a role does not exist Store does not implement IQueryableRoleStore<TRole>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IQueryableUserStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IRoleClaimStore<TRole>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserClaimStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserConfirmationStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserEmailStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserLockoutStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserLoginStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserPasswordStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserPhoneNumberStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserRoleStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserSecurityStampStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserTwoFactorStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface User already has a password set. - error when AddPasswordAsync called when a user already has a password + Error when AddPasswordAsync called when a user already has a password User already in role '{0}'. @@ -291,14 +291,34 @@ Lockout is not enabled for this user. - error when lockout is not enabled + Error when lockout is not enabled User {0} does not exist. - error when a user does not exist + Error when a user does not exist User is not in role '{0}'. Error when a user is not in the role + + {0} : Failed : {1} + Logging method execution failure + + + {0} : Success + Logging method execution success + + + {0} : Result : {1} + Logging statement for SignInManager + + + {0} for user: {1} + Message prefix for Identity result + + + {0} for role: {1} + Message prefix for Identity result for role operation + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs index 7b23ca8ab2..98b7db3990 100644 --- a/src/Microsoft.AspNet.Identity/RoleManager.cs +++ b/src/Microsoft.AspNet.Identity/RoleManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Identity { @@ -23,10 +24,11 @@ namespace Microsoft.AspNet.Identity /// /// The IRoleStore commits changes via the UpdateAsync/CreateAsync methods /// - public RoleManager(IRoleStore store, + public RoleManager(IRoleStore store, IEnumerable> roleValidators = null, ILookupNormalizer keyNormalizer = null, - IdentityErrorDescriber errors = null) + IdentityErrorDescriber errors = null, + ILoggerFactory loggerFactory = null) { if (store == null) { @@ -43,6 +45,9 @@ namespace Microsoft.AspNet.Identity RoleValidators.Add(v); } } + + loggerFactory = loggerFactory ?? new LoggerFactory(); + Logger = loggerFactory.Create(nameof(RoleManager)); } /// @@ -60,6 +65,11 @@ namespace Microsoft.AspNet.Identity /// public IdentityErrorDescriber ErrorDescriber { get; set; } + /// + /// Used to log results + /// + public ILogger Logger { get; set; } + /// /// Used to normalize user names, role names, emails for uniqueness /// @@ -134,7 +144,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task CreateAsync(TRole role, + public virtual async Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -149,7 +159,7 @@ namespace Microsoft.AspNet.Identity return result; } await UpdateNormalizedRoleNameAsync(role, cancellationToken); - return await Store.CreateAsync(role, cancellationToken); + return await LogResultAsync(await Store.CreateAsync(role, cancellationToken), role); } /// @@ -172,7 +182,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task UpdateAsync(TRole role, + public virtual async Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -181,6 +191,12 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("role"); } + return await LogResultAsync(await UpdateRoleAsync(role, cancellationToken), role); + } + + private async Task UpdateRoleAsync(TRole role, + CancellationToken cancellationToken = default(CancellationToken)) + { var result = await ValidateRoleInternal(role, cancellationToken); if (!result.Succeeded) { @@ -196,7 +212,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task DeleteAsync(TRole role, + public virtual async Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -204,7 +220,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("role"); } - return await Store.DeleteAsync(role, cancellationToken); + return await LogResultAsync(await Store.DeleteAsync(role, cancellationToken), role); } /// @@ -213,7 +229,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task RoleExistsAsync(string roleName, + public virtual async Task RoleExistsAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -242,7 +258,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task FindByIdAsync(string roleId, + public virtual async Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -255,7 +271,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task GetRoleNameAsync(TRole role, + public virtual async Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -269,13 +285,13 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task SetRoleNameAsync(TRole role, string name, + public virtual async Task SetRoleNameAsync(TRole role, string name, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); await Store.SetRoleNameAsync(role, name, cancellationToken); await UpdateNormalizedRoleNameAsync(role, cancellationToken); - return IdentityResult.Success; + return await LogResultAsync(IdentityResult.Success, role); } /// @@ -284,7 +300,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task GetRoleIdAsync(TRole role, + public virtual async Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -297,7 +313,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task FindByNameAsync(string roleName, + public virtual async Task FindByNameAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -327,7 +343,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task AddClaimAsync(TRole role, Claim claim, + public virtual async Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -341,7 +357,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("role"); } await claimStore.AddClaimAsync(role, claim, cancellationToken); - return await UpdateAsync(role, cancellationToken); + return await LogResultAsync(await UpdateRoleAsync(role, cancellationToken), role); } /// @@ -351,7 +367,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task RemoveClaimAsync(TRole role, Claim claim, + public virtual async Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -361,7 +377,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("role"); } await claimStore.RemoveClaimAsync(role, claim, cancellationToken); - return await UpdateAsync(role, cancellationToken); + return await LogResultAsync(await UpdateRoleAsync(role, cancellationToken), role); } /// @@ -370,7 +386,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task> GetClaimsAsync(TRole role, + public virtual async Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -382,6 +398,21 @@ namespace Microsoft.AspNet.Identity return await claimStore.GetClaimsAsync(role, cancellationToken); } + /// + /// Logs the current Identity Result and returns result object + /// + /// + /// + /// + /// + protected async Task LogResultAsync(IdentityResult result, + TRole role, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + result.Log(Logger, Resources.FormatLoggingResultMessageForRole(methodName, await GetRoleIdAsync(role))); + + return result; + } + private void ThrowIfDisposed() { if (_disposed) diff --git a/src/Microsoft.AspNet.Identity/SignInManager.cs b/src/Microsoft.AspNet.Identity/SignInManager.cs index fb170b95e7..d6fd276950 100644 --- a/src/Microsoft.AspNet.Identity/SignInManager.cs +++ b/src/Microsoft.AspNet.Identity/SignInManager.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Security; +using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Identity @@ -21,10 +22,11 @@ namespace Microsoft.AspNet.Identity /// public class SignInManager where TUser : class { - public SignInManager(UserManager userManager, - IHttpContextAccessor contextAccessor, - IClaimsIdentityFactory claimsFactory, - IOptions optionsAccessor = null) + public SignInManager(UserManager userManager, + IHttpContextAccessor contextAccessor, + IClaimsIdentityFactory claimsFactory, + IOptions optionsAccessor = null, + ILoggerFactory loggerFactory = null) { if (userManager == null) { @@ -38,16 +40,21 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException(nameof(claimsFactory)); } + UserManager = userManager; Context = contextAccessor.Value; ClaimsFactory = claimsFactory; Options = optionsAccessor?.Options ?? new IdentityOptions(); + + loggerFactory = loggerFactory ?? new LoggerFactory(); + Logger = loggerFactory.Create(nameof(SignInManager)); } public UserManager UserManager { get; private set; } public HttpContext Context { get; private set; } public IClaimsIdentityFactory ClaimsFactory { get; private set; } public IdentityOptions Options { get; private set; } + public ILogger Logger { get; set; } // Should this be a func? public virtual async Task CreateUserIdentityAsync(TUser user, @@ -61,13 +68,13 @@ namespace Microsoft.AspNet.Identity { if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user, cancellationToken))) { - return false; + return await LogResultAsync(false, user); } if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user, cancellationToken))) { - return false; + return await LogResultAsync(false, user); } - return true; + return await LogResultAsync(true, user); } public virtual async Task SignInAsync(TUser user, bool isPersistent, string authenticationMethod = null, @@ -139,7 +146,7 @@ namespace Microsoft.AspNet.Identity return null; } - public virtual async Task PasswordSignInAsync(TUser user, string password, + public virtual async Task PasswordSignInAsync(TUser user, string password, bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken)) { if (user == null) @@ -149,16 +156,16 @@ namespace Microsoft.AspNet.Identity var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error; + return await LogResultAsync(error, user); } if (await IsLockedOut(user, cancellationToken)) { - return SignInResult.LockedOut; + return await LogResultAsync(SignInResult.LockedOut, user); } if (await UserManager.CheckPasswordAsync(user, password, cancellationToken)) { await ResetLockout(user, cancellationToken); - return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken); + return await LogResultAsync(await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken), user); } if (UserManager.SupportsUserLockout && shouldLockout) { @@ -166,10 +173,11 @@ namespace Microsoft.AspNet.Identity await UserManager.AccessFailedAsync(user, cancellationToken); if (await UserManager.IsLockedOutAsync(user, cancellationToken)) { - return SignInResult.LockedOut; + + return await LogResultAsync(SignInResult.LockedOut, user); } } - return SignInResult.Failed; + return await LogResultAsync(SignInResult.Failed, user); } public virtual async Task PasswordSignInAsync(string userName, string password, @@ -214,7 +222,7 @@ namespace Microsoft.AspNet.Identity } var token = await UserManager.GenerateTwoFactorTokenAsync(user, provider, cancellationToken); await UserManager.NotifyTwoFactorTokenAsync(user, provider, token, cancellationToken); - return true; + return await LogResultAsync(true, user); } public async Task IsTwoFactorClientRememberedAsync(TUser user, @@ -257,7 +265,7 @@ namespace Microsoft.AspNet.Identity var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error; + return await LogResultAsync(error, user); } if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code, cancellationToken)) { @@ -275,11 +283,11 @@ namespace Microsoft.AspNet.Identity } await UserManager.ResetAccessFailedCountAsync(user, cancellationToken); await SignInAsync(user, isPersistent); - return SignInResult.Success; + return await LogResultAsync(SignInResult.Success, user); } // If the token is incorrect, record the failure which also may cause the user to be locked out await UserManager.AccessFailedAsync(user, cancellationToken); - return SignInResult.Failed; + return await LogResultAsync(SignInResult.Failed, user); } /// @@ -310,9 +318,9 @@ namespace Microsoft.AspNet.Identity var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error; + return await LogResultAsync(error, user); } - return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider); + return await LogResultAsync(await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider), user); } private const string LoginProviderKey = "LoginProvider"; @@ -323,7 +331,7 @@ namespace Microsoft.AspNet.Identity return Context.GetAuthenticationTypes().Where(d => !string.IsNullOrEmpty(d.Caption)); } - public virtual async Task GetExternalLoginInfoAsync(string expectedXsrf = null, + public virtual async Task GetExternalLoginInfoAsync(string expectedXsrf = null, CancellationToken cancellationToken = default(CancellationToken)) { var auth = await Context.AuthenticateAsync(IdentityOptions.ExternalCookieAuthenticationType); @@ -368,7 +376,7 @@ namespace Microsoft.AspNet.Identity private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, CancellationToken cancellationToken, string loginProvider = null) { - if (UserManager.SupportsUserTwoFactor && + if (UserManager.SupportsUserTwoFactor && await UserManager.GetTwoFactorEnabledAsync(user, cancellationToken) && (await UserManager.GetValidTwoFactorProvidersAsync(user, cancellationToken)).Count > 0) { @@ -403,6 +411,35 @@ namespace Microsoft.AspNet.Identity return null; } + /// + /// Log boolean result for user and return result + /// + /// + /// + /// + /// + protected async virtual Task LogResultAsync(bool result, TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + Logger.WriteInformation(Resources.FormatLoggingSigninResult(Resources.FormatLoggingResultMessage(methodName, + await UserManager.GetUserIdAsync(user)), result)); + + return result; + } + + /// + /// Log SignInStatus for user and return SignInStatus + /// + /// + /// + /// + /// + protected async virtual Task LogResultAsync(SignInResult status, TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + status.Log(Logger, Resources.FormatLoggingResultMessage(methodName, await UserManager.GetUserIdAsync(user))); + + return status; + } + internal static ClaimsIdentity StoreTwoFactorInfo(string userId, string loginProvider) { var identity = new ClaimsIdentity(IdentityOptions.TwoFactorUserIdCookieAuthenticationType); diff --git a/src/Microsoft.AspNet.Identity/SignInResult.cs b/src/Microsoft.AspNet.Identity/SignInResult.cs index 9c88817de2..7ef69a60bb 100644 --- a/src/Microsoft.AspNet.Identity/SignInResult.cs +++ b/src/Microsoft.AspNet.Identity/SignInResult.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.Framework.Logging; + namespace Microsoft.AspNet.Identity { /// @@ -78,5 +80,34 @@ namespace Microsoft.AspNet.Identity { get { return _twoFactorRequired; } } + + /// + /// Log result based on properties + /// + /// + /// + public virtual void Log(ILogger logger, string message) + { + if (IsLockedOut) + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "Lockedout")); + } + else if (IsNotAllowed) + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "NotAllowed")); + } + else if (RequiresTwoFactor) + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "RequiresTwoFactor")); + } + else if (Succeeded) + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "Succeeded")); + } + else + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "Failed")); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index e7897a7e1a..fa63e7d5ff 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -9,6 +9,7 @@ using System.Security.Claims; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Identity @@ -41,15 +42,17 @@ namespace Microsoft.AspNet.Identity /// /// /// - public UserManager(IUserStore store, + /// + public UserManager(IUserStore store, IOptions optionsAccessor = null, - IPasswordHasher passwordHasher = null, + IPasswordHasher passwordHasher = null, IEnumerable> userValidators = null, - IEnumerable> passwordValidators = null, + IEnumerable> passwordValidators = null, ILookupNormalizer keyNormalizer = null, IdentityErrorDescriber errors = null, - IEnumerable> tokenProviders = null, - IEnumerable msgProviders = null) + IEnumerable> tokenProviders = null, + IEnumerable msgProviders = null, + ILoggerFactory loggerFactory = null) { if (store == null) { @@ -60,6 +63,7 @@ namespace Microsoft.AspNet.Identity PasswordHasher = passwordHasher ?? new PasswordHasher(); KeyNormalizer = keyNormalizer ?? new UpperInvariantLookupNormalizer(); ErrorDescriber = errors ?? new IdentityErrorDescriber(); + if (userValidators != null) { foreach (var v in userValidators) @@ -74,6 +78,10 @@ namespace Microsoft.AspNet.Identity PasswordValidators.Add(v); } } + + loggerFactory = loggerFactory ?? new LoggerFactory(); + Logger = loggerFactory.Create(nameof(UserManager)); + if (tokenProviders != null) { foreach (var tokenProvider in tokenProviders) @@ -138,6 +146,11 @@ namespace Microsoft.AspNet.Identity /// public IdentityErrorDescriber ErrorDescriber { get; set; } + /// + /// Used to log IdentityResult + /// + public ILogger Logger { get; set; } + public IdentityOptions Options { get @@ -329,12 +342,31 @@ namespace Microsoft.AspNet.Identity return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; } - public virtual Task GenerateConcurrencyStampAsync(TUser user, + public virtual Task GenerateConcurrencyStampAsync(TUser user, CancellationToken token = default(CancellationToken)) { return Task.FromResult(Guid.NewGuid().ToString()); } + /// + /// Validate user and update. Called by other UserManager methods + /// + /// + /// + /// + private async Task UpdateUserAsync(TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + var result = await ValidateUserInternal(user, cancellationToken); + if (!result.Succeeded) + { + return result; + } + await UpdateNormalizedUserNameAsync(user, cancellationToken); + await UpdateNormalizedEmailAsync(user, cancellationToken); + return await Store.UpdateAsync(user, cancellationToken); + } + /// /// Create a user with no password /// @@ -357,7 +389,7 @@ namespace Microsoft.AspNet.Identity } await UpdateNormalizedUserNameAsync(user, cancellationToken); await UpdateNormalizedEmailAsync(user, cancellationToken); - return await Store.CreateAsync(user, cancellationToken); + return await LogResultAsync(await Store.CreateAsync(user, cancellationToken), user); } /// @@ -374,14 +406,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - var result = await ValidateUserInternal(user, cancellationToken); - if (!result.Succeeded) - { - return result; - } - await UpdateNormalizedUserNameAsync(user, cancellationToken); - await UpdateNormalizedEmailAsync(user, cancellationToken); - return await Store.UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -398,7 +423,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - return await Store.DeleteAsync(user, cancellationToken); + return await LogResultAsync(await Store.DeleteAsync(user, cancellationToken), user); } /// @@ -527,7 +552,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await UpdateUserName(user, userName, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } private async Task UpdateUserName(TUser user, string userName, CancellationToken cancellationToken) @@ -566,11 +591,13 @@ namespace Microsoft.AspNet.Identity return false; } var result = await VerifyPasswordAsync(passwordStore, user, password, cancellationToken); - if (result == PasswordVerificationResult.SuccessRehashNeeded) { + if (result == PasswordVerificationResult.SuccessRehashNeeded) + { await UpdatePasswordHash(passwordStore, user, password, cancellationToken, validatePassword: false); - await UpdateAsync(user, cancellationToken); + await UpdateUserAsync(user, cancellationToken); } - return result != PasswordVerificationResult.Failed; + + return await LogResultAsync(result != PasswordVerificationResult.Failed, user); } /// @@ -588,7 +615,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - return await passwordStore.HasPasswordAsync(user, cancellationToken); + return await LogResultAsync(await passwordStore.HasPasswordAsync(user, cancellationToken), user); } /// @@ -610,14 +637,14 @@ namespace Microsoft.AspNet.Identity var hash = await passwordStore.GetPasswordHashAsync(user, cancellationToken); if (hash != null) { - return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword()), user); } var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken); if (!result.Succeeded) { - return result; + return await LogResultAsync(result, user); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -642,11 +669,11 @@ namespace Microsoft.AspNet.Identity var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken); if (!result.Succeeded) { - return result; + return await LogResultAsync(result, user); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } - return IdentityResult.Failed(ErrorDescriber.PasswordMismatch()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.PasswordMismatch()), user); } /// @@ -665,13 +692,13 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await UpdatePasswordHash(passwordStore, user, null, cancellationToken, validatePassword: false); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } internal async Task UpdatePasswordHash(IUserPasswordStore passwordStore, TUser user, string newPassword, CancellationToken cancellationToken, bool validatePassword = true) { - if (validatePassword) + if (validatePassword) { var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken); if (!validate.Succeeded) @@ -746,7 +773,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -759,7 +786,10 @@ namespace Microsoft.AspNet.Identity CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); - return await GenerateUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", cancellationToken); + var token = await GenerateUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -781,15 +811,15 @@ namespace Microsoft.AspNet.Identity // Make sure the token is valid and the stamp matches if (!await VerifyUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", token, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.InvalidToken()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); } var passwordStore = GetPasswordStore(); var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken); if (!result.Succeeded) { - return result; + return await LogResultAsync(result, user); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } // Update the security stamp if the store supports it @@ -866,7 +896,7 @@ namespace Microsoft.AspNet.Identity } await loginStore.RemoveLoginAsync(user, loginProvider, providerKey, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -892,10 +922,10 @@ namespace Microsoft.AspNet.Identity var existingUser = await FindByLoginAsync(login.LoginProvider, login.ProviderKey, cancellationToken); if (existingUser != null) { - return IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated()), user); } await loginStore.AddLoginAsync(user, login, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -971,7 +1001,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await claimStore.AddClaimsAsync(user, claims, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1000,7 +1030,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await claimStore.ReplaceClaimAsync(user, claim, newClaim, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1047,7 +1077,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("claims"); } await claimStore.RemoveClaimsAsync(user, claims, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1097,10 +1127,10 @@ namespace Microsoft.AspNet.Identity var userRoles = await userRoleStore.GetRolesAsync(user, cancellationToken); if (userRoles.Contains(role)) { - return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)), user); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1128,11 +1158,11 @@ namespace Microsoft.AspNet.Identity { if (userRoles.Contains(role)) { - return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)), user); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1153,10 +1183,10 @@ namespace Microsoft.AspNet.Identity } if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)), user); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1183,11 +1213,11 @@ namespace Microsoft.AspNet.Identity { if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)), user); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1276,7 +1306,7 @@ namespace Microsoft.AspNet.Identity await store.SetEmailAsync(user, email, cancellationToken); await store.SetEmailConfirmedAsync(user, false, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1321,11 +1351,14 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual Task GenerateEmailConfirmationTokenAsync(TUser user, + public async virtual Task GenerateEmailConfirmationTokenAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); - return GenerateUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", cancellationToken); + var token = await GenerateUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -1346,10 +1379,10 @@ namespace Microsoft.AspNet.Identity } if (!await VerifyUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", token, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.InvalidToken()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); } await store.SetEmailConfirmedAsync(user, true, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1385,7 +1418,10 @@ namespace Microsoft.AspNet.Identity CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); - return await GenerateUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), cancellationToken); + var token = await GenerateUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -1407,13 +1443,13 @@ namespace Microsoft.AspNet.Identity // Make sure the token is valid and the stamp matches if (!await VerifyUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), token, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.InvalidToken()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); } var store = GetEmailStore(); await store.SetEmailAsync(user, newEmail, cancellationToken); await store.SetEmailConfirmedAsync(user, true, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } // IUserPhoneNumberStore methods @@ -1464,7 +1500,7 @@ namespace Microsoft.AspNet.Identity await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken); await store.SetPhoneNumberConfirmedAsync(user, false, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1486,12 +1522,12 @@ namespace Microsoft.AspNet.Identity } if (!await VerifyChangePhoneNumberTokenAsync(user, token, phoneNumber, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.InvalidToken()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); } await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken); await store.SetPhoneNumberConfirmedAsync(user, true, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1530,9 +1566,12 @@ namespace Microsoft.AspNet.Identity CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); - return Rfc6238AuthenticationService.GenerateCode( + var token = Rfc6238AuthenticationService.GenerateCode( await CreateSecurityTokenAsync(user, cancellationToken), phoneNumber) .ToString(CultureInfo.InvariantCulture); + + await LogResultAsync(IdentityResult.Success, user); + return token; } /// @@ -1550,8 +1589,13 @@ namespace Microsoft.AspNet.Identity int code; if (securityToken != null && Int32.TryParse(token, out code)) { - return Rfc6238AuthenticationService.ValidateCode(securityToken, code, phoneNumber); + if (Rfc6238AuthenticationService.ValidateCode(securityToken, code, phoneNumber)) + { + await LogResultAsync(IdentityResult.Success, user); + return true; + } } + await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); return false; } @@ -1580,7 +1624,18 @@ namespace Microsoft.AspNet.Identity throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } // Make sure the token is valid - return await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user, cancellationToken); + var result = await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user, cancellationToken); + + if (result) + { + await LogResultAsync(IdentityResult.Success, user); + } + else + { + await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); + } + + return result; } /// @@ -1606,7 +1661,11 @@ namespace Microsoft.AspNet.Identity { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } - return await _tokenProviders[tokenProvider].GenerateAsync(purpose, this, user, cancellationToken); + + var token = await _tokenProviders[tokenProvider].GenerateAsync(purpose, this, user, cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -1684,7 +1743,18 @@ namespace Microsoft.AspNet.Identity Resources.NoTokenProvider, tokenProvider)); } // Make sure the token is valid - return await _tokenProviders[tokenProvider].ValidateAsync("TwoFactor", token, this, user, cancellationToken); + var result = await _tokenProviders[tokenProvider].ValidateAsync("TwoFactor", token, this, user, cancellationToken); + + if (result) + { + await LogResultAsync(IdentityResult.Success, user); + } + else + { + await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); + } + + return result; } /// @@ -1707,7 +1777,10 @@ namespace Microsoft.AspNet.Identity throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } - return await _tokenProviders[tokenProvider].GenerateAsync("TwoFactor", this, user, cancellationToken); + var token = await _tokenProviders[tokenProvider].GenerateAsync("TwoFactor", this, user, cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -1736,7 +1809,7 @@ namespace Microsoft.AspNet.Identity Resources.NoTokenProvider, tokenProvider)); } await _tokenProviders[tokenProvider].NotifyAsync(token, this, user, cancellationToken); - return IdentityResult.Success; + return await LogResultAsync(IdentityResult.Success, user); } // IUserFactorStore methods @@ -1786,7 +1859,7 @@ namespace Microsoft.AspNet.Identity } await store.SetTwoFactorEnabledAsync(user, enabled, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } // Messaging methods @@ -1798,7 +1871,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task SendMessageAsync(string messageProvider, IdentityMessage message, + public virtual async Task SendMessageAsync(string messageProvider, IdentityMessage message, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -1866,7 +1939,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await store.SetLockoutEnabledAsync(user, enabled, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1923,10 +1996,10 @@ namespace Microsoft.AspNet.Identity } if (!await store.GetLockoutEnabledAsync(user, cancellationToken).ConfigureAwait((false))) { - return IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled()), user); } await store.SetLockoutEndDateAsync(user, lockoutEnd, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1950,12 +2023,12 @@ namespace Microsoft.AspNet.Identity var count = await store.IncrementAccessFailedCountAsync(user, cancellationToken); if (count < Options.Lockout.MaxFailedAccessAttempts) { - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), cancellationToken); await store.ResetAccessFailedCountAsync(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1974,7 +2047,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await store.ResetAccessFailedCountAsync(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -2026,6 +2099,44 @@ namespace Microsoft.AspNet.Identity return store.GetUsersInRoleAsync(roleName, cancellationToken); } + /// + /// Logs the current Identity Result and returns result object + /// + /// + /// + /// + /// + protected async Task LogResultAsync(IdentityResult result, + TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + result.Log(Logger, Resources.FormatLoggingResultMessage(methodName, await GetUserIdAsync(user))); + + return result; + } + + /// + /// Logs result of operation being true/false + /// + /// + /// + /// + /// result + protected async Task LogResultAsync(bool result, + TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + var baseMessage = Resources.FormatLoggingResultMessage(methodName, await GetUserIdAsync(user)); + if (result) + { + Logger.WriteInformation(string.Format("{0} : {1}", baseMessage, result.ToString())); + } + else + { + Logger.WriteWarning(string.Format("{0} : {1}", baseMessage, result.ToString())); + } + + return result; + } + private void ThrowIfDisposed() { if (_disposed) diff --git a/src/Microsoft.AspNet.Identity/project.json b/src/Microsoft.AspNet.Identity/project.json index 0ef1603874..ad83945ac3 100644 --- a/src/Microsoft.AspNet.Identity/project.json +++ b/src/Microsoft.AspNet.Identity/project.json @@ -7,7 +7,8 @@ "Microsoft.AspNet.Security.DataProtection": "1.0.0-*", "Microsoft.Framework.ConfigurationModel": "1.0.0-*", "Microsoft.Framework.DependencyInjection" : "1.0.0-*", - "Microsoft.Framework.OptionsModel": "1.0.0-*" + "Microsoft.Framework.OptionsModel": "1.0.0-*", + "Microsoft.Framework.Logging": "1.0.0-*" }, "frameworks": { "aspnet50": {}, diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs index 66c01e5998..758e799ff9 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Identity.Test; using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test { - public class InMemoryEFUserStoreTest : UserManagerTestBase + public class InMemoryEFUserStoreTest : UserManagerTestBase,IDisposable { protected override object CreateTestContext() { @@ -23,5 +24,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test var store = new RoleStore((InMemoryContext)context); services.AddInstance>(store); } + + public void Dispose() + { + loggerFactory.Dispose(); + } } } diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs b/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs index b7e52134aa..3b6aca6f78 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs @@ -7,10 +7,7 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Identity.Test; -using Microsoft.Data.Entity; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; -using Microsoft.Framework.OptionsModel; using Microsoft.Framework.Runtime.Infrastructure; using Xunit; @@ -37,6 +34,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test public void DropDatabaseDone() { DropDb(); + loggerFactory.Dispose(); } public void DropDb() diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs index 9fbc2536dc..7cca942833 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Identity.Test; using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Identity.InMemory.Test { - public class InMemoryStoreTest : UserManagerTestBase + public class InMemoryStoreTest : UserManagerTestBase, IDisposable { protected override object CreateTestContext() { @@ -22,5 +23,10 @@ namespace Microsoft.AspNet.Identity.InMemory.Test { services.AddSingleton, InMemoryRoleStore>(); } + + public void Dispose() + { + loggerFactory.Dispose(); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs index 488fcc7545..ee4b210d7b 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs @@ -10,6 +10,7 @@ using System; using System.Threading; using System.Threading.Tasks; using System.Linq; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Identity.Test { @@ -236,13 +237,13 @@ namespace Microsoft.AspNet.Identity.Test private class MyUserManager : UserManager { - public MyUserManager(IUserStore store) : base(store) { } + public MyUserManager(IUserStore store) : base(store) { } } private class MyRoleManager : RoleManager { public MyRoleManager(IRoleStore store, - IEnumerable> roleValidators) : base(store, roleValidators) + IEnumerable> roleValidators) : base(store) { } diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs index 7d24b52bce..f65ab23cfb 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.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.Linq; +using System.Text; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -23,5 +24,29 @@ namespace Microsoft.AspNet.Identity.Test Assert.False(result.Succeeded); Assert.Equal(0, result.Errors.Count()); } + + [Fact] + public void VerifySuccessResultLog() + { + var result = IdentityResult.Success; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Success", logMessage.ToString()); + } + + [Fact] + public void VerifyFailureResultLog() + { + var result = IdentityResult.Failed(new IdentityError() { Code = "Foo" }, new IdentityError() { Code = "Bar" }); + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Failed : Foo,Bar", logMessage.ToString()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj b/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj index 1bbfe3ac3e..3c06d031ab 100644 --- a/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj +++ b/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -14,4 +14,9 @@ 2.0 - + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs index cab11a83df..99e35a31fb 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs @@ -124,7 +124,7 @@ namespace Microsoft.AspNet.Identity.Test public async Task RoleManagerPublicNullChecks() { Assert.Throws("store", - () => new RoleManager(null, null)); + () => new RoleManager(null, null, null)); var manager = CreateRoleManager(new NotImplementedStore()); await Assert.ThrowsAsync("role", async () => await manager.CreateAsync(null)); await Assert.ThrowsAsync("role", async () => await manager.UpdateAsync(null)); @@ -150,7 +150,7 @@ namespace Microsoft.AspNet.Identity.Test { var v = new List>(); v.Add(new RoleValidator()); - return new RoleManager(roleStore, v); + return new RoleManager(roleStore); } private class NotImplementedStore : IRoleStore diff --git a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs index 75a84c24cb..f1b0aa48fa 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var validator = new RoleValidator(); - var manager = new RoleManager(new NoopRoleStore(), null); + var manager = new RoleManager(new NoopRoleStore()); // Act // Assert @@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var validator = new RoleValidator(); - var manager = new RoleManager(new NoopRoleStore(), null); + var manager = new RoleManager(new NoopRoleStore()); var user = new TestRole {Name = input}; // Act diff --git a/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs index 1528665dde..4bad958493 100644 --- a/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs @@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Identity.Test var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, - contextAccessor.Object, claimsManager.Object, options.Object); + contextAccessor.Object, claimsManager.Object, options.Object, null); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny(), user.Id, CancellationToken.None)).ReturnsAsync(user).Verifiable(); signInManager.Setup(s => s.SignInAsync(user, isPersistent, null, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); var services = new ServiceCollection(); @@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Identity.Test var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, - contextAccessor.Object, claimsManager.Object, options.Object); + contextAccessor.Object, claimsManager.Object, options.Object, null); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny(), user.Id, CancellationToken.None)).ReturnsAsync(null).Verifiable(); var services = new ServiceCollection(); services.AddInstance(options.Object); @@ -116,7 +116,7 @@ namespace Microsoft.AspNet.Identity.Test var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, - contextAccessor.Object, claimsManager.Object, options.Object); + contextAccessor.Object, claimsManager.Object, options.Object, null); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny(), user.Id, CancellationToken.None)).ReturnsAsync(null).Verifiable(); var services = new ServiceCollection(); services.AddInstance(options.Object); @@ -150,7 +150,7 @@ namespace Microsoft.AspNet.Identity.Test var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, - contextAccessor.Object, claimsManager.Object, options.Object); + contextAccessor.Object, claimsManager.Object, options.Object, null); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny(), user.Id, CancellationToken.None)).Throws(new Exception("Shouldn't be called")); signInManager.Setup(s => s.SignInAsync(user, false, null, CancellationToken.None)).Throws(new Exception("Shouldn't be called")); var services = new ServiceCollection(); diff --git a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs index 7d1d1d6c90..401d4cf384 100644 --- a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Security.Claims; using System.Security.Principal; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Hosting; @@ -68,7 +69,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public void ConstructorNullChecks() { - Assert.Throws("userManager", () => new SignInManager(null, null, null, null)); + Assert.Throws("userManager", () => new SignInManager(null, null, null, null, null)); var userManager = MockHelpers.MockUserManager().Object; Assert.Throws("contextAccessor", () => new SignInManager(userManager, null, null, null)); var contextAccessor = new Mock(); @@ -117,6 +118,8 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable(); manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); + var context = new Mock(); var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); @@ -125,7 +128,10 @@ namespace Microsoft.AspNet.Identity.Test var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "PasswordSignInAsync", user.Id, "Lockedout"); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); @@ -133,9 +139,10 @@ namespace Microsoft.AspNet.Identity.Test // Assert Assert.False(result.Succeeded); Assert.True(result.IsLockedOut); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); } - + [Theory] [InlineData(true)] [InlineData(false)] @@ -149,6 +156,8 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.ResetAccessFailedCountAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); + var context = new Mock(); var response = new Mock(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); @@ -161,13 +170,17 @@ namespace Microsoft.AspNet.Identity.Test options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); claimsFactory.Setup(m => m.CreateAsync(user, CancellationToken.None)).ReturnsAsync(new ClaimsIdentity("Microsoft.AspNet.Identity")).Verifiable(); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "PasswordSignInAsync", user.Id, "Succeeded"); // Act var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); // Assert Assert.True(result.Succeeded); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -186,6 +199,8 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.ResetAccessFailedCountAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); + var context = new Mock(); var response = new Mock(); response.Setup(r => r.SignIn(It.IsAny(), It.IsAny())).Verifiable(); @@ -245,7 +260,10 @@ namespace Microsoft.AspNet.Identity.Test var identityOptions = new IdentityOptions(); var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); - var helper = new SignInManager(manager.Object, contextAccessor.Object, new ClaimsIdentityFactory(manager.Object, roleManager.Object, options.Object), options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, new ClaimsIdentityFactory(manager.Object, roleManager.Object, options.Object), options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "PasswordSignInAsync", user.Id, "RequiresTwoFactor"); // Act var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); @@ -253,6 +271,7 @@ namespace Microsoft.AspNet.Identity.Test // Assert Assert.False(result.Succeeded); Assert.True(result.RequiresTwoFactor); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -277,6 +296,8 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(false).Verifiable(); } manager.Setup(m => m.FindByLoginAsync(loginProvider, providerKey, CancellationToken.None)).ReturnsAsync(user).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); + var context = new Mock(); var response = new Mock(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); @@ -292,13 +313,17 @@ namespace Microsoft.AspNet.Identity.Test options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); claimsFactory.Setup(m => m.CreateAsync(user, CancellationToken.None)).ReturnsAsync(new ClaimsIdentity("Microsoft.AspNet.Identity")).Verifiable(); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "ExternalLoginSignInAsync", user.Id.ToString(), "Succeeded"); // Act var result = await helper.ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent); // Assert Assert.True(result.Succeeded); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -323,7 +348,6 @@ namespace Microsoft.AspNet.Identity.Test [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 @@ -377,13 +401,17 @@ namespace Microsoft.AspNet.Identity.Test context.Setup(c => c.Response).Returns(response.Object).Verifiable(); context.Setup(c => c.AuthenticateAsync(IdentityOptions.TwoFactorUserIdCookieAuthenticationType)).ReturnsAsync(authResult).Verifiable(); contextAccessor.Setup(a => a.Value).Returns(context.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "TwoFactorSignInAsync", user.Id.ToString(), "Succeeded"); // Act var result = await helper.TwoFactorSignInAsync(provider, code, isPersistent, rememberClient); // Assert Assert.True(result.Succeeded); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -496,7 +524,9 @@ namespace Microsoft.AspNet.Identity.Test options.Setup(a => a.Options).Returns(identityOptions); IdentityOptions.ApplicationCookieAuthenticationType = authenticationType; var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); // Act helper.SignOut(); @@ -518,6 +548,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(false).Verifiable(); manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "bogus", CancellationToken.None)).ReturnsAsync(false).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); var context = new Mock(); var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); @@ -526,12 +557,16 @@ namespace Microsoft.AspNet.Identity.Test var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "PasswordSignInAsync", user.Id.ToString(), "Failed"); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); // Assert Assert.False(result.Succeeded); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); contextAccessor.VerifyAll(); @@ -611,6 +646,7 @@ namespace Microsoft.AspNet.Identity.Test { manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); } + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); var context = new Mock(); var response = new Mock(); if (confirmed) @@ -627,7 +663,10 @@ namespace Microsoft.AspNet.Identity.Test var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "CanSignInAsync", user.Id.ToString(), confirmed.ToString()); // Act var result = await helper.PasswordSignInAsync(user, "password", false, false); @@ -636,6 +675,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(confirmed, result.Succeeded); Assert.NotEqual(confirmed, result.IsNotAllowed); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -650,7 +690,8 @@ namespace Microsoft.AspNet.Identity.Test // Setup var user = new TestUser { UserName = "Foo" }; var manager = MockHelpers.MockUserManager(); - manager.Setup(m => m.IsEmailConfirmedAsync(user, CancellationToken.None)).ReturnsAsync(confirmed).Verifiable(); + manager.Setup(m => m.IsPhoneNumberConfirmedAsync(user, CancellationToken.None)).ReturnsAsync(confirmed).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); var context = new Mock(); var response = new Mock(); if (confirmed) @@ -664,11 +705,14 @@ namespace Microsoft.AspNet.Identity.Test contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); - identityOptions.SignIn.RequireConfirmedEmail = true; + identityOptions.SignIn.RequireConfirmedPhoneNumber = true; var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "CanSignInAsync", user.Id.ToString(), confirmed.ToString()); // Act var result = await helper.PasswordSignInAsync(user, "password", false, false); @@ -676,6 +720,7 @@ namespace Microsoft.AspNet.Identity.Test // Assert Assert.Equal(confirmed, result.Succeeded); Assert.NotEqual(confirmed, result.IsNotAllowed); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); diff --git a/test/Microsoft.AspNet.Identity.Test/SignInResultTest.cs b/test/Microsoft.AspNet.Identity.Test/SignInResultTest.cs new file mode 100644 index 0000000000..ce7a522937 --- /dev/null +++ b/test/Microsoft.AspNet.Identity.Test/SignInResultTest.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text; +using Xunit; + +namespace Microsoft.AspNet.Identity.Test +{ + public class SignInResultTest + { + [Fact] + public void VerifyLogSuccess() + { + var result = SignInResult.Success; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : Succeeded", logMessage.ToString()); + } + + [Fact] + public void VerifyLogLockedOut() + { + var result = SignInResult.LockedOut; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : Lockedout", logMessage.ToString()); + } + + [Fact] + public void VerifyLogNotAllowed() + { + var result = SignInResult.NotAllowed; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : NotAllowed", logMessage.ToString()); + } + + [Fact] + public void VerifyLogRequiresTwoFactor() + { + var result = SignInResult.TwoFactorRequired; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : RequiresTwoFactor", logMessage.ToString()); + } + + [Fact] + public void VerifyLogRequiresFailed() + { + var result = SignInResult.Failed; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : Failed", logMessage.ToString()); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index e4cf94ecd5..62f1e77917 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -175,7 +175,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var store = new Mock>(); - var user = new TestUser {UserName="Foo"}; + var user = new TestUser { UserName = "Foo" }; store.Setup(s => s.FindByNameAsync(user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); @@ -192,7 +192,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var store = new Mock>(); - var user = new TestUser {UserName="Foo"}; + var user = new TestUser { UserName = "Foo" }; store.Setup(s => s.FindByNameAsync(user.UserName, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); userManager.KeyNormalizer = null; @@ -246,7 +246,7 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; - var roles = new string[] {"A", "B", "C"}; + var roles = new string[] { "A", "B", "C" }; store.Setup(s => s.AddToRoleAsync(user, "A", CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); @@ -642,7 +642,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new NotImplementedStore(); Assert.Throws("store", - () => new UserManager(null)); + () => new UserManager(null, null)); var manager = new UserManager(store); @@ -682,7 +682,7 @@ namespace Microsoft.AspNet.Identity.Test await Assert.ThrowsAsync("user", async () => await manager.AddClaimAsync(null, new Claim("a", "b"))); await Assert.ThrowsAsync("user", - async () => await manager.AddLoginAsync(null, new UserLoginInfo("","",""))); + async () => await manager.AddLoginAsync(null, new UserLoginInfo("", "", ""))); await Assert.ThrowsAsync("user", async () => await manager.AddPasswordAsync(null, null)); await Assert.ThrowsAsync("user", diff --git a/test/Shared/IdentityResultAssert.cs b/test/Shared/IdentityResultAssert.cs index 617e5c0c6b..907aacecb1 100644 --- a/test/Shared/IdentityResultAssert.cs +++ b/test/Shared/IdentityResultAssert.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.IO; using System.Linq; +using Microsoft.Framework.Logging; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -35,5 +37,69 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(error.Code, result.Errors.First().Code); } + public static void VerifyUserManagerFailureLog(ILogger logger, string methodName, string userId, params IdentityError[] errors) + { + VerifyFailureLog(logger, "UserManager", methodName, userId, "user", errors); + } + + public static void VerifyRoleManagerFailureLog(ILogger logger, string methodName, string roleId, params IdentityError[] errors) + { + VerifyFailureLog(logger, "RoleManager", methodName, roleId, "role", errors); + } + + public static void VerifyUserManagerSuccessLog(ILogger logger, string methodName, string userId) + { + VerifySuccessLog(logger, "UserManager", methodName, userId, "user"); + + } + + public static void VerifyRoleManagerSuccessLog(ILogger logger, string methodName, string roleId) + { + VerifySuccessLog(logger, "RoleManager", methodName, roleId, "role"); + + } + private static void VerifySuccessLog(ILogger logger, string className, string methodName, string id, string userOrRole = "user") + { + if (logger is TestFileLogger) + { + var fileLogger = logger as TestFileLogger; + string expected = string.Format("{0} for {1}: {2} : Success", methodName, userOrRole, id); + + Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); + } + else + { + Assert.True(true, "No logger registered"); + } + } + + public static void VerifyLogMessage(ILogger logger, string expectedLog) + { + if (logger is TestFileLogger) + { + var fileLogger = logger as TestFileLogger; + Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expectedLog)); + } + else + { + Assert.True(true, "No logger registered"); + } + } + + private static void VerifyFailureLog(ILogger logger, string className, string methodName, string userId, string userOrRole = "user", params IdentityError[] errors) + { + if (logger is TestFileLogger) + { + var fileLogger = logger as TestFileLogger; + errors = errors ?? new IdentityError[] { new IdentityError() }; + string expected = string.Format("{0} for {1}: {2} : Failed : {3}", methodName, userOrRole, userId, string.Join(",", errors.Select(x => x.Code).ToList())); + + Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); + } + else + { + Assert.True(true, "No logger registered"); + } + } } } \ No newline at end of file diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs index f70541bec0..36f2690c60 100644 --- a/test/Shared/MockHelpers.cs +++ b/test/Shared/MockHelpers.cs @@ -1,20 +1,24 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.Threading; using System.Threading.Tasks; -using Microsoft.Framework.OptionsModel; +using Microsoft.Framework.Logging; using Moq; +using System.Text; namespace Microsoft.AspNet.Identity.Test { public static class MockHelpers { + public static StringBuilder LogMessage = new StringBuilder(); + public static Mock> MockUserManager() where TUser : class { var store = new Mock>(); - var mgr = new Mock>(store.Object, null, null, null, null, null, null, null, null); + var mgr = new Mock>(store.Object, null, null, null, null, null, null, null, null, null); mgr.Object.UserValidators.Add(new UserValidator()); mgr.Object.PasswordValidators.Add(new PasswordValidator()); return mgr; @@ -25,7 +29,37 @@ namespace Microsoft.AspNet.Identity.Test store = store ?? new Mock>().Object; var roles = new List>(); roles.Add(new RoleValidator()); - return new Mock>(store, roles, null, null); + return new Mock>(store, roles, null, null,null); + } + + public static Mock MockILogger(StringBuilder logStore = null) + { + logStore = logStore ?? LogMessage; + var logger = new Mock(); + logger.Setup(x => x.Write(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny>())) + .Callback((LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) => + { logStore.Append(state.ToString()); }); + logger.Setup(x => x.IsEnabled(LogLevel.Information)).Returns(true); + logger.Setup(x => x.IsEnabled(LogLevel.Warning)).Returns(true); + + return logger; + } + + public static Mock MockILoggerFactory(ILogger logger = null) + { + logger = logger ?? MockILogger().Object; + var loggerFactory = new Mock(); + loggerFactory.Setup(x => x.Create(It.IsAny())).Returns(logger); + return loggerFactory; + } + + public static UserManager UserManagerWithMockLogger(ILoggerFactory loggerFactory = null) where TUser : class + { + var userstore = new Mock>().Object; + var userManager = new UserManager(userstore, loggerFactory: loggerFactory ?? MockILoggerFactory().Object); + + return userManager; } public static UserManager TestUserManager(IUserStore store = null) where TUser : class diff --git a/test/Shared/TestFileLogger.cs b/test/Shared/TestFileLogger.cs new file mode 100644 index 0000000000..8dc93126d2 --- /dev/null +++ b/test/Shared/TestFileLogger.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.IO; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Identity.Test +{ + public class TestFileLogger : ILogger + { + public string FileName { get; set; } + + public object FileLock { get; private set; } = new object(); + + public TestFileLogger(string name) + { + var directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "IdentityTests"); + Directory.CreateDirectory(directory); + FileName = Path.Combine(directory, (name + DateTime.Now.Ticks + "log.txt")); + if (!File.Exists(FileName)) + { + File.Create(FileName).Close(); + } + } + + public IDisposable BeginScope(object state) + { + throw new NotImplementedException(); + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Write(LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) + { + lock (FileLock) + { + File.AppendAllLines(FileName, new string[] { state.ToString() }); + } + } + } +} \ No newline at end of file diff --git a/test/Shared/TestFileLoggerFactory.cs b/test/Shared/TestFileLoggerFactory.cs new file mode 100644 index 0000000000..eb6f8f86f4 --- /dev/null +++ b/test/Shared/TestFileLoggerFactory.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading.Tasks; +using System.IO; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Identity.Test +{ + public class TestFileLoggerFactory : ILoggerFactory, IDisposable + { + private static Dictionary _loggers; + + static TestFileLoggerFactory() + { + _loggers = new Dictionary(); + } + + public void AddProvider(ILoggerProvider provider) + { + + } + + public ILogger Create(string name) + { + if (!_loggers.ContainsKey(name)) + { + _loggers.Add(name, new TestFileLogger(name)); + } + + return _loggers[name]; + } + + public void Dispose() + { + Parallel.ForEach(_loggers.Values, l => + { + if(l is TestFileLogger) + { + var logger = l as TestFileLogger; + File.Delete(logger.FileName); + } + }); + } + } +} diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index 1be1741852..037a8b1ca9 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Testing; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; +using Microsoft.Framework.Logging; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -20,17 +21,25 @@ namespace Microsoft.AspNet.Identity.Test where TRole : IdentityRole, new() { } - public abstract class UserManagerTestBase - where TUser: IdentityUser, new() - where TRole: IdentityRole, new() + public abstract class UserManagerTestBase + where TUser : IdentityUser, new() + where TRole : IdentityRole, new() where TKey : IEquatable { + protected TestFileLoggerFactory loggerFactory; + + public UserManagerTestBase() + { + loggerFactory = new TestFileLoggerFactory(); + } + protected virtual void SetupIdentityServices(IServiceCollection services, object context = null) { services.AddHosting(); services.AddIdentity().AddDefaultTokenProviders(); AddUserStore(services, context); AddRoleStore(services, context); + services.AddInstance(loggerFactory); services.ConfigureIdentity(options => { options.Password.RequireDigit = false; @@ -74,11 +83,13 @@ namespace Microsoft.AspNet.Identity.Test protected abstract void AddUserStore(IServiceCollection services, object context = null); protected abstract void AddRoleStore(IServiceCollection services, object context = null); - protected TUser CreateTestUser(string namePrefix = "") { + protected TUser CreateTestUser(string namePrefix = "") + { return new TUser() { UserName = namePrefix + Guid.NewGuid().ToString() }; } - protected TRole CreateRole(string namePrefix = "") { + protected TRole CreateRole(string namePrefix = "") + { return new TRole() { Name = namePrefix + Guid.NewGuid().ToString() }; } @@ -88,7 +99,11 @@ namespace Microsoft.AspNet.Identity.Test var manager = CreateManager(); var user = CreateTestUser(); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "CreateAsync", user.Id.ToString()); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "DeleteAsync", user.Id.ToString()); + Assert.Null(await manager.FindByIdAsync(user.Id.ToString())); } @@ -103,6 +118,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Null(await manager.FindByNameAsync(newName)); user.UserName = newName; IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "UpdateAsync", user.Id.ToString()); Assert.NotNull(await manager.FindByNameAsync(newName)); Assert.Null(await manager.FindByNameAsync(name)); } @@ -126,9 +142,14 @@ namespace Microsoft.AspNet.Identity.Test var user = new TUser() { UserName = "UpdatePassword" }; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); Assert.True(await manager.CheckPasswordAsync(user, "password")); + string expectedLog = string.Format("{0} for user: {1} : {2}", "CheckPasswordAsync", user.Id.ToString(), true.ToString()); + IdentityResultAssert.VerifyLogMessage(manager.Logger, expectedLog); + user.PasswordHash = manager.PasswordHasher.HashPassword(user, "New"); IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); Assert.False(await manager.CheckPasswordAsync(user, "password")); + expectedLog = string.Format("{0} for user: {1} : {2}", "CheckPasswordAsync", user.Id.ToString(), false.ToString()); + IdentityResultAssert.VerifyLogMessage(manager.Logger, expectedLog); Assert.True(await manager.CheckPasswordAsync(user, "New")); } @@ -160,6 +181,7 @@ namespace Microsoft.AspNet.Identity.Test manager.UserValidators.Clear(); manager.UserValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.UpdateAsync(user), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "UpdateAsync", user.Id.ToString(), AlwaysBadValidator.ErrorMessage); } [Fact] @@ -208,6 +230,7 @@ namespace Microsoft.AspNet.Identity.Test manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "AddPasswordAsync", user.Id.ToString(), AlwaysBadValidator.ErrorMessage); } [Fact] @@ -234,6 +257,17 @@ namespace Microsoft.AspNet.Identity.Test manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.ChangePasswordAsync(user, "password", "new"), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ChangePasswordAsync", user.Id.ToString(), AlwaysBadValidator.ErrorMessage); + } + + [Fact] + public async Task PasswordValidatorCanBlockCreateUser() + { + var manager = CreateManager(); + var user = CreateTestUser(); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user, "password"), AlwaysBadValidator.ErrorMessage); } [Fact] @@ -260,6 +294,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); user = await manager.FindByNameAsync(user.UserName); IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, new UserLoginInfo(provider, providerKey, display))); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddLoginAsync", user.Id.ToString()); var logins = await manager.GetLoginsAsync(user); Assert.NotNull(logins); Assert.Equal(1, logins.Count()); @@ -278,6 +313,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); Assert.False(await manager.HasPasswordAsync(user)); IdentityResultAssert.IsSuccess(await manager.AddPasswordAsync(user, "password")); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddPasswordAsync", user.Id.ToString()); Assert.True(await manager.HasPasswordAsync(user)); var logins = await manager.GetLoginsAsync(user); Assert.NotNull(logins); @@ -295,6 +331,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.True(await manager.HasPasswordAsync(user)); IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), "User already has a password set."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "AddPasswordAsync", user.Id.ToString(), IdentityErrorDescriber.Default.UserAlreadyHasPassword()); } [Fact] @@ -316,6 +353,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(login.ProviderDisplayName, logins.Last().ProviderDisplayName); var stamp = user.SecurityStamp; IdentityResultAssert.IsSuccess(await manager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "RemoveLoginAsync", user.Id.ToString()); Assert.Null(await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey)); logins = await manager.GetLoginsAsync(user); Assert.NotNull(logins); @@ -332,6 +370,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); var stamp = user.SecurityStamp; IdentityResultAssert.IsSuccess(await manager.RemovePasswordAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "RemovePasswordAsync", user.Id.ToString()); var u = await manager.FindByNameAsync(user.UserName); Assert.NotNull(u); Assert.Null(u.PasswordHash); @@ -351,6 +390,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, password, newPassword)); Assert.False(await manager.CheckPasswordAsync(user, password)); Assert.True(await manager.CheckPasswordAsync(user, newPassword)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ChangePasswordAsync", user.Id.ToString()); Assert.NotEqual(stamp, user.SecurityStamp); } @@ -365,9 +405,11 @@ namespace Microsoft.AspNet.Identity.Test { IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); } + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddClaimsAsync", user.Id.ToString()); var userClaims = await manager.GetClaimsAsync(user); Assert.Equal(3, userClaims.Count); IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "RemoveClaimsAsync", user.Id.ToString()); userClaims = await manager.GetClaimsAsync(user); Assert.Equal(2, userClaims.Count); IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); @@ -419,6 +461,7 @@ namespace Microsoft.AspNet.Identity.Test Claim claim = new Claim("c", "b"); Claim oldClaim = userClaims.FirstOrDefault(); IdentityResultAssert.IsSuccess(await manager.ReplaceClaimAsync(user, oldClaim, claim)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ReplaceClaimAsync", user.Id.ToString()); var newUserClaims = await manager.GetClaimsAsync(user); Assert.Equal(1, newUserClaims.Count); Claim newClaim = newUserClaims.FirstOrDefault(); @@ -463,6 +506,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); var result = await manager.ChangePasswordAsync(user, "bogus", "newpassword"); IdentityResultAssert.IsFailure(result, "Incorrect password."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ChangePasswordAsync", user.Id.ToString(), IdentityErrorDescriber.Default.PasswordMismatch()); } [Fact] @@ -511,6 +555,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = user.SecurityStamp; Assert.NotNull(stamp); IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "UpdateSecurityStampAsync", user.Id.ToString()); Assert.NotEqual(stamp, user.SecurityStamp); } @@ -524,6 +569,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); var result = await manager.AddLoginAsync(user, login); IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.LoginAlreadyAssociated()); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "AddLoginAsync", user.Id.ToString(), IdentityErrorDescriber.Default.LoginAlreadyAssociated()); } // Email tests @@ -607,7 +653,9 @@ namespace Microsoft.AspNet.Identity.Test Assert.NotNull(stamp); var token = await manager.GeneratePasswordResetTokenAsync(user); Assert.NotNull(token); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GeneratePasswordResetTokenAsync", user.Id.ToString()); IdentityResultAssert.IsSuccess(await manager.ResetPasswordAsync(user, token, newPassword)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ResetPasswordAsync", user.Id.ToString()); Assert.False(await manager.CheckPasswordAsync(user, password)); Assert.True(await manager.CheckPasswordAsync(user, newPassword)); Assert.NotEqual(stamp, user.SecurityStamp); @@ -630,6 +678,7 @@ namespace Microsoft.AspNet.Identity.Test manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, token, newPassword), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ResetPasswordAsync", user.Id.ToString(), AlwaysBadValidator.ErrorMessage); Assert.True(await manager.CheckPasswordAsync(user, password)); Assert.Equal(stamp, user.SecurityStamp); } @@ -647,6 +696,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = user.SecurityStamp; Assert.NotNull(stamp); IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, "bogus", newPassword), "Invalid token."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ResetPasswordAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); Assert.True(await manager.CheckPasswordAsync(user, password)); Assert.Equal(stamp, user.SecurityStamp); } @@ -661,8 +711,14 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); var token = await manager.GenerateUserTokenAsync(user, "Static", "test"); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GenerateUserTokenAsync", user.Id.ToString()); + Assert.True(await manager.VerifyUserTokenAsync(user, "Static", "test", token)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "VerifyUserTokenAsync", user.Id.ToString()); + Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test2", token)); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "VerifyUserTokenAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); + Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test", token + "a")); Assert.False(await manager.VerifyUserTokenAsync(user2, "Static", "test", token)); } @@ -678,7 +734,9 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); var token = await manager.GenerateEmailConfirmationTokenAsync(user); Assert.NotNull(token); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GenerateEmailConfirmationTokenAsync", user.Id.ToString()); IdentityResultAssert.IsSuccess(await manager.ConfirmEmailAsync(user, token)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ConfirmEmailAsync", user.Id.ToString()); Assert.True(await manager.IsEmailConfirmedAsync(user)); IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); Assert.False(await manager.IsEmailConfirmedAsync(user)); @@ -695,6 +753,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, "bogus"), "Invalid token."); Assert.False(await manager.IsEmailConfirmedAsync(user)); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ConfirmEmailAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); } [Fact] @@ -770,6 +829,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); IdentityResultAssert.IsSuccess(await mgr.ResetAccessFailedCountAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(mgr.Logger, "ResetAccessFailedCountAsync", user.Id.ToString()); Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); Assert.False(await mgr.IsLockedOutAsync(user)); Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); @@ -792,8 +852,10 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await mgr.SetLockoutEnabledAsync(user, true)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); Assert.True(user.LockoutEnabled); + IdentityResultAssert.VerifyUserManagerSuccessLog(mgr.Logger, "SetLockoutEnabledAsync", user.Id.ToString()); Assert.False(await mgr.IsLockedOutAsync(user)); IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(mgr.Logger, "AccessFailedAsync", user.Id.ToString()); Assert.False(await mgr.IsLockedOutAsync(user)); Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); @@ -814,6 +876,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.True(user.LockoutEnabled); IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset())); Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(mgr.Logger, "SetLockoutEndDateAsync", user.Id.ToString()); Assert.Equal(new DateTimeOffset(), await mgr.GetLockoutEndDateAsync(user)); Assert.Equal(new DateTimeOffset(), user.LockoutEnd); } @@ -900,7 +963,7 @@ namespace Microsoft.AspNet.Identity.Test private class AlwaysBadValidator : IUserValidator, IRoleValidator, IPasswordValidator { - public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad." }; + public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad.", Code = "BadValidator" }; public Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { @@ -950,6 +1013,7 @@ namespace Microsoft.AspNet.Identity.Test manager.RoleValidators.Clear(); manager.RoleValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.UpdateAsync(role), error); + IdentityResultAssert.VerifyRoleManagerFailureLog(manager.Logger, "UpdateAsync", role.Id.ToString(), AlwaysBadValidator.ErrorMessage); } [Fact] @@ -961,6 +1025,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); IdentityResultAssert.IsSuccess(await manager.DeleteAsync(role)); Assert.False(await manager.RoleExistsAsync(role.Name)); + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "DeleteAsync", role.Id.ToString()); } [Fact] @@ -974,6 +1039,7 @@ namespace Microsoft.AspNet.Identity.Test { IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(role, c)); } + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "AddClaimAsync", role.Id.ToString()); var roleClaims = await manager.GetClaimsAsync(role); Assert.Equal(3, roleClaims.Count); IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(role, claims[0])); @@ -985,6 +1051,8 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(role, claims[2])); roleClaims = await manager.GetClaimsAsync(role); Assert.Equal(0, roleClaims.Count); + + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "RemoveClaimAsync", role.Id.ToString()); } [Fact] @@ -1018,6 +1086,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.True(await manager.RoleExistsAsync(role.Name)); IdentityResultAssert.IsSuccess(await manager.SetRoleNameAsync(role, "Changed")); IdentityResultAssert.IsSuccess(await manager.UpdateAsync(role)); + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "UpdateAsync", role.Id.ToString()); Assert.False(await manager.RoleExistsAsync("update")); Assert.Equal(role, await manager.FindByNameAsync("Changed")); } @@ -1085,6 +1154,7 @@ namespace Microsoft.AspNet.Identity.Test var role = CreateRole("dupeRole"); Assert.False(await manager.RoleExistsAsync(role.Name)); IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "CreateAsync", role.Id.ToString()); Assert.True(await manager.RoleExistsAsync(role.Name)); var role2 = CreateRole(); role2.Name = role.Name; @@ -1109,6 +1179,8 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(u)); IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(u, role.Name)); Assert.True(await manager.IsInRoleAsync(u, role.Name)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddToRoleAsync", u.Id.ToString()); + } } @@ -1161,6 +1233,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.True(await userManager.IsInRoleAsync(user, r.Name)); } IdentityResultAssert.IsSuccess(await userManager.RemoveFromRoleAsync(user, roles[2].Name)); + IdentityResultAssert.VerifyUserManagerSuccessLog(userManager.Logger, "RemoveFromRoleAsync", user.Id.ToString()); Assert.False(await userManager.IsInRoleAsync(user, roles[2].Name)); } @@ -1201,6 +1274,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); var result = await userMgr.RemoveFromRoleAsync(user, role.Name); IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserNotInRole(role.Name)); + IdentityResultAssert.VerifyUserManagerFailureLog(userMgr.Logger, "RemoveFromRoleAsync", user.Id.ToString(), IdentityErrorDescriber.Default.UserNotInRole(role.Name)); } [Fact] @@ -1216,6 +1290,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); Assert.True(await userMgr.IsInRoleAsync(user, role.Name)); IdentityResultAssert.IsFailure(await userMgr.AddToRoleAsync(user, role.Name), IdentityErrorDescriber.Default.UserAlreadyInRole(role.Name)); + IdentityResultAssert.VerifyUserManagerFailureLog(userMgr.Logger, "AddToRoleAsync", user.Id.ToString(), IdentityErrorDescriber.Default.UserAlreadyInRole(role.Name)); } [Fact] @@ -1261,6 +1336,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = await manager.GetSecurityStampAsync(user); var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); IdentityResultAssert.IsSuccess(await manager.ChangePhoneNumberAsync(user, "111-111-1111", token1)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ChangePhoneNumberAsync", user.Id.ToString()); Assert.True(await manager.IsPhoneNumberConfirmedAsync(user)); Assert.Equal(await manager.GetPhoneNumberAsync(user), "111-111-1111"); Assert.NotEqual(stamp, user.SecurityStamp); @@ -1277,6 +1353,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = await manager.GetSecurityStampAsync(user); IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "111-111-1111", "bogus"), "Invalid token."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ChangePhoneNumberAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); Assert.Equal(await manager.GetPhoneNumberAsync(user), "123-456-7890"); Assert.Equal(stamp, user.SecurityStamp); @@ -1308,12 +1385,16 @@ namespace Microsoft.AspNet.Identity.Test const string num1 = "111-123-4567"; const string num2 = "111-111-1111"; var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, num1); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GenerateChangePhoneNumberTokenAsync", user.Id.ToString()); + var token2 = await manager.GenerateChangePhoneNumberTokenAsync(user, num2); Assert.NotEqual(token1, token2); Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num1)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "VerifyChangePhoneNumberTokenAsync", user.Id.ToString()); Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num2)); Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num1)); Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num2)); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "VerifyChangePhoneNumberTokenAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); } [Fact] @@ -1328,6 +1409,7 @@ namespace Microsoft.AspNet.Identity.Test string newEmail = user.UserName + "@en.vec"; var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail); IdentityResultAssert.IsSuccess(await manager.ChangeEmailAsync(user, newEmail, token1)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ChangeEmailAsync", user.Id.ToString()); Assert.True(await manager.IsEmailConfirmedAsync(user)); Assert.Equal(await manager.GetEmailAsync(user), newEmail); Assert.NotEqual(stamp, user.SecurityStamp); @@ -1345,6 +1427,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = await manager.GetSecurityStampAsync(user); IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "whatevah@foo.barf", "bogus"), "Invalid token."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ChangeEmailAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); Assert.False(await manager.IsEmailConfirmedAsync(user)); Assert.Equal(await manager.GetEmailAsync(user), oldEmail); Assert.Equal(stamp, user.SecurityStamp); @@ -1386,8 +1469,9 @@ namespace Microsoft.AspNet.Identity.Test Assert.Null(messageService.Message); IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); Assert.NotNull(messageService.Message); - Assert.Equal("Your security code is: "+token, messageService.Message.Body); + Assert.Equal("Your security code is: " + token, messageService.Message.Body); Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "VerifyTwoFactorTokenAsync", user.Id.ToString()); } [Fact] @@ -1427,11 +1511,13 @@ namespace Microsoft.AspNet.Identity.Test Assert.NotNull(stamp); var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); Assert.NotNull(token); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GenerateTwoFactorTokenAsync", user.Id.ToString()); Assert.Null(messageService.Message); IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); Assert.NotNull(messageService.Message); Assert.Equal(subject, messageService.Message.Subject); Assert.Equal(string.Format(body, token), messageService.Message.Body); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "NotifyTwoFactorTokenAsync", user.Id.ToString()); Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); } @@ -1461,6 +1547,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = user.SecurityStamp; Assert.NotNull(stamp); IdentityResultAssert.IsSuccess(await manager.SetTwoFactorEnabledAsync(user, true)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "SetTwoFactorEnabledAsync", user.Id.ToString()); Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); Assert.True(await manager.GetTwoFactorEnabledAsync(user)); } @@ -1497,7 +1584,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Null(messageService.Message); IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); Assert.NotNull(messageService.Message); - Assert.Equal("Your security code is: "+token, messageService.Message.Body); + Assert.Equal("Your security code is: " + token, messageService.Message.Body); Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); } @@ -1563,6 +1650,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.NotNull(factors); Assert.False(factors.Any()); IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "SetPhoneNumberAsync", user.Id.ToString()); user.PhoneNumberConfirmed = true; await manager.UpdateAsync(user); factors = await manager.GetValidTwoFactorProvidersAsync(user); @@ -1570,6 +1658,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(1, factors.Count()); Assert.Equal("Phone", factors[0]); IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, "test@test.com")); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "SetEmailAsync", user.Id.ToString()); user.EmailConfirmed = true; await manager.UpdateAsync(user); factors = await manager.GetValidTwoFactorProvidersAsync(user); @@ -1618,6 +1707,7 @@ namespace Microsoft.AspNet.Identity.Test user.PhoneNumber = "4251234567"; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "Phone", "bogus")); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "VerifyTwoFactorTokenAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); } [Fact] @@ -1637,9 +1727,9 @@ namespace Microsoft.AspNet.Identity.Test // set to a valid value await userMgr.SetLockoutEndDateAsync(user, DateTimeOffset.Parse("01/01/2014")); Assert.Equal(DateTimeOffset.Parse("01/01/2014"), await userMgr.GetLockoutEndDateAsync(user)); - } + } - [Fact] + [Fact] public async Task CanGetUsersWithClaims() { var manager = CreateManager(); @@ -1651,7 +1741,7 @@ namespace Microsoft.AspNet.Identity.Test if ((i % 2) == 0) { - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("foo", "bar"))); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("foo", "bar"))); } } @@ -1680,7 +1770,8 @@ namespace Microsoft.AspNet.Identity.Test if ((i % 2) == 0) { - IdentityResultAssert.IsSuccess(await manager.AddToRolesAsync(user, roles.Select(x=>x.Name).AsEnumerable())); + IdentityResultAssert.IsSuccess(await manager.AddToRolesAsync(user, roles.Select(x => x.Name).AsEnumerable())); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddToRolesAsync", user.Id.ToString()); } } @@ -1711,6 +1802,5 @@ namespace Microsoft.AspNet.Identity.Test } return roles; } - } } From 08b2b11940d6cdd6c914a7f897a50f74f6d283af Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Thu, 15 Jan 2015 16:14:08 -0800 Subject: [PATCH 22/31] Remove PipelineCore dependency. --- .../project.json | 1 - test/Microsoft.AspNet.Identity.EntityFramework.Test/project.json | 1 - test/Microsoft.AspNet.Identity.InMemory.Test/project.json | 1 - test/Microsoft.AspNet.Identity.Test/project.json | 1 - 4 files changed, 4 deletions(-) diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/project.json b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/project.json index 1ee5fbe41a..e70b17a933 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/project.json +++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/project.json @@ -4,7 +4,6 @@ "Microsoft.AspNet.Http": "1.0.0-*", "Microsoft.AspNet.Identity": "3.0.0-*", "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-*", - "Microsoft.AspNet.PipelineCore": "1.0.0-*", "Microsoft.AspNet.RequestContainer": "1.0.0-*", "Microsoft.AspNet.Security.DataProtection": "1.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/project.json b/test/Microsoft.AspNet.Identity.EntityFramework.Test/project.json index 03296b2fcb..3dc499053e 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/project.json +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/project.json @@ -4,7 +4,6 @@ "Microsoft.AspNet.Http": "1.0.0-*", "Microsoft.AspNet.Identity": "3.0.0-*", "Microsoft.AspNet.Identity.EntityFramework": "1.0.0-*", - "Microsoft.AspNet.PipelineCore": "1.0.0-*", "Microsoft.AspNet.RequestContainer": "1.0.0-*", "Microsoft.AspNet.Security.DataProtection": "1.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/project.json b/test/Microsoft.AspNet.Identity.InMemory.Test/project.json index fdf436789e..c9eef4b87a 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/project.json +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/project.json @@ -3,7 +3,6 @@ "Microsoft.AspNet.Hosting": "1.0.0-*", "Microsoft.AspNet.Http" : "1.0.0-*", "Microsoft.AspNet.Identity" : "3.0.0-*", - "Microsoft.AspNet.PipelineCore" : "1.0.0-*", "Microsoft.AspNet.RequestContainer" : "1.0.0-*", "Microsoft.AspNet.Security" : "1.0.0-*", "Microsoft.AspNet.Security.Cookies" : "1.0.0-*", diff --git a/test/Microsoft.AspNet.Identity.Test/project.json b/test/Microsoft.AspNet.Identity.Test/project.json index 8223b94bad..60d12687be 100644 --- a/test/Microsoft.AspNet.Identity.Test/project.json +++ b/test/Microsoft.AspNet.Identity.Test/project.json @@ -3,7 +3,6 @@ "Microsoft.AspNet.Hosting" : "1.0.0-*", "Microsoft.AspNet.Http" : "1.0.0-*", "Microsoft.AspNet.Identity" : "3.0.0-*", - "Microsoft.AspNet.PipelineCore" : "1.0.0-*", "Microsoft.AspNet.RequestContainer" : "1.0.0-*", "Microsoft.AspNet.Testing" : "1.0.0-*", "Microsoft.Framework.ConfigurationModel" : "1.0.0-*", From 7977cf6a46d981c7f513c87b73505f56f5458b5a Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Thu, 15 Jan 2015 16:47:14 -0800 Subject: [PATCH 23/31] Making file lock static --- src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj | 2 +- test/Shared/TestFileLogger.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj index 61c02b9a59..10681e7ef3 100644 --- a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj +++ b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj @@ -14,7 +14,7 @@ 2.0 - True + False diff --git a/test/Shared/TestFileLogger.cs b/test/Shared/TestFileLogger.cs index 8dc93126d2..fe236f63f1 100644 --- a/test/Shared/TestFileLogger.cs +++ b/test/Shared/TestFileLogger.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Identity.Test { public string FileName { get; set; } - public object FileLock { get; private set; } = new object(); + public static object FileLock { get; private set; } = new object(); public TestFileLogger(string name) { From 5bde831eab9f3a4be66fa32d8b647aa8270ef1ad Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Thu, 15 Jan 2015 17:12:09 -0800 Subject: [PATCH 24/31] Using file lock when reading file --- test/Shared/IdentityResultAssert.cs | 15 ++++++++++++--- test/Shared/TestFileLogger.cs | 7 +++++-- test/Shared/TestFileLoggerFactory.cs | 9 ++++++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/test/Shared/IdentityResultAssert.cs b/test/Shared/IdentityResultAssert.cs index 907aacecb1..9b941b234d 100644 --- a/test/Shared/IdentityResultAssert.cs +++ b/test/Shared/IdentityResultAssert.cs @@ -65,7 +65,10 @@ namespace Microsoft.AspNet.Identity.Test var fileLogger = logger as TestFileLogger; string expected = string.Format("{0} for {1}: {2} : Success", methodName, userOrRole, id); - Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); + lock (TestFileLogger.FileLock) + { + Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); + } } else { @@ -78,7 +81,10 @@ namespace Microsoft.AspNet.Identity.Test if (logger is TestFileLogger) { var fileLogger = logger as TestFileLogger; - Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expectedLog)); + lock (TestFileLogger.FileLock) + { + Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expectedLog)); + } } else { @@ -94,7 +100,10 @@ namespace Microsoft.AspNet.Identity.Test errors = errors ?? new IdentityError[] { new IdentityError() }; string expected = string.Format("{0} for {1}: {2} : Failed : {3}", methodName, userOrRole, userId, string.Join(",", errors.Select(x => x.Code).ToList())); - Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); + lock (TestFileLogger.FileLock) + { + Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); + } } else { diff --git a/test/Shared/TestFileLogger.cs b/test/Shared/TestFileLogger.cs index fe236f63f1..2124942b4c 100644 --- a/test/Shared/TestFileLogger.cs +++ b/test/Shared/TestFileLogger.cs @@ -18,9 +18,12 @@ namespace Microsoft.AspNet.Identity.Test var directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "IdentityTests"); Directory.CreateDirectory(directory); FileName = Path.Combine(directory, (name + DateTime.Now.Ticks + "log.txt")); - if (!File.Exists(FileName)) + lock (FileLock) { - File.Create(FileName).Close(); + if (!File.Exists(FileName)) + { + File.Create(FileName).Close(); + } } } diff --git a/test/Shared/TestFileLoggerFactory.cs b/test/Shared/TestFileLoggerFactory.cs index eb6f8f86f4..97ce20582e 100644 --- a/test/Shared/TestFileLoggerFactory.cs +++ b/test/Shared/TestFileLoggerFactory.cs @@ -27,7 +27,14 @@ namespace Microsoft.AspNet.Identity.Test { if (!_loggers.ContainsKey(name)) { - _loggers.Add(name, new TestFileLogger(name)); + try + { + _loggers.Add(name, new TestFileLogger(name)); + } + catch (ArgumentException ex) + { + // Silently skip if there is already a logger with that key + } } return _loggers[name]; From dd4ad28e1bbaf3262f8ded6b92d68ede6529c788 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Wed, 14 Jan 2015 21:49:42 -0800 Subject: [PATCH 25/31] Reacting to EF changes --- .../IdentityDbContext.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs b/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs index 65f8e2fe2c..5ea2553f6d 100644 --- a/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs +++ b/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs @@ -43,14 +43,14 @@ namespace Microsoft.AspNet.Identity.EntityFramework builder.Entity>(b => { b.Key(uc => uc.Id); - b.ManyToOne().ForeignKey(uc => uc.UserId); + b.HasOne().WithMany().ForeignKey(uc => uc.UserId); b.ForRelational().Table("AspNetUserClaims"); }); builder.Entity>(b => { b.Key(rc => rc.Id); - b.ManyToOne().ForeignKey(rc => rc.RoleId); + b.HasOne().WithMany().ForeignKey(rc => rc.RoleId); b.ForRelational().Table("AspNetRoleClaims"); }); @@ -66,7 +66,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework builder.Entity>(b => { b.Key(l => new { l.LoginProvider, l.ProviderKey }); - b.ManyToOne().ForeignKey(uc => uc.UserId); + b.HasOne().WithMany().ForeignKey(uc => uc.UserId); b.ForRelational().Table("AspNetUserLogins"); }); } From 5d06b0d353f214d420b7f401915e3adf3d5b1b44 Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Sun, 18 Jan 2015 11:33:25 -0800 Subject: [PATCH 26/31] Switched logging from file to inmemory for tests --- .../InMemoryEFUserStoreTest.cs | 7 +-- .../SqlStoreTestBase.cs | 1 - .../InMemoryStoreTest.cs | 7 +-- test/Shared/IdentityResultAssert.cs | 29 +++------- test/Shared/TestFileLogger.cs | 48 ---------------- test/Shared/TestFileLoggerFactory.cs | 55 ------------------- test/Shared/TestLogger.cs | 29 ++++++++++ test/Shared/TestLoggerFactory.cs | 20 +++++++ test/Shared/UserManagerTestBase.cs | 4 +- 9 files changed, 62 insertions(+), 138 deletions(-) delete mode 100644 test/Shared/TestFileLogger.cs delete mode 100644 test/Shared/TestFileLoggerFactory.cs create mode 100644 test/Shared/TestLogger.cs create mode 100644 test/Shared/TestLoggerFactory.cs diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs index 758e799ff9..bbdf4a73db 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs @@ -7,7 +7,7 @@ using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test { - public class InMemoryEFUserStoreTest : UserManagerTestBase,IDisposable + public class InMemoryEFUserStoreTest : UserManagerTestBase { protected override object CreateTestContext() { @@ -24,10 +24,5 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test var store = new RoleStore((InMemoryContext)context); services.AddInstance>(store); } - - public void Dispose() - { - loggerFactory.Dispose(); - } } } diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs b/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs index 3b6aca6f78..72b6275eda 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs @@ -34,7 +34,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test public void DropDatabaseDone() { DropDb(); - loggerFactory.Dispose(); } public void DropDb() diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs index 7cca942833..4e034f666e 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs @@ -7,7 +7,7 @@ using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Identity.InMemory.Test { - public class InMemoryStoreTest : UserManagerTestBase, IDisposable + public class InMemoryStoreTest : UserManagerTestBase { protected override object CreateTestContext() { @@ -23,10 +23,5 @@ namespace Microsoft.AspNet.Identity.InMemory.Test { services.AddSingleton, InMemoryRoleStore>(); } - - public void Dispose() - { - loggerFactory.Dispose(); - } } } \ No newline at end of file diff --git a/test/Shared/IdentityResultAssert.cs b/test/Shared/IdentityResultAssert.cs index 9b941b234d..80fabb9c02 100644 --- a/test/Shared/IdentityResultAssert.cs +++ b/test/Shared/IdentityResultAssert.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.IO; using System.Linq; using Microsoft.Framework.Logging; using Xunit; @@ -60,15 +59,11 @@ namespace Microsoft.AspNet.Identity.Test } private static void VerifySuccessLog(ILogger logger, string className, string methodName, string id, string userOrRole = "user") { - if (logger is TestFileLogger) + TestLogger testlogger = logger as TestLogger; + if (testlogger != null) { - var fileLogger = logger as TestFileLogger; string expected = string.Format("{0} for {1}: {2} : Success", methodName, userOrRole, id); - - lock (TestFileLogger.FileLock) - { - Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); - } + Assert.True(testlogger.LogMessages.Contains(expected)); } else { @@ -78,13 +73,10 @@ namespace Microsoft.AspNet.Identity.Test public static void VerifyLogMessage(ILogger logger, string expectedLog) { - if (logger is TestFileLogger) + TestLogger testlogger = logger as TestLogger; + if (testlogger != null) { - var fileLogger = logger as TestFileLogger; - lock (TestFileLogger.FileLock) - { - Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expectedLog)); - } + Assert.True(testlogger.LogMessages.Contains(expectedLog)); } else { @@ -94,16 +86,13 @@ namespace Microsoft.AspNet.Identity.Test private static void VerifyFailureLog(ILogger logger, string className, string methodName, string userId, string userOrRole = "user", params IdentityError[] errors) { - if (logger is TestFileLogger) + TestLogger testlogger = logger as TestLogger; + if (testlogger != null) { - var fileLogger = logger as TestFileLogger; errors = errors ?? new IdentityError[] { new IdentityError() }; string expected = string.Format("{0} for {1}: {2} : Failed : {3}", methodName, userOrRole, userId, string.Join(",", errors.Select(x => x.Code).ToList())); - lock (TestFileLogger.FileLock) - { - Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); - } + Assert.True(testlogger.LogMessages.Contains(expected)); } else { diff --git a/test/Shared/TestFileLogger.cs b/test/Shared/TestFileLogger.cs deleted file mode 100644 index 2124942b4c..0000000000 --- a/test/Shared/TestFileLogger.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.IO; -using Microsoft.Framework.Logging; - -namespace Microsoft.AspNet.Identity.Test -{ - public class TestFileLogger : ILogger - { - public string FileName { get; set; } - - public static object FileLock { get; private set; } = new object(); - - public TestFileLogger(string name) - { - var directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "IdentityTests"); - Directory.CreateDirectory(directory); - FileName = Path.Combine(directory, (name + DateTime.Now.Ticks + "log.txt")); - lock (FileLock) - { - if (!File.Exists(FileName)) - { - File.Create(FileName).Close(); - } - } - } - - public IDisposable BeginScope(object state) - { - throw new NotImplementedException(); - } - - public bool IsEnabled(LogLevel logLevel) - { - return true; - } - - public void Write(LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) - { - lock (FileLock) - { - File.AppendAllLines(FileName, new string[] { state.ToString() }); - } - } - } -} \ No newline at end of file diff --git a/test/Shared/TestFileLoggerFactory.cs b/test/Shared/TestFileLoggerFactory.cs deleted file mode 100644 index 97ce20582e..0000000000 --- a/test/Shared/TestFileLoggerFactory.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading.Tasks; -using System.IO; -using Microsoft.Framework.Logging; - -namespace Microsoft.AspNet.Identity.Test -{ - public class TestFileLoggerFactory : ILoggerFactory, IDisposable - { - private static Dictionary _loggers; - - static TestFileLoggerFactory() - { - _loggers = new Dictionary(); - } - - public void AddProvider(ILoggerProvider provider) - { - - } - - public ILogger Create(string name) - { - if (!_loggers.ContainsKey(name)) - { - try - { - _loggers.Add(name, new TestFileLogger(name)); - } - catch (ArgumentException ex) - { - // Silently skip if there is already a logger with that key - } - } - - return _loggers[name]; - } - - public void Dispose() - { - Parallel.ForEach(_loggers.Values, l => - { - if(l is TestFileLogger) - { - var logger = l as TestFileLogger; - File.Delete(logger.FileName); - } - }); - } - } -} diff --git a/test/Shared/TestLogger.cs b/test/Shared/TestLogger.cs new file mode 100644 index 0000000000..7cd1db5d86 --- /dev/null +++ b/test/Shared/TestLogger.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Identity.Test +{ + public class TestLogger : ILogger + { + public IList LogMessages { get; private set; } = new List(); + + public IDisposable BeginScope(object state) + { + throw new NotImplementedException(); + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Write(LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) + { + LogMessages.Add(state.ToString()); + } + } +} \ No newline at end of file diff --git a/test/Shared/TestLoggerFactory.cs b/test/Shared/TestLoggerFactory.cs new file mode 100644 index 0000000000..5d03765a57 --- /dev/null +++ b/test/Shared/TestLoggerFactory.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Identity.Test +{ + public class TestLoggerFactory : ILoggerFactory + { + public void AddProvider(ILoggerProvider provider) + { + + } + + public ILogger Create(string name) + { + return new TestLogger(); + } + } +} diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index 037a8b1ca9..a4d1e0b4ce 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -26,11 +26,11 @@ namespace Microsoft.AspNet.Identity.Test where TRole : IdentityRole, new() where TKey : IEquatable { - protected TestFileLoggerFactory loggerFactory; + protected TestLoggerFactory loggerFactory; public UserManagerTestBase() { - loggerFactory = new TestFileLoggerFactory(); + loggerFactory = new TestLoggerFactory(); } protected virtual void SetupIdentityServices(IServiceCollection services, object context = null) From 1826b23617b8b0219010e2d8c598a80d09b430ed Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 20 Jan 2015 01:31:53 -0800 Subject: [PATCH 27/31] Updating build.cmd and build.sh to use dotnetsdk --- build.cmd | 6 +++--- build.sh | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.cmd b/build.cmd index 86ca5bbbf1..c8041fdd9d 100644 --- a/build.cmd +++ b/build.cmd @@ -20,9 +20,9 @@ IF EXIST packages\KoreBuild goto run .nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion IF "%SKIP_KRE_INSTALL%"=="1" goto run -CALL packages\KoreBuild\build\kvm upgrade -runtime CLR -x86 -CALL packages\KoreBuild\build\kvm install default -runtime CoreCLR -x86 +CALL packages\KoreBuild\build\dotnetsdk upgrade -runtime CLR -x86 +CALL packages\KoreBuild\build\dotnetsdk install default -runtime CoreCLR -x86 :run -CALL packages\KoreBuild\build\kvm use default -runtime CLR -x86 +CALL packages\KoreBuild\build\dotnetsdk use default -runtime CLR -x86 packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %* diff --git a/build.sh b/build.sh index c7873ef58e..3f3c731c04 100644 --- a/build.sh +++ b/build.sh @@ -28,11 +28,11 @@ if test ! -d packages/KoreBuild; then fi if ! type k > /dev/null 2>&1; then - source packages/KoreBuild/build/kvm.sh + source setup/dotnetsdk.sh fi if ! type k > /dev/null 2>&1; then - kvm upgrade + dotnetsdk upgrade fi mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@" From e97d78ec941044dbbbc6c4dea4aa2d574c3210af Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 20 Jan 2015 01:36:12 -0800 Subject: [PATCH 28/31] Updating build.cmd and build.sh to use dotnetsdk --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 3f3c731c04..350d7e389a 100644 --- a/build.sh +++ b/build.sh @@ -28,7 +28,7 @@ if test ! -d packages/KoreBuild; then fi if ! type k > /dev/null 2>&1; then - source setup/dotnetsdk.sh + source packages/KoreBuild/build/dotnetsdk.sh fi if ! type k > /dev/null 2>&1; then From a94e74c0341e6bf8e9286876a12535b8418ad84a Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Mon, 19 Jan 2015 16:08:03 -0800 Subject: [PATCH 29/31] Change K_DETAILED_ERRORS to ASPNET_DETAILED_ERRORS --- samples/IdentitySample.Mvc/web.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/IdentitySample.Mvc/web.config b/samples/IdentitySample.Mvc/web.config index 7a210ad15b..71ebc78e72 100644 --- a/samples/IdentitySample.Mvc/web.config +++ b/samples/IdentitySample.Mvc/web.config @@ -9,7 +9,7 @@ - + \ No newline at end of file From dba66c0b5dbefff7e533c61fc847960da453f7fc Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Tue, 20 Jan 2015 18:24:26 -0800 Subject: [PATCH 30/31] Rename SKIP_KRE_INSTALL to SKIP_DOTNET_INSTALL --- build.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cmd b/build.cmd index c8041fdd9d..220a1ff561 100644 --- a/build.cmd +++ b/build.cmd @@ -19,7 +19,7 @@ IF EXIST packages\KoreBuild goto run .nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre .nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion -IF "%SKIP_KRE_INSTALL%"=="1" goto run +IF "%SKIP_DOTNET_INSTALL%"=="1" goto run CALL packages\KoreBuild\build\dotnetsdk upgrade -runtime CLR -x86 CALL packages\KoreBuild\build\dotnetsdk install default -runtime CoreCLR -x86 From 636b27bb736550210dbb90a75f2a7ec37c233cd2 Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Wed, 21 Jan 2015 15:49:52 -0800 Subject: [PATCH 31/31] Updating to release NuGet.config --- NuGet.Config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGet.Config b/NuGet.Config index f41e9c631d..2d3b0cb857 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,7 +1,7 @@  - +