Sure, will share the code privately via email
we have deployed the FE stack on Cloudfront + s3. This connects via Application Load balancer to backend stack which is on Elastic Container Service.
Note that in the example I gave, I called the endpoint from the Swagger UI.
Also the X-Forwarded-For header showing a valid IP address is the exact same request which was logged by Audit Log (which recorded a loopback IP address). I would assume that the IP address would be taken from that X-Forwarded-For header.
Hi,
I created an application template with Angular UI from scratch but I didn't reproduce your problem.
If
Impersonation
permission is granted for the admin user, I can see the "Login with this tenant" button like above.
Can you also generate a new template from scratch? So, we can understand the problem is in ABP or your solution. If the problem is in your solution, we can only focus on your solution.
Hi, i found the bug, had to add these to angular env files
impersonation: { tenantImpersonation: true, userImpersonation: true, }
this is not in the migration article - https://abp.io/docs/latest/release-info/migration-guides/abp-9-0
Hi,
Did you follow migration guide properly?
https://abp.io/docs/latest/release-info/migration-guides/abp-9-0
Hi yes, we did follow the guide.
Helpful, think we can close this off. Thanks
I've updated the login model and it stops redirecting to the change password screen for external users. Can you advise if this is okay (i've only commented out one block of code)?
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Owl.reCAPTCHA;
using Volo.Abp.Account.Security.Recaptcha;
using Volo.Abp.Account.ExternalProviders;
using Volo.Abp.Identity;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
using Volo.Abp.Account.Public.Web.Pages.Account;
using Volo.Abp.Account.Public.Web;
using Volo.Abp.Account;
using Volo.Abp.DependencyInjection;
// namespace Volo.Abp.Account.Public.Web.Pages.Account;
namespace Eduverse.Pages.Account
{
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(LoginModel))]
public class EduverseLoginModel: LoginModel
{
public EduverseLoginModel(IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IAbpRecaptchaValidatorFactory recaptchaValidatorFactory, IAccountExternalProviderAppService accountExternalProviderAppService, ICurrentPrincipalAccessor currentPrincipalAccessor, IOptions<IdentityOptions> identityOptions, IOptionsSnapshot<reCAPTCHAOptions> reCaptchaOptions) : base(schemeProvider, accountOptions, recaptchaValidatorFactory, accountExternalProviderAppService, currentPrincipalAccessor, identityOptions, reCaptchaOptions)
{
System.Console.WriteLine("EduverseLoginModel");
}
[UnitOfWork]
public override async Task<IActionResult> OnGetExternalLoginCallbackAsync(string remoteError = null)
{
//TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample)
/* Also did not implement these:
* - Logout(string logoutId)
*/
if (remoteError != null)
{
Logger.LogWarning($"External login callback error: {remoteError}");
return RedirectToPage("./Login");
}
await IdentityOptions.SetAsync();
var loginInfo = await SignInManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
Logger.LogWarning("External login info is not available");
return RedirectToPage("./Login");
}
IsLinkLogin = await VerifyLinkTokenAsync();
// Commented out for resolve ED-520 - Prevent prompt change pw for external users
// if (await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin))
// {
// var user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
// if (user != null && user.PasswordHash == null)
// {
// await StoreChangePasswordUser(user);
// return RedirectToPage("./ChangePassword", new {
// returnUrl = ReturnUrl ?? "/",
// returnUrlHash = ReturnUrlHash
// });
// }
// }
var askUserPasswordResult = await ShouldAskUserPasswordAsync(loginInfo);
if (askUserPasswordResult != null)
{
return askUserPasswordResult;
}
var result = await SignInManager.ExternalLoginSignInAsync(
loginInfo.LoginProvider,
loginInfo.ProviderKey,
isPersistent: true,
bypassTwoFactor: true
);
if (!result.Succeeded)
{
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = "Login" + result
});
}
if (result.IsLockedOut)
{
var user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
await StoreLockedUser(user);
Logger.LogWarning($"Cannot proceed because user is locked out!");
return RedirectToPage("./LockedOut", new {
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash
});
}
if (result.IsNotAllowed)
{
Logger.LogWarning($"External login callback error: User is Not Allowed!");
var user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
if (user == null)
{
Logger.LogWarning($"External login callback error: User is Not Found!");
return RedirectToPage("./Login");
}
if (user.ShouldChangePasswordOnNextLogin || await UserManager.ShouldPeriodicallyChangePasswordAsync(user))
{
await StoreChangePasswordUser(user);
return RedirectToPage("./ChangePassword", new {
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash
});
}
if (user.IsActive)
{
await StoreConfirmUser(user);
return RedirectToPage("./ConfirmUser", new {
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash
});
}
return RedirectToPage("./Login");
}
if (result.Succeeded)
{
var user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
if (user == null)
{
Logger.LogWarning($"External login callback error: User is Not Found!");
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(user)))
{
await AccountExternalLoginAppService.SetPasswordVerifiedAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
}
if (IsLinkLogin)
{
using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(user)))
{
await IdentityLinkUserAppService.LinkAsync(new LinkUserInput
{
UserId = LinkUserId.Value,
TenantId = LinkTenantId,
Token = LinkToken
});
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = IdentityProSecurityLogActionConsts.LinkUser,
UserName = user.UserName,
ExtraProperties =
{
{ IdentityProSecurityLogActionConsts.LinkTargetTenantId, LinkTenantId },
{ IdentityProSecurityLogActionConsts.LinkTargetUserId, LinkUserId }
}
});
using (CurrentTenant.Change(LinkTenantId))
{
var targetUser = await UserManager.GetByIdAsync(LinkUserId.Value);
using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(targetUser)))
{
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = IdentityProSecurityLogActionConsts.LinkUser,
UserName = targetUser.UserName,
ExtraProperties =
{
{ IdentityProSecurityLogActionConsts.LinkTargetTenantId, targetUser.TenantId },
{ IdentityProSecurityLogActionConsts.LinkTargetUserId, targetUser.Id }
}
});
}
}
}
}
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = result.ToIdentitySecurityLogAction(),
UserName = user.UserName
});
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
//TODO: Handle other cases for result!
var email = loginInfo.Principal.FindFirstValue(AbpClaimTypes.Email) ?? loginInfo.Principal.FindFirstValue(ClaimTypes.Email);
var externalUser = email.IsNullOrWhiteSpace() ? null : await UserManager.FindByEmailAsync(email);
if (externalUser == null)
{
return RedirectToPage("./Register", new {
isExternalLogin = true,
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
linkTenantId = LinkTenantId,
linkUserId = LinkUserId,
linkToken = LinkToken
});
}
if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null)
{
CheckIdentityErrors(await UserManager.AddLoginAsync(externalUser, loginInfo));
}
if (await HasRequiredIdentitySettings())
{
Logger.LogWarning($"New external user is created but confirmation is required!");
await StoreConfirmUser(externalUser);
return RedirectToPage("./ConfirmUser", new {
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash
});
}
askUserPasswordResult = await ShouldAskUserPasswordAsync(loginInfo);
if (askUserPasswordResult != null)
{
return askUserPasswordResult;
}
await SignInManager.SignInAsync(externalUser, false , loginInfo.LoginProvider);
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(externalUser.Id, externalUser.TenantId);
if (IsLinkLogin)
{
using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(externalUser)))
{
await IdentityLinkUserAppService.LinkAsync(new LinkUserInput
{
UserId = LinkUserId.Value,
TenantId = LinkTenantId,
Token = LinkToken
});
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = IdentityProSecurityLogActionConsts.LinkUser,
UserName = externalUser.UserName,
ExtraProperties =
{
{ IdentityProSecurityLogActionConsts.LinkTargetTenantId, LinkTenantId },
{ IdentityProSecurityLogActionConsts.LinkTargetUserId, LinkUserId }
}
});
using (CurrentTenant.Change(LinkTenantId))
{
var targetUser = await UserManager.GetByIdAsync(LinkUserId.Value);
using (CurrentPrincipalAccessor.Change(await SignInManager.CreateUserPrincipalAsync(targetUser)))
{
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = IdentityProSecurityLogActionConsts.LinkUser,
UserName = targetUser.UserName,
ExtraProperties =
{
{ IdentityProSecurityLogActionConsts.LinkTargetTenantId, targetUser.TenantId },
{ IdentityProSecurityLogActionConsts.LinkTargetUserId, targetUser.Id }
}
});
}
}
}
}
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = result.ToIdentitySecurityLogAction(),
UserName = externalUser.Name
});
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
}
}
After investigating, i've realised that the user is not redirected to a /Register screen. I've tested overriding the ChangePassword model and breakpoints are being hit
Seems like Login > Microsoft SSO > change password screen