diff --git a/.gitignore b/.gitignore index cdebbbc..c18f14f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/artifacts/ + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. @@ -154,4 +156,9 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store -/TestResult.xml + +TestResult.xml + +project.lock.json + +.vs/ diff --git a/README.md b/README.md index 0d87e86..2c47ba0 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,63 @@ -AspNet.Identity.Mongo -===================== +## Microsoft.AspNetCore.Identity.MongoDB -A mongodb provider for the new ASP.NET Identity framework. My aim is to ensure this project is well tested and configurable. +This is a MongoDB provider for the ASP.NET Core Identity framework. This was ported from the v2 Identity framework that was a part of ASP.NET (AspNet.Identity.Mongo NuGet package) -## Usage - - var client = new MongoClient("mongodb://localhost:27017"); - var database = client.GetDatabase("mydb"); - var users = database.GetCollection("users"); - - var store = new UserStore(users); - var manager = new UserManager(store); +I've released a new package for the ASP.NET Core Identity framework for the following reasons: +- Discoverability - named AspNetCore. +- ASP.NET Core is a rewrite of ASP.NET, this Core Identity framework won't run on traditional ASP.NET. +- Migrating isn't a matter of updating dependencies. - // if you want roles too: - var roles = database.GetCollection("roles"); - var roleStore = new RoleStore(roles); +This project has extensive test coverage. - // at some point in application startup it would be good to ensure unique indexes on user and role names exist: +If you want something easy to setup, this adapter is for you. I do not intend to cover every possible desirable configuration, if you don't like my decisions, write your own adapter. Use this as a learning tool to make your own adapter. These adapters are not complicated, but trying to make them configurable would become a complicated mess. And would confuse the majority of people that want something simple to use. So I'm favoring simplicity over making every last person happy. - IndexChecks.EnsureUniqueIndexOnUserName(users); - IndexChecks.EnsureUniqueIndexOnEmail(users); - - IndexChecks.EnsureUniqueIndexOnRoleName(roles); - -OR +## Usage -a sample [aspnet-identity-mongo-sample](https://github.com/g0t4/aspnet-identity-mongo-sample) based on [Microsoft ASP.NET Identity Samples](http://www.nuget.org/packages/Microsoft.AspNet.Identity.Samples). +- Reference this package in project.json: Microsoft.AspNetCore.Identity.MongoDB +- Then, in ConfigureServices--or wherever you are registering services--include the following to register both the Identity services and MongoDB stores: -## Installation +```csharp +services.AddIdentityWithMongoStores("mongodb://localhost/myDB"); +``` -via nuget: +- If you want to customize what is registered, refer to the tests for further options (CoreTests/MongoIdentityBuilderExtensionsTests.cs) +- Remember with the Identity framework, the whole point is that both a `UserManager` and `RoleManager` are provided for you to use, here's how you can resolve instances manually. Of course, constructor injection is also available. - Install-Package AspNet.Identity.MongoDB +```csharp +var userManager = provider.GetService>(); +var roleManager = provider.GetService>(); +``` -## Building and Testing +- The following methods help create indexes that will boost lookups by UserName, Email and role Name. These have changed since Identity v2 to refer to Normalized fields. I dislike this aspect of Core Identity, but it is what it is. Basically these three fields are stored in uppercase format for case insensitive searches. -I'm using the albacore project with rake. +```csharp + IndexChecks.EnsureUniqueIndexOnNormalizedUserName(users); + IndexChecks.EnsureUniqueIndexOnNormalizedEmail(users); + IndexChecks.EnsureUniqueIndexOnNormalizedRoleName(roles); +``` -To build: +- Here is a sample project, review the commit log for the steps taken to port the default template from EntityFramework MSSQL to MongoDB. [aspnet-identity-mongo-sample](https://github.com/g0t4/aspnet-identity-mongo-sample). - rake msbuild - -To test: +What frameworks are targeted, with rationale: - rake tests - rake integration_tests +- Microsoft.AspNetCore.Identity - supports net451 and netstandard1.3 +- MongoDB.Driver v2.3 - supports net45 and netstandard1.5 +- Thus, the lowest common denominators are net451 (of net45 and net451) and netstandard1.5 (of netstandard1.3 and netstandard1.5) +- FYI net451 supports netstandard1.2, that's obviously too low for a single target -To package: - - rake package +## Building instructions -## Documentation +run commands in [](build.sh) -I'm writing about my design decisions on my blog: +## Migrating from ASP.NET Identity 2.0 +<<<<<<< HEAD +- Roles names need to be normalized as follows + - On IdentityRole documents, create a NormalizedName field = uppercase(Name). Leave Name as is. + - On IdentityUser documents, convert the values in the Roles array to uppercase +- User names need to be normalized as follows + - On IdentityUser documents, create a NormalizedUserName field = uppercase(UserName) and create a NormalizedEmail field = uppercase(Email). Leave UserName and Email as is. +======= - [Building a mongodb provider for the new ASP.NET Identity framework - Part 1](http://devblog.weshigbee.name/posts/building-a-mongodb-provider-for-the-new-asp.net-identity-framework-part-1) - [Building a mongodb provider for the new ASP.NET Identity framework - Part 2 RoleStore And Sample](http://devblog.weshigbee.name/posts/building-a-mongodb-provider-for-the-new-asp.net-identity-framework-part-2-rolestore-and-sample) +>>>>>>> g0t4/master diff --git a/Rakefile.rb b/Rakefile.rb deleted file mode 100644 index 1b1329e..0000000 --- a/Rakefile.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'albacore' - -msbuild :msbuild do |msb| - msb.solution = 'src/AspNet.Identity.MongoDB.sln' - msb.properties = { :configuration => :Release } - msb.targets = [ :Clean, :Build ] -end - -nunit :tests => [:msbuild] do |tests| - tests.command = 'src/packages/NUnit.Runners.2.6.3/tools/nunit-console.exe' - tests.assemblies = ['build/tests/Tests.dll'] -end - -nunit :integration_tests => [:msbuild] do |tests| - tests.command = 'src/packages/NUnit.Runners.2.6.3/tools/nunit-console.exe' - tests.assemblies = ['build/integrationTests/IntegrationTests.dll'] -end - -task :all_tests => [:tests, :integration_tests] - -task :package => [:all_tests] do - sh 'src/.nuget/nuget.exe pack src/AspNet.Identity.MongoDB/AspNet.Identity.MongoDB.csproj' -end \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..2c5ce70 --- /dev/null +++ b/build.bat @@ -0,0 +1,10 @@ +REM optional clean +REM rm -rf artifacts CoreTests/bin CoreTests/obj CoreIntegrationTests/bin CoreIntegrationTests/obj Microsoft.AspNetCore.Identity.MongoDB/bin Microsoft.AspNetCore.Identity.MongoDB/obj + +dotnet restore src +dotnet test -c Release src/CoreTests +dotnet test -c Release src/CoreIntegrationTests +dotnet pack -c Release -o artifacts src/Microsoft.AspNetCore.Identity.MongoDB + +REM nuget add artifacts\X.nupkg -Source C:\Code\scratch\localnugetfeedtesting +REM nuget publish artifacts\X.nupkg \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB.sln b/src/AspNet.Identity.MongoDB.sln index aa6d41b..28f20a8 100644 --- a/src/AspNet.Identity.MongoDB.sln +++ b/src/AspNet.Identity.MongoDB.sln @@ -1,13 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.12 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Identity.MongoDB", "AspNet.Identity.MongoDB\AspNet.Identity.MongoDB.csproj", "{AD64128A-6855-49F7-A846-1FCDAD368C55}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreTests", "CoreTests\CoreTests.csproj", "{EAC53866-6DBF-40B7-900A-22267FD0634A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{62483144-11D8-4ECE-990D-17B4716AFF69}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.MongoDB", "Microsoft.AspNetCore.Identity.MongoDB\Microsoft.AspNetCore.Identity.MongoDB.csproj", "{6DFF5058-E107-459E-87C3-DA41B2C1463C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "IntegrationTests\IntegrationTests.csproj", "{2817B6BF-006D-415D-8CAB-34E14DCCBC3C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreIntegrationTests", "CoreIntegrationTests\CoreIntegrationTests.csproj", "{836F4635-9B6B-4090-8CDB-9CD9F7BEA829}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,20 +15,23 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AD64128A-6855-49F7-A846-1FCDAD368C55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD64128A-6855-49F7-A846-1FCDAD368C55}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD64128A-6855-49F7-A846-1FCDAD368C55}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD64128A-6855-49F7-A846-1FCDAD368C55}.Release|Any CPU.Build.0 = Release|Any CPU - {62483144-11D8-4ECE-990D-17B4716AFF69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {62483144-11D8-4ECE-990D-17B4716AFF69}.Debug|Any CPU.Build.0 = Debug|Any CPU - {62483144-11D8-4ECE-990D-17B4716AFF69}.Release|Any CPU.ActiveCfg = Release|Any CPU - {62483144-11D8-4ECE-990D-17B4716AFF69}.Release|Any CPU.Build.0 = Release|Any CPU - {2817B6BF-006D-415D-8CAB-34E14DCCBC3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2817B6BF-006D-415D-8CAB-34E14DCCBC3C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2817B6BF-006D-415D-8CAB-34E14DCCBC3C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2817B6BF-006D-415D-8CAB-34E14DCCBC3C}.Release|Any CPU.Build.0 = Release|Any CPU + {EAC53866-6DBF-40B7-900A-22267FD0634A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAC53866-6DBF-40B7-900A-22267FD0634A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAC53866-6DBF-40B7-900A-22267FD0634A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAC53866-6DBF-40B7-900A-22267FD0634A}.Release|Any CPU.Build.0 = Release|Any CPU + {6DFF5058-E107-459E-87C3-DA41B2C1463C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DFF5058-E107-459E-87C3-DA41B2C1463C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DFF5058-E107-459E-87C3-DA41B2C1463C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DFF5058-E107-459E-87C3-DA41B2C1463C}.Release|Any CPU.Build.0 = Release|Any CPU + {836F4635-9B6B-4090-8CDB-9CD9F7BEA829}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {836F4635-9B6B-4090-8CDB-9CD9F7BEA829}.Debug|Any CPU.Build.0 = Debug|Any CPU + {836F4635-9B6B-4090-8CDB-9CD9F7BEA829}.Release|Any CPU.ActiveCfg = Release|Any CPU + {836F4635-9B6B-4090-8CDB-9CD9F7BEA829}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F4D69225-2814-4DFF-93D6-7AA341335C1C} + EndGlobalSection EndGlobal diff --git a/src/AspNet.Identity.MongoDB.sln.DotSettings b/src/AspNet.Identity.MongoDB.sln.DotSettings index ddaa17f..90b3393 100644 --- a/src/AspNet.Identity.MongoDB.sln.DotSettings +++ b/src/AspNet.Identity.MongoDB.sln.DotSettings @@ -6,4 +6,6 @@ True <?xml version="1.0" encoding="utf-16"?><Profile name="Shared"><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><HtmlReformatCode>True</HtmlReformatCode><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReformatCode>True</CSReformatCode><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><JsInsertSemicolon>True</JsInsertSemicolon><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><CssReformatCode>True</CssReformatCode><XMLReformatCode>True</XMLReformatCode></Profile> Shared - True \ No newline at end of file + True + True + True \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB/AspNet.Identity.MongoDB.csproj b/src/AspNet.Identity.MongoDB/AspNet.Identity.MongoDB.csproj deleted file mode 100644 index d7761b3..0000000 --- a/src/AspNet.Identity.MongoDB/AspNet.Identity.MongoDB.csproj +++ /dev/null @@ -1,80 +0,0 @@ - - - - - Debug - AnyCPU - {AD64128A-6855-49F7-A846-1FCDAD368C55} - Library - Properties - AspNet.Identity.MongoDB - AspNet.Identity.MongoDB - v4.5 - 512 - ..\ - - - - true - full - false - ..\..\build\lib\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\build\lib\ - TRACE - prompt - 4 - - - - False - ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\MongoDB.Bson.2.2.0\lib\net45\MongoDB.Bson.dll - True - - - ..\packages\MongoDB.Driver.2.2.0\lib\net45\MongoDB.Driver.dll - True - - - ..\packages\MongoDB.Driver.Core.2.2.0\lib\net45\MongoDB.Driver.Core.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB/AspNet.Identity.MongoDB.nuspec b/src/AspNet.Identity.MongoDB/AspNet.Identity.MongoDB.nuspec deleted file mode 100644 index 398e5e1..0000000 --- a/src/AspNet.Identity.MongoDB/AspNet.Identity.MongoDB.nuspec +++ /dev/null @@ -1,17 +0,0 @@ - - - - AspNet.Identity.MongoDB - 2.1.0 - Wes Higbee - Wes Higbee - https://github.com/g0t4/aspnet-identity-mongo/blob/master/LICENSE - https://github.com/g0t4/aspnet-identity-mongo - https://github.com/g0t4/aspnet-identity-mongo - false - A mongodb provider for the new ASP.NET Identity framework. My aim is to ensure this project is well tested and configurable. - MongoDB 2.2 c# driver and add back queryable (LINQ) user and role stores. - MIT - mongo mongodb aspnet identity - - \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB/IdentityUser.cs b/src/AspNet.Identity.MongoDB/IdentityUser.cs deleted file mode 100644 index fae52c8..0000000 --- a/src/AspNet.Identity.MongoDB/IdentityUser.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace AspNet.Identity.MongoDB -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Security.Claims; - using global::MongoDB.Bson; - using global::MongoDB.Bson.Serialization.Attributes; - using Microsoft.AspNet.Identity; - - public class IdentityUser : IUser - { - public IdentityUser() - { - Id = ObjectId.GenerateNewId().ToString(); - Roles = new List(); - Logins = new List(); - Claims = new List(); - } - - [BsonRepresentation(BsonType.ObjectId)] - public string Id { get; private set; } - - public string UserName { get; set; } - - public virtual string SecurityStamp { get; set; } - - public virtual string Email { get; set; } - - public virtual bool EmailConfirmed { get; set; } - - public virtual string PhoneNumber { get; set; } - - public virtual bool PhoneNumberConfirmed { get; set; } - - public virtual bool TwoFactorEnabled { get; set; } - - public virtual DateTime? LockoutEndDateUtc { get; set; } - - public virtual bool LockoutEnabled { get; set; } - - public virtual int AccessFailedCount { get; set; } - - [BsonIgnoreIfNull] - public List Roles { get; set; } - - public virtual void AddRole(string role) - { - Roles.Add(role); - } - - public virtual void RemoveRole(string role) - { - Roles.Remove(role); - } - - [BsonIgnoreIfNull] - public virtual string PasswordHash { get; set; } - - [BsonIgnoreIfNull] - public List Logins { get; set; } - - public virtual void AddLogin(UserLoginInfo login) - { - Logins.Add(login); - } - - public virtual void RemoveLogin(UserLoginInfo login) - { - var loginsToRemove = Logins - .Where(l => l.LoginProvider == login.LoginProvider) - .Where(l => l.ProviderKey == login.ProviderKey); - - Logins = Logins.Except(loginsToRemove).ToList(); - } - - public virtual bool HasPassword() - { - return false; - } - - [BsonIgnoreIfNull] - public List Claims { get; set; } - - public virtual void AddClaim(Claim claim) - { - Claims.Add(new IdentityUserClaim(claim)); - } - - public virtual void RemoveClaim(Claim claim) - { - var claimsToRemove = Claims - .Where(c => c.Type == claim.Type) - .Where(c => c.Value == claim.Value); - - Claims = Claims.Except(claimsToRemove).ToList(); - } - } -} \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB/IndexChecks.cs b/src/AspNet.Identity.MongoDB/IndexChecks.cs deleted file mode 100644 index 2626959..0000000 --- a/src/AspNet.Identity.MongoDB/IndexChecks.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace AspNet.Identity.MongoDB -{ - using global::MongoDB.Driver; - - public class IndexChecks - { - public static void EnsureUniqueIndexOnUserName(IMongoCollection users) - where TUser : IdentityUser - { - var userName = Builders.IndexKeys.Ascending(t => t.UserName); - var unique = new CreateIndexOptions {Unique = true}; - users.Indexes.CreateOneAsync(userName, unique); - } - - public static void EnsureUniqueIndexOnRoleName(IMongoCollection roles) - where TRole : IdentityRole - { - var roleName = Builders.IndexKeys.Ascending(t => t.Name); - var unique = new CreateIndexOptions {Unique = true}; - roles.Indexes.CreateOneAsync(roleName, unique); - } - - public static void EnsureUniqueIndexOnEmail(IMongoCollection users) - where TUser : IdentityUser - { - var email = Builders.IndexKeys.Ascending(t => t.Email); - var unique = new CreateIndexOptions {Unique = true}; - users.Indexes.CreateOneAsync(email, unique); - } - } -} \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB/Properties/AssemblyInfo.cs b/src/AspNet.Identity.MongoDB/Properties/AssemblyInfo.cs deleted file mode 100644 index 17f407b..0000000 --- a/src/AspNet.Identity.MongoDB/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("AspNet.Identity.MongoDB")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("AspNet.Identity.MongoDB")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("95d6fb99-0451-44a0-8550-82a9b070208c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/AspNet.Identity.MongoDB/RoleStore.cs b/src/AspNet.Identity.MongoDB/RoleStore.cs deleted file mode 100644 index c7863cb..0000000 --- a/src/AspNet.Identity.MongoDB/RoleStore.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace AspNet.Identity.MongoDB -{ - using System.Linq; - using System.Threading.Tasks; - using global::MongoDB.Driver; - using Microsoft.AspNet.Identity; - - /// - /// Note: Deleting and updating do not modify the roles stored on a user document. If you desire this dynamic - /// capability, override the appropriate operations on RoleStore as desired for your application. For example you could - /// perform a document modification on the users collection before a delete or a rename. - /// - /// - public class RoleStore : IRoleStore, IQueryableRoleStore - where TRole : IdentityRole - { - private readonly IMongoCollection _Roles; - - public RoleStore(IMongoCollection roles) - { - _Roles = roles; - } - - public virtual void Dispose() - { - // no need to dispose of anything, mongodb handles connection pooling automatically - } - - public virtual Task CreateAsync(TRole role) - { - return _Roles.InsertOneAsync(role); - } - - public virtual Task UpdateAsync(TRole role) - { - return _Roles.ReplaceOneAsync(r => r.Id == role.Id, role); - } - - public virtual Task DeleteAsync(TRole role) - { - return _Roles.DeleteOneAsync(r => r.Id == role.Id); - } - - public virtual Task FindByIdAsync(string roleId) - { - return _Roles.Find(r => r.Id == roleId).FirstOrDefaultAsync(); - } - - public virtual Task FindByNameAsync(string roleName) - { - return _Roles.Find(r => r.Name == roleName).FirstOrDefaultAsync(); - } - - public virtual IQueryable Roles => _Roles.AsQueryable(); - } -} \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB/UserStore.cs b/src/AspNet.Identity.MongoDB/UserStore.cs deleted file mode 100644 index 2a36b40..0000000 --- a/src/AspNet.Identity.MongoDB/UserStore.cs +++ /dev/null @@ -1,255 +0,0 @@ -namespace AspNet.Identity.MongoDB -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Security.Claims; - using System.Threading.Tasks; - using global::MongoDB.Driver; - using Microsoft.AspNet.Identity; - - public class UserStore : IUserStore, - IUserPasswordStore, - IUserRoleStore, - IUserLoginStore, - IUserSecurityStampStore, - IUserEmailStore, - IUserClaimStore, - IUserPhoneNumberStore, - IUserTwoFactorStore, - IUserLockoutStore, - IQueryableUserStore - where TUser : IdentityUser - { - private readonly IMongoCollection _Users; - - public UserStore(IMongoCollection users) - { - _Users = users; - } - - public virtual void Dispose() - { - // no need to dispose of anything, mongodb handles connection pooling automatically - } - - public virtual Task CreateAsync(TUser user) - { - return _Users.InsertOneAsync(user); - } - - public virtual Task UpdateAsync(TUser user) - { - // todo should add an optimistic concurrency check - return _Users.ReplaceOneAsync(u => u.Id == user.Id, user); - } - - public virtual Task DeleteAsync(TUser user) - { - return _Users.DeleteOneAsync(u => u.Id == user.Id); - } - - public virtual Task FindByIdAsync(string userId) - { - return _Users.Find(u => u.Id == userId).FirstOrDefaultAsync(); - } - - public virtual Task FindByNameAsync(string userName) - { - // todo exception on duplicates? or better to enforce unique index to ensure this - return _Users.Find(u => u.UserName == userName).FirstOrDefaultAsync(); - } - - public virtual Task SetPasswordHashAsync(TUser user, string passwordHash) - { - user.PasswordHash = passwordHash; - return Task.FromResult(0); - } - - public virtual Task GetPasswordHashAsync(TUser user) - { - return Task.FromResult(user.PasswordHash); - } - - public virtual Task HasPasswordAsync(TUser user) - { - return Task.FromResult(user.HasPassword()); - } - - public virtual Task AddToRoleAsync(TUser user, string roleName) - { - user.AddRole(roleName); - return Task.FromResult(0); - } - - public virtual Task RemoveFromRoleAsync(TUser user, string roleName) - { - user.RemoveRole(roleName); - return Task.FromResult(0); - } - - public virtual Task> GetRolesAsync(TUser user) - { - return Task.FromResult((IList) user.Roles); - } - - public virtual Task IsInRoleAsync(TUser user, string roleName) - { - return Task.FromResult(user.Roles.Contains(roleName)); - } - - public virtual Task AddLoginAsync(TUser user, UserLoginInfo login) - { - user.AddLogin(login); - return Task.FromResult(0); - } - - public virtual Task RemoveLoginAsync(TUser user, UserLoginInfo login) - { - user.RemoveLogin(login); - return Task.FromResult(0); - } - - public virtual Task> GetLoginsAsync(TUser user) - { - return Task.FromResult((IList) user.Logins); - } - - public virtual Task FindAsync(UserLoginInfo login) - { - return _Users - .Find(u => u.Logins.Any(l => l.LoginProvider == login.LoginProvider && l.ProviderKey == login.ProviderKey)) - .FirstOrDefaultAsync(); - } - - public virtual Task SetSecurityStampAsync(TUser user, string stamp) - { - user.SecurityStamp = stamp; - return Task.FromResult(0); - } - - public virtual Task GetSecurityStampAsync(TUser user) - { - return Task.FromResult(user.SecurityStamp); - } - - public virtual Task GetEmailConfirmedAsync(TUser user) - { - return Task.FromResult(user.EmailConfirmed); - } - - public virtual Task SetEmailConfirmedAsync(TUser user, bool confirmed) - { - user.EmailConfirmed = confirmed; - return Task.FromResult(0); - } - - public virtual Task SetEmailAsync(TUser user, string email) - { - user.Email = email; - return Task.FromResult(0); - } - - public virtual Task GetEmailAsync(TUser user) - { - return Task.FromResult(user.Email); - } - - public virtual Task FindByEmailAsync(string email) - { - // todo what if a user can have multiple accounts with the same email? - return _Users.Find(u => u.Email == email).FirstOrDefaultAsync(); - } - - public virtual Task> GetClaimsAsync(TUser user) - { - return Task.FromResult((IList) user.Claims.Select(c => c.ToSecurityClaim()).ToList()); - } - - public virtual Task AddClaimAsync(TUser user, Claim claim) - { - user.AddClaim(claim); - return Task.FromResult(0); - } - - public virtual Task RemoveClaimAsync(TUser user, Claim claim) - { - user.RemoveClaim(claim); - return Task.FromResult(0); - } - - public virtual Task SetPhoneNumberAsync(TUser user, string phoneNumber) - { - user.PhoneNumber = phoneNumber; - return Task.FromResult(0); - } - - public virtual Task GetPhoneNumberAsync(TUser user) - { - return Task.FromResult(user.PhoneNumber); - } - - public virtual Task GetPhoneNumberConfirmedAsync(TUser user) - { - return Task.FromResult(user.PhoneNumberConfirmed); - } - - public virtual Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed) - { - user.PhoneNumberConfirmed = confirmed; - return Task.FromResult(0); - } - - public virtual Task SetTwoFactorEnabledAsync(TUser user, bool enabled) - { - user.TwoFactorEnabled = enabled; - return Task.FromResult(0); - } - - public virtual Task GetTwoFactorEnabledAsync(TUser user) - { - return Task.FromResult(user.TwoFactorEnabled); - } - - public virtual Task GetLockoutEndDateAsync(TUser user) - { - return Task.FromResult(user.LockoutEndDateUtc ?? new DateTimeOffset()); - } - - public virtual Task SetLockoutEndDateAsync(TUser user, DateTimeOffset lockoutEnd) - { - user.LockoutEndDateUtc = new DateTime(lockoutEnd.Ticks, DateTimeKind.Utc); - return Task.FromResult(0); - } - - public virtual Task IncrementAccessFailedCountAsync(TUser user) - { - user.AccessFailedCount++; - return Task.FromResult(user.AccessFailedCount); - } - - public virtual Task ResetAccessFailedCountAsync(TUser user) - { - user.AccessFailedCount = 0; - return Task.FromResult(0); - } - - public virtual Task GetAccessFailedCountAsync(TUser user) - { - return Task.FromResult(user.AccessFailedCount); - } - - public virtual Task GetLockoutEnabledAsync(TUser user) - { - return Task.FromResult(user.LockoutEnabled); - } - - public virtual Task SetLockoutEnabledAsync(TUser user, bool enabled) - { - user.LockoutEnabled = enabled; - return Task.FromResult(0); - } - - public virtual IQueryable Users => _Users.AsQueryable(); - } -} \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB/packages.config b/src/AspNet.Identity.MongoDB/packages.config deleted file mode 100644 index d661142..0000000 --- a/src/AspNet.Identity.MongoDB/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/CoreIntegrationTests/CoreIntegrationTests.csproj b/src/CoreIntegrationTests/CoreIntegrationTests.csproj new file mode 100644 index 0000000..292762b --- /dev/null +++ b/src/CoreIntegrationTests/CoreIntegrationTests.csproj @@ -0,0 +1,34 @@ + + + + net451;netcoreapp1.0 + CoreIntegrationTests + CoreIntegrationTests + true + $(PackageTargetFallback);portable-net45+win8 + 1.0.4 + false + false + false + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/IntegrationTests/EnsureWeCanExtendIdentityRoleTests.cs b/src/CoreIntegrationTests/EnsureWeCanExtendIdentityRoleTests.cs similarity index 59% rename from src/IntegrationTests/EnsureWeCanExtendIdentityRoleTests.cs rename to src/CoreIntegrationTests/EnsureWeCanExtendIdentityRoleTests.cs index d6baaac..24104e4 100644 --- a/src/IntegrationTests/EnsureWeCanExtendIdentityRoleTests.cs +++ b/src/CoreIntegrationTests/EnsureWeCanExtendIdentityRoleTests.cs @@ -1,8 +1,10 @@ namespace IntegrationTests { using System.Linq; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity; + using Microsoft.AspNetCore.Identity.MongoDB; + using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; [TestFixture] @@ -19,9 +21,8 @@ public class ExtendedIdentityRole : IdentityRole [SetUp] public void BeforeEachTestAfterBase() { - var roles = DatabaseNewApi.GetCollection("roles"); - var roleStore = new RoleStore(roles); - _Manager = new RoleManager(roleStore); + _Manager = CreateServiceProvider() + .GetService>(); _Role = new ExtendedIdentityRole { Name = "admin" @@ -29,24 +30,24 @@ public void BeforeEachTestAfterBase() } [Test] - public void Create_ExtendedRoleType_SavesExtraFields() + public async Task Create_ExtendedRoleType_SavesExtraFields() { _Role.ExtendedField = "extendedField"; - _Manager.Create(_Role); + await _Manager.CreateAsync(_Role); var savedRole = Roles.FindAllAs().Single(); Expect(savedRole.ExtendedField, Is.EqualTo("extendedField")); } [Test] - public void Create_ExtendedRoleType_ReadsExtraFields() + public async Task Create_ExtendedRoleType_ReadsExtraFields() { _Role.ExtendedField = "extendedField"; - _Manager.Create(_Role); + await _Manager.CreateAsync(_Role); - var savedRole = _Manager.FindById(_Role.Id); + var savedRole = await _Manager.FindByIdAsync(_Role.Id); Expect(savedRole.ExtendedField, Is.EqualTo("extendedField")); } } diff --git a/src/IntegrationTests/EnsureWeCanExtendIdentityUserTests.cs b/src/CoreIntegrationTests/EnsureWeCanExtendIdentityUserTests.cs similarity index 59% rename from src/IntegrationTests/EnsureWeCanExtendIdentityUserTests.cs rename to src/CoreIntegrationTests/EnsureWeCanExtendIdentityUserTests.cs index 0675414..d451074 100644 --- a/src/IntegrationTests/EnsureWeCanExtendIdentityUserTests.cs +++ b/src/CoreIntegrationTests/EnsureWeCanExtendIdentityUserTests.cs @@ -1,8 +1,10 @@ namespace IntegrationTests { using System.Linq; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity; + using Microsoft.AspNetCore.Identity.MongoDB; + using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; [TestFixture] @@ -19,9 +21,8 @@ public class ExtendedIdentityUser : IdentityUser [SetUp] public void BeforeEachTestAfterBase() { - var users = DatabaseNewApi.GetCollection("users"); - var userStore = new UserStore(users); - _Manager = new UserManager(userStore); + _Manager = CreateServiceProvider() + .GetService>(); _User = new ExtendedIdentityUser { UserName = "bob" @@ -29,24 +30,24 @@ public void BeforeEachTestAfterBase() } [Test] - public void Create_ExtendedUserType_SavesExtraFields() + public async Task Create_ExtendedUserType_SavesExtraFields() { _User.ExtendedField = "extendedField"; - _Manager.Create(_User); + await _Manager.CreateAsync(_User); var savedUser = Users.FindAllAs().Single(); Expect(savedUser.ExtendedField, Is.EqualTo("extendedField")); } [Test] - public void Create_ExtendedUserType_ReadsExtraFields() + public async Task Create_ExtendedUserType_ReadsExtraFields() { _User.ExtendedField = "extendedField"; - _Manager.Create(_User); + await _Manager.CreateAsync(_User); - var savedUser = _Manager.FindById(_User.Id); + var savedUser = await _Manager.FindByIdAsync(_User.Id); Expect(savedUser.ExtendedField, Is.EqualTo("extendedField")); } } diff --git a/src/IntegrationTests/IdentityUserTests.cs b/src/CoreIntegrationTests/IdentityUserTests.cs similarity index 87% rename from src/IntegrationTests/IdentityUserTests.cs rename to src/CoreIntegrationTests/IdentityUserTests.cs index 840ca5e..bbbe935 100644 --- a/src/IntegrationTests/IdentityUserTests.cs +++ b/src/CoreIntegrationTests/IdentityUserTests.cs @@ -1,6 +1,6 @@ namespace IntegrationTests { - using AspNet.Identity.MongoDB; + using Microsoft.AspNetCore.Identity.MongoDB; using MongoDB.Bson; using NUnit.Framework; using Tests; @@ -12,7 +12,7 @@ public class IdentityUserTests : UserIntegrationTestsBase public void Insert_NoId_SetsId() { var user = new IdentityUser(); - user.SetId(null); + user.Id = null; Users.Insert(user); diff --git a/src/CoreIntegrationTests/IndexChecksTests.cs b/src/CoreIntegrationTests/IndexChecksTests.cs new file mode 100644 index 0000000..9912a3d --- /dev/null +++ b/src/CoreIntegrationTests/IndexChecksTests.cs @@ -0,0 +1,42 @@ +namespace IntegrationTests +{ + using System; + using System.Linq; + using Microsoft.AspNetCore.Identity.MongoDB; + using MongoDB.Driver; + using NUnit.Framework; + + [TestFixture] + public class IndexChecksTests : UserIntegrationTestsBase + { + [Test] + public void EnsureUniqueIndexes() + { + EnsureUniqueIndex(IndexChecks.OptionalIndexChecks.EnsureUniqueIndexOnUserName, "UserName"); + EnsureUniqueIndex(IndexChecks.OptionalIndexChecks.EnsureUniqueIndexOnEmail, "Email"); + EnsureUniqueIndex(IndexChecks.OptionalIndexChecks.EnsureUniqueIndexOnRoleName, "Name"); + + EnsureUniqueIndex(IndexChecks.EnsureUniqueIndexOnNormalizedUserName, "NormalizedUserName"); + EnsureUniqueIndex(IndexChecks.EnsureUniqueIndexOnNormalizedEmail, "NormalizedEmail"); + EnsureUniqueIndex(IndexChecks.EnsureUniqueIndexOnNormalizedRoleName, "NormalizedName"); + } + + private void EnsureUniqueIndex(Action> addIndex, string indexedField) + { + var testCollectionName = "indextest"; + Database.DropCollection(testCollectionName); + var testCollection = DatabaseNewApi.GetCollection(testCollectionName); + + addIndex(testCollection); + + var legacyCollectionInterface = Database.GetCollection(testCollectionName); + var index = legacyCollectionInterface.GetIndexes() + .Where(i => i.IsUnique) + .Where(i => i.Key.Count() == 1) + .FirstOrDefault(i => i.Key.Contains(indexedField)); + var failureMessage = $"No unique index found on {indexedField}"; + Expect(index, Is.Not.Null, failureMessage); + Expect(index.Key.Count(), Is.EqualTo(1), failureMessage); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/Properties/AssemblyInfo.cs b/src/CoreIntegrationTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3da3074 --- /dev/null +++ b/src/CoreIntegrationTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CoreIntegrationTests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("836f4635-9b6b-4090-8cdb-9cd9f7bea829")] diff --git a/src/CoreIntegrationTests/RoleStoreTests.cs b/src/CoreIntegrationTests/RoleStoreTests.cs new file mode 100644 index 0000000..a09fd86 --- /dev/null +++ b/src/CoreIntegrationTests/RoleStoreTests.cs @@ -0,0 +1,102 @@ +namespace IntegrationTests +{ + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity.MongoDB; + using MongoDB.Bson; + using NUnit.Framework; + + [TestFixture] + public class RoleStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task Create_NewRole_Saves() + { + var roleName = "admin"; + var role = new IdentityRole(roleName); + var manager = GetRoleManager(); + + await manager.CreateAsync(role); + + var savedRole = Roles.FindAll().Single(); + Expect(savedRole.Name, Is.EqualTo(roleName)); + Expect(savedRole.NormalizedName, Is.EqualTo("ADMIN")); + } + + [Test] + public async Task FindByName_SavedRole_ReturnsRole() + { + var roleName = "name"; + var role = new IdentityRole {Name = roleName}; + var manager = GetRoleManager(); + await manager.CreateAsync(role); + + // note: also tests normalization as FindByName now uses normalization + var foundRole = await manager.FindByNameAsync(roleName); + + Expect(foundRole, Is.Not.Null); + Expect(foundRole.Name, Is.EqualTo(roleName)); + } + + [Test] + public async Task FindById_SavedRole_ReturnsRole() + { + var roleId = ObjectId.GenerateNewId().ToString(); + var role = new IdentityRole {Name = "name"}; + role.Id = roleId; + var manager = GetRoleManager(); + await manager.CreateAsync(role); + + var foundRole = await manager.FindByIdAsync(roleId); + + Expect(foundRole, Is.Not.Null); + Expect(foundRole.Id, Is.EqualTo(roleId)); + } + + [Test] + public async Task Delete_ExistingRole_Removes() + { + var role = new IdentityRole {Name = "name"}; + var manager = GetRoleManager(); + await manager.CreateAsync(role); + Expect(Roles.FindAll(), Is.Not.Empty); + + await manager.DeleteAsync(role); + + Expect(Roles.FindAll(), Is.Empty); + } + + [Test] + public async Task Update_ExistingRole_Updates() + { + var role = new IdentityRole {Name = "name"}; + var manager = GetRoleManager(); + await manager.CreateAsync(role); + var savedRole = await manager.FindByIdAsync(role.Id); + savedRole.Name = "newname"; + + await manager.UpdateAsync(savedRole); + + var changedRole = Roles.FindAll().Single(); + Expect(changedRole, Is.Not.Null); + Expect(changedRole.Name, Is.EqualTo("newname")); + } + + [Test] + public async Task SimpleAccessorsAndGetters() + { + var role = new IdentityRole + { + Name = "name" + }; + var manager = GetRoleManager(); + await manager.CreateAsync(role); + + Expect(await manager.GetRoleIdAsync(role), Is.EqualTo(role.Id)); + Expect(await manager.GetRoleNameAsync(role), Is.EqualTo("name")); + + await manager.SetRoleNameAsync(role, "newName"); + Expect(await manager.GetRoleNameAsync(role), Is.EqualTo("newName")); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserAuthenticationTokenStoreTests.cs b/src/CoreIntegrationTests/UserAuthenticationTokenStoreTests.cs new file mode 100644 index 0000000..487af66 --- /dev/null +++ b/src/CoreIntegrationTests/UserAuthenticationTokenStoreTests.cs @@ -0,0 +1,28 @@ +namespace CoreIntegrationTests +{ + using System.Threading.Tasks; + using IntegrationTests; + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + + public class UserAuthenticationTokenStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task SetGetAndRemoveTokens() + { + // note: this is just an integration test, testing of IdentityUser behavior is in domain/unit tests + var user = new IdentityUser(); + var manager = GetUserManager(); + await manager.CreateAsync(user); + + await manager.SetAuthenticationTokenAsync(user, "loginProvider", "tokenName", "tokenValue"); + + var tokenValue = await manager.GetAuthenticationTokenAsync(user, "loginProvider", "tokenName"); + Expect(tokenValue, Is.EqualTo("tokenValue")); + + await manager.RemoveAuthenticationTokenAsync(user, "loginProvider", "tokenName"); + var afterRemovedValue = await manager.GetAuthenticationTokenAsync(user, "loginProvider", "tokenName"); + Expect(afterRemovedValue, Is.Null); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserClaimStoreTests.cs b/src/CoreIntegrationTests/UserClaimStoreTests.cs new file mode 100644 index 0000000..fe8adff --- /dev/null +++ b/src/CoreIntegrationTests/UserClaimStoreTests.cs @@ -0,0 +1,120 @@ +namespace IntegrationTests +{ + using System.Linq; + using System.Security.Claims; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + using Tests; + + [TestFixture] + public class UserClaimStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task Create_NewUser_HasNoClaims() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + var claims = await manager.GetClaimsAsync(user); + + Expect(claims, Is.Empty); + } + + [Test] + public async Task AddClaim_ReturnsClaim() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + await manager.AddClaimAsync(user, new Claim("type", "value")); + + var claim = (await manager.GetClaimsAsync(user)).Single(); + Expect(claim.Type, Is.EqualTo("type")); + Expect(claim.Value, Is.EqualTo("value")); + } + + [Test] + public async Task RemoveClaim_RemovesExistingClaim() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + await manager.AddClaimAsync(user, new Claim("type", "value")); + + await manager.RemoveClaimAsync(user, new Claim("type", "value")); + + Expect(await manager.GetClaimsAsync(user), Is.Empty); + } + + [Test] + public async Task RemoveClaim_DifferentType_DoesNotRemoveClaim() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + await manager.AddClaimAsync(user, new Claim("type", "value")); + + await manager.RemoveClaimAsync(user, new Claim("otherType", "value")); + + Expect(await manager.GetClaimsAsync(user), Is.Not.Empty); + } + + [Test] + public async Task RemoveClaim_DifferentValue_DoesNotRemoveClaim() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + await manager.AddClaimAsync(user, new Claim("type", "value")); + + await manager.RemoveClaimAsync(user, new Claim("type", "otherValue")); + + Expect(await manager.GetClaimsAsync(user), Is.Not.Empty); + } + + [Test] + public async Task ReplaceClaim_Replaces() + { + // note: unit tests cover behavior of ReplaceClaim method on IdentityUser + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + var existingClaim = new Claim("type", "value"); + await manager.AddClaimAsync(user, existingClaim); + var newClaim = new Claim("newType", "newValue"); + + await manager.ReplaceClaimAsync(user, existingClaim, newClaim); + + user.ExpectOnlyHasThisClaim(newClaim); + } + + [Test] + public async Task GetUsersForClaim() + { + var userWithClaim = new IdentityUser + { + UserName = "with" + }; + var userWithout = new IdentityUser(); + var manager = GetUserManager(); + await manager.CreateAsync(userWithClaim); + await manager.CreateAsync(userWithout); + var claim = new Claim("sameType", "sameValue"); + await manager.AddClaimAsync(userWithClaim, claim); + + var matchedUsers = await manager.GetUsersForClaimAsync(claim); + + Expect(matchedUsers.Count, Is.EqualTo(1)); + Expect(matchedUsers.Single().UserName, Is.EqualTo("with")); + + var matchesForWrongType = await manager.GetUsersForClaimAsync(new Claim("wrongType", "sameValue")); + Expect(matchesForWrongType, Is.Empty, "Users with claim with wrongType should not be returned but were."); + + var matchesForWrongValue = await manager.GetUsersForClaimAsync(new Claim("sameType", "wrongValue")); + Expect(matchesForWrongValue, Is.Empty, "Users with claim with wrongValue should not be returned but were."); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserEmailStoreTests.cs b/src/CoreIntegrationTests/UserEmailStoreTests.cs new file mode 100644 index 0000000..5dc49e8 --- /dev/null +++ b/src/CoreIntegrationTests/UserEmailStoreTests.cs @@ -0,0 +1,88 @@ +namespace IntegrationTests +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + + [TestFixture] + public class UserEmailStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task Create_NewUser_HasNoEmail() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + var email = await manager.GetEmailAsync(user); + + Expect(email, Is.Null); + } + + [Test] + public async Task SetEmail_SetsEmail() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + await manager.SetEmailAsync(user, "email"); + + Expect(await manager.GetEmailAsync(user), Is.EqualTo("email")); + } + + [Test] + public async Task FindUserByEmail_ReturnsUser() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + Expect(await manager.FindByEmailAsync("email"), Is.Null); + + await manager.SetEmailAsync(user, "email"); + + Expect(await manager.FindByEmailAsync("email"), Is.Not.Null); + } + + [Test] + public async Task Create_NewUser_IsNotEmailConfirmed() + { + var manager = GetUserManager(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + + var isConfirmed = await manager.IsEmailConfirmedAsync(user); + + Expect(isConfirmed, Is.False); + } + + [Test] + public async Task SetEmailConfirmed_IsConfirmed() + { + var manager = GetUserManager(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + var token = await manager.GenerateEmailConfirmationTokenAsync(user); + + await manager.ConfirmEmailAsync(user, token); + + var isConfirmed = await manager.IsEmailConfirmedAsync(user); + Expect(isConfirmed); + } + + [Test] + public async Task ChangeEmail_AfterConfirmedOriginalEmail_NotEmailConfirmed() + { + var manager = GetUserManager(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + var token = await manager.GenerateEmailConfirmationTokenAsync(user); + await manager.ConfirmEmailAsync(user, token); + + await manager.SetEmailAsync(user, "new@email.com"); + + var isConfirmed = await manager.IsEmailConfirmedAsync(user); + Expect(isConfirmed, Is.False); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserIntegrationTestsBase.cs b/src/CoreIntegrationTests/UserIntegrationTestsBase.cs new file mode 100644 index 0000000..ff819cb --- /dev/null +++ b/src/CoreIntegrationTests/UserIntegrationTestsBase.cs @@ -0,0 +1,62 @@ +namespace IntegrationTests +{ + using System; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Identity; + using Microsoft.AspNetCore.Identity.MongoDB; + using Microsoft.Extensions.DependencyInjection; + using MongoDB.Driver; + using NUnit.Framework; + + public class UserIntegrationTestsBase : AssertionHelper + { + protected MongoDatabase Database; + protected MongoCollection Users; + protected MongoCollection Roles; + + // note: for now we'll have interfaces to both the new and old apis for MongoDB, that way we don't have to update all the tests at once and risk introducing bugs + protected IMongoDatabase DatabaseNewApi; + protected IServiceProvider ServiceProvider; + private readonly string _TestingConnectionString = $"mongodb://localhost:27017/{IdentityTesting}"; + private const string IdentityTesting = "identity-testing"; + + [SetUp] + public void BeforeEachTest() + { + var client = new MongoClient(_TestingConnectionString); + + // todo move away from GetServer which could be deprecated at some point + Database = client.GetServer().GetDatabase(IdentityTesting); + Users = Database.GetCollection("users"); + Roles = Database.GetCollection("roles"); + + DatabaseNewApi = client.GetDatabase(IdentityTesting); + + Database.DropCollection("users"); + Database.DropCollection("roles"); + + ServiceProvider = CreateServiceProvider(); + } + + protected UserManager GetUserManager() + => ServiceProvider.GetService>(); + + protected RoleManager GetRoleManager() + => ServiceProvider.GetService>(); + + protected IServiceProvider CreateServiceProvider(Action optionsProvider = null) + where TUser : IdentityUser + where TRole : IdentityRole + { + var services = new ServiceCollection(); + optionsProvider = optionsProvider ?? (options => { }); + services.AddIdentity(optionsProvider) + .AddDefaultTokenProviders() + .RegisterMongoStores(_TestingConnectionString); + + services.AddLogging(); + + return services.BuildServiceProvider(); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserLockoutStoreTests.cs b/src/CoreIntegrationTests/UserLockoutStoreTests.cs new file mode 100644 index 0000000..8e99159 --- /dev/null +++ b/src/CoreIntegrationTests/UserLockoutStoreTests.cs @@ -0,0 +1,102 @@ +namespace IntegrationTests +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity; + using Microsoft.AspNetCore.Identity.MongoDB; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + // todo low - validate all tests work + [TestFixture] + public class UserLockoutStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task AccessFailed_IncrementsAccessFailedCount() + { + var manager = GetUserManagerWithThreeMaxAccessAttempts(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + + await manager.AccessFailedAsync(user); + + Expect(await manager.GetAccessFailedCountAsync(user), Is.EqualTo(1)); + } + + private UserManager GetUserManagerWithThreeMaxAccessAttempts() + { + return CreateServiceProvider(options => options.Lockout.MaxFailedAccessAttempts = 3) + .GetService>(); + } + + [Test] + public void IncrementAccessFailedCount_ReturnsNewCount() + { + var store = new UserStore(null); + var user = new IdentityUser {UserName = "bob"}; + + var count = store.IncrementAccessFailedCountAsync(user, default(CancellationToken)); + + Expect(count.Result, Is.EqualTo(1)); + } + + [Test] + public async Task ResetAccessFailed_AfterAnAccessFailed_SetsToZero() + { + var manager = GetUserManagerWithThreeMaxAccessAttempts(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + await manager.AccessFailedAsync(user); + + await manager.ResetAccessFailedCountAsync(user); + + Expect(await manager.GetAccessFailedCountAsync(user), Is.EqualTo(0)); + } + + [Test] + public async Task AccessFailed_NotOverMaxFailures_NoLockoutEndDate() + { + var manager = GetUserManagerWithThreeMaxAccessAttempts(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + + await manager.AccessFailedAsync(user); + + Expect(await manager.GetLockoutEndDateAsync(user), Is.Null); + } + + [Test] + public async Task AccessFailed_ExceedsMaxFailedAccessAttempts_LocksAccount() + { + var manager = CreateServiceProvider(options => + { + options.Lockout.MaxFailedAccessAttempts = 0; + options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + }) + .GetService>(); + + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + + await manager.AccessFailedAsync(user); + + var lockoutEndDate = await manager.GetLockoutEndDateAsync(user); + Expect(lockoutEndDate?.Subtract(DateTime.UtcNow).TotalHours, Is.GreaterThan(0.9).And.LessThan(1.1)); + } + + [Test] + public async Task SetLockoutEnabled() + { + var manager = GetUserManager(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + + await manager.SetLockoutEnabledAsync(user, true); + Expect(await manager.GetLockoutEnabledAsync(user)); + + await manager.SetLockoutEnabledAsync(user, false); + Expect(await manager.GetLockoutEnabledAsync(user), Is.False); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserLoginStoreTests.cs b/src/CoreIntegrationTests/UserLoginStoreTests.cs new file mode 100644 index 0000000..5d987d5 --- /dev/null +++ b/src/CoreIntegrationTests/UserLoginStoreTests.cs @@ -0,0 +1,103 @@ +namespace IntegrationTests +{ + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity; + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + + // todo low - validate all tests work + [TestFixture] + public class UserLoginStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task AddLogin_NewLogin_Adds() + { + var manager = GetUserManager(); + var login = new UserLoginInfo("provider", "key", "name"); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + + await manager.AddLoginAsync(user, login); + + var savedLogin = Users.FindAll().Single().Logins.Single(); + Expect(savedLogin.LoginProvider, Is.EqualTo("provider")); + Expect(savedLogin.ProviderKey, Is.EqualTo("key")); + Expect(savedLogin.ProviderDisplayName, Is.EqualTo("name")); + } + + [Test] + public async Task RemoveLogin_NewLogin_Removes() + { + var manager = GetUserManager(); + var login = new UserLoginInfo("provider", "key", "name"); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + await manager.AddLoginAsync(user, login); + + await manager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey); + + var savedUser = Users.FindAll().Single(); + Expect(savedUser.Logins, Is.Empty); + } + + [Test] + public async Task GetLogins_OneLogin_ReturnsLogin() + { + var manager = GetUserManager(); + var login = new UserLoginInfo("provider", "key", "name"); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + await manager.AddLoginAsync(user, login); + + var logins = await manager.GetLoginsAsync(user); + + var savedLogin = logins.Single(); + Expect(savedLogin.LoginProvider, Is.EqualTo("provider")); + Expect(savedLogin.ProviderKey, Is.EqualTo("key")); + Expect(savedLogin.ProviderDisplayName, Is.EqualTo("name")); + } + + [Test] + public async Task Find_UserWithLogin_FindsUser() + { + var manager = GetUserManager(); + var login = new UserLoginInfo("provider", "key", "name"); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + await manager.AddLoginAsync(user, login); + + var findUser = await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey); + + Expect(findUser, Is.Not.Null); + } + + [Test] + public async Task Find_UserWithDifferentKey_DoesNotFindUser() + { + var manager = GetUserManager(); + var login = new UserLoginInfo("provider", "key", "name"); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + await manager.AddLoginAsync(user, login); + + var findUser = await manager.FindByLoginAsync("provider", "otherkey"); + + Expect(findUser, Is.Null); + } + + [Test] + public async Task Find_UserWithDifferentProvider_DoesNotFindUser() + { + var manager = GetUserManager(); + var login = new UserLoginInfo("provider", "key", "name"); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + await manager.AddLoginAsync(user, login); + + var findUser = await manager.FindByLoginAsync("otherprovider", "key"); + + Expect(findUser, Is.Null); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserPasswordStoreTests.cs b/src/CoreIntegrationTests/UserPasswordStoreTests.cs new file mode 100644 index 0000000..c58d271 --- /dev/null +++ b/src/CoreIntegrationTests/UserPasswordStoreTests.cs @@ -0,0 +1,62 @@ +namespace IntegrationTests +{ + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity; + using Microsoft.AspNetCore.Identity.MongoDB; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + // todo low - validate all tests work + [TestFixture] + public class UserPasswordStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task HasPassword_NoPassword_ReturnsFalse() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + var hasPassword = await manager.HasPasswordAsync(user); + + Expect(hasPassword, Is.False); + } + + [Test] + public async Task AddPassword_NewPassword_CanFindUserByPassword() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = CreateServiceProvider(options => + { + options.Password.RequireDigit = false; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequireUppercase = false; + }) + .GetService>(); + await manager.CreateAsync(user); + + var result = await manager.AddPasswordAsync(user, "testtest"); + Expect(result.Succeeded, Is.True); + + var userByName = await manager.FindByNameAsync("bob"); + Expect(userByName, Is.Not.Null); + var passwordIsValid = await manager.CheckPasswordAsync(userByName, "testtest"); + Expect(passwordIsValid, Is.True); + } + + [Test] + public async Task RemovePassword_UserWithPassword_SetsPasswordNull() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + await manager.AddPasswordAsync(user, "testtest"); + + await manager.RemovePasswordAsync(user); + + var savedUser = Users.FindAll().Single(); + Expect(savedUser.PasswordHash, Is.Null); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserPhoneNumberStoreTests.cs b/src/CoreIntegrationTests/UserPhoneNumberStoreTests.cs new file mode 100644 index 0000000..9e8fed9 --- /dev/null +++ b/src/CoreIntegrationTests/UserPhoneNumberStoreTests.cs @@ -0,0 +1,52 @@ +namespace IntegrationTests +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + + // todo low - validate all tests work + [TestFixture] + public class UserPhoneNumberStoreTests : UserIntegrationTestsBase + { + private const string PhoneNumber = "1234567890"; + + [Test] + public async Task SetPhoneNumber_StoresPhoneNumber() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + await manager.SetPhoneNumberAsync(user, PhoneNumber); + + Expect(await manager.GetPhoneNumberAsync(user), Is.EqualTo(PhoneNumber)); + } + + [Test] + public async Task ConfirmPhoneNumber_StoresPhoneNumberConfirmed() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + var token = await manager.GenerateChangePhoneNumberTokenAsync(user, PhoneNumber); + + await manager.ChangePhoneNumberAsync(user, PhoneNumber, token); + + Expect(await manager.IsPhoneNumberConfirmedAsync(user)); + } + + [Test] + public async Task ChangePhoneNumber_OriginalPhoneNumberWasConfirmed_NotPhoneNumberConfirmed() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + var token = await manager.GenerateChangePhoneNumberTokenAsync(user, PhoneNumber); + await manager.ChangePhoneNumberAsync(user, PhoneNumber, token); + + await manager.SetPhoneNumberAsync(user, PhoneNumber); + + Expect(await manager.IsPhoneNumberConfirmedAsync(user), Is.False); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserRoleStoreTests.cs b/src/CoreIntegrationTests/UserRoleStoreTests.cs new file mode 100644 index 0000000..bdf4927 --- /dev/null +++ b/src/CoreIntegrationTests/UserRoleStoreTests.cs @@ -0,0 +1,73 @@ +namespace IntegrationTests +{ + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + + // todo low - validate all tests work + [TestFixture] + public class UserRoleStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task GetRoles_UserHasNoRoles_ReturnsNoRoles() + { + var manager = GetUserManager(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + + var roles = await manager.GetRolesAsync(user); + + Expect(roles, Is.Empty); + } + + [Test] + public async Task AddRole_Adds() + { + var manager = GetUserManager(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + + await manager.AddToRoleAsync(user, "role"); + + var savedUser = Users.FindAll().Single(); + // note: addToRole now passes a normalized role name + Expect(savedUser.Roles, Is.EquivalentTo(new[] {"ROLE"})); + Expect(await manager.IsInRoleAsync(user, "role"), Is.True); + } + + [Test] + public async Task RemoveRole_Removes() + { + var manager = GetUserManager(); + var user = new IdentityUser {UserName = "bob"}; + await manager.CreateAsync(user); + await manager.AddToRoleAsync(user, "role"); + + await manager.RemoveFromRoleAsync(user, "role"); + + var savedUser = Users.FindAll().Single(); + Expect(savedUser.Roles, Is.Empty); + Expect(await manager.IsInRoleAsync(user, "role"), Is.False); + } + + [Test] + public async Task GetUsersInRole_FiltersOnRole() + { + var roleA = "roleA"; + var roleB = "roleB"; + var userInA = new IdentityUser {UserName = "nameA"}; + var userInB = new IdentityUser {UserName = "nameB"}; + var manager = GetUserManager(); + await manager.CreateAsync(userInA); + await manager.CreateAsync(userInB); + await manager.AddToRoleAsync(userInA, roleA); + await manager.AddToRoleAsync(userInB, roleB); + + var matchedUsers = await manager.GetUsersInRoleAsync("roleA"); + + Expect(matchedUsers.Count, Is.EqualTo(1)); + Expect(matchedUsers.First().UserName, Is.EqualTo("nameA")); + } + } +} \ No newline at end of file diff --git a/src/IntegrationTests/UserSecurityStampStoreTests.cs b/src/CoreIntegrationTests/UserSecurityStampStoreTests.cs similarity index 61% rename from src/IntegrationTests/UserSecurityStampStoreTests.cs rename to src/CoreIntegrationTests/UserSecurityStampStoreTests.cs index 12a6d05..b02e1de 100644 --- a/src/IntegrationTests/UserSecurityStampStoreTests.cs +++ b/src/CoreIntegrationTests/UserSecurityStampStoreTests.cs @@ -1,33 +1,33 @@ namespace IntegrationTests { using System.Linq; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity.MongoDB; using NUnit.Framework; [TestFixture] public class UserSecurityStampStoreTests : UserIntegrationTestsBase { [Test] - public void Create_NewUser_HasSecurityStamp() + public async Task Create_NewUser_HasSecurityStamp() { var manager = GetUserManager(); var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); + await manager.CreateAsync(user); var savedUser = Users.FindAll().Single(); Expect(savedUser.SecurityStamp, Is.Not.Null); } [Test] - public void GetSecurityStamp_NewUser_ReturnsStamp() + public async Task GetSecurityStamp_NewUser_ReturnsStamp() { var manager = GetUserManager(); var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); + await manager.CreateAsync(user); - var stamp = manager.GetSecurityStamp(user.Id); + var stamp = await manager.GetSecurityStampAsync(user); Expect(stamp, Is.Not.Null); } diff --git a/src/CoreIntegrationTests/UserStoreTests.cs b/src/CoreIntegrationTests/UserStoreTests.cs new file mode 100644 index 0000000..dfabf17 --- /dev/null +++ b/src/CoreIntegrationTests/UserStoreTests.cs @@ -0,0 +1,131 @@ +namespace IntegrationTests +{ + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity.MongoDB; + using MongoDB.Bson; + using NUnit.Framework; + + // todo low - validate all tests work + [TestFixture] + public class UserStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task Create_NewUser_Saves() + { + var userName = "name"; + var user = new IdentityUser {UserName = userName}; + var manager = GetUserManager(); + + await manager.CreateAsync(user); + + var savedUser = Users.FindAll().Single(); + Expect(savedUser.UserName, Is.EqualTo(user.UserName)); + } + + [Test] + public async Task FindByName_SavedUser_ReturnsUser() + { + var userName = "name"; + var user = new IdentityUser {UserName = userName}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + var foundUser = await manager.FindByNameAsync(userName); + + Expect(foundUser, Is.Not.Null); + Expect(foundUser.UserName, Is.EqualTo(userName)); + } + + [Test] + public async Task FindByName_NoUser_ReturnsNull() + { + var manager = GetUserManager(); + + var foundUser = await manager.FindByNameAsync("nouserbyname"); + + Expect(foundUser, Is.Null); + } + + [Test] + public async Task FindById_SavedUser_ReturnsUser() + { + var userId = ObjectId.GenerateNewId().ToString(); + var user = new IdentityUser {UserName = "name"}; + user.Id = userId; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + var foundUser = await manager.FindByIdAsync(userId); + + Expect(foundUser, Is.Not.Null); + Expect(foundUser.Id, Is.EqualTo(userId)); + } + + [Test] + public async Task FindById_NoUser_ReturnsNull() + { + var manager = GetUserManager(); + + var foundUser = await manager.FindByIdAsync(ObjectId.GenerateNewId().ToString()); + + Expect(foundUser, Is.Null); + } + + [Test] + public async Task FindById_IdIsNotAnObjectId_ReturnsNull() + { + var manager = GetUserManager(); + + var foundUser = await manager.FindByIdAsync("notanobjectid"); + + Expect(foundUser, Is.Null); + } + + [Test] + public async Task Delete_ExistingUser_Removes() + { + var user = new IdentityUser {UserName = "name"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + Expect(Users.FindAll(), Is.Not.Empty); + + await manager.DeleteAsync(user); + + Expect(Users.FindAll(), Is.Empty); + } + + [Test] + public async Task Update_ExistingUser_Updates() + { + var user = new IdentityUser {UserName = "name"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + var savedUser = await manager.FindByIdAsync(user.Id); + savedUser.UserName = "newname"; + + await manager.UpdateAsync(savedUser); + + var changedUser = Users.FindAll().Single(); + Expect(changedUser, Is.Not.Null); + Expect(changedUser.UserName, Is.EqualTo("newname")); + } + + [Test] + public async Task SimpleAccessorsAndGetters() + { + var user = new IdentityUser + { + UserName = "username" + }; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + Expect(await manager.GetUserIdAsync(user), Is.EqualTo(user.Id)); + Expect(await manager.GetUserNameAsync(user), Is.EqualTo("username")); + + await manager.SetUserNameAsync(user, "newUserName"); + Expect(await manager.GetUserNameAsync(user), Is.EqualTo("newUserName")); + } + } +} \ No newline at end of file diff --git a/src/CoreIntegrationTests/UserTwoFactorStoreTests.cs b/src/CoreIntegrationTests/UserTwoFactorStoreTests.cs new file mode 100644 index 0000000..fd12b4a --- /dev/null +++ b/src/CoreIntegrationTests/UserTwoFactorStoreTests.cs @@ -0,0 +1,36 @@ +namespace IntegrationTests +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + + // todo low - validate all tests work + [TestFixture] + public class UserTwoFactorStoreTests : UserIntegrationTestsBase + { + [Test] + public async Task SetTwoFactorEnabled() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + + await manager.SetTwoFactorEnabledAsync(user, true); + + Expect(await manager.GetTwoFactorEnabledAsync(user)); + } + + [Test] + public async Task ClearTwoFactorEnabled_PreviouslyEnabled_NotEnabled() + { + var user = new IdentityUser {UserName = "bob"}; + var manager = GetUserManager(); + await manager.CreateAsync(user); + await manager.SetTwoFactorEnabledAsync(user, true); + + await manager.SetTwoFactorEnabledAsync(user, false); + + Expect(await manager.GetTwoFactorEnabledAsync(user), Is.False); + } + } +} \ No newline at end of file diff --git a/src/CoreTests/CoreTests.csproj b/src/CoreTests/CoreTests.csproj new file mode 100644 index 0000000..b612c1e --- /dev/null +++ b/src/CoreTests/CoreTests.csproj @@ -0,0 +1,32 @@ + + + + net451;netcoreapp1.0 + CoreTests + CoreTests + true + $(PackageTargetFallback);portable-net45+win8 + 1.0.4 + false + false + false + + + + + + + + + + + + + + + + + + + + diff --git a/src/Tests/IdentityRoleTests.cs b/src/CoreTests/IdentityRoleTests.cs similarity index 92% rename from src/Tests/IdentityRoleTests.cs rename to src/CoreTests/IdentityRoleTests.cs index 9c21a52..1e9513f 100644 --- a/src/Tests/IdentityRoleTests.cs +++ b/src/CoreTests/IdentityRoleTests.cs @@ -1,9 +1,10 @@ namespace Tests { - using AspNet.Identity.MongoDB; + using Microsoft.AspNetCore.Identity.MongoDB; using MongoDB.Bson; using NUnit.Framework; + // todo low - validate all tests work [TestFixture] public class IdentityRoleTests : AssertionHelper { @@ -11,7 +12,6 @@ public class IdentityRoleTests : AssertionHelper public void ToBsonDocument_IdAssigned_MapsToBsonObjectId() { var role = new IdentityRole(); - role.SetId(ObjectId.GenerateNewId().ToString()); var document = role.ToBsonDocument(); diff --git a/src/CoreTests/IdentityUserAuthenticationTokenTests.cs b/src/CoreTests/IdentityUserAuthenticationTokenTests.cs new file mode 100644 index 0000000..d774990 --- /dev/null +++ b/src/CoreTests/IdentityUserAuthenticationTokenTests.cs @@ -0,0 +1,66 @@ +namespace CoreTests +{ + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + + public class IdentityUserAuthenticationTokenTests : AssertionHelper + { + [Test] + public void GetToken_NoTokens_ReturnsNull() + { + var user = new IdentityUser(); + + var value = user.GetTokenValue("loginProvider", "tokenName"); + + Expect(value, Is.Null); + } + + [Test] + public void GetToken_WithToken_ReturnsValueIfProviderAndNameMatch() + { + var user = new IdentityUser(); + user.SetToken("loginProvider", "tokenName", "tokenValue"); + + Expect(user.GetTokenValue("loginProvider", "tokenName"), + Is.EqualTo("tokenValue"), "GetToken should match on both provider and name, but isn't"); + + Expect(user.GetTokenValue("wrongProvider", "tokenName"), + Is.Null, "GetToken should match on loginProvider, but isn't"); + + Expect(user.GetTokenValue("loginProvider", "wrongName"), + Is.Null, "GetToken should match on tokenName, but isn't"); + } + + [Test] + public void RemoveToken_OnlyRemovesIfNameAndProviderMatch() + { + var user = new IdentityUser(); + user.SetToken("loginProvider", "tokenName", "tokenValue"); + + user.RemoveToken("wrongProvider", "tokenName"); + Expect(user.GetTokenValue("loginProvider", "tokenName"), + Is.EqualTo("tokenValue"), "RemoveToken should match on loginProvider, but isn't"); + + user.RemoveToken("loginProvider", "wrongName"); + Expect(user.GetTokenValue("loginProvider", "tokenName"), + Is.EqualTo("tokenValue"), "RemoveToken should match on tokenName, but isn't"); + + user.RemoveToken("loginProvider", "tokenName"); + Expect(user.GetTokenValue("loginProvider", "tokenName"), + Is.Null, "RemoveToken should match on both loginProvider and tokenName, but isn't"); + } + + [Test] + public void SetToken_ReplacesValue() + { + var user = new IdentityUser(); + user.SetToken("loginProvider", "tokenName", "tokenValue"); + + user.SetToken("loginProvider", "tokenName", "updatedValue"); + + Expect(user.Tokens.Count, Is.EqualTo(1)); + Expect(user.GetTokenValue("loginProvider", "tokenName"), + Is.EqualTo("updatedValue")); + } + } +} \ No newline at end of file diff --git a/src/CoreTests/IdentityUserClaimTests.cs b/src/CoreTests/IdentityUserClaimTests.cs new file mode 100644 index 0000000..e6dc137 --- /dev/null +++ b/src/CoreTests/IdentityUserClaimTests.cs @@ -0,0 +1,83 @@ +namespace Tests +{ + using System.Security.Claims; + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + + [TestFixture] + public class IdentityUserClaimTests : AssertionHelper + { + [Test] + public void Create_FromClaim_SetsTypeAndValue() + { + var claim = new Claim("type", "value"); + + var userClaim = new IdentityUserClaim(claim); + + Expect(userClaim.Type, Is.EqualTo("type")); + Expect(userClaim.Value, Is.EqualTo("value")); + } + + [Test] + public void ToSecurityClaim_SetsTypeAndValue() + { + var userClaim = new IdentityUserClaim {Type = "t", Value = "v"}; + + var claim = userClaim.ToSecurityClaim(); + + Expect(claim.Type, Is.EqualTo("t")); + Expect(claim.Value, Is.EqualTo("v")); + } + + [Test] + public void ReplaceClaim_NoExistingClaim_Ignores() + { + // note: per EF implemention - only existing claims are updated by looping through them so that impl ignores too + var user = new IdentityUser(); + var newClaim = new Claim("newType", "newValue"); + + user.ReplaceClaim(newClaim, newClaim); + + Expect(user.Claims, Is.Empty); + } + + [Test] + public void ReplaceClaim_ExistingClaim_Replaces() + { + var user = new IdentityUser(); + var firstClaim = new Claim("type", "value"); + user.AddClaim(firstClaim); + var newClaim = new Claim("newType", "newValue"); + + user.ReplaceClaim(firstClaim, newClaim); + + user.ExpectOnlyHasThisClaim(newClaim); + } + + [Test] + public void ReplaceClaim_ValueMatchesButTypeDoesNot_DoesNotReplace() + { + var user = new IdentityUser(); + var firstClaim = new Claim("type", "sameValue"); + user.AddClaim(firstClaim); + var newClaim = new Claim("newType", "sameValue"); + + user.ReplaceClaim(new Claim("wrongType", "sameValue"), newClaim); + + user.ExpectOnlyHasThisClaim(firstClaim); + } + + [Test] + public void ReplaceClaim_TypeMatchesButValueDoesNot_DoesNotReplace() + { + var user = new IdentityUser(); + var firstClaim = new Claim("sameType", "value"); + user.AddClaim(firstClaim); + var newClaim = new Claim("sameType", "newValue"); + + user.ReplaceClaim(new Claim("sameType", "wrongValue"), newClaim); + + user.ExpectOnlyHasThisClaim(firstClaim); + } + } +} \ No newline at end of file diff --git a/src/Tests/IdentityUserTests.cs b/src/CoreTests/IdentityUserTests.cs similarity index 57% rename from src/Tests/IdentityUserTests.cs rename to src/CoreTests/IdentityUserTests.cs index 97ba772..3f1a59b 100644 --- a/src/Tests/IdentityUserTests.cs +++ b/src/CoreTests/IdentityUserTests.cs @@ -1,10 +1,10 @@ namespace Tests { - using System; - using AspNet.Identity.MongoDB; + using Microsoft.AspNetCore.Identity.MongoDB; using MongoDB.Bson; using NUnit.Framework; + // todo low - validate all tests work [TestFixture] public class IdentityUserTests : AssertionHelper { @@ -12,7 +12,6 @@ public class IdentityUserTests : AssertionHelper public void ToBsonDocument_IdAssigned_MapsToBsonObjectId() { var user = new IdentityUser(); - user.SetId(ObjectId.GenerateNewId().ToString()); var document = user.ToBsonDocument(); @@ -42,65 +41,32 @@ public void Create_NoPassword_DoesNotSerializePasswordField() } [Test] - public void Create_NewIdentityUser_RolesNotNull() - { - var user = new IdentityUser(); - - Expect(user.Roles, Is.Not.Null); - } - - [Test] - public void Create_NullRoles_DoesNotSerializeRoles() + public void Create_NullLists_DoesNotSerializeNullLists() { // serialized nulls can cause havoc in deserialization, overwriting the constructor's initial empty list var user = new IdentityUser(); user.Roles = null; - - var document = user.ToBsonDocument(); - - Expect(document.Contains("Roles"), Is.False); - } - - // todo consider if we want to not serialize the empty Roles array, also empty Logins array - - [Test] - public void Create_NewIdentityUser_LoginsNotNull() - { - var user = new IdentityUser(); - - Expect(user.Logins, Is.Not.Null); - } - - [Test] - public void Create_NullLogins_DoesNotSerializeLogins() - { - // serialized nulls can cause havoc in deserialization, overwriting the constructor's initial empty list - var user = new IdentityUser(); + user.Tokens = null; user.Logins = null; + user.Claims = null; var document = user.ToBsonDocument(); + Expect(document.Contains("Roles"), Is.False); + Expect(document.Contains("Tokens"), Is.False); Expect(document.Contains("Logins"), Is.False); + Expect(document.Contains("Claims"), Is.False); } [Test] - public void Create_NewIdentityUser_ClaimsNotNull() - { - var user = new IdentityUser(); - - Expect(user.Claims, Is.Not.Null); - } - - [Test] - public void Create_NullClaims_DoesNotSerializeClaims() + public void Create_NewIdentityUser_ListsNotNull() { - // serialized nulls can cause havoc in deserialization, overwriting the constructor's initial empty list var user = new IdentityUser(); - user.Claims = null; - - var document = user.ToBsonDocument(); - Expect(document.Contains("Claims"), Is.False); + Expect(user.Logins, Is.Empty); + Expect(user.Tokens, Is.Empty); + Expect(user.Roles, Is.Empty); + Expect(user.Claims, Is.Empty); } } } \ No newline at end of file diff --git a/src/CoreTests/MongoIdentityBuilderExtensionsTests.cs b/src/CoreTests/MongoIdentityBuilderExtensionsTests.cs new file mode 100644 index 0000000..0da23da --- /dev/null +++ b/src/CoreTests/MongoIdentityBuilderExtensionsTests.cs @@ -0,0 +1,105 @@ +namespace CoreTests +{ + using Microsoft.AspNetCore.Identity; + using Microsoft.AspNetCore.Identity.MongoDB; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class MongoIdentityBuilderExtensionsTests : AssertionHelper + { + private const string FakeConnectionStringWithDatabase = "mongodb://fakehost:27017/database"; + + [Test] + public void AddMongoStores_WithDefaultTypes_ResolvesStoresAndManagers() + { + var services = new ServiceCollection(); + services.AddIdentityWithMongoStores(FakeConnectionStringWithDatabase); + // note: UserManager and RoleManager use logging + services.AddLogging(); + + var provider = services.BuildServiceProvider(); + var resolvedUserStore = provider.GetService>(); + Expect(resolvedUserStore, Is.Not.Null, "User store did not resolve"); + + var resolvedRoleStore = provider.GetService>(); + Expect(resolvedRoleStore, Is.Not.Null, "Role store did not resolve"); + + var resolvedUserManager = provider.GetService>(); + Expect(resolvedUserManager, Is.Not.Null, "User manager did not resolve"); + + var resolvedRoleManager = provider.GetService>(); + Expect(resolvedRoleManager, Is.Not.Null, "Role manager did not resolve"); + } + + protected class CustomUser : IdentityUser + { + } + + protected class CustomRole : IdentityRole + { + } + + [Test] + public void AddMongoStores_WithCustomTypes_ThisShouldLookReasonableForUsers() + { + // this test is just to make sure I consider the interface for using custom types + // so that it's not a horrible experience even though it should be rarely used + var services = new ServiceCollection(); + services.AddIdentityWithMongoStoresUsingCustomTypes(FakeConnectionStringWithDatabase); + services.AddLogging(); + + var provider = services.BuildServiceProvider(); + var resolvedUserStore = provider.GetService>(); + Expect(resolvedUserStore, Is.Not.Null, "User store did not resolve"); + + var resolvedRoleStore = provider.GetService>(); + Expect(resolvedRoleStore, Is.Not.Null, "Role store did not resolve"); + + var resolvedUserManager = provider.GetService>(); + Expect(resolvedUserManager, Is.Not.Null, "User manager did not resolve"); + + var resolvedRoleManager = provider.GetService>(); + Expect(resolvedRoleManager, Is.Not.Null, "Role manager did not resolve"); + } + + [Test] + public void AddMongoStores_ConnectionStringWithoutDatabase_Throws() + { + var connectionStringWithoutDatabase = "mongodb://fakehost"; + + TestDelegate addMongoStores = () => new ServiceCollection() + .AddIdentity() + .RegisterMongoStores(connectionStringWithoutDatabase); + + Expect(addMongoStores, Throws.Exception + .With.Message.Contains("Your connection string must contain a database name")); + } + + protected class WrongUser : IdentityUser + { + } + + protected class WrongRole : IdentityRole + { + } + + [Test] + public void AddMongoStores_MismatchedTypes_ThrowsWarningToHelpUsers() + { + Expect(() => new ServiceCollection() + .AddIdentity() + .RegisterMongoStores(FakeConnectionStringWithDatabase), + Throws.Exception.With.Message + .EqualTo("User type passed to RegisterMongoStores must match user type passed to AddIdentity. You passed Microsoft.AspNetCore.Identity.MongoDB.IdentityUser to AddIdentity and CoreTests.MongoIdentityBuilderExtensionsTests+WrongUser to RegisterMongoStores, these do not match.") + ); + + Expect(() => new ServiceCollection() + .AddIdentity() + .RegisterMongoStores(FakeConnectionStringWithDatabase), + Throws.Exception.With.Message + .EqualTo("Role type passed to RegisterMongoStores must match role type passed to AddIdentity. You passed Microsoft.AspNetCore.Identity.MongoDB.IdentityRole to AddIdentity and CoreTests.MongoIdentityBuilderExtensionsTests+WrongRole to RegisterMongoStores, these do not match.") + ); + } + } +} \ No newline at end of file diff --git a/src/Tests/ObjectIdHelpers.cs b/src/CoreTests/ObjectIdHelpers.cs similarity index 100% rename from src/Tests/ObjectIdHelpers.cs rename to src/CoreTests/ObjectIdHelpers.cs diff --git a/src/CoreTests/Properties/AssemblyInfo.cs b/src/CoreTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..35d4cff --- /dev/null +++ b/src/CoreTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CoreTests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("eac53866-6dbf-40b7-900a-22267fd0634a")] diff --git a/src/CoreTests/TestExtensions.cs b/src/CoreTests/TestExtensions.cs new file mode 100644 index 0000000..1e0795f --- /dev/null +++ b/src/CoreTests/TestExtensions.cs @@ -0,0 +1,18 @@ +namespace Tests +{ + using System.Linq; + using System.Security.Claims; + using Microsoft.AspNetCore.Identity.MongoDB; + using NUnit.Framework; + + public static class TestExtensions + { + public static void ExpectOnlyHasThisClaim(this IdentityUser user, Claim expectedClaim) + { + AssertionHelper.Expect(user.Claims.Count, Is.EqualTo(1)); + var actualClaim = user.Claims.Single(); + AssertionHelper.Expect(actualClaim.Type, Is.EqualTo(expectedClaim.Type)); + AssertionHelper.Expect(actualClaim.Value, Is.EqualTo(expectedClaim.Value)); + } + } +} \ No newline at end of file diff --git a/src/IntegrationTests/IndexChecksTests.cs b/src/IntegrationTests/IndexChecksTests.cs deleted file mode 100644 index 0b95e95..0000000 --- a/src/IntegrationTests/IndexChecksTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace IntegrationTests -{ - using System.Linq; - using AspNet.Identity.MongoDB; - using NUnit.Framework; - - [TestFixture] - public class IndexChecksTests : UserIntegrationTestsBase - { - [Test] - public void EnsureUniqueIndexOnUserName_NoIndexOnUserName_AddsUniqueIndexOnUserName() - { - var userCollectionName = "userindextest"; - Database.DropCollection(userCollectionName); - var usersNewApi = DatabaseNewApi.GetCollection(userCollectionName); - - IndexChecks.EnsureUniqueIndexOnUserName(usersNewApi); - - var users = Database.GetCollection(userCollectionName); - var index = users.GetIndexes() - .Where(i => i.IsUnique) - .Where(i => i.Key.Count() == 1) - .First(i => i.Key.Contains("UserName")); - Expect(index.Key.Count(), Is.EqualTo(1)); - } - - [Test] - public void EnsureEmailUniqueIndex_NoIndexOnEmail_AddsUniqueIndexOnEmail() - { - var userCollectionName = "userindextest"; - Database.DropCollection(userCollectionName); - var usersNewApi = DatabaseNewApi.GetCollection(userCollectionName); - - IndexChecks.EnsureUniqueIndexOnEmail(usersNewApi); - - var users = Database.GetCollection(userCollectionName); - var index = users.GetIndexes() - .Where(i => i.IsUnique) - .Where(i => i.Key.Count() == 1) - .First(i => i.Key.Contains("Email")); - Expect(index.Key.Count(), Is.EqualTo(1)); - } - - [Test] - public void EnsureUniqueIndexOnRoleName_NoIndexOnRoleName_AddsUniqueIndexOnRoleName() - { - var roleCollectionName = "roleindextest"; - Database.DropCollection(roleCollectionName); - var rolesNewApi = DatabaseNewApi.GetCollection(roleCollectionName); - - IndexChecks.EnsureUniqueIndexOnRoleName(rolesNewApi); - - var roles = Database.GetCollection(roleCollectionName); - var index = roles.GetIndexes() - .Where(i => i.IsUnique) - .Where(i => i.Key.Count() == 1) - .First(i => i.Key.Contains("Name")); - Expect(index.Key.Count(), Is.EqualTo(1)); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/IntegrationTests.csproj b/src/IntegrationTests/IntegrationTests.csproj deleted file mode 100644 index efb2cff..0000000 --- a/src/IntegrationTests/IntegrationTests.csproj +++ /dev/null @@ -1,108 +0,0 @@ - - - - - Debug - AnyCPU - {2817B6BF-006D-415D-8CAB-34E14DCCBC3C} - Library - Properties - IntegrationTests - IntegrationTests - v4.5.1 - 512 - ..\ - - - true - full - false - ..\..\build\integrationtests\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\build\integrationtests\ - TRACE - prompt - 4 - - - - False - ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\MongoDB.Bson.2.2.0\lib\net45\MongoDB.Bson.dll - True - - - ..\packages\MongoDB.Driver.2.2.0\lib\net45\MongoDB.Driver.dll - True - - - ..\packages\MongoDB.Driver.Core.2.2.0\lib\net45\MongoDB.Driver.Core.dll - True - - - ..\packages\mongocsharpdriver.2.2.0\lib\net45\MongoDB.Driver.Legacy.dll - True - - - ..\packages\NUnit.2.6.3\lib\nunit.framework.dll - - - ..\packages\ReflectionMagic.2.0.0\lib\net40\ReflectionMagic.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {AD64128A-6855-49F7-A846-1FCDAD368C55} - AspNet.Identity.MongoDB - - - {62483144-11D8-4ECE-990D-17B4716AFF69} - Tests - - - - - \ No newline at end of file diff --git a/src/IntegrationTests/Properties/AssemblyInfo.cs b/src/IntegrationTests/Properties/AssemblyInfo.cs deleted file mode 100644 index 5607e35..0000000 --- a/src/IntegrationTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("IntegrationTests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("IntegrationTests")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c09b5202-c583-46be-b9ee-b514c8fc102b")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/IntegrationTests/RoleStoreTests.cs b/src/IntegrationTests/RoleStoreTests.cs deleted file mode 100644 index 743ce9d..0000000 --- a/src/IntegrationTests/RoleStoreTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace IntegrationTests -{ - using System.Linq; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using MongoDB.Bson; - using NUnit.Framework; - using Tests; - - [TestFixture] - public class RoleStoreTests : UserIntegrationTestsBase - { - [Test] - public void Create_NewRole_Saves() - { - var roleName = "admin"; - var role = new IdentityRole(roleName); - var manager = GetRoleManager(); - - manager.Create(role); - - var savedRole = Roles.FindAll().Single(); - Expect(savedRole.Name, Is.EqualTo(roleName)); - } - - [Test] - public void FindByName_SavedRole_ReturnsRole() - { - var roleName = "name"; - var role = new IdentityRole {Name = roleName}; - var manager = GetRoleManager(); - manager.Create(role); - - var foundRole = manager.FindByName(roleName); - - Expect(foundRole, Is.Not.Null); - Expect(foundRole.Name, Is.EqualTo(roleName)); - } - - [Test] - public void FindById_SavedRole_ReturnsRole() - { - var roleId = ObjectId.GenerateNewId().ToString(); - var role = new IdentityRole {Name = "name"}; - role.SetId(roleId); - var manager = GetRoleManager(); - manager.Create(role); - - var foundRole = manager.FindById(roleId); - - Expect(foundRole, Is.Not.Null); - Expect(foundRole.Id, Is.EqualTo(roleId)); - } - - [Test] - public void Delete_ExistingRole_Removes() - { - var role = new IdentityRole {Name = "name"}; - var manager = GetRoleManager(); - manager.Create(role); - Expect(Roles.FindAll(), Is.Not.Empty); - - manager.Delete(role); - - Expect(Roles.FindAll(), Is.Empty); - } - - [Test] - public void Update_ExistingRole_Updates() - { - var role = new IdentityRole {Name = "name"}; - var manager = GetRoleManager(); - manager.Create(role); - var savedRole = manager.FindById(role.Id); - savedRole.Name = "newname"; - - manager.Update(savedRole); - - var changedRole = Roles.FindAll().Single(); - Expect(changedRole, Is.Not.Null); - Expect(changedRole.Name, Is.EqualTo("newname")); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserClaimStoreTests.cs b/src/IntegrationTests/UserClaimStoreTests.cs deleted file mode 100644 index 27a259a..0000000 --- a/src/IntegrationTests/UserClaimStoreTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace IntegrationTests -{ - using System.Linq; - using System.Security.Claims; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using NUnit.Framework; - - [TestFixture] - public class UserClaimStoreTests : UserIntegrationTestsBase - { - [Test] - public void Create_NewUser_HasNoClaims() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - - var claims = manager.GetClaims(user.Id); - - Expect(claims, Is.Empty); - } - - [Test] - public void AddClaim_ReturnsClaim() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - - manager.AddClaim(user.Id, new Claim("type", "value")); - - var claim = manager.GetClaims(user.Id).Single(); - Expect(claim.Type, Is.EqualTo("type")); - Expect(claim.Value, Is.EqualTo("value")); - } - - [Test] - public void RemoveClaim_RemovesExistingClaim() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - manager.AddClaim(user.Id, new Claim("type", "value")); - - manager.RemoveClaim(user.Id, new Claim("type", "value")); - - Expect(manager.GetClaims(user.Id), Is.Empty); - } - - [Test] - public void RemoveClaim_DifferentType_DoesNotRemoveClaim() - { - var user = new IdentityUser { UserName = "bob" }; - var manager = GetUserManager(); - manager.Create(user); - manager.AddClaim(user.Id, new Claim("type", "value")); - - manager.RemoveClaim(user.Id, new Claim("otherType", "value")); - - Expect(manager.GetClaims(user.Id), Is.Not.Empty); - } - - [Test] - public void RemoveClaim_DifferentValue_DoesNotRemoveClaim() - { - var user = new IdentityUser { UserName = "bob" }; - var manager = GetUserManager(); - manager.Create(user); - manager.AddClaim(user.Id, new Claim("type", "value")); - - manager.RemoveClaim(user.Id, new Claim("type", "otherValue")); - - Expect(manager.GetClaims(user.Id), Is.Not.Empty); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserEmailStoreTests.cs b/src/IntegrationTests/UserEmailStoreTests.cs deleted file mode 100644 index 0210819..0000000 --- a/src/IntegrationTests/UserEmailStoreTests.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace IntegrationTests -{ - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using NUnit.Framework; - - [TestFixture] - public class UserEmailStoreTests : UserIntegrationTestsBase - { - [Test] - public void Create_NewUser_HasNoEmail() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - - var email = manager.GetEmail(user.Id); - - Expect(email, Is.Null); - } - - [Test] - public void SetEmail_SetsEmail() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - - manager.SetEmail(user.Id, "email"); - - Expect(manager.GetEmail(user.Id), Is.EqualTo("email")); - } - - [Test] - public void FindUserByEmail_ReturnsUser() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - Expect(manager.FindByEmail("email"), Is.Null); - - manager.SetEmail(user.Id, "email"); - - Expect(manager.FindByEmail("email"), Is.Not.Null); - } - - [Test] - public void Create_NewUser_IsNotEmailConfirmed() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - - var isConfirmed = manager.IsEmailConfirmed(user.Id); - - Expect(isConfirmed, Is.False); - } - - [Test] - public void SetEmailConfirmed_IsConfirmed() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.UserTokenProvider = new EmailTokenProvider(); - var token = manager.GenerateEmailConfirmationToken(user.Id); - - manager.ConfirmEmail(user.Id, token); - - var isConfirmed = manager.IsEmailConfirmed(user.Id); - Expect(isConfirmed); - } - - [Test] - public void ChangeEmail_AfterConfirmedOriginalEmail_NotEmailConfirmed() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.UserTokenProvider = new EmailTokenProvider(); - var token = manager.GenerateEmailConfirmationToken(user.Id); - manager.ConfirmEmail(user.Id, token); - - manager.SetEmail(user.Id, "new@email.com"); - - var isConfirmed = manager.IsEmailConfirmed(user.Id); - Expect(isConfirmed, Is.False); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserIntegrationTestsBase.cs b/src/IntegrationTests/UserIntegrationTestsBase.cs deleted file mode 100644 index ee8f471..0000000 --- a/src/IntegrationTests/UserIntegrationTestsBase.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace IntegrationTests -{ - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using MongoDB.Driver; - using NUnit.Framework; - - public class UserIntegrationTestsBase : AssertionHelper - { - protected MongoDatabase Database; - protected MongoCollection Users; - protected MongoCollection Roles; - - // note: for now we'll have interfaces to both the new and old apis for MongoDB, that way we don't have to update all the tests at once and risk introducing bugs - protected IMongoDatabase DatabaseNewApi; - private IMongoCollection _UsersNewApi; - private IMongoCollection _RolesNewApi; - - [SetUp] - public void BeforeEachTest() - { - var client = new MongoClient("mongodb://localhost:27017"); - var identityTesting = "identity-testing"; - - Database = client.GetServer().GetDatabase(identityTesting); - Users = Database.GetCollection("users"); - Roles = Database.GetCollection("roles"); - - DatabaseNewApi = client.GetDatabase(identityTesting); - _UsersNewApi = DatabaseNewApi.GetCollection("users"); - _RolesNewApi = DatabaseNewApi.GetCollection("roles"); - - Database.DropCollection("users"); - Database.DropCollection("roles"); - } - - protected UserManager GetUserManager() - { - var store = new UserStore(_UsersNewApi); - return new UserManager(store); - } - - protected RoleManager GetRoleManager() - { - var store = new RoleStore(_RolesNewApi); - return new RoleManager(store); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserLockoutStoreTests.cs b/src/IntegrationTests/UserLockoutStoreTests.cs deleted file mode 100644 index 67ee8a5..0000000 --- a/src/IntegrationTests/UserLockoutStoreTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace IntegrationTests -{ - using System; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using NUnit.Framework; - - [TestFixture] - public class UserLockoutStoreTests : UserIntegrationTestsBase - { - [Test] - public void AccessFailed_IncrementsAccessFailedCount() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.MaxFailedAccessAttemptsBeforeLockout = 3; - - manager.AccessFailed(user.Id); - - Expect(manager.GetAccessFailedCount(user.Id), Is.EqualTo(1)); - } - - [Test] - public void IncrementAccessFailedCount_ReturnsNewCount() - { - var store = new UserStore(null); - var user = new IdentityUser {UserName = "bob"}; - - var count = store.IncrementAccessFailedCountAsync(user); - - Expect(count.Result, Is.EqualTo(1)); - } - - [Test] - public void ResetAccessFailed_AfterAnAccessFailed_SetsToZero() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.MaxFailedAccessAttemptsBeforeLockout = 3; - manager.AccessFailed(user.Id); - - manager.ResetAccessFailedCount(user.Id); - - Expect(manager.GetAccessFailedCount(user.Id), Is.EqualTo(0)); - } - - [Test] - public void AccessFailed_NotOverMaxFailures_NoLockoutEndDate() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.MaxFailedAccessAttemptsBeforeLockout = 3; - - manager.AccessFailed(user.Id); - - Expect(manager.GetLockoutEndDate(user.Id), Is.EqualTo(DateTimeOffset.MinValue)); - } - - [Test] - public void AccessFailed_ExceedsMaxFailedAccessAttempts_LocksAccount() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.MaxFailedAccessAttemptsBeforeLockout = 0; - manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromHours(1); - - manager.AccessFailed(user.Id); - - var lockoutEndDate = manager.GetLockoutEndDate(user.Id); - Expect(lockoutEndDate.Subtract(DateTime.UtcNow).TotalHours, Is.GreaterThan(0.9).And.LessThan(1.1)); - } - - [Test] - public void SetLockoutEnabled() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - - manager.SetLockoutEnabled(user.Id, true); - Expect(manager.GetLockoutEnabled(user.Id)); - - manager.SetLockoutEnabled(user.Id, false); - Expect(manager.GetLockoutEnabled(user.Id), Is.False); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserLoginStoreTests.cs b/src/IntegrationTests/UserLoginStoreTests.cs deleted file mode 100644 index fd8f57d..0000000 --- a/src/IntegrationTests/UserLoginStoreTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace IntegrationTests -{ - using System.Linq; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using NUnit.Framework; - - [TestFixture] - public class UserLoginStoreTests : UserIntegrationTestsBase - { - [Test] - public void AddLogin_NewLogin_Adds() - { - var manager = GetUserManager(); - var login = new UserLoginInfo("provider", "key"); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - - manager.AddLogin(user.Id, login); - - var savedLogin = Users.FindAll().Single().Logins.Single(); - Expect(savedLogin.LoginProvider, Is.EqualTo("provider")); - Expect(savedLogin.ProviderKey, Is.EqualTo("key")); - } - - - [Test] - public void RemoveLogin_NewLogin_Removes() - { - var manager = GetUserManager(); - var login = new UserLoginInfo("provider", "key"); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.AddLogin(user.Id, login); - - manager.RemoveLogin(user.Id, login); - - var savedUser = Users.FindAll().Single(); - Expect(savedUser.Logins, Is.Empty); - } - - [Test] - public void GetLogins_OneLogin_ReturnsLogin() - { - var manager = GetUserManager(); - var login = new UserLoginInfo("provider", "key"); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.AddLogin(user.Id, login); - - var logins = manager.GetLogins(user.Id); - - var savedLogin = logins.Single(); - Expect(savedLogin.LoginProvider, Is.EqualTo("provider")); - Expect(savedLogin.ProviderKey, Is.EqualTo("key")); - } - - [Test] - public void Find_UserWithLogin_FindsUser() - { - var manager = GetUserManager(); - var login = new UserLoginInfo("provider", "key"); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.AddLogin(user.Id, login); - - var findUser = manager.Find(login); - - Expect(findUser, Is.Not.Null); - } - - [Test] - public void Find_UserWithDifferentKey_DoesNotFindUser() - { - var manager = GetUserManager(); - var login = new UserLoginInfo("provider", "key"); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.AddLogin(user.Id, login); - - var findUser = manager.Find(new UserLoginInfo("provider", "otherkey")); - - Expect(findUser, Is.Null); - } - - [Test] - public void Find_UserWithDifferentProvider_DoesNotFindUser() - { - var manager = GetUserManager(); - var login = new UserLoginInfo("provider", "key"); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.AddLogin(user.Id, login); - - var findUser = manager.Find(new UserLoginInfo("otherprovider", "key")); - - Expect(findUser, Is.Null); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserPasswordStoreTests.cs b/src/IntegrationTests/UserPasswordStoreTests.cs deleted file mode 100644 index e15af52..0000000 --- a/src/IntegrationTests/UserPasswordStoreTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace IntegrationTests -{ - using System.Linq; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using NUnit.Framework; - - [TestFixture] - public class UserPasswordStoreTests : UserIntegrationTestsBase - { - [Test] - public void HasPassword_NoPassword_ReturnsFalse() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - - var hasPassword = manager.HasPassword(user.Id); - - Expect(hasPassword, Is.False); - } - - [Test] - public void AddPassword_NewPassword_CanFindUserByPassword() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - - manager.AddPassword(user.Id, "testtest"); - - var findUserByPassword = manager.Find("bob", "testtest"); - Expect(findUserByPassword, Is.Not.Null); - } - - [Test] - public void RemovePassword_UserWithPassword_SetsPasswordNull() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - manager.AddPassword(user.Id, "testtest"); - - manager.RemovePassword(user.Id); - - var savedUser = Users.FindAll().Single(); - Expect(savedUser.PasswordHash, Is.Null); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserPhoneNumberStoreTests.cs b/src/IntegrationTests/UserPhoneNumberStoreTests.cs deleted file mode 100644 index 5eb9209..0000000 --- a/src/IntegrationTests/UserPhoneNumberStoreTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace IntegrationTests -{ - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using NUnit.Framework; - - [TestFixture] - public class UserPhoneNumberStoreTests : UserIntegrationTestsBase - { - private const string PhoneNumber = "1234567890"; - - [Test] - public void SetPhoneNumber_StoresPhoneNumber() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - - manager.SetPhoneNumber(user.Id, PhoneNumber); - - Expect(manager.GetPhoneNumber(user.Id), Is.EqualTo(PhoneNumber)); - } - - [Test] - public void ConfirmPhoneNumber_StoresPhoneNumberConfirmed() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - var token = manager.GenerateChangePhoneNumberToken(user.Id, PhoneNumber); - - manager.ChangePhoneNumber(user.Id, PhoneNumber, token); - - Expect(manager.IsPhoneNumberConfirmed(user.Id)); - } - - [Test] - public void ChangePhoneNumber_OriginalPhoneNumberWasConfirmed_NotPhoneNumberConfirmed() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - var token = manager.GenerateChangePhoneNumberToken(user.Id, PhoneNumber); - manager.ChangePhoneNumber(user.Id, PhoneNumber, token); - - manager.SetPhoneNumber(user.Id, PhoneNumber); - - Expect(manager.IsPhoneNumberConfirmed(user.Id), Is.False); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserRoleStoreTests.cs b/src/IntegrationTests/UserRoleStoreTests.cs deleted file mode 100644 index 3d39978..0000000 --- a/src/IntegrationTests/UserRoleStoreTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace IntegrationTests -{ - using System.Linq; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using NUnit.Framework; - - [TestFixture] - public class UserRoleStoreTests : UserIntegrationTestsBase - { - [Test] - public void GetRoles_UserHasNoRoles_ReturnsNoRoles() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - - var roles = manager.GetRoles(user.Id); - - Expect(roles, Is.Empty); - } - - [Test] - public void AddRole_Adds() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - - manager.AddToRole(user.Id, "role"); - - var savedUser = Users.FindAll().Single(); - Expect(savedUser.Roles, Is.EquivalentTo(new[] {"role"})); - Expect(manager.IsInRole(user.Id, "role"), Is.True); - } - - [Test] - public void RemoveRole_Removes() - { - var manager = GetUserManager(); - var user = new IdentityUser {UserName = "bob"}; - manager.Create(user); - manager.AddToRole(user.Id, "role"); - - manager.RemoveFromRole(user.Id, "role"); - - var savedUser = Users.FindAll().Single(); - Expect(savedUser.Roles, Is.Empty); - Expect(manager.IsInRole(user.Id, "role"), Is.False); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserStoreTests.cs b/src/IntegrationTests/UserStoreTests.cs deleted file mode 100644 index c10ea83..0000000 --- a/src/IntegrationTests/UserStoreTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace IntegrationTests -{ - using System.Linq; - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using MongoDB.Bson; - using NUnit.Framework; - using Tests; - - [TestFixture] - public class UserStoreTests : UserIntegrationTestsBase - { - [Test] - public void Create_NewUser_Saves() - { - var userName = "name"; - var user = new IdentityUser {UserName = userName}; - var manager = GetUserManager(); - - manager.Create(user); - - var savedUser = Users.FindAll().Single(); - Expect(savedUser.UserName, Is.EqualTo(user.UserName)); - } - - [Test] - public void FindByName_SavedUser_ReturnsUser() - { - var userName = "name"; - var user = new IdentityUser {UserName = userName}; - var manager = GetUserManager(); - manager.Create(user); - - var foundUser = manager.FindByName(userName); - - Expect(foundUser, Is.Not.Null); - Expect(foundUser.UserName, Is.EqualTo(userName)); - } - - [Test] - public void FindByName_NoUser_ReturnsNull() - { - var manager = GetUserManager(); - - var foundUser = manager.FindByName("nouserbyname"); - - Expect(foundUser, Is.Null); - } - - [Test] - public void FindById_SavedUser_ReturnsUser() - { - var userId = ObjectId.GenerateNewId().ToString(); - var user = new IdentityUser {UserName = "name"}; - user.SetId(userId); - var manager = GetUserManager(); - manager.Create(user); - - var foundUser = manager.FindById(userId); - - Expect(foundUser, Is.Not.Null); - Expect(foundUser.Id, Is.EqualTo(userId)); - } - - [Test] - public void FindById_NoUser_ReturnsNull() - { - var manager = GetUserManager(); - - var foundUser = manager.FindById(ObjectId.GenerateNewId().ToString()); - - Expect(foundUser, Is.Null); - } - - [Test] - public void Delete_ExistingUser_Removes() - { - var user = new IdentityUser {UserName = "name"}; - var manager = GetUserManager(); - manager.Create(user); - Expect(Users.FindAll(), Is.Not.Empty); - - manager.Delete(user); - - Expect(Users.FindAll(), Is.Empty); - } - - [Test] - public void Update_ExistingUser_Updates() - { - var user = new IdentityUser {UserName = "name"}; - var manager = GetUserManager(); - manager.Create(user); - var savedUser = manager.FindById(user.Id); - savedUser.UserName = "newname"; - - manager.Update(savedUser); - - var changedUser = Users.FindAll().Single(); - Expect(changedUser, Is.Not.Null); - Expect(changedUser.UserName, Is.EqualTo("newname")); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/UserTwoFactorStoreTests.cs b/src/IntegrationTests/UserTwoFactorStoreTests.cs deleted file mode 100644 index 1739680..0000000 --- a/src/IntegrationTests/UserTwoFactorStoreTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace IntegrationTests -{ - using AspNet.Identity.MongoDB; - using Microsoft.AspNet.Identity; - using NUnit.Framework; - - [TestFixture] - public class UserTwoFactorStoreTests : UserIntegrationTestsBase - { - [Test] - public void SetTwoFactorEnabled() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - - manager.SetTwoFactorEnabled(user.Id, true); - - Expect(manager.GetTwoFactorEnabled(user.Id)); - } - - [Test] - public void ClearTwoFactorEnabled_PreviouslyEnabled_NotEnabled() - { - var user = new IdentityUser {UserName = "bob"}; - var manager = GetUserManager(); - manager.Create(user); - manager.SetTwoFactorEnabled(user.Id, true); - - manager.SetTwoFactorEnabled(user.Id, false); - - Expect(manager.GetTwoFactorEnabled(user.Id), Is.False); - } - } -} \ No newline at end of file diff --git a/src/IntegrationTests/packages.config b/src/IntegrationTests/packages.config deleted file mode 100644 index 92ef483..0000000 --- a/src/IntegrationTests/packages.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB/IdentityRole.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityRole.cs similarity index 61% rename from src/AspNet.Identity.MongoDB/IdentityRole.cs rename to src/Microsoft.AspNetCore.Identity.MongoDB/IdentityRole.cs index fee22ec..b61f65e 100644 --- a/src/AspNet.Identity.MongoDB/IdentityRole.cs +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityRole.cs @@ -1,10 +1,9 @@ -namespace AspNet.Identity.MongoDB +namespace Microsoft.AspNetCore.Identity.MongoDB { using global::MongoDB.Bson; using global::MongoDB.Bson.Serialization.Attributes; - using Microsoft.AspNet.Identity; - public class IdentityRole : IRole + public class IdentityRole { public IdentityRole() { @@ -17,8 +16,12 @@ public IdentityRole(string roleName) : this() } [BsonRepresentation(BsonType.ObjectId)] - public string Id { get; private set; } + public string Id { get; set; } public string Name { get; set; } + + public string NormalizedName { get; set; } + + public override string ToString() => Name; } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUser.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUser.cs new file mode 100644 index 0000000..7b5f6a5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUser.cs @@ -0,0 +1,148 @@ +namespace Microsoft.AspNetCore.Identity.MongoDB +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; + using global::MongoDB.Bson; + using global::MongoDB.Bson.Serialization.Attributes; + + public class IdentityUser + { + public IdentityUser() + { + Id = ObjectId.GenerateNewId().ToString(); + Roles = new List(); + Logins = new List(); + Claims = new List(); + Tokens = new List(); + } + + [BsonRepresentation(BsonType.ObjectId)] + public virtual string Id { get; set; } + + public virtual string UserName { get; set; } + + public virtual string NormalizedUserName { get; set; } + + /// + /// A random value that must change whenever a users credentials change + /// (password changed, login removed) + /// + public virtual string SecurityStamp { get; set; } + + public virtual string Email { get; set; } + + public virtual string NormalizedEmail { get; set; } + + public virtual bool EmailConfirmed { get; set; } + + public virtual string PhoneNumber { get; set; } + + public virtual bool PhoneNumberConfirmed { get; set; } + + public virtual bool TwoFactorEnabled { get; set; } + + public virtual DateTime? LockoutEndDateUtc { get; set; } + + public virtual bool LockoutEnabled { get; set; } + + public virtual int AccessFailedCount { get; set; } + + [BsonIgnoreIfNull] + public virtual List Roles { get; set; } + + public virtual void AddRole(string role) + { + Roles.Add(role); + } + + public virtual void RemoveRole(string role) + { + Roles.Remove(role); + } + + [BsonIgnoreIfNull] + public virtual string PasswordHash { get; set; } + + [BsonIgnoreIfNull] + public virtual List Logins { get; set; } + + public virtual void AddLogin(UserLoginInfo login) + { + Logins.Add(new IdentityUserLogin(login)); + } + + public virtual void RemoveLogin(string loginProvider, string providerKey) + { + Logins.RemoveAll(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); + } + + public virtual bool HasPassword() + { + return false; + } + + [BsonIgnoreIfNull] + public virtual List Claims { get; set; } + + public virtual void AddClaim(Claim claim) + { + Claims.Add(new IdentityUserClaim(claim)); + } + + public virtual void RemoveClaim(Claim claim) + { + Claims.RemoveAll(c => c.Type == claim.Type && c.Value == claim.Value); + } + + public virtual void ReplaceClaim(Claim existingClaim, Claim newClaim) + { + var claimExists = Claims + .Any(c => c.Type == existingClaim.Type && c.Value == existingClaim.Value); + if (!claimExists) + { + // note: nothing to update, ignore, no need to throw + return; + } + RemoveClaim(existingClaim); + AddClaim(newClaim); + } + + [BsonIgnoreIfNull] + public virtual List Tokens { get; set; } + + private IdentityUserToken GetToken(string loginProider, string name) + => Tokens + .FirstOrDefault(t => t.LoginProvider == loginProider && t.Name == name); + + public virtual void SetToken(string loginProider, string name, string value) + { + var existingToken = GetToken(loginProider, name); + if (existingToken != null) + { + existingToken.Value = value; + return; + } + + Tokens.Add(new IdentityUserToken + { + LoginProvider = loginProider, + Name = name, + Value = value + }); + } + + public virtual string GetTokenValue(string loginProider, string name) + { + return GetToken(loginProider, name)?.Value; + } + + public virtual void RemoveToken(string loginProvider, string name) + { + Tokens.RemoveAll(t => t.LoginProvider == loginProvider && t.Name == name); + } + + public override string ToString() => UserName; + } +} \ No newline at end of file diff --git a/src/AspNet.Identity.MongoDB/IdentityUserClaim.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUserClaim.cs similarity index 61% rename from src/AspNet.Identity.MongoDB/IdentityUserClaim.cs rename to src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUserClaim.cs index c2d5780..7f8292e 100644 --- a/src/AspNet.Identity.MongoDB/IdentityUserClaim.cs +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUserClaim.cs @@ -1,7 +1,10 @@ -namespace AspNet.Identity.MongoDB +namespace Microsoft.AspNetCore.Identity.MongoDB { using System.Security.Claims; + /// + /// A claim that a user possesses. + /// public class IdentityUserClaim { public IdentityUserClaim() @@ -14,7 +17,14 @@ public IdentityUserClaim(Claim claim) Value = claim.Value; } + /// + /// Claim type + /// public string Type { get; set; } + + /// + /// Claim value + /// public string Value { get; set; } public Claim ToSecurityClaim() diff --git a/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUserLogin.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUserLogin.cs new file mode 100644 index 0000000..b17f4ed --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUserLogin.cs @@ -0,0 +1,28 @@ +namespace Microsoft.AspNetCore.Identity.MongoDB +{ + public class IdentityUserLogin + { + public IdentityUserLogin(string loginProvider, string providerKey, string providerDisplayName) + { + LoginProvider = loginProvider; + ProviderDisplayName = providerDisplayName; + ProviderKey = providerKey; + } + + public IdentityUserLogin(UserLoginInfo login) + { + LoginProvider = login.LoginProvider; + ProviderDisplayName = login.ProviderDisplayName; + ProviderKey = login.ProviderKey; + } + + public string LoginProvider { get; set; } + public string ProviderDisplayName { get; set; } + public string ProviderKey { get; set; } + + public UserLoginInfo ToUserLoginInfo() + { + return new UserLoginInfo(LoginProvider, ProviderKey, ProviderDisplayName); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUserToken.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUserToken.cs new file mode 100644 index 0000000..85f72ab --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/IdentityUserToken.cs @@ -0,0 +1,23 @@ +namespace Microsoft.AspNetCore.Identity.MongoDB +{ + /// + /// Authentication token associated with a user + /// + public class IdentityUserToken + { + /// + /// The provider that the token came from. + /// + public string LoginProvider { get; set; } + + /// + /// The name of the token. + /// + public string Name { get; set; } + + /// + /// The value of the token. + /// + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.MongoDB/IndexChecks.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/IndexChecks.cs new file mode 100644 index 0000000..f377110 --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/IndexChecks.cs @@ -0,0 +1,62 @@ +namespace Microsoft.AspNetCore.Identity.MongoDB +{ + using global::MongoDB.Driver; + + public static class IndexChecks + { + public static void EnsureUniqueIndexOnNormalizedUserName(IMongoCollection users) + where TUser : IdentityUser + { + var userName = Builders.IndexKeys.Ascending(t => t.NormalizedUserName); + var unique = new CreateIndexOptions {Unique = true}; + users.Indexes.CreateOneAsync(userName, unique); + } + + public static void EnsureUniqueIndexOnNormalizedRoleName(IMongoCollection roles) + where TRole : IdentityRole + { + var roleName = Builders.IndexKeys.Ascending(t => t.NormalizedName); + var unique = new CreateIndexOptions {Unique = true}; + roles.Indexes.CreateOneAsync(roleName, unique); + } + + public static void EnsureUniqueIndexOnNormalizedEmail(IMongoCollection users) + where TUser : IdentityUser + { + var email = Builders.IndexKeys.Ascending(t => t.NormalizedEmail); + var unique = new CreateIndexOptions {Unique = true}; + users.Indexes.CreateOneAsync(email, unique); + } + + /// + /// ASP.NET Core Identity now searches on normalized fields so these indexes are no longer required, replace with + /// normalized checks. + /// + public static class OptionalIndexChecks + { + public static void EnsureUniqueIndexOnUserName(IMongoCollection users) + where TUser : IdentityUser + { + var userName = Builders.IndexKeys.Ascending(t => t.UserName); + var unique = new CreateIndexOptions {Unique = true}; + users.Indexes.CreateOneAsync(userName, unique); + } + + public static void EnsureUniqueIndexOnRoleName(IMongoCollection roles) + where TRole : IdentityRole + { + var roleName = Builders.IndexKeys.Ascending(t => t.Name); + var unique = new CreateIndexOptions {Unique = true}; + roles.Indexes.CreateOneAsync(roleName, unique); + } + + public static void EnsureUniqueIndexOnEmail(IMongoCollection users) + where TUser : IdentityUser + { + var email = Builders.IndexKeys.Ascending(t => t.Email); + var unique = new CreateIndexOptions {Unique = true}; + users.Indexes.CreateOneAsync(email, unique); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.MongoDB/Microsoft.AspNetCore.Identity.MongoDB.csproj b/src/Microsoft.AspNetCore.Identity.MongoDB/Microsoft.AspNetCore.Identity.MongoDB.csproj new file mode 100644 index 0000000..a5a895d --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/Microsoft.AspNetCore.Identity.MongoDB.csproj @@ -0,0 +1,44 @@ + + + + A MongoDB provider for ASP.NET Core Identity framework. + 1.0.2 + Wes Higbee + net451;netstandard1.5;netstandard2.0 + true + Microsoft.AspNetCore.Identity.MongoDB + Microsoft.AspNetCore.Identity.MongoDB + aspnetcore;mongo;mongodb;identity;membership + Convert back to using DateTime to store LockoutEndDate, DateTimeOffset serializes to an array of values which could make it hard for people to sort on this and query on this. Also DateTime was used in the v2 driver, so this makes the upgrade story easier. + https://github.com/g0t4/aspnet-identity-mongo + https://github.com/g0t4/aspnet-identity-mongo/blob/master/LICENSE + git + https://github.com/g0t4/aspnet-identity-mongo + 1.6.0 + + false + false + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.AspNetCore.Identity.MongoDB/MongoIdentityBuilderExtensions.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/MongoIdentityBuilderExtensions.cs new file mode 100644 index 0000000..21abf43 --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/MongoIdentityBuilderExtensions.cs @@ -0,0 +1,94 @@ +// ReSharper disable once CheckNamespace - Common convention to locate extensions in Microsoft namespaces for simplifying autocompletion as a consumer. + +namespace Microsoft.Extensions.DependencyInjection +{ + using System; + using AspNetCore.Identity; + using AspNetCore.Identity.MongoDB; + using MongoDB.Driver; + + public static class MongoIdentityBuilderExtensions + { + /// + /// This method only registers mongo stores, you also need to call AddIdentity. + /// Consider using AddIdentityWithMongoStores. + /// + /// + /// Must contain the database name + public static IdentityBuilder RegisterMongoStores(this IdentityBuilder builder, string connectionString) + where TRole : IdentityRole + where TUser : IdentityUser + { + var url = new MongoUrl(connectionString); + var client = new MongoClient(url); + if (url.DatabaseName == null) + { + throw new ArgumentException("Your connection string must contain a database name", connectionString); + } + var database = client.GetDatabase(url.DatabaseName); + return builder.RegisterMongoStores( + p => database.GetCollection("users"), + p => database.GetCollection("roles")); + } + + /// + /// If you want control over creating the users and roles collections, use this overload. + /// This method only registers mongo stores, you also need to call AddIdentity. + /// + /// + /// + /// + /// + /// + public static IdentityBuilder RegisterMongoStores(this IdentityBuilder builder, + Func> usersCollectionFactory, + Func> rolesCollectionFactory) + where TRole : IdentityRole + where TUser : IdentityUser + { + if (typeof(TUser) != builder.UserType) + { + var message = "User type passed to RegisterMongoStores must match user type passed to AddIdentity. " + + $"You passed {builder.UserType} to AddIdentity and {typeof(TUser)} to RegisterMongoStores, " + + "these do not match."; + throw new ArgumentException(message); + } + if (typeof(TRole) != builder.RoleType) + { + var message = "Role type passed to RegisterMongoStores must match role type passed to AddIdentity. " + + $"You passed {builder.RoleType} to AddIdentity and {typeof(TRole)} to RegisterMongoStores, " + + "these do not match."; + throw new ArgumentException(message); + } + builder.Services.AddSingleton>(p => new UserStore(usersCollectionFactory(p))); + builder.Services.AddSingleton>(p => new RoleStore(rolesCollectionFactory(p))); + return builder; + } + + /// + /// This method registers identity services and MongoDB stores using the IdentityUser and IdentityRole types. + /// + /// + /// Connection string must contain the database name + public static IdentityBuilder AddIdentityWithMongoStores(this IServiceCollection services, string connectionString) + { + return services.AddIdentityWithMongoStoresUsingCustomTypes(connectionString); + } + + /// + /// This method allows you to customize the user and role type when registering identity services + /// and MongoDB stores. + /// + /// + /// + /// + /// Connection string must contain the database name + public static IdentityBuilder AddIdentityWithMongoStoresUsingCustomTypes(this IServiceCollection services, string connectionString) + where TUser : IdentityUser + where TRole : IdentityRole + { + return services.AddIdentity() + .RegisterMongoStores(connectionString); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.MongoDB/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..066aa19 --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.AspNetCore.Identity.MongoDB")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6dff5058-e107-459e-87c3-da41b2c1463c")] diff --git a/src/Microsoft.AspNetCore.Identity.MongoDB/RoleStore.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/RoleStore.cs new file mode 100644 index 0000000..48ea086 --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/RoleStore.cs @@ -0,0 +1,82 @@ + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously +// I'm using async methods to leverage implicit Task wrapping of results from expression bodied functions. + +namespace Microsoft.AspNetCore.Identity.MongoDB +{ + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using global::MongoDB.Driver; + + /// + /// Note: Deleting and updating do not modify the roles stored on a user document. If you desire this dynamic + /// capability, override the appropriate operations on RoleStore as desired for your application. For example you could + /// perform a document modification on the users collection before a delete or a rename. + /// When passing a cancellation token, it will only be used if the operation requires a database interaction. + /// + /// Needs to extend the provided IdentityRole type. + public class RoleStore : IQueryableRoleStore + // todo IRoleClaimStore + where TRole : IdentityRole + { + private readonly IMongoCollection _Roles; + + public RoleStore(IMongoCollection roles) + { + _Roles = roles; + } + + public virtual void Dispose() + { + // no need to dispose of anything, mongodb handles connection pooling automatically + } + + public virtual async Task CreateAsync(TRole role, CancellationToken token) + { + await _Roles.InsertOneAsync(role, cancellationToken: token); + return IdentityResult.Success; + } + + public virtual async Task UpdateAsync(TRole role, CancellationToken token) + { + var result = await _Roles.ReplaceOneAsync(r => r.Id == role.Id, role, cancellationToken: token); + // todo low priority result based on replace result + return IdentityResult.Success; + } + + public virtual async Task DeleteAsync(TRole role, CancellationToken token) + { + var result = await _Roles.DeleteOneAsync(r => r.Id == role.Id, token); + // todo low priority result based on delete result + return IdentityResult.Success; + } + + public virtual async Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken) + => role.Id; + + public virtual async Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken) + => role.Name; + + public virtual async Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken) + => role.Name = roleName; + + // note: can't test as of yet through integration testing because the Identity framework doesn't use this method internally anywhere + public virtual async Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken) + => role.NormalizedName; + + public virtual async Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken) + => role.NormalizedName = normalizedName; + + public virtual Task FindByIdAsync(string roleId, CancellationToken token) + => _Roles.Find(r => r.Id == roleId) + .FirstOrDefaultAsync(token); + + public virtual Task FindByNameAsync(string normalizedName, CancellationToken token) + => _Roles.Find(r => r.NormalizedName == normalizedName) + .FirstOrDefaultAsync(token); + + public virtual IQueryable Roles + => _Roles.AsQueryable(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.MongoDB/UserStore.cs b/src/Microsoft.AspNetCore.Identity.MongoDB/UserStore.cs new file mode 100644 index 0000000..405aaec --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.MongoDB/UserStore.cs @@ -0,0 +1,285 @@ + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously +// I'm using async methods to leverage implicit Task wrapping of results from expression bodied functions. + +namespace Microsoft.AspNetCore.Identity.MongoDB +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; + using System.Threading; + using System.Threading.Tasks; + using global::MongoDB.Bson; + using global::MongoDB.Driver; + + /// + /// When passing a cancellation token, it will only be used if the operation requires a database interaction. + /// + /// + public class UserStore : + IUserPasswordStore, + IUserRoleStore, + IUserLoginStore, + IUserSecurityStampStore, + IUserEmailStore, + IUserClaimStore, + IUserPhoneNumberStore, + IUserTwoFactorStore, + IUserLockoutStore, + IQueryableUserStore, + IUserAuthenticationTokenStore + where TUser : IdentityUser + { + private readonly IMongoCollection _Users; + + public UserStore(IMongoCollection users) + { + _Users = users; + } + + public virtual void Dispose() + { + // no need to dispose of anything, mongodb handles connection pooling automatically + } + + public virtual async Task CreateAsync(TUser user, CancellationToken token) + { + await _Users.InsertOneAsync(user, cancellationToken: token); + return IdentityResult.Success; + } + + public virtual async Task UpdateAsync(TUser user, CancellationToken token) + { + // todo should add an optimistic concurrency check + await _Users.ReplaceOneAsync(u => u.Id == user.Id, user, cancellationToken: token); + // todo success based on replace result + return IdentityResult.Success; + } + + public virtual async Task DeleteAsync(TUser user, CancellationToken token) + { + await _Users.DeleteOneAsync(u => u.Id == user.Id, token); + // todo success based on delete result + return IdentityResult.Success; + } + + public virtual async Task GetUserIdAsync(TUser user, CancellationToken cancellationToken) + => user.Id; + + public virtual async Task GetUserNameAsync(TUser user, CancellationToken cancellationToken) + => user.UserName; + + public virtual async Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken) + => user.UserName = userName; + + // note: again this isn't used by Identity framework so no way to integration test it + public virtual async Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken) + => user.NormalizedUserName; + + public virtual async Task SetNormalizedUserNameAsync(TUser user, string normalizedUserName, CancellationToken cancellationToken) + => user.NormalizedUserName = normalizedUserName; + + public virtual Task FindByIdAsync(string userId, CancellationToken token) + => IsObjectId(userId) + ? _Users.Find(u => u.Id == userId).FirstOrDefaultAsync(token) + : Task.FromResult(null); + + private bool IsObjectId(string id) + { + ObjectId temp; + return ObjectId.TryParse(id, out temp); + } + + public virtual Task FindByNameAsync(string normalizedUserName, CancellationToken token) + // todo low priority exception on duplicates? or better to enforce unique index to ensure this + => _Users.Find(u => u.NormalizedUserName == normalizedUserName).FirstOrDefaultAsync(token); + + public virtual async Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken token) + => user.PasswordHash = passwordHash; + + public virtual async Task GetPasswordHashAsync(TUser user, CancellationToken token) + => user.PasswordHash; + + public virtual async Task HasPasswordAsync(TUser user, CancellationToken token) + => user.HasPassword(); + + public virtual async Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken token) + => user.AddRole(normalizedRoleName); + + public virtual async Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken token) + => user.RemoveRole(normalizedRoleName); + + // todo might have issue, I'm just storing Normalized only now, so I'm returning normalized here instead of not normalized. + // EF provider returns not noramlized here + // however, the rest of the API uses normalized (add/remove/isinrole) so maybe this approach is better anyways + // note: could always map normalized to not if people complain + public virtual async Task> GetRolesAsync(TUser user, CancellationToken token) + => user.Roles; + + public virtual async Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken token) + => user.Roles.Contains(normalizedRoleName); + + public virtual async Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken token) + => await _Users.Find(u => u.Roles.Contains(normalizedRoleName)) + .ToListAsync(token); + + public virtual async Task AddLoginAsync(TUser user, UserLoginInfo login, CancellationToken token) + => user.AddLogin(login); + + public virtual async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) + => user.RemoveLogin(loginProvider, providerKey); + + public virtual async Task> GetLoginsAsync(TUser user, CancellationToken token) + => user.Logins + .Select(l => l.ToUserLoginInfo()) + .ToList(); + + public virtual Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) + => _Users + .Find(u => u.Logins.Any(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey)) + .FirstOrDefaultAsync(cancellationToken); + + public virtual async Task SetSecurityStampAsync(TUser user, string stamp, CancellationToken token) + => user.SecurityStamp = stamp; + + public virtual async Task GetSecurityStampAsync(TUser user, CancellationToken token) + => user.SecurityStamp; + + public virtual async Task GetEmailConfirmedAsync(TUser user, CancellationToken token) + => user.EmailConfirmed; + + public virtual async Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken token) + => user.EmailConfirmed = confirmed; + + public virtual async Task SetEmailAsync(TUser user, string email, CancellationToken token) + => user.Email = email; + + public virtual async Task GetEmailAsync(TUser user, CancellationToken token) + => user.Email; + + // note: no way to intergation test as this isn't used by Identity framework + public virtual async Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken) + => user.NormalizedEmail; + + public virtual async Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken) + => user.NormalizedEmail = normalizedEmail; + + public virtual Task FindByEmailAsync(string normalizedEmail, CancellationToken token) + { + // note: I don't like that this now searches on normalized email :(... why not FindByNormalizedEmailAsync then? + // todo low - what if a user can have multiple accounts with the same email? + return _Users.Find(u => u.NormalizedEmail == normalizedEmail).FirstOrDefaultAsync(token); + } + + public virtual async Task> GetClaimsAsync(TUser user, CancellationToken token) + => user.Claims.Select(c => c.ToSecurityClaim()).ToList(); + + public virtual Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken token) + { + foreach (var claim in claims) + { + user.AddClaim(claim); + } + return Task.FromResult(0); + } + + public virtual Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken token) + { + foreach (var claim in claims) + { + user.RemoveClaim(claim); + } + return Task.FromResult(0); + } + + public virtual async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) + { + user.ReplaceClaim(claim, newClaim); + } + + public virtual Task SetPhoneNumberAsync(TUser user, string phoneNumber, CancellationToken token) + { + user.PhoneNumber = phoneNumber; + return Task.FromResult(0); + } + + public virtual Task GetPhoneNumberAsync(TUser user, CancellationToken token) + { + return Task.FromResult(user.PhoneNumber); + } + + public virtual Task GetPhoneNumberConfirmedAsync(TUser user, CancellationToken token) + { + return Task.FromResult(user.PhoneNumberConfirmed); + } + + public virtual Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed, CancellationToken token) + { + user.PhoneNumberConfirmed = confirmed; + return Task.FromResult(0); + } + + public virtual Task SetTwoFactorEnabledAsync(TUser user, bool enabled, CancellationToken token) + { + user.TwoFactorEnabled = enabled; + return Task.FromResult(0); + } + + public virtual Task GetTwoFactorEnabledAsync(TUser user, CancellationToken token) + { + return Task.FromResult(user.TwoFactorEnabled); + } + + public virtual async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + return await _Users + .Find(u => u.Claims.Any(c => c.Type == claim.Type && c.Value == claim.Value)) + .ToListAsync(cancellationToken); + } + + public virtual Task GetLockoutEndDateAsync(TUser user, CancellationToken token) + { + DateTimeOffset? dateTimeOffset = user.LockoutEndDateUtc; + return Task.FromResult(dateTimeOffset); + } + + public virtual Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken token) + { + user.LockoutEndDateUtc = lockoutEnd?.UtcDateTime; + return Task.FromResult(0); + } + + public virtual Task IncrementAccessFailedCountAsync(TUser user, CancellationToken token) + { + user.AccessFailedCount++; + return Task.FromResult(user.AccessFailedCount); + } + + public virtual Task ResetAccessFailedCountAsync(TUser user, CancellationToken token) + { + user.AccessFailedCount = 0; + return Task.FromResult(0); + } + + public virtual async Task GetAccessFailedCountAsync(TUser user, CancellationToken token) + => user.AccessFailedCount; + + public virtual async Task GetLockoutEnabledAsync(TUser user, CancellationToken token) + => user.LockoutEnabled; + + public virtual async Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken token) + => user.LockoutEnabled = enabled; + + public virtual IQueryable Users => _Users.AsQueryable(); + + public virtual async Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken) + => user.SetToken(loginProvider, name, value); + + public virtual async Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) + => user.RemoveToken(loginProvider, name); + + public virtual async Task GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) + => user.GetTokenValue(loginProvider, name); + } +} \ No newline at end of file diff --git a/src/Tests/IdentityUserClaimTests.cs b/src/Tests/IdentityUserClaimTests.cs deleted file mode 100644 index f0123c1..0000000 --- a/src/Tests/IdentityUserClaimTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Tests -{ - using System.Security.Claims; - using AspNet.Identity.MongoDB; - using NUnit.Framework; - - [TestFixture] - public class IdentityUserClaimTests : AssertionHelper - { - [Test] - public void Create_FromClaim_SetsTypeAndValue() - { - var claim = new Claim("type", "value"); - - var userClaim = new IdentityUserClaim(claim); - - Expect(userClaim.Type, Is.EqualTo("type")); - Expect(userClaim.Value, Is.EqualTo("value")); - } - - [Test] - public void ToSecurityClaim_SetsTypeAndValue() - { - var userClaim = new IdentityUserClaim {Type = "t", Value = "v"}; - - var claim = userClaim.ToSecurityClaim(); - - Expect(claim.Type, Is.EqualTo("t")); - Expect(claim.Value, Is.EqualTo("v")); - } - } -} \ No newline at end of file diff --git a/src/Tests/Properties/AssemblyInfo.cs b/src/Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 773f5c4..0000000 --- a/src/Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Tests")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("415f115b-e9ba-4bb0-ad6f-1f3aa83df9b7")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj deleted file mode 100644 index 008de45..0000000 --- a/src/Tests/Tests.csproj +++ /dev/null @@ -1,93 +0,0 @@ - - - - - Debug - AnyCPU - {62483144-11D8-4ECE-990D-17B4716AFF69} - Library - Properties - Tests - Tests - v4.5.1 - 512 - ..\ - - - true - full - false - ..\..\build\tests\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\build\tests\ - TRACE - prompt - 4 - - - - False - ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\MongoDB.Bson.2.2.0\lib\net45\MongoDB.Bson.dll - True - - - ..\packages\MongoDB.Driver.2.2.0\lib\net45\MongoDB.Driver.dll - True - - - ..\packages\MongoDB.Driver.Core.2.2.0\lib\net45\MongoDB.Driver.Core.dll - True - - - ..\packages\mongocsharpdriver.2.2.0\lib\net45\MongoDB.Driver.Legacy.dll - True - - - ..\packages\NUnit.2.6.3\lib\nunit.framework.dll - - - ..\packages\ReflectionMagic.2.0.0\lib\net40\ReflectionMagic.dll - - - - - - - - - - - - - - - - - - - - - - - {AD64128A-6855-49F7-A846-1FCDAD368C55} - AspNet.Identity.MongoDB - - - - - \ No newline at end of file diff --git a/src/Tests/UserHelpers.cs b/src/Tests/UserHelpers.cs deleted file mode 100644 index 319ec14..0000000 --- a/src/Tests/UserHelpers.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Tests -{ - using ReflectionMagic; - - public static class UserHelpers - { - public static void SetId(this object instance, object value) - { - // note: nice to keep reflection code isolated in one place - instance.AsDynamic().Id = value; - } - } -} \ No newline at end of file diff --git a/src/Tests/packages.config b/src/Tests/packages.config deleted file mode 100644 index 92ef483..0000000 --- a/src/Tests/packages.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file