From c232b8d761136c8efc6bac893ac57455934e72d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 14:18:32 +0300 Subject: [PATCH 01/13] Create and register AbpSignInManager. --- .../AspNetCore/AbpIdentityAspNetCoreModule.cs | 5 ++- .../Identity/AspNetCore/AbpSignInManager.cs | 41 +++++++++++++++++++ .../AspNetCore/AbpSignInManager_Tests.cs | 9 ++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs index c0e3031568..019ef0469e 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs @@ -13,8 +13,9 @@ namespace Volo.Abp.Identity.AspNetCore { PreConfigure(builder => { - builder.AddDefaultTokenProviders(); - builder.AddSignInManager(); + builder + .AddDefaultTokenProviders() + .AddSignInManager(); }); } diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs new file mode 100644 index 0000000000..30f9407e6e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class AbpSignInManager : SignInManager + { + public AbpSignInManager( + UserManager userManager, + IHttpContextAccessor contextAccessor, + IUserClaimsPrincipalFactory claimsFactory, + IOptions optionsAccessor, + ILogger> logger, + IAuthenticationSchemeProvider schemes, + IUserConfirmation confirmation + ) : base( + userManager, + contextAccessor, + claimsFactory, + optionsAccessor, + logger, + schemes, + confirmation) + { + + } + + public override Task PasswordSignInAsync( + string userName, + string password, + bool isPersistent, + bool lockoutOnFailure) + { + return base.PasswordSignInAsync(userName, password, isPersistent, lockoutOnFailure); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs index 268bfae40c..212c341733 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; using Shouldly; using Xunit; @@ -8,6 +9,13 @@ namespace Volo.Abp.Identity.AspNetCore { public class AbpSignInManager_Tests : AbpIdentityAspNetCoreTestBase { + [Fact] + public async Task Should_Resolve_AbpSignInManager() + { + var signInManager = GetRequiredService>(); + signInManager.ShouldBeOfType(); + } + [Fact] public async Task Should_SignIn_With_Correct_Credentials() { @@ -17,6 +25,7 @@ namespace Volo.Abp.Identity.AspNetCore result.ShouldBe("Succeeded"); } + [Fact] public async Task Should_Not_SignIn_With_Wrong_Credentials() { From 01580e3aa71ca0b5c0d34175b8b5eb3f3cc12f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 15:12:56 +0300 Subject: [PATCH 02/13] Introduce IExternalLoginProvider --- .../AbpIdentityAspNetCoreOptions.cs | 9 ++++- .../Identity/AspNetCore/AbpSignInManager.cs | 38 +++++++++++++++++-- .../ExternalLoginProviderDictionary.cs | 18 +++++++++ .../AspNetCore/ExternalLoginProviderInfo.cs | 25 ++++++++++++ .../AspNetCore/IExternalLoginProvider.cs | 30 +++++++++++++++ .../AbpIdentityAspNetCoreTestBase.cs | 17 ++++++++- .../AbpIdentityAspNetCoreTestModule.cs | 8 ++++ .../AspNetCore/AbpSignInManager_Tests.cs | 15 -------- .../AspNetCore/ExternalLoginProvider_Tests.cs | 19 ++++++++++ .../AspNetCore/FakeExternalLoginProvider.cs | 34 +++++++++++++++++ 10 files changed, 192 insertions(+), 21 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderDictionary.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderInfo.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IExternalLoginProvider.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreOptions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreOptions.cs index ad42a23f9a..d7c5f57265 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreOptions.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreOptions.cs @@ -6,5 +6,12 @@ /// Default: true. /// public bool ConfigureAuthentication { get; set; } = true; + + public ExternalLoginProviderDictionary ExternalLoginProviders { get; } + + public AbpIdentityAspNetCoreOptions() + { + ExternalLoginProviders = new ExternalLoginProviderDictionary(); + } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs index 30f9407e6e..85e39ff8c7 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -9,6 +10,8 @@ namespace Volo.Abp.Identity.AspNetCore { public class AbpSignInManager : SignInManager { + protected AbpIdentityAspNetCoreOptions AbpOptions { get; } + public AbpSignInManager( UserManager userManager, IHttpContextAccessor contextAccessor, @@ -16,7 +19,8 @@ namespace Volo.Abp.Identity.AspNetCore IOptions optionsAccessor, ILogger> logger, IAuthenticationSchemeProvider schemes, - IUserConfirmation confirmation + IUserConfirmation confirmation, + IOptions options ) : base( userManager, contextAccessor, @@ -26,16 +30,42 @@ namespace Volo.Abp.Identity.AspNetCore schemes, confirmation) { - + AbpOptions = options.Value; } - public override Task PasswordSignInAsync( + public override async Task PasswordSignInAsync( string userName, string password, bool isPersistent, bool lockoutOnFailure) { - return base.PasswordSignInAsync(userName, password, isPersistent, lockoutOnFailure); + foreach (var externalLoginProviderInfo in AbpOptions.ExternalLoginProviders.Values) + { + var externalLoginProvider = (IExternalLoginProvider) Context.RequestServices + .GetRequiredService(externalLoginProviderInfo.Type); + + if (await externalLoginProvider.TryAuthenticateAsync(userName, password)) + { + var user = await UserManager.FindByNameAsync(userName); + if (user == null) + { + user = await externalLoginProvider.CreateUserAsync(userName); + //TODO: TenantId, LoginProvider, Password, NormalizeNames + //TODO: Set default roles + await UserManager.CreateAsync(user); + } + else + { + await externalLoginProvider.UpdateUserAsync(user); + //TODO: LoginProvider + await UserManager.UpdateAsync(user); + } + + return await SignInOrTwoFactorAsync(user, isPersistent); + } + } + + return await base.PasswordSignInAsync(userName, password, isPersistent, lockoutOnFailure); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderDictionary.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderDictionary.cs new file mode 100644 index 0000000000..bca983cb86 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderDictionary.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class ExternalLoginProviderDictionary : Dictionary + { + /// + /// Adds or replaces a provider. + /// + public void Add([NotNull] string name) + where TProvider : IExternalLoginProvider + { + this[name] = new ExternalLoginProviderInfo(name, typeof(TProvider)); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderInfo.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderInfo.cs new file mode 100644 index 0000000000..86ef94d58f --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderInfo.cs @@ -0,0 +1,25 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class ExternalLoginProviderInfo + { + public string Name { get; } + + public Type Type + { + get => _type; + set => _type = Check.NotNull(value, nameof(value)); + } + private Type _type; + + public ExternalLoginProviderInfo( + [NotNull] string name, + [NotNull] Type type) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + Type = Check.AssignableTo(type, nameof(type)); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IExternalLoginProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IExternalLoginProvider.cs new file mode 100644 index 0000000000..eabed8d628 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IExternalLoginProvider.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Identity.AspNetCore +{ + public interface IExternalLoginProvider //TODO: A base class to simplift implementing this! + { + /// + /// Used to try authenticate a user by this source. + /// + /// User name or email address + /// Plain password of the user + /// True, indicates that this used has authenticated by this source + Task TryAuthenticateAsync(string userName, string plainPassword); + + /// + /// This method is called when a user is authenticated by this source but the user does not exists yet. + /// So, the source should create the user and fill the properties. + /// + /// User name + /// Newly created user + Task CreateUserAsync(string userName); + + /// + /// This method is called after an existing user is authenticated by this source. + /// It can be used to update some properties of the user by the source. + /// + /// The user that can be updated + Task UpdateUserAsync(IdentityUser user); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestBase.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestBase.cs index a66bb129bb..c9e11f49b3 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestBase.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestBase.cs @@ -1,9 +1,24 @@ -using Volo.Abp.AspNetCore.TestBase; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.AspNetCore.TestBase; namespace Volo.Abp.Identity.AspNetCore { public abstract class AbpIdentityAspNetCoreTestBase : AbpAspNetCoreIntegratedTestBase { + protected virtual async Task GetResponseAsStringAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) + { + var response = await GetResponseAsync(url, expectedStatusCode); + return await response.Content.ReadAsStringAsync(); + } + protected virtual async Task GetResponseAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) + { + var response = await Client.GetAsync(url); + response.StatusCode.ShouldBe(expectedStatusCode); + return response; + } } } diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestModule.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestModule.cs index 79815d3941..7b39096a0f 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestModule.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreTestModule.cs @@ -24,6 +24,14 @@ namespace Volo.Abp.Identity.AspNetCore }); } + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.ExternalLoginProviders.Add(FakeExternalLoginProvider.Name); + }); + } + public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs index 212c341733..a99f2d7212 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs @@ -35,20 +35,5 @@ namespace Volo.Abp.Identity.AspNetCore result.ShouldBe("Failed"); } - - //TODO: Move to a better common place ---------------------------------------------------- - - protected virtual async Task GetResponseAsStringAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) - { - var response = await GetResponseAsync(url, expectedStatusCode); - return await response.Content.ReadAsStringAsync(); - } - - protected virtual async Task GetResponseAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) - { - var response = await Client.GetAsync(url); - response.StatusCode.ShouldBe(expectedStatusCode); - return response; - } } } diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs new file mode 100644 index 0000000000..89e12d47a1 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class ExternalLoginProvider_Tests : AbpIdentityAspNetCoreTestBase + { + [Fact] + public async Task Should_SignIn_With_ExternalLoginProvider() + { + var result = await GetResponseAsStringAsync( + "api/signin-test/password?userName=ext_user&password=abc" + ); + + result.ShouldBe("Succeeded"); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs new file mode 100644 index 0000000000..8dacdae65e --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class FakeExternalLoginProvider : IExternalLoginProvider, ITransientDependency + { + public const string Name = "Fake"; + + public Task TryAuthenticateAsync(string userName, string plainPassword) + { + return Task.FromResult( + userName == "ext_user" && plainPassword == "abc" + ); + } + + public Task CreateUserAsync(string userName) + { + return Task.FromResult( + new IdentityUser( + Guid.NewGuid(), + userName, + "test@abp.io" + ) + ); + } + + public Task UpdateUserAsync(IdentityUser user) + { + return Task.CompletedTask; + } + } +} From def92907a382da4056551cd90a0ed99ce7228990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 15:19:06 +0300 Subject: [PATCH 03/13] Introduce ExternalLoginProviderBase --- .../AspNetCore/ExternalLoginProviderBase.cs | 16 ++++++++++++++++ .../AspNetCore/FakeExternalLoginProvider.cs | 11 +++-------- 2 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs new file mode 100644 index 0000000000..9871fb788c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Identity.AspNetCore +{ + public abstract class ExternalLoginProviderBase : IExternalLoginProvider + { + public abstract Task TryAuthenticateAsync(string userName, string plainPassword); + + public abstract Task CreateUserAsync(string userName); + + public virtual Task UpdateUserAsync(IdentityUser user) + { + return Task.CompletedTask; + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs index 8dacdae65e..c95d65143f 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs @@ -4,18 +4,18 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.Identity.AspNetCore { - public class FakeExternalLoginProvider : IExternalLoginProvider, ITransientDependency + public class FakeExternalLoginProvider : ExternalLoginProviderBase, ITransientDependency { public const string Name = "Fake"; - public Task TryAuthenticateAsync(string userName, string plainPassword) + public override Task TryAuthenticateAsync(string userName, string plainPassword) { return Task.FromResult( userName == "ext_user" && plainPassword == "abc" ); } - public Task CreateUserAsync(string userName) + public override Task CreateUserAsync(string userName) { return Task.FromResult( new IdentityUser( @@ -25,10 +25,5 @@ namespace Volo.Abp.Identity.AspNetCore ) ); } - - public Task UpdateUserAsync(IdentityUser user) - { - return Task.CompletedTask; - } } } From 30d7afb5e6a37c04172d49c6221a4094413e62f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 15:45:46 +0300 Subject: [PATCH 04/13] Set TenantId --- .../Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs | 3 ++- .../Identity/AspNetCore/FakeExternalLoginProvider.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs index 85e39ff8c7..6e72ebf703 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Identity.AspNetCore { @@ -50,7 +51,7 @@ namespace Volo.Abp.Identity.AspNetCore if (user == null) { user = await externalLoginProvider.CreateUserAsync(userName); - //TODO: TenantId, LoginProvider, Password, NormalizeNames + //TODO: +TenantId, LoginProvider, Password, +NormalizeNames //TODO: Set default roles await UserManager.CreateAsync(user); } diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs index c95d65143f..64277765d2 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Identity.AspNetCore { @@ -8,6 +9,13 @@ namespace Volo.Abp.Identity.AspNetCore { public const string Name = "Fake"; + private readonly ICurrentTenant _currentTenant; + + public FakeExternalLoginProvider(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + public override Task TryAuthenticateAsync(string userName, string plainPassword) { return Task.FromResult( @@ -21,7 +29,8 @@ namespace Volo.Abp.Identity.AspNetCore new IdentityUser( Guid.NewGuid(), userName, - "test@abp.io" + "test@abp.io", + tenantId: _currentTenant.Id ) ); } From e98084fdf60e4957320b8791a78b0c30bfd863f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 16:30:51 +0300 Subject: [PATCH 05/13] Refactor: Extract class RandomPasswordGenerator --- .../Pages/Account/Register.cshtml.cs | 47 +---------- .../Abp/Identity/RandomPasswordGenerator.cs | 84 +++++++++++++++++++ .../IdentityClaimTypeManager_Tests.cs | 2 - .../Identity/RandomPasswordGenerator_Tests.cs | 23 +++++ 4 files changed, 111 insertions(+), 45 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RandomPasswordGenerator.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RandomPasswordGenerator_Tests.cs diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index 6baefc6745..d6b7a862cf 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -19,6 +19,7 @@ namespace Volo.Abp.Account.Web.Pages.Account public class RegisterModel : AccountPageModel { protected IAccountAppService AccountAppService { get; } + protected RandomPasswordGenerator RandomPasswordGenerator { get; } [BindProperty(SupportsGet = true)] public string ReturnUrl { get; set; } @@ -35,9 +36,10 @@ namespace Volo.Abp.Account.Web.Pages.Account [BindProperty(SupportsGet = true)] public string ExternalLoginAuthSchema { get; set; } - public RegisterModel(IAccountAppService accountAppService) + public RegisterModel(IAccountAppService accountAppService, RandomPasswordGenerator randomPasswordGenerator) { AccountAppService = accountAppService; + RandomPasswordGenerator = randomPasswordGenerator; } public virtual async Task OnGetAsync() @@ -94,7 +96,7 @@ namespace Volo.Abp.Account.Web.Pages.Account registerDto.EmailAddress = Input.EmailAddress; registerDto.UserName = Input.EmailAddress; - registerDto.Password = GeneratePassword(); + registerDto.Password = await RandomPasswordGenerator.CreateAsync(); } else { @@ -137,47 +139,6 @@ namespace Volo.Abp.Account.Web.Pages.Account } } - protected virtual string GeneratePassword() - { - var random = new Random(); - var options = UserManager.Options.Password; - int length = random.Next(options.RequiredLength, IdentityUserConsts.MaxPasswordLength - 1); - - bool nonAlphanumeric = options.RequireNonAlphanumeric; - bool digit = options.RequireDigit; - bool lowercase = options.RequireLowercase; - bool uppercase = options.RequireUppercase; - - StringBuilder password = new StringBuilder(); - - while (password.Length < length) - { - char c = (char)random.Next(32, 126); - - password.Append(c); - - if (char.IsDigit(c)) - digit = false; - else if (char.IsLower(c)) - lowercase = false; - else if (char.IsUpper(c)) - uppercase = false; - else if (!char.IsLetterOrDigit(c)) - nonAlphanumeric = false; - } - - if (nonAlphanumeric) - password.Append((char)random.Next(33, 48)); - if (digit) - password.Append((char)random.Next(48, 58)); - if (lowercase) - password.Append((char)random.Next(97, 123)); - if (uppercase) - password.Append((char)random.Next(65, 91)); - - return password.ToString(); - } - protected virtual async Task CheckSelfRegistrationAsync() { if (!await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled) || diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RandomPasswordGenerator.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RandomPasswordGenerator.cs new file mode 100644 index 0000000000..40643d8a76 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RandomPasswordGenerator.cs @@ -0,0 +1,84 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Identity +{ + /// + /// This class can be used to generate random password + /// based on the rules defined in the . + /// + public class RandomPasswordGenerator : ITransientDependency + { + public const int MinPasswordLength = 32; + + protected IdentityOptions Options { get; } + protected Random Random { get; } + + public RandomPasswordGenerator(IOptions options) + { + Options = options.Value; + Random = new Random(); + } + + public virtual Task CreateAsync() + { + var nonAlphanumeric = Options.Password.RequireNonAlphanumeric; + var digit = Options.Password.RequireDigit; + var lowercase = Options.Password.RequireLowercase; + var uppercase = Options.Password.RequireUppercase; + + var passwordBuilder = new StringBuilder(); + + var length = Math.Max(Options.Password.RequiredLength, MinPasswordLength); + while (passwordBuilder.Length < length) + { + var nextChar = (char)Random.Next(32, 126); + + passwordBuilder.Append(nextChar); + + if (char.IsDigit(nextChar)) + { + digit = false; + } + else if (char.IsLower(nextChar)) + { + lowercase = false; + } + else if (char.IsUpper(nextChar)) + { + uppercase = false; + } + else if (!char.IsLetterOrDigit(nextChar)) + { + nonAlphanumeric = false; + } + } + + if (nonAlphanumeric) + { + passwordBuilder.Append((char)Random.Next(33, 48)); + } + + if (digit) + { + passwordBuilder.Append((char)Random.Next(48, 58)); + } + + if (lowercase) + { + passwordBuilder.Append((char)Random.Next(97, 123)); + } + + if (uppercase) + { + passwordBuilder.Append((char)Random.Next(65, 91)); + } + + return Task.FromResult(passwordBuilder.ToString()); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityClaimTypeManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityClaimTypeManager_Tests.cs index c5c68d40c6..1ef603e6b3 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityClaimTypeManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityClaimTypeManager_Tests.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using Shouldly; using Xunit; diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RandomPasswordGenerator_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RandomPasswordGenerator_Tests.cs new file mode 100644 index 0000000000..f28fed08d8 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RandomPasswordGenerator_Tests.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Identity +{ + public class RandomPasswordGenerator_Tests : AbpIdentityDomainTestBase + { + private readonly RandomPasswordGenerator _randomPasswordGenerator; + + public RandomPasswordGenerator_Tests() + { + _randomPasswordGenerator = GetRequiredService(); + } + + [Fact] + public async Task CreateAsync() + { + var password = await _randomPasswordGenerator.CreateAsync(); + password.Length.ShouldBeGreaterThanOrEqualTo(RandomPasswordGenerator.MinPasswordLength); + } + } +} From cc9477910797fbafd32503801079cfffa36a203a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 17:10:48 +0300 Subject: [PATCH 06/13] Move create/update to the provider. --- .../Identity/AspNetCore/AbpSignInManager.cs | 8 +--- .../Volo/Abp/Identity/IdentityUserConsts.cs | 5 +++ .../Volo/Abp/Identity/IdentityUser.cs | 45 ++++++++++++++++++- .../Volo/Abp/Identity/IdentityUserStore.cs | 22 ++++----- .../AspNetCore/FakeExternalLoginProvider.cs | 35 +++++++++++---- 5 files changed, 86 insertions(+), 29 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs index 6e72ebf703..7775ca46d8 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Volo.Abp.MultiTenancy; namespace Volo.Abp.Identity.AspNetCore { @@ -14,7 +13,7 @@ namespace Volo.Abp.Identity.AspNetCore protected AbpIdentityAspNetCoreOptions AbpOptions { get; } public AbpSignInManager( - UserManager userManager, + IdentityUserManager userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory claimsFactory, IOptions optionsAccessor, @@ -51,15 +50,10 @@ namespace Volo.Abp.Identity.AspNetCore if (user == null) { user = await externalLoginProvider.CreateUserAsync(userName); - //TODO: +TenantId, LoginProvider, Password, +NormalizeNames - //TODO: Set default roles - await UserManager.CreateAsync(user); } else { await externalLoginProvider.UpdateUserAsync(user); - //TODO: LoginProvider - await UserManager.UpdateAsync(user); } return await SignInOrTwoFactorAsync(user, isPersistent); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs index c374cbc678..01c6e285a9 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs @@ -32,5 +32,10 @@ namespace Volo.Abp.Identity /// Default value: 256 /// public static int MaxSecurityStampLength { get; set; } = 256; + + /// + /// Default value: 16 + /// + public static int MaxLoginProviderLength { get; set; } = 16; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs index ea1f5ccf72..1649ce8a76 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs @@ -66,6 +66,8 @@ namespace Volo.Abp.Identity [DisableAuditing] public virtual string SecurityStamp { get; protected internal set; } + public virtual string LoginProvider { get; protected set; } + /// /// Gets or sets a telephone number for the user. /// @@ -133,7 +135,11 @@ namespace Volo.Abp.Identity { } - public IdentityUser(Guid id, [NotNull] string userName, [NotNull] string email, Guid? tenantId = null) + public IdentityUser( + Guid id, + [NotNull] string userName, + [NotNull] string email, + Guid? tenantId = null) { Check.NotNull(userName, nameof(userName)); Check.NotNull(email, nameof(email)); @@ -254,7 +260,8 @@ namespace Volo.Abp.Identity Check.NotNull(loginProvider, nameof(loginProvider)); Check.NotNull(providerKey, nameof(providerKey)); - Logins.RemoveAll(userLogin => userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey); + Logins.RemoveAll(userLogin => + userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey); } [CanBeNull] @@ -316,9 +323,43 @@ namespace Volo.Abp.Identity ); } + public virtual void SetLoginProvider([CanBeNull] string loginProvider) + { + LoginProvider = Check.Length(loginProvider, nameof(loginProvider), + IdentityUserConsts.MaxLoginProviderLength); + } + + /// + /// Use for regular email confirmation. + /// Using this skips the confirmation process and directly sets the . + /// + public virtual void SetEmailConfirmed(bool confirmed) + { + EmailConfirmed = confirmed; + } + + public virtual void SetPhoneNumberConfirmed(bool confirmed) + { + PhoneNumberConfirmed = confirmed; + } + public override string ToString() { return $"{base.ToString()}, UserName = {UserName}"; } + + /// + /// Normally use to change the phone number + /// in the application code. + /// This method is to directly set it with a confirmation information. + /// + /// + /// + /// + public void SetPhoneNumber(string phoneNumber, bool confirmed) + { + PhoneNumber = phoneNumber; + PhoneNumberConfirmed = !phoneNumber.IsNullOrWhiteSpace() && confirmed; + } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs index 81269adf5a..17a0fbd39d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs @@ -291,7 +291,7 @@ namespace Volo.Abp.Identity /// /// The user to retrieve the password hash for. /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user has a password. If the + /// A containing a flag indicating if the specified user has a password. If the /// user has a password the returned value with be true, otherwise it will be false. public virtual Task HasPasswordAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) { @@ -326,7 +326,7 @@ namespace Volo.Abp.Identity { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Role {0} does not exist!", normalizedRoleName)); } - + await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken); user.AddRole(role.Id); @@ -353,7 +353,7 @@ namespace Volo.Abp.Identity } await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken); - + user.RemoveRole(role.Id); } @@ -384,15 +384,15 @@ namespace Volo.Abp.Identity /// 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 + /// 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 virtual async Task IsInRoleAsync( - [NotNull] IdentityUser user, + [NotNull] IdentityUser user, [NotNull] string normalizedRoleName, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - + Check.NotNull(user, nameof(user)); Check.NotNullOrWhiteSpace(normalizedRoleName, nameof(normalizedRoleName)); @@ -589,7 +589,7 @@ namespace Volo.Abp.Identity Check.NotNull(user, nameof(user)); - user.EmailConfirmed = confirmed; + user.SetEmailConfirmed(confirmed); return Task.CompletedTask; } @@ -864,7 +864,7 @@ namespace Volo.Abp.Identity Check.NotNull(user, nameof(user)); - user.PhoneNumberConfirmed = confirmed; + user.SetPhoneNumberConfirmed(confirmed); return Task.CompletedTask; } @@ -928,7 +928,7 @@ namespace Volo.Abp.Identity /// The user whose two factor authentication enabled status should be set. /// The used to propagate notifications that the operation should be canceled. /// - /// The that represents the asynchronous operation, containing a flag indicating whether the specified + /// The that represents the asynchronous operation, containing a flag indicating whether the specified /// has two factor authentication enabled or not. /// public virtual Task GetTwoFactorEnabledAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) @@ -946,7 +946,7 @@ namespace Volo.Abp.Identity /// 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. + /// The contains a list of users, if any, that contain the specified claim. /// public virtual async Task> GetUsersForClaimAsync([NotNull] Claim claim, CancellationToken cancellationToken = default) { @@ -963,7 +963,7 @@ namespace Volo.Abp.Identity /// 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. + /// The contains a list of users, if any, that are in the specified role. /// public virtual async Task> GetUsersInRoleAsync([NotNull] string normalizedRoleName, CancellationToken cancellationToken = default) { diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs index 64277765d2..409a0e1e24 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; @@ -10,10 +11,14 @@ namespace Volo.Abp.Identity.AspNetCore public const string Name = "Fake"; private readonly ICurrentTenant _currentTenant; + private readonly IdentityUserManager _userManager; + private RandomPasswordGenerator _randomPasswordGenerator; - public FakeExternalLoginProvider(ICurrentTenant currentTenant) + public FakeExternalLoginProvider(ICurrentTenant currentTenant, IdentityUserManager userManager, RandomPasswordGenerator randomPasswordGenerator) { _currentTenant = currentTenant; + _userManager = userManager; + _randomPasswordGenerator = randomPasswordGenerator; } public override Task TryAuthenticateAsync(string userName, string plainPassword) @@ -23,16 +28,28 @@ namespace Volo.Abp.Identity.AspNetCore ); } - public override Task CreateUserAsync(string userName) + public override async Task CreateUserAsync(string userName) { - return Task.FromResult( - new IdentityUser( - Guid.NewGuid(), - userName, - "test@abp.io", - tenantId: _currentTenant.Id - ) + var user = new IdentityUser( + Guid.NewGuid(), + userName, + "test@abp.io", + tenantId: _currentTenant.Id //Setting TenantId is responsibility of the provider! ); + + user.SetLoginProvider(Name); + user.SetEmailConfirmed(true); //Setting this is responsibility of the provider! + user.SetPhoneNumber("123123", true); + + (await _userManager.CreateAsync(user, await _randomPasswordGenerator.CreateAsync())).CheckErrors(); + (await _userManager.AddDefaultRolesAsync(user)).CheckErrors(); + + return user; + } + + public override async Task UpdateUserAsync(IdentityUser user) + { + (await _userManager.SetEmailAsync(user, "test-updated@abp.io")).CheckErrors(); } } } From ed792609d7605b2b679062027160c638532e2c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 18:25:47 +0300 Subject: [PATCH 07/13] Fully implement the ExternalLoginProviderBase --- .../Identity/AspNetCore/AbpSignInManager.cs | 4 +- .../AspNetCore/ExternalLoginProviderBase.cs | 143 +++++++++++++++++- .../AspNetCore/ExternalLoginUserInfo.cs | 36 +++++ .../AspNetCore/IExternalLoginProvider.cs | 8 +- .../Volo/Abp/Identity/IdentityUser.cs | 3 + .../AspNetCore/AbpSignInManager_Tests.cs | 2 +- .../AspNetCore/FakeExternalLoginProvider.cs | 49 +++--- 7 files changed, 206 insertions(+), 39 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginUserInfo.cs diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs index 7775ca46d8..f9072be6e8 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs @@ -49,11 +49,11 @@ namespace Volo.Abp.Identity.AspNetCore var user = await UserManager.FindByNameAsync(userName); if (user == null) { - user = await externalLoginProvider.CreateUserAsync(userName); + user = await externalLoginProvider.CreateUserAsync(userName, externalLoginProviderInfo.Name); } else { - await externalLoginProvider.UpdateUserAsync(user); + await externalLoginProvider.UpdateUserAsync(user, externalLoginProviderInfo.Name); } return await SignInOrTwoFactorAsync(user, isPersistent); diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs index 9871fb788c..ca40510f3a 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs @@ -1,16 +1,151 @@ -using System.Threading.Tasks; +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Identity.AspNetCore { public abstract class ExternalLoginProviderBase : IExternalLoginProvider { + protected IGuidGenerator GuidGenerator { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IdentityUserManager UserManager { get; } + protected RandomPasswordGenerator RandomPasswordGenerator { get; } + + protected ExternalLoginProviderBase( + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant, + IdentityUserManager userManager, + RandomPasswordGenerator randomPasswordGenerator) + { + GuidGenerator = guidGenerator; + CurrentTenant = currentTenant; + UserManager = userManager; + RandomPasswordGenerator = randomPasswordGenerator; + } + public abstract Task TryAuthenticateAsync(string userName, string plainPassword); - public abstract Task CreateUserAsync(string userName); + public virtual async Task CreateUserAsync(string userName, string providerName) + { + var externalUser = await GetUserInfoAsync(userName); + NormalizeExternalLoginUserInfo(externalUser, userName); + + var user = new IdentityUser( + GuidGenerator.Create(), + userName, + externalUser.Email, + tenantId: CurrentTenant.Id + ); + + user.Name = externalUser.Name; + user.Surname = externalUser.Surname; + + user.SetLoginProvider(providerName); + + user.SetEmailConfirmed(externalUser.EmailConfirmed ?? false); + user.SetPhoneNumber(externalUser.PhoneNumber, externalUser.PhoneNumberConfirmed ?? false); + + (await UserManager.CreateAsync(user, await RandomPasswordGenerator.CreateAsync())).CheckErrors(); + + if (externalUser.TwoFactorEnabled != null) + { + (await UserManager.SetTwoFactorEnabledAsync(user, externalUser.TwoFactorEnabled.Value)).CheckErrors(); + } + + (await UserManager.AddDefaultRolesAsync(user)).CheckErrors(); + (await UserManager.AddLoginAsync( + user, + new UserLoginInfo( + providerName, + externalUser.ProviderKey , + providerName + ) + ) + ).CheckErrors(); + + return user; + } + + public virtual async Task UpdateUserAsync(IdentityUser user, string providerName) + { + var externalUser = await GetUserInfoAsync(user); + NormalizeExternalLoginUserInfo(externalUser, user.UserName); + + if (!externalUser.Name.IsNullOrWhiteSpace()) + { + user.Name = externalUser.Name; + } + + if (!externalUser.Surname.IsNullOrWhiteSpace()) + { + user.Surname = externalUser.Surname; + } + + if (user.PhoneNumber != externalUser.PhoneNumber) + { + if (!externalUser.PhoneNumber.IsNullOrWhiteSpace()) + { + await UserManager.SetPhoneNumberAsync(user, externalUser.PhoneNumber); + user.SetPhoneNumberConfirmed(externalUser.PhoneNumberConfirmed == true); + } + } + else + { + if (!user.PhoneNumber.IsNullOrWhiteSpace() && + user.PhoneNumberConfirmed == false && + externalUser.PhoneNumberConfirmed == true) + { + user.SetPhoneNumberConfirmed(true); + } + } + + if (!string.Equals(user.Email, externalUser.Email, StringComparison.OrdinalIgnoreCase)) + { + (await UserManager.SetEmailAsync(user, externalUser.Email)).CheckErrors(); + user.SetEmailConfirmed(externalUser.EmailConfirmed ?? false); + } + + if (externalUser.TwoFactorEnabled != null) + { + (await UserManager.SetTwoFactorEnabledAsync(user, externalUser.TwoFactorEnabled.Value)).CheckErrors(); + } + + var userLogin = user.Logins.FirstOrDefault(l => l.LoginProvider == providerName); + if (userLogin != null) + { + if (userLogin.ProviderKey != externalUser.ProviderKey) + { + (await UserManager.RemoveLoginAsync(user, providerName, userLogin.ProviderKey)).CheckErrors(); + (await UserManager.AddLoginAsync(user, new UserLoginInfo(providerName, externalUser.ProviderKey, providerName))).CheckErrors(); + } + } + else + { + (await UserManager.AddLoginAsync(user, new UserLoginInfo(providerName, externalUser.ProviderKey, providerName))).CheckErrors(); + } + + (await UserManager.UpdateAsync(user)).CheckErrors(); + } + + protected abstract Task GetUserInfoAsync(string userName); + + protected virtual Task GetUserInfoAsync(IdentityUser user) + { + return GetUserInfoAsync(user.UserName); + } - public virtual Task UpdateUserAsync(IdentityUser user) + private static void NormalizeExternalLoginUserInfo( + ExternalLoginUserInfo externalUser, + string userName + ) { - return Task.CompletedTask; + if (externalUser.ProviderKey.IsNullOrWhiteSpace()) + { + externalUser.ProviderKey = userName; + } } } } diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginUserInfo.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginUserInfo.cs new file mode 100644 index 0000000000..530a9bcfe0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginUserInfo.cs @@ -0,0 +1,36 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Identity.AspNetCore +{ + public class ExternalLoginUserInfo + { + [CanBeNull] + public string Name { get; set; } + + [CanBeNull] + public string Surname { get; set; } + + [CanBeNull] + public string PhoneNumber { get; set; } + + [NotNull] + public string Email { get; private set; } + + [CanBeNull] + public bool? PhoneNumberConfirmed { get; set; } + + [CanBeNull] + public bool? EmailConfirmed { get; set; } + + [CanBeNull] + public bool? TwoFactorEnabled { get; set; } + + [CanBeNull] + public string ProviderKey { get; set; } + + public ExternalLoginUserInfo([System.Diagnostics.CodeAnalysis.NotNull] string email) + { + Email = Check.NotNullOrWhiteSpace(email, nameof(email)); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IExternalLoginProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IExternalLoginProvider.cs index eabed8d628..29d0f0df3c 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IExternalLoginProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/IExternalLoginProvider.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.Identity.AspNetCore { - public interface IExternalLoginProvider //TODO: A base class to simplift implementing this! + public interface IExternalLoginProvider { /// /// Used to try authenticate a user by this source. @@ -17,14 +17,16 @@ namespace Volo.Abp.Identity.AspNetCore /// So, the source should create the user and fill the properties. /// /// User name + /// The name of this provider /// Newly created user - Task CreateUserAsync(string userName); + Task CreateUserAsync(string userName, string providerName); /// /// This method is called after an existing user is authenticated by this source. /// It can be used to update some properties of the user by the source. /// + /// The name of this provider /// The user that can be updated - Task UpdateUserAsync(IdentityUser user); + Task UpdateUserAsync(IdentityUser user, string providerName); } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs index 1649ce8a76..8a38dd1498 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs @@ -30,11 +30,13 @@ namespace Volo.Abp.Identity /// /// Gets or sets the Name for the user. /// + [CanBeNull] public virtual string Name { get; set; } /// /// Gets or sets the Surname for the user. /// + [CanBeNull] public virtual string Surname { get; set; } /// @@ -71,6 +73,7 @@ namespace Volo.Abp.Identity /// /// Gets or sets a telephone number for the user. /// + [CanBeNull] public virtual string PhoneNumber { get; protected internal set; } /// diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs index a99f2d7212..dc5d961f9a 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpSignInManager_Tests.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.Identity.AspNetCore public class AbpSignInManager_Tests : AbpIdentityAspNetCoreTestBase { [Fact] - public async Task Should_Resolve_AbpSignInManager() + public void Should_Resolve_AbpSignInManager() { var signInManager = GetRequiredService>(); signInManager.ShouldBeOfType(); diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs index 409a0e1e24..7fd2e344c5 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; namespace Volo.Abp.Identity.AspNetCore @@ -10,15 +10,14 @@ namespace Volo.Abp.Identity.AspNetCore { public const string Name = "Fake"; - private readonly ICurrentTenant _currentTenant; - private readonly IdentityUserManager _userManager; - private RandomPasswordGenerator _randomPasswordGenerator; - - public FakeExternalLoginProvider(ICurrentTenant currentTenant, IdentityUserManager userManager, RandomPasswordGenerator randomPasswordGenerator) + public FakeExternalLoginProvider( + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant, + IdentityUserManager userManager, + RandomPasswordGenerator randomPasswordGenerator) + : base(guidGenerator, currentTenant, userManager, randomPasswordGenerator) { - _currentTenant = currentTenant; - _userManager = userManager; - _randomPasswordGenerator = randomPasswordGenerator; + } public override Task TryAuthenticateAsync(string userName, string plainPassword) @@ -28,28 +27,20 @@ namespace Volo.Abp.Identity.AspNetCore ); } - public override async Task CreateUserAsync(string userName) + protected override Task GetUserInfoAsync(string userName) { - var user = new IdentityUser( - Guid.NewGuid(), - userName, - "test@abp.io", - tenantId: _currentTenant.Id //Setting TenantId is responsibility of the provider! + return Task.FromResult( + new ExternalLoginUserInfo("ext_user@test.com") + { + Name = "Test Name", + Surname = "Test Surname", + EmailConfirmed = true, + TwoFactorEnabled = false, + PhoneNumber = "123", + PhoneNumberConfirmed = false, + ProviderKey = "123" + } ); - - user.SetLoginProvider(Name); - user.SetEmailConfirmed(true); //Setting this is responsibility of the provider! - user.SetPhoneNumber("123123", true); - - (await _userManager.CreateAsync(user, await _randomPasswordGenerator.CreateAsync())).CheckErrors(); - (await _userManager.AddDefaultRolesAsync(user)).CheckErrors(); - - return user; - } - - public override async Task UpdateUserAsync(IdentityUser user) - { - (await _userManager.SetEmailAsync(user, "test-updated@abp.io")).CheckErrors(); } } } From ece1842150860044e58b2b705ff2fdd2e26d6535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 18:39:59 +0300 Subject: [PATCH 08/13] Update FakeExternalLoginProvider.cs --- .../AspNetCore/FakeExternalLoginProvider.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs index 7fd2e344c5..ae9bdb78bb 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; @@ -29,16 +28,18 @@ namespace Volo.Abp.Identity.AspNetCore protected override Task GetUserInfoAsync(string userName) { + // The only required property is the email, which is set in the constructor. + return Task.FromResult( new ExternalLoginUserInfo("ext_user@test.com") { - Name = "Test Name", - Surname = "Test Surname", - EmailConfirmed = true, - TwoFactorEnabled = false, - PhoneNumber = "123", - PhoneNumberConfirmed = false, - ProviderKey = "123" + Name = "Test Name", //optional, if the provider knows it + Surname = "Test Surname", //optional, if the provider knows it + EmailConfirmed = true, //optional, if the provider knows it + TwoFactorEnabled = false, //optional, if the provider knows it + PhoneNumber = "123", //optional, if the provider knows it + PhoneNumberConfirmed = false, //optional, if the provider knows it + ProviderKey = "123" //The id of the user on the provider side } ); } From d12ef80a9d431e2968fd96b6466fe19962c52561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 18:56:33 +0300 Subject: [PATCH 09/13] Fix ExternalLoginProviderBase --- .../AspNetCore/ExternalLoginProviderBase.cs | 9 ++++- .../AspNetCore/ExternalLoginProvider_Tests.cs | 40 ++++++++++++++++++- .../AspNetCore/FakeExternalLoginProvider.cs | 18 +++++++-- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs index ca40510f3a..930718aa5b 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; +using Volo.Abp.Domain.Repositories; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; @@ -14,16 +15,20 @@ namespace Volo.Abp.Identity.AspNetCore protected IdentityUserManager UserManager { get; } protected RandomPasswordGenerator RandomPasswordGenerator { get; } + protected IIdentityUserRepository IdentityUserRepository { get; } + protected ExternalLoginProviderBase( IGuidGenerator guidGenerator, ICurrentTenant currentTenant, IdentityUserManager userManager, - RandomPasswordGenerator randomPasswordGenerator) + RandomPasswordGenerator randomPasswordGenerator, + IIdentityUserRepository identityUserRepository) { GuidGenerator = guidGenerator; CurrentTenant = currentTenant; UserManager = userManager; RandomPasswordGenerator = randomPasswordGenerator; + IdentityUserRepository = identityUserRepository; } public abstract Task TryAuthenticateAsync(string userName, string plainPassword); @@ -113,6 +118,8 @@ namespace Volo.Abp.Identity.AspNetCore (await UserManager.SetTwoFactorEnabledAsync(user, externalUser.TwoFactorEnabled.Value)).CheckErrors(); } + await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.Logins); + var userLogin = user.Logins.FirstOrDefault(l => l.LoginProvider == providerName); if (userLogin != null) { diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs index 89e12d47a1..da0178a819 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using Shouldly; using Xunit; @@ -9,11 +10,48 @@ namespace Volo.Abp.Identity.AspNetCore [Fact] public async Task Should_SignIn_With_ExternalLoginProvider() { + // User does not exists yet + (await GetRequiredService().FindByNameAsync("ext_user")).ShouldBeNull(); + + // Try to login + var result = await GetResponseAsStringAsync( "api/signin-test/password?userName=ext_user&password=abc" ); result.ShouldBe("Succeeded"); + + // User should be created now + + await CheckUserAsync(); + + // Re-login + + result = await GetResponseAsStringAsync( + "api/signin-test/password?userName=ext_user&password=abc" + ); + + result.ShouldBe("Succeeded"); + + await CheckUserAsync(); + } + + private async Task CheckUserAsync() + { + var userRepository = GetRequiredService(); + + var user = await userRepository.FindByNormalizedUserNameAsync("EXT_USER"); + user.Name.ShouldBe("Test Name"); + user.Surname.ShouldBe("Test Surname"); + user.EmailConfirmed.ShouldBeTrue(); + user.TwoFactorEnabled.ShouldBeFalse(); + user.PhoneNumber.ShouldBe("123"); + user.PhoneNumberConfirmed.ShouldBeFalse(); + user.LoginProvider.ShouldBe("Fake"); + + var logins = user.Logins.Where(l => l.LoginProvider == "Fake").ToList(); + logins.Count.ShouldBe(1); + logins[0].ProviderKey.ShouldBe("123"); } } } diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs index ae9bdb78bb..95eb3e6958 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; @@ -13,8 +14,14 @@ namespace Volo.Abp.Identity.AspNetCore IGuidGenerator guidGenerator, ICurrentTenant currentTenant, IdentityUserManager userManager, - RandomPasswordGenerator randomPasswordGenerator) - : base(guidGenerator, currentTenant, userManager, randomPasswordGenerator) + RandomPasswordGenerator randomPasswordGenerator, + IIdentityUserRepository identityUserRepository) + : base( + guidGenerator, + currentTenant, + userManager, + randomPasswordGenerator, + identityUserRepository) { } @@ -28,7 +35,10 @@ namespace Volo.Abp.Identity.AspNetCore protected override Task GetUserInfoAsync(string userName) { - // The only required property is the email, which is set in the constructor. + if (userName != "ext_user") + { + throw new ArgumentException(); + } return Task.FromResult( new ExternalLoginUserInfo("ext_user@test.com") From dc05e3e301f40e040bc6d0d8db0bf7a78fd24673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 22:00:28 +0300 Subject: [PATCH 10/13] Replace user.LoginProvider by IsExternal. --- .../Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs | 6 ++++-- .../Volo/Abp/Identity/IdentityUser.cs | 8 +------- .../IdentityDbContextModelBuilderExtensions.cs | 3 +++ .../Identity/AspNetCore/ExternalLoginProvider_Tests.cs | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs index 930718aa5b..631a1593d2 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs @@ -48,12 +48,12 @@ namespace Volo.Abp.Identity.AspNetCore user.Name = externalUser.Name; user.Surname = externalUser.Surname; - user.SetLoginProvider(providerName); + user.IsExternal = true; user.SetEmailConfirmed(externalUser.EmailConfirmed ?? false); user.SetPhoneNumber(externalUser.PhoneNumber, externalUser.PhoneNumberConfirmed ?? false); - (await UserManager.CreateAsync(user, await RandomPasswordGenerator.CreateAsync())).CheckErrors(); + (await UserManager.CreateAsync(user)).CheckErrors(); if (externalUser.TwoFactorEnabled != null) { @@ -134,6 +134,8 @@ namespace Volo.Abp.Identity.AspNetCore (await UserManager.AddLoginAsync(user, new UserLoginInfo(providerName, externalUser.ProviderKey, providerName))).CheckErrors(); } + user.IsExternal = true; + (await UserManager.UpdateAsync(user)).CheckErrors(); } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs index 8a38dd1498..287328573d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs @@ -68,7 +68,7 @@ namespace Volo.Abp.Identity [DisableAuditing] public virtual string SecurityStamp { get; protected internal set; } - public virtual string LoginProvider { get; protected set; } + public virtual bool IsExternal { get; set; } /// /// Gets or sets a telephone number for the user. @@ -326,12 +326,6 @@ namespace Volo.Abp.Identity ); } - public virtual void SetLoginProvider([CanBeNull] string loginProvider) - { - LoginProvider = Check.Length(loginProvider, nameof(loginProvider), - IdentityUserConsts.MaxLoginProviderLength); - } - /// /// Use for regular email confirmation. /// Using this skips the confirmation process and directly sets the . diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs index 373abbe804..8efe82c5ce 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs @@ -43,6 +43,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.Property(u => u.LockoutEnabled).HasDefaultValue(false) .HasColumnName(nameof(IdentityUser.LockoutEnabled)); + b.Property(u => u.IsExternal).IsRequired().HasDefaultValue(false) + .HasColumnName(nameof(IdentityUser.IsExternal)); + b.Property(u => u.AccessFailedCount) .If(!builder.IsUsingOracle(), p => p.HasDefaultValue(0)) .HasColumnName(nameof(IdentityUser.AccessFailedCount)); diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs index da0178a819..ee9a56432e 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/ExternalLoginProvider_Tests.cs @@ -47,7 +47,7 @@ namespace Volo.Abp.Identity.AspNetCore user.TwoFactorEnabled.ShouldBeFalse(); user.PhoneNumber.ShouldBe("123"); user.PhoneNumberConfirmed.ShouldBeFalse(); - user.LoginProvider.ShouldBe("Fake"); + user.IsExternal.ShouldBeTrue(); var logins = user.Logins.Where(l => l.LoginProvider == "Fake").ToList(); logins.Count.ShouldBe(1); From 7bfe05ce15655c1be4f8fa6c05f12473a0f5b3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 22:21:35 +0300 Subject: [PATCH 11/13] Re-generate migration for the app startup template --- ...ial.Designer.cs => 20200806191520_Initial.Designer.cs} | 8 +++++++- ...0200710064926_Initial.cs => 20200806191520_Initial.cs} | 1 + .../MyProjectNameMigrationsDbContextModelSnapshot.cs | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) rename templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/{20200710064926_Initial.Designer.cs => 20200806191520_Initial.Designer.cs} (99%) rename templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/{20200710064926_Initial.cs => 20200806191520_Initial.cs} (99%) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200710064926_Initial.Designer.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200806191520_Initial.Designer.cs similarity index 99% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200710064926_Initial.Designer.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200806191520_Initial.Designer.cs index e2ca97db45..e5df7f6948 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200710064926_Initial.Designer.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200806191520_Initial.Designer.cs @@ -11,7 +11,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(MyProjectNameMigrationsDbContext))] - [Migration("20200710064926_Initial")] + [Migration("20200806191520_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -619,6 +619,12 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200710064926_Initial.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200806191520_Initial.cs similarity index 99% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200710064926_Initial.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200806191520_Initial.cs index 2c82babc5b..57d7dadb66 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200710064926_Initial.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/20200806191520_Initial.cs @@ -244,6 +244,7 @@ namespace MyCompanyName.MyProjectName.Migrations EmailConfirmed = table.Column(nullable: false, defaultValue: false), PasswordHash = table.Column(maxLength: 256, nullable: true), SecurityStamp = table.Column(maxLength: 256, nullable: false), + IsExternal = table.Column(nullable: false, defaultValue: false), PhoneNumber = table.Column(maxLength: 16, nullable: true), PhoneNumberConfirmed = table.Column(nullable: false, defaultValue: false), TwoFactorEnabled = table.Column(nullable: false, defaultValue: false), diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/MyProjectNameMigrationsDbContextModelSnapshot.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/MyProjectNameMigrationsDbContextModelSnapshot.cs index f06dda1edc..305502aa64 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/MyProjectNameMigrationsDbContextModelSnapshot.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations/MyProjectNameMigrationsDbContextModelSnapshot.cs @@ -617,6 +617,12 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); From 6cac77d262dc01111a9c3b3b44f568d45c3f9bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 22:21:59 +0300 Subject: [PATCH 12/13] Don't set password for external login. --- .../Pages/Account/Register.cshtml.cs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index d6b7a862cf..536c17ab26 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -19,7 +19,6 @@ namespace Volo.Abp.Account.Web.Pages.Account public class RegisterModel : AccountPageModel { protected IAccountAppService AccountAppService { get; } - protected RandomPasswordGenerator RandomPasswordGenerator { get; } [BindProperty(SupportsGet = true)] public string ReturnUrl { get; set; } @@ -36,10 +35,9 @@ namespace Volo.Abp.Account.Web.Pages.Account [BindProperty(SupportsGet = true)] public string ExternalLoginAuthSchema { get; set; } - public RegisterModel(IAccountAppService accountAppService, RandomPasswordGenerator randomPasswordGenerator) + public RegisterModel(IAccountAppService accountAppService) { AccountAppService = accountAppService; - RandomPasswordGenerator = randomPasswordGenerator; } public virtual async Task OnGetAsync() @@ -80,11 +78,6 @@ namespace Volo.Abp.Account.Web.Pages.Account { await CheckSelfRegistrationAsync(); - var registerDto = new RegisterDto() - { - AppName = "MVC" - }; - if (IsExternalLogin) { var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync(); @@ -94,34 +87,40 @@ namespace Volo.Abp.Account.Web.Pages.Account return RedirectToPage("./Login"); } - registerDto.EmailAddress = Input.EmailAddress; - registerDto.UserName = Input.EmailAddress; - registerDto.Password = await RandomPasswordGenerator.CreateAsync(); + await RegisterExternalUserAsync(externalLoginInfo, Input.EmailAddress); } else { - ValidateModel(); - - registerDto.EmailAddress = Input.EmailAddress; - registerDto.Password = Input.Password; - registerDto.UserName = Input.UserName; + await RegisterLocalUserAsync(); } - var userDto = await AccountAppService.RegisterAsync(registerDto); - var user = await UserManager.GetByIdAsync(userDto.Id); - await SignInManager.SignInAsync(user, isPersistent: false); + return Redirect(ReturnUrl ?? "~/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow! + } - if (IsExternalLogin) - { - await AddToUserLogins(user); - } + protected virtual async Task RegisterLocalUserAsync() + { + ValidateModel(); - return Redirect(ReturnUrl ?? "~/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow! + var userDto = await AccountAppService.RegisterAsync( + new RegisterDto + { + AppName = "MVC", + EmailAddress = Input.EmailAddress, + Password = Input.Password, + UserName = Input.UserName + } + ); + + var user = await UserManager.GetByIdAsync(userDto.Id); + await SignInManager.SignInAsync(user, isPersistent: true); } - protected virtual async Task AddToUserLogins(IdentityUser user) + protected virtual async Task RegisterExternalUserAsync(ExternalLoginInfo externalLoginInfo, string emailAddress) { - var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync(); + var user = new IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id); + + (await UserManager.CreateAsync(user)).CheckErrors(); + (await UserManager.AddDefaultRolesAsync(user)).CheckErrors(); var userLoginAlreadyExists = user.Logins.Any(x => x.TenantId == user.TenantId && @@ -130,13 +129,14 @@ namespace Volo.Abp.Account.Web.Pages.Account if (!userLoginAlreadyExists) { - user.AddLogin(new UserLoginInfo( - externalLoginInfo.LoginProvider, - externalLoginInfo.ProviderKey, - externalLoginInfo.ProviderDisplayName - ) - ); + (await UserManager.AddLoginAsync(user, new UserLoginInfo( + externalLoginInfo.LoginProvider, + externalLoginInfo.ProviderKey, + externalLoginInfo.ProviderDisplayName + ))).CheckErrors(); } + + await SignInManager.SignInAsync(user, isPersistent: true); } protected virtual async Task CheckSelfRegistrationAsync() From 2ad0beb94a29c5876b0003da3021a56e023d2e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 6 Aug 2020 22:31:03 +0300 Subject: [PATCH 13/13] Delete unused RandomPasswordGenerator. --- .../AspNetCore/ExternalLoginProviderBase.cs | 4 - .../Abp/Identity/RandomPasswordGenerator.cs | 84 ------------------- .../AspNetCore/FakeExternalLoginProvider.cs | 2 - .../Identity/RandomPasswordGenerator_Tests.cs | 23 ----- 4 files changed, 113 deletions(-) delete mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RandomPasswordGenerator.cs delete mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RandomPasswordGenerator_Tests.cs diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs index 631a1593d2..d1db82e254 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/ExternalLoginProviderBase.cs @@ -13,21 +13,17 @@ namespace Volo.Abp.Identity.AspNetCore protected IGuidGenerator GuidGenerator { get; } protected ICurrentTenant CurrentTenant { get; } protected IdentityUserManager UserManager { get; } - protected RandomPasswordGenerator RandomPasswordGenerator { get; } - protected IIdentityUserRepository IdentityUserRepository { get; } protected ExternalLoginProviderBase( IGuidGenerator guidGenerator, ICurrentTenant currentTenant, IdentityUserManager userManager, - RandomPasswordGenerator randomPasswordGenerator, IIdentityUserRepository identityUserRepository) { GuidGenerator = guidGenerator; CurrentTenant = currentTenant; UserManager = userManager; - RandomPasswordGenerator = randomPasswordGenerator; IdentityUserRepository = identityUserRepository; } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RandomPasswordGenerator.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RandomPasswordGenerator.cs deleted file mode 100644 index 40643d8a76..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RandomPasswordGenerator.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Identity -{ - /// - /// This class can be used to generate random password - /// based on the rules defined in the . - /// - public class RandomPasswordGenerator : ITransientDependency - { - public const int MinPasswordLength = 32; - - protected IdentityOptions Options { get; } - protected Random Random { get; } - - public RandomPasswordGenerator(IOptions options) - { - Options = options.Value; - Random = new Random(); - } - - public virtual Task CreateAsync() - { - var nonAlphanumeric = Options.Password.RequireNonAlphanumeric; - var digit = Options.Password.RequireDigit; - var lowercase = Options.Password.RequireLowercase; - var uppercase = Options.Password.RequireUppercase; - - var passwordBuilder = new StringBuilder(); - - var length = Math.Max(Options.Password.RequiredLength, MinPasswordLength); - while (passwordBuilder.Length < length) - { - var nextChar = (char)Random.Next(32, 126); - - passwordBuilder.Append(nextChar); - - if (char.IsDigit(nextChar)) - { - digit = false; - } - else if (char.IsLower(nextChar)) - { - lowercase = false; - } - else if (char.IsUpper(nextChar)) - { - uppercase = false; - } - else if (!char.IsLetterOrDigit(nextChar)) - { - nonAlphanumeric = false; - } - } - - if (nonAlphanumeric) - { - passwordBuilder.Append((char)Random.Next(33, 48)); - } - - if (digit) - { - passwordBuilder.Append((char)Random.Next(48, 58)); - } - - if (lowercase) - { - passwordBuilder.Append((char)Random.Next(97, 123)); - } - - if (uppercase) - { - passwordBuilder.Append((char)Random.Next(65, 91)); - } - - return Task.FromResult(passwordBuilder.ToString()); - } - } -} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs index 95eb3e6958..6a54ccf7e3 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/FakeExternalLoginProvider.cs @@ -14,13 +14,11 @@ namespace Volo.Abp.Identity.AspNetCore IGuidGenerator guidGenerator, ICurrentTenant currentTenant, IdentityUserManager userManager, - RandomPasswordGenerator randomPasswordGenerator, IIdentityUserRepository identityUserRepository) : base( guidGenerator, currentTenant, userManager, - randomPasswordGenerator, identityUserRepository) { diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RandomPasswordGenerator_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RandomPasswordGenerator_Tests.cs deleted file mode 100644 index f28fed08d8..0000000000 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/RandomPasswordGenerator_Tests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Threading.Tasks; -using Shouldly; -using Xunit; - -namespace Volo.Abp.Identity -{ - public class RandomPasswordGenerator_Tests : AbpIdentityDomainTestBase - { - private readonly RandomPasswordGenerator _randomPasswordGenerator; - - public RandomPasswordGenerator_Tests() - { - _randomPasswordGenerator = GetRequiredService(); - } - - [Fact] - public async Task CreateAsync() - { - var password = await _randomPasswordGenerator.CreateAsync(); - password.Length.ShouldBeGreaterThanOrEqualTo(RandomPasswordGenerator.MinPasswordLength); - } - } -}