diff --git a/IdentityCore.sln b/IdentityCore.sln new file mode 100644 index 0000000000..e88571a3a6 --- /dev/null +++ b/IdentityCore.sln @@ -0,0 +1,224 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26507.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0F647068-6602-4E24-B1DC-8ED91481A50A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{52D59F18-62D2-4D17-8CF2-BE192445AF8E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity", "src\Microsoft.AspNetCore.Identity\Microsoft.AspNetCore.Identity.csproj", "{1729302E-A58E-4652-B639-5B6B68DA2748}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.Test", "test\Microsoft.AspNetCore.Identity.Test\Microsoft.AspNetCore.Identity.Test.csproj", "{2CF3927B-19E4-4866-9BAA-2C131580E7C3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.InMemory.Test", "test\Microsoft.AspNetCore.Identity.InMemory.Test\Microsoft.AspNetCore.Identity.InMemory.Test.csproj", "{65161409-C4C4-4D63-A73B-231FCFF4D503}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{58D94A0E-C2B7-43A7-8826-99ECBB1E0A50}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentitySample.Mvc", "samples\IdentitySample.Mvc\IdentitySample.Mvc.csproj", "{E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test", "test\Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test\Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test.csproj", "{37236EA3-915D-46D5-997C-DF513C500E4B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test", "test\Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test\Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test.csproj", "{EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.EntityFrameworkCore", "src\Microsoft.AspNetCore.Identity.EntityFrameworkCore\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj", "{4490894C-3572-4E63-86F1-EE5105CE8A06}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.Identity.AspNetCoreCompat", "src\Microsoft.AspNet.Identity.AspNetCoreCompat\Microsoft.AspNet.Identity.AspNetCoreCompat.csproj", "{6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Identity.Specification.Tests", "src\Microsoft.AspNetCore.Identity.Specification.Tests\Microsoft.AspNetCore.Identity.Specification.Tests.csproj", "{5608E828-DD54-4E2A-B73C-FC22268BE797}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Identity.Core", "src\Microsoft.Extensions.Identity.Core\Microsoft.Extensions.Identity.Core.csproj", "{D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Identity.Stores", "src\Microsoft.Extensions.Identity.Stores\Microsoft.Extensions.Identity.Stores.csproj", "{FADA11FC-DC06-4832-A569-7B2374A6CD42}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|x64.ActiveCfg = Debug|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|x64.Build.0 = Debug|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Debug|x86.ActiveCfg = Debug|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Release|Any CPU.Build.0 = Release|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Release|x64.ActiveCfg = Release|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Release|x64.Build.0 = Release|Any CPU + {1729302E-A58E-4652-B639-5B6B68DA2748}.Release|x86.ActiveCfg = Release|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|x64.ActiveCfg = Debug|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|x64.Build.0 = Debug|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Debug|x86.ActiveCfg = Debug|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|Any CPU.Build.0 = Release|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|x64.ActiveCfg = Release|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|x64.Build.0 = Release|Any CPU + {2CF3927B-19E4-4866-9BAA-2C131580E7C3}.Release|x86.ActiveCfg = Release|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|x64.ActiveCfg = Debug|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|x64.Build.0 = Debug|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Debug|x86.ActiveCfg = Debug|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|Any CPU.Build.0 = Release|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|x64.ActiveCfg = Release|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|x64.Build.0 = Release|Any CPU + {65161409-C4C4-4D63-A73B-231FCFF4D503}.Release|x86.ActiveCfg = Release|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|x64.ActiveCfg = Debug|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|x64.Build.0 = Debug|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|Any CPU.Build.0 = Release|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|x64.ActiveCfg = Release|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|x64.Build.0 = Release|Any CPU + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6}.Release|x86.ActiveCfg = Release|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|x64.ActiveCfg = Debug|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|x64.Build.0 = Debug|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Debug|x86.ActiveCfg = Debug|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Release|Any CPU.Build.0 = Release|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Release|x64.ActiveCfg = Release|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Release|x64.Build.0 = Release|Any CPU + {37236EA3-915D-46D5-997C-DF513C500E4B}.Release|x86.ActiveCfg = Release|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|x64.Build.0 = Debug|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|Any CPU.Build.0 = Release|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|x64.ActiveCfg = Release|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|x64.Build.0 = Release|Any CPU + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD}.Release|x86.ActiveCfg = Release|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|x64.ActiveCfg = Debug|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|x64.Build.0 = Debug|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Debug|x86.ActiveCfg = Debug|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|Any CPU.Build.0 = Release|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|x64.ActiveCfg = Release|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|x64.Build.0 = Release|Any CPU + {4490894C-3572-4E63-86F1-EE5105CE8A06}.Release|x86.ActiveCfg = Release|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|x64.ActiveCfg = Debug|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|x64.Build.0 = Debug|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Debug|x86.Build.0 = Debug|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|Any CPU.Build.0 = Release|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|x64.ActiveCfg = Release|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|x64.Build.0 = Release|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|x86.ActiveCfg = Release|Any CPU + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475}.Release|x86.Build.0 = Release|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|x64.ActiveCfg = Debug|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|x64.Build.0 = Debug|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|x86.ActiveCfg = Debug|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Debug|x86.Build.0 = Debug|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|Any CPU.Build.0 = Release|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|x64.ActiveCfg = Release|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|x64.Build.0 = Release|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|x86.ActiveCfg = Release|Any CPU + {5608E828-DD54-4E2A-B73C-FC22268BE797}.Release|x86.Build.0 = Release|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|x64.ActiveCfg = Debug|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|x64.Build.0 = Debug|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|x86.ActiveCfg = Debug|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Debug|x86.Build.0 = Debug|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|Any CPU.Build.0 = Release|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|x64.ActiveCfg = Release|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|x64.Build.0 = Release|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|x86.ActiveCfg = Release|Any CPU + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8}.Release|x86.Build.0 = Release|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|x64.ActiveCfg = Debug|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|x64.Build.0 = Debug|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|x86.ActiveCfg = Debug|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Debug|x86.Build.0 = Debug|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|Any CPU.Build.0 = Release|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|x64.ActiveCfg = Release|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|x64.Build.0 = Release|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|x86.ActiveCfg = Release|Any CPU + {FADA11FC-DC06-4832-A569-7B2374A6CD42}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1729302E-A58E-4652-B639-5B6B68DA2748} = {0F647068-6602-4E24-B1DC-8ED91481A50A} + {2CF3927B-19E4-4866-9BAA-2C131580E7C3} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E} + {65161409-C4C4-4D63-A73B-231FCFF4D503} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E} + {E1BFA023-CFFD-49CE-8466-1C28DD2EC1F6} = {58D94A0E-C2B7-43A7-8826-99ECBB1E0A50} + {37236EA3-915D-46D5-997C-DF513C500E4B} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E} + {EA7EB28F-53B8-4009-9C6B-74DB090CA8DD} = {52D59F18-62D2-4D17-8CF2-BE192445AF8E} + {4490894C-3572-4E63-86F1-EE5105CE8A06} = {0F647068-6602-4E24-B1DC-8ED91481A50A} + {6A74C6EA-B241-4D6B-BCE4-BF89EC1D2475} = {0F647068-6602-4E24-B1DC-8ED91481A50A} + {5608E828-DD54-4E2A-B73C-FC22268BE797} = {0F647068-6602-4E24-B1DC-8ED91481A50A} + {D5905D78-A32E-44B8-8F21-EDAEDC95D9B8} = {0F647068-6602-4E24-B1DC-8ED91481A50A} + {FADA11FC-DC06-4832-A569-7B2374A6CD42} = {0F647068-6602-4E24-B1DC-8ED91481A50A} + EndGlobalSection +EndGlobal diff --git a/build/repo.props b/build/repo.props new file mode 100644 index 0000000000..205ecdd92f --- /dev/null +++ b/build/repo.props @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs index 2fafc38694..8044d33d09 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// Base class for the Entity Framework database context used for identity. /// /// The type of the user objects. - public class IdentityDbContext : IdentityDbContext where TUser : IdentityUser + public class IdentityDbContext : IdentityDbContext where TUser : IdentityUser { /// /// Initializes a new instance of . @@ -41,6 +41,27 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore protected IdentityDbContext() { } } + /// + /// Base class for the Entity Framework database context used for identity. + /// + /// The type of user objects. + /// The type of the primary key for users and roles. + public class IdentityDbContext : IdentityDbContext, IdentityUserLogin, IdentityUserToken> + where TUser : IdentityUser + where TKey : IEquatable + { + /// + /// Initializes a new instance of the db context. + /// + /// The options to be used by a . + public IdentityDbContext(DbContextOptions options) : base(options) { } + + /// + /// Initializes a new instance of the class. + /// + protected IdentityDbContext() { } + } + /// /// Base class for the Entity Framework database context used for identity. /// @@ -68,21 +89,15 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// Base class for the Entity Framework database context used for identity. /// /// The type of user objects. - /// The type of role objects. /// The type of the primary key for users and roles. /// The type of the user claim object. - /// The type of the user role object. /// The type of the user login object. - /// The type of the role claim object. /// The type of the user token object. - public abstract class IdentityDbContext : DbContext - where TUser : IdentityUser - where TRole : IdentityRole + public abstract class IdentityDbContext : DbContext + where TUser : IdentityUser where TKey : IEquatable where TUserClaim : IdentityUserClaim - where TUserRole : IdentityUserRole where TUserLogin : IdentityUserLogin - where TRoleClaim : IdentityRoleClaim where TUserToken : IdentityUserToken { /// @@ -111,26 +126,11 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// public DbSet UserLogins { get; set; } - /// - /// Gets or sets the of User roles. - /// - public DbSet UserRoles { get; set; } - /// /// Gets or sets the of User tokens. /// public DbSet UserTokens { get; set; } - /// - /// Gets or sets the of roles. - /// - public DbSet Roles { get; set; } - - /// - /// Gets or sets the of role claims. - /// - public DbSet RoleClaims { get; set; } - /// /// Configures the schema needed for the identity framework. /// @@ -155,10 +155,90 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore // Replace with b.HasMany(). b.HasMany().WithOne().HasForeignKey(uc => uc.UserId).IsRequired(); b.HasMany().WithOne().HasForeignKey(ul => ul.UserId).IsRequired(); - b.HasMany().WithOne().HasForeignKey(ur => ur.UserId).IsRequired(); b.HasMany().WithOne().HasForeignKey(ut => ut.UserId).IsRequired(); }); + builder.Entity(b => + { + b.HasKey(uc => uc.Id); + b.ToTable("AspNetUserClaims"); + }); + + builder.Entity(b => + { + b.HasKey(l => new { l.LoginProvider, l.ProviderKey }); + b.ToTable("AspNetUserLogins"); + }); + + builder.Entity(b => + { + b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name }); + b.ToTable("AspNetUserTokens"); + }); + } + } + + /// + /// Base class for the Entity Framework database context used for identity. + /// + /// The type of user objects. + /// The type of role objects. + /// The type of the primary key for users and roles. + /// The type of the user claim object. + /// The type of the user role object. + /// The type of the user login object. + /// The type of the role claim object. + /// The type of the user token object. + public abstract class IdentityDbContext : IdentityDbContext + where TUser : IdentityUser + where TRole : IdentityRole + where TKey : IEquatable + where TUserClaim : IdentityUserClaim + where TUserRole : IdentityUserRole + where TUserLogin : IdentityUserLogin + where TRoleClaim : IdentityRoleClaim + where TUserToken : IdentityUserToken + { + /// + /// Initializes a new instance of the class. + /// + /// The options to be used by a . + public IdentityDbContext(DbContextOptions options) : base(options) { } + + /// + /// Initializes a new instance of the class. + /// + protected IdentityDbContext() { } + + /// + /// Gets or sets the of User roles. + /// + public DbSet UserRoles { get; set; } + + /// + /// Gets or sets the of roles. + /// + public DbSet Roles { get; set; } + + /// + /// Gets or sets the of role claims. + /// + public DbSet RoleClaims { get; set; } + + /// + /// Configures the schema needed for the identity framework. + /// + /// + /// The builder being used to construct the model for this context. + /// + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.Entity(b => + { + b.HasMany().WithOne().HasForeignKey(ur => ur.UserId).IsRequired(); + }); + builder.Entity(b => { b.HasKey(r => r.Id); @@ -173,35 +253,17 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore b.HasMany().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired(); }); - builder.Entity(b => - { - b.HasKey(uc => uc.Id); - b.ToTable("AspNetUserClaims"); - }); - - builder.Entity(b => + builder.Entity(b => { b.HasKey(rc => rc.Id); b.ToTable("AspNetRoleClaims"); }); - builder.Entity(b => + builder.Entity(b => { b.HasKey(r => new { r.UserId, r.RoleId }); b.ToTable("AspNetUserRoles"); }); - - builder.Entity(b => - { - b.HasKey(l => new { l.LoginProvider, l.ProviderKey }); - b.ToTable("AspNetUserLogins"); - }); - - builder.Entity(b => - { - b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name }); - b.ToTable("AspNetUserTokens"); - }); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs index 6a25296f53..0c7d1d48fc 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityEntityFrameworkBuilderExtensions.cs @@ -30,32 +30,68 @@ namespace Microsoft.Extensions.DependencyInjection private static void AddStores(IServiceCollection services, Type userType, Type roleType, Type contextType) { - var identityUserType = FindGenericBaseType(userType, typeof(IdentityUser<,,,,>)); + var identityUserType = FindGenericBaseType(userType, typeof(IdentityUser<>)); if (identityUserType == null) { throw new InvalidOperationException(Resources.NotIdentityUser); } - var identityRoleType = FindGenericBaseType(roleType, typeof(IdentityRole<,,>)); - if (identityRoleType == null) + + var keyType = identityUserType.GenericTypeArguments[0]; + + if (roleType != null) { - throw new InvalidOperationException(Resources.NotIdentityRole); + var identityRoleType = FindGenericBaseType(roleType, typeof(IdentityRole<>)); + if (identityRoleType == null) + { + throw new InvalidOperationException(Resources.NotIdentityRole); + } + + Type userStoreType = null; + Type roleStoreType = null; + var identityContext = FindGenericBaseType(contextType, typeof(IdentityDbContext<,,,,,,,>)); + if (identityContext == null) + { + // If its a custom DbContext, we can only add the default POCOs + userStoreType = typeof(UserStore<,,,>).MakeGenericType(userType, roleType, contextType, keyType); + roleStoreType = typeof(RoleStore<,,>).MakeGenericType(roleType, contextType, keyType); + } + else + { + userStoreType = typeof(UserStore<,,,,,,,,>).MakeGenericType(userType, roleType, contextType, + identityContext.GenericTypeArguments[2], + identityContext.GenericTypeArguments[3], + identityContext.GenericTypeArguments[4], + identityContext.GenericTypeArguments[5], + identityContext.GenericTypeArguments[7], + identityContext.GenericTypeArguments[6]); + roleStoreType = typeof(RoleStore<,,,,>).MakeGenericType(roleType, contextType, + identityContext.GenericTypeArguments[2], + identityContext.GenericTypeArguments[4], + identityContext.GenericTypeArguments[6]); + } + services.TryAddScoped(typeof(IUserStore<>).MakeGenericType(userType), userStoreType); + services.TryAddScoped(typeof(IRoleStore<>).MakeGenericType(roleType), roleStoreType); + } + else + { // No Roles + Type userStoreType = null; + var identityContext = FindGenericBaseType(contextType, typeof(IdentityDbContext<,,,,>)); + if (identityContext == null) + { + // If its a custom DbContext, we can only add the default POCOs + userStoreType = typeof(UserStore<,,,>).MakeGenericType(userType, roleType, contextType, keyType); + } + else + { + userStoreType = typeof(UserOnlyStore<,,,,,>).MakeGenericType(userType, roleType, contextType, + identityContext.GenericTypeArguments[1], + identityContext.GenericTypeArguments[2], + identityContext.GenericTypeArguments[3], + identityContext.GenericTypeArguments[4]); + } + services.TryAddScoped(typeof(IUserStore<>).MakeGenericType(userType), userStoreType); } - services.TryAddScoped( - typeof(IUserStore<>).MakeGenericType(userType), - typeof(UserStore<,,,,,,,,>).MakeGenericType(userType, roleType, contextType, - identityUserType.GenericTypeArguments[0], - identityUserType.GenericTypeArguments[1], - identityUserType.GenericTypeArguments[2], - identityUserType.GenericTypeArguments[3], - identityUserType.GenericTypeArguments[4], - identityRoleType.GenericTypeArguments[2])); - services.TryAddScoped( - typeof(IRoleStore<>).MakeGenericType(roleType), - typeof(RoleStore<,,,,>).MakeGenericType(roleType, contextType, - identityRoleType.GenericTypeArguments[0], - identityRoleType.GenericTypeArguments[1], - identityRoleType.GenericTypeArguments[2])); } private static TypeInfo FindGenericBaseType(Type currentType, Type genericBaseType) diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Properties/Resources.Designer.cs index 9862d64fb6..6e9c08a032 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/Properties/Resources.Designer.cs @@ -15,64 +15,56 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// internal static string NotIdentityRole { - get { return GetString("NotIdentityRole"); } + get => GetString("NotIdentityRole"); } /// /// AddEntityFrameworkStores can only be called with a role that derives from IdentityRole<TKey, TUserRole, TRoleClaim>. /// internal static string FormatNotIdentityRole() - { - return GetString("NotIdentityRole"); - } + => GetString("NotIdentityRole"); /// /// AddEntityFrameworkStores can only be called with a user that derives from IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin, TUserToken>. /// internal static string NotIdentityUser { - get { return GetString("NotIdentityUser"); } + get => GetString("NotIdentityUser"); } /// /// AddEntityFrameworkStores can only be called with a user that derives from IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin, TUserToken>. /// internal static string FormatNotIdentityUser() - { - return GetString("NotIdentityUser"); - } + => GetString("NotIdentityUser"); /// /// Role {0} does not exist. /// internal static string RoleNotFound { - get { return GetString("RoleNotFound"); } + get => GetString("RoleNotFound"); } /// /// Role {0} does not exist. /// internal static string FormatRoleNotFound(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("RoleNotFound"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("RoleNotFound"), p0); /// /// Value cannot be null or empty. /// internal static string ValueCannotBeNullOrEmpty { - get { return GetString("ValueCannotBeNullOrEmpty"); } + get => GetString("ValueCannotBeNullOrEmpty"); } /// /// Value cannot be null or empty. /// internal static string FormatValueCannotBeNullOrEmpty() - { - return GetString("ValueCannotBeNullOrEmpty"); - } + => GetString("ValueCannotBeNullOrEmpty"); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs index 31212b061f..140d51765e 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/RoleStore.cs @@ -9,7 +9,6 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { @@ -77,7 +76,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore public class RoleStore : IQueryableRoleStore, IRoleClaimStore - where TRole : IdentityRole + where TRole : IdentityRole where TKey : IEquatable where TContext : DbContext where TUserRole : IdentityUserRole, new() @@ -361,10 +360,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// /// Dispose the stores /// - public void Dispose() - { - _disposed = true; - } + public void Dispose() => _disposed = true; /// /// Get the claims associated with the specified as an asynchronous operation. @@ -434,10 +430,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// /// A navigation property for the roles the store contains. /// - public virtual IQueryable Roles - { - get { return Context.Set(); } - } + public virtual IQueryable Roles => Context.Set(); private DbSet RoleClaims { get { return Context.Set(); } } @@ -448,8 +441,6 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// The associated claim. /// The role claim entity. protected virtual TRoleClaim CreateRoleClaim(TRole role, Claim claim) - { - return new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; - } + => new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; } } diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserOnlyStore.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserOnlyStore.cs new file mode 100644 index 0000000000..e0a36a98cf --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserOnlyStore.cs @@ -0,0 +1,567 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore +{ + /// + /// Creates a new instance of a persistence store for the specified user type. + /// + /// The type representing a user. + public class UserOnlyStore : UserOnlyStore where TUser : IdentityUser, new() + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public UserOnlyStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } + } + + /// + /// Represents a new instance of a persistence store for the specified user and role types. + /// + /// The type representing a user. + /// The type of the data context class used to access the store. + public class UserOnlyStore : UserOnlyStore + where TUser : IdentityUser + where TContext : DbContext + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public UserOnlyStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } + } + + /// + /// Represents a new instance of a persistence store for the specified user and role types. + /// + /// The type representing a user. + /// The type of the data context class used to access the store. + /// The type of the primary key for a role. + public class UserOnlyStore : UserOnlyStore, IdentityUserLogin, IdentityUserToken> + where TUser : IdentityUser + where TContext : DbContext + where TKey : IEquatable + { + /// + /// Constructs a new instance of . + /// + /// The . + /// The . + public UserOnlyStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } + } + + /// + /// Represents a new instance of a persistence store for the specified user and role types. + /// + /// The type representing a user. + /// The type of the data context class used to access the store. + /// The type of the primary key for a role. + /// The type representing a claim. + /// The type representing a user external login. + /// The type representing a user token. + public class UserOnlyStore : + UserStoreBase, + IUserLoginStore, + IUserClaimStore, + IUserPasswordStore, + IUserSecurityStampStore, + IUserEmailStore, + IUserLockoutStore, + IUserPhoneNumberStore, + IQueryableUserStore, + IUserTwoFactorStore, + IUserAuthenticationTokenStore, + IUserAuthenticatorKeyStore, + IUserTwoFactorRecoveryCodeStore + where TUser : IdentityUser + where TContext : DbContext + where TKey : IEquatable + where TUserClaim : IdentityUserClaim, new() + where TUserLogin : IdentityUserLogin, new() + where TUserToken : IdentityUserToken, new() + { + /// + /// Creates a new instance of the store. + /// + /// The context used to access the store. + /// The used to describe store errors. + public UserOnlyStore(TContext context, IdentityErrorDescriber describer = null) : base(describer ?? new IdentityErrorDescriber()) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + Context = context; + } + + /// + /// Gets the database context for this store. + /// + public TContext Context { get; private set; } + + /// + /// DbSet of users. + /// + protected DbSet UsersSet { get { return Context.Set(); } } + + /// + /// DbSet of user claims. + /// + protected DbSet UserClaims { get { return Context.Set(); } } + + /// + /// DbSet of user logins. + /// + protected DbSet UserLogins { get { return Context.Set(); } } + + /// + /// DbSet of user tokens. + /// + protected DbSet UserTokens { get { return Context.Set(); } } + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + public bool AutoSaveChanges { get; set; } = true; + + /// Saves the current store. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + protected Task SaveChanges(CancellationToken cancellationToken) + { + return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.CompletedTask; + } + + /// + /// Creates the specified in the user store. + /// + /// The user to create. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the creation operation. + public async override Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + Context.Add(user); + await SaveChanges(cancellationToken); + return IdentityResult.Success; + } + + /// + /// Updates the specified in the user store. + /// + /// The user to update. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the update operation. + public async override Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + Context.Attach(user); + user.ConcurrencyStamp = Guid.NewGuid().ToString(); + Context.Update(user); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + return IdentityResult.Success; + } + + /// + /// Deletes the specified from the user store. + /// + /// The user to delete. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the update operation. + public async override Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + Context.Remove(user); + try + { + await SaveChanges(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + return IdentityResult.Success; + } + + /// + /// Finds and returns a user, if any, who has the specified . + /// + /// The user ID to search for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + public override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + var id = ConvertIdFromString(userId); + return UsersSet.FindAsync(new object[] { id }, cancellationToken); + } + + /// + /// Finds and returns a user, if any, who has the specified normalized user name. + /// + /// The normalized user name to search for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + public override Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName, cancellationToken); + } + + /// + /// A navigation property for the users the store contains. + /// + public override IQueryable Users + { + get { return UsersSet; } + } + + /// + /// Return a user with the matching userId if it exists. + /// + /// The user's id. + /// The used to propagate notifications that the operation should be canceled. + /// The user if it exists. + protected override Task FindUserAsync(TKey userId, CancellationToken cancellationToken) + { + return Users.SingleOrDefaultAsync(u => u.Id.Equals(userId), cancellationToken); + } + + /// + /// Return a user login with the matching userId, provider, providerKey if it exists. + /// + /// The user's id. + /// The login provider name. + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// The user login if it exists. + protected override Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken) + { + return UserLogins.SingleOrDefaultAsync(userLogin => userLogin.UserId.Equals(userId) && userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey, cancellationToken); + } + + /// + /// Return a user login with provider, providerKey if it exists. + /// + /// The login provider name. + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// The user login if it exists. + protected override Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) + { + return UserLogins.SingleOrDefaultAsync(userLogin => userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey, cancellationToken); + } + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The user whose claims should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the claims granted to a user. + public async override Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => c.ToClaim()).ToListAsync(cancellationToken); + } + + /// + /// Adds the given to the specified . + /// + /// The user to add the claim to. + /// The claim to add to the user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public override Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + if (claims == null) + { + throw new ArgumentNullException(nameof(claims)); + } + foreach (var claim in claims) + { + UserClaims.Add(CreateUserClaim(user, claim)); + } + return Task.FromResult(false); + } + + /// + /// Replaces the on the specified , with the . + /// + /// The user to replace the claim on. + /// The claim replace. + /// The new claim replacing the . + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async override Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + if (claim == null) + { + throw new ArgumentNullException(nameof(claim)); + } + if (newClaim == null) + { + throw new ArgumentNullException(nameof(newClaim)); + } + + var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); + foreach (var matchedClaim in matchedClaims) + { + matchedClaim.ClaimValue = newClaim.Value; + matchedClaim.ClaimType = newClaim.Type; + } + } + + /// + /// Removes the given from the specified . + /// + /// The user to remove the claims from. + /// The claim to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async override Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + if (claims == null) + { + throw new ArgumentNullException(nameof(claims)); + } + foreach (var claim in claims) + { + var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken); + foreach (var c in matchedClaims) + { + UserClaims.Remove(c); + } + } + } + + /// + /// Adds the given to the specified . + /// + /// The user to add the login to. + /// The login to add to the user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public override Task AddLoginAsync(TUser user, UserLoginInfo login, + CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + if (login == null) + { + throw new ArgumentNullException(nameof(login)); + } + UserLogins.Add(CreateUserLogin(user, login)); + return Task.FromResult(false); + } + + /// + /// Removes the given from the specified . + /// + /// The user to remove the login from. + /// The login to remove from the user. + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public override async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, + CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + var entry = await FindUserLoginAsync(user.Id, loginProvider, providerKey, cancellationToken); + if (entry != null) + { + UserLogins.Remove(entry); + } + } + + /// + /// Retrieves the associated logins for the specified . + /// + /// The user whose associated logins to retrieve. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The for the asynchronous operation, containing a list of for the specified , if any. + /// + public async override Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + var userId = user.Id; + return await UserLogins.Where(l => l.UserId.Equals(userId)) + .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToListAsync(cancellationToken); + } + + /// + /// Retrieves the user associated with the specified login provider and login provider key. + /// + /// The login provider who provided the . + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The for the asynchronous operation, containing the user, if any which matched the specified login provider and key. + /// + public async override Task FindByLoginAsync(string loginProvider, string providerKey, + CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + var userLogin = await FindUserLoginAsync(loginProvider, providerKey, cancellationToken); + if (userLogin != null) + { + return await FindUserAsync(userLogin.UserId, cancellationToken); + } + return null; + } + + /// + /// Gets the user, if any, associated with the specified, normalized email address. + /// + /// The normalized email address to return the user for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous lookup operation, the user if any associated with the specified normalized email address. + /// + public override Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken); + } + + /// + /// Retrieves all users with the specified claim. + /// + /// The claim whose users should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The contains a list of users, if any, that contain the specified claim. + /// + public async override Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (claim == null) + { + throw new ArgumentNullException(nameof(claim)); + } + + var query = from userclaims in UserClaims + join user in Users on userclaims.UserId equals user.Id + where userclaims.ClaimValue == claim.Value + && userclaims.ClaimType == claim.Type + select user; + + return await query.ToListAsync(cancellationToken); + } + + /// + /// Find a user token if it exists. + /// + /// The token owner. + /// The login provider for the token. + /// The name of the token. + /// The used to propagate notifications that the operation should be canceled. + /// The user token if it exists. + protected override Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) + => UserTokens.FindAsync(new object[] { user.Id, loginProvider, name }, cancellationToken); + + /// + /// Add a new user token. + /// + /// The token to be added. + /// + protected override Task AddUserTokenAsync(TUserToken token) + { + UserTokens.Add(token); + return Task.CompletedTask; + } + + + /// + /// Remove a new user token. + /// + /// The token to be removed. + /// + protected override Task RemoveUserTokenAsync(TUserToken token) + { + UserTokens.Remove(token); + return Task.CompletedTask; + } + } +} diff --git a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs index 20f8de152a..cedb9f4bae 100644 --- a/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs +++ b/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/UserStore.cs @@ -9,7 +9,6 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore { @@ -95,22 +94,9 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore /// The type representing a user token. /// The type representing a role claim. public class UserStore : - UserStoreBase, - IUserLoginStore, - IUserRoleStore, - IUserClaimStore, - IUserPasswordStore, - IUserSecurityStampStore, - IUserEmailStore, - IUserLockoutStore, - IUserPhoneNumberStore, - IQueryableUserStore, - IUserTwoFactorStore, - IUserAuthenticationTokenStore, - IUserAuthenticatorKeyStore, - IUserTwoFactorRecoveryCodeStore - where TUser : IdentityUser - where TRole : IdentityRole + UserStoreBase + where TUser : IdentityUser + where TRole : IdentityRole where TContext : DbContext where TKey : IEquatable where TUserClaim : IdentityUserClaim, new() diff --git a/src/Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore/IdentityServiceDbContext.cs b/src/Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore/IdentityServiceDbContext.cs index f14807c6bb..2cdd17f9a9 100644 --- a/src/Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore/IdentityServiceDbContext.cs +++ b/src/Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore/IdentityServiceDbContext.cs @@ -54,8 +54,8 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore TRedirectUri, TApplicationKey> : IdentityDbContext - where TUser : IdentityUser - where TRole : IdentityRole + where TUser : IdentityUser + where TRole : IdentityRole where TUserKey : IEquatable where TUserClaim : IdentityUserClaim where TUserRole : IdentityUserRole diff --git a/src/Microsoft.AspNetCore.Identity.Specification.Tests/IdentitySpecificationTestBase.cs b/src/Microsoft.AspNetCore.Identity.Specification.Tests/IdentitySpecificationTestBase.cs index 220dd11818..80852ab575 100644 --- a/src/Microsoft.AspNetCore.Identity.Specification.Tests/IdentitySpecificationTestBase.cs +++ b/src/Microsoft.AspNetCore.Identity.Specification.Tests/IdentitySpecificationTestBase.cs @@ -7,9 +7,7 @@ using System.Linq; using System.Linq.Expressions; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Xunit; @@ -32,32 +30,18 @@ namespace Microsoft.AspNetCore.Identity.Test /// The type of the user. /// The type of the role. /// The primary key type. - public abstract class IdentitySpecificationTestBase + public abstract class IdentitySpecificationTestBase : UserManagerSpecificationTestBase where TUser : class where TRole : class where TKey : IEquatable { - private const string NullValue = "(null)"; - - private readonly IdentityErrorDescriber _errorDescriber = new IdentityErrorDescriber(); - - /// - /// If true, tests that require a database will be skipped. - /// - /// - protected virtual bool ShouldSkipDbTests() - { - return false; - } - /// /// Configure the service collection used for tests. /// /// /// - protected virtual void SetupIdentityServices(IServiceCollection services, object context = null) + protected override void SetupIdentityServices(IServiceCollection services, object context) { - services.AddSingleton(new ConfigurationBuilder().Build()); services.AddSingleton(); services.AddIdentity(options => { @@ -75,25 +59,18 @@ namespace Microsoft.AspNetCore.Identity.Test } /// - /// Creates the user manager used for tests. + /// Setup the IdentityBuilder /// - /// The context that will be passed into the store, typically a db context. - /// The service collection to use, optional. - /// Delegate used to configure the services, optional. - /// The user manager to use for tests. - protected virtual UserManager CreateManager(object context = null, IServiceCollection services = null, Action configureServices = null) + /// + /// + /// + protected override IdentityBuilder SetupBuilder(IServiceCollection services, object context) { - if (services == null) - { - services = new ServiceCollection(); - } - if (context == null) - { - context = CreateTestContext(); - } - SetupIdentityServices(services, context); - configureServices?.Invoke(services); - return services.BuildServiceProvider().GetService>(); + var builder = base.SetupBuilder(services, context); + builder.AddRoles(); + AddRoleStore(services, context); + services.AddSingleton>>(new TestLogger>()); + return builder; } /// @@ -102,7 +79,7 @@ namespace Microsoft.AspNetCore.Identity.Test /// The context that will be passed into the store, typically a db context. /// The service collection to use, optional. /// - protected RoleManager CreateRoleManager(object context = null, IServiceCollection services = null) + protected virtual RoleManager CreateRoleManager(object context = null, IServiceCollection services = null) { if (services == null) { @@ -116,19 +93,6 @@ namespace Microsoft.AspNetCore.Identity.Test return services.BuildServiceProvider().GetService>(); } - /// - /// Creates the context object for a test, typically a DbContext. - /// - /// The context object for a test, typically a DbContext. - protected abstract object CreateTestContext(); - - /// - /// Adds an IUserStore to services for the test. - /// - /// The service collection to add to. - /// The context for the store to use, optional. - protected abstract void AddUserStore(IServiceCollection services, object context = null); - /// /// Adds an IRoleStore to services for the test. /// @@ -136,26 +100,6 @@ namespace Microsoft.AspNetCore.Identity.Test /// The context for the store to use, optional. protected abstract void AddRoleStore(IServiceCollection services, object context = null); - /// - /// Set the user's password hash. - /// - /// The user to set. - /// The password hash to set. - protected abstract void SetUserPasswordHash(TUser user, string hashedPassword); - - /// - /// Create a new test user instance. - /// - /// Optional name prefix, name will be randomized. - /// Optional email. - /// Optional phone number. - /// Optional lockout enabled. - /// Optional lockout end. - /// If true, the prefix should be used as the username without a random pad. - /// The new test user instance. - protected abstract TUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", - bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = null, bool useNamePrefixAsUserName = false); - /// /// Creates a new test role instance. /// @@ -164,20 +108,6 @@ namespace Microsoft.AspNetCore.Identity.Test /// protected abstract TRole CreateTestRole(string roleNamePrefix = "", bool useRoleNamePrefixAsRoleName = false); - /// - /// Query used to do name equality checks. - /// - /// The user name to match. - /// The query to use. - protected abstract Expression> UserNameEqualsPredicate(string userName); - - /// - /// Query used to do user name prefix matching. - /// - /// The user name to match. - /// The query to use. - protected abstract Expression> UserNameStartsWithPredicate(string userName); - /// /// Query used to do name equality checks. /// @@ -192,1316 +122,6 @@ namespace Microsoft.AspNetCore.Identity.Test /// The query to use. protected abstract Expression> RoleNameStartsWithPredicate(string roleName); - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanDeleteUser() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var userId = await manager.GetUserIdAsync(user); - IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); - Assert.Null(await manager.FindByIdAsync(userId)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanUpdateUserName() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var name = Guid.NewGuid().ToString(); - var user = CreateTestUser(name); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var newName = Guid.NewGuid().ToString(); - Assert.Null(await manager.FindByNameAsync(newName)); - IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, newName)); - IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); - Assert.NotNull(await manager.FindByNameAsync(newName)); - Assert.Null(await manager.FindByNameAsync(name)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CheckSetUserNameValidatesUser() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var username = "UpdateAsync" + Guid.NewGuid().ToString(); - var newUsername = "New" + Guid.NewGuid().ToString(); - var user = CreateTestUser(username, useNamePrefixAsUserName: true); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.Null(await manager.FindByNameAsync(newUsername)); - IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, newUsername)); - Assert.NotNull(await manager.FindByNameAsync(newUsername)); - Assert.Null(await manager.FindByNameAsync(username)); - - var newUser = CreateTestUser(username, useNamePrefixAsUserName: true); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(newUser)); - var error = _errorDescriber.InvalidUserName(""); - IdentityResultAssert.IsFailure(await manager.SetUserNameAsync(newUser, ""), error); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(newUser)} validation failed: {error.Code}."); - - error = _errorDescriber.DuplicateUserName(newUsername); - IdentityResultAssert.IsFailure(await manager.SetUserNameAsync(newUser, newUsername), error); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(newUser)} validation failed: {error.Code}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task SetUserNameUpdatesSecurityStamp() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var username = "UpdateAsync" + Guid.NewGuid().ToString(); - var newUsername = "New" + Guid.NewGuid().ToString(); - var user = CreateTestUser(username, useNamePrefixAsUserName: true); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.Null(await manager.FindByNameAsync(newUsername)); - IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, newUsername)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CreateUpdatesSecurityStamp() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var username = "Create" + Guid.NewGuid().ToString(); - var user = CreateTestUser(username, useNamePrefixAsUserName: true); - var stamp = await manager.GetSecurityStampAsync(user); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.NotNull(await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CheckSetEmailValidatesUser() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.Options.User.RequireUniqueEmail = true; - manager.UserValidators.Add(new UserValidator()); - var random = new Random(); - var email = "foo" + random.Next() + "@example.com"; - var newEmail = "bar" + random.Next() + "@example.com"; - var user = CreateTestUser(email: email); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, newEmail)); - - var newUser = CreateTestUser(email: email); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(newUser)); - IdentityResultAssert.IsFailure(await manager.SetEmailAsync(newUser, newEmail), _errorDescriber.DuplicateEmail(newEmail)); - IdentityResultAssert.IsFailure(await manager.SetEmailAsync(newUser, ""), _errorDescriber.InvalidEmail("")); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanUpdatePasswordUsingHasher() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser("UpdatePassword"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); - Assert.True(await manager.CheckPasswordAsync(user, "password")); - var userId = await manager.GetUserIdAsync(user); - - SetUserPasswordHash(user, manager.PasswordHasher.HashPassword(user, "New")); - IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); - Assert.False(await manager.CheckPasswordAsync(user, "password")); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Invalid password for user {await manager.GetUserIdAsync(user)}."); - Assert.True(await manager.CheckPasswordAsync(user, "New")); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanFindById() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.NotNull(await manager.FindByIdAsync(await manager.GetUserIdAsync(user))); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task UserValidatorCanBlockCreate() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - manager.UserValidators.Clear(); - manager.UserValidators.Add(new AlwaysBadValidator()); - IdentityResultAssert.IsFailure(await manager.CreateAsync(user), AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task UserValidatorCanBlockUpdate() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - manager.UserValidators.Clear(); - manager.UserValidators.Add(new AlwaysBadValidator()); - IdentityResultAssert.IsFailure(await manager.UpdateAsync(user), AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanChainUserValidators() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.UserValidators.Clear(); - var user = CreateTestUser(); - manager.UserValidators.Add(new AlwaysBadValidator()); - manager.UserValidators.Add(new AlwaysBadValidator()); - var result = await manager.CreateAsync(user); - IdentityResultAssert.IsFailure(result, AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code};{AlwaysBadValidator.ErrorMessage.Code}."); - Assert.Equal(2, result.Errors.Count()); - } - - /// - /// Test. - /// - /// Task - [Theory] - [InlineData("")] - [InlineData(null)] - public async Task UserValidatorBlocksShortEmailsWhenRequiresUniqueEmail(string email) - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - manager.Options.User.RequireUniqueEmail = true; - IdentityResultAssert.IsFailure(await manager.CreateAsync(user), _errorDescriber.InvalidEmail(email)); - } - - /// - /// Test. - /// - /// Task - [Theory] - [InlineData("@@afd")] - [InlineData("bogus")] - public async Task UserValidatorBlocksInvalidEmailsWhenRequiresUniqueEmail(string email) - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser("UpdateBlocked", email); - manager.Options.User.RequireUniqueEmail = true; - IdentityResultAssert.IsFailure(await manager.CreateAsync(user), _errorDescriber.InvalidEmail(email)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task PasswordValidatorCanBlockAddPassword() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - manager.PasswordValidators.Clear(); - manager.PasswordValidators.Add(new AlwaysBadValidator()); - IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), - AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanChainPasswordValidators() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.PasswordValidators.Clear(); - manager.PasswordValidators.Add(new AlwaysBadValidator()); - manager.PasswordValidators.Add(new AlwaysBadValidator()); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var result = await manager.AddPasswordAsync(user, "pwd"); - IdentityResultAssert.IsFailure(result, AlwaysBadValidator.ErrorMessage); - Assert.Equal(2, result.Errors.Count()); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task PasswordValidatorCanBlockChangePassword() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); - manager.PasswordValidators.Clear(); - manager.PasswordValidators.Add(new AlwaysBadValidator()); - IdentityResultAssert.IsFailure(await manager.ChangePasswordAsync(user, "password", "new"), - AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task PasswordValidatorCanBlockCreateUser() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - manager.PasswordValidators.Clear(); - manager.PasswordValidators.Add(new AlwaysBadValidator()); - IdentityResultAssert.IsFailure(await manager.CreateAsync(user, "password"), AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanCreateUserNoPassword() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var username = "CreateUserTest" + Guid.NewGuid(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(CreateTestUser(username, useNamePrefixAsUserName: true))); - var user = await manager.FindByNameAsync(username); - Assert.NotNull(user); - Assert.False(await manager.HasPasswordAsync(user)); - Assert.False(await manager.CheckPasswordAsync(user, "whatever")); - var logins = await manager.GetLoginsAsync(user); - Assert.NotNull(logins); - Assert.Equal(0, logins.Count()); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanCreateUserAddLogin() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - const string provider = "ZzAuth"; - const string display = "display"; - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var providerKey = await manager.GetUserIdAsync(user); - IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, new UserLoginInfo(provider, providerKey, display))); - var logins = await manager.GetLoginsAsync(user); - Assert.NotNull(logins); - Assert.Equal(1, logins.Count()); - Assert.Equal(provider, logins.First().LoginProvider); - Assert.Equal(providerKey, logins.First().ProviderKey); - Assert.Equal(display, logins.First().ProviderDisplayName); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanCreateUserLoginAndAddPassword() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var userId = await manager.GetUserIdAsync(user); - var login = new UserLoginInfo("Provider", userId, "display"); - IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); - Assert.False(await manager.HasPasswordAsync(user)); - IdentityResultAssert.IsSuccess(await manager.AddPasswordAsync(user, "password")); - Assert.True(await manager.HasPasswordAsync(user)); - var logins = await manager.GetLoginsAsync(user); - Assert.NotNull(logins); - Assert.Equal(1, logins.Count()); - Assert.Equal(user, await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey)); - Assert.True(await manager.CheckPasswordAsync(user, "password")); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task AddPasswordFailsIfAlreadyHave() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "Password")); - Assert.True(await manager.HasPasswordAsync(user)); - IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), - "User already has a password set."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} already has a password."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanCreateUserAddRemoveLogin() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - var result = await manager.CreateAsync(user); - Assert.NotNull(user); - var userId = await manager.GetUserIdAsync(user); - var login = new UserLoginInfo("Provider", userId, "display"); - IdentityResultAssert.IsSuccess(result); - IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); - Assert.Equal(user, await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey)); - var logins = await manager.GetLoginsAsync(user); - Assert.NotNull(logins); - Assert.Equal(1, logins.Count()); - Assert.Equal(login.LoginProvider, logins.Last().LoginProvider); - Assert.Equal(login.ProviderKey, logins.Last().ProviderKey); - Assert.Equal(login.ProviderDisplayName, logins.Last().ProviderDisplayName); - var stamp = await manager.GetSecurityStampAsync(user); - IdentityResultAssert.IsSuccess(await manager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey)); - Assert.Null(await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey)); - logins = await manager.GetLoginsAsync(user); - Assert.NotNull(logins); - Assert.Equal(0, logins.Count()); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanRemovePassword() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser("CanRemovePassword"); - const string password = "password"; - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); - var stamp = await manager.GetSecurityStampAsync(user); - var username = await manager.GetUserNameAsync(user); - IdentityResultAssert.IsSuccess(await manager.RemovePasswordAsync(user)); - var u = await manager.FindByNameAsync(username); - Assert.NotNull(u); - Assert.False(await manager.HasPasswordAsync(user)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanChangePassword() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - const string password = "password"; - const string newPassword = "newpassword"; - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.NotNull(stamp); - IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, password, newPassword)); - Assert.False(await manager.CheckPasswordAsync(user, password)); - Assert.True(await manager.CheckPasswordAsync(user, newPassword)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanAddRemoveUserClaim() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Claim[] claims = { new Claim("c", "v"), new Claim("c2", "v2"), new Claim("c2", "v3") }; - foreach (Claim c in claims) - { - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); - } - var userId = await manager.GetUserIdAsync(user); - 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); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task RemoveClaimOnlyAffectsUser() - { - 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"), new Claim("c2", "v2"), new Claim("c2", "v3") }; - foreach (Claim c in claims) - { - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user2, c)); - } - var userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(3, userClaims.Count); - IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); - userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(2, userClaims.Count); - IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); - userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(1, userClaims.Count); - IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2])); - userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(0, userClaims.Count); - var userClaims2 = await manager.GetClaimsAsync(user2); - Assert.Equal(3, userClaims2.Count); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanReplaceUserClaim() - { - 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"))); - var userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(1, userClaims.Count); - Claim claim = new Claim("c", "b"); - Claim oldClaim = userClaims.FirstOrDefault(); - IdentityResultAssert.IsSuccess(await manager.ReplaceClaimAsync(user, oldClaim, claim)); - var newUserClaims = await manager.GetClaimsAsync(user); - Assert.Equal(1, newUserClaims.Count); - Claim newClaim = newUserClaims.FirstOrDefault(); - Assert.Equal(claim.Type, newClaim.Type); - Assert.Equal(claim.Value, newClaim.Value); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ReplaceUserClaimOnlyAffectsUser() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - var user2 = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("c", "a"))); - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user2, new Claim("c", "a"))); - var userClaims = await manager.GetClaimsAsync(user); - Assert.Equal(1, userClaims.Count); - var userClaims2 = await manager.GetClaimsAsync(user); - Assert.Equal(1, userClaims2.Count); - Claim claim = new Claim("c", "b"); - Claim oldClaim = userClaims.FirstOrDefault(); - IdentityResultAssert.IsSuccess(await manager.ReplaceClaimAsync(user, oldClaim, claim)); - var newUserClaims = await manager.GetClaimsAsync(user); - Assert.Equal(1, newUserClaims.Count); - Claim newClaim = newUserClaims.FirstOrDefault(); - Assert.Equal(claim.Type, newClaim.Type); - Assert.Equal(claim.Value, newClaim.Value); - userClaims2 = await manager.GetClaimsAsync(user2); - Assert.Equal(1, userClaims2.Count); - Claim oldClaim2 = userClaims2.FirstOrDefault(); - Assert.Equal("c", oldClaim2.Type); - Assert.Equal("a", oldClaim2.Value); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ChangePasswordFallsIfPasswordWrong() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); - var result = await manager.ChangePasswordAsync(user, "bogus", "newpassword"); - IdentityResultAssert.IsFailure(result, "Incorrect password."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Change password failed for user {await manager.GetUserIdAsync(user)}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task AddDupeUserNameFails() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var username = "AddDupeUserNameFails" + Guid.NewGuid(); - var user = CreateTestUser(username, useNamePrefixAsUserName: true); - var user2 = CreateTestUser(username, useNamePrefixAsUserName: true); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), _errorDescriber.DuplicateUserName(username)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task AddDupeEmailAllowedByDefault() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(email: "yup@yup.com"); - var user2 = CreateTestUser(email: "yup@yup.com"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user2, await manager.GetEmailAsync(user))); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task AddDupeEmailFailsWhenUniqueEmailRequired() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.Options.User.RequireUniqueEmail = true; - var user = CreateTestUser(email: "FooUser@yup.com"); - var user2 = CreateTestUser(email: "FooUser@yup.com"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), _errorDescriber.DuplicateEmail("FooUser@yup.com")); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task UpdateSecurityStampActuallyChanges() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - Assert.Null(await manager.GetSecurityStampAsync(user)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.NotNull(stamp); - IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task AddDupeLoginFails() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - var login = new UserLoginInfo("Provider", "key", "display"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); - var result = await manager.AddLoginAsync(user, login); - IdentityResultAssert.IsFailure(result, _errorDescriber.LoginAlreadyAssociated()); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"AddLogin for user {await manager.GetUserIdAsync(user)} failed because it was already assocated with another user."); - } - - // Email tests - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanFindByEmail() - { - if (ShouldSkipDbTests()) - { - return; - } - var email = "foouser@test.com"; - var manager = CreateManager(); - var user = CreateTestUser(email: email); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var fetch = await manager.FindByEmailAsync(email); - Assert.Equal(user, fetch); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanFindUsersViaUserQuerable() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - if (mgr.SupportsQueryableUsers) - { - var users = GenerateUsers("CanFindUsersViaUserQuerable", 4); - foreach (var u in users) - { - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(u)); - } - Assert.Equal(users.Count, mgr.Users.Count(UserNameStartsWithPredicate("CanFindUsersViaUserQuerable"))); - Assert.Null(mgr.Users.FirstOrDefault(UserNameEqualsPredicate("bogus"))); - } - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ConfirmEmailFalseByDefaultTest() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - } - - private class StaticTokenProvider : IUserTwoFactorTokenProvider - { - public async Task GenerateAsync(string purpose, UserManager manager, TUser user) - { - return MakeToken(purpose, await manager.GetUserIdAsync(user)); - } - - public async Task ValidateAsync(string purpose, string token, UserManager manager, TUser user) - { - return token == MakeToken(purpose, await manager.GetUserIdAsync(user)); - } - - public Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user) - { - return Task.FromResult(true); - } - - private static string MakeToken(string purpose, string userId) - { - return string.Join(":", userId, purpose, "ImmaToken"); - } - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanResetPasswordWithStaticTokenProvider() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.RegisterTokenProvider("Static", new StaticTokenProvider()); - manager.Options.Tokens.PasswordResetTokenProvider = "Static"; - var user = CreateTestUser(); - const string password = "password"; - const string newPassword = "newpassword"; - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.NotNull(stamp); - var token = await manager.GeneratePasswordResetTokenAsync(user); - Assert.NotNull(token); - var userId = await manager.GetUserIdAsync(user); - IdentityResultAssert.IsSuccess(await manager.ResetPasswordAsync(user, token, newPassword)); - Assert.False(await manager.CheckPasswordAsync(user, password)); - Assert.True(await manager.CheckPasswordAsync(user, newPassword)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task PasswordValidatorCanBlockResetPasswordWithStaticTokenProvider() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.RegisterTokenProvider("Static", new StaticTokenProvider()); - manager.Options.Tokens.PasswordResetTokenProvider = "Static"; - var user = CreateTestUser(); - const string password = "password"; - const string newPassword = "newpassword"; - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.NotNull(stamp); - var token = await manager.GeneratePasswordResetTokenAsync(user); - Assert.NotNull(token); - manager.PasswordValidators.Add(new AlwaysBadValidator()); - IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, token, newPassword), - AlwaysBadValidator.ErrorMessage); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); - Assert.True(await manager.CheckPasswordAsync(user, password)); - Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ResetPasswordWithStaticTokenProviderFailsWithWrongToken() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.RegisterTokenProvider("Static", new StaticTokenProvider()); - manager.Options.Tokens.PasswordResetTokenProvider = "Static"; - var user = CreateTestUser(); - const string password = "password"; - const string newPassword = "newpassword"; - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.NotNull(stamp); - IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, "bogus", newPassword), "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ResetPassword for user { await manager.GetUserIdAsync(user)}."); - Assert.True(await manager.CheckPasswordAsync(user, password)); - Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanGenerateAndVerifyUserTokenWithStaticTokenProvider() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.RegisterTokenProvider("Static", new StaticTokenProvider()); - var user = CreateTestUser(); - var user2 = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); - var userId = await manager.GetUserIdAsync(user); - var token = await manager.GenerateUserTokenAsync(user, "Static", "test"); - - Assert.True(await manager.VerifyUserTokenAsync(user, "Static", "test", token)); - - Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test2", token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test2 for user { await manager.GetUserIdAsync(user)}."); - - Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test", token + "a")); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test for user { await manager.GetUserIdAsync(user)}."); - - Assert.False(await manager.VerifyUserTokenAsync(user2, "Static", "test", token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test for user { await manager.GetUserIdAsync(user2)}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanConfirmEmailWithStaticToken() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.RegisterTokenProvider("Static", new StaticTokenProvider()); - manager.Options.Tokens.EmailConfirmationTokenProvider = "Static"; - var user = CreateTestUser(); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var token = await manager.GenerateEmailConfirmationTokenAsync(user); - Assert.NotNull(token); - var userId = await manager.GetUserIdAsync(user); - IdentityResultAssert.IsSuccess(await manager.ConfirmEmailAsync(user, token)); - Assert.True(await manager.IsEmailConfirmedAsync(user)); - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ConfirmEmailWithStaticTokenFailsWithWrongToken() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - manager.RegisterTokenProvider("Static", new StaticTokenProvider()); - manager.Options.Tokens.EmailConfirmationTokenProvider = "Static"; - var user = CreateTestUser(); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, "bogus"), "Invalid token."); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user { await manager.GetUserIdAsync(user)}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ConfirmTokenFailsAfterPasswordChange() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(namePrefix: "Test"); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); - var token = await manager.GenerateEmailConfirmationTokenAsync(user); - Assert.NotNull(token); - IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, "password", "newpassword")); - IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, token), "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user { await manager.GetUserIdAsync(user)}."); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - } - - // Lockout tests - - /// - /// Test. - /// - /// Task - [Fact] - public async Task SingleFailureLockout() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); - mgr.Options.Lockout.MaxFailedAccessAttempts = 0; - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.True(await mgr.IsLockedOutAsync(user)); - Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User {await mgr.GetUserIdAsync(user)} is locked out."); - - Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task TwoFailureLockout() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); - mgr.Options.Lockout.MaxFailedAccessAttempts = 2; - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.True(await mgr.IsLockedOutAsync(user)); - Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User {await mgr.GetUserIdAsync(user)} is locked out."); - Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ResetAccessCountPreventsLockout() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); - mgr.Options.Lockout.MaxFailedAccessAttempts = 2; - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.ResetAccessFailedCountAsync(user)); - Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanEnableLockoutManuallyAndLockout() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); - mgr.Options.Lockout.AllowedForNewUsers = false; - mgr.Options.Lockout.MaxFailedAccessAttempts = 2; - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.False(await mgr.GetLockoutEnabledAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.SetLockoutEnabledAsync(user, true)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); - Assert.True(await mgr.IsLockedOutAsync(user)); - Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); - IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User {await mgr.GetUserIdAsync(user)} is locked out."); - Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task UserNotLockedOutWithNullDateTimeAndIsSetToNullDate() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset())); - Assert.False(await mgr.IsLockedOutAsync(user)); - Assert.Equal(new DateTimeOffset(), await mgr.GetLockoutEndDateAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task LockoutFailsIfNotEnabled() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - mgr.Options.Lockout.AllowedForNewUsers = false; - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.False(await mgr.GetLockoutEnabledAsync(user)); - IdentityResultAssert.IsFailure(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset()), - "Lockout is not enabled for this user."); - IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"Lockout for user {await mgr.GetUserIdAsync(user)} failed because lockout is not enabled for this user."); - Assert.False(await mgr.IsLockedOutAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task LockoutEndToUtcNowMinus1SecInUserShouldNotBeLockedOut() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - var user = CreateTestUser(lockoutEnd: DateTimeOffset.UtcNow.AddSeconds(-1)); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.False(await mgr.IsLockedOutAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task LockoutEndToUtcNowSubOneSecondWithManagerShouldNotBeLockedOut() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddSeconds(-1))); - Assert.False(await mgr.IsLockedOutAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task LockoutEndToUtcNowPlus5ShouldBeLockedOut() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - var lockoutEnd = DateTimeOffset.UtcNow.AddMinutes(5); - var user = CreateTestUser(lockoutEnd: lockoutEnd); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - Assert.True(await mgr.IsLockedOutAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task UserLockedOutWithDateTimeLocalKindNowPlus30() - { - if (ShouldSkipDbTests()) - { - return; - } - var mgr = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); - Assert.True(await mgr.GetLockoutEnabledAsync(user)); - var lockoutEnd = new DateTimeOffset(DateTime.Now.AddMinutes(30).ToLocalTime()); - IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, lockoutEnd)); - Assert.True(await mgr.IsLockedOutAsync(user)); - var end = await mgr.GetLockoutEndDateAsync(user); - Assert.Equal(lockoutEnd, end); - } - /// /// Test. /// @@ -2020,608 +640,6 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.Equal(roleName, await roleMgr.GetRoleNameAsync(await roleMgr.FindByNameAsync(roleName))); } - /// - /// Test. - /// - /// Task - [Fact] - public async Task SetPhoneNumberTest() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(phoneNumber: "123-456-7890"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.Equal("123-456-7890", await manager.GetPhoneNumberAsync(user)); - IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); - Assert.Equal("111-111-1111", await manager.GetPhoneNumberAsync(user)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanChangePhoneNumber() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(phoneNumber: "123-456-7890"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); - IdentityResultAssert.IsSuccess(await manager.ChangePhoneNumberAsync(user, "111-111-1111", token1)); - Assert.True(await manager.IsPhoneNumberConfirmedAsync(user)); - Assert.Equal("111-111-1111", await manager.GetPhoneNumberAsync(user)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ChangePhoneNumberFailsWithWrongToken() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(phoneNumber: "123-456-7890"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "111-111-1111", "bogus"), - "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:111-111-1111 for user { await manager.GetUserIdAsync(user)}."); - Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); - Assert.Equal("123-456-7890", await manager.GetPhoneNumberAsync(user)); - Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ChangePhoneNumberFailsWithWrongPhoneNumber() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(phoneNumber: "123-456-7890"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); - IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "bogus", token1), - "Invalid token."); - Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); - Assert.Equal("123-456-7890", await manager.GetPhoneNumberAsync(user)); - Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanVerifyPhoneNumber() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - const string num1 = "111-123-4567"; - const string num2 = "111-111-1111"; - var userId = await manager.GetUserIdAsync(user); - var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, num1); - - var token2 = await manager.GenerateChangePhoneNumberTokenAsync(user, num2); - Assert.NotEqual(token1, token2); - Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num1)); - Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num2)); - Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num1)); - Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num2)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:111-123-4567 for user {await manager.GetUserIdAsync(user)}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanChangeEmail() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser("foouser"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - var newEmail = await manager.GetUserNameAsync(user) + "@en.vec"; - var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail); - IdentityResultAssert.IsSuccess(await manager.ChangeEmailAsync(user, newEmail, token1)); - Assert.True(await manager.IsEmailConfirmedAsync(user)); - Assert.Equal(await manager.GetEmailAsync(user), newEmail); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanChangeEmailWithDifferentTokenProvider() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(context: null, services: null, - configureServices: s => s.Configure( - o => o.Tokens.ProviderMap["NewProvider2"] = new TokenProviderDescriptor(typeof(EmailTokenProvider)))); - manager.Options.Tokens.ChangeEmailTokenProvider = "NewProvider2"; - var user = CreateTestUser("foouser"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - var newEmail = await manager.GetUserNameAsync(user) + "@en.vec"; - var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail); - IdentityResultAssert.IsSuccess(await manager.ChangeEmailAsync(user, newEmail, token1)); - Assert.True(await manager.IsEmailConfirmedAsync(user)); - Assert.Equal(await manager.GetEmailAsync(user), newEmail); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ChangeEmailTokensFailsAfterEmailChanged() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser("foouser"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - var newEmail = await manager.GetUserNameAsync(user) + "@en.vec"; - var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail); - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, "another@email.com")); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, newEmail, token1)); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - Assert.Equal("another@email.com", await manager.GetEmailAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ChangeEmailFailsWithWrongToken() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser("foouser"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); - string oldEmail = email; - Assert.False(await manager.IsEmailConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "whatevah@foo.boop", "bogus"), - "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangeEmail:whatevah@foo.boop for user { await manager.GetUserIdAsync(user)}."); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - Assert.Equal(await manager.GetEmailAsync(user), oldEmail); - Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task ChangeEmailFailsWithEmail() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser("foouser"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); - string oldEmail = email; - Assert.False(await manager.IsEmailConfirmedAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - var token1 = await manager.GenerateChangeEmailTokenAsync(user, "forgot@alrea.dy"); - IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "oops@foo.boop", token1), - "Invalid token."); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangeEmail:oops@foo.boop for user { await manager.GetUserIdAsync(user)}."); - Assert.False(await manager.IsEmailConfirmedAsync(user)); - Assert.Equal(await manager.GetEmailAsync(user), oldEmail); - Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task EmailFactorFailsAfterSecurityStampChangeTest() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - string factorId = "Email"; //default - var user = CreateTestUser("foouser"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); - var token = await manager.GenerateEmailConfirmationTokenAsync(user); - await manager.ConfirmEmailAsync(user, token); - - var stamp = await manager.GetSecurityStampAsync(user); - Assert.NotNull(stamp); - token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - Assert.NotNull(token); - IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); - Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task EnableTwoFactorChangesSecurityStamp() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.NotNull(stamp); - IdentityResultAssert.IsSuccess(await manager.SetTwoFactorEnabledAsync(user, true)); - Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); - Assert.True(await manager.GetTwoFactorEnabledAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task GenerateTwoFactorWithUnknownFactorProviderWillThrow() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - const string error = "No IUserTokenProvider named 'bogus' is registered."; - var ex = await - Assert.ThrowsAsync( - () => manager.GenerateTwoFactorTokenAsync(user, "bogus")); - Assert.Equal(error, ex.Message); - ex = await Assert.ThrowsAsync( - () => manager.VerifyTwoFactorTokenAsync(user, "bogus", "bogus")); - Assert.Equal(error, ex.Message); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task GetValidTwoFactorTestEmptyWithNoProviders() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var factors = await manager.GetValidTwoFactorProvidersAsync(user); - Assert.NotNull(factors); - Assert.True(!factors.Any()); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanGetSetUpdateAndRemoveUserToken() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.Null(await manager.GetAuthenticationTokenAsync(user, "provider", "name")); - IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "provider", "name", "value")); - Assert.Equal("value", await manager.GetAuthenticationTokenAsync(user, "provider", "name")); - - IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "provider", "name", "value2")); - Assert.Equal("value2", await manager.GetAuthenticationTokenAsync(user, "provider", "name")); - - IdentityResultAssert.IsSuccess(await manager.RemoveAuthenticationTokenAsync(user, "whatevs", "name")); - Assert.Equal("value2", await manager.GetAuthenticationTokenAsync(user, "provider", "name")); - - IdentityResultAssert.IsSuccess(await manager.RemoveAuthenticationTokenAsync(user, "provider", "name")); - Assert.Null(await manager.GetAuthenticationTokenAsync(user, "provider", "name")); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanRedeemRecoveryCodeOnlyOnce() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - - var numCodes = 15; - var newCodes = await manager.GenerateNewTwoFactorRecoveryCodesAsync(user, numCodes); - Assert.Equal(numCodes, newCodes.Count()); - - foreach (var code in newCodes) - { - IdentityResultAssert.IsSuccess(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); - IdentityResultAssert.IsFailure(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); - } - // One last time to be sure - foreach (var code in newCodes) - { - IdentityResultAssert.IsFailure(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); - } - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task RecoveryCodesInvalidAfterReplace() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - - var numCodes = 15; - var newCodes = await manager.GenerateNewTwoFactorRecoveryCodesAsync(user, numCodes); - Assert.Equal(numCodes, newCodes.Count()); - var realCodes = await manager.GenerateNewTwoFactorRecoveryCodesAsync(user, numCodes); - Assert.Equal(numCodes, realCodes.Count()); - - foreach (var code in newCodes) - { - IdentityResultAssert.IsFailure(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); - } - - foreach (var code in realCodes) - { - IdentityResultAssert.IsSuccess(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); - } - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanGetValidTwoFactor() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var userId = await manager.GetUserIdAsync(user); - var factors = await manager.GetValidTwoFactorProvidersAsync(user); - Assert.NotNull(factors); - Assert.False(factors.Any()); - IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); - var token = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); - IdentityResultAssert.IsSuccess(await manager.ChangePhoneNumberAsync(user, "111-111-1111", token)); - await manager.UpdateAsync(user); - factors = await manager.GetValidTwoFactorProvidersAsync(user); - Assert.NotNull(factors); - Assert.Equal(1, factors.Count()); - Assert.Equal("Phone", factors[0]); - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, "test@test.com")); - token = await manager.GenerateEmailConfirmationTokenAsync(user); - await manager.ConfirmEmailAsync(user, token); - factors = await manager.GetValidTwoFactorProvidersAsync(user); - Assert.NotNull(factors); - Assert.Equal(2, factors.Count()); - IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); - factors = await manager.GetValidTwoFactorProvidersAsync(user); - Assert.NotNull(factors); - Assert.Equal(1, factors.Count()); - Assert.Equal("Phone", factors[0]); - IdentityResultAssert.IsSuccess(await manager.ResetAuthenticatorKeyAsync(user)); - factors = await manager.GetValidTwoFactorProvidersAsync(user); - Assert.NotNull(factors); - Assert.Equal(2, factors.Count()); - Assert.Equal("Authenticator", factors[1]); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task PhoneFactorFailsAfterSecurityStampChangeTest() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var factorId = "Phone"; // default - var user = CreateTestUser(phoneNumber: "4251234567"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var stamp = await manager.GetSecurityStampAsync(user); - Assert.NotNull(stamp); - var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); - Assert.NotNull(token); - IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); - Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task VerifyTokenFromWrongTokenProviderFails() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(phoneNumber: "4251234567"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - var token = await manager.GenerateTwoFactorTokenAsync(user, "Phone"); - Assert.NotNull(token); - Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "Email", token)); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task VerifyWithWrongSmsTokenFails() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - var user = CreateTestUser(phoneNumber: "4251234567"); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "Phone", "bogus")); - IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task NullableDateTimeOperationTest() - { - if (ShouldSkipDbTests()) - { - return; - } - var userMgr = CreateManager(); - var user = CreateTestUser(lockoutEnabled: true); - IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); - - Assert.Null(await userMgr.GetLockoutEndDateAsync(user)); - - // set LockoutDateEndDate to null - await userMgr.SetLockoutEndDateAsync(user, null); - Assert.Null(await userMgr.GetLockoutEndDateAsync(user)); - - // set to a valid value - await userMgr.SetLockoutEndDateAsync(user, DateTimeOffset.Parse("01/01/2014")); - Assert.Equal(DateTimeOffset.Parse("01/01/2014"), await userMgr.GetLockoutEndDateAsync(user)); - } - - /// - /// Test. - /// - /// Task - [Fact] - public async Task CanGetUsersWithClaims() - { - if (ShouldSkipDbTests()) - { - return; - } - var manager = CreateManager(); - - for (int i = 0; i < 6; i++) - { - var user = CreateTestUser(); - IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); - - if ((i % 2) == 0) - { - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("foo", "bar"))); - } - } - - Assert.Equal(3, (await manager.GetUsersForClaimAsync(new Claim("foo", "bar"))).Count); - - Assert.Equal(0, (await manager.GetUsersForClaimAsync(new Claim("123", "456"))).Count); - } - /// /// Test. /// @@ -2664,16 +682,6 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.Equal(0, (await manager.GetUsersInRoleAsync("123456")).Count); } - private List GenerateUsers(string userNamePrefix, int count) - { - var users = new List(count); - for (var i = 0; i < count; i++) - { - users.Add(CreateTestUser(userNamePrefix + i)); - } - return users; - } - private List GenerateRoles(string namePrefix, int count) { var roles = new List(count); diff --git a/src/Microsoft.AspNetCore.Identity.Specification.Tests/UserManagerSpecificationTests.cs b/src/Microsoft.AspNetCore.Identity.Specification.Tests/UserManagerSpecificationTests.cs new file mode 100644 index 0000000000..0cc5f8e3b0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.Specification.Tests/UserManagerSpecificationTests.cs @@ -0,0 +1,2110 @@ +// 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.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Identity.Test +{ + /// + /// Base class for tests that exercise basic identity functionality that all stores should support. + /// + /// The type of the user. + public abstract class UserManagerSpecificationTestBase : UserManagerSpecificationTestBase where TUser : class { } + + /// + /// Base class for tests that exercise basic identity functionality that all stores should support. + /// + /// The type of the user. + /// The primary key type. + public abstract class UserManagerSpecificationTestBase + where TUser : class + where TKey : IEquatable + { + /// + /// Null value. + /// + protected const string NullValue = "(null)"; + + /// + /// Error describer. + /// + protected readonly IdentityErrorDescriber _errorDescriber = new IdentityErrorDescriber(); + + /// + /// Configure the service collection used for tests. + /// + /// + /// + protected virtual void SetupIdentityServices(IServiceCollection services, object context) + => SetupBuilder(services, context); + + /// + /// Configure the service collection used for tests. + /// + /// + /// + protected virtual IdentityBuilder SetupBuilder(IServiceCollection services, object context) + { + services.AddSingleton(); + services.AddDataProtection(); + var builder = services.AddIdentityCore(options => + { + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequireUppercase = false; + options.User.AllowedUserNameCharacters = null; + }).AddDefaultTokenProviders(); + AddUserStore(services, context); + services.AddLogging(); + services.AddSingleton>>(new TestLogger>()); + return builder; + } + + /// + /// If true, tests that require a database will be skipped. + /// + /// + protected virtual bool ShouldSkipDbTests() => false; + + /// + /// Creates the user manager used for tests. + /// + /// The context that will be passed into the store, typically a db context. + /// The service collection to use, optional. + /// Delegate used to configure the services, optional. + /// The user manager to use for tests. + protected virtual UserManager CreateManager(object context = null, IServiceCollection services = null, Action configureServices = null) + { + if (services == null) + { + services = new ServiceCollection(); + } + if (context == null) + { + context = CreateTestContext(); + } + SetupIdentityServices(services, context); + configureServices?.Invoke(services); + return services.BuildServiceProvider().GetService>(); + } + + /// + /// Creates the context object for a test, typically a DbContext. + /// + /// The context object for a test, typically a DbContext. + protected abstract object CreateTestContext(); + + /// + /// Adds an IUserStore to services for the test. + /// + /// The service collection to add to. + /// The context for the store to use, optional. + protected abstract void AddUserStore(IServiceCollection services, object context = null); + + /// + /// Set the user's password hash. + /// + /// The user to set. + /// The password hash to set. + protected abstract void SetUserPasswordHash(TUser user, string hashedPassword); + + /// + /// Create a new test user instance. + /// + /// Optional name prefix, name will be randomized. + /// Optional email. + /// Optional phone number. + /// Optional lockout enabled. + /// Optional lockout end. + /// If true, the prefix should be used as the username without a random pad. + /// The new test user instance. + protected abstract TUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", + bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = null, bool useNamePrefixAsUserName = false); + + /// + /// Query used to do name equality checks. + /// + /// The user name to match. + /// The query to use. + protected abstract Expression> UserNameEqualsPredicate(string userName); + + /// + /// Query used to do user name prefix matching. + /// + /// The user name to match. + /// The query to use. + protected abstract Expression> UserNameStartsWithPredicate(string userName); + + private class AlwaysBadValidator : IUserValidator, + IPasswordValidator + { + public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad.", Code = "BadValidator" }; + + public Task ValidateAsync(UserManager manager, TUser user, string password) + { + return Task.FromResult(IdentityResult.Failed(ErrorMessage)); + } + + public Task ValidateAsync(UserManager manager, TUser user) + { + return Task.FromResult(IdentityResult.Failed(ErrorMessage)); + } + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CreateUserWillSetCreateDateOnlyIfSupported() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var userId = await manager.GetUserIdAsync(user); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanDeleteUser() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var userId = await manager.GetUserIdAsync(user); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); + Assert.Null(await manager.FindByIdAsync(userId)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanUpdateUserName() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var name = Guid.NewGuid().ToString(); + var user = CreateTestUser(name); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var newName = Guid.NewGuid().ToString(); + Assert.Null(await manager.FindByNameAsync(newName)); + IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, newName)); + IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); + Assert.NotNull(await manager.FindByNameAsync(newName)); + Assert.Null(await manager.FindByNameAsync(name)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CheckSetUserNameValidatesUser() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var username = "UpdateAsync" + Guid.NewGuid().ToString(); + var newUsername = "New" + Guid.NewGuid().ToString(); + var user = CreateTestUser(username, useNamePrefixAsUserName: true); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.Null(await manager.FindByNameAsync(newUsername)); + IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, newUsername)); + Assert.NotNull(await manager.FindByNameAsync(newUsername)); + Assert.Null(await manager.FindByNameAsync(username)); + + var newUser = CreateTestUser(username, useNamePrefixAsUserName: true); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(newUser)); + var error = _errorDescriber.InvalidUserName(""); + IdentityResultAssert.IsFailure(await manager.SetUserNameAsync(newUser, ""), error); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(newUser)} validation failed: {error.Code}."); + + error = _errorDescriber.DuplicateUserName(newUsername); + IdentityResultAssert.IsFailure(await manager.SetUserNameAsync(newUser, newUsername), error); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(newUser)} validation failed: {error.Code}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task SetUserNameUpdatesSecurityStamp() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var username = "UpdateAsync" + Guid.NewGuid().ToString(); + var newUsername = "New" + Guid.NewGuid().ToString(); + var user = CreateTestUser(username, useNamePrefixAsUserName: true); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.Null(await manager.FindByNameAsync(newUsername)); + IdentityResultAssert.IsSuccess(await manager.SetUserNameAsync(user, newUsername)); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CreateUpdatesSecurityStamp() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var username = "Create" + Guid.NewGuid().ToString(); + var user = CreateTestUser(username, useNamePrefixAsUserName: true); + var stamp = await manager.GetSecurityStampAsync(user); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.NotNull(await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CheckSetEmailValidatesUser() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.Options.User.RequireUniqueEmail = true; + manager.UserValidators.Add(new UserValidator()); + var random = new Random(); + var email = "foo" + random.Next() + "@example.com"; + var newEmail = "bar" + random.Next() + "@example.com"; + var user = CreateTestUser(email: email); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, newEmail)); + + var newUser = CreateTestUser(email: email); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(newUser)); + IdentityResultAssert.IsFailure(await manager.SetEmailAsync(newUser, newEmail), _errorDescriber.DuplicateEmail(newEmail)); + IdentityResultAssert.IsFailure(await manager.SetEmailAsync(newUser, ""), _errorDescriber.InvalidEmail("")); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanUpdatePasswordUsingHasher() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser("UpdatePassword"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); + Assert.True(await manager.CheckPasswordAsync(user, "password")); + var userId = await manager.GetUserIdAsync(user); + + SetUserPasswordHash(user, manager.PasswordHasher.HashPassword(user, "New")); + IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); + Assert.False(await manager.CheckPasswordAsync(user, "password")); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Invalid password for user {await manager.GetUserIdAsync(user)}."); + Assert.True(await manager.CheckPasswordAsync(user, "New")); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanFindById() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.NotNull(await manager.FindByIdAsync(await manager.GetUserIdAsync(user))); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task UserValidatorCanBlockCreate() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + manager.UserValidators.Clear(); + manager.UserValidators.Add(new AlwaysBadValidator()); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task UserValidatorCanBlockUpdate() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + manager.UserValidators.Clear(); + manager.UserValidators.Add(new AlwaysBadValidator()); + IdentityResultAssert.IsFailure(await manager.UpdateAsync(user), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanChainUserValidators() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.UserValidators.Clear(); + var user = CreateTestUser(); + manager.UserValidators.Add(new AlwaysBadValidator()); + manager.UserValidators.Add(new AlwaysBadValidator()); + var result = await manager.CreateAsync(user); + IdentityResultAssert.IsFailure(result, AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} validation failed: {AlwaysBadValidator.ErrorMessage.Code};{AlwaysBadValidator.ErrorMessage.Code}."); + Assert.Equal(2, result.Errors.Count()); + } + + /// + /// Test. + /// + /// Task + [Theory] + [InlineData("")] + [InlineData(null)] + public async Task UserValidatorBlocksShortEmailsWhenRequiresUniqueEmail(string email) + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + manager.Options.User.RequireUniqueEmail = true; + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), _errorDescriber.InvalidEmail(email)); + } + + /// + /// Test. + /// + /// Task + [Theory] + [InlineData("@@afd")] + [InlineData("bogus")] + public async Task UserValidatorBlocksInvalidEmailsWhenRequiresUniqueEmail(string email) + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser("UpdateBlocked", email); + manager.Options.User.RequireUniqueEmail = true; + IdentityResultAssert.IsFailure(await manager.CreateAsync(user), _errorDescriber.InvalidEmail(email)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task PasswordValidatorCanBlockAddPassword() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), + AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanChainPasswordValidators() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var result = await manager.AddPasswordAsync(user, "pwd"); + IdentityResultAssert.IsFailure(result, AlwaysBadValidator.ErrorMessage); + Assert.Equal(2, result.Errors.Count()); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task PasswordValidatorCanBlockChangePassword() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + IdentityResultAssert.IsFailure(await manager.ChangePasswordAsync(user, "password", "new"), + AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task PasswordValidatorCanBlockCreateUser() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user, "password"), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user) ?? NullValue} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanCreateUserNoPassword() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var username = "CreateUserTest" + Guid.NewGuid(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(CreateTestUser(username, useNamePrefixAsUserName: true))); + var user = await manager.FindByNameAsync(username); + Assert.NotNull(user); + Assert.False(await manager.HasPasswordAsync(user)); + Assert.False(await manager.CheckPasswordAsync(user, "whatever")); + var logins = await manager.GetLoginsAsync(user); + Assert.NotNull(logins); + Assert.Equal(0, logins.Count()); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanCreateUserAddLogin() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + const string provider = "ZzAuth"; + const string display = "display"; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var providerKey = await manager.GetUserIdAsync(user); + IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, new UserLoginInfo(provider, providerKey, display))); + var logins = await manager.GetLoginsAsync(user); + Assert.NotNull(logins); + Assert.Equal(1, logins.Count()); + Assert.Equal(provider, logins.First().LoginProvider); + Assert.Equal(providerKey, logins.First().ProviderKey); + Assert.Equal(display, logins.First().ProviderDisplayName); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanCreateUserLoginAndAddPassword() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var userId = await manager.GetUserIdAsync(user); + var login = new UserLoginInfo("Provider", userId, "display"); + IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); + Assert.False(await manager.HasPasswordAsync(user)); + IdentityResultAssert.IsSuccess(await manager.AddPasswordAsync(user, "password")); + Assert.True(await manager.HasPasswordAsync(user)); + var logins = await manager.GetLoginsAsync(user); + Assert.NotNull(logins); + Assert.Equal(1, logins.Count()); + Assert.Equal(user, await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey)); + Assert.True(await manager.CheckPasswordAsync(user, "password")); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task AddPasswordFailsIfAlreadyHave() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "Password")); + Assert.True(await manager.HasPasswordAsync(user)); + IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), + "User already has a password set."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} already has a password."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanCreateUserAddRemoveLogin() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + var result = await manager.CreateAsync(user); + Assert.NotNull(user); + var userId = await manager.GetUserIdAsync(user); + var login = new UserLoginInfo("Provider", userId, "display"); + IdentityResultAssert.IsSuccess(result); + IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); + Assert.Equal(user, await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey)); + var logins = await manager.GetLoginsAsync(user); + Assert.NotNull(logins); + Assert.Equal(1, logins.Count()); + Assert.Equal(login.LoginProvider, logins.Last().LoginProvider); + Assert.Equal(login.ProviderKey, logins.Last().ProviderKey); + Assert.Equal(login.ProviderDisplayName, logins.Last().ProviderDisplayName); + var stamp = await manager.GetSecurityStampAsync(user); + IdentityResultAssert.IsSuccess(await manager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey)); + Assert.Null(await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey)); + logins = await manager.GetLoginsAsync(user); + Assert.NotNull(logins); + Assert.Equal(0, logins.Count()); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanRemovePassword() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser("CanRemovePassword"); + const string password = "password"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = await manager.GetSecurityStampAsync(user); + var username = await manager.GetUserNameAsync(user); + IdentityResultAssert.IsSuccess(await manager.RemovePasswordAsync(user)); + var u = await manager.FindByNameAsync(username); + Assert.NotNull(u); + Assert.False(await manager.HasPasswordAsync(user)); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanChangePassword() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, password, newPassword)); + Assert.False(await manager.CheckPasswordAsync(user, password)); + Assert.True(await manager.CheckPasswordAsync(user, newPassword)); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanAddRemoveUserClaim() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Claim[] claims = { new Claim("c", "v"), new Claim("c2", "v2"), new Claim("c2", "v3") }; + foreach (Claim c in claims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); + } + var userId = await manager.GetUserIdAsync(user); + 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); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task RemoveClaimOnlyAffectsUser() + { + 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"), new Claim("c2", "v2"), new Claim("c2", "v3") }; + foreach (Claim c in claims) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user2, c)); + } + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(3, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(2, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[2])); + userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(0, userClaims.Count); + var userClaims2 = await manager.GetClaimsAsync(user2); + Assert.Equal(3, userClaims2.Count); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanReplaceUserClaim() + { + 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"))); + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + Claim claim = new Claim("c", "b"); + Claim oldClaim = userClaims.FirstOrDefault(); + IdentityResultAssert.IsSuccess(await manager.ReplaceClaimAsync(user, oldClaim, claim)); + var newUserClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, newUserClaims.Count); + Claim newClaim = newUserClaims.FirstOrDefault(); + Assert.Equal(claim.Type, newClaim.Type); + Assert.Equal(claim.Value, newClaim.Value); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ReplaceUserClaimOnlyAffectsUser() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + var user2 = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("c", "a"))); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user2, new Claim("c", "a"))); + var userClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims.Count); + var userClaims2 = await manager.GetClaimsAsync(user); + Assert.Equal(1, userClaims2.Count); + Claim claim = new Claim("c", "b"); + Claim oldClaim = userClaims.FirstOrDefault(); + IdentityResultAssert.IsSuccess(await manager.ReplaceClaimAsync(user, oldClaim, claim)); + var newUserClaims = await manager.GetClaimsAsync(user); + Assert.Equal(1, newUserClaims.Count); + Claim newClaim = newUserClaims.FirstOrDefault(); + Assert.Equal(claim.Type, newClaim.Type); + Assert.Equal(claim.Value, newClaim.Value); + userClaims2 = await manager.GetClaimsAsync(user2); + Assert.Equal(1, userClaims2.Count); + Claim oldClaim2 = userClaims2.FirstOrDefault(); + Assert.Equal("c", oldClaim2.Type); + Assert.Equal("a", oldClaim2.Value); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ChangePasswordFallsIfPasswordWrong() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); + var result = await manager.ChangePasswordAsync(user, "bogus", "newpassword"); + IdentityResultAssert.IsFailure(result, "Incorrect password."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"Change password failed for user {await manager.GetUserIdAsync(user)}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task AddDupeUserNameFails() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var username = "AddDupeUserNameFails" + Guid.NewGuid(); + var user = CreateTestUser(username, useNamePrefixAsUserName: true); + var user2 = CreateTestUser(username, useNamePrefixAsUserName: true); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), _errorDescriber.DuplicateUserName(username)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task AddDupeEmailAllowedByDefault() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(email: "yup@yup.com"); + var user2 = CreateTestUser(email: "yup@yup.com"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user2, await manager.GetEmailAsync(user))); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task AddDupeEmailFailsWhenUniqueEmailRequired() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.Options.User.RequireUniqueEmail = true; + var user = CreateTestUser(email: "FooUser@yup.com"); + var user2 = CreateTestUser(email: "FooUser@yup.com"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user2), _errorDescriber.DuplicateEmail("FooUser@yup.com")); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task UpdateSecurityStampActuallyChanges() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + Assert.Null(await manager.GetSecurityStampAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task AddDupeLoginFails() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + var login = new UserLoginInfo("Provider", "key", "display"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); + var result = await manager.AddLoginAsync(user, login); + IdentityResultAssert.IsFailure(result, _errorDescriber.LoginAlreadyAssociated()); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"AddLogin for user {await manager.GetUserIdAsync(user)} failed because it was already assocated with another user."); + } + + // Email tests + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanFindByEmail() + { + if (ShouldSkipDbTests()) + { + return; + } + var email = "foouser@test.com"; + var manager = CreateManager(); + var user = CreateTestUser(email: email); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var fetch = await manager.FindByEmailAsync(email); + Assert.Equal(user, fetch); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanFindUsersViaUserQuerable() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + if (mgr.SupportsQueryableUsers) + { + var users = GenerateUsers("CanFindUsersViaUserQuerable", 4); + foreach (var u in users) + { + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(u)); + } + Assert.Equal(users.Count, mgr.Users.Count(UserNameStartsWithPredicate("CanFindUsersViaUserQuerable"))); + Assert.Null(mgr.Users.FirstOrDefault(UserNameEqualsPredicate("bogus"))); + } + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ConfirmEmailFalseByDefaultTest() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + } + + private class StaticTokenProvider : IUserTwoFactorTokenProvider + { + public async Task GenerateAsync(string purpose, UserManager manager, TUser user) + { + return MakeToken(purpose, await manager.GetUserIdAsync(user)); + } + + public async Task ValidateAsync(string purpose, string token, UserManager manager, TUser user) + { + return token == MakeToken(purpose, await manager.GetUserIdAsync(user)); + } + + public Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user) + { + return Task.FromResult(true); + } + + private static string MakeToken(string purpose, string userId) + { + return string.Join(":", userId, purpose, "ImmaToken"); + } + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanResetPasswordWithStaticTokenProvider() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.RegisterTokenProvider("Static", new StaticTokenProvider()); + manager.Options.Tokens.PasswordResetTokenProvider = "Static"; + var user = CreateTestUser(); + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.NotNull(stamp); + var token = await manager.GeneratePasswordResetTokenAsync(user); + Assert.NotNull(token); + var userId = await manager.GetUserIdAsync(user); + IdentityResultAssert.IsSuccess(await manager.ResetPasswordAsync(user, token, newPassword)); + Assert.False(await manager.CheckPasswordAsync(user, password)); + Assert.True(await manager.CheckPasswordAsync(user, newPassword)); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task PasswordValidatorCanBlockResetPasswordWithStaticTokenProvider() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.RegisterTokenProvider("Static", new StaticTokenProvider()); + manager.Options.Tokens.PasswordResetTokenProvider = "Static"; + var user = CreateTestUser(); + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.NotNull(stamp); + var token = await manager.GeneratePasswordResetTokenAsync(user); + Assert.NotNull(token); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, token, newPassword), + AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"User {await manager.GetUserIdAsync(user)} password validation failed: {AlwaysBadValidator.ErrorMessage.Code}."); + Assert.True(await manager.CheckPasswordAsync(user, password)); + Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ResetPasswordWithStaticTokenProviderFailsWithWrongToken() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.RegisterTokenProvider("Static", new StaticTokenProvider()); + manager.Options.Tokens.PasswordResetTokenProvider = "Static"; + var user = CreateTestUser(); + const string password = "password"; + const string newPassword = "newpassword"; + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.NotNull(stamp); + IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, "bogus", newPassword), "Invalid token."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ResetPassword for user { await manager.GetUserIdAsync(user)}."); + Assert.True(await manager.CheckPasswordAsync(user, password)); + Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanGenerateAndVerifyUserTokenWithStaticTokenProvider() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.RegisterTokenProvider("Static", new StaticTokenProvider()); + var user = CreateTestUser(); + var user2 = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); + var userId = await manager.GetUserIdAsync(user); + var token = await manager.GenerateUserTokenAsync(user, "Static", "test"); + + Assert.True(await manager.VerifyUserTokenAsync(user, "Static", "test", token)); + + Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test2", token)); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test2 for user { await manager.GetUserIdAsync(user)}."); + + Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test", token + "a")); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test for user { await manager.GetUserIdAsync(user)}."); + + Assert.False(await manager.VerifyUserTokenAsync(user2, "Static", "test", token)); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: test for user { await manager.GetUserIdAsync(user2)}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanConfirmEmailWithStaticToken() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.RegisterTokenProvider("Static", new StaticTokenProvider()); + manager.Options.Tokens.EmailConfirmationTokenProvider = "Static"; + var user = CreateTestUser(); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var token = await manager.GenerateEmailConfirmationTokenAsync(user); + Assert.NotNull(token); + var userId = await manager.GetUserIdAsync(user); + IdentityResultAssert.IsSuccess(await manager.ConfirmEmailAsync(user, token)); + Assert.True(await manager.IsEmailConfirmedAsync(user)); + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ConfirmEmailWithStaticTokenFailsWithWrongToken() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + manager.RegisterTokenProvider("Static", new StaticTokenProvider()); + manager.Options.Tokens.EmailConfirmationTokenProvider = "Static"; + var user = CreateTestUser(); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, "bogus"), "Invalid token."); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user { await manager.GetUserIdAsync(user)}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ConfirmTokenFailsAfterPasswordChange() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(namePrefix: "Test"); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); + var token = await manager.GenerateEmailConfirmationTokenAsync(user); + Assert.NotNull(token); + IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, "password", "newpassword")); + IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, token), "Invalid token."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user { await manager.GetUserIdAsync(user)}."); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + } + + // Lockout tests + + /// + /// Test. + /// + /// Task + [Fact] + public async Task SingleFailureLockout() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.MaxFailedAccessAttempts = 0; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.True(await mgr.IsLockedOutAsync(user)); + Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User {await mgr.GetUserIdAsync(user)} is locked out."); + + Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task TwoFailureLockout() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.MaxFailedAccessAttempts = 2; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.True(await mgr.IsLockedOutAsync(user)); + Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User {await mgr.GetUserIdAsync(user)} is locked out."); + Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ResetAccessCountPreventsLockout() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.MaxFailedAccessAttempts = 2; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.ResetAccessFailedCountAsync(user)); + Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanEnableLockoutManuallyAndLockout() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + mgr.Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(1); + mgr.Options.Lockout.AllowedForNewUsers = false; + mgr.Options.Lockout.MaxFailedAccessAttempts = 2; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.False(await mgr.GetLockoutEnabledAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEnabledAsync(user, true)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + Assert.True(await mgr.IsLockedOutAsync(user)); + Assert.True(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); + IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"User {await mgr.GetUserIdAsync(user)} is locked out."); + Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task UserNotLockedOutWithNullDateTimeAndIsSetToNullDate() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset())); + Assert.False(await mgr.IsLockedOutAsync(user)); + Assert.Equal(new DateTimeOffset(), await mgr.GetLockoutEndDateAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task LockoutFailsIfNotEnabled() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + mgr.Options.Lockout.AllowedForNewUsers = false; + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.False(await mgr.GetLockoutEnabledAsync(user)); + IdentityResultAssert.IsFailure(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset()), + "Lockout is not enabled for this user."); + IdentityResultAssert.VerifyLogMessage(mgr.Logger, $"Lockout for user {await mgr.GetUserIdAsync(user)} failed because lockout is not enabled for this user."); + Assert.False(await mgr.IsLockedOutAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task LockoutEndToUtcNowMinus1SecInUserShouldNotBeLockedOut() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + var user = CreateTestUser(lockoutEnd: DateTimeOffset.UtcNow.AddSeconds(-1)); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.False(await mgr.IsLockedOutAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task LockoutEndToUtcNowSubOneSecondWithManagerShouldNotBeLockedOut() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddSeconds(-1))); + Assert.False(await mgr.IsLockedOutAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task LockoutEndToUtcNowPlus5ShouldBeLockedOut() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + var lockoutEnd = DateTimeOffset.UtcNow.AddMinutes(5); + var user = CreateTestUser(lockoutEnd: lockoutEnd); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + Assert.True(await mgr.IsLockedOutAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task UserLockedOutWithDateTimeLocalKindNowPlus30() + { + if (ShouldSkipDbTests()) + { + return; + } + var mgr = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await mgr.CreateAsync(user)); + Assert.True(await mgr.GetLockoutEnabledAsync(user)); + var lockoutEnd = new DateTimeOffset(DateTime.Now.AddMinutes(30).ToLocalTime()); + IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, lockoutEnd)); + Assert.True(await mgr.IsLockedOutAsync(user)); + var end = await mgr.GetLockoutEndDateAsync(user); + Assert.Equal(lockoutEnd, end); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task SetPhoneNumberTest() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(phoneNumber: "123-456-7890"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.Equal("123-456-7890", await manager.GetPhoneNumberAsync(user)); + IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); + Assert.Equal("111-111-1111", await manager.GetPhoneNumberAsync(user)); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanChangePhoneNumber() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(phoneNumber: "123-456-7890"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); + IdentityResultAssert.IsSuccess(await manager.ChangePhoneNumberAsync(user, "111-111-1111", token1)); + Assert.True(await manager.IsPhoneNumberConfirmedAsync(user)); + Assert.Equal("111-111-1111", await manager.GetPhoneNumberAsync(user)); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ChangePhoneNumberFailsWithWrongToken() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(phoneNumber: "123-456-7890"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "111-111-1111", "bogus"), + "Invalid token."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:111-111-1111 for user { await manager.GetUserIdAsync(user)}."); + Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); + Assert.Equal("123-456-7890", await manager.GetPhoneNumberAsync(user)); + Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ChangePhoneNumberFailsWithWrongPhoneNumber() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(phoneNumber: "123-456-7890"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); + IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "bogus", token1), + "Invalid token."); + Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); + Assert.Equal("123-456-7890", await manager.GetPhoneNumberAsync(user)); + Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanVerifyPhoneNumber() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + const string num1 = "111-123-4567"; + const string num2 = "111-111-1111"; + var userId = await manager.GetUserIdAsync(user); + var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, num1); + + var token2 = await manager.GenerateChangePhoneNumberTokenAsync(user, num2); + Assert.NotEqual(token1, token2); + Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num1)); + Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num2)); + Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num1)); + Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num2)); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangePhoneNumber:111-123-4567 for user {await manager.GetUserIdAsync(user)}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanChangeEmail() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser("foouser"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + var newEmail = await manager.GetUserNameAsync(user) + "@en.vec"; + var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail); + IdentityResultAssert.IsSuccess(await manager.ChangeEmailAsync(user, newEmail, token1)); + Assert.True(await manager.IsEmailConfirmedAsync(user)); + Assert.Equal(await manager.GetEmailAsync(user), newEmail); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanChangeEmailWithDifferentTokenProvider() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(context: null, services: null, + configureServices: s => s.Configure( + o => o.Tokens.ProviderMap["NewProvider2"] = new TokenProviderDescriptor(typeof(EmailTokenProvider)))); + manager.Options.Tokens.ChangeEmailTokenProvider = "NewProvider2"; + var user = CreateTestUser("foouser"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + var newEmail = await manager.GetUserNameAsync(user) + "@en.vec"; + var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail); + IdentityResultAssert.IsSuccess(await manager.ChangeEmailAsync(user, newEmail, token1)); + Assert.True(await manager.IsEmailConfirmedAsync(user)); + Assert.Equal(await manager.GetEmailAsync(user), newEmail); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ChangeEmailTokensFailsAfterEmailChanged() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser("foouser"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + var newEmail = await manager.GetUserNameAsync(user) + "@en.vec"; + var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail); + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, "another@email.com")); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, newEmail, token1)); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + Assert.Equal("another@email.com", await manager.GetEmailAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ChangeEmailFailsWithWrongToken() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser("foouser"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); + string oldEmail = email; + Assert.False(await manager.IsEmailConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "whatevah@foo.boop", "bogus"), + "Invalid token."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangeEmail:whatevah@foo.boop for user { await manager.GetUserIdAsync(user)}."); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + Assert.Equal(await manager.GetEmailAsync(user), oldEmail); + Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task ChangeEmailFailsWithEmail() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser("foouser"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); + string oldEmail = email; + Assert.False(await manager.IsEmailConfirmedAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + var token1 = await manager.GenerateChangeEmailTokenAsync(user, "forgot@alrea.dy"); + IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "oops@foo.boop", token1), + "Invalid token."); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyUserTokenAsync() failed with purpose: ChangeEmail:oops@foo.boop for user { await manager.GetUserIdAsync(user)}."); + Assert.False(await manager.IsEmailConfirmedAsync(user)); + Assert.Equal(await manager.GetEmailAsync(user), oldEmail); + Assert.Equal(stamp, await manager.GetSecurityStampAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task EmailFactorFailsAfterSecurityStampChangeTest() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + string factorId = "Email"; //default + var user = CreateTestUser("foouser"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var email = await manager.GetUserNameAsync(user) + "@diddly.bop"; + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, email)); + var token = await manager.GenerateEmailConfirmationTokenAsync(user); + await manager.ConfirmEmailAsync(user, token); + + var stamp = await manager.GetSecurityStampAsync(user); + Assert.NotNull(stamp); + token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + Assert.NotNull(token); + IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task EnableTwoFactorChangesSecurityStamp() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.NotNull(stamp); + IdentityResultAssert.IsSuccess(await manager.SetTwoFactorEnabledAsync(user, true)); + Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); + Assert.True(await manager.GetTwoFactorEnabledAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task GenerateTwoFactorWithUnknownFactorProviderWillThrow() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + const string error = "No IUserTokenProvider named 'bogus' is registered."; + var ex = await + Assert.ThrowsAsync( + () => manager.GenerateTwoFactorTokenAsync(user, "bogus")); + Assert.Equal(error, ex.Message); + ex = await Assert.ThrowsAsync( + () => manager.VerifyTwoFactorTokenAsync(user, "bogus", "bogus")); + Assert.Equal(error, ex.Message); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task GetValidTwoFactorTestEmptyWithNoProviders() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); + Assert.True(!factors.Any()); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanGetSetUpdateAndRemoveUserToken() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.Null(await manager.GetAuthenticationTokenAsync(user, "provider", "name")); + IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "provider", "name", "value")); + Assert.Equal("value", await manager.GetAuthenticationTokenAsync(user, "provider", "name")); + + IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "provider", "name", "value2")); + Assert.Equal("value2", await manager.GetAuthenticationTokenAsync(user, "provider", "name")); + + IdentityResultAssert.IsSuccess(await manager.RemoveAuthenticationTokenAsync(user, "whatevs", "name")); + Assert.Equal("value2", await manager.GetAuthenticationTokenAsync(user, "provider", "name")); + + IdentityResultAssert.IsSuccess(await manager.RemoveAuthenticationTokenAsync(user, "provider", "name")); + Assert.Null(await manager.GetAuthenticationTokenAsync(user, "provider", "name")); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanRedeemRecoveryCodeOnlyOnce() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + + var numCodes = 15; + var newCodes = await manager.GenerateNewTwoFactorRecoveryCodesAsync(user, numCodes); + Assert.Equal(numCodes, newCodes.Count()); + + foreach (var code in newCodes) + { + IdentityResultAssert.IsSuccess(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); + IdentityResultAssert.IsFailure(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); + } + // One last time to be sure + foreach (var code in newCodes) + { + IdentityResultAssert.IsFailure(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); + } + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task RecoveryCodesInvalidAfterReplace() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + + var numCodes = 15; + var newCodes = await manager.GenerateNewTwoFactorRecoveryCodesAsync(user, numCodes); + Assert.Equal(numCodes, newCodes.Count()); + var realCodes = await manager.GenerateNewTwoFactorRecoveryCodesAsync(user, numCodes); + Assert.Equal(numCodes, realCodes.Count()); + + foreach (var code in newCodes) + { + IdentityResultAssert.IsFailure(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); + } + + foreach (var code in realCodes) + { + IdentityResultAssert.IsSuccess(await manager.RedeemTwoFactorRecoveryCodeAsync(user, code)); + } + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanGetValidTwoFactor() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var userId = await manager.GetUserIdAsync(user); + var factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); + Assert.False(factors.Any()); + IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); + var token = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); + IdentityResultAssert.IsSuccess(await manager.ChangePhoneNumberAsync(user, "111-111-1111", token)); + await manager.UpdateAsync(user); + factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); + Assert.Equal(1, factors.Count()); + Assert.Equal("Phone", factors[0]); + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, "test@test.com")); + token = await manager.GenerateEmailConfirmationTokenAsync(user); + await manager.ConfirmEmailAsync(user, token); + factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); + Assert.Equal(2, factors.Count()); + IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); + factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); + Assert.Equal(1, factors.Count()); + Assert.Equal("Phone", factors[0]); + IdentityResultAssert.IsSuccess(await manager.ResetAuthenticatorKeyAsync(user)); + factors = await manager.GetValidTwoFactorProvidersAsync(user); + Assert.NotNull(factors); + Assert.Equal(2, factors.Count()); + Assert.Equal("Authenticator", factors[1]); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task PhoneFactorFailsAfterSecurityStampChangeTest() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var factorId = "Phone"; // default + var user = CreateTestUser(phoneNumber: "4251234567"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var stamp = await manager.GetSecurityStampAsync(user); + Assert.NotNull(stamp); + var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); + Assert.NotNull(token); + IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + Assert.False(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task VerifyTokenFromWrongTokenProviderFails() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(phoneNumber: "4251234567"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + var token = await manager.GenerateTwoFactorTokenAsync(user, "Phone"); + Assert.NotNull(token); + Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "Email", token)); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task VerifyWithWrongSmsTokenFails() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + var user = CreateTestUser(phoneNumber: "4251234567"); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "Phone", "bogus")); + IdentityResultAssert.VerifyLogMessage(manager.Logger, $"VerifyTwoFactorTokenAsync() failed for user {await manager.GetUserIdAsync(user)}."); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task NullableDateTimeOperationTest() + { + if (ShouldSkipDbTests()) + { + return; + } + var userMgr = CreateManager(); + var user = CreateTestUser(lockoutEnabled: true); + IdentityResultAssert.IsSuccess(await userMgr.CreateAsync(user)); + + Assert.Null(await userMgr.GetLockoutEndDateAsync(user)); + + // set LockoutDateEndDate to null + await userMgr.SetLockoutEndDateAsync(user, null); + Assert.Null(await userMgr.GetLockoutEndDateAsync(user)); + + // set to a valid value + await userMgr.SetLockoutEndDateAsync(user, DateTimeOffset.Parse("01/01/2014")); + Assert.Equal(DateTimeOffset.Parse("01/01/2014"), await userMgr.GetLockoutEndDateAsync(user)); + } + + /// + /// Test. + /// + /// Task + [Fact] + public async Task CanGetUsersWithClaims() + { + if (ShouldSkipDbTests()) + { + return; + } + var manager = CreateManager(); + + for (int i = 0; i < 6; i++) + { + var user = CreateTestUser(); + IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + + if ((i % 2) == 0) + { + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("foo", "bar"))); + } + } + + Assert.Equal(3, (await manager.GetUsersForClaimAsync(new Claim("foo", "bar"))).Count); + + Assert.Equal(0, (await manager.GetUsersForClaimAsync(new Claim("123", "456"))).Count); + } + + /// + /// Generate count users with a name prefix. + /// + /// + /// + /// + protected List GenerateUsers(string userNamePrefix, int count) + { + var users = new List(count); + for (var i = 0; i < count; i++) + { + users.Add(CreateTestUser(userNamePrefix + i)); + } + return users; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity/Base32.cs b/src/Microsoft.AspNetCore.Identity/Base32.cs deleted file mode 100644 index 579a58feac..0000000000 --- a/src/Microsoft.AspNetCore.Identity/Base32.cs +++ /dev/null @@ -1,119 +0,0 @@ -// 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.Text; - -namespace Microsoft.AspNetCore.Identity -{ - // See http://tools.ietf.org/html/rfc3548#section-5 - internal static class Base32 - { - private static readonly string _base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - - public static string ToBase32(byte[] input) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - - StringBuilder sb = new StringBuilder(); - for (int offset = 0; offset < input.Length;) - { - byte a, b, c, d, e, f, g, h; - int numCharsToOutput = GetNextGroup(input, ref offset, out a, out b, out c, out d, out e, out f, out g, out h); - - sb.Append((numCharsToOutput >= 1) ? _base32Chars[a] : '='); - sb.Append((numCharsToOutput >= 2) ? _base32Chars[b] : '='); - sb.Append((numCharsToOutput >= 3) ? _base32Chars[c] : '='); - sb.Append((numCharsToOutput >= 4) ? _base32Chars[d] : '='); - sb.Append((numCharsToOutput >= 5) ? _base32Chars[e] : '='); - sb.Append((numCharsToOutput >= 6) ? _base32Chars[f] : '='); - sb.Append((numCharsToOutput >= 7) ? _base32Chars[g] : '='); - sb.Append((numCharsToOutput >= 8) ? _base32Chars[h] : '='); - } - - return sb.ToString(); - } - - public static byte[] FromBase32(string input) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - input = input.TrimEnd('=').ToUpperInvariant(); - if (input.Length == 0) - { - return new byte[0]; - } - - var output = new byte[input.Length * 5 / 8]; - var bitIndex = 0; - var inputIndex = 0; - var outputBits = 0; - var outputIndex = 0; - while (outputIndex < output.Length) - { - var byteIndex = _base32Chars.IndexOf(input[inputIndex]); - if (byteIndex < 0) - { - throw new FormatException(); - } - - var bits = Math.Min(5 - bitIndex, 8 - outputBits); - output[outputIndex] <<= bits; - output[outputIndex] |= (byte)(byteIndex >> (5 - (bitIndex + bits))); - - bitIndex += bits; - if (bitIndex >= 5) - { - inputIndex++; - bitIndex = 0; - } - - outputBits += bits; - if (outputBits >= 8) - { - outputIndex++; - outputBits = 0; - } - } - return output; - } - - // returns the number of bytes that were output - private static int GetNextGroup(byte[] input, ref int offset, out byte a, out byte b, out byte c, out byte d, out byte e, out byte f, out byte g, out byte h) - { - uint b1, b2, b3, b4, b5; - - int retVal; - switch (offset - input.Length) - { - case 1: retVal = 2; break; - case 2: retVal = 4; break; - case 3: retVal = 5; break; - case 4: retVal = 7; break; - default: retVal = 8; break; - } - - b1 = (offset < input.Length) ? input[offset++] : 0U; - b2 = (offset < input.Length) ? input[offset++] : 0U; - b3 = (offset < input.Length) ? input[offset++] : 0U; - b4 = (offset < input.Length) ? input[offset++] : 0U; - b5 = (offset < input.Length) ? input[offset++] : 0U; - - a = (byte)(b1 >> 3); - b = (byte)(((b1 & 0x07) << 2) | (b2 >> 6)); - c = (byte)((b2 >> 1) & 0x1f); - d = (byte)(((b2 & 0x01) << 4) | (b3 >> 4)); - e = (byte)(((b3 & 0x0f) << 1) | (b4 >> 7)); - f = (byte)((b4 >> 2) & 0x1f); - g = (byte)(((b4 & 0x3) << 3) | (b5 >> 5)); - h = (byte)(b5 & 0x1f); - - return retVal; - } - } -} diff --git a/src/Microsoft.AspNetCore.Identity/IdentityBuilderExtensions.cs b/src/Microsoft.AspNetCore.Identity/IdentityBuilderExtensions.cs new file mode 100644 index 0000000000..6fa60725a5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity/IdentityBuilderExtensions.cs @@ -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 System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Identity +{ + /// + /// Helper functions for configuring identity services. + /// + public static class IdentityBuilderExtensions + { + /// + /// Adds the default token providers used to generate tokens for reset passwords, change email + /// and change telephone number operations, and for two factor authentication token generation. + /// + /// The current instance. + /// The current instance. + public static IdentityBuilder AddDefaultTokenProviders(this IdentityBuilder builder) + { + var userType = builder.UserType; + var dataProtectionProviderType = typeof(DataProtectorTokenProvider<>).MakeGenericType(userType); + var phoneNumberProviderType = typeof(PhoneNumberTokenProvider<>).MakeGenericType(userType); + var emailTokenProviderType = typeof(EmailTokenProvider<>).MakeGenericType(userType); + var authenticatorProviderType = typeof(AuthenticatorTokenProvider<>).MakeGenericType(userType); + return builder.AddTokenProvider(TokenOptions.DefaultProvider, dataProtectionProviderType) + .AddTokenProvider(TokenOptions.DefaultEmailProvider, emailTokenProviderType) + .AddTokenProvider(TokenOptions.DefaultPhoneProvider, phoneNumberProviderType) + .AddTokenProvider(TokenOptions.DefaultAuthenticatorProvider, authenticatorProviderType); + } + + /// + /// Adds a for the . + /// + /// The type of the sign in manager to add. + /// The current instance. + /// The current instance. + public static IdentityBuilder AddSignInManager(this IdentityBuilder builder) where TSignInManager : class + { + var managerType = typeof(SignInManager<>).MakeGenericType(builder.UserType); + + var customType = typeof(TSignInManager); + if (!managerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo())) + { + throw new InvalidOperationException(Resources.FormatInvalidManagerType(customType.Name, "SignInManager", builder.UserType.Name)); + } + if (managerType != customType) + { + builder.Services.AddScoped(typeof(TSignInManager), services => services.GetRequiredService(managerType)); + } + builder.Services.AddScoped(managerType, typeof(TSignInManager)); + return builder; + } + } +} diff --git a/src/Microsoft.AspNetCore.Identity/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Identity/Properties/Resources.Designer.cs index 35f03f932a..afd02fc138 100644 --- a/src/Microsoft.AspNetCore.Identity/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Identity/Properties/Resources.Designer.cs @@ -5,58 +5,52 @@ namespace Microsoft.AspNetCore.Identity using System.Reflection; using System.Resources; - internal static class AspNetIdentityResources + internal static class Resources { private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.AspNetCore.Identity.Resources", typeof(AspNetIdentityResources).GetTypeInfo().Assembly); + = new ResourceManager("Microsoft.AspNetCore.Identity.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// Type {0} must derive from {1}<{2}>. /// internal static string InvalidManagerType { - get { return GetString("InvalidManagerType"); } + get => GetString("InvalidManagerType"); } /// /// Type {0} must derive from {1}<{2}>. /// internal static string FormatInvalidManagerType(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("InvalidManagerType"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidManagerType"), p0, p1, p2); /// /// The provided PasswordHasherCompatibilityMode is invalid. /// internal static string InvalidPasswordHasherCompatibilityMode { - get { return GetString("InvalidPasswordHasherCompatibilityMode"); } + get => GetString("InvalidPasswordHasherCompatibilityMode"); } /// /// The provided PasswordHasherCompatibilityMode is invalid. /// internal static string FormatInvalidPasswordHasherCompatibilityMode() - { - return GetString("InvalidPasswordHasherCompatibilityMode"); - } + => GetString("InvalidPasswordHasherCompatibilityMode"); /// /// The iteration count must be a positive integer. /// internal static string InvalidPasswordHasherIterationCount { - get { return GetString("InvalidPasswordHasherIterationCount"); } + get => GetString("InvalidPasswordHasherIterationCount"); } /// /// The iteration count must be a positive integer. /// internal static string FormatInvalidPasswordHasherIterationCount() - { - return GetString("InvalidPasswordHasherIterationCount"); - } + => GetString("InvalidPasswordHasherIterationCount"); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.AspNetCore.Identity/AuthenticatorTokenProvider.cs b/src/Microsoft.Extensions.Identity.Core/AuthenticatorTokenProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/AuthenticatorTokenProvider.cs rename to src/Microsoft.Extensions.Identity.Core/AuthenticatorTokenProvider.cs diff --git a/src/Microsoft.AspNetCore.Identity/EmailTokenProvider.cs b/src/Microsoft.Extensions.Identity.Core/EmailTokenProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/EmailTokenProvider.cs rename to src/Microsoft.Extensions.Identity.Core/EmailTokenProvider.cs diff --git a/src/Microsoft.AspNetCore.Identity/IdentityBuilder.cs b/src/Microsoft.Extensions.Identity.Core/IdentityBuilder.cs similarity index 60% rename from src/Microsoft.AspNetCore.Identity/IdentityBuilder.cs rename to src/Microsoft.Extensions.Identity.Core/IdentityBuilder.cs index 486958e5cc..7146ef4e0c 100644 --- a/src/Microsoft.AspNetCore.Identity/IdentityBuilder.cs +++ b/src/Microsoft.Extensions.Identity.Core/IdentityBuilder.cs @@ -5,6 +5,8 @@ using System; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Identity.Core; namespace Microsoft.AspNetCore.Identity { @@ -17,15 +19,22 @@ namespace Microsoft.AspNetCore.Identity /// Creates a new instance of . /// /// The to use for the users. - /// The to use for the roles. /// The to attach to. - public IdentityBuilder(Type user, Type role, IServiceCollection services) + public IdentityBuilder(Type user, IServiceCollection services) { UserType = user; - RoleType = role; Services = services; } + /// + /// Creates a new instance of . + /// + /// The to use for the users. + /// The to use for the roles. + /// The to attach to. + public IdentityBuilder(Type user, Type role, IServiceCollection services) : this(user, services) + => RoleType = role; + /// /// Gets the used for users. /// @@ -60,32 +69,18 @@ namespace Microsoft.AspNetCore.Identity /// /// Adds an for the . /// - /// The user validator type. + /// The user validator type. /// The current instance. - public virtual IdentityBuilder AddUserValidator() where T : class - { - return AddScoped(typeof(IUserValidator<>).MakeGenericType(UserType), typeof(T)); - } - - /// - /// Adds an for the . - /// - /// The role validator type. - /// The current instance. - public virtual IdentityBuilder AddRoleValidator() where T : class - { - return AddScoped(typeof(IRoleValidator<>).MakeGenericType(RoleType), typeof(T)); - } + public virtual IdentityBuilder AddUserValidator() where TUser : class + => AddScoped(typeof(IUserValidator<>).MakeGenericType(UserType), typeof(TUser)); /// /// Adds an for the . /// - /// The type of the claims principal factory. + /// The type of the claims principal factory. /// The current instance. - public virtual IdentityBuilder AddClaimsPrincipalFactory() where T : class - { - return AddScoped(typeof(IUserClaimsPrincipalFactory<>).MakeGenericType(UserType), typeof(T)); - } + public virtual IdentityBuilder AddClaimsPrincipalFactory() where TUser : class + => AddScoped(typeof(IUserClaimsPrincipalFactory<>).MakeGenericType(UserType), typeof(TUser)); /// /// Adds an . @@ -101,32 +96,18 @@ namespace Microsoft.AspNetCore.Identity /// /// Adds an for the . /// - /// The user type whose password will be validated. + /// The user type whose password will be validated. /// The current instance. - public virtual IdentityBuilder AddPasswordValidator() where T : class - { - return AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(T)); - } + public virtual IdentityBuilder AddPasswordValidator() where TUser : class + => AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(TUser)); /// /// Adds an for the . /// - /// The user type held in the store. + /// The user type held in the store. /// The current instance. - public virtual IdentityBuilder AddUserStore() where T : class - { - return AddScoped(typeof(IUserStore<>).MakeGenericType(UserType), typeof(T)); - } - - /// - /// Adds a for the . - /// - /// The role type held in the store. - /// The current instance. - public virtual IdentityBuilder AddRoleStore() where T : class - { - return AddScoped(typeof(IRoleStore<>).MakeGenericType(RoleType), typeof(T)); - } + public virtual IdentityBuilder AddUserStore() where TUser : class + => AddScoped(typeof(IUserStore<>).MakeGenericType(UserType), typeof(TUser)); /// /// Adds a token provider. @@ -135,9 +116,7 @@ namespace Microsoft.AspNetCore.Identity /// The name of the provider to add. /// The current instance. public virtual IdentityBuilder AddTokenProvider(string providerName) where TProvider : class - { - return AddTokenProvider(providerName, typeof(TProvider)); - } + => AddTokenProvider(providerName, typeof(TProvider)); /// /// Adds a token provider for the . @@ -149,7 +128,7 @@ namespace Microsoft.AspNetCore.Identity { if (!typeof(IUserTwoFactorTokenProvider<>).MakeGenericType(UserType).GetTypeInfo().IsAssignableFrom(provider.GetTypeInfo())) { - throw new InvalidOperationException(AspNetIdentityResources.FormatInvalidManagerType(provider.Name, "IUserTokenProvider", UserType.Name)); + throw new InvalidOperationException(Resources.FormatInvalidManagerType(provider.Name, "IUserTokenProvider", UserType.Name)); } Services.Configure(options => { @@ -159,23 +138,6 @@ namespace Microsoft.AspNetCore.Identity return this; } - /// - /// Adds the default token providers used to generate tokens for reset passwords, change email - /// and change telephone number operations, and for two factor authentication token generation. - /// - /// The current instance. - public virtual IdentityBuilder AddDefaultTokenProviders() - { - var dataProtectionProviderType = typeof(DataProtectorTokenProvider<>).MakeGenericType(UserType); - var phoneNumberProviderType = typeof(PhoneNumberTokenProvider<>).MakeGenericType(UserType); - var emailTokenProviderType = typeof(EmailTokenProvider<>).MakeGenericType(UserType); - var authenticatorProviderType = typeof(AuthenticatorTokenProvider<>).MakeGenericType(UserType); - return AddTokenProvider(TokenOptions.DefaultProvider, dataProtectionProviderType) - .AddTokenProvider(TokenOptions.DefaultEmailProvider, emailTokenProviderType) - .AddTokenProvider(TokenOptions.DefaultPhoneProvider, phoneNumberProviderType) - .AddTokenProvider(TokenOptions.DefaultAuthenticatorProvider, authenticatorProviderType); - } - /// /// Adds a for the . /// @@ -185,15 +147,60 @@ namespace Microsoft.AspNetCore.Identity { var userManagerType = typeof(UserManager<>).MakeGenericType(UserType); var customType = typeof(TUserManager); - if (userManagerType == customType || - !userManagerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo())) + if (!userManagerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo())) { - throw new InvalidOperationException(AspNetIdentityResources.FormatInvalidManagerType(customType.Name, "UserManager", UserType.Name)); + throw new InvalidOperationException(Resources.FormatInvalidManagerType(customType.Name, "UserManager", UserType.Name)); + } + if (userManagerType != customType) + { + Services.AddScoped(customType, services => services.GetRequiredService(userManagerType)); } - Services.AddScoped(customType, services => services.GetRequiredService(userManagerType)); return AddScoped(userManagerType, customType); } + /// + /// Adds Role related services for TRole, including IRoleStore, IRoleValidator, and RoleManager. + /// + /// The role type. + /// The current instance. + public virtual IdentityBuilder AddRoles() where TRole : class + { + RoleType = typeof(TRole); + AddRoleStore(); + AddRoleValidator>(); + Services.TryAddScoped, RoleManager>(); + return this; + } + + /// + /// Adds an for the . + /// + /// The role validator type. + /// The current instance. + public virtual IdentityBuilder AddRoleValidator() where TRole : class + { + if (RoleType == null) + { + throw new InvalidOperationException(Resources.NoRoleType); + } + return AddScoped(typeof(IRoleValidator<>).MakeGenericType(RoleType), typeof(TRole)); + } + + + /// + /// Adds a for the . + /// + /// The role type held in the store. + /// The current instance. + public virtual IdentityBuilder AddRoleStore() where TRole : class + { + if (RoleType == null) + { + throw new InvalidOperationException(Resources.NoRoleType); + } + return AddScoped(typeof(IRoleStore<>).MakeGenericType(RoleType), typeof(TRole)); + } + /// /// Adds a for the . /// @@ -201,33 +208,21 @@ namespace Microsoft.AspNetCore.Identity /// The current instance. public virtual IdentityBuilder AddRoleManager() where TRoleManager : class { + if (RoleType == null) + { + throw new InvalidOperationException(Resources.NoRoleType); + } var managerType = typeof(RoleManager<>).MakeGenericType(RoleType); var customType = typeof(TRoleManager); - if (managerType == customType || - !managerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo())) + if (!managerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo())) { - throw new InvalidOperationException(AspNetIdentityResources.FormatInvalidManagerType(customType.Name, "RoleManager", RoleType.Name)); + throw new InvalidOperationException(Resources.FormatInvalidManagerType(customType.Name, "RoleManager", RoleType.Name)); + } + if (managerType != customType) + { + Services.AddScoped(typeof(TRoleManager), services => services.GetRequiredService(managerType)); } - Services.AddScoped(typeof(TRoleManager), services => services.GetRequiredService(managerType)); return AddScoped(managerType, typeof(TRoleManager)); } - - /// - /// Adds a for the . - /// - /// The type of the sign in manager to add. - /// The current instance. - public virtual IdentityBuilder AddSignInManager() where TSignInManager : class - { - var managerType = typeof(SignInManager<>).MakeGenericType(UserType); - var customType = typeof(TSignInManager); - if (managerType == customType || - !managerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo())) - { - throw new InvalidOperationException(AspNetIdentityResources.FormatInvalidManagerType(customType.Name, "SignInManager", UserType.Name)); - } - Services.AddScoped(typeof(TSignInManager), services => services.GetRequiredService(managerType)); - return AddScoped(managerType, typeof(TSignInManager)); - } } } diff --git a/src/Microsoft.Extensions.Identity.Core/IdentityErrorDescriber.cs b/src/Microsoft.Extensions.Identity.Core/IdentityErrorDescriber.cs index dd6ca26b6d..fdedb4ed0a 100644 --- a/src/Microsoft.Extensions.Identity.Core/IdentityErrorDescriber.cs +++ b/src/Microsoft.Extensions.Identity.Core/IdentityErrorDescriber.cs @@ -1,6 +1,8 @@ // 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 Microsoft.Extensions.Identity.Core; + namespace Microsoft.AspNetCore.Identity { /// diff --git a/src/Microsoft.Extensions.Identity.Core/IdentityServiceCollectionExtensions.cs b/src/Microsoft.Extensions.Identity.Core/IdentityServiceCollectionExtensions.cs new file mode 100644 index 0000000000..7f9b941875 --- /dev/null +++ b/src/Microsoft.Extensions.Identity.Core/IdentityServiceCollectionExtensions.cs @@ -0,0 +1,47 @@ +// 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.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Contains extension methods to for configuring identity services. + /// + public static class IdentityServiceCollectionExtensions + { + /// + /// Adds and configures the identity system for the specified User and Role types. + /// + /// The type representing a User in the system. + /// The services available in the application. + /// An action to configure the . + /// An for creating and configuring the identity system. + public static IdentityBuilder AddIdentityCore(this IServiceCollection services, Action setupAction) + where TUser : class + { + // Services identity depends on + services.AddOptions().AddLogging(); + + // Services used by identity + services.TryAddScoped, UserValidator>(); + services.TryAddScoped, PasswordValidator>(); + services.TryAddScoped, PasswordHasher>(); + services.TryAddScoped(); + // No interface for the error describer so we can add errors without rev'ing the interface + services.TryAddScoped(); + services.TryAddScoped, UserClaimsPrincipalFactory>(); + services.TryAddScoped, UserManager>(); + + if (setupAction != null) + { + services.Configure(setupAction); + } + + return new IdentityBuilder(typeof(TUser), services); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.Identity.Core/Microsoft.Extensions.Identity.Core.csproj b/src/Microsoft.Extensions.Identity.Core/Microsoft.Extensions.Identity.Core.csproj index b910f1e7b2..bf3e00d25b 100644 --- a/src/Microsoft.Extensions.Identity.Core/Microsoft.Extensions.Identity.Core.csproj +++ b/src/Microsoft.Extensions.Identity.Core/Microsoft.Extensions.Identity.Core.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Microsoft.AspNetCore.Identity/PasswordHasher.cs b/src/Microsoft.Extensions.Identity.Core/PasswordHasher.cs similarity index 98% rename from src/Microsoft.AspNetCore.Identity/PasswordHasher.cs rename to src/Microsoft.Extensions.Identity.Core/PasswordHasher.cs index 0f861aa94e..ebc0ded3dc 100644 --- a/src/Microsoft.AspNetCore.Identity/PasswordHasher.cs +++ b/src/Microsoft.Extensions.Identity.Core/PasswordHasher.cs @@ -5,6 +5,7 @@ using System; using System.Runtime.CompilerServices; using System.Security.Cryptography; using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using Microsoft.Extensions.Identity.Core; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Identity @@ -53,12 +54,12 @@ namespace Microsoft.AspNetCore.Identity _iterCount = options.IterationCount; if (_iterCount < 1) { - throw new InvalidOperationException(AspNetIdentityResources.InvalidPasswordHasherIterationCount); + throw new InvalidOperationException(Resources.InvalidPasswordHasherIterationCount); } break; default: - throw new InvalidOperationException(AspNetIdentityResources.InvalidPasswordHasherCompatibilityMode); + throw new InvalidOperationException(Resources.InvalidPasswordHasherCompatibilityMode); } _rng = options.Rng; diff --git a/src/Microsoft.AspNetCore.Identity/PasswordHasherOptions.cs b/src/Microsoft.Extensions.Identity.Core/PasswordHasherOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/PasswordHasherOptions.cs rename to src/Microsoft.Extensions.Identity.Core/PasswordHasherOptions.cs diff --git a/src/Microsoft.AspNetCore.Identity/PasswordValidator.cs b/src/Microsoft.Extensions.Identity.Core/PasswordValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/PasswordValidator.cs rename to src/Microsoft.Extensions.Identity.Core/PasswordValidator.cs diff --git a/src/Microsoft.AspNetCore.Identity/PhoneNumberTokenProvider.cs b/src/Microsoft.Extensions.Identity.Core/PhoneNumberTokenProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/PhoneNumberTokenProvider.cs rename to src/Microsoft.Extensions.Identity.Core/PhoneNumberTokenProvider.cs diff --git a/src/Microsoft.Extensions.Identity.Core/Properties/Resources.Designer.cs b/src/Microsoft.Extensions.Identity.Core/Properties/Resources.Designer.cs index d39c98abad..5cef01d32d 100644 --- a/src/Microsoft.Extensions.Identity.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.Extensions.Identity.Core/Properties/Resources.Designer.cs @@ -1,5 +1,5 @@ // -namespace Microsoft.AspNetCore.Identity +namespace Microsoft.Extensions.Identity.Core { using System.Globalization; using System.Reflection; @@ -15,753 +15,673 @@ namespace Microsoft.AspNetCore.Identity /// internal static string ConcurrencyFailure { - get { return GetString("ConcurrencyFailure"); } + get => GetString("ConcurrencyFailure"); } /// /// Optimistic concurrency failure, object has been modified. /// internal static string FormatConcurrencyFailure() - { - return GetString("ConcurrencyFailure"); - } + => GetString("ConcurrencyFailure"); /// /// An unknown failure has occurred. /// internal static string DefaultError { - get { return GetString("DefaultError"); } + get => GetString("DefaultError"); } /// /// An unknown failure has occurred. /// internal static string FormatDefaultError() - { - return GetString("DefaultError"); - } + => GetString("DefaultError"); /// /// Email '{0}' is already taken. /// internal static string DuplicateEmail { - get { return GetString("DuplicateEmail"); } + get => GetString("DuplicateEmail"); } /// /// Email '{0}' is already taken. /// internal static string FormatDuplicateEmail(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateEmail"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("DuplicateEmail"), p0); /// /// Role name '{0}' is already taken. /// internal static string DuplicateRoleName { - get { return GetString("DuplicateRoleName"); } + get => GetString("DuplicateRoleName"); } /// /// Role name '{0}' is already taken. /// internal static string FormatDuplicateRoleName(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateRoleName"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("DuplicateRoleName"), p0); /// /// User name '{0}' is already taken. /// internal static string DuplicateUserName { - get { return GetString("DuplicateUserName"); } + get => GetString("DuplicateUserName"); } /// /// User name '{0}' is already taken. /// internal static string FormatDuplicateUserName(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateUserName"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("DuplicateUserName"), p0); /// /// Email '{0}' is invalid. /// internal static string InvalidEmail { - get { return GetString("InvalidEmail"); } + get => GetString("InvalidEmail"); } /// /// Email '{0}' is invalid. /// internal static string FormatInvalidEmail(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("InvalidEmail"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidEmail"), p0); /// /// Type {0} must derive from {1}<{2}>. /// internal static string InvalidManagerType { - get { return GetString("InvalidManagerType"); } + get => GetString("InvalidManagerType"); } /// /// Type {0} must derive from {1}<{2}>. /// internal static string FormatInvalidManagerType(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("InvalidManagerType"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidManagerType"), p0, p1, p2); /// /// The provided PasswordHasherCompatibilityMode is invalid. /// internal static string InvalidPasswordHasherCompatibilityMode { - get { return GetString("InvalidPasswordHasherCompatibilityMode"); } + get => GetString("InvalidPasswordHasherCompatibilityMode"); } /// /// The provided PasswordHasherCompatibilityMode is invalid. /// internal static string FormatInvalidPasswordHasherCompatibilityMode() - { - return GetString("InvalidPasswordHasherCompatibilityMode"); - } + => GetString("InvalidPasswordHasherCompatibilityMode"); /// /// The iteration count must be a positive integer. /// internal static string InvalidPasswordHasherIterationCount { - get { return GetString("InvalidPasswordHasherIterationCount"); } + get => GetString("InvalidPasswordHasherIterationCount"); } /// /// The iteration count must be a positive integer. /// internal static string FormatInvalidPasswordHasherIterationCount() - { - return GetString("InvalidPasswordHasherIterationCount"); - } + => GetString("InvalidPasswordHasherIterationCount"); /// /// Role name '{0}' is invalid. /// internal static string InvalidRoleName { - get { return GetString("InvalidRoleName"); } + get => GetString("InvalidRoleName"); } /// /// Role name '{0}' is invalid. /// internal static string FormatInvalidRoleName(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("InvalidRoleName"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidRoleName"), p0); /// /// Invalid token. /// internal static string InvalidToken { - get { return GetString("InvalidToken"); } + get => GetString("InvalidToken"); } /// /// Invalid token. /// internal static string FormatInvalidToken() - { - return GetString("InvalidToken"); - } + => GetString("InvalidToken"); /// /// User name '{0}' is invalid, can only contain letters or digits. /// internal static string InvalidUserName { - get { return GetString("InvalidUserName"); } + get => GetString("InvalidUserName"); } /// /// User name '{0}' is invalid, can only contain letters or digits. /// internal static string FormatInvalidUserName(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("InvalidUserName"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidUserName"), p0); /// /// A user with this login already exists. /// internal static string LoginAlreadyAssociated { - get { return GetString("LoginAlreadyAssociated"); } + get => GetString("LoginAlreadyAssociated"); } /// /// A user with this login already exists. /// internal static string FormatLoginAlreadyAssociated() - { - return GetString("LoginAlreadyAssociated"); - } + => GetString("LoginAlreadyAssociated"); /// /// AddIdentity must be called on the service collection. /// internal static string MustCallAddIdentity { - get { return GetString("MustCallAddIdentity"); } + get => GetString("MustCallAddIdentity"); } /// /// AddIdentity must be called on the service collection. /// internal static string FormatMustCallAddIdentity() - { - return GetString("MustCallAddIdentity"); - } + => GetString("MustCallAddIdentity"); /// /// No IUserTokenProvider named '{0}' is registered. /// internal static string NoTokenProvider { - get { return GetString("NoTokenProvider"); } + get => GetString("NoTokenProvider"); } /// /// No IUserTokenProvider named '{0}' is registered. /// internal static string FormatNoTokenProvider(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("NoTokenProvider"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("NoTokenProvider"), p0); /// /// User security stamp cannot be null. /// internal static string NullSecurityStamp { - get { return GetString("NullSecurityStamp"); } + get => GetString("NullSecurityStamp"); } /// /// User security stamp cannot be null. /// internal static string FormatNullSecurityStamp() - { - return GetString("NullSecurityStamp"); - } + => GetString("NullSecurityStamp"); /// /// Incorrect password. /// internal static string PasswordMismatch { - get { return GetString("PasswordMismatch"); } + get => GetString("PasswordMismatch"); } /// /// Incorrect password. /// internal static string FormatPasswordMismatch() - { - return GetString("PasswordMismatch"); - } + => GetString("PasswordMismatch"); /// /// Passwords must have at least one digit ('0'-'9'). /// internal static string PasswordRequiresDigit { - get { return GetString("PasswordRequiresDigit"); } + get => GetString("PasswordRequiresDigit"); } /// /// Passwords must have at least one digit ('0'-'9'). /// internal static string FormatPasswordRequiresDigit() - { - return GetString("PasswordRequiresDigit"); - } + => GetString("PasswordRequiresDigit"); /// /// Passwords must have at least one lowercase ('a'-'z'). /// internal static string PasswordRequiresLower { - get { return GetString("PasswordRequiresLower"); } + get => GetString("PasswordRequiresLower"); } /// /// Passwords must have at least one lowercase ('a'-'z'). /// internal static string FormatPasswordRequiresLower() - { - return GetString("PasswordRequiresLower"); - } + => GetString("PasswordRequiresLower"); /// /// Passwords must have at least one non alphanumeric character. /// internal static string PasswordRequiresNonAlphanumeric { - get { return GetString("PasswordRequiresNonAlphanumeric"); } + get => GetString("PasswordRequiresNonAlphanumeric"); } /// /// Passwords must have at least one non alphanumeric character. /// internal static string FormatPasswordRequiresNonAlphanumeric() - { - return GetString("PasswordRequiresNonAlphanumeric"); - } + => GetString("PasswordRequiresNonAlphanumeric"); /// /// Passwords must have at least one uppercase ('A'-'Z'). /// internal static string PasswordRequiresUpper { - get { return GetString("PasswordRequiresUpper"); } + get => GetString("PasswordRequiresUpper"); } /// /// Passwords must have at least one uppercase ('A'-'Z'). /// internal static string FormatPasswordRequiresUpper() - { - return GetString("PasswordRequiresUpper"); - } + => GetString("PasswordRequiresUpper"); /// /// Passwords must be at least {0} characters. /// internal static string PasswordTooShort { - get { return GetString("PasswordTooShort"); } + get => GetString("PasswordTooShort"); } /// /// Passwords must be at least {0} characters. /// internal static string FormatPasswordTooShort(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("PasswordTooShort"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("PasswordTooShort"), p0); /// /// Role {0} does not exist. /// internal static string RoleNotFound { - get { return GetString("RoleNotFound"); } + get => GetString("RoleNotFound"); } /// /// Role {0} does not exist. /// internal static string FormatRoleNotFound(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("RoleNotFound"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("RoleNotFound"), p0); /// /// Store does not implement IQueryableRoleStore<TRole>. /// internal static string StoreNotIQueryableRoleStore { - get { return GetString("StoreNotIQueryableRoleStore"); } + get => GetString("StoreNotIQueryableRoleStore"); } /// /// Store does not implement IQueryableRoleStore<TRole>. /// internal static string FormatStoreNotIQueryableRoleStore() - { - return GetString("StoreNotIQueryableRoleStore"); - } + => GetString("StoreNotIQueryableRoleStore"); /// /// Store does not implement IQueryableUserStore<TUser>. /// internal static string StoreNotIQueryableUserStore { - get { return GetString("StoreNotIQueryableUserStore"); } + get => GetString("StoreNotIQueryableUserStore"); } /// /// Store does not implement IQueryableUserStore<TUser>. /// internal static string FormatStoreNotIQueryableUserStore() - { - return GetString("StoreNotIQueryableUserStore"); - } + => GetString("StoreNotIQueryableUserStore"); /// /// Store does not implement IRoleClaimStore<TRole>. /// internal static string StoreNotIRoleClaimStore { - get { return GetString("StoreNotIRoleClaimStore"); } + get => GetString("StoreNotIRoleClaimStore"); } /// /// Store does not implement IRoleClaimStore<TRole>. /// internal static string FormatStoreNotIRoleClaimStore() - { - return GetString("StoreNotIRoleClaimStore"); - } + => GetString("StoreNotIRoleClaimStore"); /// /// Store does not implement IUserAuthenticationTokenStore<User>. /// internal static string StoreNotIUserAuthenticationTokenStore { - get { return GetString("StoreNotIUserAuthenticationTokenStore"); } + get => GetString("StoreNotIUserAuthenticationTokenStore"); } /// /// Store does not implement IUserAuthenticationTokenStore<User>. /// internal static string FormatStoreNotIUserAuthenticationTokenStore() - { - return GetString("StoreNotIUserAuthenticationTokenStore"); - } + => GetString("StoreNotIUserAuthenticationTokenStore"); /// /// Store does not implement IUserClaimStore<TUser>. /// internal static string StoreNotIUserClaimStore { - get { return GetString("StoreNotIUserClaimStore"); } + get => GetString("StoreNotIUserClaimStore"); } /// /// Store does not implement IUserClaimStore<TUser>. /// internal static string FormatStoreNotIUserClaimStore() - { - return GetString("StoreNotIUserClaimStore"); - } + => GetString("StoreNotIUserClaimStore"); /// /// Store does not implement IUserConfirmationStore<TUser>. /// internal static string StoreNotIUserConfirmationStore { - get { return GetString("StoreNotIUserConfirmationStore"); } + get => GetString("StoreNotIUserConfirmationStore"); } /// /// Store does not implement IUserConfirmationStore<TUser>. /// internal static string FormatStoreNotIUserConfirmationStore() - { - return GetString("StoreNotIUserConfirmationStore"); - } + => GetString("StoreNotIUserConfirmationStore"); /// /// Store does not implement IUserEmailStore<TUser>. /// internal static string StoreNotIUserEmailStore { - get { return GetString("StoreNotIUserEmailStore"); } + get => GetString("StoreNotIUserEmailStore"); } /// /// Store does not implement IUserEmailStore<TUser>. /// internal static string FormatStoreNotIUserEmailStore() - { - return GetString("StoreNotIUserEmailStore"); - } + => GetString("StoreNotIUserEmailStore"); /// /// Store does not implement IUserLockoutStore<TUser>. /// internal static string StoreNotIUserLockoutStore { - get { return GetString("StoreNotIUserLockoutStore"); } + get => GetString("StoreNotIUserLockoutStore"); } /// /// Store does not implement IUserLockoutStore<TUser>. /// internal static string FormatStoreNotIUserLockoutStore() - { - return GetString("StoreNotIUserLockoutStore"); - } + => GetString("StoreNotIUserLockoutStore"); /// /// Store does not implement IUserLoginStore<TUser>. /// internal static string StoreNotIUserLoginStore { - get { return GetString("StoreNotIUserLoginStore"); } + get => GetString("StoreNotIUserLoginStore"); } /// /// Store does not implement IUserLoginStore<TUser>. /// internal static string FormatStoreNotIUserLoginStore() - { - return GetString("StoreNotIUserLoginStore"); - } + => GetString("StoreNotIUserLoginStore"); /// /// Store does not implement IUserPasswordStore<TUser>. /// internal static string StoreNotIUserPasswordStore { - get { return GetString("StoreNotIUserPasswordStore"); } + get => GetString("StoreNotIUserPasswordStore"); } /// /// Store does not implement IUserPasswordStore<TUser>. /// internal static string FormatStoreNotIUserPasswordStore() - { - return GetString("StoreNotIUserPasswordStore"); - } + => GetString("StoreNotIUserPasswordStore"); /// /// Store does not implement IUserPhoneNumberStore<TUser>. /// internal static string StoreNotIUserPhoneNumberStore { - get { return GetString("StoreNotIUserPhoneNumberStore"); } + get => GetString("StoreNotIUserPhoneNumberStore"); } /// /// Store does not implement IUserPhoneNumberStore<TUser>. /// internal static string FormatStoreNotIUserPhoneNumberStore() - { - return GetString("StoreNotIUserPhoneNumberStore"); - } + => GetString("StoreNotIUserPhoneNumberStore"); /// /// Store does not implement IUserRoleStore<TUser>. /// internal static string StoreNotIUserRoleStore { - get { return GetString("StoreNotIUserRoleStore"); } + get => GetString("StoreNotIUserRoleStore"); } /// /// Store does not implement IUserRoleStore<TUser>. /// internal static string FormatStoreNotIUserRoleStore() - { - return GetString("StoreNotIUserRoleStore"); - } + => GetString("StoreNotIUserRoleStore"); /// /// Store does not implement IUserSecurityStampStore<TUser>. /// internal static string StoreNotIUserSecurityStampStore { - get { return GetString("StoreNotIUserSecurityStampStore"); } + get => GetString("StoreNotIUserSecurityStampStore"); } /// /// Store does not implement IUserSecurityStampStore<TUser>. /// internal static string FormatStoreNotIUserSecurityStampStore() - { - return GetString("StoreNotIUserSecurityStampStore"); - } + => GetString("StoreNotIUserSecurityStampStore"); /// /// Store does not implement IUserAuthenticatorKeyStore<User>. /// internal static string StoreNotIUserAuthenticatorKeyStore { - get { return GetString("StoreNotIUserAuthenticatorKeyStore"); } + get => GetString("StoreNotIUserAuthenticatorKeyStore"); } /// /// Store does not implement IUserAuthenticatorKeyStore<User>. /// internal static string FormatStoreNotIUserAuthenticatorKeyStore() - { - return GetString("StoreNotIUserAuthenticatorKeyStore"); - } + => GetString("StoreNotIUserAuthenticatorKeyStore"); /// /// Store does not implement IUserTwoFactorStore<TUser>. /// internal static string StoreNotIUserTwoFactorStore { - get { return GetString("StoreNotIUserTwoFactorStore"); } + get => GetString("StoreNotIUserTwoFactorStore"); } /// /// Store does not implement IUserTwoFactorStore<TUser>. /// internal static string FormatStoreNotIUserTwoFactorStore() - { - return GetString("StoreNotIUserTwoFactorStore"); - } + => GetString("StoreNotIUserTwoFactorStore"); /// /// Recovery code redemption failed. /// internal static string RecoveryCodeRedemptionFailed { - get { return GetString("RecoveryCodeRedemptionFailed"); } + get => GetString("RecoveryCodeRedemptionFailed"); } /// /// Recovery code redemption failed. /// internal static string FormatRecoveryCodeRedemptionFailed() - { - return GetString("RecoveryCodeRedemptionFailed"); - } + => GetString("RecoveryCodeRedemptionFailed"); /// /// User already has a password set. /// internal static string UserAlreadyHasPassword { - get { return GetString("UserAlreadyHasPassword"); } + get => GetString("UserAlreadyHasPassword"); } /// /// User already has a password set. /// internal static string FormatUserAlreadyHasPassword() - { - return GetString("UserAlreadyHasPassword"); - } + => GetString("UserAlreadyHasPassword"); /// /// User already in role '{0}'. /// internal static string UserAlreadyInRole { - get { return GetString("UserAlreadyInRole"); } + get => GetString("UserAlreadyInRole"); } /// /// User already in role '{0}'. /// internal static string FormatUserAlreadyInRole(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("UserAlreadyInRole"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("UserAlreadyInRole"), p0); /// /// User is locked out. /// internal static string UserLockedOut { - get { return GetString("UserLockedOut"); } + get => GetString("UserLockedOut"); } /// /// User is locked out. /// internal static string FormatUserLockedOut() - { - return GetString("UserLockedOut"); - } + => GetString("UserLockedOut"); /// /// Lockout is not enabled for this user. /// internal static string UserLockoutNotEnabled { - get { return GetString("UserLockoutNotEnabled"); } + get => GetString("UserLockoutNotEnabled"); } /// /// Lockout is not enabled for this user. /// internal static string FormatUserLockoutNotEnabled() - { - return GetString("UserLockoutNotEnabled"); - } + => GetString("UserLockoutNotEnabled"); /// /// User {0} does not exist. /// internal static string UserNameNotFound { - get { return GetString("UserNameNotFound"); } + get => GetString("UserNameNotFound"); } /// /// User {0} does not exist. /// internal static string FormatUserNameNotFound(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("UserNameNotFound"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("UserNameNotFound"), p0); /// /// User is not in role '{0}'. /// internal static string UserNotInRole { - get { return GetString("UserNotInRole"); } + get => GetString("UserNotInRole"); } /// /// User is not in role '{0}'. /// internal static string FormatUserNotInRole(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("UserNotInRole"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("UserNotInRole"), p0); /// /// Store does not implement IUserTwoFactorRecoveryCodeStore<User>. /// internal static string StoreNotIUserTwoFactorRecoveryCodeStore { - get { return GetString("StoreNotIUserTwoFactorRecoveryCodeStore"); } + get => GetString("StoreNotIUserTwoFactorRecoveryCodeStore"); } /// /// Store does not implement IUserTwoFactorRecoveryCodeStore<User>. /// internal static string FormatStoreNotIUserTwoFactorRecoveryCodeStore() - { - return GetString("StoreNotIUserTwoFactorRecoveryCodeStore"); - } + => GetString("StoreNotIUserTwoFactorRecoveryCodeStore"); /// /// Passwords must use at least {0} different characters. /// internal static string PasswordRequiresUniqueChars { - get { return GetString("PasswordRequiresUniqueChars"); } + get => GetString("PasswordRequiresUniqueChars"); } /// /// Passwords must use at least {0} different characters. /// internal static string FormatPasswordRequiresUniqueChars(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("PasswordRequiresUniqueChars"), p0); + + /// + /// No RoleType was specified, try AddRoles<TRole>(). + /// + internal static string NoRoleType { - return string.Format(CultureInfo.CurrentCulture, GetString("PasswordRequiresUniqueChars"), p0); + get => GetString("NoRoleType"); } + /// + /// No RoleType was specified, try AddRoles<TRole>(). + /// + internal static string FormatNoRoleType() + => GetString("NoRoleType"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.Extensions.Identity.Core/Resources.resx b/src/Microsoft.Extensions.Identity.Core/Resources.resx index 39a1443a4b..50d6501478 100644 --- a/src/Microsoft.Extensions.Identity.Core/Resources.resx +++ b/src/Microsoft.Extensions.Identity.Core/Resources.resx @@ -305,4 +305,8 @@ Passwords must use at least {0} different characters. Error message for passwords that are based on similar characters + + No RoleType was specified, try AddRoles<TRole>(). + Error when the IdentityBuilder.RoleType was not specified + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Identity/Rfc6238AuthenticationService.cs b/src/Microsoft.Extensions.Identity.Core/Rfc6238AuthenticationService.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/Rfc6238AuthenticationService.cs rename to src/Microsoft.Extensions.Identity.Core/Rfc6238AuthenticationService.cs diff --git a/src/Microsoft.Extensions.Identity.Core/RoleManager.cs b/src/Microsoft.Extensions.Identity.Core/RoleManager.cs index fdccba2441..22d0b1a7b0 100644 --- a/src/Microsoft.Extensions.Identity.Core/RoleManager.cs +++ b/src/Microsoft.Extensions.Identity.Core/RoleManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Identity.Core; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Identity diff --git a/src/Microsoft.AspNetCore.Identity/RoleValidator.cs b/src/Microsoft.Extensions.Identity.Core/RoleValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/RoleValidator.cs rename to src/Microsoft.Extensions.Identity.Core/RoleValidator.cs diff --git a/src/Microsoft.AspNetCore.Identity/TotpSecurityStampBasedTokenProvider.cs b/src/Microsoft.Extensions.Identity.Core/TotpSecurityStampBasedTokenProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/TotpSecurityStampBasedTokenProvider.cs rename to src/Microsoft.Extensions.Identity.Core/TotpSecurityStampBasedTokenProvider.cs diff --git a/src/Microsoft.AspNetCore.Identity/UpperInvariantLookupNormalizer.cs b/src/Microsoft.Extensions.Identity.Core/UpperInvariantLookupNormalizer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/UpperInvariantLookupNormalizer.cs rename to src/Microsoft.Extensions.Identity.Core/UpperInvariantLookupNormalizer.cs diff --git a/src/Microsoft.AspNetCore.Identity/UserClaimsPrincipalFactory.cs b/src/Microsoft.Extensions.Identity.Core/UserClaimsPrincipalFactory.cs similarity index 70% rename from src/Microsoft.AspNetCore.Identity/UserClaimsPrincipalFactory.cs rename to src/Microsoft.Extensions.Identity.Core/UserClaimsPrincipalFactory.cs index 20062e6470..d7e93fbdc1 100644 --- a/src/Microsoft.AspNetCore.Identity/UserClaimsPrincipalFactory.cs +++ b/src/Microsoft.Extensions.Identity.Core/UserClaimsPrincipalFactory.cs @@ -13,36 +13,27 @@ namespace Microsoft.AspNetCore.Identity /// Provides methods to create a claims principal for a given user. /// /// The type used to represent a user. - /// The type used to represent a role. - public class UserClaimsPrincipalFactory : IUserClaimsPrincipalFactory + public class UserClaimsPrincipalFactory : IUserClaimsPrincipalFactory where TUser : class - where TRole : class { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The to retrieve user information from. - /// The to retrieve a user's roles from. /// The configured . public UserClaimsPrincipalFactory( - UserManager userManager, - RoleManager roleManager, + UserManager userManager, IOptions optionsAccessor) { if (userManager == null) { throw new ArgumentNullException(nameof(userManager)); } - if (roleManager == null) - { - throw new ArgumentNullException(nameof(roleManager)); - } if (optionsAccessor == null || optionsAccessor.Value == null) { throw new ArgumentNullException(nameof(optionsAccessor)); } UserManager = userManager; - RoleManager = roleManager; Options = optionsAccessor.Value; } @@ -54,14 +45,6 @@ namespace Microsoft.AspNetCore.Identity /// public UserManager UserManager { get; private set; } - /// - /// Gets the for this factory. - /// - /// - /// The current for this factory instance. - /// - public RoleManager RoleManager { get; private set; } - /// /// Gets the for this factory. /// @@ -81,18 +64,78 @@ namespace Microsoft.AspNetCore.Identity { throw new ArgumentNullException(nameof(user)); } + var id = await GenerateClaimsAsync(user); + return new ClaimsPrincipal(id); + } + + /// + /// Generate the claims for a user. + /// + /// The user to create a from. + /// The that represents the asynchronous creation operation, containing the created . + protected virtual async Task GenerateClaimsAsync(TUser user) + { var userId = await UserManager.GetUserIdAsync(user); var userName = await UserManager.GetUserNameAsync(user); - var id = new ClaimsIdentity(IdentityConstants.ApplicationScheme, + var id = new ClaimsIdentity("Identity.Application", // REVIEW: Used to match Application scheme Options.ClaimsIdentity.UserNameClaimType, Options.ClaimsIdentity.RoleClaimType); id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId)); id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, userName)); if (UserManager.SupportsUserSecurityStamp) { - id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType, + id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType, await UserManager.GetSecurityStampAsync(user))); } + if (UserManager.SupportsUserClaim) + { + id.AddClaims(await UserManager.GetClaimsAsync(user)); + } + return id; + } + } + + /// + /// Provides methods to create a claims principal for a given user. + /// + /// The type used to represent a user. + /// The type used to represent a role. + public class UserClaimsPrincipalFactory : UserClaimsPrincipalFactory + where TUser : class + where TRole : class + { + /// + /// Initializes a new instance of the class. + /// + /// The to retrieve user information from. + /// The to retrieve a user's roles from. + /// The configured . + public UserClaimsPrincipalFactory(UserManager userManager, RoleManager roleManager, IOptions options) + : base(userManager, options) + { + if (roleManager == null) + { + throw new ArgumentNullException(nameof(roleManager)); + } + RoleManager = roleManager; + } + + /// + /// Gets the for this factory. + /// + /// + /// The current for this factory instance. + /// + public RoleManager RoleManager { get; private set; } + + /// + /// Generate the claims for a user. + /// + /// The user to create a from. + /// The that represents the asynchronous creation operation, containing the created . + protected override async Task GenerateClaimsAsync(TUser user) + { + var id = await base.GenerateClaimsAsync(user); if (UserManager.SupportsUserRole) { var roles = await UserManager.GetRolesAsync(user); @@ -109,21 +152,7 @@ namespace Microsoft.AspNetCore.Identity } } } - if (UserManager.SupportsUserClaim) - { - id.AddClaims(await UserManager.GetClaimsAsync(user)); - } - return await CreatePrincipalAsync(id); - } - - /// - /// Creates a from a . - /// - /// The with claims. - /// The that represents the asynchronous creation operation, containing the with the . - protected virtual Task CreatePrincipalAsync(ClaimsIdentity id) - { - return Task.FromResult(new ClaimsPrincipal(id)); + return id; } } } \ No newline at end of file diff --git a/src/Microsoft.Extensions.Identity.Core/UserManager.cs b/src/Microsoft.Extensions.Identity.Core/UserManager.cs index 6ab877124b..c4aac9a0b8 100644 --- a/src/Microsoft.Extensions.Identity.Core/UserManager.cs +++ b/src/Microsoft.Extensions.Identity.Core/UserManager.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Identity.Core; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; diff --git a/src/Microsoft.AspNetCore.Identity/UserValidator.cs b/src/Microsoft.Extensions.Identity.Core/UserValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Identity/UserValidator.cs rename to src/Microsoft.Extensions.Identity.Core/UserValidator.cs diff --git a/src/Microsoft.Extensions.Identity.Stores/IdentityRole.cs b/src/Microsoft.Extensions.Identity.Stores/IdentityRole.cs index a5cb111b12..e3cbef5a24 100644 --- a/src/Microsoft.Extensions.Identity.Stores/IdentityRole.cs +++ b/src/Microsoft.Extensions.Identity.Stores/IdentityRole.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; namespace Microsoft.AspNetCore.Identity { @@ -39,34 +38,7 @@ namespace Microsoft.AspNetCore.Identity /// Represents a role in the identity system /// /// The type used for the primary key for the role. - public class IdentityRole : IdentityRole, IdentityRoleClaim> - where TKey : IEquatable - { - /// - /// Initializes a new instance of . - /// - public IdentityRole() { } - - /// - /// Initializes a new instance of . - /// - /// The role name. - public IdentityRole(string roleName) : this() - { - Name = roleName; - } - } - - /// - /// Represents a role in the identity system - /// - /// The type used for the primary key for the role. - /// The type used for user roles. - /// The type used for role claims. - public class IdentityRole - where TKey : IEquatable - where TUserRole : IdentityUserRole - where TRoleClaim : IdentityRoleClaim + public class IdentityRole where TKey : IEquatable { /// /// Initializes a new instance of . diff --git a/src/Microsoft.Extensions.Identity.Stores/IdentityUser.cs b/src/Microsoft.Extensions.Identity.Stores/IdentityUser.cs index efc8a9b713..7143fadc18 100644 --- a/src/Microsoft.Extensions.Identity.Stores/IdentityUser.cs +++ b/src/Microsoft.Extensions.Identity.Stores/IdentityUser.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; namespace Microsoft.AspNetCore.Identity { @@ -39,19 +38,7 @@ namespace Microsoft.AspNetCore.Identity /// Represents a user in the identity system /// /// The type used for the primary key for the user. - public class IdentityUser : IdentityUser, IdentityUserRole, IdentityUserLogin, IdentityUserToken> - where TKey : IEquatable - { } - - /// - /// Represents a user in the identity system - /// - /// The type used for the primary key for the user. - /// The type representing a claim. - /// The type representing a user role. - /// The type representing a user external login. - /// The type representing a user external login. - public class IdentityUser where TKey : IEquatable + public class IdentityUser where TKey : IEquatable { /// /// Initializes a new instance of . diff --git a/src/Microsoft.Extensions.Identity.Stores/RoleStoreBase.cs b/src/Microsoft.Extensions.Identity.Stores/RoleStoreBase.cs index b65b5f9937..01ca59bb6c 100644 --- a/src/Microsoft.Extensions.Identity.Stores/RoleStoreBase.cs +++ b/src/Microsoft.Extensions.Identity.Stores/RoleStoreBase.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Identity public abstract class RoleStoreBase : IQueryableRoleStore, IRoleClaimStore - where TRole : IdentityRole + where TRole : IdentityRole where TKey : IEquatable where TUserRole : IdentityUserRole, new() where TRoleClaim : IdentityRoleClaim, new() @@ -219,10 +219,7 @@ namespace Microsoft.AspNetCore.Identity /// /// Dispose the stores /// - public void Dispose() - { - _disposed = true; - } + public void Dispose() => _disposed = true; /// /// Get the claims associated with the specified as an asynchronous operation. @@ -265,8 +262,6 @@ namespace Microsoft.AspNetCore.Identity /// The associated claim. /// The role claim entity. protected virtual TRoleClaim CreateRoleClaim(TRole role, Claim claim) - { - return new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; - } + => new TRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }; } } diff --git a/src/Microsoft.Extensions.Identity.Stores/UserStoreBase.cs b/src/Microsoft.Extensions.Identity.Stores/UserStoreBase.cs index 1a72f5bf87..749cbaf8e6 100644 --- a/src/Microsoft.Extensions.Identity.Stores/UserStoreBase.cs +++ b/src/Microsoft.Extensions.Identity.Stores/UserStoreBase.cs @@ -8,24 +8,19 @@ using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Identity { /// - /// Represents a new instance of a persistence store for the specified user and role types. + /// Represents a new instance of a persistence store for the specified user type. /// /// The type representing a user. - /// The type representing a role. - /// The type of the primary key for a role. + /// The type of the primary key for a user. /// The type representing a claim. - /// The type representing a user role. /// The type representing a user external login. /// The type representing a user token. - /// The type representing a role claim. - public abstract class UserStoreBase : + public abstract class UserStoreBase : IUserLoginStore, - IUserRoleStore, IUserClaimStore, IUserPasswordStore, IUserSecurityStampStore, @@ -37,14 +32,11 @@ namespace Microsoft.AspNetCore.Identity IUserAuthenticationTokenStore, IUserAuthenticatorKeyStore, IUserTwoFactorRecoveryCodeStore - where TUser : IdentityUser - where TRole : IdentityRole + where TUser : IdentityUser where TKey : IEquatable where TUserClaim : IdentityUserClaim, new() - where TUserRole : IdentityUserRole, new() where TUserLogin : IdentityUserLogin, new() where TUserToken : IdentityUserToken, new() - where TRoleClaim : IdentityRoleClaim, new() { /// /// Creates a new instance. @@ -67,21 +59,6 @@ namespace Microsoft.AspNetCore.Identity /// public IdentityErrorDescriber ErrorDescriber { get; set; } - /// - /// Called to create a new instance of a . - /// - /// The associated user. - /// The associated role. - /// - protected virtual TUserRole CreateUserRole(TUser user, TRole role) - { - return new TUserRole() - { - UserId = user.Id, - RoleId = role.Id - }; - } - /// /// Called to create a new instance of a . /// @@ -349,23 +326,6 @@ namespace Microsoft.AspNetCore.Identity return Task.FromResult(user.PasswordHash != null); } - /// - /// Return a role with the normalized name if it exists. - /// - /// The normalized role name. - /// The used to propagate notifications that the operation should be canceled. - /// The role if it exists. - protected abstract Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken); - - /// - /// Return a user role for the userId and roleId if it exists. - /// - /// The user's id. - /// The role's id. - /// The used to propagate notifications that the operation should be canceled. - /// The user role if it exists. - protected abstract Task FindUserRoleAsync(TKey userId, TKey roleId, CancellationToken cancellationToken); - /// /// Return a user with the matching userId if it exists. /// @@ -393,42 +353,6 @@ namespace Microsoft.AspNetCore.Identity /// The user login if it exists. protected abstract Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken); - /// - /// Adds the given to the specified . - /// - /// The user to add the role to. - /// The role to add. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public abstract Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Removes the given from the specified . - /// - /// The user to remove the role from. - /// The role to remove. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - public abstract Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Retrieves the roles the specified is a member of. - /// - /// The user whose roles should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the roles the user is a member of. - public abstract Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Returns a flag indicating if the specified user is a member of the give . - /// - /// The user whose role membership should be checked. - /// The role to check membership of - /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user is a member of the given group. If the - /// user is a member of the group the returned value with be true, otherwise it will be false. - public abstract Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); - /// /// Throws if this class has been disposed. /// @@ -957,16 +881,6 @@ namespace Microsoft.AspNetCore.Identity /// public abstract Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)); - /// - /// Retrieves all users in the specified role. - /// - /// The role whose users should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The contains a list of users, if any, that are in the specified role. - /// - public abstract Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); - /// /// Find a user token if it exists. /// @@ -1139,4 +1053,113 @@ namespace Microsoft.AspNetCore.Identity return false; } } + + /// + /// Represents a new instance of a persistence store for the specified user and role types. + /// + /// The type representing a user. + /// The type representing a role. + /// The type of the primary key for a role. + /// The type representing a claim. + /// The type representing a user role. + /// The type representing a user external login. + /// The type representing a user token. + /// The type representing a role claim. + public abstract class UserStoreBase : + UserStoreBase, + IUserRoleStore + where TUser : IdentityUser + where TRole : IdentityRole + where TKey : IEquatable + where TUserClaim : IdentityUserClaim, new() + where TUserRole : IdentityUserRole, new() + where TUserLogin : IdentityUserLogin, new() + where TUserToken : IdentityUserToken, new() + where TRoleClaim : IdentityRoleClaim, new() + { + /// + /// Creates a new instance. + /// + /// The used to describe store errors. + public UserStoreBase(IdentityErrorDescriber describer) : base(describer) { } + + /// + /// Called to create a new instance of a . + /// + /// The associated user. + /// The associated role. + /// + protected virtual TUserRole CreateUserRole(TUser user, TRole role) + { + return new TUserRole() + { + UserId = user.Id, + RoleId = role.Id + }; + } + + + /// + /// Retrieves all users in the specified role. + /// + /// The role whose users should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The contains a list of users, if any, that are in the specified role. + /// + public abstract Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Adds the given to the specified . + /// + /// The user to add the role to. + /// The role to add. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public abstract Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Removes the given from the specified . + /// + /// The user to remove the role from. + /// The role to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public abstract Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Retrieves the roles the specified is a member of. + /// + /// The user whose roles should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the roles the user is a member of. + public abstract Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Returns a flag indicating if the specified user is a member of the give . + /// + /// The user whose role membership should be checked. + /// The role to check membership of + /// The used to propagate notifications that the operation should be canceled. + /// A containing a flag indicating if the specified user is a member of the given group. If the + /// user is a member of the group the returned value with be true, otherwise it will be false. + public abstract Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Return a role with the normalized name if it exists. + /// + /// The normalized role name. + /// The used to propagate notifications that the operation should be canceled. + /// The role if it exists. + protected abstract Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken); + + /// + /// Return a user role for the userId and roleId if it exists. + /// + /// The user's id. + /// The role's id. + /// The used to propagate notifications that the operation should be canceled. + /// The user role if it exists. + protected abstract Task FindUserRoleAsync(TKey userId, TKey roleId, CancellationToken cancellationToken); + } } diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs index e1d2e349a0..d1191cfbbf 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs @@ -14,11 +14,16 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test } public class InMemoryContext : - InMemoryContext + IdentityDbContext where TUser : IdentityUser { public InMemoryContext(DbContextOptions options) : base(options) { } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseInMemoryDatabase("ScratchUsers"); + } } public class InMemoryContext : IdentityDbContext @@ -36,8 +41,8 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test } public abstract class InMemoryContext : IdentityDbContext - where TUser : IdentityUser - where TRole : IdentityRole + where TUser : IdentityUser + where TRole : IdentityRole where TKey : IEquatable where TUserClaim : IdentityUserClaim where TUserRole : IdentityUserRole @@ -45,12 +50,9 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test where TRoleClaim : IdentityRoleClaim where TUserToken : IdentityUserToken { - public InMemoryContext(DbContextOptions options) : base(options) - { } + public InMemoryContext(DbContextOptions options) : base(options) { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseInMemoryDatabase("Scratch"); - } + => optionsBuilder.UseInMemoryDatabase("Scratch"); } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFOnlyUsersTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFOnlyUsersTest.cs new file mode 100644 index 0000000000..b0366d0c88 --- /dev/null +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryEFOnlyUsersTest.cs @@ -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.Linq.Expressions; +using Microsoft.AspNetCore.Identity.Test; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test +{ + public class InMemoryEFOnlyUsersTest : UserManagerSpecificationTestBase + { + protected override object CreateTestContext() + => new InMemoryContext(new DbContextOptionsBuilder().Options); + + protected override void AddUserStore(IServiceCollection services, object context = null) + => services.AddSingleton>(new UserStore, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim>((InMemoryContext)context, new IdentityErrorDescriber())); + + protected override IdentityUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", + bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false) + { + return new IdentityUser + { + UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()), + Email = email, + PhoneNumber = phoneNumber, + LockoutEnabled = lockoutEnabled, + LockoutEnd = lockoutEnd + }; + } + + protected override void SetUserPasswordHash(IdentityUser user, string hashedPassword) + { + user.PasswordHash = hashedPassword; + } + + protected override Expression> UserNameEqualsPredicate(string userName) => u => u.UserName == userName; + + protected override Expression> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName); + } +} diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs index 679221e048..ca16d349d7 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test/InMemoryStoreWithGenericsTest.cs @@ -188,7 +188,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test #region Generic Type defintions - public class IdentityUserWithGenerics : IdentityUser + public class IdentityUserWithGenerics : IdentityUser { public IdentityUserWithGenerics() { @@ -294,7 +294,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.InMemory.Test public DateTime Created { get; set; } } - public class MyIdentityRole : IdentityRole + public class MyIdentityRole : IdentityRole { public MyIdentityRole() : base() { diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreOnlyUsersTestBase.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreOnlyUsersTestBase.cs new file mode 100644 index 0000000000..4df7679aa9 --- /dev/null +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreOnlyUsersTestBase.cs @@ -0,0 +1,290 @@ +// 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.Identity.Test; +using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test +{ + public abstract class SqlStoreOnlyUsersTestBase : UserManagerSpecificationTestBase, IClassFixture + where TUser : IdentityUser, new() + where TKey : IEquatable + { + private readonly ScratchDatabaseFixture _fixture; + + protected SqlStoreOnlyUsersTestBase(ScratchDatabaseFixture fixture) + { + _fixture = fixture; + } + + protected override bool ShouldSkipDbTests() + { + return TestPlatformHelper.IsMono || !TestPlatformHelper.IsWindows; + } + + public class TestUserDbContext : IdentityDbContext + { + public TestUserDbContext(DbContextOptions options) : base(options) { } + } + + protected override TUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", + bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false) + { + return new TUser + { + UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()), + Email = email, + PhoneNumber = phoneNumber, + LockoutEnabled = lockoutEnabled, + LockoutEnd = lockoutEnd + }; + } + + protected override Expression> UserNameEqualsPredicate(string userName) => u => u.UserName == userName; + + protected override Expression> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName); + + public TestUserDbContext CreateContext() + { + var db = DbUtil.Create(_fixture.ConnectionString); + db.Database.EnsureCreated(); + return db; + } + + protected override object CreateTestContext() + { + return CreateContext(); + } + + protected override void AddUserStore(IServiceCollection services, object context = null) + { + services.AddSingleton>(new UserOnlyStore((TestUserDbContext)context)); + } + + protected override void SetUserPasswordHash(TUser user, string hashedPassword) + { + user.PasswordHash = hashedPassword; + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + public void EnsureDefaultSchema() + { + VerifyDefaultSchema(CreateContext()); + } + + internal static void VerifyDefaultSchema(TestUserDbContext dbContext) + { + var sqlConn = dbContext.Database.GetDbConnection(); + + using (var db = new SqlConnection(sqlConn.ConnectionString)) + { + db.Open(); + Assert.True(VerifyColumns(db, "AspNetUsers", "Id", "UserName", "Email", "PasswordHash", "SecurityStamp", + "EmailConfirmed", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnabled", + "LockoutEnd", "AccessFailedCount", "ConcurrencyStamp", "NormalizedUserName", "NormalizedEmail")); + Assert.False(VerifyColumns(db, "AspNetRoles", "Id", "Name", "NormalizedName", "ConcurrencyStamp")); + Assert.False(VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId")); + Assert.True(VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue")); + Assert.True(VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName")); + Assert.True(VerifyColumns(db, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value")); + + VerifyIndex(db, "AspNetUsers", "UserNameIndex", isUnique: true); + VerifyIndex(db, "AspNetUsers", "EmailIndex"); + db.Close(); + } + } + + internal static bool VerifyColumns(SqlConnection conn, string table, params string[] columns) + { + var count = 0; + using ( + var command = + new SqlCommand("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME=@Table", conn)) + { + command.Parameters.Add(new SqlParameter("Table", table)); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + count++; + if (!columns.Contains(reader.GetString(0))) + { + return false; + } + } + return count == columns.Length; + } + } + } + + internal static void VerifyIndex(SqlConnection conn, string table, string index, bool isUnique = false) + { + using ( + var command = + new SqlCommand( + "SELECT COUNT(*) FROM sys.indexes where NAME=@Index AND object_id = OBJECT_ID(@Table) AND is_unique = @Unique", conn)) + { + command.Parameters.Add(new SqlParameter("Index", index)); + command.Parameters.Add(new SqlParameter("Table", table)); + command.Parameters.Add(new SqlParameter("Unique", isUnique)); + using (var reader = command.ExecuteReader()) + { + Assert.True(reader.Read()); + Assert.True(reader.GetInt32(0) > 0); + } + } + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + 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")); + } + + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + public void CanCreateUserUsingEF() + { + using (var db = CreateContext()) + { + var user = CreateTestUser(); + db.Users.Add(user); + db.SaveChanges(); + Assert.True(db.Users.Any(u => u.UserName == user.UserName)); + Assert.NotNull(db.Users.FirstOrDefault(u => u.UserName == user.UserName)); + } + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + 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(TestUserDbContext 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)); + } + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + 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); + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + 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); + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + 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); + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + public async Task LoadFromDbFindByEmailTest() + { + var db = CreateContext(); + var user = CreateTestUser(); + user.Email = "fooz@fizzy.pop"; + await LazyLoadTestSetup(db, user); + + db = CreateContext(); + var manager = CreateManager(db); + var userByEmail = await manager.FindByEmailAsync(user.Email); + Assert.Equal(2, (await manager.GetClaimsAsync(userByEmail)).Count); + Assert.Equal(1, (await manager.GetLoginsAsync(userByEmail)).Count); + Assert.Equal(2, (await manager.GetRolesAsync(userByEmail)).Count); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreTestBase.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreTestBase.cs index 5a78f6c50d..9a5b3edabc 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreTestBase.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/SqlStoreTestBase.cs @@ -7,15 +7,19 @@ 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.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Xunit; namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test { + // TODO: Add test variation with non IdentityDbContext + public abstract class SqlStoreTestBase : IdentitySpecificationTestBase, IClassFixture where TUser : IdentityUser, new() where TRole : IdentityRole, new() @@ -28,6 +32,25 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test _fixture = fixture; } + protected override void SetupIdentityServices(IServiceCollection services, object context) + { + services.AddSingleton(); + services.AddSingleton((TestDbContext)context); + services.AddLogging(); + services.AddSingleton>>(new TestLogger>()); + services.AddSingleton>>(new TestLogger>()); + services.AddIdentity(options => + { + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequireUppercase = false; + options.User.AllowedUserNameCharacters = null; + }) + .AddDefaultTokenProviders() + .AddEntityFrameworkStores(); + } + protected override bool ShouldSkipDbTests() { return TestPlatformHelper.IsMono || !TestPlatformHelper.IsWindows; @@ -363,6 +386,5 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test Assert.Equal(1, (await manager.GetLoginsAsync(userByEmail)).Count); Assert.Equal(2, (await manager.GetRolesAsync(userByEmail)).Count); } - } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs index 89ba53590b..951e91cabf 100644 --- a/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs +++ b/test/Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test/UserStoreWithGenericsTest.cs @@ -101,15 +101,6 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test Assert.Contains("AddEntityFrameworkStores", e.Message); } - [Fact] - public void AddEntityFrameworkStoresWithMismatchedUserRoleThrows() - { - var services = new ServiceCollection(); - var builder = services.AddIdentity(); - var e = Assert.Throws(() => builder.AddEntityFrameworkStores()); - Assert.Contains("violates the constraint of type 'TRole'", e.Message); - } - [Fact] public async Task CanAddRemoveUserClaimWithIssuer() { @@ -218,7 +209,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test #region Generic Type defintions - public class IdentityUserWithGenerics : IdentityUser + public class IdentityUserWithGenerics : IdentityUser { public IdentityUserWithGenerics() { @@ -328,7 +319,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test public DateTime Created { get; set; } } - public class MyIdentityRole : IdentityRole + public class MyIdentityRole : IdentityRole { public MyIdentityRole() : base() { diff --git a/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryStore.cs b/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryStore.cs index ea7424fbf3..dc62d7e9cd 100644 --- a/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryStore.cs +++ b/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryStore.cs @@ -12,302 +12,13 @@ using Microsoft.AspNetCore.Identity.Test; namespace Microsoft.AspNetCore.Identity.InMemory { public class InMemoryStore : - IUserLoginStore, + InMemoryUserStore, IUserRoleStore, - IUserClaimStore, - IUserPasswordStore, - IUserSecurityStampStore, - IUserEmailStore, - IUserLockoutStore, - IUserPhoneNumberStore, - IQueryableUserStore, - IUserTwoFactorStore, IQueryableRoleStore, - IRoleClaimStore, - IUserAuthenticationTokenStore, - IUserAuthenticatorKeyStore, - IUserTwoFactorRecoveryCodeStore + IRoleClaimStore where TRole : TestRole where TUser : TestUser { - private readonly Dictionary _logins = new Dictionary(); - - private readonly Dictionary _users = new Dictionary(); - - public IQueryable Users - { - get { return _users.Values.AsQueryable(); } - } - - public Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - var claims = user.Claims.Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToList(); - return Task.FromResult>(claims); - } - - public Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) - { - foreach (var claim in claims) - { - user.Claims.Add(new TestUserClaim { ClaimType = claim.Type, ClaimValue = claim.Value, UserId = user.Id }); - } - return Task.FromResult(0); - } - - public Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) - { - var matchedClaims = user.Claims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToList(); - foreach (var matchedClaim in matchedClaims) - { - matchedClaim.ClaimValue = newClaim.Value; - matchedClaim.ClaimType = newClaim.Type; - } - return Task.FromResult(0); - } - - public Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) - { - foreach (var claim in claims) - { - var entity = - user.Claims.FirstOrDefault( - uc => uc.UserId == user.Id && uc.ClaimType == claim.Type && uc.ClaimValue == claim.Value); - if (entity != null) - { - user.Claims.Remove(entity); - } - } - return Task.FromResult(0); - } - - public Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) - { - user.Email = email; - return Task.FromResult(0); - } - - public Task GetEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.Email); - } - - public Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.NormalizedEmail); - } - - public Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) - { - user.NormalizedEmail = normalizedEmail; - return Task.FromResult(0); - } - - - public Task GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.EmailConfirmed); - } - - public Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) - { - user.EmailConfirmed = confirmed; - return Task.FromResult(0); - } - - public Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)) - { - return - Task.FromResult( - Users.FirstOrDefault(u => u.NormalizedEmail == email)); - } - - public Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.LockoutEnd); - } - - public Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) - { - user.LockoutEnd = lockoutEnd; - return Task.FromResult(0); - } - - public Task IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - user.AccessFailedCount++; - return Task.FromResult(user.AccessFailedCount); - } - - public Task ResetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - user.AccessFailedCount = 0; - return Task.FromResult(0); - } - - public Task GetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.AccessFailedCount); - } - - public Task GetLockoutEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.LockoutEnabled); - } - - public Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) - { - user.LockoutEnabled = enabled; - return Task.FromResult(0); - } - - private string GetLoginKey(string loginProvider, string providerKey) - { - return loginProvider + "|" + providerKey; - } - - public virtual Task AddLoginAsync(TUser user, UserLoginInfo login, - CancellationToken cancellationToken = default(CancellationToken)) - { - user.Logins.Add(new TestUserLogin - { - UserId = user.Id, - ProviderKey = login.ProviderKey, - LoginProvider = login.LoginProvider, - ProviderDisplayName = login.ProviderDisplayName - }); - _logins[GetLoginKey(login.LoginProvider, login.ProviderKey)] = user; - return Task.FromResult(0); - } - - public Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, - CancellationToken cancellationToken = default(CancellationToken)) - { - var loginEntity = - user.Logins.SingleOrDefault( - l => - l.ProviderKey == providerKey && l.LoginProvider == loginProvider && - l.UserId == user.Id); - if (loginEntity != null) - { - user.Logins.Remove(loginEntity); - } - _logins[GetLoginKey(loginProvider, providerKey)] = null; - return Task.FromResult(0); - } - - public Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - IList result = user.Logins - .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToList(); - return Task.FromResult(result); - } - - public Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) - { - string key = GetLoginKey(loginProvider, providerKey); - if (_logins.ContainsKey(key)) - { - return Task.FromResult(_logins[key]); - } - return Task.FromResult(null); - } - - public Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.Id); - } - - public Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.UserName); - } - - public Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) - { - user.UserName = userName; - return Task.FromResult(0); - } - - public Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - _users[user.Id] = user; - return Task.FromResult(IdentityResult.Success); - } - - public Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - _users[user.Id] = user; - return Task.FromResult(IdentityResult.Success); - } - - public Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) - { - if (_users.ContainsKey(userId)) - { - return Task.FromResult(_users[userId]); - } - return Task.FromResult(null); - } - - public void Dispose() - { - } - - public Task FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken)) - { - return - Task.FromResult( - Users.FirstOrDefault(u => u.NormalizedUserName == userName)); - } - - public Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - if (user == null || !_users.ContainsKey(user.Id)) - { - throw new InvalidOperationException("Unknown user"); - } - _users.Remove(user.Id); - return Task.FromResult(IdentityResult.Success); - } - - public Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) - { - user.PasswordHash = passwordHash; - return Task.FromResult(0); - } - - public Task GetPasswordHashAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.PasswordHash); - } - - public Task HasPasswordAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.PasswordHash != null); - } - - public Task SetPhoneNumberAsync(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) - { - user.PhoneNumber = phoneNumber; - return Task.FromResult(0); - } - - public Task GetPhoneNumberAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.PhoneNumber); - } - - public Task GetPhoneNumberConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.PhoneNumberConfirmed); - } - - public Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) - { - user.PhoneNumberConfirmed = confirmed; - return Task.FromResult(0); - } - // RoleId == roleName for InMemory public Task AddToRoleAsync(TUser user, string role, CancellationToken cancellationToken = default(CancellationToken)) { @@ -348,39 +59,6 @@ namespace Microsoft.AspNetCore.Identity.InMemory return Task.FromResult(result); } - public Task SetSecurityStampAsync(TUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) - { - user.SecurityStamp = stamp; - return Task.FromResult(0); - } - - public Task GetSecurityStampAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.SecurityStamp); - } - - public Task SetTwoFactorEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) - { - user.TwoFactorEnabled = enabled; - return Task.FromResult(0); - } - - public Task GetTwoFactorEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.TwoFactorEnabled); - } - - public Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.FromResult(user.NormalizedUserName); - } - - public Task SetNormalizedUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) - { - user.NormalizedUserName = userName; - return Task.FromResult(0); - } - // RoleId == rolename for inmemory store tests public Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) { @@ -397,20 +75,6 @@ namespace Microsoft.AspNetCore.Identity.InMemory return Task.FromResult>(Users.Where(u => (u.Roles.Where(x => x.RoleId == role.Id).Count() > 0)).Select(x => x).ToList()); } - public Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) - { - if (claim == null) - { - throw new ArgumentNullException(nameof(claim)); - } - - var query = from user in Users - where user.Claims.Where(x => x.ClaimType == claim.Type && x.ClaimValue == claim.Value).FirstOrDefault() != null - select user; - - return Task.FromResult>(query.ToList()); - } - private readonly Dictionary _roles = new Dictionary(); public Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) @@ -502,87 +166,6 @@ namespace Microsoft.AspNetCore.Identity.InMemory return Task.FromResult(0); } - public Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken) - { - var tokenEntity = - user.Tokens.SingleOrDefault( - l => - l.TokenName == name && l.LoginProvider == loginProvider && - l.UserId == user.Id); - if (tokenEntity != null) - { - tokenEntity.TokenValue = value; - } - else - { - user.Tokens.Add(new TestUserToken - { - UserId = user.Id, - LoginProvider = loginProvider, - TokenName = name, - TokenValue = value - }); - } - return Task.FromResult(0); - } - - public Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) - { - var tokenEntity = - user.Tokens.SingleOrDefault( - l => - l.TokenName == name && l.LoginProvider == loginProvider && - l.UserId == user.Id); - if (tokenEntity != null) - { - user.Tokens.Remove(tokenEntity); - } - return Task.FromResult(0); - } - - public Task GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) - { - var tokenEntity = - user.Tokens.SingleOrDefault( - l => - l.TokenName == name && l.LoginProvider == loginProvider && - l.UserId == user.Id); - return Task.FromResult(tokenEntity?.TokenValue); - } - - private const string AuthenticatorStoreLoginProvider = "[AspNetAuthenticatorStore]"; - private const string AuthenticatorKeyTokenName = "AuthenticatorKey"; - private const string RecoveryCodeTokenName = "RecoveryCodes"; - - public Task SetAuthenticatorKeyAsync(TUser user, string key, CancellationToken cancellationToken) - { - return SetTokenAsync(user, AuthenticatorStoreLoginProvider, AuthenticatorKeyTokenName, key, cancellationToken); - } - - public Task GetAuthenticatorKeyAsync(TUser user, CancellationToken cancellationToken) - { - return GetTokenAsync(user, AuthenticatorStoreLoginProvider, AuthenticatorKeyTokenName, cancellationToken); - } - - public Task ReplaceCodesAsync(TUser user, IEnumerable recoveryCodes, CancellationToken cancellationToken) - { - var mergedCodes = string.Join(";", recoveryCodes); - return SetTokenAsync(user, AuthenticatorStoreLoginProvider, RecoveryCodeTokenName, mergedCodes, cancellationToken); - } - - public async Task RedeemCodeAsync(TUser user, string code, CancellationToken cancellationToken) - { - var mergedCodes = await GetTokenAsync(user, AuthenticatorStoreLoginProvider, RecoveryCodeTokenName, cancellationToken) ?? ""; - var splitCodes = mergedCodes.Split(';'); - if (splitCodes.Contains(code)) - { - var updatedCodes = new List(splitCodes.Where(s => s != code)); - await ReplaceCodesAsync(user, updatedCodes, cancellationToken); - return true; - } - return false; - } - public IQueryable Roles { get { return _roles.Values.AsQueryable(); } diff --git a/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryUserStore.cs b/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryUserStore.cs new file mode 100644 index 0000000000..b71b22e785 --- /dev/null +++ b/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryUserStore.cs @@ -0,0 +1,435 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity.Test; + +namespace Microsoft.AspNetCore.Identity.InMemory +{ + public class InMemoryUserStore : + IUserLoginStore, + IUserClaimStore, + IUserPasswordStore, + IUserSecurityStampStore, + IUserEmailStore, + IUserLockoutStore, + IUserPhoneNumberStore, + IQueryableUserStore, + IUserTwoFactorStore, + IUserAuthenticationTokenStore, + IUserAuthenticatorKeyStore, + IUserTwoFactorRecoveryCodeStore + where TUser : TestUser + { + private readonly Dictionary _logins = new Dictionary(); + + private readonly Dictionary _users = new Dictionary(); + + public IQueryable Users + { + get { return _users.Values.AsQueryable(); } + } + + public Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + var claims = user.Claims.Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToList(); + return Task.FromResult>(claims); + } + + public Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) + { + foreach (var claim in claims) + { + user.Claims.Add(new TestUserClaim { ClaimType = claim.Type, ClaimValue = claim.Value, UserId = user.Id }); + } + return Task.FromResult(0); + } + + public Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) + { + var matchedClaims = user.Claims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToList(); + foreach (var matchedClaim in matchedClaims) + { + matchedClaim.ClaimValue = newClaim.Value; + matchedClaim.ClaimType = newClaim.Type; + } + return Task.FromResult(0); + } + + public Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) + { + foreach (var claim in claims) + { + var entity = + user.Claims.FirstOrDefault( + uc => uc.UserId == user.Id && uc.ClaimType == claim.Type && uc.ClaimValue == claim.Value); + if (entity != null) + { + user.Claims.Remove(entity); + } + } + return Task.FromResult(0); + } + + public Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) + { + user.Email = email; + return Task.FromResult(0); + } + + public Task GetEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.Email); + } + + public Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.NormalizedEmail); + } + + public Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) + { + user.NormalizedEmail = normalizedEmail; + return Task.FromResult(0); + } + + + public Task GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.EmailConfirmed); + } + + public Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) + { + user.EmailConfirmed = confirmed; + return Task.FromResult(0); + } + + public Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)) + { + return + Task.FromResult( + Users.FirstOrDefault(u => u.NormalizedEmail == email)); + } + + public Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.LockoutEnd); + } + + public Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) + { + user.LockoutEnd = lockoutEnd; + return Task.FromResult(0); + } + + public Task IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + user.AccessFailedCount++; + return Task.FromResult(user.AccessFailedCount); + } + + public Task ResetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + user.AccessFailedCount = 0; + return Task.FromResult(0); + } + + public Task GetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.AccessFailedCount); + } + + public Task GetLockoutEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.LockoutEnabled); + } + + public Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) + { + user.LockoutEnabled = enabled; + return Task.FromResult(0); + } + + private string GetLoginKey(string loginProvider, string providerKey) + { + return loginProvider + "|" + providerKey; + } + + public virtual Task AddLoginAsync(TUser user, UserLoginInfo login, + CancellationToken cancellationToken = default(CancellationToken)) + { + user.Logins.Add(new TestUserLogin + { + UserId = user.Id, + ProviderKey = login.ProviderKey, + LoginProvider = login.LoginProvider, + ProviderDisplayName = login.ProviderDisplayName + }); + _logins[GetLoginKey(login.LoginProvider, login.ProviderKey)] = user; + return Task.FromResult(0); + } + + public Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey, + CancellationToken cancellationToken = default(CancellationToken)) + { + var loginEntity = + user.Logins.SingleOrDefault( + l => + l.ProviderKey == providerKey && l.LoginProvider == loginProvider && + l.UserId == user.Id); + if (loginEntity != null) + { + user.Logins.Remove(loginEntity); + } + _logins[GetLoginKey(loginProvider, providerKey)] = null; + return Task.FromResult(0); + } + + public Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + IList result = user.Logins + .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToList(); + return Task.FromResult(result); + } + + public Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) + { + string key = GetLoginKey(loginProvider, providerKey); + if (_logins.ContainsKey(key)) + { + return Task.FromResult(_logins[key]); + } + return Task.FromResult(null); + } + + public Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.Id); + } + + public Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.UserName); + } + + public Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) + { + user.UserName = userName; + return Task.FromResult(0); + } + + public Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + _users[user.Id] = user; + return Task.FromResult(IdentityResult.Success); + } + + public Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + _users[user.Id] = user; + return Task.FromResult(IdentityResult.Success); + } + + public Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) + { + if (_users.ContainsKey(userId)) + { + return Task.FromResult(_users[userId]); + } + return Task.FromResult(null); + } + + public void Dispose() + { + } + + public Task FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken)) + { + return + Task.FromResult( + Users.FirstOrDefault(u => u.NormalizedUserName == userName)); + } + + public Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + if (user == null || !_users.ContainsKey(user.Id)) + { + throw new InvalidOperationException("Unknown user"); + } + _users.Remove(user.Id); + return Task.FromResult(IdentityResult.Success); + } + + public Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) + { + user.PasswordHash = passwordHash; + return Task.FromResult(0); + } + + public Task GetPasswordHashAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.PasswordHash); + } + + public Task HasPasswordAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.PasswordHash != null); + } + + public Task SetPhoneNumberAsync(TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + user.PhoneNumber = phoneNumber; + return Task.FromResult(0); + } + + public Task GetPhoneNumberAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.PhoneNumber); + } + + public Task GetPhoneNumberConfirmedAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.PhoneNumberConfirmed); + } + + public Task SetPhoneNumberConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) + { + user.PhoneNumberConfirmed = confirmed; + return Task.FromResult(0); + } + + public Task SetSecurityStampAsync(TUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) + { + user.SecurityStamp = stamp; + return Task.FromResult(0); + } + + public Task GetSecurityStampAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.SecurityStamp); + } + + public Task SetTwoFactorEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) + { + user.TwoFactorEnabled = enabled; + return Task.FromResult(0); + } + + public Task GetTwoFactorEnabledAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.TwoFactorEnabled); + } + + public Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(user.NormalizedUserName); + } + + public Task SetNormalizedUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) + { + user.NormalizedUserName = userName; + return Task.FromResult(0); + } + + public Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + if (claim == null) + { + throw new ArgumentNullException(nameof(claim)); + } + + var query = from user in Users + where user.Claims.Where(x => x.ClaimType == claim.Type && x.ClaimValue == claim.Value).FirstOrDefault() != null + select user; + + return Task.FromResult>(query.ToList()); + } + + public Task SetTokenAsync(TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken) + { + var tokenEntity = + user.Tokens.SingleOrDefault( + l => + l.TokenName == name && l.LoginProvider == loginProvider && + l.UserId == user.Id); + if (tokenEntity != null) + { + tokenEntity.TokenValue = value; + } + else + { + user.Tokens.Add(new TestUserToken + { + UserId = user.Id, + LoginProvider = loginProvider, + TokenName = name, + TokenValue = value + }); + } + return Task.FromResult(0); + } + + public Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + var tokenEntity = + user.Tokens.SingleOrDefault( + l => + l.TokenName == name && l.LoginProvider == loginProvider && + l.UserId == user.Id); + if (tokenEntity != null) + { + user.Tokens.Remove(tokenEntity); + } + return Task.FromResult(0); + } + + public Task GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + var tokenEntity = + user.Tokens.SingleOrDefault( + l => + l.TokenName == name && l.LoginProvider == loginProvider && + l.UserId == user.Id); + return Task.FromResult(tokenEntity?.TokenValue); + } + + private const string AuthenticatorStoreLoginProvider = "[AspNetAuthenticatorStore]"; + private const string AuthenticatorKeyTokenName = "AuthenticatorKey"; + private const string RecoveryCodeTokenName = "RecoveryCodes"; + + public Task SetAuthenticatorKeyAsync(TUser user, string key, CancellationToken cancellationToken) + { + return SetTokenAsync(user, AuthenticatorStoreLoginProvider, AuthenticatorKeyTokenName, key, cancellationToken); + } + + public Task GetAuthenticatorKeyAsync(TUser user, CancellationToken cancellationToken) + { + return GetTokenAsync(user, AuthenticatorStoreLoginProvider, AuthenticatorKeyTokenName, cancellationToken); + } + + public Task ReplaceCodesAsync(TUser user, IEnumerable recoveryCodes, CancellationToken cancellationToken) + { + var mergedCodes = string.Join(";", recoveryCodes); + return SetTokenAsync(user, AuthenticatorStoreLoginProvider, RecoveryCodeTokenName, mergedCodes, cancellationToken); + } + + public async Task RedeemCodeAsync(TUser user, string code, CancellationToken cancellationToken) + { + var mergedCodes = await GetTokenAsync(user, AuthenticatorStoreLoginProvider, RecoveryCodeTokenName, cancellationToken) ?? ""; + var splitCodes = mergedCodes.Split(';'); + if (splitCodes.Contains(code)) + { + var updatedCodes = new List(splitCodes.Where(s => s != code)); + await ReplaceCodesAsync(user, updatedCodes, cancellationToken); + return true; + } + return false; + } + } +} diff --git a/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryUserStoreTest.cs b/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryUserStoreTest.cs new file mode 100644 index 0000000000..d244a38a87 --- /dev/null +++ b/test/Microsoft.AspNetCore.Identity.InMemory.Test/InMemoryUserStoreTest.cs @@ -0,0 +1,45 @@ +// 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.Expressions; +using Microsoft.AspNetCore.Identity.Test; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Identity.InMemory.Test +{ + public class InMemoryUserStoreTest : UserManagerSpecificationTestBase + { + protected override object CreateTestContext() + { + return new InMemoryUserStore(); + } + + protected override void AddUserStore(IServiceCollection services, object context = null) + { + services.AddSingleton>((InMemoryUserStore)context); + } + + protected override void SetUserPasswordHash(TestUser user, string hashedPassword) + { + user.PasswordHash = hashedPassword; + } + + protected override TestUser CreateTestUser(string namePrefix = "", string email = "", string phoneNumber = "", + bool lockoutEnabled = false, DateTimeOffset? lockoutEnd = default(DateTimeOffset?), bool useNamePrefixAsUserName = false) + { + return new TestUser + { + UserName = useNamePrefixAsUserName ? namePrefix : string.Format("{0}{1}", namePrefix, Guid.NewGuid()), + Email = email, + PhoneNumber = phoneNumber, + LockoutEnabled = lockoutEnabled, + LockoutEnd = lockoutEnd + }; + } + + protected override Expression> UserNameEqualsPredicate(string userName) => u => u.UserName == userName; + + protected override Expression> UserNameStartsWithPredicate(string userName) => u => u.UserName.StartsWith(userName); + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs b/test/Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs index ae091e3f7b..0a23282f07 100644 --- a/test/Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs +++ b/test/Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore.InMemory.Test/InMemoryContext.cs @@ -53,8 +53,8 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore.InMemory.Tes TRedirectUri, TApplicationKey> : IdentityServiceDbContext - where TUser : IdentityUser - where TRole : IdentityRole + where TUser : IdentityUser + where TRole : IdentityRole where TUserKey : IEquatable where TUserClaim : IdentityUserClaim where TUserRole : IdentityUserRole diff --git a/test/Microsoft.AspNetCore.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNetCore.Identity.Test/IdentityBuilderTest.cs index 7622fc3a6b..0c3b8c7aa4 100644 --- a/test/Microsoft.AspNetCore.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Identity.Test/IdentityBuilderTest.cs @@ -160,9 +160,6 @@ namespace Microsoft.AspNetCore.Identity.Test var services = new ServiceCollection() .AddSingleton(new ConfigurationBuilder().Build()); var builder = services.AddIdentity(); - Assert.Throws(() => builder.AddUserManager>()); - Assert.Throws(() => builder.AddRoleManager>()); - Assert.Throws(() => builder.AddSignInManager>()); Assert.Throws(() => builder.AddUserManager()); Assert.Throws(() => builder.AddRoleManager()); Assert.Throws(() => builder.AddSignInManager()); diff --git a/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs index 92ff64558f..3114713f3b 100644 --- a/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNetCore.Identity.Test/UserManagerTest.cs @@ -499,7 +499,7 @@ namespace Microsoft.AspNetCore.Identity.Test // Act // Assert var ex = await Assert.ThrowsAsync(() => manager.CreateAsync(user)); - Assert.Contains(Resources.NullSecurityStamp, ex.Message); + Assert.Contains(Extensions.Identity.Core.Resources.NullSecurityStamp, ex.Message); store.VerifyAll(); } @@ -516,7 +516,7 @@ namespace Microsoft.AspNetCore.Identity.Test // Act // Assert var ex = await Assert.ThrowsAsync(() => manager.UpdateAsync(user)); - Assert.Contains(Resources.NullSecurityStamp, ex.Message); + Assert.Contains(Extensions.Identity.Core.Resources.NullSecurityStamp, ex.Message); store.VerifyAll(); } @@ -698,146 +698,6 @@ namespace Microsoft.AspNetCore.Identity.Test Assert.ThrowsAsync(() => manager.GenerateUserTokenAsync(new TestUser(), "A", "purpose")); } - [Fact] - public void TOTPTest() - { - //var verify = new TotpAuthenticatorVerification(); - //var secret = "abcdefghij"; - //var secret = Base32.FromBase32(authKey); - -// Assert.Equal(bytes, secret); - - //var code = verify.VerifyCode(secret, -1); - //Assert.Equal(code, 287004); - - - //var bytes = new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)'!', (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF }; - //var base32 = Base32.ToBase32(bytes); - // var code = Rfc6238AuthenticationService.GenerateCode(bytes); - // Assert.Equal(Rfc6238AuthenticationService.GenerateCode(bytes), Rfc6238AuthenticationService.CalculateOneTimePassword(new HMACSHA1(bytes))); - //Assert.True(Rfc6238AuthenticationService.ValidateCode(bytes, code)); - } - - public static byte[] ToBytes(string input) - { - if (string.IsNullOrEmpty(input)) - { - throw new ArgumentNullException("input"); - } - - input = input.TrimEnd('='); //remove padding characters - int byteCount = input.Length * 5 / 8; //this must be TRUNCATED - byte[] returnArray = new byte[byteCount]; - - byte curByte = 0, bitsRemaining = 8; - int mask = 0, arrayIndex = 0; - - foreach (char c in input) - { - int cValue = CharToValue(c); - - if (bitsRemaining > 5) - { - mask = cValue << (bitsRemaining - 5); - curByte = (byte)(curByte | mask); - bitsRemaining -= 5; - } - else - { - mask = cValue >> (5 - bitsRemaining); - curByte = (byte)(curByte | mask); - returnArray[arrayIndex++] = curByte; - curByte = (byte)(cValue << (3 + bitsRemaining)); - bitsRemaining += 3; - } - } - - //if we didn't end with a full byte - if (arrayIndex != byteCount) - { - returnArray[arrayIndex] = curByte; - } - - return returnArray; - } - - public static string ToString(byte[] input) - { - if (input == null || input.Length == 0) - { - throw new ArgumentNullException("input"); - } - - int charCount = (int)Math.Ceiling(input.Length / 5d) * 8; - char[] returnArray = new char[charCount]; - - byte nextChar = 0, bitsRemaining = 5; - int arrayIndex = 0; - - foreach (byte b in input) - { - nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining))); - returnArray[arrayIndex++] = ValueToChar(nextChar); - - if (bitsRemaining < 4) - { - nextChar = (byte)((b >> (3 - bitsRemaining)) & 31); - returnArray[arrayIndex++] = ValueToChar(nextChar); - bitsRemaining += 5; - } - - bitsRemaining -= 3; - nextChar = (byte)((b << bitsRemaining) & 31); - } - - //if we didn't end with a full char - if (arrayIndex != charCount) - { - returnArray[arrayIndex++] = ValueToChar(nextChar); - while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding - } - - return new string(returnArray); - } - - private static int CharToValue(char c) - { - var value = (int)c; - - //65-90 == uppercase letters - if (value < 91 && value > 64) - { - return value - 65; - } - //50-55 == numbers 2-7 - if (value < 56 && value > 49) - { - return value - 24; - } - //97-122 == lowercase letters - if (value < 123 && value > 96) - { - return value - 97; - } - - throw new ArgumentException("Character is not a Base32 character.", "c"); - } - - private static char ValueToChar(byte b) - { - if (b < 26) - { - return (char)(b + 65); - } - - if (b < 32) - { - return (char)(b + 24); - } - - throw new ArgumentException("Byte is not a value Base32 value.", "b"); - } - [Fact] public void UserManagerWillUseTokenProviderInstanceOverDefaults() { @@ -894,7 +754,7 @@ namespace Microsoft.AspNetCore.Identity.Test [Fact] public async Task AuthTokenMethodsFailWhenStoreNotImplemented() { - var error = Resources.StoreNotIUserAuthenticationTokenStore; + var error = Extensions.Identity.Core.Resources.StoreNotIUserAuthenticationTokenStore; var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserAuthenticationTokens); await VerifyException(async () => await manager.GetAuthenticationTokenAsync(null, null, null), error); @@ -905,7 +765,7 @@ namespace Microsoft.AspNetCore.Identity.Test [Fact] public async Task AuthenticatorMethodsFailWhenStoreNotImplemented() { - var error = Resources.StoreNotIUserAuthenticatorKeyStore; + var error = Extensions.Identity.Core.Resources.StoreNotIUserAuthenticatorKeyStore; var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserAuthenticatorKey); await VerifyException(async () => await manager.GetAuthenticatorKeyAsync(null), error); @@ -915,7 +775,7 @@ namespace Microsoft.AspNetCore.Identity.Test [Fact] public async Task RecoveryMethodsFailWhenStoreNotImplemented() { - var error = Resources.StoreNotIUserTwoFactorRecoveryCodeStore; + var error = Extensions.Identity.Core.Resources.StoreNotIUserTwoFactorRecoveryCodeStore; var manager = MockHelpers.TestUserManager(new NoopUserStore()); Assert.False(manager.SupportsUserTwoFactorRecoveryCodes); await VerifyException(async () => await manager.RedeemTwoFactorRecoveryCodeAsync(null, null), error);