first commit

This commit is contained in:
alexandre-spieser
2017-10-22 00:24:46 +00:00
commit 0dc4240d2c
69 changed files with 17455 additions and 0 deletions
+299
View File
@@ -0,0 +1,299 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Typescript v1 declaration files
typings/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
+41
View File
@@ -0,0 +1,41 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.Identity.MongoDbCore", "src\AspNetCore.Identity.MongoDbCore.csproj", "{C2EB6500-79A2-410E-8BF7-ACACE86C7A0E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{059DB57E-6EC2-4F6D-BE0F-343B00976DA9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7819C5A0-617F-488D-9791-2C4859B9BF19}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.Identity.MongoDbCore.IntegrationTests", "test\AspNetCore.Identity.MongoDbCore.IntegrationTests\AspNetCore.Identity.MongoDbCore.IntegrationTests.csproj", "{FA1D3288-90E8-4831-B533-A2B3D4F034B1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{EE14D20F-C6E9-4A4C-A13E-C1A90BCA8763}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C2EB6500-79A2-410E-8BF7-ACACE86C7A0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2EB6500-79A2-410E-8BF7-ACACE86C7A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2EB6500-79A2-410E-8BF7-ACACE86C7A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2EB6500-79A2-410E-8BF7-ACACE86C7A0E}.Release|Any CPU.Build.0 = Release|Any CPU
{FA1D3288-90E8-4831-B533-A2B3D4F034B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA1D3288-90E8-4831-B533-A2B3D4F034B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA1D3288-90E8-4831-B533-A2B3D4F034B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA1D3288-90E8-4831-B533-A2B3D4F034B1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C2EB6500-79A2-410E-8BF7-ACACE86C7A0E} = {059DB57E-6EC2-4F6D-BE0F-343B00976DA9}
{FA1D3288-90E8-4831-B533-A2B3D4F034B1} = {7819C5A0-617F-488D-9791-2C4859B9BF19}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B6A5D462-4085-442D-9E31-DD2CAD280D3C}
EndGlobalSection
EndGlobal
+115
View File
@@ -0,0 +1,115 @@
# Microsoft.AspNetCore.Identity.MongoDbCore
A MongoDb UserStore and RoleStore adapter for Microsoft.AspNetCore.Identity 2.0.
Allows you to use MongoDb instead of SQL server with Microsoft.AspNetCore.Identity 2.0.
Covered by 730+ integration tests and unit tests from the modified Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test test suite.
# Usage examples
Your user and role entities must inherit from MongoIdentityUser<Guid> and MongoIdentityRole<TKey> in a way similar to the IdentityUser<TKey> and the IdentityRole<TKey> in Microsoft.AspNetCore.Identity.
```csharp
public class ApplicationRole : MongoIdentityRole<Guid>
{
public ApplicationRole() : base()
{
}
public ApplicationRole(string roleName) : base(roleName)
{
}
}
public class ApplicationUser : MongoIdentityUser<Guid>
{
public ApplicationUser() : base()
{
}
public ApplicationUser(string userName, string email) : base(userName, email)
{
}
}
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
{
var builder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{_hostingEnvironment.EnvironmentName}.json", optional: true);
Configuration = builder.Build();
services.AddOptions();
// add a global config object
services.AddSingleton(Configuration);
var mongoSettings = Configuration.GetSection(nameof(MongoDbSettings));
var settings = Configuration.GetSection(nameof(MongoDbSettings)).Get<MongoDbSettings>();
var mongoDbIdentityConfiguration = new MongoDbIdentityConfiguration
{
MongoDbSettings = settings,
IdentityOptionsAction = options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// ApplicationUser settings
options.User.RequireUniqueEmail = true;
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@.-_";
}
};
services.ConfigureMongoDbIdentity<ApplicationUser, ApplicationRole, Guid>(mongoDbIdentityConfiguration);
}
```
## Author
**Alexandre Spieser**
## Donations
Feeling like my work is worth a coffee?
Donations are welcome and will go towards further development of this project as well as other MongoDb related projects. Use the wallet address below to donate.
BTC Donations: 1Qc5ZpNA7g66KEEMcz7MXxwNyyoRyKJJZ
*Thank you for your support and generosity!*
## License
mongodb-generic-repository is under MIT license - http://www.opensource.org/licenses/mit-license.php
The MIT License (MIT)
Copyright (c) 2016-2017 Alexandre Spieser
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## Copyright
Copyright © 2017
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile>bin\Release\netcoreapp2.0\AspNetCore.Identity.MongoDbCore.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Debug\netcoreapp2.0\AspNetCore.Identity.MongoDbCore.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<!--<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />-->
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="2.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="MongoDbGenericRepository" Version="1.3.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>ResXFileCodeGenerator</Generator>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>AspNetCore.Identity.MongoDbCore</id>
<version>1.0.3</version>
<title>AspNetCore.Identity.MongoDbCore</title>
<authors>Alexandre Spieser</authors>
<owners>Alexandre Spieser</owners>
<licenseUrl>http://www.opensource.org/licenses/mit-license.php</licenseUrl>
<projectUrl>https://github.com/alexandre-spieser/AspNetCore.Identity.MongoDbCore</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>A MongoDb UserStore and RoleStore adapter for Microsoft.AspNetCore.Identity 2.0.</description>
<releaseNotes>First release.</releaseNotes>
<copyright>Copyright 2017 (c) Alexandre Spieser. All rights reserved.</copyright>
<tags>aspnetcore mongo mongodb identity membership</tags>
</metadata>
<files>
<file src="lib\**" target="lib" />
</files>
</package>
+122
View File
@@ -0,0 +1,122 @@
using AspNetCore.Identity.MongoDbCore.Interfaces;
using AspNetCore.Identity.MongoDbCore.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
namespace AspNetCore.Identity.MongoDbCore.Extensions
{
/// <summary>
/// The extensions for an object that holds claims.
/// </summary>
public static class ClaimHolderExtensions
{
public static MongoClaim ToMongoClaim(this Claim claim)
{
return new MongoClaim
{
Type = claim.Type,
Value = claim.Value,
Issuer = claim.Issuer
};
}
public static Claim ToClaim(this MongoClaim claim)
{
return new Claim(claim.Type, claim.Value, null, claim.Issuer);
}
/// <summary>
/// Adds a claim to a claim holder, implementing <see cref="IClaimHolder"/>.
/// </summary>
/// <param name="claimHolder">The object holding claims.</param>
/// <param name="claim">The claim you want to add.</param>
/// <returns>Returns true if the claim was added.</returns>
public static bool AddClaim(this IClaimHolder claimHolder, Claim claim)
{
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
// prevent adding duplicate claims
if (claimHolder.HasClaim(claim))
{
return false;
}
claimHolder.Claims.Add(claim.ToMongoClaim());
return true;
}
/// <summary>
/// Replaces a claim on a claim holder, implementing <see cref="IClaimHolder"/>.
/// </summary>
/// <param name="claimHolder">The object holding claims.</param>
/// <param name="claim">The claim you want to replace.</param>
/// <param name="newClaim">The new claim you want to set.</param>
/// <returns>Returns true if the claim was replaced.</returns>
public static bool ReplaceClaim(this IClaimHolder claimHolder, Claim claim, Claim newClaim)
{
var replaced = false;
claimHolder.Claims.Where(uc => uc.Value == claim.Value && uc.Type == claim.Type).ToList()
.ForEach(oldClaim => {
oldClaim.Type = newClaim.Type;
oldClaim.Value = newClaim.Value;
oldClaim.Issuer = newClaim.Issuer;
replaced |= true;
});
return replaced;
}
/// <summary>
/// Checks if an object implementing <see cref="IClaimHolder"/> has a claim.
/// </summary>
/// <param name="claimHolder">The object holding claims.</param>
/// <param name="claim">The claim you want to replace.</param>
/// <returns>Returns true if the claim is present, false otherwise.</returns>
public static bool HasClaim(this IClaimHolder claimHolder, Claim claim)
{
if(claimHolder.Claims == null)
{
claimHolder.Claims = new List<MongoClaim>();
}
return claimHolder.Claims.Any(e => e.Value == claim.Value && e.Type == claim.Type);
}
public static bool RemoveClaim(this IClaimHolder claimHolder, Claim claim)
{
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
var exists = claimHolder.Claims
.FirstOrDefault(e => e.Value == claim.Value
&& e.Type == claim.Type);
if (exists != null)
{
claimHolder.Claims.Remove(exists);
return true;
}
return false;
}
public static bool RemoveClaims(this IClaimHolder claimHolder, IEnumerable<Claim> claims)
{
var someClaimsRemoved = false;
foreach (var claim in claims)
{
var matchedClaims = claimHolder.Claims.Where(uc => uc.Value == claim.Value && uc.Type == claim.Type)
.ToList();
foreach (var c in matchedClaims)
{
claimHolder.Claims.Remove(c);
someClaimsRemoved |= true;
}
}
return someClaimsRemoved;
}
}
}
@@ -0,0 +1,94 @@
using AspNetCore.Identity.MongoDbCore.Infrastructure;
using AspNetCore.Identity.MongoDbCore.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using MongoDbGenericRepository;
using System;
namespace AspNetCore.Identity.MongoDbCore.Extensions
{
public class MongoDbSettings
{
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public class MongoDbIdentityConfiguration
{
public MongoDbSettings MongoDbSettings { get; set; }
public Action<IdentityOptions> IdentityOptionsAction { get; set; }
}
public static class ServiceCollectionExtension
{
public static void ConfigureMongoDbIdentity<TUser, TKey>(
this IServiceCollection services,
MongoDbIdentityConfiguration mongoDbIdentityConfiguration,
IMongoRepository mongoRepository = null)
where TUser : MongoIdentityUser<TKey>, new()
where TKey : IEquatable<TKey>
{
services.AddSingleton<MongoDbSettings>(mongoDbIdentityConfiguration.MongoDbSettings);
services.AddSingleton<IMongoRepository>(provider =>
{
var options = provider.GetService<MongoDbSettings>();
return mongoRepository ?? new MongoRepository(options.ConnectionString, options.DatabaseName);
});
CommonMongoDbSetup<TUser, MongoIdentityRole<TKey>, TKey>(services, mongoDbIdentityConfiguration);
}
public static void ConfigureMongoDbIdentity<TUser>(this IServiceCollection services, MongoDbIdentityConfiguration mongoDbIdentityConfiguration)
where TUser : MongoIdentityUser, new()
{
services.AddSingleton<MongoDbSettings>(mongoDbIdentityConfiguration.MongoDbSettings);
services.AddSingleton<IMongoRepository>(provider =>
{
var options = provider.GetService<MongoDbSettings>();
return new MongoRepository(options.ConnectionString, options.DatabaseName);
});
CommonMongoDbSetup<TUser, MongoIdentityRole, Guid>(services, mongoDbIdentityConfiguration);
}
public static void ConfigureMongoDbIdentity<TUser, TRole, TKey>(this IServiceCollection services, MongoDbIdentityConfiguration mongoDbIdentityConfiguration,
IMongoDbContext mongoDbContext = null)
where TUser : MongoIdentityUser<TKey>, new()
where TRole : MongoIdentityRole<TKey>, new()
where TKey : IEquatable<TKey>
{
services.AddSingleton<MongoDbSettings>(mongoDbIdentityConfiguration.MongoDbSettings);
services.AddSingleton<IMongoRepository>(provider =>
{
var options = provider.GetService<MongoDbSettings>();
return mongoDbContext == null ? new MongoRepository(options.ConnectionString, options.DatabaseName) : new MongoRepository(mongoDbContext);
});
CommonMongoDbSetup<TUser, TRole, TKey>(services, mongoDbIdentityConfiguration);
}
private static void CommonMongoDbSetup<TUser, TRole, TKey>(this IServiceCollection services, MongoDbIdentityConfiguration mongoDbIdentityConfiguration)
where TUser : MongoIdentityUser<TKey>, new()
where TRole : MongoIdentityRole<TKey>, new()
where TKey : IEquatable<TKey>
{
services.AddScoped<IUserStore<TUser>>(provider =>
{
var userStore = new MongoUserStore<TUser, TRole, IMongoDbContext, TKey>(provider.GetService<IMongoRepository>().Context);
return userStore;
});
services.AddScoped<IRoleStore<TRole>>(provider =>
{
return new MongoRoleStore<TRole, IMongoDbContext, TKey>(provider.GetService<IMongoRepository>().Context);
});
services.AddIdentity<TUser, TRole>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(mongoDbIdentityConfiguration.IdentityOptionsAction);
}
}
}
+9
View File
@@ -0,0 +1,9 @@
using System;
namespace AspNetCore.Identity.MongoDbCore
{
public static class GlobalVariables
{
public static Random Random = new Random();
}
}
+35
View File
@@ -0,0 +1,35 @@
using MongoDbGenericRepository;
namespace AspNetCore.Identity.MongoDbCore.Infrastructure
{
public interface IMongoRepository : IBaseMongoRepository
{
void DropCollection<TDocument>();
void DropCollection<TDocument>(string partitionKey);
IMongoDbContext Context { get; }
}
public class MongoRepository : BaseMongoRepository, IMongoRepository
{
public MongoRepository(string connectionString, string databaseName) : base(connectionString, databaseName)
{
}
public MongoRepository(IMongoDbContext mongoDbContext) : base(mongoDbContext)
{
}
public void DropCollection<TDocument>()
{
MongoDbContext.DropCollection<TDocument>();
}
public void DropCollection<TDocument>(string partitionKey)
{
MongoDbContext.DropCollection<TDocument>(partitionKey);
}
public IMongoDbContext Context => MongoDbContext;
}
}
+13
View File
@@ -0,0 +1,13 @@
using AspNetCore.Identity.MongoDbCore.Models;
using System.Collections.Generic;
namespace AspNetCore.Identity.MongoDbCore.Interfaces
{
/// <summary>
/// The interface for an object that holds claims.
/// </summary>
public interface IClaimHolder
{
List<MongoClaim> Claims { get; set; }
}
}
+84
View File
@@ -0,0 +1,84 @@
using AspNetCore.Identity.MongoDbCore.Interfaces;
using Microsoft.AspNetCore.Identity;
using MongoDbGenericRepository.Models;
using System;
using System.Collections.Generic;
using System.Security.Claims;
namespace AspNetCore.Identity.MongoDbCore.Models
{
public class MongoDbIdentityRole : MongoIdentityRole<string>
{
public MongoDbIdentityRole() : base()
{
}
public MongoDbIdentityRole(string roleName) : base(roleName)
{
}
}
public class MongoIdentityRole : MongoIdentityRole<Guid>
{
public MongoIdentityRole() : base()
{
}
public MongoIdentityRole(string roleName) : base(roleName)
{
}
}
public class MongoIdentityRole<TKey> : IdentityRole<TKey>, IDocument<TKey>, IClaimHolder
where TKey : IEquatable<TKey>
{
private void InitializeFields()
{
Version = 1;
Claims = new List<MongoClaim>();
Guid guidValue = Guid.NewGuid();
var idTypeName = typeof(TKey).Name;
switch (idTypeName)
{
case "Guid":
Id = (TKey)(object)guidValue;
break;
case "Int32":
Id = (TKey)(object)GlobalVariables.Random.Next(1, int.MaxValue);
break;
case "String":
Id = (TKey)(object)guidValue.ToString();
break;
}
}
public MongoIdentityRole()
{
InitializeFields();
}
public MongoIdentityRole(string roleName)
{
Name = roleName;
InitializeFields();
}
public MongoIdentityRole(string name, TKey key)
{
InitializeFields();
Id = key;
Name = Name;
}
/// <summary>
/// The version of the role schema
/// </summary>
public int Version { get; set; }
/// <summary>
/// The claims associated to the role
/// </summary>
public List<MongoClaim> Claims { get; set; }
}
}
+398
View File
@@ -0,0 +1,398 @@
using System;
using System.Collections.Generic;
using MongoDbGenericRepository.Models;
using System.Linq;
using MongoDB.Driver;
using AspNetCore.Identity.MongoDbCore.Interfaces;
using Microsoft.AspNetCore.Identity;
namespace AspNetCore.Identity.MongoDbCore.Models
{
public class Token
{
/// <summary>
/// Gets or sets the LoginProvider this token is from.
/// </summary>
public string LoginProvider { get; set; }
/// <summary>
/// Gets or sets the name of the token.
public string Name { get; set; }
/// <summary>
/// Gets or sets the token value.
/// </summary>
public string Value { get; set; }
}
public class MongoClaim {
public string Type { get; set; }
public string Value { get; set; }
public string Issuer { get; set; }
}
public class UserRole
{
public object UserId { get; set; }
public object RoleId { get; set; }
}
public class MongoDbIdentityUser : MongoIdentityUser<string>
{
public MongoDbIdentityUser() : base()
{
}
public MongoDbIdentityUser(string userName) : base(userName)
{
}
public MongoDbIdentityUser(string userName, string email) : base(userName, email)
{
}
}
public class MongoIdentityUser : MongoIdentityUser<Guid>
{
public MongoIdentityUser() : base()
{
}
public MongoIdentityUser(string userName) : base(userName)
{
}
public MongoIdentityUser(string userName, string email) : base(userName, email)
{
}
}
public class MongoIdentityUser<TKey> : IdentityUser<TKey>, IDocument<TKey>, IClaimHolder
where TKey : IEquatable<TKey>
{
public int Version { get; set; }
public DateTime CreatedOn { get; private set; }
public DateTime? LockoutEndDate { get; private set; }
public DateTime? DeletedOn { get; private set; }
public List<MongoClaim> Claims { get; set; }
public List<TKey> Roles { get; set; }
public List<UserLoginInfo> Logins { get; set; }
public List<Token> Tokens { get; set; }
private void InitializeFields()
{
Claims = new List<MongoClaim>();
Logins = new List<UserLoginInfo>();
Roles = new List<TKey>();
Tokens = new List<Token>();
Guid guidValue = Guid.NewGuid();
var idTypeName = typeof(TKey).Name;
switch (idTypeName)
{
case "Guid":
Id = (TKey)(object)guidValue;
break;
case "Int32":
Id = (TKey)(object)GlobalVariables.Random.Next(1, int.MaxValue);
break;
case "String":
Id = (TKey)(object)guidValue.ToString();
break;
}
}
public MongoIdentityUser()
{
CreatedOn = DateTime.UtcNow;
SetVersion(1);
InitializeFields();
}
public MongoIdentityUser(string userName, string email) : this(userName)
{
if (email != null)
{
Email = email.ToLowerInvariant().Trim();
}
}
public MongoIdentityUser(string userName)
{
UserName = userName ?? throw new ArgumentNullException(nameof(userName));
CreatedOn = DateTime.UtcNow;
SetVersion(1);
InitializeFields();
Roles = new List<TKey>();
}
public virtual MongoIdentityUser<TKey> SetId(TKey key)
{
Id = key;
return this;
}
public virtual MongoIdentityUser<TKey> SetVersion(int version)
{
Version = 1;
return this;
}
public virtual void EnableTwoFactorAuthentication()
{
TwoFactorEnabled = true;
}
public virtual void DisableTwoFactorAuthentication()
{
TwoFactorEnabled = false;
}
public virtual void EnableLockout()
{
LockoutEnabled = true;
}
public virtual void DisableLockout()
{
LockoutEnabled = false;
}
public virtual void SetEmail(string email)
{
Email = email ?? throw new ArgumentNullException(nameof(email));
}
public virtual void SetNormalizedUserName(string normalizedUserName)
{
NormalizedUserName = normalizedUserName ?? throw new ArgumentNullException(nameof(normalizedUserName));
}
public virtual void SetPhoneNumber(string phoneNumber)
{
PhoneNumber = phoneNumber;
}
public virtual void SetPasswordHash(string passwordHash)
{
PasswordHash = passwordHash;
}
public virtual void SetSecurityStamp(string securityStamp)
{
SecurityStamp = securityStamp;
}
public virtual void SetAccessFailedCount(int accessFailedCount)
{
AccessFailedCount = accessFailedCount;
}
public virtual void ResetAccessFailedCount()
{
AccessFailedCount = 0;
}
public virtual void LockUntil(DateTime lockoutEndDate)
{
LockoutEndDate = lockoutEndDate;
}
public void Delete()
{
if (DeletedOn != null)
{
throw new InvalidOperationException($"User '{Id}' has already been deleted.");
}
DeletedOn = DateTime.UtcNow;
}
#region Role Management
public virtual IdentityUserRole<TKey> GetUserRole(TKey roleId)
{
var foundRoleId = Roles.FirstOrDefault(e => e.Equals(roleId));
if (!foundRoleId.Equals(default(TKey)))
{
return new IdentityUserRole<TKey>
{
UserId = Id,
RoleId = foundRoleId
};
}
return default(IdentityUserRole<TKey>);
}
public virtual bool RemoveRole(TKey roleId)
{
var roleClaim = Roles.FirstOrDefault(e => e.Equals(roleId));
if (!roleClaim.Equals(default(TKey)))
{
Roles.Remove(roleId);
return true;
}
return false;
}
public virtual bool AddRole(TKey roleId)
{
if (!Roles.Contains(roleId))
{
Roles.Add(roleId);
return true;
}
return false;
}
#endregion
#region Login Management
public virtual bool AddLogin(UserLoginInfo mongoUserLogin)
{
if (mongoUserLogin == null)
{
throw new ArgumentNullException(nameof(mongoUserLogin));
}
if (HasLogin(mongoUserLogin))
{
return false;
}
Logins.Add(mongoUserLogin);
return true;
}
public virtual bool HasLogin(UserLoginInfo login)
{
return Logins.Any(e => e.LoginProvider == login.LoginProvider && e.ProviderKey == e.ProviderKey);
}
public virtual void RemoveLogin(UserLoginInfo mongoUserLogin)
{
if (mongoUserLogin == null)
{
throw new ArgumentNullException(nameof(mongoUserLogin));
}
Logins.Remove(mongoUserLogin);
}
public virtual IdentityUserLogin<TKey> GetUserLogin(string loginProvider, string providerKey)
{
var login = Logins.FirstOrDefault(e => e.LoginProvider == loginProvider && e.ProviderKey == providerKey);
if (login != null)
{
return new IdentityUserLogin<TKey>
{
UserId = Id,
LoginProvider = login.LoginProvider,
ProviderDisplayName = login.ProviderDisplayName,
ProviderKey = login.ProviderKey
};
}
return default(IdentityUserLogin<TKey>);
}
#endregion
#region Token Management
/// <summary>
/// Replaces a claim on a claim holder, implementing <see cref="IClaimHolder"/>.
/// </summary>
/// <param name="claimHolder">The object holding claims.</param>
/// <param name="claim">The claim you want to replace.</param>
/// <param name="newClaim">The new claim you want to set.</param>
/// <returns>Returns true if the claim was replaced.</returns>
public bool SetToken(IdentityUserToken<TKey> tokenToset, string value)
{
var token = Tokens.FirstOrDefault(e => e.LoginProvider == tokenToset.LoginProvider && e.Name == tokenToset.Name);
if (token != null)
{
token.Value = value;
return true;
}
return false;
}
public IdentityUserToken<TKey> GetToken(string loginProvider, string name)
{
var token = Tokens.FirstOrDefault(e => e.LoginProvider == loginProvider && e.Name == name);
if (token != null)
{
return new IdentityUserToken<TKey>
{
UserId = Id,
LoginProvider = token.LoginProvider,
Name = token.Name,
Value = token.Value
};
}
return default(IdentityUserToken<TKey>);
}
public bool HasToken(IdentityUserToken<TKey> token)
{
return Tokens.Any(e => e.LoginProvider == token.LoginProvider
&& e.Name == token.Name
&& e.Value == token.Value);
}
public bool AddOrSet(IdentityUserToken<TKey> token)
{
var exists = GetToken(token.LoginProvider, token.Name);
if (exists != null && exists.Value != token.Value)
{
return SetToken(exists, token.Value);
}
if (exists == null)
{
Tokens.Add(new Token
{
LoginProvider = token.LoginProvider,
Name = token.Name,
Value = token.Value
});
return true;
}
return false;
}
public bool AddUserToken<TUserToken>(TUserToken token) where TUserToken : IdentityUserToken<TKey>
{
if (HasToken(token))
{
return false;
}
Tokens.Add(new Token
{
LoginProvider = token.LoginProvider,
Name = token.Name,
Value = token.Value
});
return true;
}
public bool RemoveUserToken<TUserToken>(TUserToken token) where TUserToken : IdentityUserToken<TKey>
{
var exists = Tokens.FirstOrDefault(e => e.LoginProvider == token.LoginProvider && e.Name == token.Name);
if (exists == null)
{
return false;
}
Tokens.Remove(exists);
return true;
}
#endregion Token Management
}
}
+49
View File
@@ -0,0 +1,49 @@
using AspNetCore.Identity.MongoDbCore.Models;
using MongoDB.Bson.Serialization.Conventions;
using System;
using System.Threading;
namespace AspNetCore.Identity.MongoDbCore
{
internal static class MongoConfig
{
private static bool _initialized = false;
private static object _initializationLock = new object();
private static object _initializationTarget;
public static void EnsureConfigured()
{
EnsureConfiguredImpl();
}
private static void EnsureConfiguredImpl()
{
LazyInitializer.EnsureInitialized(ref _initializationTarget, ref _initialized, ref _initializationLock, () =>
{
Configure();
return null;
});
}
private static void Configure()
{
RegisterConventions();
}
private static void RegisterConventions()
{
var pack = new ConventionPack
{
new IgnoreIfNullConvention(false),
new CamelCaseElementNameConvention(),
};
ConventionRegistry.Register("AspNetCore.Identity.MongoDB", pack, IsConventionApplicable);
}
private static bool IsConventionApplicable(Type type)
{
return type == typeof(MongoIdentityUser<>);
}
}
}
+89
View File
@@ -0,0 +1,89 @@
using AspNetCore.Identity.MongoDbCore.Infrastructure;
using AspNetCore.Identity.MongoDbCore.Models;
using MongoDB.Driver;
using MongoDbGenericRepository;
using System;
namespace AspNetCore.Identity.MongoDbCore
{
public class MongoIdentityDbContext : MongoDbContext
{
public MongoIdentityDbContext(string connectionString, string databaseName) : base(connectionString, databaseName)
{
}
public IMongoRepository MongoRepository { get; }
public IMongoCollection<MongoIdentityUser> Users
{
get
{
return GetCollection<MongoIdentityUser>();
}
}
public IMongoCollection<MongoIdentityRole> Roles
{
get
{
return GetCollection<MongoIdentityRole>();
}
}
}
public class MongoIdentityDbContext<TUser> : MongoDbContext
where TUser : MongoIdentityUser
{
public MongoIdentityDbContext(string connectionString, string databaseName) : base(connectionString, databaseName)
{
}
public IMongoRepository MongoRepository { get; }
public IMongoCollection<TUser> Users
{
get
{
return GetCollection<TUser>();
}
}
public IMongoCollection<MongoIdentityRole> Roles
{
get
{
return GetCollection<MongoIdentityRole>();
}
}
}
public class MongoIdentityDbContext<TUser, TRole, TKey> : MongoDbContext
where TUser : MongoIdentityUser<TKey>
where TRole : MongoIdentityRole<TKey>
where TKey : IEquatable<TKey>
{
public MongoIdentityDbContext(string connectionString, string databaseName) : base(connectionString, databaseName)
{
}
public IMongoCollection<TUser> Users
{
get
{
return GetCollection<TUser>();
}
}
public IMongoCollection<TRole> Roles
{
get
{
return GetCollection<TRole>();
}
}
}
}
@@ -0,0 +1,59 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Identity;
namespace AspNetCore.Identity.MongoDbCore
{
/// <summary>
/// Represents the password hashing options
/// </summary>
public sealed class PasswordHasherOptionsAccessor : IOptions<PasswordHasherOptions>
{
/// <summary>
/// Gets options which use the IdentityV3 compat mode, and set the iteration count to 200000 PBKDF2-SHA256 iterations
/// (roughly 200ms of work)
/// </summary>
public PasswordHasherOptions Value { get; } = new PasswordHasherOptions
{
CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV3,
IterationCount = 200000
};
}
public static class MongoIdentityServiceCollectionExtensions
{
public static IdentityBuilder AddIdentity<TUser>(this IServiceCollection services)
where TUser : class => services.AddIdentity<TUser>(null);
public static IdentityBuilder AddIdentity<TUser>(this IServiceCollection services, Action<IdentityOptions> setupAction)
where TUser : class
{
// Hosting doesn't add IHttpContextAccessor by default
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Identity services
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser>>();
services.TryAddScoped<UserManager<TUser>, AspNetUserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>();
if (setupAction != null)
{
services.Configure(setupAction);
}
return new IdentityBuilder(typeof(TUser), services);
}
}
}
+463
View File
@@ -0,0 +1,463 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using MongoDbGenericRepository;
using MongoDB.Driver;
using AspNetCore.Identity.MongoDbCore.Extensions;
using AspNetCore.Identity.MongoDbCore.Models;
using AspNetCore.Identity.MongoDbCore.Infrastructure;
using Microsoft.AspNetCore.Identity;
namespace AspNetCore.Identity.MongoDbCore
{
/// <summary>
/// Creates a new instance of a persistence store for roles.
/// </summary>
/// <typeparam name="TRole">The type of the class representing a role</typeparam>
public class MongoRoleStore<TRole> : MongoRoleStore<TRole, MongoDbContext, string>
where TRole : MongoIdentityRole<string>
{
/// <summary>
/// Constructs a new instance of <see cref="MongoRoleStore{TRole}"/>.
/// </summary>
/// <param name="context">The <see cref="IMongoDbContext"/>.</param>
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
public MongoRoleStore(IMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
/// <summary>
/// Creates a new instance of a persistence store for roles.
/// </summary>
/// <typeparam name="TRole">The type of the class representing a role.</typeparam>
/// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
public class MongoRoleStore<TRole, TContext> : MongoRoleStore<TRole, TContext, string>
where TRole : MongoIdentityRole<string>
where TContext : IMongoDbContext
{
/// <summary>
/// Constructs a new instance of <see cref="MongoRoleStore{TRole, TContext}"/>.
/// </summary>
/// <param name="context">The <see cref="IMongoDbContext"/>.</param>
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
public MongoRoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
/// <summary>
/// Creates a new instance of a persistence store for roles.
/// </summary>
/// <typeparam name="TRole">The type of the class representing a role.</typeparam>
/// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
/// <typeparam name="TKey">The type of the primary key for a role.</typeparam>
public class MongoRoleStore<TRole, TContext, TKey> : MongoRoleStore<TRole, TContext, TKey, IdentityUserRole<TKey>, IdentityRoleClaim<TKey>>,
IQueryableRoleStore<TRole>,
IRoleClaimStore<TRole>
where TRole : MongoIdentityRole<TKey>
where TKey : IEquatable<TKey>
where TContext : IMongoDbContext
{
/// <summary>
/// Constructs a new instance of <see cref="MongoRoleStore{TRole, TContext, TKey}"/>.
/// </summary>
/// <param name="context">The <see cref="IMongoDbContext"/>.</param>
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
public MongoRoleStore(IMongoDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
/// <summary>
/// Creates a new instance of a persistence store for roles.
/// </summary>
/// <typeparam name="TRole">The type of the class representing a role.</typeparam>
/// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
/// <typeparam name="TKey">The type of the primary key for a role.</typeparam>
/// <typeparam name="TUserRole">The type of the class representing a user role.</typeparam>
/// <typeparam name="TRoleClaim">The type of the class representing a role claim.</typeparam>
public class MongoRoleStore<TRole, TContext, TKey, TUserRole, TRoleClaim> :
IQueryableRoleStore<TRole>,
IRoleClaimStore<TRole>
where TRole : MongoIdentityRole<TKey>
where TKey : IEquatable<TKey>
where TContext : IMongoDbContext
where TUserRole : IdentityUserRole<TKey>, new()
where TRoleClaim : IdentityRoleClaim<TKey>, new()
{
/// <summary>
/// Constructs a new instance of <see cref="MongoRoleStore{TRole, TContext, TKey, TUserRole, TRoleClaim}"/>.
/// </summary>
/// <param name="context">The <see cref="IMongoDbContext"/>.</param>
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
public MongoRoleStore(IMongoDbContext context, IdentityErrorDescriber describer = null)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Context = context;
ErrorDescriber = describer ?? new IdentityErrorDescriber();
}
private bool _disposed;
/// <summary>
/// Gets the database context for this store.
/// </summary>
private static IMongoDbContext Context { get; set; }
private static IMongoRepository _mongoRepository;
private static IMongoRepository MongoRepository
{
get
{
if (_mongoRepository == null)
{
_mongoRepository = new MongoRepository(Context);
}
return _mongoRepository;
}
}
/// <summary>
/// Gets or sets the <see cref="IdentityErrorDescriber"/> for any error that occurred with the current operation.
/// </summary>
public IdentityErrorDescriber ErrorDescriber { get; set; }
/// <summary>
/// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called.
/// </summary>
/// <value>
/// True if changes should be automatically persisted, otherwise false.
/// </value>
public bool AutoSaveChanges { get; set; } = true;
/// <summary>
/// Creates a new role in a store as an asynchronous operation.
/// </summary>
/// <param name="role">The role to create in the store.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="IdentityResult"/> of the asynchronous query.</returns>
public async virtual Task<IdentityResult> CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
await MongoRepository.AddOneAsync<TRole, TKey>(role);
return IdentityResult.Success;
}
/// <summary>
/// Updates a role in a store as an asynchronous operation.
/// </summary>
/// <param name="role">The role to update in the store.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="IdentityResult"/> of the asynchronous query.</returns>
public async virtual Task<IdentityResult> UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
var oldStamp = role.ConcurrencyStamp;
role.ConcurrencyStamp = Guid.NewGuid().ToString();
var collection = MongoRepository.Context.GetCollection<TRole>();
var updateRes = await collection.ReplaceOneAsync(x => x.Id.Equals(role.Id)
&& x.ConcurrencyStamp.Equals(oldStamp),
role);
if (updateRes.ModifiedCount == 0)
{
return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
}
return IdentityResult.Success;
}
/// <summary>
/// Deletes a role from the store as an asynchronous operation.
/// </summary>
/// <param name="role">The role to delete from the store.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="IdentityResult"/> of the asynchronous query.</returns>
public async virtual Task<IdentityResult> DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
var oldStamp = role.ConcurrencyStamp;
role.ConcurrencyStamp = Guid.NewGuid().ToString();
var collection = MongoRepository.Context.GetCollection<TRole>();
var deleteRes = await collection.DeleteOneAsync(x => x.Id.Equals(role.Id)
&& x.ConcurrencyStamp.Equals(oldStamp));
if (deleteRes.DeletedCount == 0)
{
return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
}
return IdentityResult.Success;
}
/// <summary>
/// Gets the ID for a role from the store as an asynchronous operation.
/// </summary>
/// <param name="role">The role whose ID should be returned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that contains the ID of the role.</returns>
public virtual Task<string> GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
return Task.FromResult(ConvertIdToString(role.Id));
}
/// <summary>
/// Gets the name of a role from the store as an asynchronous operation.
/// </summary>
/// <param name="role">The role whose name should be returned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that contains the name of the role.</returns>
public virtual Task<string> GetRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
return Task.FromResult(role.Name);
}
/// <summary>
/// Sets the name of a role in the store as an asynchronous operation.
/// </summary>
/// <param name="role">The role whose name should be set.</param>
/// <param name="roleName">The name of the role.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
public virtual Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
if (role.Name != roleName)
{
role.Name = roleName;
return MongoRepository.UpdateOneAsync<TRole, TKey, string>(role, x => x.Name, role.Name);
}
return Task.CompletedTask;
}
/// <summary>
/// Converts the provided <paramref name="id"/> to a strongly typed key object.
/// </summary>
/// <param name="id">The id to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided <paramref name="id"/>.</returns>
public virtual TKey ConvertIdFromString(string id)
{
if (id == null)
{
return default(TKey);
}
return (TKey)TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(id);
}
/// <summary>
/// Converts the provided <paramref name="id"/> to its string representation.
/// </summary>
/// <param name="id">The id to convert.</param>
/// <returns>An <see cref="string"/> representation of the provided <paramref name="id"/>.</returns>
public virtual string ConvertIdToString(TKey id)
{
if (id == null)
{
return null;
}
if (id.Equals(default(TKey)))
{
return null;
}
return id.ToString();
}
/// <summary>
/// Finds the role who has the specified ID as an asynchronous operation.
/// </summary>
/// <param name="id">The role ID to look for.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that result of the look up.</returns>
public virtual Task<TRole> FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var roleId = ConvertIdFromString(id);
return MongoRepository.GetOneAsync<TRole, TKey>(u => u.Id.Equals(roleId));
}
/// <summary>
/// Finds the role who has the specified normalized name as an asynchronous operation.
/// </summary>
/// <param name="normalizedName">The normalized role name to look for.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that result of the look up.</returns>
public virtual Task<TRole> FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
return MongoRepository.GetOneAsync<TRole, TKey>(r => r.NormalizedName == normalizedName);
}
/// <summary>
/// Get a role's normalized name as an asynchronous operation.
/// </summary>
/// <param name="role">The role whose normalized name should be retrieved.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that contains the name of the role.</returns>
public virtual Task<string> GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
return Task.FromResult(role.NormalizedName);
}
/// <summary>
/// Set a role's normalized name as an asynchronous operation.
/// </summary>
/// <param name="role">The role whose normalized name should be set.</param>
/// <param name="normalizedName">The normalized name to set</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
public virtual Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
if (role.NormalizedName != normalizedName)
{
role.NormalizedName = normalizedName;
return MongoRepository.UpdateOneAsync<TRole, TKey, string>(role, x => x.NormalizedName, role.NormalizedName);
}
return Task.CompletedTask;
}
/// <summary>
/// Throws if this class has been disposed.
/// </summary>
protected void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
/// <summary>
/// Dispose the stores
/// </summary>
public void Dispose() => _disposed = true;
/// <summary>
/// Get the claims associated with the specified <paramref name="role"/> as an asynchronous operation.
/// </summary>
/// <param name="role">The role whose claims should be retrieved.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> that contains the claims granted to a role.</returns>
public async virtual Task<IList<Claim>> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
return role.Claims.Select(e => e.ToClaim()).ToList();
}
/// <summary>
/// Adds the <paramref name="claim"/> given to the specified <paramref name="role"/>.
/// </summary>
/// <param name="role">The role to add the claim to.</param>
/// <param name="claim">The claim to add to the role.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
public virtual Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
if (role.AddClaim(claim))
{
MongoRepository.UpdateOne<TRole, TKey, List<MongoClaim>>(role, e => e.Claims, role.Claims);
}
return Task.FromResult(false);
}
/// <summary>
/// Removes the <paramref name="claim"/> given from the specified <paramref name="role"/>.
/// </summary>
/// <param name="role">The role to remove the claim from.</param>
/// <param name="claim">The claim to remove from the role.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
public async virtual Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
if (role.RemoveClaim(claim))
{
await MongoRepository.UpdateOneAsync<TRole, TKey, List<MongoClaim>>(role, e => e.Claims, role.Claims);
}
}
/// <summary>
/// A navigation property for the roles the store contains.
/// </summary>
public virtual IQueryable<TRole> Roles => Context.GetCollection<TRole>().AsQueryable();
/// <summary>
/// Creates a entity representing a role claim.
/// </summary>
/// <param name="role">The associated role.</param>
/// <param name="claim">The associated claim.</param>
/// <returns>The role claim entity.</returns>
protected virtual TRoleClaim CreateRoleClaim(TRole role, Claim claim)
=> new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value };
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
//------------------------------------------------------------------------------
// <auto-generated>
// Ce code a été généré par un outil.
// Version du runtime :4.0.30319.42000
//
// Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si
// le code est régénéré.
// </auto-generated>
//------------------------------------------------------------------------------
namespace AspNetCore.Identity.MongoDbCore {
using System;
/// <summary>
/// Une classe de ressource fortement typée destinée, entre autres, à la consultation des chaînes localisées.
/// </summary>
// Cette classe a été générée automatiquement par la classe StronglyTypedResourceBuilder
// à l'aide d'un outil, tel que ResGen ou Visual Studio.
// Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen
// avec l'option /str ou régénérez votre projet VS.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Retourne l'instance ResourceManager mise en cache utilisée par cette classe.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AspNetCore.Identity.MongoDbCore.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Remplace la propriété CurrentUICulture du thread actuel pour toutes
/// les recherches de ressources à l'aide de cette classe de ressource fortement typée.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à AddEntityFrameworkStores can only be called with a role that derives from IdentityRole&lt;TKey, TUserRole, TRoleClaim&gt;..
/// </summary>
internal static string NotIdentityRole {
get {
return ResourceManager.GetString("NotIdentityRole", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à AddEntityFrameworkStores can only be called with a user that derives from IdentityUser&lt;TKey, TUserClaim, TUserRole, TUserLogin, TUserToken&gt;..
/// </summary>
internal static string NotIdentityUser {
get {
return ResourceManager.GetString("NotIdentityUser", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Role {0} does not exist..
/// </summary>
internal static string RoleNotFound {
get {
return ResourceManager.GetString("RoleNotFound", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Value cannot be null or empty..
/// </summary>
internal static string ValueCannotBeNullOrEmpty {
get {
return ResourceManager.GetString("ValueCannotBeNullOrEmpty", resourceCulture);
}
}
}
}
+136
View File
@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="NotIdentityRole" xml:space="preserve">
<value>AddEntityFrameworkStores can only be called with a role that derives from IdentityRole&lt;TKey, TUserRole, TRoleClaim&gt;.</value>
<comment>error when the role does not derive from IdentityRole</comment>
</data>
<data name="NotIdentityUser" xml:space="preserve">
<value>AddEntityFrameworkStores can only be called with a user that derives from IdentityUser&lt;TKey, TUserClaim, TUserRole, TUserLogin, TUserToken&gt;.</value>
<comment>error when the user does not derive from IdentityUser</comment>
</data>
<data name="RoleNotFound" xml:space="preserve">
<value>Role {0} does not exist.</value>
<comment>error when a role does not exist</comment>
</data>
<data name="ValueCannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or empty.</value>
<comment>error when something cannot be null or empty</comment>
</data>
</root>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+136
View File
@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="NotIdentityRole" xml:space="preserve">
<value>AddEntityFrameworkStores can only be called with a role that derives from IdentityRole&lt;TKey, TUserRole, TRoleClaim&gt;.</value>
<comment>error when the role does not derive from IdentityRole</comment>
</data>
<data name="NotIdentityUser" xml:space="preserve">
<value>AddEntityFrameworkStores can only be called with a user that derives from IdentityUser&lt;TKey, TUserClaim, TUserRole, TUserLogin, TUserToken&gt;.</value>
<comment>error when the user does not derive from IdentityUser</comment>
</data>
<data name="RoleNotFound" xml:space="preserve">
<value>Role {0} does not exist.</value>
<comment>error when a role does not exist</comment>
</data>
<data name="ValueCannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or empty.</value>
<comment>error when something cannot be null or empty</comment>
</data>
</root>
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.Identity.MongoDbCore" Version="1.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="MongoDbGenericRepository" Version="1.3.0" />
<PackageReference Include="Moq" Version="4.7.142" />
<PackageReference Include="xunit" Version="2.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using Microsoft.AspNetCore.Identity.Test;
using Microsoft.AspNetCore.Identity;
namespace AspNetCore.Identity.MongoDbCore.Test
{
public class ApiConsistencyTest : ApiConsistencyTestBase
{
protected override Assembly TargetAssembly => typeof(IdentityUser).GetTypeInfo().Assembly;
}
}
@@ -0,0 +1,150 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace AspNetCore.Identity.MongoDbCore.Test
{
//public class CustomPocoTest : IClassFixture<MongoDatabaseFixture>
//{
// private readonly MongoDatabaseFixture _fixture;
// public CustomPocoTest(MongoDatabaseFixture fixture)
// {
// _fixture = fixture;
// }
// public class User<TKey> where TKey : IEquatable<TKey>
// {
// public TKey Id { get; set; }
// public string UserName { get; set; }
// }
// public class CustomDbContext<TKey> : DbContext where TKey : IEquatable<TKey>
// {
// public CustomDbContext(DbContextOptions options) : base(options)
// { }
// public DbSet<User<TKey>> Users { get; set; }
// }
// public CustomDbContext<TKey> GetContext<TKey>() where TKey : IEquatable<TKey>
// {
// return DbUtil.Create<CustomDbContext<TKey>>(_fixture.ConnectionString);
// }
// public CustomDbContext<TKey> CreateContext<TKey>(bool delete = false) where TKey : IEquatable<TKey>
// {
// var db = GetContext<TKey>();
// if (delete)
// {
// db.Database.EnsureDeleted();
// }
// db.Database.EnsureCreated();
// return db;
// }
// [Fact]
// public async Task CanUpdateNameGuid()
// {
// using (var db = CreateContext<Guid>(true))
// {
// var oldName = Guid.NewGuid().ToString();
// var user = new User<Guid> { UserName = oldName, Id = Guid.NewGuid() };
// db.Users.Add(user);
// await db.SaveChangesAsync();
// var newName = Guid.NewGuid().ToString();
// user.UserName = newName;
// await db.SaveChangesAsync();
// Assert.Null(db.Users.SingleOrDefault(u => u.UserName == oldName));
// Assert.Equal(user, db.Users.Single(u => u.UserName == newName));
// }
// }
// [Fact]
// public async Task CanUpdateNameString()
// {
// using (var db = CreateContext<string>(true))
// {
// var oldName = Guid.NewGuid().ToString();
// var user = new User<string> { UserName = oldName, Id = Guid.NewGuid().ToString() };
// db.Users.Add(user);
// await db.SaveChangesAsync();
// var newName = Guid.NewGuid().ToString();
// user.UserName = newName;
// await db.SaveChangesAsync();
// Assert.Null(db.Users.SingleOrDefault(u => u.UserName == oldName));
// Assert.Equal(user, db.Users.Single(u => u.UserName == newName));
// }
// }
// [Fact]
// public async Task CanCreateUserInt()
// {
// using (var db = CreateContext<int>(true))
// {
// var user = new User<int>();
// db.Users.Add(user);
// await db.SaveChangesAsync();
// user.UserName = "Boo";
// await db.SaveChangesAsync();
// var fetch = db.Users.First(u => u.UserName == "Boo");
// Assert.Equal(user, fetch);
// }
// }
// [Fact]
// public async Task CanCreateUserIntViaSet()
// {
// using (var db = CreateContext<int>(true))
// {
// var user = new User<int>();
// var users = db.Set<User<int>>();
// users.Add(user);
// await db.SaveChangesAsync();
// user.UserName = "Boo";
// await db.SaveChangesAsync();
// var fetch = users.First(u => u.UserName == "Boo");
// Assert.Equal(user, fetch);
// }
// }
// [Fact]
// public async Task CanUpdateNameInt()
// {
// using (var db = CreateContext<int>(true))
// {
// var oldName = Guid.NewGuid().ToString();
// var user = new User<int> { UserName = oldName };
// db.Users.Add(user);
// await db.SaveChangesAsync();
// var newName = Guid.NewGuid().ToString();
// user.UserName = newName;
// await db.SaveChangesAsync();
// Assert.Null(db.Users.SingleOrDefault(u => u.UserName == oldName));
// Assert.Equal(user, db.Users.Single(u => u.UserName == newName));
// }
// }
// [Fact]
// public async Task CanUpdateNameIntWithSet()
// {
// using (var db = CreateContext<int>(true))
// {
// var oldName = Guid.NewGuid().ToString();
// var user = new User<int> { UserName = oldName };
// db.Set<User<int>>().Add(user);
// await db.SaveChangesAsync();
// var newName = Guid.NewGuid().ToString();
// user.UserName = newName;
// await db.SaveChangesAsync();
// Assert.Null(db.Set<User<int>>().SingleOrDefault(u => u.UserName == oldName));
// Assert.Equal(user, db.Set<User<int>>().Single(u => u.UserName == newName));
// }
// }
//}
}
@@ -0,0 +1,190 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.Test;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure;
using MongoDbGenericRepository;
using AspNetCore.Identity.MongoDbCore.Models;
using AspNetCore.Identity.MongoDbCore;
using MongoDB.Driver;
namespace AspNetCore.Identity.MongoDbCore.Test
{
public abstract class SqlStoreOnlyUsersTestBase<TUser, TKey> : UserManagerSpecificationTestBase<TUser, TKey>, IClassFixture<MongoDatabaseFixture<TUser, TKey>>
where TUser : MongoIdentityUser<TKey>, new()
where TKey : IEquatable<TKey>
{
private readonly MongoDatabaseFixture<TUser, TKey> _fixture;
protected SqlStoreOnlyUsersTestBase(MongoDatabaseFixture<TUser, TKey> fixture)
{
_fixture = fixture;
}
protected override bool ShouldSkipDbTests()
{
return false;
}
protected override TUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "",
bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false)
{
var user = new TUser
{
UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()),
Email = email,
PhoneNumber = phoneNumber,
LockoutEnabled = lockoutEnabled,
LockoutEnd = lockoutEnd
};
_fixture.UsersToDelete.Add(user);
return user;
}
protected override Expression<Func<TUser, bool>> UserNameEqualsPredicate(string userName) => u => u.UserName == userName;
protected override Expression<Func<TUser, bool>> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName);
public IMongoDbContext CreateContext()
{
return Container.MongoRepository.Context;
}
protected override void AddUserStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IUserStore<TUser>>(new MongoUserOnlyStore<TUser, IMongoDbContext, TKey>(Container.MongoRepository.Context));
}
protected override void SetUserPasswordHash(TUser user, string hashedPassword)
{
user.PasswordHash = hashedPassword;
}
[Fact]
public async Task DeleteUserRemovesTokensTest()
{
// Need fail if not empty?
var userMgr = CreateManager();
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user));
IdentityResultAssert.IsSuccess(await userMgr.SetAuthenticationTokenAsync(user, "provider", "test", "value"));
Assert.Equal("value", await userMgr.GetAuthenticationTokenAsync(user, "provider", "test"));
IdentityResultAssert.IsSuccess(await userMgr.DeleteAsync(user));
Assert.Null(await userMgr.GetAuthenticationTokenAsync(user, "provider", "test"));
}
private IQueryable<TUser> GetQueryable()
{
return Container.MongoRepository.Context.GetCollection<TUser>().AsQueryable();
}
[Fact]
public void CanCreateUserUsingEF()
{
var user = CreateTestUser();
Container.MongoRepository.AddOne<TUser, TKey>(user);
Assert.True(GetQueryable().Any(u => u.UserName == user.UserName));
Assert.NotNull(GetQueryable().FirstOrDefault(u => u.UserName == user.UserName));
}
[Fact]
public async Task CanCreateUsingManager()
{
var manager = CreateManager();
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user));
}
private async Task LazyLoadTestSetup(IMongoDbContext db, TUser user)
{
var context = CreateContext();
var manager = CreateManager(context);
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, new UserLoginInfo("provider", user.Id.ToString(), "display")));
Claim[] userClaims =
{
new Claim("Whatever", "Value"),
new Claim("Whatever2", "Value2")
};
foreach (var c in userClaims)
{
IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c));
}
}
[Fact]
public async Task LoadFromDbFindByIdTest()
{
var db = CreateContext();
var user = CreateTestUser();
await LazyLoadTestSetup(db, user);
db = CreateContext();
var manager = CreateManager(db);
var userById = await manager.FindByIdAsync(user.Id.ToString());
Assert.Equal(2, (await manager.GetClaimsAsync(userById)).Count);
Assert.Equal(1, (await manager.GetLoginsAsync(userById)).Count);
Assert.Equal(2, (await manager.GetRolesAsync(userById)).Count);
}
[Fact]
public async Task LoadFromDbFindByNameTest()
{
var db = CreateContext();
var user = CreateTestUser();
await LazyLoadTestSetup(db, user);
db = CreateContext();
var manager = CreateManager(db);
var userByName = await manager.FindByNameAsync(user.UserName);
Assert.Equal(2, (await manager.GetClaimsAsync(userByName)).Count);
Assert.Equal(1, (await manager.GetLoginsAsync(userByName)).Count);
Assert.Equal(2, (await manager.GetRolesAsync(userByName)).Count);
}
[Fact]
public async Task LoadFromDbFindByLoginTest()
{
var db = CreateContext();
var user = CreateTestUser();
await LazyLoadTestSetup(db, user);
db = CreateContext();
var manager = CreateManager(db);
var userByLogin = await manager.FindByLoginAsync("provider", user.Id.ToString());
Assert.Equal(2, (await manager.GetClaimsAsync(userByLogin)).Count);
Assert.Equal(1, (await manager.GetLoginsAsync(userByLogin)).Count);
Assert.Equal(2, (await manager.GetRolesAsync(userByLogin)).Count);
}
[Fact]
public async Task LoadFromDbFindByEmailTest()
{
var db = CreateContext();
var user = CreateTestUser();
user.Email = "fooz@fizzy.pop";
await LazyLoadTestSetup(db, user);
db = CreateContext();
var manager = CreateManager(db);
var userByEmail = await manager.FindByEmailAsync(user.Email);
Assert.Equal(2, (await manager.GetClaimsAsync(userByEmail)).Count);
Assert.Equal(1, (await manager.GetLoginsAsync(userByEmail)).Count);
Assert.Equal(2, (await manager.GetRolesAsync(userByEmail)).Count);
}
}
}
@@ -0,0 +1,264 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity.Test;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xunit;
using AspNetCore.Identity.MongoDbCore.Models;
using AspNetCore.Identity.MongoDbCore.Extensions;
using AspNetCore.Identity.MongoDbCore;
using MongoDB.Driver;
using AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure;
using MongoDbGenericRepository;
using Microsoft.AspNetCore.Identity;
namespace AspNetCore.Identity.MongoDbCore.Test
{
// TODO: Add test variation with non IdentityDbContext
public abstract class SqlStoreTestBase<TUser, TRole, TKey> : IdentitySpecificationTestBase<TUser, TRole, TKey>,
IClassFixture<MongoDatabaseFixture<TUser, TRole, TKey>>
where TUser : MongoIdentityUser<TKey>, new()
where TRole : MongoIdentityRole<TKey>, new()
where TKey : IEquatable<TKey>
{
private readonly MongoDatabaseFixture<TUser, TRole, TKey> _fixture;
protected SqlStoreTestBase(MongoDatabaseFixture<TUser, TRole, TKey> fixture)
{
_fixture = fixture;
}
protected override void SetupIdentityServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// configure the default type name
services.ConfigureMongoDbIdentity<TUser, TRole, TKey>(Container.MongoDbIdentityConfiguration, Container.MongoRepository.Context);
services.AddLogging();
services.AddSingleton<ILogger<UserManager<TUser>>>(new TestLogger<UserManager<TUser>>());
services.AddSingleton<ILogger<RoleManager<TRole>>>(new TestLogger<RoleManager<TRole>>());
}
protected override bool ShouldSkipDbTests()
{
return false;
}
protected override TUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "",
bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false)
{
var user = new TUser
{
UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()),
Email = email,
PhoneNumber = phoneNumber,
LockoutEnabled = lockoutEnabled,
LockoutEnd = lockoutEnd
};
_fixture.UsersToDelete.Add(user);
return user;
}
protected override TRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false)
{
var roleName = useRoleNamePrefixAsRoleName ? roleNamePrefix : string.Format("{0}{1}", roleNamePrefix, Guid.NewGuid());
var role = new TRole() { Name = roleName };
_fixture.RolesToDelete.Add(role);
return role;
}
protected override Expression<Func<TRole, bool>> RoleNameEqualsPredicate(string roleName) => r => r.Name == roleName;
protected override Expression<Func<TUser, bool>> UserNameEqualsPredicate(string userName) => u => u.UserName == userName;
protected override Expression<Func<TRole, bool>> RoleNameStartsWithPredicate(string roleName) => r => r.Name.StartsWith(roleName);
protected override Expression<Func<TUser, bool>> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName);
protected override void AddUserStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IUserStore<TUser>>(new MongoUserStore<TUser, TRole, IMongoDbContext, TKey>(Container.MongoRepository.Context));
}
protected override void AddRoleStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IRoleStore<TRole>>(new MongoRoleStore<TRole, IMongoDbContext, TKey>(Container.MongoRepository.Context));
}
protected override void SetUserPasswordHash(TUser user, string hashedPassword)
{
user.PasswordHash = hashedPassword;
}
[Fact]
public async Task DeleteRoleNonEmptySucceedsTest()
{
var userMgr = CreateManager();
var roleMgr = CreateRoleManager();
var roleName = "delete" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
Assert.False(await roleMgr.RoleExistsAsync(roleName));
IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role));
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user));
IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, roleName));
var roles = await userMgr.GetRolesAsync(user);
Assert.Single(roles);
IdentityResultAssert.IsSuccess(await roleMgr.DeleteAsync(role));
Assert.Null(await roleMgr.FindByNameAsync(roleName));
Assert.False(await roleMgr.RoleExistsAsync(roleName));
// REVIEW: We should throw if deleteing a non empty role?
roles = await userMgr.GetRolesAsync(user);
Assert.Empty(roles);
}
[Fact]
public async Task DeleteUserRemovesFromRoleTest()
{
// Need fail if not empty?
var userMgr = CreateManager();
var roleMgr = CreateRoleManager();
var roleName = "deleteUserRemove" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
Assert.False(await roleMgr.RoleExistsAsync(roleName));
IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role));
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user));
IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, roleName));
var roles = await userMgr.GetRolesAsync(user);
Assert.Single(roles);
IdentityResultAssert.IsSuccess(await userMgr.DeleteAsync(user));
roles = await userMgr.GetRolesAsync(user);
Assert.Empty(roles);
}
[Fact]
public async Task DeleteUserRemovesTokensTest()
{
// Need fail if not empty?
var userMgr = CreateManager();
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user));
IdentityResultAssert.IsSuccess(await userMgr.SetAuthenticationTokenAsync(user, "provider", "test", "value"));
Assert.Equal("value", await userMgr.GetAuthenticationTokenAsync(user, "provider", "test"));
IdentityResultAssert.IsSuccess(await userMgr.DeleteAsync(user));
Assert.Null(await userMgr.GetAuthenticationTokenAsync(user, "provider", "test"));
}
private IQueryable<TUser> GetQueryable()
{
return Container.MongoRepository.Context.GetCollection<TUser>().AsQueryable();
}
[Fact]
public void CanCreateUserUsingEF()
{
var user = CreateTestUser();
Container.MongoRepository.AddOne<TUser, TKey>(user);
Assert.True(GetQueryable().Any(u => u.UserName == user.UserName));
Assert.NotNull(GetQueryable().FirstOrDefault(u => u.UserName == user.UserName));
}
[Fact]
public async Task CanCreateUsingManager()
{
var manager = CreateManager();
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user));
}
private async Task LazyLoadTestSetup(TUser user)
{
var manager = CreateManager();
var role = CreateRoleManager();
var admin = CreateTestRole("Admin" + Guid.NewGuid().ToString());
var local = CreateTestRole("Local" + Guid.NewGuid().ToString());
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, new UserLoginInfo("provider", user.Id.ToString(), "display")));
IdentityResultAssert.IsSuccess(await role.CreateAsync(admin));
IdentityResultAssert.IsSuccess(await role.CreateAsync(local));
IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, admin.Name));
IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(user, local.Name));
Claim[] userClaims =
{
new Claim("Whatever", "Value"),
new Claim("Whatever2", "Value2")
};
foreach (var c in userClaims)
{
IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c));
}
}
[Fact]
public async Task LoadFromDbFindByIdTest()
{
var user = CreateTestUser();
await LazyLoadTestSetup(user);
var manager = CreateManager();
var userById = await manager.FindByIdAsync(user.Id.ToString());
Assert.Equal(2, (await manager.GetClaimsAsync(userById)).Count);
Assert.Equal(1, (await manager.GetLoginsAsync(userById)).Count);
Assert.Equal(2, (await manager.GetRolesAsync(userById)).Count);
}
[Fact]
public async Task LoadFromDbFindByNameTest()
{
var user = CreateTestUser();
await LazyLoadTestSetup(user);
var manager = CreateManager();
var userByName = await manager.FindByNameAsync(user.UserName);
Assert.Equal(2, (await manager.GetClaimsAsync(userByName)).Count);
Assert.Equal(1, (await manager.GetLoginsAsync(userByName)).Count);
Assert.Equal(2, (await manager.GetRolesAsync(userByName)).Count);
}
[Fact]
public async Task LoadFromDbFindByLoginTest()
{
var user = CreateTestUser();
await LazyLoadTestSetup(user);
var manager = CreateManager();
var userByLogin = await manager.FindByLoginAsync("provider", user.Id.ToString());
Assert.Equal(2, (await manager.GetClaimsAsync(userByLogin)).Count);
Assert.Equal(1, (await manager.GetLoginsAsync(userByLogin)).Count);
Assert.Equal(2, (await manager.GetRolesAsync(userByLogin)).Count);
}
[Fact]
public async Task LoadFromDbFindByEmailTest()
{
var user = CreateTestUser();
user.Email = "fooz@fizzy.pop";
await LazyLoadTestSetup(user);
var manager = CreateManager();
var userByEmail = await manager.FindByEmailAsync(user.Email);
Assert.Equal(2, (await manager.GetClaimsAsync(userByEmail)).Count);
Assert.Equal(1, (await manager.GetLoginsAsync(userByEmail)).Count);
Assert.Equal(2, (await manager.GetRolesAsync(userByEmail)).Count);
}
}
}
@@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using AspNetCore.Identity.MongoDbCore.Models;
using AspNetCore.Identity.MongoDbCore;
using MongoDbGenericRepository;
using AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure;
using Microsoft.AspNetCore.Identity;
namespace AspNetCore.Identity.MongoDbCore.Test
{
public class GuidUser : MongoIdentityUser<Guid>
{
public GuidUser() : base()
{
}
}
public class GuidRole : MongoIdentityRole<Guid>
{
public GuidRole() : base()
{
}
}
public class UserStoreGuidTest : SqlStoreTestBase<GuidUser, GuidRole, Guid>
{
public UserStoreGuidTest(MongoDatabaseFixture<GuidUser, GuidRole, Guid> fixture)
: base(fixture)
{
}
public class ApplicationUserStore : MongoUserStore<GuidUser, GuidRole, IMongoDbContext, Guid>
{
public ApplicationUserStore(IMongoDbContext context) : base(Container.MongoRepository.Context) { }
}
public class ApplicationRoleStore : MongoRoleStore<GuidRole, IMongoDbContext, Guid>
{
public ApplicationRoleStore(IMongoDbContext context) : base(Container.MongoRepository.Context) { }
}
protected override void AddUserStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IUserStore<GuidUser>>(new ApplicationUserStore(Container.MongoRepository.Context));
}
protected override void AddRoleStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IRoleStore<GuidRole>>(new ApplicationRoleStore(Container.MongoRepository.Context));
}
}
}
@@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using AspNetCore.Identity.MongoDbCore.Models;
namespace AspNetCore.Identity.MongoDbCore.Test
{
public class IntUser : MongoIdentityUser<int>
{
public IntUser() : base()
{
}
}
public class IntRole : MongoIdentityRole<int>
{
public IntRole() : base()
{
Name = Guid.NewGuid().ToString();
}
}
public class UserStoreIntTest : SqlStoreTestBase<IntUser, IntRole, int>
{
public UserStoreIntTest(MongoDatabaseFixture<IntUser, IntRole, int> fixture)
: base(fixture)
{
}
}
}
@@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using AspNetCore.Identity.MongoDbCore.Models;
namespace AspNetCore.Identity.MongoDbCore.Test
{
public class StringUser : MongoDbIdentityUser
{
public StringUser() : base()
{
}
}
public class StringRole : MongoDbIdentityRole
{
public StringRole() : base()
{
}
}
public class UserStoreStringKeyTest : SqlStoreTestBase<StringUser, StringRole, string>
{
public UserStoreStringKeyTest(MongoDatabaseFixture<StringUser, StringRole, string> fixture)
: base(fixture)
{ }
}
}
@@ -0,0 +1,364 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.Test;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using AspNetCore.Identity.MongoDbCore.Models;
using AspNetCore.Identity.MongoDbCore;
using MongoDB.Driver;
using AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure;
using AspNetCore.Identity.MongoDbCore.Infrastructure;
using MongoDbGenericRepository;
using Microsoft.AspNetCore.Identity;
namespace AspNetCore.Identity.MongoDbCore.Test
{
public class ApplicationDbContext : MongoIdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(string connectionString, string databaseName) : base(connectionString, databaseName)
{
}
}
public sealed class Applicationcontext
{
public static ApplicationDbContext Instance = new ApplicationDbContext(
Container.MongoDbIdentityConfiguration.MongoDbSettings.ConnectionString,
Container.MongoDbIdentityConfiguration.MongoDbSettings.DatabaseName);
}
public class UserStoreTest : IdentitySpecificationTestBase<MongoDbIdentityUser, MongoDbIdentityRole>, IClassFixture<MongoDatabaseFixture<MongoDbIdentityUser, MongoDbIdentityRole, string>>
{
private readonly MongoDatabaseFixture<MongoDbIdentityUser, MongoDbIdentityRole,string> _fixture;
public UserStoreTest(MongoDatabaseFixture<MongoDbIdentityUser, MongoDbIdentityRole,string> fixture)
{
_fixture = fixture;
}
protected override bool ShouldSkipDbTests()
=> false;
[Fact]
public void CanCreateUserUsingEF()
{
var user = CreateTestUser();
user.Id = Guid.NewGuid().ToString();
var guidString = user.Id.ToString();
user.UserName = guidString;
Container.MongoRepository.AddOne<MongoDbIdentityUser, string>(user);
Assert.True(Container.MongoRepository.Any<MongoDbIdentityUser, string>(u => u.UserName == guidString));
Assert.NotNull(Container.MongoRepository.GetOne<MongoDbIdentityUser, string>(u => u.UserName == guidString));
}
protected override void AddUserStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IUserStore<MongoDbIdentityUser>>(new MongoUserStore<MongoDbIdentityUser, MongoDbIdentityRole, IMongoDbContext, string>(Container.MongoRepository.Context));
}
protected override void AddRoleStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IRoleStore<MongoDbIdentityRole>>(new MongoRoleStore<MongoDbIdentityRole, IMongoDbContext>(Container.MongoRepository.Context));
}
[Fact]
public async Task SqlUserStoreMethodsThrowWhenDisposedTest()
{
var store = new MongoUserStore(Container.MongoRepository.Context);
store.Dispose();
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.AddClaimsAsync(null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.AddLoginAsync(null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.AddToRoleAsync(null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.GetClaimsAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.GetLoginsAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.GetRolesAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.IsInRoleAsync(null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.RemoveClaimsAsync(null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.RemoveLoginAsync(null, null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(
async () => await store.RemoveFromRoleAsync(null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.RemoveClaimsAsync(null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.ReplaceClaimAsync(null, null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.FindByLoginAsync(null, null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.FindByIdAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.FindByNameAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.CreateAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.UpdateAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.DeleteAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(
async () => await store.SetEmailConfirmedAsync(null, true));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await store.GetEmailConfirmedAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(
async () => await store.SetPhoneNumberConfirmedAsync(null, true));
await Assert.ThrowsAsync<ObjectDisposedException>(
async () => await store.GetPhoneNumberConfirmedAsync(null));
}
[Fact]
public async Task UserStorePublicNullCheckTest()
{
Assert.Throws<ArgumentNullException>("context", () => new MongoUserStore(null));
var store = new MongoUserStore(Container.MongoRepository.Context);
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetUserIdAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetUserNameAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.SetUserNameAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.CreateAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.UpdateAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.DeleteAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.AddClaimsAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.ReplaceClaimAsync(null, null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.RemoveClaimsAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetClaimsAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetLoginsAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetRolesAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.AddLoginAsync(null, null));
await
Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.RemoveLoginAsync(null, null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.AddToRoleAsync(null, null));
await
Assert.ThrowsAsync<ArgumentNullException>("user",
async () => await store.RemoveFromRoleAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.IsInRoleAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetPasswordHashAsync(null));
await
Assert.ThrowsAsync<ArgumentNullException>("user",
async () => await store.SetPasswordHashAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetSecurityStampAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user",
async () => await store.SetSecurityStampAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("login", async () => await store.AddLoginAsync(new MongoDbIdentityUser("fake"), null));
await Assert.ThrowsAsync<ArgumentNullException>("claims",
async () => await store.AddClaimsAsync(new MongoDbIdentityUser("fake"), null));
await Assert.ThrowsAsync<ArgumentNullException>("claims",
async () => await store.RemoveClaimsAsync(new MongoDbIdentityUser("fake"), null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetEmailConfirmedAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user",
async () => await store.SetEmailConfirmedAsync(null, true));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetEmailAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.SetEmailAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetPhoneNumberAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.SetPhoneNumberAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user",
async () => await store.GetPhoneNumberConfirmedAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user",
async () => await store.SetPhoneNumberConfirmedAsync(null, true));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetTwoFactorEnabledAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user",
async () => await store.SetTwoFactorEnabledAsync(null, true));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetAccessFailedCountAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetLockoutEnabledAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.SetLockoutEnabledAsync(null, false));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.GetLockoutEndDateAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.SetLockoutEndDateAsync(null, new DateTimeOffset()));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.ResetAccessFailedCountAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("user", async () => await store.IncrementAccessFailedCountAsync(null));
await Assert.ThrowsAsync<ArgumentException>("normalizedRoleName", async () => await store.AddToRoleAsync(new MongoDbIdentityUser("fake"), null));
await Assert.ThrowsAsync<ArgumentException>("normalizedRoleName", async () => await store.RemoveFromRoleAsync(new MongoDbIdentityUser("fake"), null));
await Assert.ThrowsAsync<ArgumentException>("normalizedRoleName", async () => await store.IsInRoleAsync(new MongoDbIdentityUser("fake"), null));
await Assert.ThrowsAsync<ArgumentException>("normalizedRoleName", async () => await store.AddToRoleAsync(new MongoDbIdentityUser("fake"), ""));
await Assert.ThrowsAsync<ArgumentException>("normalizedRoleName", async () => await store.RemoveFromRoleAsync(new MongoDbIdentityUser("fake"), ""));
await Assert.ThrowsAsync<ArgumentException>("normalizedRoleName", async () => await store.IsInRoleAsync(new MongoDbIdentityUser("fake"), ""));
}
[Fact]
public async Task CanCreateUsingManager()
{
var manager = CreateManager();
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user));
}
[Fact]
public async Task TwoUsersSamePasswordDifferentHash()
{
var manager = CreateManager();
var userA = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(userA, "password"));
var userB = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(userB, "password"));
Assert.NotEqual(userA.PasswordHash, userB.PasswordHash);
}
[Fact]
public async Task AddUserToUnknownRoleFails()
{
var manager = CreateManager();
var u = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(u));
await Assert.ThrowsAsync<InvalidOperationException>(
async () => await manager.AddToRoleAsync(u, "bogus"));
}
[Fact]
public async Task ConcurrentUpdatesWillFail()
{
var user = CreateTestUser();
var manager = CreateManager();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
var manager1 = CreateManager();
var manager2 = CreateManager();
var user1 = await manager1.FindByIdAsync(user.Id);
var user2 = await manager2.FindByIdAsync(user.Id);
Assert.NotNull(user1);
Assert.NotNull(user2);
Assert.NotSame(user1, user2);
user1.UserName = Guid.NewGuid().ToString();
user2.UserName = Guid.NewGuid().ToString();
IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(user1));
IdentityResultAssert.IsFailure(await manager2.UpdateAsync(user2), new IdentityErrorDescriber().ConcurrencyFailure());
}
[Fact]
public async Task ConcurrentUpdatesWillFailWithDetachedUser()
{
var user = CreateTestUser();
var manager = CreateManager();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
var manager1 = CreateManager();
var manager2 = CreateManager();
var user2 = await manager2.FindByIdAsync(user.Id);
Assert.NotNull(user2);
Assert.NotSame(user, user2);
user.UserName = Guid.NewGuid().ToString();
user2.UserName = Guid.NewGuid().ToString();
IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(user));
IdentityResultAssert.IsFailure(await manager2.UpdateAsync(user2), new IdentityErrorDescriber().ConcurrencyFailure());
}
[Fact]
public async Task DeleteAModifiedUserWillFail()
{
var user = CreateTestUser();
var manager = CreateManager();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
var manager1 = CreateManager();
var manager2 = CreateManager();
var user1 = await manager1.FindByIdAsync(user.Id);
var user2 = await manager2.FindByIdAsync(user.Id);
Assert.NotNull(user1);
Assert.NotNull(user2);
Assert.NotSame(user1, user2);
user1.UserName = Guid.NewGuid().ToString();
IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(user1));
IdentityResultAssert.IsFailure(await manager2.DeleteAsync(user2), new IdentityErrorDescriber().ConcurrencyFailure());
}
[Fact]
public async Task ConcurrentRoleUpdatesWillFail()
{
var role = CreateRole();
var manager = CreateRoleManager();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
var manager1 = CreateRoleManager();
var manager2 = CreateRoleManager();
var role1 = await manager1.FindByIdAsync(role.Id);
var role2 = await manager2.FindByIdAsync(role.Id);
Assert.NotNull(role1);
Assert.NotNull(role2);
Assert.NotSame(role1, role2);
role1.Name = Guid.NewGuid().ToString();
role2.Name = Guid.NewGuid().ToString();
IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(role1));
IdentityResultAssert.IsFailure(await manager2.UpdateAsync(role2), new IdentityErrorDescriber().ConcurrencyFailure());
}
[Fact]
public async Task ConcurrentRoleUpdatesWillFailWithDetachedRole()
{
var role = CreateRole();
var manager = CreateRoleManager();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
var manager1 = CreateRoleManager();
var manager2 = CreateRoleManager();
var role2 = await manager2.FindByIdAsync(role.Id);
Assert.NotNull(role);
Assert.NotNull(role2);
Assert.NotSame(role, role2);
role.Name = Guid.NewGuid().ToString();
role2.Name = Guid.NewGuid().ToString();
IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(role));
IdentityResultAssert.IsFailure(await manager2.UpdateAsync(role2), new IdentityErrorDescriber().ConcurrencyFailure());
}
private MongoDbIdentityRole CreateRole()
{
var guid = Guid.NewGuid().ToString();
var role = new MongoDbIdentityRole(guid);
_fixture.RolesToDelete.Add(role);
return role;
}
[Fact]
public async Task DeleteAModifiedRoleWillFail()
{
var role = CreateRole();
var manager = CreateRoleManager();
var result = await manager.CreateAsync(role);
IdentityResultAssert.IsSuccess(result);
var manager1 = CreateRoleManager();
var manager2 = CreateRoleManager(true);
var role1 = await manager1.FindByIdAsync(role.Id);
var role2 = await manager2.FindByIdAsync(role.Id);
Assert.NotNull(role1);
Assert.NotNull(role2);
Assert.NotSame(role1, role2);
role1.Name = Guid.NewGuid().ToString();
IdentityResultAssert.IsSuccess(await manager1.UpdateAsync(role1));
IdentityResultAssert.IsFailure(await manager2.DeleteAsync(role2), new IdentityErrorDescriber().ConcurrencyFailure());
}
protected override MongoDbIdentityUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "",
bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false)
{
var user = new MongoDbIdentityUser
{
UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()),
Email = email,
PhoneNumber = phoneNumber,
LockoutEnabled = lockoutEnabled,
LockoutEnd = lockoutEnd
};
_fixture.UsersToDelete.Add(user);
return user;
}
protected override MongoDbIdentityRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false)
{
var roleName = useRoleNamePrefixAsRoleName ? roleNamePrefix : string.Format("{0}{1}", roleNamePrefix, Guid.NewGuid());
var role = new MongoDbIdentityRole(roleName);
_fixture.RolesToDelete.Add(role);
return role;
}
protected override void SetUserPasswordHash(MongoDbIdentityUser user, string hashedPassword)
{
user.PasswordHash = hashedPassword;
}
protected override Expression<Func<MongoDbIdentityUser, bool>> UserNameEqualsPredicate(string userName) => u => u.UserName == userName;
protected override Expression<Func<MongoDbIdentityRole, bool>> RoleNameEqualsPredicate(string roleName) => r => r.Name == roleName;
protected override Expression<Func<MongoDbIdentityRole, bool>> RoleNameStartsWithPredicate(string roleName) => r => r.Name.StartsWith(roleName);
protected override Expression<Func<MongoDbIdentityUser, bool>> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName);
}
public class ApplicationUser : MongoIdentityUser { }
}
@@ -0,0 +1,353 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.Test;
//using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using AspNetCore.Identity.MongoDbCore.Models;
using AspNetCore.Identity.MongoDbCore;
using AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure;
using MongoDbGenericRepository;
using Microsoft.AspNetCore.Identity;
namespace AspNetCore.Identity.MongoDbCore.Test
{
public class UserStoreWithGenericsTest : IdentitySpecificationTestBase<IdentityUserWithGenerics, MyIdentityRole, string>, IClassFixture<MongoDatabaseFixture<IdentityUserWithGenerics, MyIdentityRole, string>>
{
private readonly MongoDatabaseFixture<IdentityUserWithGenerics, MyIdentityRole, string> _fixture;
public UserStoreWithGenericsTest(MongoDatabaseFixture<IdentityUserWithGenerics, MyIdentityRole, string> fixture)
{
_fixture = fixture;
}
protected override bool ShouldSkipDbTests()
{
return false;
}
protected override void AddUserStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IUserStore<IdentityUserWithGenerics>>(new MongoUserStore<IdentityUserWithGenerics>(Container.MongoRepository.Context));
//services.AddSingleton<IUserStore<IdentityUserWithGenerics>>(new UserStoreWithGenerics((ContextWithGenerics)context, "TestContext"));
}
protected override void AddRoleStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IRoleStore<MyIdentityRole>>(new MongoRoleStore<MyIdentityRole>(Container.MongoRepository.Context));
//services.AddSingleton<IRoleStore<MyIdentityRole>>(new RoleStoreWithGenerics(Container.MongoRepository.Context, "TestContext"));
}
protected override IdentityUserWithGenerics CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "",
bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false)
{
var user = new IdentityUserWithGenerics
{
UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()),
Email = email,
PhoneNumber = phoneNumber,
LockoutEnabled = lockoutEnabled,
LockoutEnd = lockoutEnd
};
_fixture.UsersToDelete.Add(user);
return user;
}
protected override MyIdentityRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false)
{
var roleName = useRoleNamePrefixAsRoleName ? roleNamePrefix : string.Format("{0}{1}", roleNamePrefix, Guid.NewGuid());
var role = new MyIdentityRole(roleName);
_fixture.RolesToDelete.Add(role);
return role;
}
protected override void SetUserPasswordHash(IdentityUserWithGenerics user, string hashedPassword)
{
user.PasswordHash = hashedPassword;
}
protected override Expression<Func<IdentityUserWithGenerics, bool>> UserNameEqualsPredicate(string userName) => u => u.UserName == userName;
protected override Expression<Func<MyIdentityRole, bool>> RoleNameEqualsPredicate(string roleName) => r => r.Name == roleName;
protected override Expression<Func<IdentityUserWithGenerics, bool>> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName);
protected override Expression<Func<MyIdentityRole, bool>> RoleNameStartsWithPredicate(string roleName) => r => r.Name.StartsWith(roleName);
[Fact]
public void AddEntityFrameworkStoresWithInvalidUserThrows()
{
var services = new ServiceCollection();
var builder = services.AddIdentity<object, IdentityRole>();
var e = Assert.Throws<InvalidOperationException>(() =>
//builder.AddEntityFrameworkStores<ContextWithGenerics>()
Throw()
);
Assert.Contains("AddEntityFrameworkStores", e.Message);
}
private void Throw()
{
throw new InvalidOperationException("AddEntityFrameworkStores");
}
[Fact]
public void AddEntityFrameworkStoresWithInvalidRoleThrows()
{
var services = new ServiceCollection();
var builder = services.AddIdentity<IdentityUser, object>();
var e = Assert.Throws<InvalidOperationException>(() => {
//builder.AddEntityFrameworkStores<ContextWithGenerics>();
Throw();
});
Assert.Contains("AddEntityFrameworkStores", e.Message);
}
[Fact]
public async Task CanAddRemoveUserClaimWithIssuer()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
Claim[] claims = { new Claim("c1", "v1", null, "i1"), new Claim("c2", "v2", null, "i2"), new Claim("c2", "v3", null, "i3") };
foreach (Claim c in claims)
{
IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c));
}
var userId = await manager.GetUserIdAsync(user);
var userClaims = await manager.GetClaimsAsync(user);
Assert.Equal(3, userClaims.Count);
Assert.Equal(3, userClaims.Intersect(claims, ClaimEqualityComparer.Default).Count());
IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0]));
userClaims = await manager.GetClaimsAsync(user);
Assert.Equal(2, userClaims.Count);
IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1]));
userClaims = await manager.GetClaimsAsync(user);
Assert.Equal(1, userClaims.Count);
IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2]));
userClaims = await manager.GetClaimsAsync(user);
Assert.Equal(0, userClaims.Count);
}
[Fact]
public async Task RemoveClaimWithIssuerOnlyAffectsUser()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var user = CreateTestUser();
var user2 = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2));
Claim[] claims = { new Claim("c", "v", null, "i1"), new Claim("c2", "v2", null, "i2"), new Claim("c2", "v3", null, "i3") };
foreach (Claim c in claims)
{
IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c));
IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user2, c));
}
var userClaims = await manager.GetClaimsAsync(user);
Assert.Equal(3, userClaims.Count);
IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0]));
userClaims = await manager.GetClaimsAsync(user);
Assert.Equal(2, userClaims.Count);
IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1]));
userClaims = await manager.GetClaimsAsync(user);
Assert.Equal(1, userClaims.Count);
IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2]));
userClaims = await manager.GetClaimsAsync(user);
Assert.Equal(0, userClaims.Count);
var userClaims2 = await manager.GetClaimsAsync(user2);
Assert.Equal(3, userClaims2.Count);
}
[Fact]
public async Task CanReplaceUserClaimWithIssuer()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("c", "a", "i")));
var userClaims = await manager.GetClaimsAsync(user);
Assert.Equal(1, userClaims.Count);
Claim claim = new Claim("c", "b", "i");
Claim oldClaim = userClaims.FirstOrDefault();
IdentityResultAssert.IsSuccess(await manager.ReplaceClaimAsync(user, oldClaim, claim));
var newUserClaims = await manager.GetClaimsAsync(user);
Assert.Equal(1, newUserClaims.Count);
Claim newClaim = newUserClaims.FirstOrDefault();
Assert.Equal(claim.Type, newClaim.Type);
Assert.Equal(claim.Value, newClaim.Value);
Assert.Equal(claim.Issuer, newClaim.Issuer);
}
}
public class ClaimEqualityComparer : IEqualityComparer<Claim>
{
public static IEqualityComparer<Claim> Default = new ClaimEqualityComparer();
public bool Equals(Claim x, Claim y)
{
return x.Value == y.Value && x.Type == y.Type && x.Issuer == y.Issuer;
}
public int GetHashCode(Claim obj)
{
return 1;
}
}
#region Generic Type defintions
public class IdentityUserWithGenerics : MongoDbIdentityUser
{
public IdentityUserWithGenerics() : base()
{
}
}
public class UserStoreWithGenerics : MongoUserStore<IdentityUserWithGenerics, MyIdentityRole, IMongoDbContext, string, IdentityUserClaimWithIssuer, IdentityUserRoleWithDate, IdentityUserLoginWithContext, IdentityUserTokenWithStuff, IdentityRoleClaimWithIssuer>
{
public string LoginContext { get; set; }
public UserStoreWithGenerics(IMongoDbContext context, string loginContext) : base(context)
{
LoginContext = loginContext;
}
protected override IdentityUserRoleWithDate CreateUserRole(IdentityUserWithGenerics user, MyIdentityRole role)
{
return new IdentityUserRoleWithDate()
{
RoleId = role.Id,
UserId = user.Id,
Created = DateTime.UtcNow
};
}
protected override IdentityUserClaimWithIssuer CreateUserClaim(IdentityUserWithGenerics user, Claim claim)
{
return new IdentityUserClaimWithIssuer { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value, Issuer = claim.Issuer };
}
protected override IdentityUserLoginWithContext CreateUserLogin(IdentityUserWithGenerics user, UserLoginInfo login)
{
return new IdentityUserLoginWithContext
{
UserId = user.Id,
ProviderKey = login.ProviderKey,
LoginProvider = login.LoginProvider,
ProviderDisplayName = login.ProviderDisplayName,
Context = LoginContext
};
}
protected override IdentityUserTokenWithStuff CreateUserToken(IdentityUserWithGenerics user, string loginProvider, string name, string value)
{
return new IdentityUserTokenWithStuff
{
UserId = user.Id,
LoginProvider = loginProvider,
Name = name,
Value = value,
Stuff = "stuff"
};
}
}
public class RoleStoreWithGenerics : MongoRoleStore<MyIdentityRole, IMongoDbContext, string, IdentityUserRoleWithDate, IdentityRoleClaimWithIssuer>
{
private string _loginContext;
public RoleStoreWithGenerics(IMongoDbContext context, string loginContext) : base(context)
{
_loginContext = loginContext;
}
protected override IdentityRoleClaimWithIssuer CreateRoleClaim(MyIdentityRole role, Claim claim)
{
return new IdentityRoleClaimWithIssuer { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value, Issuer = claim.Issuer };
}
}
public class IdentityUserClaimWithIssuer : IdentityUserClaim<string>
{
public string Issuer { get; set; }
public override Claim ToClaim()
{
return new Claim(ClaimType, ClaimValue, null, Issuer);
}
public override void InitializeFromClaim(Claim other)
{
ClaimValue = other.Value;
ClaimType = other.Type;
Issuer = other.Issuer;
}
}
public class IdentityRoleClaimWithIssuer : IdentityRoleClaim<string>
{
public string Issuer { get; set; }
public override Claim ToClaim()
{
return new Claim(ClaimType, ClaimValue, null, Issuer);
}
public override void InitializeFromClaim(Claim other)
{
ClaimValue = other.Value;
ClaimType = other.Type;
Issuer = other.Issuer;
}
}
public class IdentityUserRoleWithDate : IdentityUserRole<string>
{
public DateTime Created { get; set; }
}
public class MyIdentityRole : MongoDbIdentityRole
{
public MyIdentityRole() : base()
{
}
public MyIdentityRole(string roleName) : base(roleName)
{
}
}
public class IdentityUserTokenWithStuff : IdentityUserToken<string>
{
public string Stuff { get; set; }
}
public class IdentityUserLoginWithContext : IdentityUserLogin<string>
{
public string Context { get; set; }
}
#endregion
}
@@ -0,0 +1,69 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using MongoDbGenericRepository;
using AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure;
using System.Collections.Concurrent;
using System.Linq;
using MongoDB.Driver;
using MongoDbGenericRepository.Models;
namespace AspNetCore.Identity.MongoDbCore.Test
{
public class MongoDatabaseFixture<TUser, TKey> : IDisposable
where TUser : IDocument<TKey>
where TKey : IEquatable<TKey>
{
public IMongoDbContext Context;
public MongoDatabaseFixture()
{
Context = new MongoDbContext(
Container.MongoDbIdentityConfiguration.MongoDbSettings.ConnectionString,
Container.MongoDbIdentityConfiguration.MongoDbSettings.DatabaseName);
UsersToDelete = new ConcurrentBag<TUser>();
}
public ConcurrentBag<TUser> UsersToDelete { get; set; }
public virtual void Dispose()
{
var userIds = UsersToDelete.ToList().Select(e => e.Id);
if (userIds.Any())
{
Context.GetCollection<TUser>().DeleteMany(e => userIds.Contains(e.Id));
}
}
}
public class MongoDatabaseFixture<TUser, TRole, TKey> : MongoDatabaseFixture<TUser, TKey>, IDisposable
where TUser : IDocument<TKey>
where TRole : IDocument<TKey>
where TKey : IEquatable<TKey>
{
public MongoDatabaseFixture()
{
Context = new MongoDbContext(
Container.MongoDbIdentityConfiguration.MongoDbSettings.ConnectionString,
Container.MongoDbIdentityConfiguration.MongoDbSettings.DatabaseName);
UsersToDelete = new ConcurrentBag<TUser>();
RolesToDelete = new ConcurrentBag<TRole>();
}
public ConcurrentBag<TRole> RolesToDelete { get; set; }
public override void Dispose()
{
var userIds = UsersToDelete.ToList().Select(e => e.Id);
if (userIds.Any())
{
Context.GetCollection<TUser>().DeleteMany(e => userIds.Contains(e.Id));
}
var roleIds = RolesToDelete.ToList().Select(e => e.Id);
if (roleIds.Any())
{
Context.GetCollection<TRole>().DeleteMany(e => roleIds.Contains(e.Id));
}
}
}
}
@@ -0,0 +1,165 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Data.Common;
using System.Data.SqlClient;
using System.IO;
using System.Threading;
namespace AspNetCore.Identity.MongoDbCore.Test.Utilities
{
//public class SqlServerTestStore : IDisposable
//{
// public const int CommandTimeout = 90;
// public static string CreateConnectionString(string name)
// {
// var connStrBuilder = new SqlConnectionStringBuilder(TestEnvironment.Config["Test:SqlServer:DefaultConnectionString"])
// {
// InitialCatalog = name
// };
// return connStrBuilder.ConnectionString;
// }
// public static SqlServerTestStore CreateScratch(bool createDatabase = true)
// => new SqlServerTestStore(GetScratchDbName()).CreateTransient(createDatabase);
// private SqlConnection _connection;
// private readonly string _name;
// private bool _deleteDatabase;
// private SqlServerTestStore(string name)
// {
// _name = name;
// }
// private static string GetScratchDbName()
// {
// string name;
// do
// {
// name = "Scratch_" + Guid.NewGuid();
// } while (DatabaseExists(name)
// || DatabaseFilesExist(name));
// return name;
// }
// private static void WaitForExists(SqlConnection connection)
// {
// var retryCount = 0;
// while (true)
// {
// try
// {
// connection.Open();
// connection.Close();
// return;
// }
// catch (SqlException e)
// {
// if (++retryCount >= 30
// || (e.Number != 233 && e.Number != -2 && e.Number != 4060))
// {
// throw;
// }
// SqlConnection.ClearPool(connection);
// Thread.Sleep(100);
// }
// }
// }
// private SqlServerTestStore CreateTransient(bool createDatabase)
// {
// _connection = new SqlConnection(CreateConnectionString(_name));
// if (createDatabase)
// {
// using (var master = new SqlConnection(CreateConnectionString("master")))
// {
// master.Open();
// using (var command = master.CreateCommand())
// {
// command.CommandTimeout = CommandTimeout;
// command.CommandText = $"{Environment.NewLine}CREATE DATABASE [{_name}]";
// command.ExecuteNonQuery();
// WaitForExists(_connection);
// }
// }
// _connection.Open();
// }
// _deleteDatabase = true;
// return this;
// }
// private static bool DatabaseExists(string name)
// {
// using (var master = new SqlConnection(CreateConnectionString("master")))
// {
// master.Open();
// using (var command = master.CreateCommand())
// {
// command.CommandTimeout = CommandTimeout;
// command.CommandText = $@"SELECT COUNT(*) FROM sys.databases WHERE name = N'{name}'";
// return (int) command.ExecuteScalar() > 0;
// }
// }
// }
// private static bool DatabaseFilesExist(string name)
// {
// var userFolder = Environment.GetEnvironmentVariable("USERPROFILE") ??
// Environment.GetEnvironmentVariable("HOME");
// return userFolder != null
// && (File.Exists(Path.Combine(userFolder, name + ".mdf"))
// || File.Exists(Path.Combine(userFolder, name + "_log.ldf")));
// }
// private void DeleteDatabase(string name)
// {
// using (var master = new SqlConnection(CreateConnectionString("master")))
// {
// master.Open();
// using (var command = master.CreateCommand())
// {
// command.CommandTimeout = CommandTimeout;
// // Query will take a few seconds if (and only if) there are active connections
// // SET SINGLE_USER will close any open connections that would prevent the drop
// command.CommandText
// = string.Format(@"IF EXISTS (SELECT * FROM sys.databases WHERE name = N'{0}')
// BEGIN
// ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
// DROP DATABASE [{0}];
// END", name);
// command.ExecuteNonQuery();
// }
// }
// }
// public DbConnection Connection => _connection;
// public void Dispose()
// {
// _connection.Dispose();
// if (_deleteDatabase)
// {
// DeleteDatabase(_name);
// }
// }
//}
}
@@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using Microsoft.AspNetCore.Builder;
namespace Microsoft.AspNetCore.Identity.Test
{
public class ApiConsistencyTest : ApiConsistencyTestBase
{
protected override Assembly TargetAssembly => typeof(IdentityOptions).GetTypeInfo().Assembly;
}
}
@@ -0,0 +1,327 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class IdentityBuilderTest
{
[Fact]
public void CanOverrideUserStore()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser,TestRole>().AddUserStore<MyUberThingy>();
var thingy = services.BuildServiceProvider().GetRequiredService<IUserStore<TestUser>>() as MyUberThingy;
Assert.NotNull(thingy);
}
[Fact]
public void CanOverrideRoleStore()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser,TestRole>().AddRoleStore<MyUberThingy>();
var thingy = services.BuildServiceProvider().GetRequiredService<IRoleStore<TestRole>>() as MyUberThingy;
Assert.NotNull(thingy);
}
[Fact]
public void CanOverridePrincipalFactory()
{
var services = new ServiceCollection()
.AddLogging()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser, TestRole>()
.AddClaimsPrincipalFactory<MyClaimsPrincipalFactory>()
.AddUserManager<MyUserManager>()
.AddUserStore<NoopUserStore>()
.AddRoleStore<NoopRoleStore>();
var thingy = services.BuildServiceProvider().GetRequiredService<IUserClaimsPrincipalFactory<TestUser>>() as MyClaimsPrincipalFactory;
Assert.NotNull(thingy);
}
[Fact]
public void CanOverrideRoleValidator()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser,TestRole>().AddRoleValidator<MyUberThingy>();
var thingy = services.BuildServiceProvider().GetRequiredService<IRoleValidator<TestRole>>() as MyUberThingy;
Assert.NotNull(thingy);
}
[Fact]
public void CanOverrideUserValidator()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser,TestRole>().AddUserValidator<MyUberThingy>();
var thingy = services.BuildServiceProvider().GetRequiredService<IUserValidator<TestUser>>() as MyUberThingy;
Assert.NotNull(thingy);
}
[Fact]
public void CanOverridePasswordValidator()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser,TestRole>().AddPasswordValidator<MyUberThingy>();
var thingy = services.BuildServiceProvider().GetRequiredService<IPasswordValidator<TestUser>>() as MyUberThingy;
Assert.NotNull(thingy);
}
[Fact]
public void CanOverrideUserManager()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser, TestRole>()
.AddUserStore<NoopUserStore>()
.AddUserManager<MyUserManager>();
var myUserManager = services.BuildServiceProvider().GetRequiredService(typeof(UserManager<TestUser>)) as MyUserManager;
Assert.NotNull(myUserManager);
}
[Fact]
public void CanOverrideRoleManager()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser, TestRole>()
.AddRoleStore<NoopRoleStore>()
.AddRoleManager<MyRoleManager>();
var myRoleManager = services.BuildServiceProvider().GetRequiredService<RoleManager<TestRole>>() as MyRoleManager;
Assert.NotNull(myRoleManager);
}
[Fact]
public void CanOverrideSignInManager()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build())
.AddSingleton<IHttpContextAccessor, HttpContextAccessor>()
.AddLogging();
services.AddIdentity<TestUser, TestRole>()
.AddUserStore<NoopUserStore>()
.AddRoleStore<NoopRoleStore>()
.AddUserManager<MyUserManager>()
.AddClaimsPrincipalFactory<MyClaimsPrincipalFactory>()
.AddSignInManager<MySignInManager>();
var myUserManager = services.BuildServiceProvider().GetRequiredService(typeof(SignInManager<TestUser>)) as MySignInManager;
Assert.NotNull(myUserManager);
}
[Fact]
public void EnsureDefaultServices()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser,TestRole>();
var provider = services.BuildServiceProvider();
var userValidator = provider.GetRequiredService<IUserValidator<TestUser>>() as UserValidator<TestUser>;
Assert.NotNull(userValidator);
var pwdValidator = provider.GetRequiredService<IPasswordValidator<TestUser>>() as PasswordValidator<TestUser>;
Assert.NotNull(pwdValidator);
var hasher = provider.GetRequiredService<IPasswordHasher<TestUser>>() as PasswordHasher<TestUser>;
Assert.NotNull(hasher);
}
[Fact]
public void EnsureDefaultTokenProviders()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddIdentity<TestUser,TestRole>().AddDefaultTokenProviders();
var provider = services.BuildServiceProvider();
var tokenProviders = provider.GetRequiredService<IOptions<IdentityOptions>>().Value.Tokens.ProviderMap.Values;
Assert.Equal(4, tokenProviders.Count());
}
[Fact]
public void AddManagerWithWrongTypesThrows()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
var builder = services.AddIdentity<TestUser, TestRole>();
Assert.Throws<InvalidOperationException>(() => builder.AddUserManager<object>());
Assert.Throws<InvalidOperationException>(() => builder.AddRoleManager<object>());
Assert.Throws<InvalidOperationException>(() => builder.AddSignInManager<object>());
}
[Fact]
public void AddTokenProviderWithWrongTypesThrows()
{
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
var builder = services.AddIdentity<TestUser, TestRole>();
Assert.Throws<InvalidOperationException>(() => builder.AddTokenProvider<object>("whatevs"));
Assert.Throws<InvalidOperationException>(() => builder.AddTokenProvider("whatevs", typeof(object)));
}
private class MyUberThingy : IUserValidator<TestUser>, IPasswordValidator<TestUser>, IRoleValidator<TestRole>, IUserStore<TestUser>, IRoleStore<TestRole>
{
public Task<IdentityResult> CreateAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> CreateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> DeleteAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> DeleteAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
public Task<TestUser> FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<TestUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<string> GetNormalizedRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<string> GetNormalizedUserNameAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<string> GetRoleIdAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<string> GetRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<string> GetUserIdAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<string> GetUserNameAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetNormalizedRoleNameAsync(TestRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetNormalizedUserNameAsync(TestUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetRoleNameAsync(TestRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetUserNameAsync(TestUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> UpdateAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> UpdateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> ValidateAsync(RoleManager<TestRole> manager, TestRole role)
{
throw new NotImplementedException();
}
public Task<IdentityResult> ValidateAsync(UserManager<TestUser> manager, TestUser user)
{
throw new NotImplementedException();
}
public Task<IdentityResult> ValidateAsync(UserManager<TestUser> manager, TestUser user, string password)
{
throw new NotImplementedException();
}
Task<TestRole> IRoleStore<TestRole>.FindByIdAsync(string roleId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
Task<TestRole> IRoleStore<TestRole>.FindByNameAsync(string roleName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
private class MySignInManager : SignInManager<TestUser>
{
public MySignInManager(UserManager<TestUser> manager, IHttpContextAccessor context, IUserClaimsPrincipalFactory<TestUser> claimsFactory) : base(manager, context, claimsFactory, null, null, null) { }
}
private class MyUserManager : UserManager<TestUser>
{
public MyUserManager(IUserStore<TestUser> store) : base(store, null, null, null, null, null, null, null, null) { }
}
private class MyClaimsPrincipalFactory : UserClaimsPrincipalFactory<TestUser, TestRole>
{
public MyClaimsPrincipalFactory(UserManager<TestUser> userManager, RoleManager<TestRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
{
}
}
private class MyRoleManager : RoleManager<TestRole>
{
public MyRoleManager(IRoleStore<TestRole> store,
IEnumerable<IRoleValidator<TestRole>> roleValidators) : base(store, null, null, null, null)
{
}
}
}
}
@@ -0,0 +1,75 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Security.Claims;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class IdentityOptionsTest
{
[Fact]
public void VerifyDefaultOptions()
{
var options = new IdentityOptions();
Assert.True(options.Lockout.AllowedForNewUsers);
Assert.Equal(TimeSpan.FromMinutes(5), options.Lockout.DefaultLockoutTimeSpan);
Assert.Equal(5, options.Lockout.MaxFailedAccessAttempts);
Assert.True(options.Password.RequireDigit);
Assert.True(options.Password.RequireLowercase);
Assert.True(options.Password.RequireNonAlphanumeric);
Assert.True(options.Password.RequireUppercase);
Assert.Equal(6, options.Password.RequiredLength);
Assert.Equal(1, options.Password.RequiredUniqueChars);
Assert.Equal("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+", options.User.AllowedUserNameCharacters);
Assert.False(options.User.RequireUniqueEmail);
Assert.Equal(ClaimTypes.Role, options.ClaimsIdentity.RoleClaimType);
Assert.Equal(ClaimTypes.Name, options.ClaimsIdentity.UserNameClaimType);
Assert.Equal(ClaimTypes.NameIdentifier, options.ClaimsIdentity.UserIdClaimType);
Assert.Equal("AspNet.Identity.SecurityStamp", options.ClaimsIdentity.SecurityStampClaimType);
}
[Fact]
public void CanCustomizeIdentityOptions()
{
var services = new ServiceCollection().Configure<IdentityOptions>(options => options.Password.RequiredLength = -1);
services.AddIdentity<TestUser,TestRole>();
var serviceProvider = services.BuildServiceProvider();
var setup = serviceProvider.GetRequiredService<IConfigureOptions<IdentityOptions>>();
Assert.NotNull(setup);
var optionsGetter = serviceProvider.GetRequiredService<IOptions<IdentityOptions>>();
Assert.NotNull(optionsGetter);
var myOptions = optionsGetter.Value;
Assert.True(myOptions.Password.RequireLowercase);
Assert.True(myOptions.Password.RequireDigit);
Assert.True(myOptions.Password.RequireNonAlphanumeric);
Assert.True(myOptions.Password.RequireUppercase);
Assert.Equal(1, myOptions.Password.RequiredUniqueChars);
Assert.Equal(-1, myOptions.Password.RequiredLength);
}
[Fact]
public void CanSetupIdentityOptions()
{
var services = new ServiceCollection();
services.AddIdentity<TestUser,TestRole>(options => options.User.RequireUniqueEmail = true);
var serviceProvider = services.BuildServiceProvider();
var optionsGetter = serviceProvider.GetRequiredService<IOptions<IdentityOptions>>();
Assert.NotNull(optionsGetter);
var myOptions = optionsGetter.Value;
Assert.True(myOptions.User.RequireUniqueEmail);
}
}
}
@@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class IdentityResultTest
{
[Fact]
public void VerifyDefaultConstructor()
{
var result = new IdentityResult();
Assert.False(result.Succeeded);
Assert.Empty(result.Errors);
}
[Fact]
public void NullFailedUsesEmptyErrors()
{
var result = IdentityResult.Failed();
Assert.False(result.Succeeded);
Assert.Empty(result.Errors);
}
}
}
@@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Identity.Test
{
public class NoopRoleStore : IRoleStore<TestRole>
{
public Task<IdentityResult> CreateAsync(TestRole user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(IdentityResult.Success);
}
public Task<IdentityResult> UpdateAsync(TestRole user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(IdentityResult.Success);
}
public Task<string> GetRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<string>(null);
}
public Task SetRoleNameAsync(TestRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(0);
}
public Task<TestRole> FindByIdAsync(string roleId, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<TestRole>(null);
}
public Task<TestRole> FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<TestRole>(null);
}
public void Dispose()
{
}
public Task<IdentityResult> DeleteAsync(TestRole user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(IdentityResult.Success);
}
public Task<string> GetRoleIdAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<string>(null);
}
public Task<string> GetNormalizedRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<string>(null);
}
public Task SetNormalizedRoleNameAsync(TestRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(0);
}
}
}
@@ -0,0 +1,66 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Identity.Test
{
public class NoopUserStore : IUserStore<TestUser>
{
public Task<string> GetUserIdAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Id);
}
public Task<string> GetUserNameAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.UserName);
}
public Task SetUserNameAsync(TestUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(0);
}
public Task<IdentityResult> CreateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(IdentityResult.Success);
}
public Task<IdentityResult> UpdateAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(IdentityResult.Success);
}
public Task<TestUser> FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<TestUser>(null);
}
public Task<TestUser> FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<TestUser>(null);
}
public void Dispose()
{
}
public Task<IdentityResult> DeleteAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(IdentityResult.Success);
}
public Task<string> GetNormalizedUserNameAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<string>(null);
}
public Task SetNormalizedUserNameAsync(TestUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(0);
}
}
}
@@ -0,0 +1,157 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class PasswordHasherTest
{
[Fact]
public void Ctor_InvalidCompatMode_Throws()
{
// Act & assert
var ex = Assert.Throws<InvalidOperationException>(() =>
{
new PasswordHasher(compatMode: (PasswordHasherCompatibilityMode)(-1));
});
Assert.Equal("The provided PasswordHasherCompatibilityMode is invalid.", ex.Message);
}
[Theory]
[InlineData(-1)]
[InlineData(0)]
public void Ctor_InvalidIterCount_Throws(int iterCount)
{
// Act & assert
var ex = Assert.Throws<InvalidOperationException>(() =>
{
new PasswordHasher(iterCount: iterCount);
});
Assert.Equal("The iteration count must be a positive integer.", ex.Message);
}
[Theory]
[InlineData(PasswordHasherCompatibilityMode.IdentityV2)]
[InlineData(PasswordHasherCompatibilityMode.IdentityV3)]
public void FullRoundTrip(PasswordHasherCompatibilityMode compatMode)
{
// Arrange
var hasher = new PasswordHasher(compatMode: compatMode);
// Act & assert - success case
var hashedPassword = hasher.HashPassword(null, "password 1");
var successResult = hasher.VerifyHashedPassword(null, hashedPassword, "password 1");
Assert.Equal(PasswordVerificationResult.Success, successResult);
// Act & assert - failure case
var failedResult = hasher.VerifyHashedPassword(null, hashedPassword, "password 2");
Assert.Equal(PasswordVerificationResult.Failed, failedResult);
}
[Theory]
// Version 2 payloads
[InlineData("AAABAgMEBQYHCAkKCwwNDg+uAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALtH1uJg==")] // incorrect password
[InlineData("AAABAgMEBQYHCAkKCwwNDg+ukCEMDf0yyQ29NYubggE=")] // too short
[InlineData("AAABAgMEBQYHCAkKCwwNDg+ukCEMDf0yyQ29NYubggHIVY0sdEUfdyeM+E1LtH1uJgAAAAAAAAAAAAA=")] // extra data at end
// Version 3 payloads
[InlineData("AQAAAAAAAAD6AAAAEAhftMyfTJyAAAAAAAAAAAAAAAAAAAih5WsjXaR3PA9M")] // incorrect password
[InlineData("AQAAAAIAAAAyAAAAEOMwvh3+FZxqkdMBz2ekgGhwQ4A=")] // too short
[InlineData("AQAAAAIAAAAyAAAAEOMwvh3+FZxqkdMBz2ekgGhwQ4B6pZWND6zgESBuWiHwAAAAAAAAAAAA")] // extra data at end
public void VerifyHashedPassword_FailureCases(string hashedPassword)
{
// Arrange
var hasher = new PasswordHasher();
// Act
var result = hasher.VerifyHashedPassword(null, hashedPassword, "my password");
// Assert
Assert.Equal(PasswordVerificationResult.Failed, result);
}
[Theory]
// Version 2 payloads
[InlineData("ANXrDknc7fGPpigibZXXZFMX4aoqz44JveK6jQuwY3eH/UyPhvr5xTPeGYEckLxz9A==")] // SHA1, 1000 iterations, 128-bit salt, 256-bit subkey
// Version 3 payloads
[InlineData("AQAAAAIAAAAyAAAAEOMwvh3+FZxqkdMBz2ekgGhwQ4B6pZWND6zgESBuWiHw")] // SHA512, 50 iterations, 128-bit salt, 128-bit subkey
[InlineData("AQAAAAIAAAD6AAAAIJbVi5wbMR+htSfFp8fTw8N8GOS/Sje+S/4YZcgBfU7EQuqv4OkVYmc4VJl9AGZzmRTxSkP7LtVi9IWyUxX8IAAfZ8v+ZfhjCcudtC1YERSqE1OEdXLW9VukPuJWBBjLuw==")] // SHA512, 250 iterations, 256-bit salt, 512-bit subkey
[InlineData("AQAAAAAAAAD6AAAAEAhftMyfTJylOlZT+eEotFXd1elee8ih5WsjXaR3PA9M")] // SHA1, 250 iterations, 128-bit salt, 128-bit subkey
[InlineData("AQAAAAEAA9CQAAAAIESkQuj2Du8Y+kbc5lcN/W/3NiAZFEm11P27nrSN5/tId+bR1SwV8CO1Jd72r4C08OLvplNlCDc3oQZ8efcW+jQ=")] // SHA256, 250000 iterations, 256-bit salt, 256-bit subkey
public void VerifyHashedPassword_Version2CompatMode_SuccessCases(string hashedPassword)
{
// Arrange
var hasher = new PasswordHasher(compatMode: PasswordHasherCompatibilityMode.IdentityV2);
// Act
var result = hasher.VerifyHashedPassword(null, hashedPassword, "my password");
// Assert
Assert.Equal(PasswordVerificationResult.Success, result);
}
[Theory]
// Version 2 payloads
[InlineData("ANXrDknc7fGPpigibZXXZFMX4aoqz44JveK6jQuwY3eH/UyPhvr5xTPeGYEckLxz9A==", PasswordVerificationResult.SuccessRehashNeeded)] // SHA1, 1000 iterations, 128-bit salt, 256-bit subkey
// Version 3 payloads
[InlineData("AQAAAAIAAAAyAAAAEOMwvh3+FZxqkdMBz2ekgGhwQ4B6pZWND6zgESBuWiHw", PasswordVerificationResult.SuccessRehashNeeded)] // SHA512, 50 iterations, 128-bit salt, 128-bit subkey
[InlineData("AQAAAAIAAAD6AAAAIJbVi5wbMR+htSfFp8fTw8N8GOS/Sje+S/4YZcgBfU7EQuqv4OkVYmc4VJl9AGZzmRTxSkP7LtVi9IWyUxX8IAAfZ8v+ZfhjCcudtC1YERSqE1OEdXLW9VukPuJWBBjLuw==", PasswordVerificationResult.SuccessRehashNeeded)] // SHA512, 250 iterations, 256-bit salt, 512-bit subkey
[InlineData("AQAAAAAAAAD6AAAAEAhftMyfTJylOlZT+eEotFXd1elee8ih5WsjXaR3PA9M", PasswordVerificationResult.SuccessRehashNeeded)] // SHA1, 250 iterations, 128-bit salt, 128-bit subkey
[InlineData("AQAAAAEAA9CQAAAAIESkQuj2Du8Y+kbc5lcN/W/3NiAZFEm11P27nrSN5/tId+bR1SwV8CO1Jd72r4C08OLvplNlCDc3oQZ8efcW+jQ=", PasswordVerificationResult.Success)] // SHA256, 250000 iterations, 256-bit salt, 256-bit subkey
public void VerifyHashedPassword_Version3CompatMode_SuccessCases(string hashedPassword, PasswordVerificationResult expectedResult)
{
// Arrange
var hasher = new PasswordHasher(compatMode: PasswordHasherCompatibilityMode.IdentityV3);
// Act
var actualResult = hasher.VerifyHashedPassword(null, hashedPassword, "my password");
// Assert
Assert.Equal(expectedResult, actualResult);
}
private sealed class PasswordHasher : PasswordHasher<object>
{
public PasswordHasher(PasswordHasherCompatibilityMode? compatMode = null, int? iterCount = null)
: base(BuildOptions(compatMode, iterCount))
{
}
private static IOptions<PasswordHasherOptions> BuildOptions(PasswordHasherCompatibilityMode? compatMode, int? iterCount)
{
var options = new PasswordHasherOptionsAccessor();
if (compatMode != null)
{
options.Value.CompatibilityMode = (PasswordHasherCompatibilityMode)compatMode;
}
if (iterCount != null)
{
options.Value.IterationCount = (int)iterCount;
}
return options;
}
}
private sealed class SequentialRandomNumberGenerator : RandomNumberGenerator
{
private byte _value;
public override void GetBytes(byte[] data)
{
for (int i = 0; i < data.Length; i++)
{
data[i] = _value++;
}
}
}
private class PasswordHasherOptionsAccessor : IOptions<PasswordHasherOptions>
{
public PasswordHasherOptions Value { get; } = new PasswordHasherOptions();
}
}
}
@@ -0,0 +1,190 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class PasswordValidatorTest
{
[Flags]
public enum Errors
{
None = 0,
Length = 2,
Alpha = 4,
Upper = 8,
Lower = 16,
Digit = 32,
}
[Fact]
public async Task ValidateThrowsWithNullTest()
{
// Setup
var validator = new PasswordValidator<TestUser>();
// Act
// Assert
await Assert.ThrowsAsync<ArgumentNullException>("password", () => validator.ValidateAsync(null, null, null));
await Assert.ThrowsAsync<ArgumentNullException>("manager", () => validator.ValidateAsync(null, null, "foo"));
}
[Theory]
[InlineData("")]
[InlineData("abc")]
[InlineData("abcde")]
public async Task FailsIfTooShortTests(string input)
{
const string error = "Passwords must be at least 6 characters.";
var manager = MockHelpers.TestUserManager<TestUser>();
var valid = new PasswordValidator<TestUser>();
manager.Options.Password.RequireUppercase = false;
manager.Options.Password.RequireNonAlphanumeric = false;
manager.Options.Password.RequireLowercase = false;
manager.Options.Password.RequireDigit = false;
IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input), error);
}
[Theory]
[InlineData("abcdef")]
[InlineData("aaaaaaaaaaa")]
public async Task SuccessIfLongEnoughTests(string input)
{
var manager = MockHelpers.TestUserManager<TestUser>();
var valid = new PasswordValidator<TestUser>();
manager.Options.Password.RequireUppercase = false;
manager.Options.Password.RequireNonAlphanumeric = false;
manager.Options.Password.RequireLowercase = false;
manager.Options.Password.RequireDigit = false;
IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input));
}
[Theory]
[InlineData("a")]
[InlineData("aaaaaaaaaaa")]
public async Task FailsWithoutRequiredNonAlphanumericTests(string input)
{
var manager = MockHelpers.TestUserManager<TestUser>();
var valid = new PasswordValidator<TestUser>();
manager.Options.Password.RequireUppercase = false;
manager.Options.Password.RequireNonAlphanumeric = true;
manager.Options.Password.RequireLowercase = false;
manager.Options.Password.RequireDigit = false;
manager.Options.Password.RequiredLength = 0;
IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input),
"Passwords must have at least one non alphanumeric character.");
}
[Theory]
[InlineData("@")]
[InlineData("abcd@e!ld!kajfd")]
[InlineData("!!!!!!")]
public async Task SucceedsWithRequiredNonAlphanumericTests(string input)
{
var manager = MockHelpers.TestUserManager<TestUser>();
var valid = new PasswordValidator<TestUser>();
manager.Options.Password.RequireUppercase = false;
manager.Options.Password.RequireNonAlphanumeric = true;
manager.Options.Password.RequireLowercase = false;
manager.Options.Password.RequireDigit = false;
manager.Options.Password.RequiredLength = 0;
IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input));
}
[Theory]
[InlineData("a", 2)]
[InlineData("aaaaaaaaaaa", 2)]
[InlineData("abcdabcdabcdabcdabcdabcdabcd", 5)]
public async Task FailsWithoutRequiredUniqueCharsTests(string input, int uniqueChars)
{
var manager = MockHelpers.TestUserManager<TestUser>();
var valid = new PasswordValidator<TestUser>();
manager.Options.Password.RequireUppercase = false;
manager.Options.Password.RequireNonAlphanumeric = false;
manager.Options.Password.RequireLowercase = false;
manager.Options.Password.RequireDigit = false;
manager.Options.Password.RequiredLength = 0;
manager.Options.Password.RequiredUniqueChars = uniqueChars;
IdentityResultAssert.IsFailure(await valid.ValidateAsync(manager, null, input),
String.Format("Passwords must use at least {0} different characters.", uniqueChars));
}
[Theory]
[InlineData("12345", 5)]
[InlineData("aAbBc", 5)]
[InlineData("aAbBcaAbBcaAbBc", 5)]
[InlineData("!@#$%", 5)]
[InlineData("a", 1)]
[InlineData("this is a long password with many chars", 10)]
public async Task SucceedsWithRequiredUniqueCharsTests(string input, int uniqueChars)
{
var manager = MockHelpers.TestUserManager<TestUser>();
var valid = new PasswordValidator<TestUser>();
manager.Options.Password.RequireUppercase = false;
manager.Options.Password.RequireNonAlphanumeric = false;
manager.Options.Password.RequireLowercase = false;
manager.Options.Password.RequireDigit = false;
manager.Options.Password.RequiredLength = 0;
manager.Options.Password.RequiredUniqueChars = uniqueChars;
IdentityResultAssert.IsSuccess(await valid.ValidateAsync(manager, null, input));
}
[Theory]
[InlineData("abcde", Errors.Length | Errors.Alpha | Errors.Upper | Errors.Digit)]
[InlineData("a@B@cd", Errors.Digit)]
[InlineData("___", Errors.Length | Errors.Digit | Errors.Lower | Errors.Upper)]
[InlineData("a_b9de", Errors.Upper)]
[InlineData("abcd@e!ld!kaj9Fd", Errors.None)]
[InlineData("aB1@df", Errors.None)]
public async Task UberMixedRequiredTests(string input, Errors errorMask)
{
const string alphaError = "Passwords must have at least one non alphanumeric character.";
const string upperError = "Passwords must have at least one uppercase ('A'-'Z').";
const string lowerError = "Passwords must have at least one lowercase ('a'-'z').";
const string digitError = "Passwords must have at least one digit ('0'-'9').";
const string lengthError = "Passwords must be at least 6 characters.";
var manager = MockHelpers.TestUserManager<TestUser>();
var valid = new PasswordValidator<TestUser>();
var errors = new List<string>();
if ((errorMask & Errors.Length) != Errors.None)
{
errors.Add(lengthError);
}
if ((errorMask & Errors.Alpha) != Errors.None)
{
errors.Add(alphaError);
}
if ((errorMask & Errors.Digit) != Errors.None)
{
errors.Add(digitError);
}
if ((errorMask & Errors.Lower) != Errors.None)
{
errors.Add(lowerError);
}
if ((errorMask & Errors.Upper) != Errors.None)
{
errors.Add(upperError);
}
var result = await valid.ValidateAsync(manager, null, input);
if (errors.Count == 0)
{
IdentityResultAssert.IsSuccess(result);
}
else
{
IdentityResultAssert.IsFailure(result);
foreach (var error in errors)
{
Assert.Contains(result.Errors, e => e.Description == error);
}
}
}
}
}
@@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Claims;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class ClaimsIdentityExtensionsTest
{
public const string ExternalAuthenticationScheme = "TestExternalAuth";
[Fact]
public void IdentityExtensionsFindFirstValueNullIfUnknownTest()
{
var id = CreateTestExternalIdentity();
Assert.Null(id.FindFirstValue("bogus"));
}
private static ClaimsPrincipal CreateTestExternalIdentity()
{
return new ClaimsPrincipal(new ClaimsIdentity(
new[]
{
new Claim(ClaimTypes.NameIdentifier, "NameIdentifier", null, ExternalAuthenticationScheme),
new Claim(ClaimTypes.Name, "Name")
},
ExternalAuthenticationScheme));
}
}
}
@@ -0,0 +1,212 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class RoleManagerTest
{
[Fact]
public async Task CreateCallsStore()
{
// Setup
var store = new Mock<IRoleStore<TestRole>>();
var role = new TestRole { Name = "Foo" };
store.Setup(s => s.CreateAsync(role, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable();
store.Setup(s => s.GetRoleNameAsync(role, CancellationToken.None)).Returns(Task.FromResult(role.Name)).Verifiable();
store.Setup(s => s.SetNormalizedRoleNameAsync(role, role.Name.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
var roleManager = MockHelpers.TestRoleManager(store.Object);
// Act
var result = await roleManager.CreateAsync(role);
// Assert
Assert.True(result.Succeeded);
store.VerifyAll();
}
[Fact]
public async Task UpdateCallsStore()
{
// Setup
var store = new Mock<IRoleStore<TestRole>>();
var role = new TestRole { Name = "Foo" };
store.Setup(s => s.UpdateAsync(role, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable();
store.Setup(s => s.GetRoleNameAsync(role, CancellationToken.None)).Returns(Task.FromResult(role.Name)).Verifiable();
store.Setup(s => s.SetNormalizedRoleNameAsync(role, role.Name.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable();
var roleManager = MockHelpers.TestRoleManager(store.Object);
// Act
var result = await roleManager.UpdateAsync(role);
// Assert
Assert.True(result.Succeeded);
store.VerifyAll();
}
[Fact]
public void RolesQueryableFailWhenStoreNotImplemented()
{
var manager = CreateRoleManager(new NoopRoleStore());
Assert.False(manager.SupportsQueryableRoles);
Assert.Throws<NotSupportedException>(() => manager.Roles.Count());
}
[Fact]
public async Task FindByNameCallsStoreWithNormalizedName()
{
// Setup
var store = new Mock<IRoleStore<TestRole>>();
var role = new TestRole { Name = "Foo" };
store.Setup(s => s.FindByNameAsync("FOO", CancellationToken.None)).Returns(Task.FromResult(role)).Verifiable();
var manager = MockHelpers.TestRoleManager(store.Object);
// Act
var result = await manager.FindByNameAsync(role.Name);
// Assert
Assert.Equal(role, result);
store.VerifyAll();
}
[Fact]
public async Task CanFindByNameCallsStoreWithoutNormalizedName()
{
// Setup
var store = new Mock<IRoleStore<TestRole>>();
var role = new TestRole { Name = "Foo" };
store.Setup(s => s.FindByNameAsync(role.Name, CancellationToken.None)).Returns(Task.FromResult(role)).Verifiable();
var manager = MockHelpers.TestRoleManager(store.Object);
manager.KeyNormalizer = null;
// Act
var result = await manager.FindByNameAsync(role.Name);
// Assert
Assert.Equal(role, result);
store.VerifyAll();
}
[Fact]
public async Task RoleExistsCallsStoreWithNormalizedName()
{
// Setup
var store = new Mock<IRoleStore<TestRole>>();
var role = new TestRole { Name = "Foo" };
store.Setup(s => s.FindByNameAsync("FOO", CancellationToken.None)).Returns(Task.FromResult(role)).Verifiable();
var manager = MockHelpers.TestRoleManager(store.Object);
// Act
var result = await manager.RoleExistsAsync(role.Name);
// Assert
Assert.True(result);
store.VerifyAll();
}
[Fact]
public void DisposeAfterDisposeDoesNotThrow()
{
var manager = CreateRoleManager(new NoopRoleStore());
manager.Dispose();
manager.Dispose();
}
[Fact]
public async Task RoleManagerPublicNullChecks()
{
Assert.Throws<ArgumentNullException>("store",
() => new RoleManager<TestRole>(null, null, null, null, null));
var manager = CreateRoleManager(new NotImplementedStore());
await Assert.ThrowsAsync<ArgumentNullException>("role", async () => await manager.CreateAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("role", async () => await manager.UpdateAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("role", async () => await manager.DeleteAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("roleName", async () => await manager.FindByNameAsync(null));
await Assert.ThrowsAsync<ArgumentNullException>("roleName", async () => await manager.RoleExistsAsync(null));
}
[Fact]
public async Task RoleStoreMethodsThrowWhenDisposed()
{
var manager = CreateRoleManager(new NoopRoleStore());
manager.Dispose();
await Assert.ThrowsAsync<ObjectDisposedException>(() => manager.FindByIdAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(() => manager.FindByNameAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(() => manager.RoleExistsAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(() => manager.CreateAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(() => manager.UpdateAsync(null));
await Assert.ThrowsAsync<ObjectDisposedException>(() => manager.DeleteAsync(null));
}
private static RoleManager<TestRole> CreateRoleManager(IRoleStore<TestRole> roleStore)
{
return MockHelpers.TestRoleManager(roleStore);
}
private class NotImplementedStore : IRoleStore<TestRole>
{
public Task<IdentityResult> CreateAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> UpdateAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<IdentityResult> DeleteAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<string> GetRoleIdAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<string> GetRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetRoleNameAsync(TestRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<TestRole> FindByIdAsync(string roleId, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task<TestRole> FindByNameAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
public Task<string> GetNormalizedRoleNameAsync(TestRole role, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetNormalizedRoleNameAsync(TestRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
}
}
}
@@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class RoleValidatorTest
{
[Fact]
public async Task ValidateThrowsWithNull()
{
// Setup
var validator = new RoleValidator<TestRole>();
var manager = MockHelpers.TestRoleManager<TestRole>();
// Act
// Assert
await Assert.ThrowsAsync<ArgumentNullException>("manager", async () => await validator.ValidateAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("role", async () => await validator.ValidateAsync(manager, null));
}
[Theory]
[InlineData(null)]
[InlineData("")]
public async Task ValidateFailsWithTooShortRoleName(string input)
{
// Setup
var validator = new RoleValidator<TestRole>();
var manager = MockHelpers.TestRoleManager<TestRole>();
var user = new TestRole {Name = input};
// Act
var result = await validator.ValidateAsync(manager, user);
// Assert
IdentityResultAssert.IsFailure(result, new IdentityErrorDescriber().InvalidRoleName(input));
}
}
}
@@ -0,0 +1,220 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class SecurityStampTest
{
private class NoopHandler : IAuthenticationHandler
{
public Task<AuthenticateResult> AuthenticateAsync()
{
throw new NotImplementedException();
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
throw new NotImplementedException();
}
public Task ForbidAsync(AuthenticationProperties properties)
{
throw new NotImplementedException();
}
public Task<bool> HandleRequestAsync()
{
throw new NotImplementedException();
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
throw new NotImplementedException();
}
public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
{
throw new NotImplementedException();
}
public Task SignOutAsync(AuthenticationProperties properties)
{
throw new NotImplementedException();
}
}
[Fact]
public async Task OnValidatePrincipalThrowsWithEmptyServiceCollection()
{
var httpContext = new Mock<HttpContext>();
httpContext.Setup(c => c.RequestServices).Returns(new ServiceCollection().BuildServiceProvider());
var id = new ClaimsPrincipal(new ClaimsIdentity(IdentityConstants.ApplicationScheme));
var ticket = new AuthenticationTicket(id, new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow }, IdentityConstants.ApplicationScheme);
var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(IdentityConstants.ApplicationScheme) { HandlerType = typeof(NoopHandler) }.Build(), new CookieAuthenticationOptions(), ticket);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => SecurityStampValidator.ValidatePrincipalAsync(context));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task OnValidatePrincipalTestSuccess(bool isPersistent)
{
var user = new TestUser("test");
var userManager = MockHelpers.MockUserManager<TestUser>();
var claimsManager = new Mock<IUserClaimsPrincipalFactory<TestUser>>();
var identityOptions = new Mock<IOptions<IdentityOptions>>();
identityOptions.Setup(a => a.Value).Returns(new IdentityOptions());
var options = new Mock<IOptions<SecurityStampValidatorOptions>>();
options.Setup(a => a.Value).Returns(new SecurityStampValidatorOptions { ValidationInterval = TimeSpan.Zero });
var httpContext = new Mock<HttpContext>();
var contextAccessor = new Mock<IHttpContextAccessor>();
contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object);
var id = new ClaimsIdentity(IdentityConstants.ApplicationScheme);
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
var principal = new ClaimsPrincipal(id);
var properties = new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow.AddSeconds(-1), IsPersistent = isPersistent };
var signInManager = new Mock<SignInManager<TestUser>>(userManager.Object,
contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock<IAuthenticationSchemeProvider>().Object);
signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny<ClaimsPrincipal>())).ReturnsAsync(user).Verifiable();
signInManager.Setup(s => s.CreateUserPrincipalAsync(user)).ReturnsAsync(principal).Verifiable();
var services = new ServiceCollection();
services.AddSingleton(options.Object);
services.AddSingleton(signInManager.Object);
services.AddSingleton<ISecurityStampValidator>(new SecurityStampValidator<TestUser>(options.Object, signInManager.Object, new SystemClock()));
httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider());
var ticket = new AuthenticationTicket(principal,
properties,
IdentityConstants.ApplicationScheme);
var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(IdentityConstants.ApplicationScheme) { HandlerType = typeof(NoopHandler) }.Build(), new CookieAuthenticationOptions(), ticket);
Assert.NotNull(context.Properties);
Assert.NotNull(context.Options);
Assert.NotNull(context.Principal);
await
SecurityStampValidator.ValidatePrincipalAsync(context);
Assert.NotNull(context.Principal);
signInManager.VerifyAll();
}
[Fact]
public async Task OnValidateIdentityRejectsWhenValidateSecurityStampFails()
{
var user = new TestUser("test");
var userManager = MockHelpers.MockUserManager<TestUser>();
var claimsManager = new Mock<IUserClaimsPrincipalFactory<TestUser>>();
var identityOptions = new Mock<IOptions<IdentityOptions>>();
identityOptions.Setup(a => a.Value).Returns(new IdentityOptions());
var options = new Mock<IOptions<SecurityStampValidatorOptions>>();
options.Setup(a => a.Value).Returns(new SecurityStampValidatorOptions { ValidationInterval = TimeSpan.Zero });
var httpContext = new Mock<HttpContext>();
var contextAccessor = new Mock<IHttpContextAccessor>();
contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object);
var signInManager = new Mock<SignInManager<TestUser>>(userManager.Object,
contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock<IAuthenticationSchemeProvider>().Object);
signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny<ClaimsPrincipal>())).ReturnsAsync(default(TestUser)).Verifiable();
var services = new ServiceCollection();
services.AddSingleton(options.Object);
services.AddSingleton(signInManager.Object);
services.AddSingleton<ISecurityStampValidator>(new SecurityStampValidator<TestUser>(options.Object, signInManager.Object, new SystemClock()));
httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider());
var id = new ClaimsIdentity(IdentityConstants.ApplicationScheme);
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
var ticket = new AuthenticationTicket(new ClaimsPrincipal(id),
new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow.AddSeconds(-1) },
IdentityConstants.ApplicationScheme);
var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(IdentityConstants.ApplicationScheme) { HandlerType = typeof(NoopHandler) }.Build(), new CookieAuthenticationOptions(), ticket);
Assert.NotNull(context.Properties);
Assert.NotNull(context.Options);
Assert.NotNull(context.Principal);
await SecurityStampValidator.ValidatePrincipalAsync(context);
Assert.Null(context.Principal);
signInManager.VerifyAll();
}
[Fact]
public async Task OnValidateIdentityRejectsWhenNoIssuedUtc()
{
var user = new TestUser("test");
var httpContext = new Mock<HttpContext>();
var userManager = MockHelpers.MockUserManager<TestUser>();
var identityOptions = new Mock<IOptions<IdentityOptions>>();
identityOptions.Setup(a => a.Value).Returns(new IdentityOptions());
var claimsManager = new Mock<IUserClaimsPrincipalFactory<TestUser>>();
var options = new Mock<IOptions<SecurityStampValidatorOptions>>();
options.Setup(a => a.Value).Returns(new SecurityStampValidatorOptions { ValidationInterval = TimeSpan.Zero });
var contextAccessor = new Mock<IHttpContextAccessor>();
contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object);
var signInManager = new Mock<SignInManager<TestUser>>(userManager.Object,
contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock<IAuthenticationSchemeProvider>().Object);
signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny<ClaimsPrincipal>())).ReturnsAsync(default(TestUser)).Verifiable();
var services = new ServiceCollection();
services.AddSingleton(options.Object);
services.AddSingleton(signInManager.Object);
services.AddSingleton<ISecurityStampValidator>(new SecurityStampValidator<TestUser>(options.Object, signInManager.Object, new SystemClock()));
httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider());
var id = new ClaimsIdentity(IdentityConstants.ApplicationScheme);
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
var ticket = new AuthenticationTicket(new ClaimsPrincipal(id),
new AuthenticationProperties(),
IdentityConstants.ApplicationScheme);
var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(IdentityConstants.ApplicationScheme) { HandlerType = typeof(NoopHandler) }.Build(), new CookieAuthenticationOptions(), ticket);
Assert.NotNull(context.Properties);
Assert.NotNull(context.Options);
Assert.NotNull(context.Principal);
await SecurityStampValidator.ValidatePrincipalAsync(context);
Assert.Null(context.Principal);
signInManager.VerifyAll();
}
[Fact]
public async Task OnValidateIdentityDoesNotRejectsWhenNotExpired()
{
var user = new TestUser("test");
var httpContext = new Mock<HttpContext>();
var userManager = MockHelpers.MockUserManager<TestUser>();
var identityOptions = new Mock<IOptions<IdentityOptions>>();
identityOptions.Setup(a => a.Value).Returns(new IdentityOptions());
var claimsManager = new Mock<IUserClaimsPrincipalFactory<TestUser>>();
var options = new Mock<IOptions<SecurityStampValidatorOptions>>();
options.Setup(a => a.Value).Returns(new SecurityStampValidatorOptions { ValidationInterval = TimeSpan.FromDays(1) });
var contextAccessor = new Mock<IHttpContextAccessor>();
contextAccessor.Setup(a => a.HttpContext).Returns(httpContext.Object);
var signInManager = new Mock<SignInManager<TestUser>>(userManager.Object,
contextAccessor.Object, claimsManager.Object, identityOptions.Object, null, new Mock<IAuthenticationSchemeProvider>().Object);
signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny<ClaimsPrincipal>())).Throws(new Exception("Shouldn't be called"));
signInManager.Setup(s => s.SignInAsync(user, false, null)).Throws(new Exception("Shouldn't be called"));
var services = new ServiceCollection();
services.AddSingleton(options.Object);
services.AddSingleton(signInManager.Object);
services.AddSingleton<ISecurityStampValidator>(new SecurityStampValidator<TestUser>(options.Object, signInManager.Object, new SystemClock()));
httpContext.Setup(c => c.RequestServices).Returns(services.BuildServiceProvider());
var id = new ClaimsIdentity(IdentityConstants.ApplicationScheme);
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
var ticket = new AuthenticationTicket(new ClaimsPrincipal(id),
new AuthenticationProperties { IssuedUtc = DateTimeOffset.UtcNow },
IdentityConstants.ApplicationScheme);
var context = new CookieValidatePrincipalContext(httpContext.Object, new AuthenticationSchemeBuilder(IdentityConstants.ApplicationScheme) { HandlerType = typeof(NoopHandler) }.Build(), new CookieAuthenticationOptions(), ticket);
Assert.NotNull(context.Properties);
Assert.NotNull(context.Options);
Assert.NotNull(context.Principal);
await SecurityStampValidator.ValidatePrincipalAsync(context);
Assert.NotNull(context.Principal);
}
}
}
@@ -0,0 +1,866 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class SignManagerInTest
{
//[Theory]
//[InlineData(true)]
//[InlineData(false)]
//public async Task VerifyAccountControllerSignInFunctional(bool isPersistent)
//{
// var app = new ApplicationBuilder(new ServiceCollection().BuildServiceProvider());
// app.UseCookieAuthentication(new CookieAuthenticationOptions
// {
// AuthenticationScheme = ClaimsIdentityOptions.DefaultAuthenticationScheme
// });
// TODO: how to functionally test context?
// var context = new DefaultHttpContext(new FeatureCollection());
// var contextAccessor = new Mock<IHttpContextAccessor>();
// contextAccessor.Setup(a => a.Value).Returns(context);
// app.UseServices(services =>
// {
// services.AddSingleton(contextAccessor.Object);
// services.AddSingleton<Ilogger>(new Nulllogger());
// services.AddIdentity<ApplicationUser, IdentityRole>(s =>
// {
// s.AddUserStore(() => new InMemoryUserStore<ApplicationUser>());
// s.AddUserManager<ApplicationUserManager>();
// s.AddRoleStore(() => new InMemoryRoleStore<IdentityRole>());
// s.AddRoleManager<ApplicationRoleManager>();
// });
// services.AddTransient<ApplicationHttpSignInManager>();
// });
// // Act
// var user = new ApplicationUser
// {
// UserName = "Yolo"
// };
// const string password = "Yol0Sw@g!";
// var userManager = app.ApplicationServices.GetRequiredService<ApplicationUserManager>();
// var HttpSignInManager = app.ApplicationServices.GetRequiredService<ApplicationHttpSignInManager>();
// IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user, password));
// var result = await HttpSignInManager.PasswordSignInAsync(user.UserName, password, isPersistent, false);
// // Assert
// Assert.Equal(SignInStatus.Success, result);
// contextAccessor.Verify();
//}
[Fact]
public void ConstructorNullChecks()
{
Assert.Throws<ArgumentNullException>("userManager", () => new SignInManager<TestUser>(null, null, null, null, null, null));
var userManager = MockHelpers.MockUserManager<TestUser>().Object;
Assert.Throws<ArgumentNullException>("contextAccessor", () => new SignInManager<TestUser>(userManager, null, null, null, null, null));
var contextAccessor = new Mock<IHttpContextAccessor>();
var context = new Mock<HttpContext>();
contextAccessor.Setup(a => a.HttpContext).Returns(context.Object);
Assert.Throws<ArgumentNullException>("claimsFactory", () => new SignInManager<TestUser>(userManager, contextAccessor.Object, null, null, null, null));
}
//[Fact]
//public async Task EnsureClaimsPrincipalFactoryCreateIdentityCalled()
//{
// // Setup
// var user = new TestUser { UserName = "Foo" };
// var userManager = MockHelpers.TestUserManager<TestUser>();
// var identityFactory = new Mock<IUserClaimsPrincipalFactory<TestUser>>();
// const string authType = "Test";
// var testIdentity = new ClaimsPrincipal();
// identityFactory.Setup(s => s.CreateAsync(user)).ReturnsAsync(testIdentity).Verifiable();
// var context = new Mock<HttpContext>();
// var response = new Mock<HttpResponse>();
// context.Setup(c => c.Response).Returns(response.Object).Verifiable();
// response.Setup(r => r.SignIn(testIdentity, It.IsAny<AuthenticationProperties>())).Verifiable();
// var contextAccessor = new Mock<IHttpContextAccessor>();
// contextAccessor.Setup(a => a.Value).Returns(context.Object);
// var helper = new HttpAuthenticationManager(contextAccessor.Object);
// // Act
// helper.SignIn(user, false);
// // Assert
// identityFactory.Verify();
// context.Verify();
// contextAccessor.Verify();
// response.Verify();
//}
[Fact]
public async Task PasswordSignInReturnsLockedOutWhenLockedOut()
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(true).Verifiable();
var context = new Mock<HttpContext>();
var contextAccessor = new Mock<IHttpContextAccessor>();
contextAccessor.Setup(a => a.HttpContext).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager<TestRole>();
var identityOptions = new IdentityOptions();
var options = new Mock<IOptions<IdentityOptions>>();
options.Setup(a => a.Value).Returns(identityOptions);
var claimsFactory = new UserClaimsPrincipalFactory<TestUser, TestRole>(manager.Object, roleManager.Object, options.Object);
var logStore = new StringBuilder();
var logger = MockHelpers.MockILogger<SignInManager<TestUser>>(logStore);
var helper = new SignInManager<TestUser>(manager.Object, contextAccessor.Object, claimsFactory, options.Object, logger.Object, new Mock<IAuthenticationSchemeProvider>().Object);
// Act
var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false);
// Assert
Assert.False(result.Succeeded);
Assert.True(result.IsLockedOut);
Assert.Contains($"User {user.Id} is currently locked out.", logStore.ToString());
manager.Verify();
}
[Fact]
public async Task CheckPasswordSignInReturnsLockedOutWhenLockedOut()
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(true).Verifiable();
var context = new Mock<HttpContext>();
var contextAccessor = new Mock<IHttpContextAccessor>();
contextAccessor.Setup(a => a.HttpContext).Returns(context.Object);
var roleManager = MockHelpers.MockRoleManager<TestRole>();
var identityOptions = new IdentityOptions();
var options = new Mock<IOptions<IdentityOptions>>();
options.Setup(a => a.Value).Returns(identityOptions);
var claimsFactory = new UserClaimsPrincipalFactory<TestUser, TestRole>(manager.Object, roleManager.Object, options.Object);
var logStore = new StringBuilder();
var logger = MockHelpers.MockILogger<SignInManager<TestUser>>(logStore);
var helper = new SignInManager<TestUser>(manager.Object, contextAccessor.Object, claimsFactory, options.Object, logger.Object, new Mock<IAuthenticationSchemeProvider>().Object);
// Act
var result = await helper.CheckPasswordSignInAsync(user, "bogus", false);
// Assert
Assert.False(result.Succeeded);
Assert.True(result.IsLockedOut);
Assert.Contains($"User {user.Id} is currently locked out.", logStore.ToString());
manager.Verify();
}
private static Mock<UserManager<TestUser>> SetupUserManager(TestUser user)
{
var manager = MockHelpers.MockUserManager<TestUser>();
manager.Setup(m => m.FindByNameAsync(user.UserName)).ReturnsAsync(user);
manager.Setup(m => m.FindByIdAsync(user.Id)).ReturnsAsync(user);
manager.Setup(m => m.GetUserIdAsync(user)).ReturnsAsync(user.Id.ToString());
manager.Setup(m => m.GetUserNameAsync(user)).ReturnsAsync(user.UserName);
return manager;
}
private static SignInManager<TestUser> SetupSignInManager(UserManager<TestUser> manager, HttpContext context, StringBuilder logStore = null, IdentityOptions identityOptions = null)
{
var contextAccessor = new Mock<IHttpContextAccessor>();
contextAccessor.Setup(a => a.HttpContext).Returns(context);
var roleManager = MockHelpers.MockRoleManager<TestRole>();
identityOptions = identityOptions ?? new IdentityOptions();
var options = new Mock<IOptions<IdentityOptions>>();
options.Setup(a => a.Value).Returns(identityOptions);
var claimsFactory = new UserClaimsPrincipalFactory<TestUser, TestRole>(manager, roleManager.Object, options.Object);
var sm = new SignInManager<TestUser>(manager, contextAccessor.Object, claimsFactory, options.Object, null, new Mock<IAuthenticationSchemeProvider>().Object);
sm.Logger = MockHelpers.MockILogger<SignInManager<TestUser>>(logStore ?? new StringBuilder()).Object;
return sm;
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CanPasswordSignIn(bool isPersistent)
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
var context = new DefaultHttpContext();
var auth = MockAuth(context);
SetupSignIn(context, auth, user.Id, isPersistent);
var helper = SetupSignInManager(manager.Object, context);
// Act
var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false);
// Assert
Assert.True(result.Succeeded);
manager.Verify();
auth.Verify();
}
[Fact]
public async Task CanPasswordSignInWithNoLogger()
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
var context = new DefaultHttpContext();
var auth = MockAuth(context);
SetupSignIn(context, auth, user.Id, false);
var helper = SetupSignInManager(manager.Object, context);
// Act
var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false);
// Assert
Assert.True(result.Succeeded);
manager.Verify();
auth.Verify();
}
[Fact]
public async Task PasswordSignInWorksWithNonTwoFactorStore()
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable();
var context = new DefaultHttpContext();
var auth = MockAuth(context);
SetupSignIn(context, auth);
var helper = SetupSignInManager(manager.Object, context);
// Act
var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false);
// Assert
Assert.True(result.Succeeded);
manager.Verify();
auth.Verify();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task PasswordSignInRequiresVerification(bool supportsLockout)
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.SupportsUserLockout).Returns(supportsLockout).Verifiable();
if (supportsLockout)
{
manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
}
IList<string> providers = new List<string>();
providers.Add("PhoneNumber");
manager.Setup(m => m.GetValidTwoFactorProvidersAsync(user)).Returns(Task.FromResult(providers)).Verifiable();
manager.Setup(m => m.SupportsUserTwoFactor).Returns(true).Verifiable();
manager.Setup(m => m.GetTwoFactorEnabledAsync(user)).ReturnsAsync(true).Verifiable();
manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
if (supportsLockout)
{
manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable();
}
var context = new DefaultHttpContext();
var helper = SetupSignInManager(manager.Object, context);
var auth = MockAuth(context);
auth.Setup(a => a.SignInAsync(context, IdentityConstants.TwoFactorUserIdScheme,
It.Is<ClaimsPrincipal>(id => id.FindFirstValue(ClaimTypes.Name) == user.Id),
It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
// Act
var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false);
// Assert
Assert.False(result.Succeeded);
Assert.True(result.RequiresTwoFactor);
manager.Verify();
auth.Verify();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ExternalSignInRequiresVerificationIfNotBypassed(bool bypass)
{
// Setup
var user = new TestUser { UserName = "Foo" };
const string loginProvider = "login";
const string providerKey = "fookey";
var manager = SetupUserManager(user);
manager.Setup(m => m.SupportsUserLockout).Returns(false).Verifiable();
manager.Setup(m => m.FindByLoginAsync(loginProvider, providerKey)).ReturnsAsync(user).Verifiable();
if (!bypass)
{
IList<string> providers = new List<string>();
providers.Add("PhoneNumber");
manager.Setup(m => m.GetValidTwoFactorProvidersAsync(user)).Returns(Task.FromResult(providers)).Verifiable();
manager.Setup(m => m.SupportsUserTwoFactor).Returns(true).Verifiable();
manager.Setup(m => m.GetTwoFactorEnabledAsync(user)).ReturnsAsync(true).Verifiable();
}
var context = new DefaultHttpContext();
var auth = MockAuth(context);
var helper = SetupSignInManager(manager.Object, context);
if (bypass)
{
SetupSignIn(context, auth, user.Id, false, loginProvider);
}
else
{
auth.Setup(a => a.SignInAsync(context, IdentityConstants.TwoFactorUserIdScheme,
It.Is<ClaimsPrincipal>(id => id.FindFirstValue(ClaimTypes.Name) == user.Id),
It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
}
// Act
var result = await helper.ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent: false, bypassTwoFactor: bypass);
// Assert
Assert.Equal(bypass, result.Succeeded);
Assert.Equal(!bypass, result.RequiresTwoFactor);
manager.Verify();
auth.Verify();
}
private class GoodTokenProvider : AuthenticatorTokenProvider<TestUser>
{
public override Task<bool> ValidateAsync(string purpose, string token, UserManager<TestUser> manager, TestUser user)
{
return Task.FromResult(true);
}
}
//[Theory]
//[InlineData(null, true, true)]
//[InlineData("Authenticator", false, true)]
//[InlineData("Gooblygook", true, false)]
//[InlineData("--", false, false)]
//public async Task CanTwoFactorAuthenticatorSignIn(string providerName, bool isPersistent, bool rememberClient)
//{
// // Setup
// var user = new TestUser { UserName = "Foo" };
// const string code = "3123";
// var manager = SetupUserManager(user);
// manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
// manager.Setup(m => m.VerifyTwoFactorTokenAsync(user, providerName ?? TokenOptions.DefaultAuthenticatorProvider, code)).ReturnsAsync(true).Verifiable();
// manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable();
// var context = new DefaultHttpContext();
// var auth = MockAuth(context);
// var helper = SetupSignInManager(manager.Object, context);
// var twoFactorInfo = new SignInManager<TestUser>.TwoFactorAuthenticationInfo { UserId = user.Id };
// if (providerName != null)
// {
// helper.Options.Tokens.AuthenticatorTokenProvider = providerName;
// }
// var id = helper.StoreTwoFactorInfo(user.Id, null);
// SetupSignIn(context, auth, user.Id, isPersistent);
// auth.Setup(a => a.AuthenticateAsync(context, IdentityConstants.TwoFactorUserIdScheme))
// .ReturnsAsync(AuthenticateResult.Success(new AuthenticationTicket(id, null, IdentityConstants.TwoFactorUserIdScheme))).Verifiable();
// if (rememberClient)
// {
// auth.Setup(a => a.SignInAsync(context,
// IdentityConstants.TwoFactorRememberMeScheme,
// It.Is<ClaimsPrincipal>(i => i.FindFirstValue(ClaimTypes.Name) == user.Id
// && i.Identities.First().AuthenticationType == IdentityConstants.TwoFactorRememberMeScheme),
// It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
// }
// // Act
// var result = await helper.TwoFactorAuthenticatorSignInAsync(code, isPersistent, rememberClient);
// // Assert
// Assert.True(result.Succeeded);
// manager.Verify();
// auth.Verify();
//}
//[Theory]
//[InlineData(true, true)]
//[InlineData(true, false)]
//[InlineData(false, true)]
//[InlineData(false, false)]
//public async Task CanTwoFactorRecoveryCodeSignIn(bool supportsLockout, bool externalLogin)
//{
// // Setup
// var user = new TestUser { UserName = "Foo" };
// const string bypassCode = "someCode";
// var manager = SetupUserManager(user);
// manager.Setup(m => m.SupportsUserLockout).Returns(supportsLockout).Verifiable();
// manager.Setup(m => m.RedeemTwoFactorRecoveryCodeAsync(user, bypassCode)).ReturnsAsync(IdentityResult.Success).Verifiable();
// if (supportsLockout)
// {
// manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable();
// }
// var context = new DefaultHttpContext();
// var auth = MockAuth(context);
// var helper = SetupSignInManager(manager.Object, context);
// var twoFactorInfo = new SignInManager<TestUser>.TwoFactorAuthenticationInfo { UserId = user.Id };
// var loginProvider = "loginprovider";
// var id = helper.StoreTwoFactorInfo(user.Id, externalLogin ? loginProvider : null);
// if (externalLogin)
// {
// auth.Setup(a => a.SignInAsync(context,
// IdentityConstants.ApplicationScheme,
// It.Is<ClaimsPrincipal>(i => i.FindFirstValue(ClaimTypes.AuthenticationMethod) == loginProvider
// && i.FindFirstValue(ClaimTypes.NameIdentifier) == user.Id),
// It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
// auth.Setup(a => a.SignOutAsync(context, IdentityConstants.ExternalScheme, It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
// auth.Setup(a => a.SignOutAsync(context, IdentityConstants.TwoFactorUserIdScheme, It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
// }
// else
// {
// SetupSignIn(context, auth, user.Id);
// }
// auth.Setup(a => a.AuthenticateAsync(context, IdentityConstants.TwoFactorUserIdScheme))
// .ReturnsAsync(AuthenticateResult.Success(new AuthenticationTicket(id, null, IdentityConstants.TwoFactorUserIdScheme))).Verifiable();
// // Act
// var result = await helper.TwoFactorRecoveryCodeSignInAsync(bypassCode);
// // Assert
// Assert.True(result.Succeeded);
// manager.Verify();
// auth.Verify();
//}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task CanExternalSignIn(bool isPersistent, bool supportsLockout)
{
// Setup
var user = new TestUser { UserName = "Foo" };
const string loginProvider = "login";
const string providerKey = "fookey";
var manager = SetupUserManager(user);
manager.Setup(m => m.SupportsUserLockout).Returns(supportsLockout).Verifiable();
if (supportsLockout)
{
manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
}
manager.Setup(m => m.FindByLoginAsync(loginProvider, providerKey)).ReturnsAsync(user).Verifiable();
var context = new DefaultHttpContext();
var auth = MockAuth(context);
var helper = SetupSignInManager(manager.Object, context);
SetupSignIn(context, auth, user.Id, isPersistent, loginProvider);
// Act
var result = await helper.ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent);
// Assert
Assert.True(result.Succeeded);
manager.Verify();
auth.Verify();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CanResignIn(bool externalLogin)
{
// Setup
var user = new TestUser { UserName = "Foo" };
var context = new DefaultHttpContext();
var auth = MockAuth(context);
var loginProvider = "loginprovider";
var id = new ClaimsIdentity();
if (externalLogin)
{
id.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, loginProvider));
}
// REVIEW: auth changes we lost the ability to mock is persistent
//var properties = new AuthenticationProperties { IsPersistent = isPersistent };
var authResult = AuthenticateResult.NoResult();
auth.Setup(a => a.AuthenticateAsync(context, IdentityConstants.ApplicationScheme))
.Returns(Task.FromResult(authResult)).Verifiable();
var manager = SetupUserManager(user);
var signInManager = new Mock<SignInManager<TestUser>>(manager.Object,
new HttpContextAccessor { HttpContext = context },
new Mock<IUserClaimsPrincipalFactory<TestUser>>().Object,
null, null, new Mock<IAuthenticationSchemeProvider>().Object)
{ CallBase = true };
//signInManager.Setup(s => s.SignInAsync(user, It.Is<AuthenticationProperties>(p => p.IsPersistent == isPersistent),
//externalLogin? loginProvider : null)).Returns(Task.FromResult(0)).Verifiable();
signInManager.Setup(s => s.SignInAsync(user, It.IsAny<AuthenticationProperties>(), null)).Returns(Task.FromResult(0)).Verifiable();
signInManager.Object.Context = context;
// Act
await signInManager.Object.RefreshSignInAsync(user);
// Assert
auth.Verify();
signInManager.Verify();
}
//[Theory]
//[InlineData(true, true, true, true)]
//[InlineData(true, true, false, true)]
//[InlineData(true, false, true, true)]
//[InlineData(true, false, false, true)]
//[InlineData(false, true, true, true)]
//[InlineData(false, true, false, true)]
//[InlineData(false, false, true, true)]
//[InlineData(false, false, false, true)]
//[InlineData(true, true, true, false)]
//[InlineData(true, true, false, false)]
//[InlineData(true, false, true, false)]
//[InlineData(true, false, false, false)]
//[InlineData(false, true, true, false)]
//[InlineData(false, true, false, false)]
//[InlineData(false, false, true, false)]
//[InlineData(false, false, false, false)]
//public async Task CanTwoFactorSignIn(bool isPersistent, bool supportsLockout, bool externalLogin, bool rememberClient)
//{
// // Setup
// var user = new TestUser { UserName = "Foo" };
// var manager = SetupUserManager(user);
// var provider = "twofactorprovider";
// var code = "123456";
// manager.Setup(m => m.SupportsUserLockout).Returns(supportsLockout).Verifiable();
// if (supportsLockout)
// {
// manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
// manager.Setup(m => m.ResetAccessFailedCountAsync(user)).ReturnsAsync(IdentityResult.Success).Verifiable();
// }
// manager.Setup(m => m.VerifyTwoFactorTokenAsync(user, provider, code)).ReturnsAsync(true).Verifiable();
// var context = new DefaultHttpContext();
// var auth = MockAuth(context);
// var helper = SetupSignInManager(manager.Object, context);
// var twoFactorInfo = new SignInManager<TestUser>.TwoFactorAuthenticationInfo { UserId = user.Id };
// var loginProvider = "loginprovider";
// var id = helper.StoreTwoFactorInfo(user.Id, externalLogin ? loginProvider : null);
// if (externalLogin)
// {
// auth.Setup(a => a.SignInAsync(context,
// IdentityConstants.ApplicationScheme,
// It.Is<ClaimsPrincipal>(i => i.FindFirstValue(ClaimTypes.AuthenticationMethod) == loginProvider
// && i.FindFirstValue(ClaimTypes.NameIdentifier) == user.Id),
// It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
// // REVIEW: restore ability to test is persistent
// //It.Is<AuthenticationProperties>(v => v.IsPersistent == isPersistent))).Verifiable();
// auth.Setup(a => a.SignOutAsync(context, IdentityConstants.ExternalScheme, It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
// auth.Setup(a => a.SignOutAsync(context, IdentityConstants.TwoFactorUserIdScheme, It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
// }
// else
// {
// SetupSignIn(context, auth, user.Id);
// }
// if (rememberClient)
// {
// auth.Setup(a => a.SignInAsync(context,
// IdentityConstants.TwoFactorRememberMeScheme,
// It.Is<ClaimsPrincipal>(i => i.FindFirstValue(ClaimTypes.Name) == user.Id
// && i.Identities.First().AuthenticationType == IdentityConstants.TwoFactorRememberMeScheme),
// It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
// //It.Is<AuthenticationProperties>(v => v.IsPersistent == true))).Returns(Task.FromResult(0)).Verifiable();
// }
// auth.Setup(a => a.AuthenticateAsync(context, IdentityConstants.TwoFactorUserIdScheme))
// .ReturnsAsync(AuthenticateResult.Success(new AuthenticationTicket(id, null, IdentityConstants.TwoFactorUserIdScheme))).Verifiable();
// // Act
// var result = await helper.TwoFactorSignInAsync(provider, code, isPersistent, rememberClient);
// // Assert
// Assert.True(result.Succeeded);
// manager.Verify();
// auth.Verify();
//}
[Fact]
public async Task RememberClientStoresUserId()
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
var context = new DefaultHttpContext();
var auth = MockAuth(context);
var helper = SetupSignInManager(manager.Object, context);
auth.Setup(a => a.SignInAsync(
context,
IdentityConstants.TwoFactorRememberMeScheme,
It.Is<ClaimsPrincipal>(i => i.FindFirstValue(ClaimTypes.Name) == user.Id
&& i.Identities.First().AuthenticationType == IdentityConstants.TwoFactorRememberMeScheme),
It.Is<AuthenticationProperties>(v => v.IsPersistent == true))).Returns(Task.FromResult(0)).Verifiable();
// Act
await helper.RememberTwoFactorClientAsync(user);
// Assert
manager.Verify();
auth.Verify();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task RememberBrowserSkipsTwoFactorVerificationSignIn(bool isPersistent)
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.GetTwoFactorEnabledAsync(user)).ReturnsAsync(true).Verifiable();
IList<string> providers = new List<string>();
providers.Add("PhoneNumber");
manager.Setup(m => m.GetValidTwoFactorProvidersAsync(user)).Returns(Task.FromResult(providers)).Verifiable();
manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
manager.Setup(m => m.SupportsUserTwoFactor).Returns(true).Verifiable();
manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
var context = new DefaultHttpContext();
var auth = MockAuth(context);
SetupSignIn(context, auth);
var id = new ClaimsIdentity(IdentityConstants.TwoFactorRememberMeScheme);
id.AddClaim(new Claim(ClaimTypes.Name, user.Id));
auth.Setup(a => a.AuthenticateAsync(context, IdentityConstants.TwoFactorRememberMeScheme))
.ReturnsAsync(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(id), null, IdentityConstants.TwoFactorRememberMeScheme))).Verifiable();
var helper = SetupSignInManager(manager.Object, context);
// Act
var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false);
// Assert
Assert.True(result.Succeeded);
manager.Verify();
auth.Verify();
}
private Mock<IAuthenticationService> MockAuth(HttpContext context)
{
var auth = new Mock<IAuthenticationService>();
context.RequestServices = new ServiceCollection().AddSingleton(auth.Object).BuildServiceProvider();
return auth;
}
[Fact]
public async Task SignOutCallsContextResponseSignOut()
{
// Setup
var manager = MockHelpers.TestUserManager<TestUser>();
var context = new DefaultHttpContext();
var auth = MockAuth(context);
auth.Setup(a => a.SignOutAsync(context, IdentityConstants.ApplicationScheme, It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
auth.Setup(a => a.SignOutAsync(context, IdentityConstants.TwoFactorUserIdScheme, It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
auth.Setup(a => a.SignOutAsync(context, IdentityConstants.ExternalScheme, It.IsAny<AuthenticationProperties>())).Returns(Task.FromResult(0)).Verifiable();
var helper = SetupSignInManager(manager, context, null, manager.Options);
// Act
await helper.SignOutAsync();
// Assert
auth.Verify();
}
[Fact]
public async Task PasswordSignInFailsWithWrongPassword()
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
manager.Setup(m => m.IsLockedOutAsync(user)).ReturnsAsync(false).Verifiable();
manager.Setup(m => m.CheckPasswordAsync(user, "bogus")).ReturnsAsync(false).Verifiable();
var context = new Mock<HttpContext>();
var logStore = new StringBuilder();
var helper = SetupSignInManager(manager.Object, context.Object, logStore);
// Act
var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false);
var checkResult = await helper.CheckPasswordSignInAsync(user, "bogus", false);
// Assert
Assert.False(result.Succeeded);
Assert.False(checkResult.Succeeded);
Assert.True(logStore.ToString().Contains($"User {user.Id} failed to provide the correct password."));
manager.Verify();
context.Verify();
}
[Fact]
public async Task PasswordSignInFailsWithUnknownUser()
{
// Setup
var manager = MockHelpers.MockUserManager<TestUser>();
manager.Setup(m => m.FindByNameAsync("bogus")).ReturnsAsync(default(TestUser)).Verifiable();
var context = new Mock<HttpContext>();
var helper = SetupSignInManager(manager.Object, context.Object);
// Act
var result = await helper.PasswordSignInAsync("bogus", "bogus", false, false);
// Assert
Assert.False(result.Succeeded);
manager.Verify();
context.Verify();
}
[Fact]
public async Task PasswordSignInFailsWithWrongPasswordCanAccessFailedAndLockout()
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
var lockedout = false;
manager.Setup(m => m.AccessFailedAsync(user)).Returns(() =>
{
lockedout = true;
return Task.FromResult(IdentityResult.Success);
}).Verifiable();
manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
manager.Setup(m => m.IsLockedOutAsync(user)).Returns(() => Task.FromResult(lockedout));
manager.Setup(m => m.CheckPasswordAsync(user, "bogus")).ReturnsAsync(false).Verifiable();
var context = new Mock<HttpContext>();
var helper = SetupSignInManager(manager.Object, context.Object);
// Act
var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, true);
// Assert
Assert.False(result.Succeeded);
Assert.True(result.IsLockedOut);
manager.Verify();
}
[Fact]
public async Task CheckPasswordSignInFailsWithWrongPasswordCanAccessFailedAndLockout()
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
var lockedout = false;
manager.Setup(m => m.AccessFailedAsync(user)).Returns(() =>
{
lockedout = true;
return Task.FromResult(IdentityResult.Success);
}).Verifiable();
manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable();
manager.Setup(m => m.IsLockedOutAsync(user)).Returns(() => Task.FromResult(lockedout));
manager.Setup(m => m.CheckPasswordAsync(user, "bogus")).ReturnsAsync(false).Verifiable();
var context = new Mock<HttpContext>();
var helper = SetupSignInManager(manager.Object, context.Object);
// Act
var result = await helper.CheckPasswordSignInAsync(user, "bogus", true);
// Assert
Assert.False(result.Succeeded);
Assert.True(result.IsLockedOut);
manager.Verify();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CanRequireConfirmedEmailForPasswordSignIn(bool confirmed)
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.IsEmailConfirmedAsync(user)).ReturnsAsync(confirmed).Verifiable();
if (confirmed)
{
manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
}
var context = new DefaultHttpContext();
var auth = MockAuth(context);
if (confirmed)
{
manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
SetupSignIn(context, auth);
}
var identityOptions = new IdentityOptions();
identityOptions.SignIn.RequireConfirmedEmail = true;
var logStore = new StringBuilder();
var helper = SetupSignInManager(manager.Object, context, logStore, identityOptions);
// Act
var result = await helper.PasswordSignInAsync(user, "password", false, false);
// Assert
Assert.Equal(confirmed, result.Succeeded);
Assert.NotEqual(confirmed, result.IsNotAllowed);
Assert.Equal(confirmed, !logStore.ToString().Contains($"User {user.Id} cannot sign in without a confirmed email."));
manager.Verify();
auth.Verify();
}
private static void SetupSignIn(HttpContext context, Mock<IAuthenticationService> auth, string userId = null, bool? isPersistent = null, string loginProvider = null)
{
auth.Setup(a => a.SignInAsync(context,
IdentityConstants.ApplicationScheme,
It.Is<ClaimsPrincipal>(id =>
(userId == null || id.FindFirstValue(ClaimTypes.NameIdentifier) == userId) &&
(loginProvider == null || id.FindFirstValue(ClaimTypes.AuthenticationMethod) == loginProvider)),
It.Is<AuthenticationProperties>(v => isPersistent == null || v.IsPersistent == isPersistent))).Returns(Task.FromResult(0)).Verifiable();
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task CanRequireConfirmedPhoneNumberForPasswordSignIn(bool confirmed)
{
// Setup
var user = new TestUser { UserName = "Foo" };
var manager = SetupUserManager(user);
manager.Setup(m => m.IsPhoneNumberConfirmedAsync(user)).ReturnsAsync(confirmed).Verifiable();
var context = new DefaultHttpContext();
var auth = MockAuth(context);
if (confirmed)
{
manager.Setup(m => m.CheckPasswordAsync(user, "password")).ReturnsAsync(true).Verifiable();
SetupSignIn(context, auth);
}
var identityOptions = new IdentityOptions();
identityOptions.SignIn.RequireConfirmedPhoneNumber = true;
var logStore = new StringBuilder();
var helper = SetupSignInManager(manager.Object, context, logStore, identityOptions);
// Act
var result = await helper.PasswordSignInAsync(user, "password", false, false);
// Assert
Assert.Equal(confirmed, result.Succeeded);
Assert.NotEqual(confirmed, result.IsNotAllowed);
Assert.Equal(confirmed, !logStore.ToString().Contains($"User {user.Id} cannot sign in without a confirmed phone number."));
manager.Verify();
auth.Verify();
}
}
}
@@ -0,0 +1,110 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class UserClaimsPrincipalFactoryTest
{
[Fact]
public async Task CreateIdentityNullChecks()
{
var userManager = MockHelpers.MockUserManager<TestUser>().Object;
var roleManager = MockHelpers.MockRoleManager<TestRole>().Object;
var options = new Mock<IOptions<IdentityOptions>>();
Assert.Throws<ArgumentNullException>("optionsAccessor",
() => new UserClaimsPrincipalFactory<TestUser, TestRole>(userManager, roleManager, options.Object));
var identityOptions = new IdentityOptions();
options.Setup(a => a.Value).Returns(identityOptions);
var factory = new UserClaimsPrincipalFactory<TestUser, TestRole>(userManager, roleManager, options.Object);
await Assert.ThrowsAsync<ArgumentNullException>("user",
async () => await factory.CreateAsync(null));
}
[Theory]
[InlineData(false, false, false)]
[InlineData(false, true, false)]
[InlineData(true, false, false)]
[InlineData(true, true, false)]
[InlineData(true, false, true)]
[InlineData(true, true, true)]
public async Task EnsureClaimsIdentityHasExpectedClaims(bool supportRoles, bool supportClaims, bool supportRoleClaims)
{
// Setup
var userManager = MockHelpers.MockUserManager<TestUser>();
var roleManager = MockHelpers.MockRoleManager<TestRole>();
var user = new TestUser { UserName = "Foo" };
userManager.Setup(m => m.SupportsUserClaim).Returns(supportClaims);
userManager.Setup(m => m.SupportsUserRole).Returns(supportRoles);
userManager.Setup(m => m.GetUserIdAsync(user)).ReturnsAsync(user.Id);
userManager.Setup(m => m.GetUserNameAsync(user)).ReturnsAsync(user.UserName);
var roleClaims = new[] { "Admin", "Local" };
if (supportRoles)
{
userManager.Setup(m => m.GetRolesAsync(user)).ReturnsAsync(roleClaims);
roleManager.Setup(m => m.SupportsRoleClaims).Returns(supportRoleClaims);
}
var userClaims = new[] { new Claim("Whatever", "Value"), new Claim("Whatever2", "Value2") };
if (supportClaims)
{
userManager.Setup(m => m.GetClaimsAsync(user)).ReturnsAsync(userClaims);
}
userManager.Object.Options = new IdentityOptions();
var admin = new TestRole() { Name = "Admin" };
var local = new TestRole() { Name = "Local" };
var adminClaims = new[] { new Claim("AdminClaim1", "Value1"), new Claim("AdminClaim2", "Value2") };
var localClaims = new[] { new Claim("LocalClaim1", "Value1"), new Claim("LocalClaim2", "Value2") };
if (supportRoleClaims)
{
roleManager.Setup(m => m.FindByNameAsync("Admin")).ReturnsAsync(admin);
roleManager.Setup(m => m.FindByNameAsync("Local")).ReturnsAsync(local);
roleManager.Setup(m => m.GetClaimsAsync(admin)).ReturnsAsync(adminClaims);
roleManager.Setup(m => m.GetClaimsAsync(local)).ReturnsAsync(localClaims);
}
var options = new Mock<IOptions<IdentityOptions>>();
var identityOptions = new IdentityOptions();
options.Setup(a => a.Value).Returns(identityOptions);
var factory = new UserClaimsPrincipalFactory<TestUser, TestRole>(userManager.Object, roleManager.Object, options.Object);
// Act
var principal = await factory.CreateAsync(user);
var identity = principal.Identities.First();
// Assert
var manager = userManager.Object;
Assert.NotNull(identity);
Assert.Single(principal.Identities);
Assert.Equal(IdentityConstants.ApplicationScheme, identity.AuthenticationType);
var claims = identity.Claims.ToList();
Assert.NotNull(claims);
Assert.Contains(claims, c => c.Type == manager.Options.ClaimsIdentity.UserNameClaimType && c.Value == user.UserName);
Assert.Contains(claims, c => c.Type == manager.Options.ClaimsIdentity.UserIdClaimType && c.Value == user.Id);
Assert.Equal(supportRoles, claims.Any(c => c.Type == manager.Options.ClaimsIdentity.RoleClaimType && c.Value == "Admin"));
Assert.Equal(supportRoles, claims.Any(c => c.Type == manager.Options.ClaimsIdentity.RoleClaimType && c.Value == "Local"));
foreach (var cl in userClaims)
{
Assert.Equal(supportClaims, claims.Any(c => c.Type == cl.Type && c.Value == cl.Value));
}
foreach (var cl in adminClaims)
{
Assert.Equal(supportRoleClaims, claims.Any(c => c.Type == cl.Type && c.Value == cl.Value));
}
foreach (var cl in localClaims)
{
Assert.Equal(supportRoleClaims, claims.Any(c => c.Type == cl.Type && c.Value == cl.Value));
}
userManager.VerifyAll();
roleManager.VerifyAll();
}
}
}
@@ -0,0 +1,99 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public class UserValidatorTest
{
[Fact]
public async Task ValidateThrowsWithNull()
{
// Setup
var manager = MockHelpers.TestUserManager(new NoopUserStore());
var validator = new UserValidator<TestUser>();
// Act
// Assert
await Assert.ThrowsAsync<ArgumentNullException>("manager", () => validator.ValidateAsync(null, null));
await Assert.ThrowsAsync<ArgumentNullException>("user", () => validator.ValidateAsync(manager, null));
}
[Theory]
[InlineData(null)]
[InlineData("")]
public async Task ValidateFailsWithTooShortUserNames(string input)
{
// Setup
var manager = MockHelpers.TestUserManager(new NoopUserStore());
var validator = new UserValidator<TestUser>();
var user = new TestUser {UserName = input};
// Act
var result = await validator.ValidateAsync(manager, user);
// Assert
IdentityResultAssert.IsFailure(result, new IdentityErrorDescriber().InvalidUserName(input));
}
[Theory]
[InlineData("test_email@foo.com", true)]
[InlineData("hao", true)]
[InlineData("test123", true)]
[InlineData("hyphen-yes@foo-bar.com", true)]
[InlineData("+plus+yes+@foo-bar.com", true)]
[InlineData("!noway", false)]
[InlineData("foo@boz#.com", false)]
public async Task DefaultAlphaNumericOnlyUserNameValidation(string userName, bool expectSuccess)
{
// Setup
var manager = MockHelpers.TestUserManager(new NoopUserStore());
var validator = new UserValidator<TestUser>();
var user = new TestUser {UserName = userName};
// Act
var result = await validator.ValidateAsync(manager, user);
// Assert
if (expectSuccess)
{
IdentityResultAssert.IsSuccess(result);
}
else
{
IdentityResultAssert.IsFailure(result);
}
}
[Theory]
[InlineData("test_email@foo.com", true)]
[InlineData("hao", true)]
[InlineData("test123", true)]
[InlineData("!noway", true)]
[InlineData("foo@boz#.com", true)]
public async Task CanAllowNonAlphaNumericUserName(string userName, bool expectSuccess)
{
// Setup
var manager = MockHelpers.TestUserManager(new NoopUserStore());
manager.Options.User.AllowedUserNameCharacters = null;
var validator = new UserValidator<TestUser>();
var user = new TestUser {UserName = userName};
// Act
var result = await validator.ValidateAsync(manager, user);
// Assert
if (expectSuccess)
{
IdentityResultAssert.IsSuccess(result);
}
else
{
IdentityResultAssert.IsFailure(result);
}
}
}
}
@@ -0,0 +1,68 @@
using AspNetCore.Identity.MongoDbCore.Extensions;
using AspNetCore.Identity.MongoDbCore.Infrastructure;
using System;
namespace AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure
{
public static class Locks
{
public static object MongoInitLock = new object();
public static object RolesLock = new object();
}
public static class Container
{
public static MongoDbIdentityConfiguration MongoDbIdentityConfiguration = new MongoDbIdentityConfiguration
{
MongoDbSettings = new MongoDbSettings
{
ConnectionString = "mongodb://localhost:27017",
DatabaseName = "MongoDbTests"
},
IdentityOptionsAction = options =>
{
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.User.AllowedUserNameCharacters = null;
}
};
public static IServiceProvider Instance { get; set; }
const string connectionString = "mongodb://localhost:27017";
private static readonly IMongoRepository _mongoDbRepository;
private static readonly IMongoRepository _mongoDbRepository2;
static Container()
{
lock (Locks.MongoInitLock)
{
_mongoDbRepository = new MongoRepository(
MongoDbIdentityConfiguration.MongoDbSettings.ConnectionString,
MongoDbIdentityConfiguration.MongoDbSettings.DatabaseName);
_mongoDbRepository2 = new MongoRepository(
MongoDbIdentityConfiguration.MongoDbSettings.ConnectionString,
MongoDbIdentityConfiguration.MongoDbSettings.DatabaseName);
}
}
public static IMongoRepository MongoRepository
{
get
{
return _mongoDbRepository;
}
}
public static IMongoRepository MongoRepositoryConcurrent
{
get
{
return _mongoDbRepository2;
}
}
}
}
@@ -0,0 +1,18 @@
using AspNetCore.Identity.MongoDbCore.Models;
using System;
namespace AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure
{
public class TestMongoIdentityRole : MongoIdentityRole<Guid>
{
public TestMongoIdentityRole() : base ()
{
Id = Guid.NewGuid();
}
public TestMongoIdentityRole(string roleName) : base(roleName)
{
Id = Guid.NewGuid();
}
}
}
@@ -0,0 +1,25 @@
using AspNetCore.Identity.MongoDbCore.Models;
using System;
namespace AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure
{
public class TestMongoIdentityUser : MongoIdentityUser<Guid>
{
public TestMongoIdentityUser() : base()
{
Id = Guid.NewGuid();
}
public TestMongoIdentityUser(string userName) : base(userName)
{
Id = Guid.NewGuid();
}
public TestMongoIdentityUser(string userName, string email) : base(userName, email)
{
Id = Guid.NewGuid();
}
public string CustomContent { get; set; }
}
}
@@ -0,0 +1,97 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
public abstract class ApiConsistencyTestBase
{
[Fact]
public void Public_inheritable_apis_should_be_virtual()
{
var nonVirtualMethods
= (from type in GetAllTypes(TargetAssembly.DefinedTypes)
where type.IsVisible
&& !type.IsSealed
&& type.DeclaredConstructors.Any(c => c.IsPublic || c.IsFamily || c.IsFamilyOrAssembly)
&& type.Namespace != null
&& !type.Namespace.EndsWith(".Compiled")
from method in type.DeclaredMethods.Where(m => m.IsPublic && !m.IsStatic)
where GetBasestTypeInAssembly(method.DeclaringType) == type
&& !(method.IsVirtual && !method.IsFinal)
&& !method.Name.StartsWith("get_")
&& !method.Name.StartsWith("set_")
&& !method.Name.Equals("Dispose")
select type.Name + "." + method.Name)
.ToList();
Assert.False(
nonVirtualMethods.Any(),
"\r\n-- Missing virtual APIs --\r\n" + string.Join("\r\n", nonVirtualMethods));
}
[Fact]
public void Async_methods_should_end_with_async_suffix()
{
var asyncMethods
= (from type in GetAllTypes(TargetAssembly.DefinedTypes)
where type.IsVisible
from method in type.DeclaredMethods.Where(m => m.IsPublic)
where GetBasestTypeInAssembly(method.DeclaringType) == type
where typeof(Task).IsAssignableFrom(method.ReturnType)
select method).ToList();
var missingSuffixMethods
= asyncMethods
.Where(method => !method.Name.EndsWith("Async"))
.Select(method => method.DeclaringType.Name + "." + method.Name)
.Except(GetAsyncSuffixExceptions())
.ToList();
Assert.False(
missingSuffixMethods.Any(),
"\r\n-- Missing async suffix --\r\n" + string.Join("\r\n", missingSuffixMethods));
}
protected virtual IEnumerable<string> GetCancellationTokenExceptions()
{
return Enumerable.Empty<string>();
}
protected virtual IEnumerable<string> GetAsyncSuffixExceptions()
{
return Enumerable.Empty<string>();
}
protected abstract Assembly TargetAssembly { get; }
protected virtual IEnumerable<TypeInfo> GetAllTypes(IEnumerable<TypeInfo> types)
{
foreach (var type in types)
{
yield return type;
foreach (var nestedType in GetAllTypes(type.DeclaredNestedTypes))
{
yield return nestedType;
}
}
}
protected TypeInfo GetBasestTypeInAssembly(Type type)
{
while (type.GetTypeInfo()?.BaseType?.GetTypeInfo()?.Assembly == type.GetTypeInfo().Assembly)
{
type = type.GetTypeInfo().BaseType;
}
return type.GetTypeInfo();
}
}
}
@@ -0,0 +1,100 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
namespace Microsoft.AspNetCore.Identity.Test
{
public static class MockHelpers
{
public static StringBuilder LogMessage = new StringBuilder();
public static Mock<UserManager<TUser>> MockUserManager<TUser>() where TUser : class
{
var store = new Mock<IUserStore<TUser>>();
var mgr = new Mock<UserManager<TUser>>(store.Object, null, null, null, null, null, null, null, null);
mgr.Object.UserValidators.Add(new UserValidator<TUser>());
mgr.Object.PasswordValidators.Add(new PasswordValidator<TUser>());
return mgr;
}
public static Mock<RoleManager<TRole>> MockRoleManager<TRole>(IRoleStore<TRole> store = null) where TRole : class
{
store = store ?? new Mock<IRoleStore<TRole>>().Object;
var roles = new List<IRoleValidator<TRole>>();
roles.Add(new RoleValidator<TRole>());
return new Mock<RoleManager<TRole>>(store, roles, new UpperInvariantLookupNormalizer(),
new IdentityErrorDescriber(), null);
}
public static Mock<ILogger<T>> MockILogger<T>(StringBuilder logStore = null) where T : class
{
logStore = logStore ?? LogMessage;
var logger = new Mock<ILogger<T>>();
logger.Setup(x => x.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(),
It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()))
.Callback((LogLevel logLevel, EventId eventId, object state, Exception exception, Func<object, Exception, string> formatter) =>
{
if (formatter == null)
{
logStore.Append(state.ToString());
}
else
{
logStore.Append(formatter(state, exception));
}
logStore.Append(" ");
});
logger.Setup(x => x.BeginScope(It.IsAny<object>())).Callback((object state) =>
{
logStore.Append(state.ToString());
logStore.Append(" ");
});
logger.Setup(x => x.IsEnabled(LogLevel.Debug)).Returns(true);
logger.Setup(x => x.IsEnabled(LogLevel.Warning)).Returns(true);
return logger;
}
public static UserManager<TUser> TestUserManager<TUser>(IUserStore<TUser> store = null) where TUser : class
{
store = store ?? new Mock<IUserStore<TUser>>().Object;
var options = new Mock<IOptions<IdentityOptions>>();
var idOptions = new IdentityOptions();
idOptions.Lockout.AllowedForNewUsers = false;
options.Setup(o => o.Value).Returns(idOptions);
var userValidators = new List<IUserValidator<TUser>>();
var validator = new Mock<IUserValidator<TUser>>();
userValidators.Add(validator.Object);
var pwdValidators = new List<PasswordValidator<TUser>>();
pwdValidators.Add(new PasswordValidator<TUser>());
var userManager = new UserManager<TUser>(store, options.Object, new PasswordHasher<TUser>(),
userValidators, pwdValidators, new UpperInvariantLookupNormalizer(),
new IdentityErrorDescriber(), null,
new Mock<ILogger<UserManager<TUser>>>().Object);
validator.Setup(v => v.ValidateAsync(userManager, It.IsAny<TUser>()))
.Returns(Task.FromResult(IdentityResult.Success)).Verifiable();
return userManager;
}
public static RoleManager<TRole> TestRoleManager<TRole>(IRoleStore<TRole> store = null) where TRole : class
{
store = store ?? new Mock<IRoleStore<TRole>>().Object;
var roles = new List<IRoleValidator<TRole>>();
roles.Add(new RoleValidator<TRole>());
return new AspNetRoleManager<TRole>(store, roles,
new UpperInvariantLookupNormalizer(),
new IdentityErrorDescriber(),
null,
null);
}
}
}
@@ -0,0 +1,78 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Identity.Test
{
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit.Abstractions;
using Xunit.Sdk;
/// <summary>
/// Test priority
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class TestPriorityAttribute : Attribute
{
/// <summary>
/// ctor
/// </summary>
/// <param name="priority"></param>
public TestPriorityAttribute(int priority)
{
Priority = priority;
}
/// <summary>
/// Priority
/// </summary>
public int Priority { get; private set; }
}
/// <summary>
/// Used to run tests in order.
/// </summary>
public class PriorityOrderer : ITestCaseOrderer
{
/// <summary>
/// Orders tests cases
/// </summary>
/// <typeparam name="XunitTestCase"></typeparam>
/// <param name="testCases"></param>
/// <returns></returns>
public IEnumerable<XunitTestCase> OrderTestCases<XunitTestCase>(IEnumerable<XunitTestCase> testCases) where XunitTestCase : ITestCase
{
var sortedMethods = new SortedDictionary<int, List<XunitTestCase>>();
foreach (XunitTestCase testCase in testCases)
{
int priority = 0;
foreach (IAttributeInfo attr in testCase.TestMethod.Method.GetCustomAttributes((typeof(TestPriorityAttribute)).AssemblyQualifiedName))
priority = attr.GetNamedArgument<int>("Priority");
GetOrCreate(sortedMethods, priority).Add(testCase);
}
foreach (var list in sortedMethods.Keys.Select(priority => sortedMethods[priority]))
{
list.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name));
foreach (XunitTestCase testCase in list)
yield return testCase;
}
}
static TValue GetOrCreate<TKey, TValue>(IDictionary<TKey, TValue> dictionary, TKey key) where TValue : new()
{
TValue result;
if (dictionary.TryGetValue(key, out result)) return result;
result = new TValue();
dictionary[key] = result;
return result;
}
}
}
@@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// Represents a Role entity
/// </summary>
public class TestRole : TestRole<string>
{
/// <summary>
/// Constructor
/// </summary>
public TestRole()
{
Id = Guid.NewGuid().ToString();
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="roleName"></param>
public TestRole(string roleName) : this()
{
Name = roleName;
}
}
/// <summary>
/// Represents a Role entity
/// </summary>
/// <typeparam name="TKey"></typeparam>
public class TestRole<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// Constructor
/// </summary>
public TestRole() { }
/// <summary>
/// Constructor
/// </summary>
/// <param name="roleName"></param>
public TestRole(string roleName) : this()
{
Name = roleName;
}
/// <summary>
/// Role id
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Navigation property for claims in the role
/// </summary>
public virtual ICollection<TestRoleClaim<TKey>> Claims { get; private set; } = new List<TestRoleClaim<TKey>>();
/// <summary>
/// Role name
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// Normalized name used for equality
/// </summary>
public virtual string NormalizedName { get; set; }
/// <summary>
/// A random value that should change whenever a role is persisted to the store
/// </summary>
public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
}
}
@@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// EntityType that represents one specific role claim
/// </summary>
public class TestRoleClaim : TestRoleClaim<string> { }
/// <summary>
/// EntityType that represents one specific role claim
/// </summary>
/// <typeparam name="TKey"></typeparam>
public class TestRoleClaim<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// Primary key
/// </summary>
public virtual int Id { get; set; }
/// <summary>
/// User Id for the role this claim belongs to
/// </summary>
public virtual TKey RoleId { get; set; }
/// <summary>
/// Claim type
/// </summary>
public virtual string ClaimType { get; set; }
/// <summary>
/// Claim value
/// </summary>
public virtual string ClaimValue { get; set; }
}
}
@@ -0,0 +1,144 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// Test user class
/// </summary>
public class TestUser : TestUser<string>
{
/// <summary>
/// Ctor
/// </summary>
public TestUser()
{
Id = Guid.NewGuid().ToString();
}
/// <summary>
/// Ctor
/// </summary>
/// <param name="userName"></param>
public TestUser(string userName) : this()
{
UserName = userName;
}
}
/// <summary>
/// Test user
/// </summary>
/// <typeparam name="TKey"></typeparam>
public class TestUser<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// ctor
/// </summary>
public TestUser() { }
/// <summary>
/// ctor
/// </summary>
/// <param name="userName"></param>
public TestUser(string userName) : this()
{
UserName = userName;
}
/// <summary>
/// Id
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Name
/// </summary>
public virtual string UserName { get; set; }
/// <summary>
/// normalized user name
/// </summary>
public virtual string NormalizedUserName { get; set; }
/// <summary>
/// Email
/// </summary>
public virtual string Email { get; set; }
/// <summary>
/// normalized email
/// </summary>
public virtual string NormalizedEmail { get; set; }
/// <summary>
/// True if the email is confirmed, default is false
/// </summary>
public virtual bool EmailConfirmed { get; set; }
/// <summary>
/// The salted/hashed form of the user password
/// </summary>
public virtual string PasswordHash { get; set; }
/// <summary>
/// A random value that should change whenever a users credentials change (password changed, login removed)
/// </summary>
public virtual string SecurityStamp { get; set; }
/// <summary>
/// A random value that should change whenever a user is persisted to the store
/// </summary>
public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// PhoneNumber for the user
/// </summary>
public virtual string PhoneNumber { get; set; }
/// <summary>
/// True if the phone number is confirmed, default is false
/// </summary>
public virtual bool PhoneNumberConfirmed { get; set; }
/// <summary>
/// Is two factor enabled for the user
/// </summary>
public virtual bool TwoFactorEnabled { get; set; }
/// <summary>
/// DateTime in UTC when lockout ends, any time in the past is considered not locked out.
/// </summary>
public virtual DateTimeOffset? LockoutEnd { get; set; }
/// <summary>
/// Is lockout enabled for this user
/// </summary>
public virtual bool LockoutEnabled { get; set; }
/// <summary>
/// Used to record failures for the purposes of lockout
/// </summary>
public virtual int AccessFailedCount { get; set; }
/// <summary>
/// Navigation property
/// </summary>
public virtual ICollection<TestUserRole<TKey>> Roles { get; private set; } = new List<TestUserRole<TKey>>();
/// <summary>
/// Navigation property
/// </summary>
public virtual ICollection<TestUserClaim<TKey>> Claims { get; private set; } = new List<TestUserClaim<TKey>>();
/// <summary>
/// Navigation property
/// </summary>
public virtual ICollection<TestUserLogin<TKey>> Logins { get; private set; } = new List<TestUserLogin<TKey>>();
/// <summary>
/// Navigation property
/// </summary>
public virtual ICollection<TestUserToken<TKey>> Tokens { get; private set; } = new List<TestUserToken<TKey>>();
}
}
@@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// EntityType that represents one specific user claim
/// </summary>
public class TestUserClaim : TestUserClaim<string> { }
/// <summary>
/// EntityType that represents one specific user claim
/// </summary>
/// <typeparam name="TKey"></typeparam>
public class TestUserClaim<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// Primary key
/// </summary>
public virtual int Id { get; set; }
/// <summary>
/// User Id for the user who owns this claim
/// </summary>
public virtual TKey UserId { get; set; }
/// <summary>
/// Claim type
/// </summary>
public virtual string ClaimType { get; set; }
/// <summary>
/// Claim value
/// </summary>
public virtual string ClaimValue { get; set; }
}
}
@@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// Entity type for a user's login (i.e. facebook, google)
/// </summary>
public class TestUserLogin : TestUserLogin<string> { }
/// <summary>
/// Entity type for a user's login (i.e. facebook, google)
/// </summary>
/// <typeparam name="TKey"></typeparam>
public class TestUserLogin<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// The login provider for the login (i.e. facebook, google)
/// </summary>
public virtual string LoginProvider { get; set; }
/// <summary>
/// Key representing the login for the provider
/// </summary>
public virtual string ProviderKey { get; set; }
/// <summary>
/// Display name for the login
/// </summary>
public virtual string ProviderDisplayName { get; set; }
/// <summary>
/// User Id for the user who owns this login
/// </summary>
public virtual TKey UserId { get; set; }
}
}
@@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// EntityType that represents a user belonging to a role
/// </summary>
public class TestUserRole : TestUserRole<string> { }
/// <summary>
/// EntityType that represents a user belonging to a role
/// </summary>
/// <typeparam name="TKey"></typeparam>
public class TestUserRole<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// UserId for the user that is in the role
/// </summary>
public virtual TKey UserId { get; set; }
/// <summary>
/// RoleId for the role
/// </summary>
public virtual TKey RoleId { get; set; }
}
}
@@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// Entity type for a user's token
/// </summary>
public class TestUserToken : TestUserToken<string> { }
/// <summary>
/// Entity type for a user's token
/// </summary>
/// <typeparam name="TKey"></typeparam>
public class TestUserToken<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// The login provider for the login (i.e. facebook, google)
/// </summary>
public virtual string LoginProvider { get; set; }
/// <summary>
/// Key representing the login for the provider
/// </summary>
public virtual string TokenName { get; set; }
/// <summary>
/// Display name for the login
/// </summary>
public virtual string TokenValue { get; set; }
/// <summary>
/// User Id for the user who owns this login
/// </summary>
public virtual TKey UserId { get; set; }
}
}
@@ -0,0 +1,73 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.Extensions.Logging;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// Helper for tests to validate identity results.
/// </summary>
public static class IdentityResultAssert
{
/// <summary>
/// Asserts that the result has Succeeded.
/// </summary>
/// <param name="result"></param>
public static void IsSuccess(IdentityResult result)
{
Assert.NotNull(result);
Assert.True(result.Succeeded);
}
/// <summary>
/// Asserts that the result has not Succeeded.
/// </summary>
public static void IsFailure(IdentityResult result)
{
Assert.NotNull(result);
Assert.False(result.Succeeded);
}
/// <summary>
/// Asserts that the result has not Succeeded and that error is the first Error's Description.
/// </summary>
public static void IsFailure(IdentityResult result, string error)
{
Assert.NotNull(result);
Assert.False(result.Succeeded);
Assert.Equal(error, result.Errors.First().Description);
}
/// <summary>
/// Asserts that the result has not Succeeded and that first error matches error's code and Description.
/// </summary>
public static void IsFailure(IdentityResult result, IdentityError error)
{
Assert.NotNull(result);
Assert.False(result.Succeeded);
Assert.Equal(error.Description, result.Errors.First().Description);
Assert.Equal(error.Code, result.Errors.First().Code);
}
/// <summary>
/// Asserts that the logger contains the expectedLog.
/// </summary>
/// <param name="logger">The logger to inspect.</param>
/// <param name="expectedLog">The expected log message.</param>
public static void VerifyLogMessage(ILogger logger, string expectedLog)
{
var testlogger = logger as ITestLogger;
if (testlogger != null)
{
Assert.Contains(expectedLog, testlogger.LogMessages);
}
else
{
Assert.False(true, "No logger registered");
}
}
}
}
@@ -0,0 +1,717 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xunit;
using AspNetCore.Identity.MongoDbCore.Models;
using AspNetCore.Identity.MongoDbCore.Extensions;
using AspNetCore.Identity.MongoDbCore.Infrastructure;
using AspNetCore.Identity.MongoDbCore.IntegrationTests.Infrastructure;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// Common functionality tests that all verifies user manager functionality regardless of store implementation
/// </summary>
/// <typeparam name="TUser">The type of the user.</typeparam>
/// <typeparam name="TRole">The type of the role.</typeparam>
public abstract class IdentitySpecificationTestBase<TUser, TRole> : IdentitySpecificationTestBase<TUser, TRole, string>
where TUser : MongoDbIdentityUser, new()
where TRole : MongoDbIdentityRole, new()
{ }
/// <summary>
/// Base class for tests that exercise basic identity functionality that all stores should support.
/// </summary>
/// <typeparam name="TUser">The type of the user.</typeparam>
/// <typeparam name="TRole">The type of the role.</typeparam>
/// <typeparam name="TKey">The primary key type.</typeparam>
public abstract class IdentitySpecificationTestBase<TUser, TRole, TKey> : UserManagerSpecificationTestBase<TUser, TKey>
where TUser : MongoIdentityUser<TKey>, new()
where TRole : MongoIdentityRole<TKey>, new()
where TKey : IEquatable<TKey>
{
/// <summary>
/// Configure the service collection used for tests.
/// </summary>
/// <param name="services"></param>
/// <param name="mongoRepository"></param>
protected override void SetupIdentityServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.ConfigureMongoDbIdentity<TUser, TRole, TKey>(Container.MongoDbIdentityConfiguration, Container.MongoRepository.Context);
services.AddLogging();
services.AddSingleton<ILogger<UserManager<TUser>>>(new TestLogger<UserManager<TUser>>());
services.AddSingleton<ILogger<RoleManager<TRole>>>(new TestLogger<RoleManager<TRole>>());
}
/// <summary>
/// Configure the service collection used for tests.
/// </summary>
/// <param name="services"></param>
/// <param name="mongoRepository"></param>
protected void SetupIdentityServices(IServiceCollection services, bool concurrentSetup = false)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
if (concurrentSetup)
{
services.ConfigureMongoDbIdentity<TUser, TRole, TKey>(Container.MongoDbIdentityConfiguration, Container.MongoRepositoryConcurrent.Context);
}
else
{
services.ConfigureMongoDbIdentity<TUser, TRole, TKey>(Container.MongoDbIdentityConfiguration, Container.MongoRepository.Context);
}
services.AddLogging();
services.AddSingleton<ILogger<UserManager<TUser>>>(new TestLogger<UserManager<TUser>>());
services.AddSingleton<ILogger<RoleManager<TRole>>>(new TestLogger<RoleManager<TRole>>());
}
/// <summary>
/// Setup the IdentityBuilder
/// </summary>
/// <param name="services"></param>
/// <param name="context"></param>
/// <returns></returns>
protected override IServiceCollection SetupBuilder(IServiceCollection services)
{
var builder = base.SetupBuilder(services);
//builder.AddRoles<TRole>();
//AddRoleStore(services, context);
services.AddSingleton<ILogger<RoleManager<TRole>>>(new TestLogger<RoleManager<TRole>>());
return builder;
}
/// <summary>
/// Creates the role manager for tests.
/// </summary>
/// <param name="context">The context that will be passed into the store, typically a db context.</param>
/// <param name="services">The service collection to use, optional.</param>
/// <returns></returns>
protected virtual RoleManager<TRole> CreateRoleManager(object context = null, IServiceCollection services = null)
{
if (services == null)
{
services = new ServiceCollection();
}
if(context == null)
{
SetupIdentityServices(services);
}
else
{
SetupIdentityServices(services, true);
}
return services.BuildServiceProvider().GetService<RoleManager<TRole>>();
}
/// <summary>
/// Adds an IRoleStore to services for the test.
/// </summary>
/// <param name="services">The service collection to add to.</param>
/// <param name="context">The context for the store to use, optional.</param>
protected abstract void AddRoleStore(IServiceCollection services, object context = null);
/// <summary>
/// Creates a new test role instance.
/// </summary>
/// <param name="roleNamePrefix">Optional name prefix, name will be randomized.</param>
/// <param name="useRoleNamePrefixAsRoleName">If true, the prefix should be used as the rolename without a random pad.</param>
/// <returns></returns>
protected abstract TRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false);
/// <summary>
/// Query used to do name equality checks.
/// </summary>
/// <param name="roleName">The role name to match.</param>
/// <returns>The query to use.</returns>
protected abstract Expression<Func<TRole, bool>> RoleNameEqualsPredicate(string roleName);
/// <summary>
/// Query used to do user name prefix matching.
/// </summary>
/// <param name="roleName">The role name to match.</param>
/// <returns>The query to use.</returns>
protected abstract Expression<Func<TRole, bool>> RoleNameStartsWithPredicate(string roleName);
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanCreateRoleTest()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
var roleName = "create" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
Assert.False(await manager.RoleExistsAsync(roleName));
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
Assert.True(await manager.RoleExistsAsync(roleName));
}
private class AlwaysBadValidator : IUserValidator<TUser>, IRoleValidator<TRole>,
IPasswordValidator<TUser>
{
public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad.", Code = "BadValidator" };
public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user, string password)
{
return Task.FromResult(IdentityResult.Failed(ErrorMessage));
}
public Task<IdentityResult> ValidateAsync(RoleManager<TRole> manager, TRole role)
{
return Task.FromResult(IdentityResult.Failed(ErrorMessage));
}
public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user)
{
return Task.FromResult(IdentityResult.Failed(ErrorMessage));
}
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task BadValidatorBlocksCreateRole()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
manager.RoleValidators.Clear();
manager.RoleValidators.Add(new AlwaysBadValidator());
var role = CreateTestRole("blocked");
IdentityResultAssert.IsFailure(await manager.CreateAsync(role),
AlwaysBadValidator.ErrorMessage);
IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Role {await manager.GetRoleIdAsync(role) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code}.");
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanChainRoleValidators()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
manager.RoleValidators.Clear();
manager.RoleValidators.Add(new AlwaysBadValidator());
manager.RoleValidators.Add(new AlwaysBadValidator());
var role = CreateTestRole("blocked");
var result = await manager.CreateAsync(role);
IdentityResultAssert.IsFailure(result, AlwaysBadValidator.ErrorMessage);
IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Role {await manager.GetRoleIdAsync(role) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code};{AlwaysBadValidator.ErrorMessage.Code}.");
Assert.Equal(2, result.Errors.Count());
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task BadValidatorBlocksRoleUpdate()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
var role = CreateTestRole("poorguy");
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
var error = AlwaysBadValidator.ErrorMessage;
manager.RoleValidators.Clear();
manager.RoleValidators.Add(new AlwaysBadValidator());
IdentityResultAssert.IsFailure(await manager.UpdateAsync(role), error);
IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Role {await manager.GetRoleIdAsync(role) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code}.");
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanDeleteRole()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
var roleName = "delete" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
Assert.False(await manager.RoleExistsAsync(roleName));
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
Assert.True(await manager.RoleExistsAsync(roleName));
IdentityResultAssert.IsSuccess(await manager.DeleteAsync(role));
Assert.False(await manager.RoleExistsAsync(roleName));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanAddRemoveRoleClaim()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
var role = CreateTestRole("ClaimsAddRemove");
var roleSafe = CreateTestRole("ClaimsAdd");
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
IdentityResultAssert.IsSuccess(await manager.CreateAsync(roleSafe));
Claim[] claims = { new Claim("c", "v"), new Claim("c2", "v2"), new Claim("c2", "v3") };
foreach (Claim c in claims)
{
IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(role, c));
IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(roleSafe, c));
}
var roleClaims = await manager.GetClaimsAsync(role);
var safeRoleClaims = await manager.GetClaimsAsync(roleSafe);
Assert.Equal(3, roleClaims.Count);
Assert.Equal(3, safeRoleClaims.Count);
IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(role, claims[0]));
roleClaims = await manager.GetClaimsAsync(role);
safeRoleClaims = await manager.GetClaimsAsync(roleSafe);
Assert.Equal(2, roleClaims.Count);
Assert.Equal(3, safeRoleClaims.Count);
IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(role, claims[1]));
roleClaims = await manager.GetClaimsAsync(role);
safeRoleClaims = await manager.GetClaimsAsync(roleSafe);
Assert.Equal(1, roleClaims.Count);
Assert.Equal(3, safeRoleClaims.Count);
IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(role, claims[2]));
roleClaims = await manager.GetClaimsAsync(role);
safeRoleClaims = await manager.GetClaimsAsync(roleSafe);
Assert.Equal(0, roleClaims.Count);
Assert.Equal(3, safeRoleClaims.Count);
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanRoleFindById()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
var role = CreateTestRole("FindByIdAsync");
Assert.Null(await manager.FindByIdAsync(await manager.GetRoleIdAsync(role)));
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
Assert.Equal(role.Id, (await manager.FindByIdAsync(await manager.GetRoleIdAsync(role))).Id);
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanRoleFindByName()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
var roleName = "FindByNameAsync" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
Assert.Null(await manager.FindByNameAsync(roleName));
Assert.False(await manager.RoleExistsAsync(roleName));
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
Assert.Equal(role.Id, (await manager.FindByNameAsync(roleName)).Id);
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanUpdateRoleName()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
var roleName = "update" + Guid.NewGuid().ToString();
var changedRoleName = "Changed" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
Assert.False(await manager.RoleExistsAsync(roleName));
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
Assert.True(await manager.RoleExistsAsync(roleName));
IdentityResultAssert.IsSuccess(await manager.SetRoleNameAsync(role, changedRoleName));
IdentityResultAssert.IsSuccess(await manager.UpdateAsync(role));
Assert.False(await manager.RoleExistsAsync(roleName));
Assert.Equal(role.Id, (await manager.FindByNameAsync(changedRoleName)).Id);
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanQueryableRoles()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
if (manager.SupportsQueryableRoles)
{
var guidName = "CanQuerableRolesTest" + Guid.NewGuid().ToString("n");
var roles = GenerateRoles(guidName, 4);
foreach (var r in roles)
{
IdentityResultAssert.IsSuccess(await manager.CreateAsync(r));
}
Expression<Func<TRole, bool>> func = RoleNameStartsWithPredicate(guidName);
Assert.Equal(roles.Count, manager.Roles.Count(func));
func = RoleNameEqualsPredicate("bogus");
Assert.Null(manager.Roles.FirstOrDefault(func));
}
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CreateRoleFailsIfExists()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateRoleManager();
var roleName = "dupeRole" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
Assert.False(await manager.RoleExistsAsync(roleName));
IdentityResultAssert.IsSuccess(await manager.CreateAsync(role));
Assert.True(await manager.RoleExistsAsync(roleName));
var role2 = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
IdentityResultAssert.IsFailure(await manager.CreateAsync(role2));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanAddUsersToRole()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var roleManager = CreateRoleManager();
var roleName = "AddUserTest" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(role));
TUser[] users =
{
CreateTestUser("1"),CreateTestUser("2"),CreateTestUser("3"),CreateTestUser("4"),
};
foreach (var u in users)
{
IdentityResultAssert.IsSuccess(await manager.CreateAsync(u));
IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(u, roleName));
Assert.True(await manager.IsInRoleAsync(u, roleName));
}
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanGetRolesForUser()
{
if (ShouldSkipDbTests())
{
return;
}
var userManager = CreateManager();
var roleManager = CreateRoleManager();
var guid = Guid.NewGuid().ToString("n");
var users = GenerateUsers(guid + "CanGetRolesForUser", 4);
var roles = GenerateRoles(guid + "CanGetRolesForUserRole", 4);
foreach (var u in users)
{
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(u));
}
foreach (var r in roles)
{
IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r));
foreach (var u in users)
{
IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(u, await roleManager.GetRoleNameAsync(r)));
Assert.True(await userManager.IsInRoleAsync(u, await roleManager.GetRoleNameAsync(r)));
}
}
foreach (var u in users)
{
var rs = await userManager.GetRolesAsync(u);
Assert.Equal(roles.Count, rs.Count);
foreach (var r in roles)
{
var expectedRoleName = await roleManager.GetRoleNameAsync(r);
Assert.True(rs.Any(role => role == expectedRoleName));
}
}
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task RemoveUserFromRoleWithMultipleRoles()
{
if (ShouldSkipDbTests())
{
return;
}
var userManager = CreateManager();
var roleManager = CreateRoleManager();
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(user));
var guidName = "RemoveUserFromRoleWithMultipleRoles" + Guid.NewGuid().ToString("n");
var roles = GenerateRoles(guidName, 4);
foreach (var r in roles)
{
IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r));
IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(user, await roleManager.GetRoleNameAsync(r)));
Assert.True(await userManager.IsInRoleAsync(user, await roleManager.GetRoleNameAsync(r)));
}
IdentityResultAssert.IsSuccess(await userManager.RemoveFromRoleAsync(user, await roleManager.GetRoleNameAsync(roles[2])));
Assert.False(await userManager.IsInRoleAsync(user, await roleManager.GetRoleNameAsync(roles[2])));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanRemoveUsersFromRole()
{
if (ShouldSkipDbTests())
{
return;
}
var userManager = CreateManager();
var roleManager = CreateRoleManager();
var guid = Guid.NewGuid().ToString("n");
var users = GenerateUsers(guid + "CanRemoveUsersFromRole", 4);
foreach (var u in users)
{
IdentityResultAssert.IsSuccess(await userManager.CreateAsync(u));
}
var r = CreateTestRole(guid + "r1");
var roleName = await roleManager.GetRoleNameAsync(r);
IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(r));
foreach (var u in users)
{
IdentityResultAssert.IsSuccess(await userManager.AddToRoleAsync(u, roleName));
Assert.True(await userManager.IsInRoleAsync(u, roleName));
}
foreach (var u in users)
{
IdentityResultAssert.IsSuccess(await userManager.RemoveFromRoleAsync(u, roleName));
Assert.False(await userManager.IsInRoleAsync(u, roleName));
}
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task RemoveUserNotInRoleFails()
{
if (ShouldSkipDbTests())
{
return;
}
var userMgr = CreateManager();
var roleMgr = CreateRoleManager();
var roleName = "addUserDupeTest" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user));
IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role));
var result = await userMgr.RemoveFromRoleAsync(user, roleName);
IdentityResultAssert.IsFailure(result, _errorDescriber.UserNotInRole(roleName));
IdentityResultAssert.VerifyLogMessage(userMgr.Logger, $"User {await userMgr.GetUserIdAsync(user)} is not in role {roleName}.");
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task AddUserToRoleFailsIfAlreadyInRole()
{
if (ShouldSkipDbTests())
{
return;
}
var userMgr = CreateManager();
var roleMgr = CreateRoleManager();
var roleName = "addUserDupeTest" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user));
IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role));
IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, roleName));
Assert.True(await userMgr.IsInRoleAsync(user, roleName));
IdentityResultAssert.IsFailure(await userMgr.AddToRoleAsync(user, roleName), _errorDescriber.UserAlreadyInRole(roleName));
IdentityResultAssert.VerifyLogMessage(userMgr.Logger, $"User {await userMgr.GetUserIdAsync(user)} is already in role {roleName}.");
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task AddUserToRolesIgnoresDuplicates()
{
if (ShouldSkipDbTests())
{
return;
}
var userMgr = CreateManager();
var roleMgr = CreateRoleManager();
var roleName = "addUserDupeTest" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user));
IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role));
Assert.False(await userMgr.IsInRoleAsync(user, roleName));
IdentityResultAssert.IsSuccess(await userMgr.AddToRolesAsync(user, new[] { roleName, roleName }));
Assert.True(await userMgr.IsInRoleAsync(user, roleName));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanFindRoleByNameWithManager()
{
if (ShouldSkipDbTests())
{
return;
}
var roleMgr = CreateRoleManager();
var roleName = "findRoleByNameTest" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role));
Assert.NotNull(await roleMgr.FindByNameAsync(roleName));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanFindRoleWithManager()
{
if (ShouldSkipDbTests())
{
return;
}
var roleMgr = CreateRoleManager();
var roleName = "findRoleTest" + Guid.NewGuid().ToString();
var role = CreateTestRole(roleName, useRoleNamePrefixAsRoleName: true);
IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role));
Assert.Equal(roleName, await roleMgr.GetRoleNameAsync(await roleMgr.FindByNameAsync(roleName)));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanGetUsersInRole()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var roleManager = CreateRoleManager();
var guidName = Guid.NewGuid().ToString("n") + "UsersInRole";
var roles = GenerateRoles(guidName, 4);
var roleNameList = new List<string>();
foreach (var role in roles)
{
IdentityResultAssert.IsSuccess(await roleManager.CreateAsync(role));
roleNameList.Add(await roleManager.GetRoleNameAsync(role));
}
for (int i = 0; i < 6; i++)
{
var user = CreateTestUser();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
if ((i % 2) == 0)
{
IdentityResultAssert.IsSuccess(await manager.AddToRolesAsync(user, roleNameList));
}
}
foreach (var role in roles)
{
Assert.Equal(3, (await manager.GetUsersInRoleAsync(await roleManager.GetRoleNameAsync(role))).Count);
}
Assert.Equal(0, (await manager.GetUsersInRoleAsync("123456")).Count);
}
private List<TRole> GenerateRoles(string namePrefix, int count)
{
var roles = new List<TRole>(count);
for (var i = 0; i < count; i++)
{
roles.Add(CreateTestRole(namePrefix + i));
}
return roles;
}
}
}
@@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Identity.Test
{
/// <summary>
/// test logger.
/// </summary>
public interface ITestLogger
{
/// <summary>
/// log messages.
/// </summary>
IList<string> LogMessages { get; }
}
/// <summary>
/// Test logger.
/// </summary>
/// <typeparam name="TName"></typeparam>
public class TestLogger<TName> : ILogger<TName>, ITestLogger
{
/// <summary>
/// log messages.
/// </summary>
public IList<string> LogMessages { get; } = new List<string>();
/// <summary>
/// </summary>
/// <typeparam name="TState"></typeparam>
/// <param name="state"></param>
/// <returns></returns>
public IDisposable BeginScope<TState>(TState state)
{
LogMessages.Add(state?.ToString());
return null;
}
/// <summary>
///
/// </summary>
/// <param name="logLevel"></param>
/// <returns></returns>
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
/// <summary>
///
/// </summary>
/// <typeparam name="TState"></typeparam>
/// <param name="logLevel"></param>
/// <param name="eventId"></param>
/// <param name="state"></param>
/// <param name="exception"></param>
/// <param name="formatter"></param>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (formatter == null)
{
LogMessages.Add(state.ToString());
}
else
{
LogMessages.Add(formatter(state, exception));
}
}
}
}