Hi @maliming!
Thank you for pointing me to the right direction.
I found out that the issue occurs in UserLookupService > FindByUserId. It seems that sometimes the tenant filter is not working correctly, although I changed the tenant.
I worked around by disabling multi-tenancy filter for the user lookup:
// Fix CmsUser Insert
TUser localUser;
using (_dataFilter.Disable<IMultiTenant>())
{
localUser = await _userRepository.FindAsync(id, cancellationToken: cancellationToken);
}
Seems a bit quick and dirty, but on the other hand, the id is unique anyway, so I don't see a problem doing the search globally. Since that change the issue is gone.
Therefore I'll close this issue.
hi @maliming,
I can't change to the host, as it would only find host users. I don't want to disable multi-tenancy, I absolutely need all items to be multi-tenant. I only want to let the users login from a unified login page without having to select the tenant from tenantbox or having different subdomains for each tenant.
the function of tenantId = await _extendedOrganizationUnitAppService.GetTenantByUsername(LoginInput.UserNameOrEmailAddress);
is quite simple:
public async Task<Guid?> GetTenantByUsername(string userName)
{
var user = await _identityUserRepository.FindByNormalizedUserNameAsync(userName.ToUpper());
if (user != null)
{
return user.TenantId;
}
else
{
return null;
}
}
Every few days one I got the issue again after a couple of days without the issue. It's really strange. A user can't login from the unified login page, because a duplicate Id insertion into table CmsUsers is tried and the result is an error 500. When I change his password and try to login, I can reproduce the issue. When I login for one time using the tenant specific URL (with subdomain), it works and after that, the unified login works again, too. Therefore I think that the tenant (null, from the unified page) is stored somewhere and is used during CmsUser search, although I changed it in my custom login method... of course I tried in private mode of the browser to ensure the tenant isn't stored somewhere in the cookies.
Where does CmsUser search/ insert during login process takes place? Could you point me to the source file? Maybe overriding it to customize it to my needs is easier than finding the cause of that issue.
Thanks
Hi @maliming,
sure, this is my LoginModel extension:
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(AccountPageModel), typeof(LoginModel))]
public class CustomLoginModel : LoginModel
{
private readonly IDataFilter _dataFilter;
private readonly ICurrentTenant _currentTenant;
private readonly IExtendedOrganizationUnitAppService _extendedOrganizationUnitAppService;
public CustomLoginModel(IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IAbpRecaptchaValidatorFactory recaptchaValidatorFactory,
IAccountExternalProviderAppService accountExternalProviderAppService,
ICurrentPrincipalAccessor currentPrincipalAccessor,
IOptions<IdentityOptions> identityOptions,
IOptionsSnapshot<reCAPTCHAOptions> reCaptchaOptions,
ICurrentTenant currentTenant,
IExtendedOrganizationUnitAppService extendedOrganizationUnitAppService,
IDataFilter dataFilter) : base(schemeProvider, accountOptions, recaptchaValidatorFactory, accountExternalProviderAppService, currentPrincipalAccessor, identityOptions, reCaptchaOptions)
{
_dataFilter = dataFilter;
_currentTenant = currentTenant;
_extendedOrganizationUnitAppService = extendedOrganizationUnitAppService;
}
public override async Task<IActionResult> OnPostAsync(string action)
{
try
{
await ReCaptchaVerification();
}
catch (UserFriendlyException e)
{
if (e is ScoreBelowThresholdException)
{
var onScoreBelowThresholdResult = OnRecaptchaScoreBelowThreshold();
if (onScoreBelowThresholdResult != null)
{
return await onScoreBelowThresholdResult;
}
}
Alerts.Danger(GetLocalizeExceptionMessage(e));
return Page();
}
ValidateModel();
await IdentityOptions.SetAsync();
var localLoginResult = await CheckLocalLoginAsync();
if (localLoginResult != null)
{
return localLoginResult;
}
IsSelfRegistrationEnabled = await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled);
Volo.Abp.Identity.IdentityUser user;
Guid? tenantId;
// disable TenantFilter
using (_dataFilter.Disable<IMultiTenant>())
{
// read tenant from username (unique for all tenants, by overwritten RegisterModel)
// if we login with disabled IMultiTenant DataFilter, we have issues with claims
// therefore we read tenant from username, then switch the current tenant and login
await ReplaceEmailToUsernameOfInputIfNeeds();
tenantId = await _extendedOrganizationUnitAppService.GetTenantByUsername(LoginInput.UserNameOrEmailAddress);
}
// change the current tenant
using (CurrentTenant.Change(tenantId))
{
IsLinkLogin = await VerifyLinkTokenAsync();
var result = await SignInManager.PasswordSignInAsync(
LoginInput.UserNameOrEmailAddress,
LoginInput.Password,
LoginInput.RememberMe,
true
);
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = result.ToIdentitySecurityLogAction(),
UserName = LoginInput.UserNameOrEmailAddress
});
if (result.RequiresTwoFactor)
{
return RedirectToPage("./SendSecurityCode", new
{
returnUrl = base.ReturnUrl,
returnUrlHash = base.ReturnUrlHash,
rememberMe = LoginInput.RememberMe,
linkUserId = base.LinkUserId,
linkTenantId = base.LinkTenantId,
linkToken = base.LinkToken
});
}
if (result.IsLockedOut)
{
return RedirectToPage("./LockedOut", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash
});
}
if (result.IsNotAllowed)
{
var notAllowedUser = await GetIdentityUser(LoginInput.UserNameOrEmailAddress);
if (notAllowedUser.IsActive && await UserManager.CheckPasswordAsync(notAllowedUser, LoginInput.Password))
{
await StoreConfirmUser(notAllowedUser);
return RedirectToPage("./ConfirmUser", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash
});
}
Alerts.Danger(L["LoginIsNotAllowed"]);
return Page();
}
if (!result.Succeeded)
{
Alerts.Danger(L["InvalidUserNameOrPassword"]);
return Page();
}
user = await GetIdentityUser(LoginInput.UserNameOrEmailAddress);
}
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 }
}
});
}
}
return RedirectToPage("./LinkLogged", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
TargetLinkUserId = LinkUserId,
TargetLinkTenantId = LinkTenantId
});
}
}
return RedirectSafely(ReturnUrl, ReturnUrlHash);
}
}
https://github.com/abpframework/abp/issues/13999
If somebody else also struggles with this, it seems that it will be fixed in 6.0 final. Thanks.
Btw. I think it's related this issue, therefore it seems to be a new feature, not bad at all, but there should be a possibility to disable it, since we might want the users to set their SocialSecurityNumber, but not their UserLevel, Department, Income,...
Hi @mahmut.gundogdu
I think you misunderstood me.
I'm using MVC and since 6.0.0-rc.3, the users of my application can change the ExtraProperties in their profile settings. I just want to disable this, as I'm using ExtraProperties that should be set only by administrators.
In Module Extensions Documentation there is also a sample where a User Type (Regular/ Moderator/ Superuser) is set by ExtraProperties. Of course we wouldn't want the user to set himself as Superuser ;-) I just implemented a similar logic.
Best regards Claus
Hi @liangshiwei,
thanks for your blazing-fast reply! After some additional testing, I found out that it works as expected (and as the documentation states). It is just as easy as manually adding the CmsKit.Pro module to my single application. However, it doesn't create the required tables. The created migration is empty. I could also reproduce this behavior by creating a single dummy application with 5.3.0 rc-3 and adding the cmskit. It also creates an empty migration. hat's why I assumed CmsKit would expect a public website.
After adding the required tables manually (by creating the sql script from other database) it works as I expected.
Thanks for your support and maybe the empty migration error will be fixed in a future version. :)
Best regards Claus
Thank you liangshiwei,
it works!
Best regards Claus
Earlier versions suite was generating AppService names singular. With version 4.3 we realised suite started to generate new service names plural.
Sample:
Old:ProductAppService
New:ProductsAppService
Is this a new approach for naming convention? Or is this a bug in the templates? The generated .cs class file name is also still singular.
Hi ABP team,
I'd also like to know whether this was accidently implemented or it's a design change. Please let us know :)
Thanks, Claus