Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: account external callback login #9

Merged
merged 1 commit into from
Jul 7, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions modules/account/common.props
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@
<Description>an abp module that provider account service, such as login, 2fa, account link,
impersonation, settings. </Description>
<PackageTags>abp-module</PackageTags>
<Version>1.3.0</Version>
<PackageVersion>1.3.0</PackageVersion>
<Version>1.3.1</Version>
<PackageVersion>1.3.1</PackageVersion>
</PropertyGroup>

<ItemGroup>
@@ -17,4 +17,4 @@
</None>
</ItemGroup>

</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@

namespace Passingwind.Abp.Account;

public class AccountSecurityLogPagedListRequestDto : PagedAndSortedResultRequestDto
public class AccountSecurityLogsPagedListRequestDto : PagedAndSortedResultRequestDto
{
public string? ApplicationName { get; set; }
public string? Identity { get; set; }
Original file line number Diff line number Diff line change
@@ -7,5 +7,5 @@ namespace Passingwind.Abp.Account;

public interface IAccountSecurityLogsAppService : IApplicationService
{
Task<PagedResultDto<IdentitySecurityLogDto>> GetListAsync(AccountSecurityLogPagedListRequestDto input);
Task<PagedResultDto<IdentitySecurityLogsDto>> GetListAsync(AccountSecurityLogsPagedListRequestDto input);
}
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ public class AccountExternalAppService : AccountAppBaseService, IAccountExternal
{
protected HttpContext? HttpContext { get; }
protected IJsonSerializer JsonSerializer { get; }
protected SignInManager<IdentityUser> SignInManager { get; }
protected SignInManager SignInManager { get; }
protected IOptions<IdentityOptions> IdentityOptions { get; }
protected IdentitySecurityLogManager IdentitySecurityLogManager { get; }
protected IdentityUserManager UserManager { get; }
@@ -38,7 +38,7 @@ public class AccountExternalAppService : AccountAppBaseService, IAccountExternal
protected ILocalEventBus LocalEventBus { get; }

public AccountExternalAppService(
SignInManager<IdentityUser> signInManager,
SignInManager signInManager,
IHttpContextAccessor httpContextAccessor,
IOptions<IdentityOptions> identityOptions,
IdentitySecurityLogManager identitySecurityLogManager,
@@ -102,9 +102,6 @@ public virtual async Task<AccountExternalLoginResultDto> CallbackAsync([NotNull]
Logger.LogDebug("Received external login principal claims: \n{LogClaimsString}", logClaimsString);
}

// TODO: Check Tenant ???
//

var result = await ExternalLoginSignInAsync(loginInfo);

if (result.ToString() != SignInResult.Failed.ToString())
@@ -122,23 +119,18 @@ public virtual async Task<AccountExternalLoginResultDto> CallbackAsync([NotNull]
throw new BusinessException(AccountErrorCodes.ExternalLoginUserNotFound);
}

// TODO: two-factory check!
// sign in
await SignInManager.SignInAsync(user, false);
// try login again
result = await ExternalLoginSignInAsync(loginInfo);

await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
if (result.ToString() != SignInResult.Failed.ToString())
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = IdentitySecurityLogActionConsts.LoginSucceeded,
UserName = user.Name
});

await LocalEventBus.PublishAsync(new UserLoginEvent(user.Id, UserLoginEvent.ExternalLogin), onUnitOfWorkComplete: true);
return new AccountExternalLoginResultDto(GetAccountLoginResultType(result))
{
RedirectUrl = input.ReturnUrl,
};
}

return new AccountExternalLoginResultDto(AccountLoginResultType.Success)
{
RedirectUrl = input.ReturnUrl,
};
throw new BusinessException(AccountErrorCodes.ExternalLoginUserNotFound);
}

protected virtual async Task<SignInResult> ExternalLoginSignInAsync(ExternalLoginInfo loginInfo)
@@ -164,6 +156,7 @@ await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = result.ToIdentitySecurityLogAction(),
UserName = user.UserName,
});
}

Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ public AccountSecurityLogsAppService(IIdentitySecurityLogRepository securityLogR
SecurityLogRepository = securityLogRepository;
}

public virtual async Task<PagedResultDto<IdentitySecurityLogDto>> GetListAsync(AccountSecurityLogPagedListRequestDto input)
public virtual async Task<PagedResultDto<IdentitySecurityLogsDto>> GetListAsync(AccountSecurityLogsPagedListRequestDto input)
{
var count = await SecurityLogRepository.GetCountAsync(
startTime: input.StartTime,
@@ -42,6 +42,6 @@ public virtual async Task<PagedResultDto<IdentitySecurityLogDto>> GetListAsync(A
clientId: input.ClientId,
correlationId: input.CorrelationId);

return new PagedResultDto<IdentitySecurityLogDto>(count, ObjectMapper.Map<List<IdentitySecurityLog>, List<IdentitySecurityLogDto>>(list));
return new PagedResultDto<IdentitySecurityLogsDto>(count, ObjectMapper.Map<List<IdentitySecurityLog>, List<IdentitySecurityLogsDto>>(list));
}
}
Original file line number Diff line number Diff line change
@@ -17,29 +17,35 @@ public NullAccountTwoFactorTokenSender(ILogger<NullAccountTwoFactorTokenSender>

public virtual Task SendAsync(IdentityUser user, string provider, string token, CancellationToken cancellationToken = default)
{
Logger.LogWarning("Two-factor token not sent. Please implement '{0}' first.", typeof(IAccountTwoFactorTokenSender).FullName);
Logger.NotImplement(token, typeof(IAccountTwoFactorTokenSender).FullName);

return Task.CompletedTask;
}

public virtual Task SendEmailConfirmationTokenAsync(IdentityUser user, string token, CancellationToken cancellationToken = default)
{
Logger.LogWarning("Token not sent. Please implement '{0}' first.", typeof(IAccountTwoFactorTokenSender).FullName);
Logger.NotImplement(token, typeof(IAccountTwoFactorTokenSender).FullName);

return Task.CompletedTask;
}

public virtual Task SendChangePhoneNumberTokenAsync(IdentityUser user, string phoneNumber, string token, CancellationToken cancellationToken = default)
{
Logger.LogWarning("Token not sent. Please implement '{0}' first.", typeof(IAccountTwoFactorTokenSender).FullName);
Logger.NotImplement(token, typeof(IAccountTwoFactorTokenSender).FullName);

return Task.CompletedTask;
}

public virtual Task SendChangeEmailTokenAsync(IdentityUser user, string email, string token, CancellationToken cancellationToken = default)
{
Logger.LogWarning("Token not sent. Please implement '{0}' first.", typeof(IAccountTwoFactorTokenSender).FullName);
Logger.NotImplement(token, typeof(IAccountTwoFactorTokenSender).FullName);

return Task.CompletedTask;
}
}

internal static partial class NullAccountTwoFactorTokenSenderLoggerMessage
{
[LoggerMessage("Token '{Token}' not sent. Please implement '{TypeName}' first.", Level = LogLevel.Warning)]
internal static partial void NotImplement(this ILogger logger, string token, string? typeName);
}
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ public AccountExternalAuthController(IIdentityClientLoginAppService identityClie

[AllowAnonymous]
[HttpGet("identity/{provider}/login")]
public virtual async Task IdentityClientLoginAsync(string provider, string? returnUrl = null, string? returnUrlHash = null)
public virtual async Task LoginAsync(string provider, string? returnUrl = null, string? returnUrlHash = null)
{
var redirectUrl = Url.Action("callback", values: new { returnUrl, returnUrlHash });

Original file line number Diff line number Diff line change
@@ -25,9 +25,14 @@ public override async Task LoginInfoReceivedAsync(AccountExternalCallbackLoginIn
// check is debug mode
var identityClient = await IdentityClientRepository.FindByProviderNameAsync(providerName);

// TODO check tenant
//
if (identityClient?.IsDebugMode == true)
if (identityClient == null)
{
context.Handled = true;
context.Result = new NotFoundResult();
return;
}

if (identityClient.IsDebugMode)
{
Logger.LogWarning("YOU ARE USE DEBUG MODE FOR IDENTITY PROVIDER");
context.Handled = true;
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ public AccountSecurityLogsController(IAccountSecurityLogsAppService service)
}

[HttpGet]
public virtual Task<PagedResultDto<IdentitySecurityLogDto>> GetListAsync([FromQuery] AccountSecurityLogPagedListRequestDto input)
public virtual Task<PagedResultDto<IdentitySecurityLogsDto>> GetListAsync([FromQuery] AccountSecurityLogsPagedListRequestDto input)
{
return _service.GetListAsync(input);
}
Original file line number Diff line number Diff line change
@@ -7,9 +7,9 @@ namespace Passingwind.Abp.Identity;

public interface IIdentitySecurityLogAppService : IApplicationService
{
Task<PagedResultDto<IdentitySecurityLogDto>> GetListAsync(IdentitySecurityLogPagedListRequestDto input);
Task<PagedResultDto<IdentitySecurityLogsDto>> GetListAsync(IdentitySecurityLogPagedListRequestDto input);

Task<IdentitySecurityLogDto> GetAsync(Guid id);
Task<IdentitySecurityLogsDto> GetAsync(Guid id);

Task DeleteAsync(Guid id);
}
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@

namespace Passingwind.Abp.Identity;

public class IdentitySecurityLogDto : ExtensibleEntityDto<Guid>
public class IdentitySecurityLogsDto : ExtensibleEntityDto<Guid>
{
public Guid? TenantId { get; set; }

Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ public IdentityApplicationAutoMapperProfile()
CreateMap<OrganizationUnit, OrganizationUnitDto>()
.ForMember(x => x.RoleIds, x => x.MapFrom(r => r.Roles.Select(s => s.RoleId)));

CreateMap<IdentitySecurityLog, IdentitySecurityLogDto>();
CreateMap<IdentitySecurityLog, IdentitySecurityLogsDto>();

CreateMap<IdentityUserSettings, IdentityUserSettingsDto>().ReverseMap();
CreateMap<IdentityPasswordSettings, IdentityPasswordSettingsDto>().ReverseMap();
Original file line number Diff line number Diff line change
@@ -23,14 +23,14 @@ public virtual async Task DeleteAsync(Guid id)
await SecurityLogRepository.DeleteAsync(id);
}

public virtual async Task<IdentitySecurityLogDto> GetAsync(Guid id)
public virtual async Task<IdentitySecurityLogsDto> GetAsync(Guid id)
{
var entity = await SecurityLogRepository.GetAsync(id);

return ObjectMapper.Map<IdentitySecurityLog, IdentitySecurityLogDto>(entity);
return ObjectMapper.Map<IdentitySecurityLog, IdentitySecurityLogsDto>(entity);
}

public virtual async Task<PagedResultDto<IdentitySecurityLogDto>> GetListAsync(IdentitySecurityLogPagedListRequestDto input)
public virtual async Task<PagedResultDto<IdentitySecurityLogsDto>> GetListAsync(IdentitySecurityLogPagedListRequestDto input)
{
var count = await SecurityLogRepository.GetCountAsync(
input.StartTime,
@@ -57,6 +57,6 @@ public virtual async Task<PagedResultDto<IdentitySecurityLogDto>> GetListAsync(I
input.ClientId,
input.CorrelationId);

return new PagedResultDto<IdentitySecurityLogDto>(count, ObjectMapper.Map<List<IdentitySecurityLog>, List<IdentitySecurityLogDto>>(list));
return new PagedResultDto<IdentitySecurityLogsDto>(count, ObjectMapper.Map<List<IdentitySecurityLog>, List<IdentitySecurityLogsDto>>(list));
}
}
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ public SignInManager(
IdentityUserManager = identityUserManager;
}

protected override async Task<SignInResult?> PreSignInCheck(Volo.Abp.Identity.IdentityUser user)
protected override async Task<SignInResult?> PreSignInCheck(IdentityUser user)
{
if (!user.IsActive)
{
@@ -56,16 +56,23 @@ public SignInManager(
return null;
}

protected virtual async Task<SignInResult> PostSignInCheck(IdentityUser user, SignInResult signInResult)
/// <summary>
/// Post action when user signin
/// </summary>
protected virtual Task<SignInResult> PostSignInCheckAsync(IdentityUser user, SignInResult signInResult)
{
if (await IdentityUserManager.ShouldChangePasswordAsync(user))
{
return AbpSignInResult.ChangePasswordRequired;
}
return Task.FromResult(signInResult);
}

return signInResult;
/// <summary>
/// Check if the user has need change password before login
/// </summary>
public virtual async Task<bool> ShouldChangePasswordAsync(IdentityUser user)
{
return await IdentityUserManager.ShouldChangePasswordAsync(user);
}

/// <inheritdoc />
public override async Task<bool> IsTwoFactorEnabledAsync(IdentityUser user)
{
var behaviour = await SettingProvider.GetEnumValueAsync<IdentityTwofactoryBehaviour>(IdentitySettingNamesV2.Twofactor.TwoFactorBehaviour);
@@ -79,55 +86,46 @@ public override async Task<bool> IsTwoFactorEnabledAsync(IdentityUser user)
return await base.IsTwoFactorEnabledAsync(user);
}

public virtual async Task<bool> ShouldChangePasswordAsync(IdentityUser user)
/// <summary>
/// This is to call the protection method <see cref="SignInOrTwoFactorAsync"/>
/// </summary>
public virtual async Task<SignInResult> DirectSignInAsync(IdentityUser user, bool isPersistent, string? loginProvider = null, bool bypassTwoFactor = false, bool bypassChangePassword = false)
{
return await IdentityUserManager.ShouldChangePasswordAsync(user);
return await SignInOrTwoFactorAsync(
user,
isPersistent: isPersistent,
loginProvider: loginProvider,
bypassTwoFactor: bypassTwoFactor,
bypassChangePassword: bypassChangePassword);
}

// TODO
// review this when upgrade to .net8
protected override async Task<SignInResult> SignInOrTwoFactorAsync(IdentityUser user, bool isPersistent, string? loginProvider = null, bool bypassTwoFactor = false)
/// <inheritdoc />
protected virtual async Task<SignInResult> SignInOrTwoFactorAsync(IdentityUser user, bool isPersistent, string? loginProvider = null, bool bypassTwoFactor = false, bool bypassChangePassword = false)
{
if (await ShouldChangePasswordAsync(user))
SignInResult result = SignInResult.Failed;

if (!bypassChangePassword && await ShouldChangePasswordAsync(user))
{
var userId = await UserManager.GetUserIdAsync(user);
await Context.SignInAsync(IdentityV2Constants.ChangePasswordUserIdScheme, StoreChangePasswordInfo(userId, loginProvider));

return AbpSignInResult.ChangePasswordRequired;
}

if (!bypassTwoFactor && await IsTwoFactorEnabledAsync(user))
{
if (!await IsTwoFactorClientRememberedAsync(user))
{
// Store the userId for use after two factor check
var userId = await UserManager.GetUserIdAsync(user);
await Context.SignInAsync(IdentityConstants.TwoFactorUserIdScheme, StoreTwoFactorInfo(userId, loginProvider));

return SignInResult.TwoFactorRequired;
}
}

// Cleanup external cookie
if (loginProvider != null)
{
await Context.SignOutAsync(IdentityConstants.ExternalScheme);
}

if (loginProvider == null)
{
await SignInWithClaimsAsync(user, isPersistent, new Claim[] { new Claim("amr", "pwd") });
result = AbpSignInResult.ChangePasswordRequired;
}
else
{
await SignInAsync(user, isPersistent, loginProvider);
result = await base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor);
}

return SignInResult.Success;
return await PostSignInCheckAsync(user, result);
}

// return await base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor);
/// <inheritdoc />
protected override async Task<SignInResult> SignInOrTwoFactorAsync(IdentityUser user, bool isPersistent, string? loginProvider = null, bool bypassTwoFactor = false)
{
return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor, false);
}

/// <inheritdoc />
public override async Task SignOutAsync()
{
await base.SignOutAsync();
@@ -154,23 +152,6 @@ public override async Task SignOutAsync()
return await UserManager.FindByIdAsync(userId);
}

/// <summary>
/// Creates a claims principal for the specified 2fa information.
/// </summary>
/// <param name="userId">The user whose is logging in via 2fa.</param>
/// <param name="loginProvider">The 2fa provider.</param>
/// <returns>A <see cref="ClaimsPrincipal"/> containing the user 2fa information.</returns>
internal static ClaimsPrincipal StoreTwoFactorInfo(string userId, string? loginProvider)
{
var identity = new ClaimsIdentity(IdentityConstants.TwoFactorUserIdScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, userId));
if (loginProvider != null)
{
identity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, loginProvider));
}
return new ClaimsPrincipal(identity);
}

/// <summary>
/// Creates a claims principal for the specified user information that requires change password.
/// </summary>
Original file line number Diff line number Diff line change
@@ -20,13 +20,13 @@ public IdentitySecurityLogController(IIdentitySecurityLogAppService service)
}

[HttpGet("{id}")]
public virtual Task<IdentitySecurityLogDto> GetAsync(Guid id)
public virtual Task<IdentitySecurityLogsDto> GetAsync(Guid id)
{
return _service.GetAsync(id);
}

[HttpGet]
public virtual Task<PagedResultDto<IdentitySecurityLogDto>> GetListAsync([FromQuery] IdentitySecurityLogPagedListRequestDto input)
public virtual Task<PagedResultDto<IdentitySecurityLogsDto>> GetListAsync([FromQuery] IdentitySecurityLogPagedListRequestDto input)
{
return _service.GetListAsync(input);
}

Unchanged files with check annotations Beta

public enum FileAccessMode
{
/// <summary>
/// Anonymous can read & upload

Check warning on line 6 in modules/file-management/src/Passingwind.Abp.FileManagement.Domain.Shared/Files/FileAccessMode.cs

GitHub Actions / build

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'
/// </summary>
Anonymous = 0,
/// <summary>
/// </summary>
Readonly = 1,
/// <summary>
/// Only authorized user can read & upload

Check warning on line 14 in modules/file-management/src/Passingwind.Abp.FileManagement.Domain.Shared/Files/FileAccessMode.cs

GitHub Actions / build

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'
/// </summary>
Authorized = 10,
/// <summary>
/// Only created user can read & upload

Check warning on line 18 in modules/file-management/src/Passingwind.Abp.FileManagement.Domain.Shared/Files/FileAccessMode.cs

GitHub Actions / build

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'
/// </summary>
Private = 20,
}
/// <summary>
/// TokenProvider that generates tokens from the user's security stamp and notifies a user via email.
/// </summary>
/// <typeparam name="TUser"></typeparam>

Check warning on line 13 in modules/identity/src/Passingwind.Abp.Identity.Domain/TokenProviders/EmailTokenProviderV2.cs

GitHub Actions / build

Unused 'typeparam' element in a documentation comment (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1228)
public class EmailTokenProviderV2<TUser> : EmailTokenProvider<TUser> where TUser : class
{
protected IdentityUserTwoFactorManager UserTwoFactorManager { get; }
/// <summary>
/// Checks if a two-factor authentication token can be generated for the specified <paramref name="user"/>.
/// </summary>
/// <param name="manager"></param>

Check warning on line 28 in modules/identity/src/Passingwind.Abp.Identity.Domain/TokenProviders/EmailTokenProviderV2.cs

GitHub Actions / build

Unused 'param' element in a documentation comment (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1228)
/// <param name="user"></param>
/// <exception cref="ArgumentNullException"><paramref name="manager"/> is <c>null</c>.</exception>
public override async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user)
/// <summary>
/// Used for authenticator code verification.
/// </summary>
/// <typeparam name="TUser"></typeparam>

Check warning on line 9 in modules/identity/src/Passingwind.Abp.Identity.Domain/TokenProviders/AuthenticatorTokenProviderV2.cs

GitHub Actions / build

Unused 'typeparam' element in a documentation comment (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1228)
public class AuthenticatorTokenProviderV2<TUser> : AuthenticatorTokenProvider<TUser> where TUser : class
{
/// <summary>
/// Checks if a two-factor authentication token can be generated for the specified <paramref name="user"/>.
/// </summary>
/// <param name="manager"></param>

Check warning on line 15 in modules/identity/src/Passingwind.Abp.Identity.Domain/TokenProviders/AuthenticatorTokenProviderV2.cs

GitHub Actions / build

Unused 'param' element in a documentation comment (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1228)
/// <param name="user"></param>

Check warning on line 16 in modules/identity/src/Passingwind.Abp.Identity.Domain/TokenProviders/AuthenticatorTokenProviderV2.cs

GitHub Actions / build

Unused 'param' element in a documentation comment (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1228)
public override async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user)
{
var key = await manager.GetAuthenticatorKeyAsync(user);
/// Returns a flag indicating whether the token provider can generate a token suitable for two-factor authentication token for
/// the specified <paramref name="user"/>.
/// </summary>
/// <param name="manager"></param>

Check warning on line 29 in modules/identity/src/Passingwind.Abp.Identity.Domain/TokenProviders/PhoneNumberTokenProviderV2.cs

GitHub Actions / build

Unused 'param' element in a documentation comment (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1228)
/// <param name="user"></param>

Check warning on line 30 in modules/identity/src/Passingwind.Abp.Identity.Domain/TokenProviders/PhoneNumberTokenProviderV2.cs

GitHub Actions / build

Unused 'param' element in a documentation comment (https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1228)
/// <exception cref="ArgumentNullException"><paramref name="manager"/> is <c>null</c>.</exception>
public override async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<IdentityUser> manager, IdentityUser user)
{