diff --git a/src/Microsoft.AspNet.Identity.Entity/EntityRoleStore.cs b/src/Microsoft.AspNet.Identity.Entity/EntityRoleStore.cs new file mode 100644 index 0000000000..45db10dc6a --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/EntityRoleStore.cs @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +// NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing +// permissions and limitations under the License. + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.Entity; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class EntityRoleStore : EntityRoleStore where TRole : EntityRole + { + public EntityRoleStore(DbContext context) : base(context) { } + } + + public class EntityRoleStore : + IQueryableRoleStore + where TRole : EntityRole + where TKey : IEquatable + { + private bool _disposed; + + public EntityRoleStore(DbContext context) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + Context = context; + AutoSaveChanges = true; + } + + public DbContext Context { get; private set; } + + /// + /// If true will call SaveChanges after CreateAsync/UpdateAsync/DeleteAsync + /// + public bool AutoSaveChanges { get; set; } + + private async Task SaveChanges(CancellationToken cancellationToken) + { + if (AutoSaveChanges) + { + await Context.SaveChangesAsync(cancellationToken); + } + } + + public virtual Task GetRoleAggregate(Expression> filter, CancellationToken cancellationToken = default(CancellationToken)) + { + // TODO: return Roles.SingleOrDefaultAsync(filter, cancellationToken); + return Task.FromResult(Roles.SingleOrDefault(filter)); + } + + public async virtual Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException("role"); + } + await Context.AddAsync(role, cancellationToken); + await SaveChanges(cancellationToken); + } + + public async virtual Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException("role"); + } + await Context.UpdateAsync(role, cancellationToken); + await SaveChanges(cancellationToken); + } + + public async virtual Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException("role"); + } + Context.Delete(role); + await SaveChanges(cancellationToken); + } + + public Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = new CancellationToken()) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException("role"); + } + return Task.FromResult(role.Id); + } + + public Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken = new CancellationToken()) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException("role"); + } + return Task.FromResult(role.Name); + } + + public Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken = new CancellationToken()) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (role == null) + { + throw new ArgumentNullException("role"); + } + role.Name = roleName; + return Task.FromResult(0); + } + + + public virtual TKey ConvertId(string userId) + { + return (TKey)Convert.ChangeType(userId, typeof(TKey)); + } + + /// + /// Find a role by id + /// + /// + /// + /// + public virtual Task FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + var roleId = ConvertId(id); + return GetRoleAggregate(u => u.Id.Equals(roleId), cancellationToken); + } + + /// + /// Find a role by name + /// + /// + /// + /// + public virtual Task FindByNameAsync(string name, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return GetRoleAggregate(u => u.Name.ToUpper() == name.ToUpper(), cancellationToken); + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Dispose the store + /// + public void Dispose() + { + _disposed = true; + } + + public IQueryable Roles + { + get { return Context.Set(); } + } + } +} diff --git a/src/Microsoft.AspNet.Identity.Entity/EntityUser.cs b/src/Microsoft.AspNet.Identity.Entity/EntityUser.cs index 70870beece..6ffea6a4b5 100644 --- a/src/Microsoft.AspNet.Identity.Entity/EntityUser.cs +++ b/src/Microsoft.AspNet.Identity.Entity/EntityUser.cs @@ -45,7 +45,6 @@ namespace Microsoft.AspNet.Identity.Entity Claims = new List(); Roles = new List(); Logins = new List(); - } public virtual TKey Id { get; set; } diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs b/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs index db3a4a2edd..17f16c9955 100644 --- a/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityBuilderExtensions.cs @@ -9,11 +9,19 @@ namespace Microsoft.AspNet.Identity where TUser : EntityUser where TRole : EntityRole { - builder.Services.AddScoped, UserStore>(); + builder.Services.AddScoped, InMemoryUserStore>(); builder.Services.AddScoped, UserManager>(); - builder.Services.AddScoped, RoleStore>(); + builder.Services.AddScoped, EntityRoleStore>(); builder.Services.AddScoped, RoleManager>(); return builder; } + + public static IdentityBuilder AddEntity(this IdentityBuilder builder) + where TUser : User + { + builder.Services.AddScoped, UserStore>(); + 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 0fbd3fc1ac..2269ae17bb 100644 --- a/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs +++ b/src/Microsoft.AspNet.Identity.Entity/IdentityContext.cs @@ -52,7 +52,7 @@ namespace Microsoft.AspNet.Identity.Entity protected override void OnConfiguring(DbContextOptions builder) { //#if NET45 -// builder.UseSqlServer(@"Server=(localdb)\v11.0;Database=IdentityDb3;Trusted_Connection=True;"); +// builder.SqlServerConnectionString(@"Server=(localdb)\v11.0;Database=IdentityEF5-5-1;Trusted_Connection=True;"); //#else builder.UseInMemoryStore(); //#endif diff --git a/src/Microsoft.AspNet.Identity.Entity/IdentitySqlContext.cs b/src/Microsoft.AspNet.Identity.Entity/IdentitySqlContext.cs new file mode 100644 index 0000000000..16641957ba --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/IdentitySqlContext.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +// NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing +// permissions and limitations under the License. + +using System; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; +using Microsoft.Data.Entity; +using Microsoft.Data.Entity.SqlServer; +using Microsoft.Data.Entity.Metadata; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class IdentitySqlContext : + IdentitySqlContext + { + public IdentitySqlContext() { } + public IdentitySqlContext(IServiceProvider serviceProvider) : base(serviceProvider) { } + } + + public class IdentitySqlContext : DbContext + where TUser : User + { + + public DbSet Users { get; set; } + //public DbSet Roles { get; set; } + + public IdentitySqlContext(IServiceProvider serviceProvider) + : base(serviceProvider) { } + + public IdentitySqlContext() { } + + protected override void OnConfiguring(DbContextOptions builder) + { + // TODO: pull connection string from config + builder.UseSqlServer(@"Server=(localdb)\v11.0;Database=SimpleIdentity3;Trusted_Connection=True;"); + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .Key(u => u.Id) + .Properties(ps => ps.Property(u => u.UserName)) + .ToTable("AspNetUsers"); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity.Entity/UserStore.cs b/src/Microsoft.AspNet.Identity.Entity/InMemoryUserStore.cs similarity index 98% rename from src/Microsoft.AspNet.Identity.Entity/UserStore.cs rename to src/Microsoft.AspNet.Identity.Entity/InMemoryUserStore.cs index b383a00cd3..610257b5c1 100644 --- a/src/Microsoft.AspNet.Identity.Entity/UserStore.cs +++ b/src/Microsoft.AspNet.Identity.Entity/InMemoryUserStore.cs @@ -27,18 +27,18 @@ using Microsoft.Data.Entity; namespace Microsoft.AspNet.Identity.Entity { - public class UserStore : - UserStore + public class InMemoryInMemoryUserStore : + InMemoryUserStore { - public UserStore(DbContext context) : base(context) { } + public InMemoryInMemoryUserStore(DbContext context) : base(context) { } } - public class UserStore : UserStore where TUser:EntityUser + public class InMemoryUserStore : InMemoryUserStore where TUser:EntityUser { - public UserStore(DbContext context) : base(context) { } + public InMemoryUserStore(DbContext context) : base(context) { } } - public class UserStore : + public class InMemoryUserStore : IUserLoginStore, IUserClaimStore, IUserRoleStore, @@ -58,7 +58,7 @@ namespace Microsoft.AspNet.Identity.Entity { private bool _disposed; - public UserStore(DbContext context) + public InMemoryUserStore(DbContext context) { if (context == null) { diff --git a/src/Microsoft.AspNet.Identity.Entity/SqlUserStore.cs b/src/Microsoft.AspNet.Identity.Entity/SqlUserStore.cs new file mode 100644 index 0000000000..dbfdcd0065 --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/SqlUserStore.cs @@ -0,0 +1,351 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +// NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing +// permissions and limitations under the License. + +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.Entity +{ + // Real Sql implementation + public class SqlUserStore : + UserStore + { + public SqlUserStore(DbContext context) : base(context) { } + } + + public class UserStore : + //IUserRoleStore, + IUserPasswordStore, + IQueryableUserStore + where TUser : User + { + private bool _disposed; + + public UserStore(DbContext context) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + Context = context; + AutoSaveChanges = true; + } + + public DbContext 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 = new 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 = new 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 = new CancellationToken()) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException("user"); + } + user.UserName = 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"); + } + await Context.UpdateAsync(user, cancellationToken); + 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.Delete(user); + await SaveChanges(cancellationToken); + } + + /// + /// Find a user by id + /// + /// + /// + /// + public virtual Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return GetUserAggregate(u => u.Id.Equals(userId), 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(); } + } + + /// + /// 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); + } + + ///// + ///// 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); + //} + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Dispose the store + /// + public void Dispose() + { + _disposed = true; + } + } +} diff --git a/src/Microsoft.AspNet.Identity.Entity/User.cs b/src/Microsoft.AspNet.Identity.Entity/User.cs new file mode 100644 index 0000000000..03ce5f77fd --- /dev/null +++ b/src/Microsoft.AspNet.Identity.Entity/User.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +// NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing +// permissions and limitations under the License. + +using System; + +namespace Microsoft.AspNet.Identity.Entity +{ + public class User + { + public User() + { + Id = Guid.NewGuid().ToString(); + // TODO: remove when bug is fixed + UserName = ""; + PasswordHash = ""; + } + + public User(string userName) : this() + { + UserName = userName; + } + + public virtual string Id { get; set; } + public virtual string UserName { get; set; } + + /// + /// The salted/hashed form of the user password + /// + public virtual string PasswordHash { get; set; } + } +} + diff --git a/src/Microsoft.AspNet.Identity.Entity/project.json b/src/Microsoft.AspNet.Identity.Entity/project.json index 1f945f9260..a4f6637d5a 100644 --- a/src/Microsoft.AspNet.Identity.Entity/project.json +++ b/src/Microsoft.AspNet.Identity.Entity/project.json @@ -14,7 +14,8 @@ "net45": { "dependencies": { "System.Runtime": "", - "System.Collections": "" + "System.Collections": "", + "System.Data": "" } }, "k10": { diff --git a/src/Microsoft.AspNet.Identity/IdentityResult.cs b/src/Microsoft.AspNet.Identity/IdentityResult.cs index 55fd735189..aab66f45e7 100644 --- a/src/Microsoft.AspNet.Identity/IdentityResult.cs +++ b/src/Microsoft.AspNet.Identity/IdentityResult.cs @@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Identity Errors = errors; } - private IdentityResult(bool success) + protected IdentityResult(bool success) { Succeeded = success; Errors = new string[0]; diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/InMemoryUserStoreTest.cs similarity index 99% rename from test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs rename to test/Microsoft.AspNet.Identity.Entity.Test/InMemoryUserStoreTest.cs index f8d15188be..8148e838ce 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/UserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/InMemoryUserStoreTest.cs @@ -15,23 +15,24 @@ // See the Apache 2 License for the specific language governing // permissions and limitations under the License. +using Microsoft.AspNet.Identity.Test; using Microsoft.AspNet.Testing; using Microsoft.Data.Entity; using Microsoft.Data.Entity.Metadata; using Microsoft.Data.Entity.InMemory; using Microsoft.Data.Entity.SqlServer; -using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; using System; using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; using Xunit; namespace Microsoft.AspNet.Identity.Entity.Test { - public class UserStoreTest + public class InMemoryUserStoreTest { class ApplicationUserManager : UserManager { @@ -49,7 +50,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test #endif services.AddSingleton, OptionsAccessor>(); services.AddInstance(new IdentityContext()); - services.AddTransient, UserStore>(); + services.AddTransient, InMemoryInMemoryUserStore>(); services.AddSingleton(); var provider = services.BuildServiceProvider(); var manager = provider.GetService(); @@ -67,7 +68,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test services.AddEntityFramework(s => s.AddInMemoryStore()); #endif // TODO: this needs to construct a new instance of InMemoryStore - var store = new UserStore(new IdentityContext()); + var store = new InMemoryInMemoryUserStore(new IdentityContext()); services.AddIdentity(s => { s.AddUserStore(() => store); @@ -84,7 +85,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test //public async Task CanUseSingletonGenericManagerInstance() //{ // var services = new ServiceCollection(); - // var store = new UserStore(new IdentityContext()); + // var store = new EntityUserStore(new IdentityContext()); // services.AddIdentity(s => // { // s.UseStore(() => store); @@ -98,9 +99,9 @@ namespace Microsoft.AspNet.Identity.Entity.Test //} [Fact] - public async Task UserStoreMethodsThrowWhenDisposedTest() + public async Task EntityUserStoreMethodsThrowWhenDisposedTest() { - var store = new UserStore(new IdentityContext()); + var store = new InMemoryInMemoryUserStore(new IdentityContext()); store.Dispose(); await Assert.ThrowsAsync(async () => await store.AddClaimAsync(null, null)); await Assert.ThrowsAsync(async () => await store.AddLoginAsync(null, null)); @@ -130,10 +131,10 @@ namespace Microsoft.AspNet.Identity.Entity.Test } [Fact] - public async Task UserStorePublicNullCheckTest() + public async Task EntityUserStorePublicNullCheckTest() { - Assert.Throws("context", () => new UserStore(null)); - var store = new UserStore(new IdentityContext()); + Assert.Throws("context", () => new InMemoryInMemoryUserStore(null)); + var store = new InMemoryInMemoryUserStore(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)); diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs b/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs index 6cc6073e2e..eb60ad5ad7 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/RoleStoreTest.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test services.AddEntityFramework(s => s.AddInMemoryStore()); #endif // TODO: this should construct a new instance of InMemoryStore - var store = new RoleStore(new IdentityContext()); + var store = new EntityRoleStore(new IdentityContext()); services.AddIdentity(s => { s.AddRoleStore(() => store); @@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test services.AddEntityFramework(s => s.AddInMemoryStore()); #endif services.AddTransient(); - services.AddTransient, RoleStore>(); + services.AddTransient, EntityRoleStore>(); //todo: services.AddSingleton, RoleManager>(); // TODO: How to configure SqlServer? services.AddSingleton(); @@ -77,7 +77,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test [Fact] public async Task RoleStoreMethodsThrowWhenDisposedTest() { - var store = new RoleStore(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)); @@ -92,8 +92,8 @@ namespace Microsoft.AspNet.Identity.Entity.Test [Fact] public async Task RoleStorePublicNullCheckTest() { - Assert.Throws("context", () => new RoleStore(null)); - var store = new RoleStore(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 new file mode 100644 index 0000000000..b805adddbb --- /dev/null +++ b/test/Microsoft.AspNet.Identity.Entity.Test/SqlUserStoreTest.cs @@ -0,0 +1,232 @@ +//// Copyright (c) Microsoft Open Technologies, Inc. +//// All Rights Reserved +//// +//// Licensed under the Apache License, Version 2.0 (the "License"); +//// you may not use this file except in compliance with the License. +//// You may obtain a copy of the License at +//// +//// http://www.apache.org/licenses/LICENSE-2.0 +//// +//// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR +//// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +//// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF +//// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR +//// NON-INFRINGEMENT. +//// See the Apache 2 License for the specific language governing +//// permissions and limitations under the License. + +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Testing; +using Microsoft.Data.Entity; +using Microsoft.Data.Entity.Metadata; +using Microsoft.Data.Entity.InMemory; +using Microsoft.Data.Entity.SqlServer; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.Fallback; +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNet.Identity.Entity.Test +{ + public class SqlUserStoreTest + { + public class ApplicationUser : User { } + public class ApplicationUserManager : UserManager + { + 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 ApplicationDbContext : IdentitySqlContext + { + public ApplicationDbContext(IServiceProvider services) : base(services) { } + } + + [Fact] + public async Task EnsureStartupUsageWorks() + { + IBuilder builder = new Microsoft.AspNet.Builder.Builder(new ServiceCollection().BuildServiceProvider()); + + //builder.UseServices(services => services.AddIdentity(s => + // s.AddEntity() + //{ + + builder.UseServices(services => + { + services.AddEntityFramework(); + services.AddInstance(CreateAppContext()); + services.AddIdentity(s => + { + s.AddEntity(); + s.AddUserManager(); + }); + }); + + var userStore = builder.ApplicationServices.GetService>(); + var userManager = builder.ApplicationServices.GetService(); + + Assert.NotNull(userStore); + Assert.NotNull(userManager); + + const string userName = "admin"; + const string password = "1qaz@WSX"; + var user = new ApplicationUser { UserName = userName }; + IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password)); + IdentityResultAssert.IsSuccess(await userManager.DeleteAsync(user)); + } + + [Fact] + public async Task CanCreateUserUsingEF() + { + using (var db = CreateContext()) + { + var guid = Guid.NewGuid().ToString(); + db.Users.Add(new User {Id = guid, UserName = guid}); + db.SaveChanges(); + Assert.NotNull(db.Users.FirstOrDefault(u => u.UserName == guid)); + } + } + + public static IdentitySqlContext CreateContext() + { + var serviceProvider = new ServiceCollection() + .AddEntityFramework(s => s.AddSqlServer()) + .BuildServiceProvider(); + + var db = new IdentitySqlContext(serviceProvider); + + // TODO: Recreate DB, doesn't support String ID or Identity context yet + if (!db.Database.Exists()) + { + db.Database.Create(); + } + + // TODO: CreateAsync DB? + return db; + } + + public static ApplicationDbContext CreateAppContext() + { + var serviceProvider = new ServiceCollection() + .AddEntityFramework(s => s.AddSqlServer()) + .BuildServiceProvider(); + + var db = new ApplicationDbContext(serviceProvider); + + // TODO: Recreate DB, doesn't support String ID or Identity context yet + if (!db.Database.Exists()) + { + db.Database.Create(); + } + + // 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); + } + + public static UserManager CreateManager() + { + return CreateManager(CreateContext()); + } + + [Fact] + public async Task CanCreateUsingManager() + { + var manager = CreateManager(); + var guid = Guid.NewGuid().ToString(); + var user = new User { UserName = "New"+guid }; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); + } + + [Fact] + public async Task CanDeleteUser() + { + var manager = CreateManager(); + var user = new User("DeleteAsync"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); + Assert.Null(await manager.FindByIdAsync(user.Id)); + } + + [Fact] + public async Task CanUpdateUserName() + { + var manager = CreateManager(); + var user = new User("UpdateAsync"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.Null(await manager.FindByNameAsync("New")); + user.UserName = "New"; + IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); + Assert.NotNull(await manager.FindByNameAsync("New")); + Assert.Null(await manager.FindByNameAsync("UpdateAsync")); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); + } + + [Fact] + public async Task CanSetUserName() + { + var manager = CreateManager(); + var user = new User("UpdateAsync"); + 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")); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); + } + + [Fact] + public async Task CanChangePassword() + { + var manager = TestIdentityFactory.CreateManager(); + var user = new EntityUser("ChangePasswordTest"); + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + //Assert.Equal(manager.Users.Count(), 1); + //var stamp = user.SecurityStamp; + //Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, password, newPassword)); + Assert.Null(await manager.FindByUserNamePasswordAsync(user.UserName, password)); + Assert.Equal(user, await manager.FindByUserNamePasswordAsync(user.UserName, newPassword)); + //Assert.NotEqual(stamp, user.SecurityStamp); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); + } + + } +} diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs b/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs index 9355ad31a3..b8e76f18ee 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs +++ b/test/Microsoft.AspNet.Identity.Entity.Test/TestIdentityFactory.cs @@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test { public static class TestIdentityFactory { - public static DbContext CreateContext() + public static IdentityContext CreateContext() { var serviceProvider = new ServiceCollection() //#if NET45 @@ -46,10 +46,10 @@ namespace Microsoft.AspNet.Identity.Entity.Test var db = new IdentityContext(serviceProvider); // TODO: Recreate DB, doesn't support String ID or Identity context yet - //if (!db.Database.Exists()) - //{ - // db.Database.Create(); - //} + if (!db.Database.Exists()) + { + db.Database.Create(); + } // TODO: CreateAsync DB? return db; @@ -77,7 +77,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test var services = new ServiceCollection(); services.AddTransient, UserValidator>(); services.AddTransient, PasswordValidator>(); - services.AddInstance>(new UserStore(context)); + services.AddInstance>(new InMemoryUserStore(context)); services.AddSingleton, UserManager>(); var options = new IdentityOptions { @@ -92,7 +92,7 @@ namespace Microsoft.AspNet.Identity.Entity.Test 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 UserStore(context), optionsAccessor); + return new UserManager(services.BuildServiceProvider(), new InMemoryUserStore(context), optionsAccessor); } public static UserManager CreateManager() @@ -104,9 +104,9 @@ namespace Microsoft.AspNet.Identity.Entity.Test { var services = new ServiceCollection(); services.AddTransient, RoleValidator>(); - services.AddInstance>(new RoleStore(context)); + services.AddInstance>(new EntityRoleStore(context)); // return services.BuildServiceProvider().GetService>(); - return new RoleManager(services.BuildServiceProvider(), new RoleStore(context)); + return new RoleManager(services.BuildServiceProvider(), new EntityRoleStore(context)); } public static RoleManager CreateRoleManager() diff --git a/test/Microsoft.AspNet.Identity.Entity.Test/project.json b/test/Microsoft.AspNet.Identity.Entity.Test/project.json index bddadaef69..d5eba33819 100644 --- a/test/Microsoft.AspNet.Identity.Entity.Test/project.json +++ b/test/Microsoft.AspNet.Identity.Entity.Test/project.json @@ -2,12 +2,17 @@ "version": "0.1-alpha-*", "dependencies": { "Microsoft.AspNet.Http": "0.1-alpha-*", + "Microsoft.AspNet.Identity": "", "Microsoft.AspNet.Identity.Entity": "", "Microsoft.AspNet.PipelineCore": "0.1-alpha-*", "Microsoft.AspNet.RequestContainer": "0.1-alpha-*", "Microsoft.AspNet.Security.DataProtection": "0.1-alpha-*", "Microsoft.AspNet.Testing": "0.1-alpha-*", - "Microsoft.Bcl.Immutable": "1.1.18-beta-*", + "Microsoft.Data.Common": "0.1-alpha-*", + "Microsoft.Data.Entity": "0.1-alpha-*", + "Microsoft.Data.Entity.Relational": "0.1-alpha-*", + "Microsoft.Data.Entity.SqlServer": "0.1-alpha-*", + "Microsoft.Data.Entity.InMemory": "0.1-alpha-*", "Microsoft.Framework.ConfigurationModel": "0.1-alpha-*", "Microsoft.Framework.DependencyInjection": "0.1-alpha-*", "Microsoft.Framework.Logging": "0.1-alpha-*", @@ -28,6 +33,7 @@ } } }, + "code": "**\\*.cs;..\\Shared\\*.cs", "commands": { "test": "Xunit.KRunner" }