diff --git a/src/Microsoft.AspNet.Identity.Entity/EntityIdentityBuilderExtensions.cs b/src/Microsoft.AspNet.Identity.Entity/EntityIdentityBuilderExtensions.cs new file mode 100644 index 0000000000..7412967337 --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/EntityIdentityBuilderExtensions.cs @@ -0,0 +1,33 @@ +// 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.Entity; +using Microsoft.Data.Entity; +using Microsoft.Framework.DependencyInjection; + +// Move to DI namespace? +namespace Microsoft.AspNet.Identity +{ + public static class EntityIdentityBuilderExtensions + { + public static IdentityBuilder AddEntityFrameworkInMemory(this IdentityBuilder builder) + where TUser : EntityUser + where TRole : EntityRole + where TDbContext : DbContext + { + builder.Services.AddScoped(); + builder.Services.AddScoped, InMemoryUserStore>(); + builder.Services.AddScoped, EntityRoleStore>(); + return builder; + } + + // todo: add overloads + public static IdentityBuilder AddEntityFramework(this IdentityBuilder builder) + where TUser : User where TContext : DbContext + { + builder.Services.AddScoped, UserStore>(); + builder.Services.AddScoped(); + return builder; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/EntityRoleStore.cs b/src/Microsoft.AspNet.Identity.Entity/EntityRoleStore.cs index 347d51675c..2fd53e7429 100644 --- a/src/Microsoft.AspNet.Identity.Entity/EntityRoleStore.cs +++ b/src/Microsoft.AspNet.Identity.Entity/EntityRoleStore.cs @@ -10,19 +10,28 @@ using Microsoft.Data.Entity; namespace Microsoft.AspNet.Identity.Entity { - public class EntityRoleStore : EntityRoleStore where TRole : EntityRole + public class EntityRoleStore : EntityRoleStore + where TRole : EntityRole { - public EntityRoleStore(DbContext context) : base(context) { } + public EntityRoleStore(IdentityContext context) : base(context) { } } - public class EntityRoleStore : + public class EntityRoleStore : EntityRoleStore + where TRole : EntityRole + where TContext : DbContext + { + public EntityRoleStore(TContext context) : base(context) { } + } + + public class EntityRoleStore : IQueryableRoleStore where TRole : EntityRole where TKey : IEquatable + where TContext : DbContext { private bool _disposed; - public EntityRoleStore(DbContext context) + public EntityRoleStore(TContext context) { if (context == null) { diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs deleted file mode 100644 index 66be171033..0000000000 --- a/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs +++ /dev/null @@ -1,49 +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.Entity; -using Microsoft.Data.Entity; -using Microsoft.Framework.DependencyInjection; - -namespace Microsoft.AspNet.Identity -{ - public static class IdentityBuilderExtensions - { - public static IdentityBuilder AddEntity(this IdentityBuilder builder) - where TUser : EntityUser - where TRole : EntityRole - { - builder.Services.AddScoped, InMemoryUserStore>(); - builder.Services.AddScoped, EntityRoleStore>(); - return builder; - } - - public static IdentityBuilder AddEntity(this IdentityBuilder builder) - where TUser : User - { - builder.Services.AddScoped, UserStore>(); - builder.Services.AddScoped>(); - return builder; - } - - // todo: remove - public static IdentityBuilder AddEntity(this IdentityBuilder builder) - where TUser : User where TContext : DbContext - { - builder.Services.AddScoped, UserStore>(); - builder.Services.AddScoped>(); - builder.Services.AddScoped(); - return builder; - } - - // todo: add overloads - public static IdentityBuilder AddEntityFramework(this IdentityBuilder builder) - where TUser : User where TContext : DbContext - { - builder.Services.AddScoped, UserStore>(); - builder.Services.AddScoped>(); - builder.Services.AddScoped(); - return builder; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs index 9467e07931..a533407cbd 100644 --- a/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs @@ -18,6 +18,14 @@ namespace Microsoft.AspNet.Identity.Entity public IdentityContext(IServiceProvider serviceProvider) : base(serviceProvider) { } } + public class IdentityContext : + IdentityContext + where TUser : EntityUser + { + public IdentityContext() { } + public IdentityContext(IServiceProvider serviceProvider) : base(serviceProvider) { } + } + public class IdentityContext : DbContext where TUser : EntityUser where TRole : EntityRole diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityEntityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityEntityServiceCollectionExtensions.cs deleted file mode 100644 index 547870db5f..0000000000 --- a/src/Microsoft.AspNet.Identity.Entity/IdentityEntityServiceCollectionExtensions.cs +++ /dev/null @@ -1,21 +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; -using Microsoft.AspNet.Identity.Entity; -using Microsoft.Data.Entity; - -namespace Microsoft.Framework.DependencyInjection -{ - public static class IdentityEntityServiceCollectionExtensions - { - public static IdentityBuilder AddIdentityEntityFramework(this ServiceCollection services) - where TUser : User where TContext : DbContext - { - var builder = services.AddIdentity(); - services.AddScoped(); - services.AddScoped, UserStore>(); - return builder; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentitySqlContext.cs b/src/Microsoft.AspNet.Identity.Entity/IdentitySqlContext.cs index f2153384c8..28990af88f 100644 --- a/src/Microsoft.AspNet.Identity.Entity/IdentitySqlContext.cs +++ b/src/Microsoft.AspNet.Identity.Entity/IdentitySqlContext.cs @@ -12,6 +12,7 @@ namespace Microsoft.AspNet.Identity.Entity { public IdentitySqlContext() { } public IdentitySqlContext(IServiceProvider serviceProvider) : base(serviceProvider) { } + public IdentitySqlContext(IServiceProvider serviceProvider, string nameOrConnectionString) : base(serviceProvider, nameOrConnectionString) { } public IdentitySqlContext(DbContextOptions options) : base(options) { } public IdentitySqlContext(IServiceProvider serviceProvider, DbContextOptions options) : base(serviceProvider, options) { } } @@ -23,16 +24,23 @@ namespace Microsoft.AspNet.Identity.Entity public DbSet UserClaims { get; set; } //public DbSet Roles { get; set; } + private readonly string _nameOrConnectionString; + public IdentitySqlContext() { } + public IdentitySqlContext(IServiceProvider serviceProvider, string nameOrConnectionString) : base(serviceProvider) + { + _nameOrConnectionString = nameOrConnectionString; + } public IdentitySqlContext(IServiceProvider serviceProvider) : base(serviceProvider) { } public IdentitySqlContext(DbContextOptions options) : base(options) { } public IdentitySqlContext(IServiceProvider serviceProvider, DbContextOptions options) : base(serviceProvider, options) { } - protected override void OnConfiguring(DbContextOptions builder) { - // TODO: pull connection string from config - builder.UseSqlServer(@"Server=(localdb)\v11.0;Database=SimpleIdentity-5-28;Trusted_Connection=True;"); + if (!string.IsNullOrEmpty(_nameOrConnectionString)) + { + builder.UseSqlServer(_nameOrConnectionString); + } } protected override void OnModelCreating(ModelBuilder builder) diff --git a/src/Microsoft.AspNet.Identity.Entity/InMemoryUserStore.cs b/src/Microsoft.AspNet.Identity.Entity/InMemoryUserStore.cs index 70cfa7b642..4250764782 100644 --- a/src/Microsoft.AspNet.Identity.Entity/InMemoryUserStore.cs +++ b/src/Microsoft.AspNet.Identity.Entity/InMemoryUserStore.cs @@ -13,18 +13,25 @@ using Microsoft.Data.Entity; namespace Microsoft.AspNet.Identity.Entity { - public class InMemoryInMemoryUserStore : - InMemoryUserStore + public class InMemoryUserStore : InMemoryUserStore { - public InMemoryInMemoryUserStore(DbContext context) : base(context) { } + public InMemoryUserStore(IdentityContext context) : base(context) { } } - public class InMemoryUserStore : InMemoryUserStore where TUser:EntityUser + public class InMemoryUserStore : InMemoryUserStore + where TUser : EntityUser { - public InMemoryUserStore(DbContext context) : base(context) { } + public InMemoryUserStore(IdentityContext context) : base(context) { } } - public class InMemoryUserStore : + public class InMemoryUserStore : InMemoryUserStore + where TUser:EntityUser + where TContext : DbContext + { + public InMemoryUserStore(TContext context) : base(context) { } + } + + public class InMemoryUserStore : IUserLoginStore, IUserClaimStore, IUserRoleStore, @@ -41,10 +48,11 @@ namespace Microsoft.AspNet.Identity.Entity where TUserLogin : IdentityUserLogin, new() where TUserRole : IdentityUserRole, new() where TUserClaim : IdentityUserClaim, new() + where TContext : DbContext { private bool _disposed; - public InMemoryUserStore(DbContext context) + public InMemoryUserStore(TContext context) { if (context == null) { @@ -54,7 +62,7 @@ namespace Microsoft.AspNet.Identity.Entity AutoSaveChanges = true; } - public DbContext Context { get; private set; } + public TContext Context { get; private set; } /// /// If true will call SaveChanges after CreateAsync/UpdateAsync/DeleteAsync diff --git a/src/Microsoft.AspNet.Identity.Entity/Microsoft.AspNet.Identity.Entity.kproj b/src/Microsoft.AspNet.Identity.Entity/Microsoft.AspNet.Identity.Entity.kproj index d31063f4c7..ed254fa09f 100644 --- a/src/Microsoft.AspNet.Identity.Entity/Microsoft.AspNet.Identity.Entity.kproj +++ b/src/Microsoft.AspNet.Identity.Entity/Microsoft.AspNet.Identity.Entity.kproj @@ -23,9 +23,8 @@ - + - diff --git a/src/Microsoft.AspNet.Identity.Entity/User.cs b/src/Microsoft.AspNet.Identity.Entity/User.cs index 22ee5e4ece..e0e44f30fd 100644 --- a/src/Microsoft.AspNet.Identity.Entity/User.cs +++ b/src/Microsoft.AspNet.Identity.Entity/User.cs @@ -10,9 +10,6 @@ namespace Microsoft.AspNet.Identity.Entity public User() { Id = Guid.NewGuid().ToString(); - // TODO: remove when bug is fixed - UserName = ""; - PasswordHash = ""; } public User(string userName) : this() diff --git a/src/Microsoft.AspNet.Identity.Security/HttpAuthenticationManager.cs b/src/Microsoft.AspNet.Identity.Security/HttpAuthenticationManager.cs new file mode 100644 index 0000000000..1bb1017d3d --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Security/HttpAuthenticationManager.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; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Security; +using Microsoft.Framework.DependencyInjection; + +namespace Microsoft.AspNet.Identity.Security +{ + public class HttpAuthenticationManager : IAuthenticationManager + { + public static readonly string TwoFactorUserIdAuthenticationType = "Microsoft.AspNet.Identity.TwoFactor.UserId"; + public static readonly string TwoFactorRememberedAuthenticationType = "Microsoft.AspNet.Identity.TwoFactor.Remembered"; + + public HttpAuthenticationManager(IContextAccessor contextAccessor) + { + Context = contextAccessor.Value; + } + + public HttpContext Context { get; private set; } + + public void ForgetClient() + { + Context.Response.SignOut(TwoFactorRememberedAuthenticationType); + } + + public async Task IsClientRememeberedAsync(string userId) + { + var result = + await Context.AuthenticateAsync(TwoFactorRememberedAuthenticationType); + return (result != null && result.Identity != null && result.Identity.Name == userId); + } + + public void RememberClient(string userId) + { + var rememberBrowserIdentity = new ClaimsIdentity(TwoFactorRememberedAuthenticationType); + rememberBrowserIdentity.AddClaim(new Claim(ClaimTypes.Name, userId)); + Context.Response.SignIn(rememberBrowserIdentity); + } + + public async Task RetrieveUserId() + { + var result = await Context.AuthenticateAsync(TwoFactorUserIdAuthenticationType); + if (result != null && result.Identity != null) + { + return result.Identity.Name; + } + return null; + } + + public void SignIn(ClaimsIdentity identity, bool isPersistent) + { + Context.Response.SignIn(identity, new AuthenticationProperties { IsPersistent = isPersistent }); + } + public void SignOut(string authenticationType) + { + Context.Response.SignOut(authenticationType); + } + + public Task StoreUserId(string userId) + { + var userIdentity = new ClaimsIdentity(TwoFactorUserIdAuthenticationType); + userIdentity.AddClaim(new Claim(ClaimTypes.Name, userId)); + Context.Response.SignIn(userIdentity); + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Security/IdentityBuilderExtensions.cs b/src/Microsoft.AspNet.Identity.Security/IdentityBuilderExtensions.cs index ca3d891513..0ef31f0d96 100644 --- a/src/Microsoft.AspNet.Identity.Security/IdentityBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Identity.Security/IdentityBuilderExtensions.cs @@ -8,18 +8,11 @@ namespace Microsoft.AspNet.Identity { public static class IdentityBuilderExtensions { - // TODO: remove - public static IdentityBuilder AddSecurity(this IdentityBuilder builder) - where TUser : class - { - builder.Services.AddScoped>(); - return builder; - } - public static IdentityBuilder AddHttpSignIn(this IdentityBuilder builder) where TUser : class { - builder.Services.AddScoped>(); + // todo: review should this be scoped? + builder.Services.AddTransient(); return builder; } } diff --git a/src/Microsoft.AspNet.Identity.Security/Microsoft.AspNet.Identity.Security.kproj b/src/Microsoft.AspNet.Identity.Security/Microsoft.AspNet.Identity.Security.kproj index ec8ca0781d..216d6d756b 100644 --- a/src/Microsoft.AspNet.Identity.Security/Microsoft.AspNet.Identity.Security.kproj +++ b/src/Microsoft.AspNet.Identity.Security/Microsoft.AspNet.Identity.Security.kproj @@ -20,12 +20,9 @@ - + - - - \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Security/SecurityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity.Security/SecurityServiceCollectionExtensions.cs deleted file mode 100644 index 951d88a75b..0000000000 --- a/src/Microsoft.AspNet.Identity.Security/SecurityServiceCollectionExtensions.cs +++ /dev/null @@ -1,18 +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.Security; - -namespace Microsoft.Framework.DependencyInjection -{ - public static class SecurityServiceCollectionExtensions - { - // todo: remove? - public static ServiceCollection AddSecurity(this ServiceCollection services) - where TUser : class - { - services.AddTransient>(); - return services; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Security/SignInManager.cs b/src/Microsoft.AspNet.Identity.Security/SignInManager.cs deleted file mode 100644 index a9b6cbeaf1..0000000000 --- a/src/Microsoft.AspNet.Identity.Security/SignInManager.cs +++ /dev/null @@ -1,178 +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.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Http.Security; -using Microsoft.Framework.DependencyInjection; - -namespace Microsoft.AspNet.Identity.Security -{ - public class SignInManager : SignInManager, TUser> where TUser : class - { - public SignInManager(UserManager userManager, IContextAccessor contextAccessor) - : base(userManager, contextAccessor) { } - } - - - public class SignInManager where TManager : UserManager where TUser : class - { - public SignInManager(TManager userManager, IContextAccessor contextAccessor) - { - if (userManager == null) - { - throw new ArgumentNullException("userManager"); - } - if (contextAccessor == null || contextAccessor.Value == null) - { - throw new ArgumentNullException("contextAccessor"); - } - UserManager = userManager; - Context = contextAccessor.Value; - } - - // TODO: this should go into some kind of Options/setup - private string _authType; - public string AuthenticationType - { - get { return _authType ?? DefaultAuthenticationTypes.ApplicationCookie; } - set { _authType = value; } - } - - public TManager UserManager { get; private set; } - public HttpContext Context { get; private set; } - - public virtual async Task CreateUserIdentityAsync(TUser user) - { - return await UserManager.CreateIdentityAsync(user, AuthenticationType); - } - - public virtual async Task SignInAsync(TUser user, bool isPersistent, bool rememberBrowser) - { - // TODO: all the two factor logic/external/rememberBrowser - var userIdentity = await CreateUserIdentityAsync(user); - Context.Response.SignIn(userIdentity, new AuthenticationProperties { IsPersistent = isPersistent }); - } - - public virtual void SignOut() - { - Context.Response.SignOut(AuthenticationType); - } - - //public virtual async Task SendTwoFactorCode(string provider) - //{ - // var userId = await GetVerifiedUserId(); - // if (userId == null) - // { - // return false; - // } - - // var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider); - // // See IdentityConfig.cs to plug in Email/SMS services to actually send the code - // await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token); - // return true; - //} - - //public Task GetVerifiedUserId() - //{ - // //var result = await AuthenticationManager.Authenticate(DefaultAuthenticationTypes.TwoFactorCookie); - // //if (result != null && result.Identity != null && !String.IsNullOrEmpty(result.Identity.GetUserIdAsync())) - // //{ - // // return result.Identity.GetUserIdAsync(); - // //} - // return Task.FromResult(default(TKey)); - //} - - //public async Task HasBeenVerified() - //{ - // return await GetVerifiedUserId() != null; - //} - - //public virtual async Task TwoFactorSignIn(string provider, string code, bool isPersistent, bool rememberBrowser) - //{ - // var userId = await GetVerifiedUserId(); - // if (userId == null) - // { - // return SignInStatus.Failure; - // } - // var user = await UserManager.FindByIdAsync(userId); - // if (user == null) - // { - // return SignInStatus.Failure; - // } - // if (await UserManager.IsLockedOutAsync(user.Id)) - // { - // return SignInStatus.LockedOut; - // } - // if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code)) - // { - // // When token is verified correctly, clear the access failed count used for lockout - // await UserManager.ResetAccessFailedCountAsync(user.Id); - // await SignIn(user, isPersistent, rememberBrowser); - // return SignInStatus.Success; - // } - // // If the token is incorrect, record the failure which also may cause the user to be locked out - // await UserManager.AccessFailedAsync(user.Id); - // return SignInStatus.Failure; - //} - - //public async Task ExternalSignIn(ExternalLoginInfo loginInfo, bool isPersistent) - //{ - // var user = await UserManager.FindByLoginAsync(loginInfo.Login); - // if (user == null) - // { - // return SignInStatus.Failure; - // } - // if (await UserManager.IsLockedOutAsync(user.Id)) - // { - // return SignInStatus.LockedOut; - // } - // return await SignInOrTwoFactor(user, isPersistent); - //} - - //private async Task SignInOrTwoFactor(TUser user, bool isPersistent) - //{ - // if (await UserManager.GetTwoFactorEnabledAsync(user.Id)) - // //&& !await AuthenticationManager.TwoFactorBrowserRemembered(user.Id)) - // { - // //var identity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie); - // //identity.AddClaimAsync(new Claim(ClaimTypes.NameIdentifier, user.Id)); - // //AuthenticationManager.SignIn(identity); - // return SignInStatus.RequiresTwoFactorAuthentication; - // } - // await SignIn(user, isPersistent, false); - // return SignInStatus.Success; - //} - - public virtual async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) - { - var user = await UserManager.FindByNameAsync(userName); - if (user == null) - { - return SignInStatus.Failure; - } - if (UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user)) - { - return SignInStatus.LockedOut; - } - if (await UserManager.CheckPasswordAsync(user, password)) - { - await SignInAsync(user, isPersistent, false); - return SignInStatus.Success; - //TODO: return await SignInOrTwoFactor(user, isPersistent); - } - if (UserManager.SupportsUserLockout && shouldLockout) - { - // If lockout is requested, increment access failed count which might lock out the user - await UserManager.AccessFailedAsync(user); - if (await UserManager.IsLockedOutAsync(user)) - { - return SignInStatus.LockedOut; - } - } - return SignInStatus.Failure; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/ClaimTypeOptions.cs b/src/Microsoft.AspNet.Identity/ClaimTypeOptions.cs index e00d606ce3..0a584a369a 100644 --- a/src/Microsoft.AspNet.Identity/ClaimTypeOptions.cs +++ b/src/Microsoft.AspNet.Identity/ClaimTypeOptions.cs @@ -42,17 +42,5 @@ namespace Microsoft.AspNet.Identity /// Claim type used for the user security stamp /// public string SecurityStamp { get; set; } - - public virtual void Copy(ClaimTypeOptions options) - { - if (options == null) - { - return; - } - Role = options.Role; - SecurityStamp = options.SecurityStamp; - UserId = options.UserId; - UserName = options.UserName; - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Security/DefaultAuthenticationTypes.cs b/src/Microsoft.AspNet.Identity/DefaultAuthenticationTypes.cs similarity index 88% rename from src/Microsoft.AspNet.Identity.Security/DefaultAuthenticationTypes.cs rename to src/Microsoft.AspNet.Identity/DefaultAuthenticationTypes.cs index 7d267f5e11..83a8ad8ae5 100644 --- a/src/Microsoft.AspNet.Identity.Security/DefaultAuthenticationTypes.cs +++ b/src/Microsoft.AspNet.Identity/DefaultAuthenticationTypes.cs @@ -1,7 +1,7 @@ // 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.Security +namespace Microsoft.AspNet.Identity { public static class DefaultAuthenticationTypes { diff --git a/src/Microsoft.AspNet.Identity/IAuthenticationManager.cs b/src/Microsoft.AspNet.Identity/IAuthenticationManager.cs new file mode 100644 index 0000000000..6eba2566e8 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/IAuthenticationManager.cs @@ -0,0 +1,23 @@ +// 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.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Identity +{ + public interface IAuthenticationManager + { + void SignIn(ClaimsIdentity identity, bool isPersistent); + void SignOut(string authenticationType); + + // remember browser for two factor + void ForgetClient(); + void RememberClient(string userId); + Task IsClientRememeberedAsync(string userId); + + // half cookie + Task StoreUserId(string userId); + Task RetrieveUserId(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityOptions.cs b/src/Microsoft.AspNet.Identity/IdentityOptions.cs index a0f3923226..d5c15c02ae 100644 --- a/src/Microsoft.AspNet.Identity/IdentityOptions.cs +++ b/src/Microsoft.AspNet.Identity/IdentityOptions.cs @@ -14,7 +14,6 @@ namespace Microsoft.AspNet.Identity public IdentityOptions() { - // TODO: Split into sub options ClaimType = new ClaimTypeOptions(); User = new UserOptions(); Password = new PasswordOptions(); @@ -28,18 +27,5 @@ namespace Microsoft.AspNet.Identity public PasswordOptions Password { get; set; } public LockoutOptions Lockout { get; set; } - - // TODO: maybe make this internal as its only for tests - public void Copy(IdentityOptions options) - { - if (options == null) - { - return; - } - User.Copy(options.User); - Password.Copy(options.Password); - Lockout.Copy(options.Lockout); - ClaimType.Copy(options.ClaimType); - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs index d85a05a5ea..f5d0355aac 100644 --- a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs @@ -36,6 +36,7 @@ namespace Microsoft.Framework.DependencyInjection services.Add(IdentityServices.GetDefaultUserServices()); services.Add(IdentityServices.GetDefaultRoleServices()); services.AddScoped>(); + services.AddScoped>(); services.AddScoped>(); return new IdentityBuilder(services); } diff --git a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj index 2a7d8b61e7..1d4d531945 100644 --- a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj +++ b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj @@ -24,6 +24,8 @@ + + @@ -64,6 +66,8 @@ + + diff --git a/src/Microsoft.AspNet.Identity/PasswordOptions.cs b/src/Microsoft.AspNet.Identity/PasswordOptions.cs index 4f21ae3573..05f93e3cc5 100644 --- a/src/Microsoft.AspNet.Identity/PasswordOptions.cs +++ b/src/Microsoft.AspNet.Identity/PasswordOptions.cs @@ -40,18 +40,5 @@ namespace Microsoft.AspNet.Identity /// Require a digit ('0' - '9') /// public bool RequireDigit { get; set; } - - public virtual void Copy(PasswordOptions options) - { - if (options == null) - { - return; - } - RequireDigit = options.RequireDigit; - RequireLowercase = options.RequireLowercase; - RequireNonLetterOrDigit = options.RequireNonLetterOrDigit; - RequireUppercase = options.RequireUppercase; - RequiredLength = options.RequiredLength; - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/SignInManager.cs b/src/Microsoft.AspNet.Identity/SignInManager.cs new file mode 100644 index 0000000000..de859b0382 --- /dev/null +++ b/src/Microsoft.AspNet.Identity/SignInManager.cs @@ -0,0 +1,176 @@ +// 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.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Identity +{ + /// + /// Interface that manages SignIn operations for a user + /// + /// + public class SignInManager where TUser : class + { + public SignInManager(UserManager userManager, IAuthenticationManager authenticationManager) + { + if (userManager == null) + { + throw new ArgumentNullException("userManager"); + } + if (authenticationManager == null) + { + throw new ArgumentNullException("authenticationManager"); + } + UserManager = userManager; + AuthenticationManager = authenticationManager; + AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie; + } + + // TODO: this should go into some kind of Options/setup + public string AuthenticationType { get; set; } + + public UserManager UserManager { get; private set; } + public IAuthenticationManager AuthenticationManager { get; private set; } + + // Should this be a func? + public virtual async Task CreateUserIdentityAsync(TUser user) + { + return await UserManager.CreateIdentityAsync(user, AuthenticationType); + } + + public virtual async Task SignInAsync(TUser user, bool isPersistent) + { + var userIdentity = await CreateUserIdentityAsync(user); + AuthenticationManager.SignIn(userIdentity, isPersistent); + } + + // TODO: Should this be async? + public void SignOut() + { + AuthenticationManager.SignOut(AuthenticationType); + } + + public virtual async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) + { + var user = await UserManager.FindByNameAsync(userName); + if (user == null) + { + return SignInStatus.Failure; + } + if (UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user)) + { + return SignInStatus.LockedOut; + } + if (await UserManager.CheckPasswordAsync(user, password)) + { + return await SignInOrTwoFactor(user, isPersistent); + } + if (UserManager.SupportsUserLockout && shouldLockout) + { + // If lockout is requested, increment access failed count which might lock out the user + await UserManager.AccessFailedAsync(user); + if (await UserManager.IsLockedOutAsync(user)) + { + return SignInStatus.LockedOut; + } + } + return SignInStatus.Failure; + } + + public virtual async Task SendTwoFactorCode(string provider) + { + var userId = await AuthenticationManager.RetrieveUserId(); + if (userId == null) + { + return false; + } + + var user = await UserManager.FindByIdAsync(userId); + if (user == null) + { + return false; + } + var token = await UserManager.GenerateTwoFactorTokenAsync(user, provider); + // See IdentityConfig.cs to plug in Email/SMS services to actually send the code + await UserManager.NotifyTwoFactorTokenAsync(user, provider, token); + return true; + } + + //public async Task HasBeenVerified() + //{ + // return await GetVerifiedUserId() != null; + //} + + public virtual async Task RememberTwoFactorClient(TUser user) + { + var userId = await UserManager.GetUserIdAsync(user); + AuthenticationManager.RememberClient(userId); + } + + public virtual Task ForgetTwoFactorClientAsync() + { + AuthenticationManager.ForgetClient(); + return Task.FromResult(0); + } + + public virtual async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent) + { + var userId = await AuthenticationManager.RetrieveUserId(); + if (userId == null) + { + return SignInStatus.Failure; + } + var user = await UserManager.FindByIdAsync(userId); + if (user == null) + { + return SignInStatus.Failure; + } + if (await UserManager.IsLockedOutAsync(user)) + { + return SignInStatus.LockedOut; + } + if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code)) + { + // When token is verified correctly, clear the access failed count used for lockout + await UserManager.ResetAccessFailedCountAsync(user); + await SignInAsync(user, isPersistent); + return SignInStatus.Success; + } + // If the token is incorrect, record the failure which also may cause the user to be locked out + await UserManager.AccessFailedAsync(user); + return SignInStatus.Failure; + } + + public async Task ExternalLoginSignInAsync(UserLoginInfo loginInfo, bool isPersistent) + { + var user = await UserManager.FindByLoginAsync(loginInfo); + if (user == null) + { + return SignInStatus.Failure; + } + if (await UserManager.IsLockedOutAsync(user)) + { + return SignInStatus.LockedOut; + } + return await SignInOrTwoFactor(user, isPersistent); + } + + private async Task SignInOrTwoFactor(TUser user, bool isPersistent) + { + if (await UserManager.GetTwoFactorEnabledAsync(user)) + { + var userId = await UserManager.GetUserIdAsync(user); + if (!await AuthenticationManager.IsClientRememeberedAsync(userId)) + { + // Store the userId for use after two factor check + await AuthenticationManager.StoreUserId(userId); + return SignInStatus.RequiresVerification; + } + } + await SignInAsync(user, isPersistent); + return SignInStatus.Success; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Security/SignInStatus.cs b/src/Microsoft.AspNet.Identity/SignInStatus.cs similarity index 76% rename from src/Microsoft.AspNet.Identity.Security/SignInStatus.cs rename to src/Microsoft.AspNet.Identity/SignInStatus.cs index a7213837f1..6cb7e20c5d 100644 --- a/src/Microsoft.AspNet.Identity.Security/SignInStatus.cs +++ b/src/Microsoft.AspNet.Identity/SignInStatus.cs @@ -1,14 +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. -namespace Microsoft.AspNet.Identity.Security +namespace Microsoft.AspNet.Identity { public enum SignInStatus { Success, LockedOut, - RequiresTwoFactorAuthentication, + RequiresVerification, Failure } - } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index d06edbb135..91289e0b61 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -727,7 +727,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task ResetPassword(TUser user, string token, string newPassword, CancellationToken cancellationToken = default(CancellationToken)) + public virtual async Task ResetPasswordAsync(TUser user, string token, string newPassword, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); if (user == null) diff --git a/src/Microsoft.AspNet.Identity/UserOptions.cs b/src/Microsoft.AspNet.Identity/UserOptions.cs index a0764bdc67..f24ceb3bf6 100644 --- a/src/Microsoft.AspNet.Identity/UserOptions.cs +++ b/src/Microsoft.AspNet.Identity/UserOptions.cs @@ -22,15 +22,5 @@ namespace Microsoft.AspNet.Identity /// If set, enforces that emails are non empty, valid, and unique /// public bool RequireUniqueEmail { get; set; } - - public virtual void Copy(UserOptions options) - { - if (options == null) - { - return; - } - AllowOnlyAlphanumericNames = options.AllowOnlyAlphanumericNames; - RequireUniqueEmail = options.RequireUniqueEmail; - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/InMemoryUserStoreTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/InMemoryUserStoreTest.cs index f8c592a1f7..349cf44cb6 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/InMemoryUserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/InMemoryUserStoreTest.cs @@ -19,10 +19,6 @@ namespace Microsoft.AspNet.Identity.Entity.Test { public class InMemoryUserStoreTest { - class ApplicationUserManager : UserManager - { - public ApplicationUserManager(IServiceProvider services, IUserStore store, IOptionsAccessor options) : base(services, store, options) { } - } [Fact] public async Task CanUseAddedManagerInstance() @@ -30,11 +26,11 @@ namespace Microsoft.AspNet.Identity.Entity.Test var services = new ServiceCollection(); services.AddEntityFramework().AddInMemoryStore(); services.AddSingleton, OptionsAccessor>(); - services.AddInstance(new IdentityContext()); - services.AddTransient, InMemoryInMemoryUserStore>(); - services.AddSingleton(); + services.AddInstance(new IdentityContext()); + services.AddTransient, InMemoryUserStore>(); + services.AddSingleton>(); var provider = services.BuildServiceProvider(); - var manager = provider.GetService(); + var manager = provider.GetService>(); Assert.NotNull(manager); IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityUser("hello"))); } @@ -46,41 +42,23 @@ namespace Microsoft.AspNet.Identity.Entity.Test services.AddEntityFramework().AddInMemoryStore(); // TODO: this needs to construct a new instance of InMemoryStore - var store = new InMemoryInMemoryUserStore(new IdentityContext()); + var store = new InMemoryUserStore(new IdentityContext()); services.Add(OptionsServices.GetDefaultServices()); services.AddIdentity(s => { s.AddUserStore(() => store); - s.AddUserManager(); }); var provider = services.BuildServiceProvider(); - var manager = provider.GetService(); + var manager = provider.GetService>(); Assert.NotNull(manager); IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityUser("hello2"))); } - //[Fact] - //public async Task CanUseSingletonGenericManagerInstance() - //{ - // var services = new ServiceCollection(); - // var store = new EntityUserStore(new IdentityContext()); - // services.AddIdentity(s => - // { - // s.UseStore(() => store); - // s.UseManager>(); - // }); - - // var provider = services.BuildServiceProvider(); - // var manager = provider.GetService>(); - // Assert.NotNull(manager); - // IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityUser("hello"))); - //} - [Fact] public async Task EntityUserStoreMethodsThrowWhenDisposedTest() { - var store = new InMemoryInMemoryUserStore(new IdentityContext()); + var store = new InMemoryUserStore(new IdentityContext()); store.Dispose(); await Assert.ThrowsAsync(async () => await store.AddClaimAsync(null, null)); await Assert.ThrowsAsync(async () => await store.AddLoginAsync(null, null)); @@ -112,8 +90,8 @@ namespace Microsoft.AspNet.Identity.Entity.Test [Fact] public async Task EntityUserStorePublicNullCheckTest() { - Assert.Throws("context", () => new InMemoryInMemoryUserStore(null)); - var store = new InMemoryInMemoryUserStore(new IdentityContext()); + Assert.Throws("context", () => new InMemoryUserStore(null)); + var store = new InMemoryUserStore(new IdentityContext()); await Assert.ThrowsAsync("user", async () => await store.GetUserIdAsync(null)); await Assert.ThrowsAsync("user", async () => await store.GetUserNameAsync(null)); await Assert.ThrowsAsync("user", async () => await store.SetUserNameAsync(null, null)); @@ -675,7 +653,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test Assert.NotNull(stamp); var token = await manager.GeneratePasswordResetTokenAsync(user); Assert.NotNull(token); - IdentityResultAssert.IsSuccess(await manager.ResetPassword(user, token, newPassword)); + IdentityResultAssert.IsSuccess(await manager.ResetPasswordAsync(user, token, newPassword)); Assert.Null(await manager.FindByUserNamePasswordAsync(user.UserName, password)); Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, newPassword)); Assert.NotEqual(stamp, user.SecurityStamp); @@ -695,7 +673,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test var token = await manager.GeneratePasswordResetTokenAsync(user); Assert.NotNull(token); manager.PasswordValidator = new AlwaysBadValidator(); - IdentityResultAssert.IsFailure(await manager.ResetPassword(user, token, newPassword), + IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, token, newPassword), AlwaysBadValidator.ErrorMessage); Assert.NotNull(await manager.FindByUserNamePasswordAsync(user.UserName, password)); Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, password)); @@ -713,7 +691,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); var stamp = user.SecurityStamp; Assert.NotNull(stamp); - IdentityResultAssert.IsFailure(await manager.ResetPassword(user, "bogus", newPassword), "Invalid token."); + IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, "bogus", newPassword), "Invalid token."); Assert.NotNull(await manager.FindByUserNamePasswordAsync(user.UserName, password)); Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, password)); Assert.Equal(stamp, user.SecurityStamp); diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/Microsoft.AspNet.Identity.Entity.Test.kproj b/test/Microsoft.AspNet.Identity.Entity.Test/Microsoft.AspNet.Identity.Entity.Test.kproj index ccb2a8a66b..42617178b1 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/Microsoft.AspNet.Identity.Entity.Test.kproj +++ b/test/Microsoft.AspNet.Identity.Entity.Test/Microsoft.AspNet.Identity.Entity.Test.kproj @@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs index bba6312ca1..119f4724f2 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs @@ -14,11 +14,6 @@ namespace Microsoft.AspNet.Identity.Entity.Test { public class RoleStoreTest { - class ApplicationRoleManager : RoleManager - { - public ApplicationRoleManager(IServiceProvider services, IRoleStore store) : base(services, store) { } - } - [Fact] public async Task CanCreateUsingAddRoleManager() { @@ -29,11 +24,10 @@ namespace Microsoft.AspNet.Identity.Entity.Test services.AddIdentity(s => { s.AddRoleStore(() => store); - s.AddRoleManager(); }); var provider = services.BuildServiceProvider(); - var manager = provider.GetService(); + var manager = provider.GetService>(); Assert.NotNull(manager); IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityRole("arole"))); } @@ -42,13 +36,11 @@ namespace Microsoft.AspNet.Identity.Entity.Test { var services = new ServiceCollection(); services.AddEntityFramework().AddInMemoryStore(); - services.AddTransient(); + services.AddTransient(); services.AddTransient, EntityRoleStore>(); - //todo: services.AddSingleton, RoleManager>(); - // TODO: How to configure SqlServer? - services.AddSingleton(); + services.AddSingleton>(); var provider = services.BuildServiceProvider(); - var manager = provider.GetService(); + var manager = provider.GetService>(); Assert.NotNull(manager); IdentityResultAssert.IsSuccess(await manager.CreateAsync(new EntityRole("someRole"))); } @@ -56,7 +48,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test [Fact] public async Task RoleStoreMethodsThrowWhenDisposedTest() { - var store = new EntityRoleStore(new IdentityContext()); + var store = new EntityRoleStore(new IdentityContext()); store.Dispose(); await Assert.ThrowsAsync(async () => await store.FindByIdAsync(null)); await Assert.ThrowsAsync(async () => await store.FindByNameAsync(null)); @@ -71,8 +63,8 @@ namespace Microsoft.AspNet.Identity.Entity.Test [Fact] public async Task RoleStorePublicNullCheckTest() { - Assert.Throws("context", () => new EntityRoleStore(null)); - var store = new EntityRoleStore(new IdentityContext()); + Assert.Throws("context", () => new EntityRoleStore(null)); + var store = new EntityRoleStore(new IdentityContext()); await Assert.ThrowsAsync("role", async () => await store.GetRoleIdAsync(null)); await Assert.ThrowsAsync("role", async () => await store.GetRoleNameAsync(null)); await Assert.ThrowsAsync("role", async () => await store.SetRoleNameAsync(null, null)); diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/SqlUserStoreTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/SqlUserStoreTest.cs index cbab9dfd25..375280d51f 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/SqlUserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/SqlUserStoreTest.cs @@ -17,11 +17,20 @@ namespace Microsoft.AspNet.Identity.Entity.Test { public class SqlUserStoreTest { + private const string ConnectionString = @"Server=(localdb)\v11.0;Database=SqlUserStoreTest;Trusted_Connection=True;"; + public class ApplicationUser : User { } public class ApplicationDbContext : IdentitySqlContext { - public ApplicationDbContext(IServiceProvider services) : base(services) { } + public ApplicationDbContext(IServiceProvider services, IOptionsAccessor options) : base(services, options.Options) { } + } + + [TestPriority(0)] + [Fact] + public void RecreateDatabase() + { + CreateContext(true); } [Fact] @@ -32,9 +41,10 @@ namespace Microsoft.AspNet.Identity.Entity.Test builder.UseServices(services => { - services.Add(OptionsServices.GetDefaultServices()); services.AddEntityFramework().AddSqlServer(); - services.AddIdentityEntityFramework(); + services.AddIdentity().AddEntityFramework(); + services.SetupOptions(options => + options.UseSqlServer(ConnectionString)); }); var userStore = builder.ApplicationServices.GetService>(); @@ -58,19 +68,23 @@ namespace Microsoft.AspNet.Identity.Entity.Test var guid = Guid.NewGuid().ToString(); db.Users.Add(new User {Id = guid, UserName = guid}); db.SaveChanges(); + Assert.True(db.Users.Any(u => u.UserName == guid)); Assert.NotNull(db.Users.FirstOrDefault(u => u.UserName == guid)); } } - public static IdentitySqlContext CreateContext() + public static IdentitySqlContext CreateContext(bool delete = false) { var services = new ServiceCollection(); services.AddEntityFramework().AddSqlServer(); var serviceProvider = services.BuildServiceProvider(); - var db = new IdentitySqlContext(serviceProvider); + var db = new IdentitySqlContext(serviceProvider, ConnectionString); + if (delete) + { + db.Database.EnsureDeleted(); + } db.Database.EnsureCreated(); - return db; } @@ -81,45 +95,20 @@ namespace Microsoft.AspNet.Identity.Entity.Test public static ApplicationDbContext CreateAppContext() { + CreateContext(); var services = new ServiceCollection(); services.AddEntityFramework().AddSqlServer(); + services.Add(OptionsServices.GetDefaultServices()); var serviceProvider = services.BuildServiceProvider(); - var db = new ApplicationDbContext(serviceProvider); - - // TODO: Recreate DB, doesn't support String ID or Identity context yet + var db = new ApplicationDbContext(serviceProvider, serviceProvider.GetService>()); db.Database.EnsureCreated(); - - // TODO: CreateAsync DB? return db; } public static UserManager CreateManager(DbContext context) { - var services = new ServiceCollection(); - services.AddTransient, UserValidator>(); - services.AddTransient, PasswordValidator>(); - //services.AddInstance>(new UserStore(context)); - //services.AddSingleton>(); - var options = new IdentityOptions - { - Password = new PasswordOptions - { - RequireDigit = false, - RequireLowercase = false, - RequireNonLetterOrDigit = false, - RequireUppercase = false - }, - User = new UserOptions - { - AllowOnlyAlphanumericNames = false - } - - }; - var optionsAccessor = new OptionsAccessor(new[] { new TestIdentityFactory.TestSetup(options) }); - //services.AddInstance>(new OptionsAccessor(new[] { new TestSetup(options) })); - //return services.BuildServiceProvider().GetService>(); - return new UserManager(services.BuildServiceProvider(), new UserStore(context), optionsAccessor); + return MockHelpers.CreateManager(() => new UserStore(context)); } public static UserManager CreateManager() @@ -151,13 +140,14 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task CanUpdateUserName() { var manager = CreateManager(); - var user = new User("UpdateAsync"); + var oldName = Guid.NewGuid().ToString(); + var user = new User(oldName); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.Null(await manager.FindByNameAsync("New")); - user.UserName = "New"; + var newName = Guid.NewGuid().ToString(); + user.UserName = newName; IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); - Assert.NotNull(await manager.FindByNameAsync("New")); - Assert.Null(await manager.FindByNameAsync("UpdateAsync")); + Assert.NotNull(await manager.FindByNameAsync(newName)); + Assert.Null(await manager.FindByNameAsync(oldName)); IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); } @@ -165,12 +155,13 @@ namespace Microsoft.AspNet.Identity.Entity.Test public async Task CanSetUserName() { var manager = CreateManager(); - var user = new User("UpdateAsync"); + var oldName = Guid.NewGuid().ToString(); + var user = new User(oldName); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.Null(await manager.FindByNameAsync("New")); - IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, "New")); - Assert.NotNull(await manager.FindByNameAsync("New")); - Assert.Null(await manager.FindByNameAsync("UpdateAsync")); + var newName = Guid.NewGuid().ToString(); + IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, newName)); + Assert.NotNull(await manager.FindByNameAsync(newName)); + Assert.Null(await manager.FindByNameAsync(oldName)); IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); } diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/StartupTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/StartupTest.cs index ebd734b106..3d423b212f 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/StartupTest.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/StartupTest.cs @@ -3,7 +3,6 @@ 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; @@ -16,95 +15,39 @@ namespace Microsoft.AspNet.Identity.Entity.Test public class StartupTest { public class ApplicationUser : EntityUser { } - public class ApplicationUserManager : UserManager + + public class ApplicationDbContext : IdentityContext { - public ApplicationUserManager(IServiceProvider services, IUserStore store, IOptionsAccessor options) : base(services, store, options) { } - } - public class ApplicationRoleManager : RoleManager - { - public ApplicationRoleManager(IServiceProvider services, IRoleStore store) : base(services, store) { } - } - - - public class PasswordsNegativeLengthSetup : IOptionsSetup - { - public int Order { get { return 0; } } - public void Setup(IdentityOptions options) - { - options.Password.RequiredLength = -1; - } - } - - [Fact] - public void CanCustomizeIdentityOptions() - { - IBuilder builder = new Builder.Builder(new ServiceCollection().BuildServiceProvider()); - builder.UseServices(services => { - services.Add(OptionsServices.GetDefaultServices()); - services.AddIdentity(identityServices => { }); - services.AddSetup(); - }); - - var setup = builder.ApplicationServices.GetService>(); - Assert.IsType(typeof(PasswordsNegativeLengthSetup), setup); - var optionsGetter = builder.ApplicationServices.GetService>(); - Assert.NotNull(optionsGetter); - setup.Setup(optionsGetter.Options); - - var myOptions = optionsGetter.Options; - Assert.True(myOptions.Password.RequireLowercase); - Assert.True(myOptions.Password.RequireDigit); - Assert.True(myOptions.Password.RequireNonLetterOrDigit); - Assert.True(myOptions.Password.RequireUppercase); - Assert.Equal(-1, myOptions.Password.RequiredLength); - } - - [Fact] - public void CanSetupIdentityOptions() - { - IBuilder app = new Builder.Builder(new ServiceCollection().BuildServiceProvider()); - app.UseServices(services => { - services.Add(OptionsServices.GetDefaultServices()); - services.AddIdentity(identityServices => identityServices.SetupOptions(options => options.User.RequireUniqueEmail = true)); - }); - - var optionsGetter = app.ApplicationServices.GetService>(); - Assert.NotNull(optionsGetter); - - var myOptions = optionsGetter.Options; - Assert.True(myOptions.User.RequireUniqueEmail); + public ApplicationDbContext(IServiceProvider services) : base(services) { } } [Fact] public async Task EnsureStartupUsageWorks() { + EnsureDatabase(); + IBuilder builder = new Builder.Builder(new ServiceCollection().BuildServiceProvider()); builder.UseServices(services => { - services.Add(OptionsServices.GetDefaultServices()); - services.AddEntityFramework(); - services.AddTransient(); + services.AddEntityFramework().AddInMemoryStore(); services.AddIdentity(s => { - s.AddEntity(); - s.AddUserManager(); - s.AddRoleManager(); + s.AddEntityFrameworkInMemory(); }); }); var userStore = builder.ApplicationServices.GetService>(); var roleStore = builder.ApplicationServices.GetService>(); - var userManager = builder.ApplicationServices.GetService(); - //TODO: var userManager = builder.ApplicationServices.GetService(); - var roleManager = builder.ApplicationServices.GetService(); + var userManager = builder.ApplicationServices.GetService>(); + var roleManager = builder.ApplicationServices.GetService>(); Assert.NotNull(userStore); Assert.NotNull(userManager); Assert.NotNull(roleStore); Assert.NotNull(roleManager); - //await CreateAdminUser(builder.ApplicationServices); + await CreateAdminUser(builder.ApplicationServices); } private static async Task CreateAdminUser(IServiceProvider serviceProvider) @@ -112,13 +55,23 @@ namespace Microsoft.AspNet.Identity.Entity.Test const string userName = "admin"; const string roleName = "Admins"; const string password = "1qaz@WSX"; - var userManager = serviceProvider.GetService(); - var roleManager = serviceProvider.GetService(); + var userManager = serviceProvider.GetService>(); + var roleManager = serviceProvider.GetService>(); var user = new ApplicationUser { UserName = userName }; IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(new EntityRole { Name = roleName })); IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(user, roleName)); } + + public static void EnsureDatabase() + { + var services = new ServiceCollection(); + services.AddEntityFramework().AddInMemoryStore(); + var serviceProvider = services.BuildServiceProvider(); + + var db = new ApplicationDbContext(serviceProvider); + db.Database.EnsureCreated(); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs b/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs index aacccf5283..e0064ce4d4 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs @@ -1,10 +1,12 @@ // 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.Test; using Microsoft.Data.Entity; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; using Microsoft.Framework.OptionsModel; +using System; namespace Microsoft.AspNet.Identity.Entity.Test { @@ -13,61 +15,18 @@ namespace Microsoft.AspNet.Identity.Entity.Test public static IdentityContext CreateContext() { var services = new ServiceCollection(); - -//#if NET45 -// services.AddEntityFramework().AddSqlServer(); -//#else services.AddEntityFramework().AddInMemoryStore(); -//#endif var serviceProvider = services.BuildServiceProvider(); var db = new IdentityContext(serviceProvider); - - // TODO: Recreate DB, doesn't support String ID or Identity context yet db.Database.EnsureCreated(); - // TODO: CreateAsync DB? return db; } - public class TestSetup : IOptionsSetup + public static UserManager CreateManager(IdentityContext context) { - private readonly IdentityOptions _options; - - public TestSetup(IdentityOptions options) - { - _options = options; - } - - public int Order { get { return 0; } } - public void Setup(IdentityOptions options) - { - options.Copy(_options); - } - } - - - public static UserManager CreateManager(DbContext context) - { - var services = new ServiceCollection(); - services.AddTransient, UserValidator>(); - services.AddTransient, PasswordValidator>(); - services.AddInstance>(new InMemoryUserStore(context)); - services.AddSingleton, UserManager>(); - var options = new IdentityOptions - { - Password = new PasswordOptions - { - RequireDigit = false, - RequireLowercase = false, - RequireNonLetterOrDigit = false, - RequireUppercase = false - } - }; - var optionsAccessor = new OptionsAccessor(new[] { new TestSetup(options) }); - //services.AddInstance>(new OptionsAccessor(new[] { new TestSetup(options) })); - //return services.BuildServiceProvider().GetService>(); - return new UserManager(services.BuildServiceProvider(), new InMemoryUserStore(context), optionsAccessor); + return MockHelpers.CreateManager(() => new InMemoryUserStore(context)); } public static UserManager CreateManager() @@ -75,13 +34,12 @@ namespace Microsoft.AspNet.Identity.Entity.Test return CreateManager(CreateContext()); } - public static RoleManager CreateRoleManager(DbContext context) + public static RoleManager CreateRoleManager(IdentityContext context) { var services = new ServiceCollection(); - services.AddTransient, RoleValidator>(); - services.AddInstance>(new EntityRoleStore(context)); -// return services.BuildServiceProvider().GetService>(); - return new RoleManager(services.BuildServiceProvider(), new EntityRoleStore(context)); + services.Add(OptionsServices.GetDefaultServices()); + services.AddIdentity(b => b.AddRoleStore(() => new EntityRoleStore(context))); + return services.BuildServiceProvider().GetService>(); } public static RoleManager CreateRoleManager() diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs index e0e0d5ff95..6c259ad15d 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs @@ -16,24 +16,17 @@ namespace Microsoft.AspNet.Identity.InMemory.Test services.Add(OptionsServices.GetDefaultServices()); services.AddTransient, UserValidator>(); services.AddTransient, PasswordValidator>(); - var options = new IdentityOptions + services.AddSingleton, InMemoryUserStore>(); + services.AddSingleton>(); + services.SetupOptions(options => { - Password = new PasswordOptions { - RequireDigit = false, - RequireLowercase = false, - RequireNonLetterOrDigit = false, - RequireUppercase = false - }, - User = new UserOptions { - AllowOnlyAlphanumericNames = false - } - }; - var optionsAccessor = new OptionsAccessor(new[] {new TestSetup(options)}); - //services.AddInstance>(optionsAccessor); - //services.AddInstance>(new InMemoryUserStore()); - //services.AddSingleton, UserManager>(); - //return services.BuildServiceProvider().GetService>(); - return new UserManager(services.BuildServiceProvider(), new InMemoryUserStore(), optionsAccessor); + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireNonLetterOrDigit = false; + options.Password.RequireUppercase = false; + options.User.AllowOnlyAlphanumericNames = false; + }); + return services.BuildServiceProvider().GetService>(); } protected override RoleManager CreateRoleManager() @@ -41,8 +34,8 @@ namespace Microsoft.AspNet.Identity.InMemory.Test var services = new ServiceCollection(); services.AddTransient, RoleValidator>(); services.AddInstance>(new InMemoryRoleStore()); - //return services.BuildServiceProvider().GetService>(); - return new RoleManager(services.BuildServiceProvider(), new InMemoryRoleStore()); + services.AddSingleton>(); + return services.BuildServiceProvider().GetService>(); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/Microsoft.AspNet.Identity.InMemory.Test.kproj b/test/Microsoft.AspNet.Identity.InMemory.Test/Microsoft.AspNet.Identity.InMemory.Test.kproj index 086b58d15c..d4e86bce7f 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/Microsoft.AspNet.Identity.InMemory.Test.kproj +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/Microsoft.AspNet.Identity.InMemory.Test.kproj @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/StartupTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/StartupTest.cs index a91b73e0bb..0b34db44e4 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/StartupTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/StartupTest.cs @@ -1,13 +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 System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Identity.Test; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; using Microsoft.Framework.OptionsModel; +using System; +using System.Threading.Tasks; using Xunit; namespace Microsoft.AspNet.Identity.InMemory.Test @@ -16,56 +16,6 @@ namespace Microsoft.AspNet.Identity.InMemory.Test { public class ApplicationUser : IdentityUser { } - public class PasswordsNegativeLengthSetup : IOptionsSetup - { - public int Order { get { return 0; } } - public void Setup(IdentityOptions options) - { - options.Password.RequiredLength = -1; - } - } - - [Fact] - public void CanCustomizeIdentityOptions() - { - var builder = new Builder.Builder(new ServiceCollection().BuildServiceProvider()); - builder.UseServices(services => { - services.Add(OptionsServices.GetDefaultServices()); - services.AddIdentity(identityServices => { }); - services.AddSetup(); - }); - - var setup = builder.ApplicationServices.GetService>(); - Assert.IsType(typeof(PasswordsNegativeLengthSetup), setup); - var optionsGetter = builder.ApplicationServices.GetService>(); - Assert.NotNull(optionsGetter); - setup.Setup(optionsGetter.Options); - - var myOptions = optionsGetter.Options; - Assert.True(myOptions.Password.RequireLowercase); - Assert.True(myOptions.Password.RequireDigit); - Assert.True(myOptions.Password.RequireNonLetterOrDigit); - Assert.True(myOptions.Password.RequireUppercase); - Assert.Equal(-1, myOptions.Password.RequiredLength); - } - - [Fact] - public void CanSetupIdentityOptions() - { - var app = new Builder.Builder(new ServiceCollection().BuildServiceProvider()); - app.UseServices(services => - { - services.Add(OptionsServices.GetDefaultServices()); - services.AddIdentity(identityServices => identityServices.SetupOptions(options => options.User.RequireUniqueEmail = true)); - }); - - var optionsGetter = app.ApplicationServices.GetService>(); - Assert.NotNull(optionsGetter); - - var myOptions = optionsGetter.Options; - Assert.True(myOptions.User.RequireUniqueEmail); - } - [Fact] public async Task EnsureStartupUsageWorks() { @@ -73,7 +23,6 @@ namespace Microsoft.AspNet.Identity.InMemory.Test builder.UseServices(services => services.AddIdentity(s => { - services.Add(OptionsServices.GetDefaultServices()); s.AddInMemory(); })); @@ -96,7 +45,6 @@ namespace Microsoft.AspNet.Identity.InMemory.Test var builder = new Builder.Builder(new ServiceCollection().BuildServiceProvider()); builder.UseServices(services => { - services.Add(OptionsServices.GetDefaultServices()); services.AddIdentity(s => s.AddInMemory()); }); @@ -116,6 +64,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test var userManager2 = builder.ApplicationServices.GetService>(); var roleManager2 = builder.ApplicationServices.GetService>(); + // Stores are singleton, managers are scoped Assert.Equal(userStore, userStore2); Assert.Equal(userManager, userManager2); Assert.Equal(roleStore, roleStore2); diff --git a/test/Microsoft.AspNet.Identity.Security.Test/SignInManagerTest.cs b/test/Microsoft.AspNet.Identity.Security.Test/HttpSignInTest.cs similarity index 57% rename from test/Microsoft.AspNet.Identity.Security.Test/SignInManagerTest.cs rename to test/Microsoft.AspNet.Identity.Security.Test/HttpSignInTest.cs index 4076374a79..ef1de9ff39 100644 --- a/test/Microsoft.AspNet.Identity.Security.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Security.Test/HttpSignInTest.cs @@ -9,7 +9,6 @@ using Microsoft.AspNet.Identity.Test; using Microsoft.AspNet.Security.Cookies; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; -using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; using Moq; using System.Security.Claims; @@ -19,7 +18,9 @@ using Microsoft.AspNet.Builder; namespace Microsoft.AspNet.Identity.Security.Test { - public class SignInManagerTest + public class ApplicationUser : IdentityUser { } + + public class HttpSignInTest { #if NET45 [Theory] @@ -43,7 +44,6 @@ namespace Microsoft.AspNet.Identity.Security.Test { services.Add(OptionsServices.GetDefaultServices()); services.AddInstance(contextAccessor.Object); - services.AddInstance(new NullLoggerFactory()); services.AddIdentity(s => { s.AddInMemory(); @@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Identity.Security.Test // s.AddRoleStore(() => new InMemoryRoleStore()); // s.AddRoleManager(); // }); - // services.AddTransient(); + // services.AddTransient(); // }); // // Act @@ -105,10 +105,10 @@ namespace Microsoft.AspNet.Identity.Security.Test // }; // const string password = "Yol0Sw@g!"; // var userManager = app.ApplicationServices.GetService(); - // var signInManager = app.ApplicationServices.GetService(); + // var HttpSignInManager = app.ApplicationServices.GetService(); // IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); - // var result = await signInManager.PasswordSignInAsync(user.UserName, password, isPersistent, false); + // var result = await HttpSignInManager.PasswordSignInAsync(user.UserName, password, isPersistent, false); // // Assert // Assert.Equal(SignInStatus.Success, result); @@ -120,41 +120,38 @@ namespace Microsoft.AspNet.Identity.Security.Test { Assert.Throws("userManager", () => new SignInManager(null, null)); var userManager = MockHelpers.MockUserManager().Object; - Assert.Throws("contextAccessor", () => new SignInManager(userManager, null)); + Assert.Throws("authenticationManager", () => new SignInManager(userManager, null)); } //TODO: Mock fails in K (this works fine in net45) - [Fact] - public async Task EnsureClaimsIdentityFactoryCreateIdentityCalled() - { - // Setup - var user = new TestUser { UserName = "Foo" }; - var userManager = MockHelpers.TestUserManager(); - var identityFactory = new Mock>(); - const string authType = "Test"; - var testIdentity = new ClaimsIdentity(authType); - identityFactory.Setup(s => s.CreateAsync(userManager, user, authType, CancellationToken.None)).ReturnsAsync(testIdentity).Verifiable(); - userManager.ClaimsIdentityFactory = identityFactory.Object; - var context = new Mock(); - 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>(); - contextAccessor.Setup(a => a.Value).Returns(context.Object); - var helper = new SignInManager(userManager, contextAccessor.Object) - { - AuthenticationType = authType - }; + //[Fact] + //public async Task EnsureClaimsIdentityFactoryCreateIdentityCalled() + //{ + // // Setup + // var user = new TestUser { UserName = "Foo" }; + // var userManager = MockHelpers.TestUserManager(); + // var identityFactory = new Mock>(); + // const string authType = "Test"; + // var testIdentity = new ClaimsIdentity(authType); + // identityFactory.Setup(s => s.CreateAsync(userManager, user, authType, CancellationToken.None)).ReturnsAsync(testIdentity).Verifiable(); + // userManager.ClaimsIdentityFactory = identityFactory.Object; + // var context = new Mock(); + // 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>(); + // contextAccessor.Setup(a => a.Value).Returns(context.Object); + // var helper = new HttpAuthenticationManager(contextAccessor.Object); - // Act - await helper.SignInAsync(user, false, false); + // // Act + // helper.SignIn(user, false); - // Assert - identityFactory.VerifyAll(); - context.VerifyAll(); - contextAccessor.VerifyAll(); - response.VerifyAll(); - } + // // Assert + // identityFactory.VerifyAll(); + // context.VerifyAll(); + // contextAccessor.VerifyAll(); + // response.VerifyAll(); + //} [Fact] public async Task PasswordSignInReturnsLockedOutWhenLockedOut() @@ -168,7 +165,7 @@ namespace Microsoft.AspNet.Identity.Security.Test var context = new Mock(); var contextAccessor = new Mock>(); contextAccessor.Setup(a => a.Value).Returns(context.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object); + var helper = new SignInManager(manager.Object, new HttpAuthenticationManager(contextAccessor.Object)); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); @@ -197,7 +194,138 @@ namespace Microsoft.AspNet.Identity.Security.Test response.Setup(r => r.SignIn(It.IsAny(), It.Is(v => v.IsPersistent == isPersistent))).Verifiable(); var contextAccessor = new Mock>(); contextAccessor.Setup(a => a.Value).Returns(context.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object); + var helper = new SignInManager(manager.Object, new HttpAuthenticationManager(contextAccessor.Object)); + + // Act + var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); + + // Assert + Assert.Equal(SignInStatus.Success, result); + manager.VerifyAll(); + context.VerifyAll(); + response.VerifyAll(); + contextAccessor.VerifyAll(); + } + + [Fact] + public async Task PasswordSignInRequiresVerification() + { + // Setup + var user = new TestUser { UserName = "Foo" }; + var manager = MockHelpers.MockUserManager(); + manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable(); + manager.Setup(m => m.GetTwoFactorEnabledAsync(user, CancellationToken.None)).ReturnsAsync(true).Verifiable(); + 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.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id).Verifiable(); + manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); + var context = new Mock(); + 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>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var helper = new SignInManager(manager.Object, new HttpAuthenticationManager(contextAccessor.Object)); + + // Act + var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); + + // Assert + Assert.Equal(SignInStatus.RequiresVerification, result); + manager.VerifyAll(); + context.VerifyAll(); + response.VerifyAll(); + contextAccessor.VerifyAll(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CanTwoFactorSignIn(bool isPersistent) + { + // Setup + var user = new TestUser { UserName = "Foo" }; + var manager = MockHelpers.MockUserManager(); + var provider = "twofactorprovider"; + var code = "123456"; + manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(false).Verifiable(); + manager.Setup(m => m.FindByIdAsync(user.Id, CancellationToken.None)).ReturnsAsync(user).Verifiable(); + manager.Setup(m => m.VerifyTwoFactorTokenAsync(user, provider, code, CancellationToken.None)).ReturnsAsync(true).Verifiable(); + var context = new Mock(); + var response = new Mock(); + response.Setup(r => r.SignIn(It.IsAny(), It.Is(v => v.IsPersistent == isPersistent))).Verifiable(); + context.Setup(c => c.Response).Returns(response.Object).Verifiable(); + var id = new ClaimsIdentity(HttpAuthenticationManager.TwoFactorUserIdAuthenticationType); + id.AddClaim(new Claim(ClaimTypes.Name, user.Id)); + var authResult = new AuthenticationResult(id, new AuthenticationProperties(), new AuthenticationDescription()); + context.Setup(c => c.AuthenticateAsync(HttpAuthenticationManager.TwoFactorUserIdAuthenticationType)).ReturnsAsync(authResult).Verifiable(); + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var helper = new SignInManager(manager.Object, new HttpAuthenticationManager(contextAccessor.Object)); + + // Act + var result = await helper.TwoFactorSignInAsync(provider, code, isPersistent); + + // Assert + Assert.Equal(SignInStatus.Success, result); + manager.VerifyAll(); + context.VerifyAll(); + response.VerifyAll(); + contextAccessor.VerifyAll(); + } + + [Fact] + public void RememberClientStoresUserId() + { + // Setup + var user = new TestUser { UserName = "Foo" }; + var context = new Mock(); + var response = new Mock(); + context.Setup(c => c.Response).Returns(response.Object).Verifiable(); + response.Setup(r => r.SignIn(It.Is(i => i.AuthenticationType == HttpAuthenticationManager.TwoFactorRememberedAuthenticationType))).Verifiable(); + var id = new ClaimsIdentity(HttpAuthenticationManager.TwoFactorRememberedAuthenticationType); + id.AddClaim(new Claim(ClaimTypes.Name, user.Id)); + var authResult = new AuthenticationResult(id, new AuthenticationProperties(), new AuthenticationDescription()); + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var signInService = new HttpAuthenticationManager(contextAccessor.Object); + + // Act + signInService.RememberClient(user.Id); + + // Assert + context.VerifyAll(); + response.VerifyAll(); + contextAccessor.VerifyAll(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task RememberBrowserSkipsTwoFactorVerificationSignIn(bool isPersistent) + { + // Setup + var user = new TestUser { UserName = "Foo" }; + var manager = MockHelpers.MockUserManager(); + manager.Setup(m => m.GetTwoFactorEnabledAsync(user, CancellationToken.None)).ReturnsAsync(true).Verifiable(); + manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable(); + 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.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id).Verifiable(); + manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); + manager.Setup(m => m.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie, CancellationToken.None)).ReturnsAsync(new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie)).Verifiable(); + var context = new Mock(); + var response = new Mock(); + context.Setup(c => c.Response).Returns(response.Object).Verifiable(); + response.Setup(r => r.SignIn(It.Is(i => i.AuthenticationType == DefaultAuthenticationTypes.ApplicationCookie), It.Is(v => v.IsPersistent == isPersistent))).Verifiable(); + var id = new ClaimsIdentity(HttpAuthenticationManager.TwoFactorRememberedAuthenticationType); + id.AddClaim(new Claim(ClaimTypes.Name, user.Id)); + var authResult = new AuthenticationResult(id, new AuthenticationProperties(), new AuthenticationDescription()); + context.Setup(c => c.AuthenticateAsync(HttpAuthenticationManager.TwoFactorRememberedAuthenticationType)).ReturnsAsync(authResult).Verifiable(); + var contextAccessor = new Mock>(); + contextAccessor.Setup(a => a.Value).Returns(context.Object); + var signInService = new HttpAuthenticationManager(contextAccessor.Object); + var helper = new SignInManager(manager.Object, signInService); // Act var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); @@ -223,7 +351,7 @@ namespace Microsoft.AspNet.Identity.Security.Test response.Setup(r => r.SignOut(authenticationType)).Verifiable(); var contextAccessor = new Mock>(); contextAccessor.Setup(a => a.Value).Returns(context.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object) + var helper = new SignInManager(manager.Object, new HttpAuthenticationManager(contextAccessor.Object)) { AuthenticationType = authenticationType }; @@ -249,7 +377,7 @@ namespace Microsoft.AspNet.Identity.Security.Test var context = new Mock(); var contextAccessor = new Mock>(); contextAccessor.Setup(a => a.Value).Returns(context.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object); + var helper = new SignInManager(manager.Object, new HttpAuthenticationManager(contextAccessor.Object)); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); @@ -258,7 +386,6 @@ namespace Microsoft.AspNet.Identity.Security.Test manager.VerifyAll(); } - [Fact] public async Task PasswordSignInFailsWithUnknownUser() { @@ -268,7 +395,7 @@ namespace Microsoft.AspNet.Identity.Security.Test var context = new Mock(); var contextAccessor = new Mock>(); contextAccessor.Setup(a => a.Value).Returns(context.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object); + var helper = new SignInManager(manager.Object, new HttpAuthenticationManager(contextAccessor.Object)); // Act var result = await helper.PasswordSignInAsync("bogus", "bogus", false, false); @@ -297,7 +424,7 @@ namespace Microsoft.AspNet.Identity.Security.Test var context = new Mock(); var contextAccessor = new Mock>(); contextAccessor.Setup(a => a.Value).Returns(context.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object); + var helper = new SignInManager(manager.Object, new HttpAuthenticationManager(contextAccessor.Object)); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, true); @@ -307,30 +434,5 @@ namespace Microsoft.AspNet.Identity.Security.Test manager.VerifyAll(); } #endif - public class ApplicationSignInManager : SignInManager - { - public ApplicationSignInManager(ApplicationUserManager manager, IContextAccessor contextAccessor) - : base(manager, contextAccessor) - { - AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie; - } - } - - public class NullLoggerFactory : ILoggerFactory - { - public ILogger Create(string name) - { - return new NullLogger(); - } - } - - public class NullLogger : ILogger - { - public bool WriteCore(TraceType eventType, int eventId, object state, Exception exception, Func formatter) - { - return false; - } - } - } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Security.Test/Microsoft.AspNet.Identity.Security.Test.kproj b/test/Microsoft.AspNet.Identity.Security.Test/Microsoft.AspNet.Identity.Security.Test.kproj index 25348689af..53a096b7a7 100644 --- a/test/Microsoft.AspNet.Identity.Security.Test/Microsoft.AspNet.Identity.Security.Test.kproj +++ b/test/Microsoft.AspNet.Identity.Security.Test/Microsoft.AspNet.Identity.Security.Test.kproj @@ -21,8 +21,8 @@ + - - \ 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 cad5bc7a1c..1b773c31c6 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityOptionsTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Security.Claims; +using Microsoft.AspNet.Builder; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; @@ -37,13 +38,6 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(ClaimTypeOptions.DefaultSecurityStampClaimType, options.ClaimType.SecurityStamp); } - [Fact] - public void CopyNullIsNoop() - { - var options = new IdentityOptions(); - options.Copy(null); - } - [Fact] public void IdentityOptionsFromConfig() { @@ -91,47 +85,54 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(1000, options.Lockout.MaxFailedAccessAttempts); } - //[Fact] - //public void ClaimTypeOptionsFromConfig() - //{ - // const string roleClaimType = "rolez"; - // const string usernameClaimType = "namez"; - // const string useridClaimType = "idz"; - // const string securityStampClaimType = "stampz"; - // var dic = new Dictionary - // { - // {"role", roleClaimType}, - // {"username", usernameClaimType}, - // {"userid", useridClaimType}, - // {"securitystamp", securityStampClaimType} - // }; - // var config = new ConfigurationModel.Configuration {new MemoryConfigurationSource(dic)}; - // Assert.Equal(roleClaimType, config.Get("role")); - // var options = new ClaimTypeOptions(config); - // Assert.Equal(roleClaimType, options.Role); - // Assert.Equal(useridClaimType, options.UserId); - // Assert.Equal(usernameClaimType, options.UserName); - // Assert.Equal(securityStampClaimType, options.SecurityStamp); - //} + public class PasswordsNegativeLengthSetup : IOptionsSetup + { + public int Order { get { return 0; } } + public void Setup(IdentityOptions options) + { + options.Password.RequiredLength = -1; + } + } + + [Fact] + public void CanCustomizeIdentityOptions() + { + var builder = new Builder.Builder(new ServiceCollection().BuildServiceProvider()); + builder.UseServices(services => + { + services.AddIdentity(); + services.AddSetup(); + }); + + var setup = builder.ApplicationServices.GetService>(); + Assert.IsType(typeof(PasswordsNegativeLengthSetup), setup); + var optionsGetter = builder.ApplicationServices.GetService>(); + Assert.NotNull(optionsGetter); + setup.Setup(optionsGetter.Options); + + var myOptions = optionsGetter.Options; + Assert.True(myOptions.Password.RequireLowercase); + Assert.True(myOptions.Password.RequireDigit); + Assert.True(myOptions.Password.RequireNonLetterOrDigit); + Assert.True(myOptions.Password.RequireUppercase); + Assert.Equal(-1, myOptions.Password.RequiredLength); + } + + [Fact] + public void CanSetupIdentityOptions() + { + var app = new Builder.Builder(new ServiceCollection().BuildServiceProvider()); + app.UseServices(services => + { + services.AddIdentity(identityServices => identityServices.SetupOptions(options => options.User.RequireUniqueEmail = true)); + }); + + var optionsGetter = app.ApplicationServices.GetService>(); + Assert.NotNull(optionsGetter); + + var myOptions = optionsGetter.Options; + Assert.True(myOptions.User.RequireUniqueEmail); + } - //[Fact] - //public void PasswordOptionsFromConfig() - //{ - // var dic = new Dictionary - // { - // {"RequiredLength", "10"}, - // {"RequireNonLetterOrDigit", "false"}, - // {"RequireUpperCase", "false"}, - // {"RequireDigit", "false"}, - // {"RequireLowerCase", "false"} - // }; - // var config = new ConfigurationModel.Configuration { new MemoryConfigurationSource(dic) }; - // var options = new PasswordOptions(config); - // Assert.False(options.RequireDigit); - // Assert.False(options.RequireLowercase); - // Assert.False(options.RequireNonLetterOrDigit); - // Assert.False(options.RequireUppercase); - // Assert.Equal(10, options.RequiredLength); - //} } } \ 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 0f449ce55b..969ef2c0da 100644 --- a/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj +++ b/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj @@ -34,4 +34,4 @@ - + \ 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 ddec1e1e62..a6e9b6ada2 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -503,7 +503,7 @@ namespace Microsoft.AspNet.Identity.Test await Assert.ThrowsAsync("user", async () => await manager.GeneratePasswordResetTokenAsync(null)); await Assert.ThrowsAsync("user", - async () => await manager.ResetPassword(null, null, null)); + async () => await manager.ResetPasswordAsync(null, null, null)); await Assert.ThrowsAsync("user", async () => await manager.IsEmailConfirmedAsync(null)); await Assert.ThrowsAsync("user", @@ -594,7 +594,7 @@ namespace Microsoft.AspNet.Identity.Test await Assert.ThrowsAsync(() => manager.UpdateSecurityStampAsync(null)); await Assert.ThrowsAsync(() => manager.GetSecurityStampAsync(null)); await Assert.ThrowsAsync(() => manager.GeneratePasswordResetTokenAsync(null)); - await Assert.ThrowsAsync(() => manager.ResetPassword(null, null, null)); + await Assert.ThrowsAsync(() => manager.ResetPasswordAsync(null, null, null)); await Assert.ThrowsAsync(() => manager.GenerateEmailConfirmationTokenAsync(null)); await Assert.ThrowsAsync(() => manager.IsEmailConfirmedAsync(null)); await Assert.ThrowsAsync(() => manager.ConfirmEmailAsync(null, null)); diff --git a/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs index 08c8d854b3..a79b8bbf31 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserValidatorTest.cs @@ -3,8 +3,6 @@ using System; using System.Threading.Tasks; -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; using Xunit; namespace Microsoft.AspNet.Identity.Test diff --git a/test/Microsoft.AspNet.Identity.Test/project.json b/test/Microsoft.AspNet.Identity.Test/project.json index 557a1b1b05..a6731fa8f2 100644 --- a/test/Microsoft.AspNet.Identity.Test/project.json +++ b/test/Microsoft.AspNet.Identity.Test/project.json @@ -1,7 +1,10 @@ { "version": "0.1-alpha-*", "dependencies": { + "Microsoft.AspNet.Http" : "0.1-alpha-*", "Microsoft.AspNet.Identity" : "0.1-alpha-*", + "Microsoft.AspNet.PipelineCore" : "0.1-alpha-*", + "Microsoft.AspNet.RequestContainer" : "0.1-alpha-*", "Microsoft.AspNet.Testing" : "0.1-alpha-*", "Microsoft.Framework.ConfigurationModel" : "0.1-alpha-*", "Microsoft.Framework.DependencyInjection" : "0.1-alpha-*", diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs index 443a1ae06c..c6e85bba19 100644 --- a/test/Shared/MockHelpers.cs +++ b/test/Shared/MockHelpers.cs @@ -7,11 +7,28 @@ using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; using Microsoft.Framework.OptionsModel; using Moq; +using System; namespace Microsoft.AspNet.Identity.Test { public static class MockHelpers { + public static UserManager CreateManager(Func> storeFunc) where TUser : class + { + var services = new ServiceCollection(); + services.Add(OptionsServices.GetDefaultServices()); + services.AddIdentity(b => b.AddUserStore(storeFunc)); + services.SetupOptions(options => + { + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireNonLetterOrDigit = false; + options.Password.RequireUppercase = false; + options.User.AllowOnlyAlphanumericNames = false; + }); + return services.BuildServiceProvider().GetService>(); + } + public static Mock> MockUserManager() where TUser : class { var store = new Mock>(); diff --git a/test/Shared/PriorityOrderer.cs b/test/Shared/PriorityOrderer.cs new file mode 100644 index 0000000000..d9161de20f --- /dev/null +++ b/test/Shared/PriorityOrderer.cs @@ -0,0 +1,59 @@ +// 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.Test +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Xunit.Abstractions; + using Xunit.Sdk; + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class TestPriorityAttribute : Attribute + { + public TestPriorityAttribute(int priority) + { + Priority = priority; + } + + public int Priority { get; private set; } + } + + public class PriorityOrderer : ITestCaseOrderer + { + public IEnumerable OrderTestCases(IEnumerable testCases) where XunitTestCase : ITestCase + { + var sortedMethods = new SortedDictionary>(); + + foreach (XunitTestCase testCase in testCases) + { + int priority = 0; + + foreach (IAttributeInfo attr in testCase.Method.GetCustomAttributes((typeof(TestPriorityAttribute)))) + priority = attr.GetNamedArgument("Priority"); + + GetOrCreate(sortedMethods, priority).Add(testCase); + } + + foreach (var list in sortedMethods.Keys.Select(priority => sortedMethods[priority])) + { + list.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.Method.Name, y.Method.Name)); + foreach (XunitTestCase testCase in list) + yield return testCase; + } + } + + static TValue GetOrCreate(IDictionary dictionary, TKey key) where TValue : new() + { + TValue result; + + if (dictionary.TryGetValue(key, out result)) return result; + + result = new TValue(); + dictionary[key] = result; + + return result; + } + } +} \ No newline at end of file diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index f3020e3ace..e7f7ef267a 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -466,7 +466,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.NotNull(stamp); var token = await manager.GeneratePasswordResetTokenAsync(user); Assert.NotNull(token); - IdentityResultAssert.IsSuccess(await manager.ResetPassword(user, token, newPassword)); + IdentityResultAssert.IsSuccess(await manager.ResetPasswordAsync(user, token, newPassword)); Assert.Null(await manager.FindByUserNamePasswordAsync(user.UserName, password)); Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, newPassword)); Assert.NotEqual(stamp, user.SecurityStamp); @@ -486,7 +486,7 @@ namespace Microsoft.AspNet.Identity.Test var token = await manager.GeneratePasswordResetTokenAsync(user); Assert.NotNull(token); manager.PasswordValidator = new AlwaysBadValidator(); - IdentityResultAssert.IsFailure(await manager.ResetPassword(user, token, newPassword), + IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, token, newPassword), AlwaysBadValidator.ErrorMessage); Assert.NotNull(await manager.FindByUserNamePasswordAsync(user.UserName, password)); Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, password)); @@ -504,7 +504,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); var stamp = user.SecurityStamp; Assert.NotNull(stamp); - IdentityResultAssert.IsFailure(await manager.ResetPassword(user, "bogus", newPassword), "Invalid token."); + IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, "bogus", newPassword), "Invalid token."); Assert.NotNull(await manager.FindByUserNamePasswordAsync(user.UserName, password)); Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, password)); Assert.Equal(stamp, user.SecurityStamp); @@ -1443,21 +1443,5 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, "bogus")); } - - public class TestSetup : IOptionsSetup - { - private readonly IdentityOptions _options; - - public TestSetup(IdentityOptions options) - { - _options = options; - } - - public int Order { get { return 0; } } - public void Setup(IdentityOptions options) - { - options.Copy(_options); - } - } } } \ No newline at end of file