fix #4580 - add logout everywhere support using SecurityStamp
This commit is contained in:
parent
1f2e2148d5
commit
48f2079f88
|
@ -8,14 +8,12 @@
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<AuthorizeView Roles="@RoleNames.Registered">
|
@if (PageState.User != null)
|
||||||
<Authorizing>
|
{
|
||||||
<text>...</text>
|
|
||||||
</Authorizing>
|
|
||||||
<Authorized>
|
|
||||||
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
|
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
|
||||||
</Authorized>
|
}
|
||||||
<NotAuthorized>
|
else
|
||||||
|
{
|
||||||
@if (!twofactor)
|
@if (!twofactor)
|
||||||
{
|
{
|
||||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
@ -23,7 +21,9 @@
|
||||||
@if (_allowexternallogin)
|
@if (_allowexternallogin)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||||
<br /><br />
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
}
|
}
|
||||||
@if (_allowsitelogin)
|
@if (_allowsitelogin)
|
||||||
{
|
{
|
||||||
|
@ -49,11 +49,15 @@
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
<br /><br />
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||||
@if (PageState.Site.AllowRegistration)
|
@if (PageState.Site.AllowRegistration)
|
||||||
{
|
{
|
||||||
<br /><br />
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,8 +78,7 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
</NotAuthorized>
|
}
|
||||||
</AuthorizeView>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _allowsitelogin = true;
|
private bool _allowsitelogin = true;
|
||||||
|
@ -204,7 +207,7 @@
|
||||||
user = await UserService.VerifyTwoFactorAsync(user, _code);
|
user = await UserService.VerifyTwoFactorAsync(user, _code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.IsAuthenticated)
|
if (user != null && user.IsAuthenticated)
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
||||||
|
|
||||||
|
@ -228,7 +231,7 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired)
|
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
||||||
{
|
{
|
||||||
twofactor = true;
|
twofactor = true;
|
||||||
validated = false;
|
validated = false;
|
||||||
|
|
|
@ -11,14 +11,12 @@
|
||||||
{
|
{
|
||||||
if (!_userCreated)
|
if (!_userCreated)
|
||||||
{
|
{
|
||||||
<AuthorizeView Roles="@RoleNames.Registered">
|
if (PageState.User != null)
|
||||||
<Authorizing>
|
{
|
||||||
<text>...</text>
|
|
||||||
</Authorizing>
|
|
||||||
<Authorized>
|
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||||
</Authorized>
|
}
|
||||||
<NotAuthorized>
|
else
|
||||||
|
{
|
||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -64,12 +62,13 @@
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
@if (_allowsitelogin)
|
@if (_allowsitelogin)
|
||||||
{
|
{
|
||||||
<br /><br />
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
</NotAuthorized>
|
}
|
||||||
</AuthorizeView>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -4,11 +4,8 @@
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<span class="app-login">
|
<span class="app-login">
|
||||||
<AuthorizeView Roles="@RoleNames.Registered">
|
@if (PageState.User != null)
|
||||||
<Authorizing>
|
{
|
||||||
<text>...</text>
|
|
||||||
</Authorizing>
|
|
||||||
<Authorized>
|
|
||||||
@if (PageState.Runtime == Runtime.Hybrid)
|
@if (PageState.Runtime == Runtime.Hybrid)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
||||||
|
@ -21,14 +18,14 @@
|
||||||
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
|
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
</Authorized>
|
}
|
||||||
<NotAuthorized>
|
else
|
||||||
|
{
|
||||||
@if (ShowLogin)
|
@if (ShowLogin)
|
||||||
{
|
{
|
||||||
<a href="@loginurl" class="btn btn-primary">@SharedLocalizer["Login"]</a>
|
<a href="@loginurl" class="btn btn-primary">@SharedLocalizer["Login"]</a>
|
||||||
}
|
}
|
||||||
</NotAuthorized>
|
}
|
||||||
</AuthorizeView>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
|
|
|
@ -6,20 +6,17 @@
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
<span class="app-profile">
|
<span class="app-profile">
|
||||||
<AuthorizeView Roles="@RoleNames.Registered">
|
@if (PageState.User != null)
|
||||||
<Authorizing>
|
{
|
||||||
<text>...</text>
|
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="btn btn-primary">@PageState.User.Username</a>
|
||||||
</Authorizing>
|
}
|
||||||
<Authorized>
|
else
|
||||||
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="btn btn-primary">@context.User.Identity.Name</a>
|
{
|
||||||
</Authorized>
|
|
||||||
<NotAuthorized>
|
|
||||||
@if (ShowRegister && PageState.Site.AllowRegistration)
|
@if (ShowRegister && PageState.Site.AllowRegistration)
|
||||||
{
|
{
|
||||||
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="btn btn-primary">@Localizer["Register"]</a>
|
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="btn btn-primary">@Localizer["Register"]</a>
|
||||||
}
|
}
|
||||||
</NotAuthorized>
|
}
|
||||||
</AuthorizeView>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
|
@ -157,7 +157,7 @@
|
||||||
|
|
||||||
// verify user is authenticated for current site
|
// verify user is authenticated for current site
|
||||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey))
|
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == Constants.SiteKeyClaimType && item.Value == SiteState.Alias.SiteKey))
|
||||||
{
|
{
|
||||||
// get user
|
// get user
|
||||||
var userid = int.Parse(authState.User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
|
var userid = int.Parse(authState.User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Oqtane.Models;
|
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.Extensions
|
namespace Oqtane.Extensions
|
||||||
|
@ -41,9 +40,9 @@ namespace Oqtane.Extensions
|
||||||
|
|
||||||
public static string SiteKey(this ClaimsPrincipal claimsPrincipal)
|
public static string SiteKey(this ClaimsPrincipal claimsPrincipal)
|
||||||
{
|
{
|
||||||
if (claimsPrincipal.HasClaim(item => item.Type == "sitekey"))
|
if (claimsPrincipal.HasClaim(item => item.Type == Constants.SiteKeyClaimType))
|
||||||
{
|
{
|
||||||
return claimsPrincipal.Claims.FirstOrDefault(item => item.Type == "sitekey").Value;
|
return claimsPrincipal.Claims.FirstOrDefault(item => item.Type == Constants.SiteKeyClaimType).Value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -71,6 +70,18 @@ namespace Oqtane.Extensions
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string SecurityStamp(this ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
if (claimsPrincipal.HasClaim(item => item.Type == Constants.SecurityStampClaimType))
|
||||||
|
{
|
||||||
|
return claimsPrincipal.Claims.FirstOrDefault(item => item.Type == Constants.SecurityStampClaimType).Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsOnlyInRole(this ClaimsPrincipal claimsPrincipal, string role)
|
public static bool IsOnlyInRole(this ClaimsPrincipal claimsPrincipal, string role)
|
||||||
{
|
{
|
||||||
var identity = claimsPrincipal.Identities.FirstOrDefault(item => item.AuthenticationType == Constants.AuthenticationScheme);
|
var identity = claimsPrincipal.Identities.FirstOrDefault(item => item.AuthenticationType == Constants.AuthenticationScheme);
|
||||||
|
|
|
@ -231,6 +231,7 @@ namespace Oqtane.Managers
|
||||||
{
|
{
|
||||||
identityuser.PasswordHash = _identityUserManager.PasswordHasher.HashPassword(identityuser, user.Password);
|
identityuser.PasswordHash = _identityUserManager.PasswordHasher.HashPassword(identityuser, user.Password);
|
||||||
await _identityUserManager.UpdateAsync(identityuser);
|
await _identityUserManager.UpdateAsync(identityuser);
|
||||||
|
await _identityUserManager.UpdateSecurityStampAsync(identityuser); // will force user to sign in again
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -241,7 +242,8 @@ namespace Oqtane.Managers
|
||||||
|
|
||||||
if (user.Email != identityuser.Email)
|
if (user.Email != identityuser.Email)
|
||||||
{
|
{
|
||||||
await _identityUserManager.SetEmailAsync(identityuser, user.Email);
|
identityuser.Email = user.Email;
|
||||||
|
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||||
|
|
||||||
// if email address changed and it is not confirmed, verification is required for new email address
|
// if email address changed and it is not confirmed, verification is required for new email address
|
||||||
if (!user.EmailConfirmed)
|
if (!user.EmailConfirmed)
|
||||||
|
|
|
@ -10,6 +10,7 @@ using System;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Extensions;
|
using Oqtane.Extensions;
|
||||||
using Oqtane.Managers;
|
using Oqtane.Managers;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
namespace Oqtane.Providers
|
namespace Oqtane.Providers
|
||||||
{
|
{
|
||||||
|
@ -41,6 +42,8 @@ namespace Oqtane.Providers
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
//var principalStamp = authState.User.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType);
|
||||||
|
//return principalStamp == user.SecurityStamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
|
@ -14,13 +15,15 @@ namespace Oqtane.Repository
|
||||||
private readonly IDbContextFactory<TenantDBContext> _dbContextFactory;
|
private readonly IDbContextFactory<TenantDBContext> _dbContextFactory;
|
||||||
private readonly IRoleRepository _roles;
|
private readonly IRoleRepository _roles;
|
||||||
private readonly ITenantManager _tenantManager;
|
private readonly ITenantManager _tenantManager;
|
||||||
|
private readonly UserManager<IdentityUser> _identityUserManager;
|
||||||
private readonly IMemoryCache _cache;
|
private readonly IMemoryCache _cache;
|
||||||
|
|
||||||
public UserRoleRepository(IDbContextFactory<TenantDBContext> dbContextFactory, IRoleRepository roles, ITenantManager tenantManager, IMemoryCache cache)
|
public UserRoleRepository(IDbContextFactory<TenantDBContext> dbContextFactory, IRoleRepository roles, ITenantManager tenantManager, UserManager<IdentityUser> identityUserManager, IMemoryCache cache)
|
||||||
{
|
{
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
_roles = roles;
|
_roles = roles;
|
||||||
_tenantManager = tenantManager;
|
_tenantManager = tenantManager;
|
||||||
|
_identityUserManager = identityUserManager;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,9 +72,7 @@ namespace Oqtane.Repository
|
||||||
DeleteUserRoles(userRole.UserId);
|
DeleteUserRoles(userRole.UserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
var alias = _tenantManager.GetAlias();
|
UpdateSecurityStamp(userRole.UserId);
|
||||||
_cache.Remove($"user:{userRole.UserId}:{alias.SiteKey}");
|
|
||||||
_cache.Remove($"userroles:{userRole.UserId}:{alias.SiteKey}");
|
|
||||||
|
|
||||||
return userRole;
|
return userRole;
|
||||||
}
|
}
|
||||||
|
@ -82,9 +83,7 @@ namespace Oqtane.Repository
|
||||||
db.Entry(userRole).State = EntityState.Modified;
|
db.Entry(userRole).State = EntityState.Modified;
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
|
|
||||||
var alias = _tenantManager.GetAlias();
|
UpdateSecurityStamp(userRole.UserId);
|
||||||
_cache.Remove($"user:{userRole.UserId}:{alias.SiteKey}");
|
|
||||||
_cache.Remove($"userroles:{userRole.UserId}:{alias.SiteKey}");
|
|
||||||
|
|
||||||
return userRole;
|
return userRole;
|
||||||
}
|
}
|
||||||
|
@ -144,9 +143,7 @@ namespace Oqtane.Repository
|
||||||
db.UserRole.Remove(userRole);
|
db.UserRole.Remove(userRole);
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
|
|
||||||
var alias = _tenantManager.GetAlias();
|
UpdateSecurityStamp(userRole.UserId);
|
||||||
_cache.Remove($"user:{userRole.UserId}:{alias.SiteKey}");
|
|
||||||
_cache.Remove($"userroles:{userRole.UserId}:{alias.SiteKey}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteUserRoles(int userId)
|
public void DeleteUserRoles(int userId)
|
||||||
|
@ -158,9 +155,30 @@ namespace Oqtane.Repository
|
||||||
}
|
}
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
|
|
||||||
|
UpdateSecurityStamp(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSecurityStamp(int userId)
|
||||||
|
{
|
||||||
|
// update user security stamp
|
||||||
|
using var db = _dbContextFactory.CreateDbContext();
|
||||||
|
var user = db.User.Find(userId);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
var identityuser = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult();
|
||||||
|
if (identityuser != null)
|
||||||
|
{
|
||||||
|
_identityUserManager.UpdateSecurityStampAsync(identityuser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh cache
|
||||||
var alias = _tenantManager.GetAlias();
|
var alias = _tenantManager.GetAlias();
|
||||||
|
if (alias != null)
|
||||||
|
{
|
||||||
_cache.Remove($"user:{userId}:{alias.SiteKey}");
|
_cache.Remove($"user:{userId}:{alias.SiteKey}");
|
||||||
_cache.Remove($"userroles:{userId}:{alias.SiteKey}");
|
_cache.Remove($"userroles:{userId}:{alias.SiteKey}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,17 @@ namespace Oqtane.Security
|
||||||
public class ClaimsPrincipalFactory<TUser> : UserClaimsPrincipalFactory<TUser> where TUser : IdentityUser
|
public class ClaimsPrincipalFactory<TUser> : UserClaimsPrincipalFactory<TUser> where TUser : IdentityUser
|
||||||
{
|
{
|
||||||
private readonly ITenantManager _tenants;
|
private readonly ITenantManager _tenants;
|
||||||
|
// cannot utilize IUserManager due to circular references - which is fine as this method is only called on login
|
||||||
private readonly IUserRepository _users;
|
private readonly IUserRepository _users;
|
||||||
private readonly IUserRoleRepository _userRoles;
|
private readonly IUserRoleRepository _userRoles;
|
||||||
|
private readonly UserManager<TUser> _userManager;
|
||||||
|
|
||||||
public ClaimsPrincipalFactory(UserManager<TUser> userManager, IOptions<IdentityOptions> optionsAccessor, ITenantManager tenants, IUserRepository users, IUserRoleRepository userroles) : base(userManager, optionsAccessor)
|
public ClaimsPrincipalFactory(UserManager<TUser> userManager, IOptions<IdentityOptions> optionsAccessor, ITenantManager tenants, IUserRepository users, IUserRoleRepository userroles) : base(userManager, optionsAccessor)
|
||||||
{
|
{
|
||||||
_tenants = tenants;
|
_tenants = tenants;
|
||||||
_users = users;
|
_users = users;
|
||||||
_userRoles = userroles;
|
_userRoles = userroles;
|
||||||
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(TUser identityuser)
|
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(TUser identityuser)
|
||||||
|
@ -33,6 +36,7 @@ namespace Oqtane.Security
|
||||||
Alias alias = _tenants.GetAlias();
|
Alias alias = _tenants.GetAlias();
|
||||||
if (alias != null)
|
if (alias != null)
|
||||||
{
|
{
|
||||||
|
user.SecurityStamp = await _userManager.GetSecurityStampAsync(identityuser);
|
||||||
List<UserRole> userroles = _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList();
|
List<UserRole> userroles = _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList();
|
||||||
identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles);
|
identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,11 @@ using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Repository;
|
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Oqtane.Extensions;
|
using Oqtane.Extensions;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
using System.IO;
|
using Oqtane.Managers;
|
||||||
|
|
||||||
|
|
||||||
namespace Oqtane.Security
|
namespace Oqtane.Security
|
||||||
{
|
{
|
||||||
|
@ -24,49 +23,38 @@ namespace Oqtane.Security
|
||||||
// check if framework is installed
|
// check if framework is installed
|
||||||
if (config.IsInstalled() && !path.StartsWith("/_")) // ignore Blazor framework requests
|
if (config.IsInstalled() && !path.StartsWith("/_")) // ignore Blazor framework requests
|
||||||
{
|
{
|
||||||
// get current site
|
var _logger = context.HttpContext.RequestServices.GetService(typeof(ILogManager)) as ILogManager;
|
||||||
|
|
||||||
var alias = context.HttpContext.GetAlias();
|
var alias = context.HttpContext.GetAlias();
|
||||||
if (alias != null)
|
if (alias != null)
|
||||||
{
|
{
|
||||||
var claims = context.Principal.Claims;
|
var userManager = context.HttpContext.RequestServices.GetService(typeof(IUserManager)) as IUserManager;
|
||||||
|
var user = userManager.GetUser(context.Principal.UserId(), alias.SiteId); // cached
|
||||||
|
|
||||||
// check if principal has roles and matches current site
|
// check if user is valid, not deleted, has roles, and security stamp has not changed
|
||||||
if (!claims.Any(item => item.Type == ClaimTypes.Role) || !claims.Any(item => item.Type == "sitekey" && item.Value == alias.SiteKey))
|
if (user != null && !user.IsDeleted && user.Roles.Any() && context.Principal.SecurityStamp() == user.SecurityStamp)
|
||||||
{
|
{
|
||||||
var userRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRepository)) as IUserRepository;
|
// validate sitekey in case user has changed sites in installation
|
||||||
var userRoleRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
|
if (context.Principal.SiteKey() != alias.SiteKey || !context.Principal.Roles().Any())
|
||||||
var _logger = context.HttpContext.RequestServices.GetService(typeof(ILogManager)) as ILogManager;
|
|
||||||
|
|
||||||
User user = userRepository.GetUser(context.Principal.Identity.Name);
|
|
||||||
if (user != null)
|
|
||||||
{
|
{
|
||||||
// replace principal with roles for current site
|
// refresh principal
|
||||||
List<UserRole> userroles = userRoleRepository.GetUserRoles(user.UserId, alias.SiteId).ToList();
|
var identity = UserSecurity.CreateClaimsIdentity(alias, user);
|
||||||
if (userroles.Any())
|
|
||||||
{
|
|
||||||
var identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles);
|
|
||||||
context.ReplacePrincipal(new ClaimsPrincipal(identity));
|
context.ReplacePrincipal(new ClaimsPrincipal(identity));
|
||||||
context.ShouldRenew = true;
|
context.ShouldRenew = true;
|
||||||
Log(_logger, alias, "Permissions Updated For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
|
Log(_logger, alias, "Permissions Refreshed For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// user has no roles - remove principal
|
// remove principal (ie. log user out)
|
||||||
Log(_logger, alias, "Permissions Removed For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
|
Log(_logger, alias, "Permissions Removed For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
|
||||||
context.RejectPrincipal();
|
context.RejectPrincipal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// user does not exist - remove principal
|
// user is signed in but site cannot be determined
|
||||||
Log(_logger, alias, "Permissions Removed For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
|
Log(_logger, alias, "Alias Could Not Be Resolved For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
|
||||||
context.RejectPrincipal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// user is signed in but tenant cannot be determined
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ namespace Oqtane.Security
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
|
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
|
||||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()));
|
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()));
|
||||||
identity.AddClaim(new Claim("sitekey", alias.SiteKey));
|
identity.AddClaim(new Claim(Constants.SiteKeyClaimType, alias.SiteKey));
|
||||||
if (user.Roles.Contains(RoleNames.Host))
|
if (user.Roles.Contains(RoleNames.Host))
|
||||||
{
|
{
|
||||||
// host users are site admins by default
|
// host users are site admins by default
|
||||||
|
@ -115,6 +115,7 @@ namespace Oqtane.Security
|
||||||
identity.AddClaim(new Claim(ClaimTypes.Role, role));
|
identity.AddClaim(new Claim(ClaimTypes.Role, role));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
identity.AddClaim(new Claim(Constants.SecurityStampClaimType, user.SecurityStamp));
|
||||||
}
|
}
|
||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,9 @@ namespace Oqtane.Shared
|
||||||
public static readonly string AntiForgeryTokenHeaderName = "X-XSRF-TOKEN-HEADER";
|
public static readonly string AntiForgeryTokenHeaderName = "X-XSRF-TOKEN-HEADER";
|
||||||
public static readonly string AntiForgeryTokenCookieName = "X-XSRF-TOKEN-COOKIE";
|
public static readonly string AntiForgeryTokenCookieName = "X-XSRF-TOKEN-COOKIE";
|
||||||
|
|
||||||
|
public static readonly string SecurityStampClaimType = "AspNet.Identity.SecurityStamp";
|
||||||
|
public static readonly string SiteKeyClaimType = "Oqtane.Identity.SiteKey";
|
||||||
|
|
||||||
public static readonly string DefaultVisitorFilter = "bot,crawler,slurp,spider,(none),??";
|
public static readonly string DefaultVisitorFilter = "bot,crawler,slurp,spider,(none),??";
|
||||||
|
|
||||||
public static readonly string HttpContextAliasKey = "Alias";
|
public static readonly string HttpContextAliasKey = "Alias";
|
||||||
|
|
Loading…
Reference in New Issue
Block a user