add Jwt authorization support for for API
This commit is contained in:
parent
c8129607e8
commit
a97af42e4b
|
@ -129,6 +129,17 @@ else
|
|||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Cookie" Heading="Cookie Settings" ResourceKey="CookieSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookietype" HelpText="Cookies are managed per domain by default. However you can also choose to have distinct cookies for each site." ResourceKey="CookieType">Cookie Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="cookietype" class="form-select" @bind="@_cookietype">
|
||||
<option value="domain">@Localizer["Domain"]</option>
|
||||
<option value="site">@Localizer["Site"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||
|
@ -255,6 +266,23 @@ else
|
|||
</div>
|
||||
}
|
||||
</Section>
|
||||
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="secret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Site Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="secret" class="form-control" @bind="@_secret" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="token" HelpText="Select the Create Token button to generate an access token. The token will be valid for 1 year. Be sure to save this token in a safe place as you will not be able to view it in the future." ResourceKey="Token">Access Token:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="token" class="form-control" @bind="@_token" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@CreateToken">@Localizer["CreateToken"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
|
@ -277,6 +305,8 @@ else
|
|||
private string _maximumfailures;
|
||||
private string _lockoutduration;
|
||||
|
||||
private string _cookietype;
|
||||
|
||||
private string _providertype;
|
||||
private string _providername;
|
||||
private string _authority;
|
||||
|
@ -294,6 +324,9 @@ else
|
|||
private string _createusers;
|
||||
private string _allowsitelogin;
|
||||
|
||||
private string _secret;
|
||||
private string _token;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
|
@ -311,9 +344,12 @@ else
|
|||
_requireupper = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireUppercase", "true");
|
||||
_requirelower = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireLowercase", "true");
|
||||
_requirepunctuation = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true");
|
||||
|
||||
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
|
||||
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
|
||||
|
||||
_cookietype = SettingService.GetSetting(settings, "CookieOptions:CookieType", "domain");
|
||||
|
||||
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
|
||||
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
|
||||
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
|
||||
|
@ -330,6 +366,8 @@ else
|
|||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
_allowsitelogin = SettingService.GetSetting(settings, "ExternalLogin:AllowSiteLogin", "true");
|
||||
|
||||
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
|
||||
}
|
||||
|
||||
private List<UserRole> Search(string search)
|
||||
|
@ -406,9 +444,12 @@ else
|
|||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "CookieOptions:CookieType", _cookietype, true);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
|
||||
|
@ -425,6 +466,9 @@ else
|
|||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowSiteLogin", _allowsitelogin, false);
|
||||
|
||||
if (!string.IsNullOrEmpty(_secret) && _secret.Length < 16) _secret = (_secret + "????????????????").Substring(0, 16);
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
|
||||
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||
await SettingService.ClearSiteSettingsCacheAsync(site.SiteId);
|
||||
|
||||
|
@ -451,4 +495,9 @@ else
|
|||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task CreateToken()
|
||||
{
|
||||
_token = await UserService.GetTokenAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,5 +104,10 @@ namespace Oqtane.Services
|
|||
/// <returns></returns>
|
||||
Task<bool> ValidatePasswordAsync(string password);
|
||||
|
||||
/// <summary>
|
||||
/// Get token for current user
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<string> GetTokenAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,5 +79,10 @@ namespace Oqtane.Services
|
|||
{
|
||||
return await GetJsonAsync<bool>($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}");
|
||||
}
|
||||
|
||||
public async Task<string> GetTokenAsync()
|
||||
{
|
||||
return await GetStringAsync($"{Apiurl}/token");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
|
@ -142,6 +143,8 @@ namespace Oqtane.Controllers
|
|||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public void Clear(int id)
|
||||
{
|
||||
var cookieAuthenticationOptionsCache = new SiteOptionsCache<CookieAuthenticationOptions>(_aliasAccessor);
|
||||
cookieAuthenticationOptionsCache.Clear();
|
||||
var openIdConnectOptionsCache = new SiteOptionsCache<OpenIdConnectOptions>(_aliasAccessor);
|
||||
openIdConnectOptionsCache.Clear();
|
||||
var oAuthOptionsCache = new SiteOptionsCache<OAuthOptions>(_aliasAccessor);
|
||||
|
|
|
@ -14,6 +14,8 @@ using System.Net;
|
|||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Extensions;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
|
@ -30,9 +32,10 @@ namespace Oqtane.Controllers
|
|||
private readonly IFolderRepository _folders;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ISiteRepository _sites;
|
||||
private readonly IJwtManager _jwtManager;
|
||||
private readonly ILogManager _logger;
|
||||
|
||||
public UserController(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, ILogManager logger)
|
||||
public UserController(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, IJwtManager jwtManager, ILogManager logger)
|
||||
{
|
||||
_users = users;
|
||||
_roles = roles;
|
||||
|
@ -44,6 +47,7 @@ namespace Oqtane.Controllers
|
|||
_notifications = notifications;
|
||||
_syncManager = syncManager;
|
||||
_sites = sites;
|
||||
_jwtManager = jwtManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -516,6 +520,24 @@ namespace Oqtane.Controllers
|
|||
return result.Succeeded;
|
||||
}
|
||||
|
||||
// GET api/<controller>/token
|
||||
[HttpGet("token")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public string Token()
|
||||
{
|
||||
var token = "";
|
||||
var user = _users.GetUser(User.Identity.Name);
|
||||
if (user != null)
|
||||
{
|
||||
var secret = HttpContext.GetSiteSettings().GetValue("JwtOptions:Secret", "");
|
||||
if (!string.IsNullOrEmpty(secret))
|
||||
{
|
||||
token = _jwtManager.GenerateToken(user, secret);
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
// GET api/<controller>/authenticate
|
||||
[HttpGet("authenticate")]
|
||||
public User Authenticate()
|
||||
|
|
|
@ -41,5 +41,8 @@ namespace Oqtane.Extensions
|
|||
|
||||
public static IApplicationBuilder UseTenantResolution(this IApplicationBuilder builder)
|
||||
=> builder.UseMiddleware<TenantMiddleware>();
|
||||
|
||||
public static IApplicationBuilder UseJwtAuthorization(this IApplicationBuilder builder)
|
||||
=> builder.UseMiddleware<JwtMiddleware>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,10 +85,12 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
{
|
||||
services.AddTransient<ITenantManager, TenantManager>();
|
||||
services.AddTransient<IAliasAccessor, AliasAccessor>();
|
||||
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
|
||||
services.AddTransient<IThemeRepository, ThemeRepository>();
|
||||
services.AddTransient<IUserPermissions, UserPermissions>();
|
||||
services.AddTransient<ITenantResolver, TenantResolver>();
|
||||
services.AddTransient<IJwtManager, JwtManager>();
|
||||
|
||||
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
|
||||
services.AddTransient<IThemeRepository, ThemeRepository>();
|
||||
services.AddTransient<IAliasRepository, AliasRepository>();
|
||||
services.AddTransient<ITenantRepository, TenantRepository>();
|
||||
services.AddTransient<ISiteRepository, SiteRepository>();
|
||||
|
@ -115,6 +117,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.AddTransient<ILanguageRepository, LanguageRepository>();
|
||||
services.AddTransient<IVisitorRepository, VisitorRepository>();
|
||||
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
|
||||
|
||||
// obsolete - replaced by ITenantManager
|
||||
services.AddTransient<ITenantResolver, TenantResolver>();
|
||||
|
||||
|
@ -181,7 +184,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
options.SignIn.RequireConfirmedPhoneNumber = false;
|
||||
|
||||
// User settings
|
||||
options.User.RequireUniqueEmail = false;
|
||||
options.User.RequireUniqueEmail = true;
|
||||
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
|
||||
});
|
||||
|
||||
|
|
|
@ -11,13 +11,13 @@ using System.Security.Claims;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Oqtane.Repository;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Security;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
|
||||
namespace Oqtane.Extensions
|
||||
{
|
||||
|
@ -25,7 +25,21 @@ namespace Oqtane.Extensions
|
|||
{
|
||||
public static OqtaneSiteOptionsBuilder WithSiteAuthentication(this OqtaneSiteOptionsBuilder builder)
|
||||
{
|
||||
// site OpenIdConnect options
|
||||
// site cookie authentication options
|
||||
builder.AddSiteOptions<CookieAuthenticationOptions>((options, alias, sitesettings) =>
|
||||
{
|
||||
if (sitesettings.GetValue("CookieOptions:CookieType", "domain") == "domain")
|
||||
{
|
||||
options.Cookie.Name = ".AspNetCore.Identity.Application";
|
||||
}
|
||||
else
|
||||
{
|
||||
// use unique cookie name for site
|
||||
options.Cookie.Name = ".AspNetCore.Identity.Application" + alias.SiteKey;
|
||||
}
|
||||
});
|
||||
|
||||
// site OpenId Connect options
|
||||
builder.AddSiteOptions<OpenIdConnectOptions>((options, alias, sitesettings) =>
|
||||
{
|
||||
if (sitesettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OpenIDConnect)
|
||||
|
@ -33,7 +47,7 @@ namespace Oqtane.Extensions
|
|||
// default options
|
||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||
options.RequireHttpsMetadata = true;
|
||||
options.SaveTokens = true;
|
||||
options.SaveTokens = false;
|
||||
options.GetClaimsFromUserInfoEndpoint = true;
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OpenIDConnect : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OpenIDConnect;
|
||||
options.ResponseType = OpenIdConnectResponseType.Code; // authorization code flow
|
||||
|
@ -62,7 +76,7 @@ namespace Oqtane.Extensions
|
|||
}
|
||||
});
|
||||
|
||||
// site OAuth2.0 options
|
||||
// site OAuth 2.0 options
|
||||
builder.AddSiteOptions<OAuthOptions>((options, alias, sitesettings) =>
|
||||
{
|
||||
if (sitesettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OAuth2)
|
||||
|
@ -70,7 +84,7 @@ namespace Oqtane.Extensions
|
|||
// default options
|
||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OAuth2 : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OAuth2;
|
||||
options.SaveTokens = true;
|
||||
options.SaveTokens = false;
|
||||
|
||||
// site options
|
||||
options.AuthorizationEndpoint = sitesettings.GetValue("ExternalLogin:AuthorizationUrl", "");
|
||||
|
@ -264,11 +278,9 @@ namespace Oqtane.Extensions
|
|||
// add claims to principal
|
||||
if (user != null)
|
||||
{
|
||||
// add Oqtane claims
|
||||
var principal = (ClaimsIdentity)claimsPrincipal.Identity;
|
||||
UserSecurity.ResetClaimsIdentity(principal);
|
||||
List<UserRole> userroles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList();
|
||||
var identity = UserSecurity.CreateClaimsIdentity(httpContext.GetAlias(), user, userroles);
|
||||
var identity = UserSecurity.CreateClaimsIdentity(httpContext.GetAlias(), user, _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList());
|
||||
principal.AddClaims(identity.Claims);
|
||||
|
||||
// update user
|
||||
|
@ -277,7 +289,7 @@ namespace Oqtane.Extensions
|
|||
_users.UpdateUser(user);
|
||||
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} Using Provider {Provider}", user.Username, providerType);
|
||||
}
|
||||
else // user not logged in
|
||||
else // user not valid
|
||||
{
|
||||
await httpContext.SignOutAsync();
|
||||
}
|
||||
|
|
57
Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs
Normal file
57
Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
internal class JwtMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public JwtMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
if (context.Request.Headers.ContainsKey("Authorization"))
|
||||
{
|
||||
var alias = context.GetAlias();
|
||||
if (alias != null)
|
||||
{
|
||||
var secret = context.GetSiteSettings().GetValue("JwtOptions:Secret", "");
|
||||
if (!string.IsNullOrEmpty(secret))
|
||||
{
|
||||
var logger = context.RequestServices.GetService(typeof(ILogManager)) as ILogManager;
|
||||
var jwtManager = context.RequestServices.GetService(typeof(IJwtManager)) as IJwtManager;
|
||||
|
||||
var token = context.Request.Headers["Authorization"].First().Split(" ").Last();
|
||||
var user = jwtManager.ValidateToken(token, secret);
|
||||
if (user != null)
|
||||
{
|
||||
// populate principal
|
||||
var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
|
||||
var principal = (ClaimsIdentity)context.User.Identity;
|
||||
UserSecurity.ResetClaimsIdentity(principal);
|
||||
var identity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, alias.SiteId).ToList());
|
||||
principal.AddClaims(identity.Claims);
|
||||
logger.Log(alias.SiteId, LogLevel.Information, "TokenValidation", Enums.LogFunction.Security, "Token Validated For User {Username}", user.Username);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Log(alias.SiteId, LogLevel.Error, "TokenValidation", Enums.LogFunction.Security, "Token Validation Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
using Oqtane.Models;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Oqtane.Security
|
||||
{
|
||||
public interface IUserPermissions
|
||||
{
|
||||
bool IsAuthorized(ClaimsPrincipal user, string entityName, int entityId, string permissionName);
|
||||
bool IsAuthorized(ClaimsPrincipal user, string permissionName, string permissions);
|
||||
User GetUser(ClaimsPrincipal user);
|
||||
User GetUser();
|
||||
}
|
||||
}
|
66
Oqtane.Server/Security/JwtManager.cs
Normal file
66
Oqtane.Server/Security/JwtManager.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Security
|
||||
{
|
||||
public interface IJwtManager
|
||||
{
|
||||
string GenerateToken(User user, string secret);
|
||||
User ValidateToken(string token, string secret);
|
||||
}
|
||||
|
||||
public class JwtManager : IJwtManager
|
||||
{
|
||||
public string GenerateToken(User user, string secret)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(secret);
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new[] { new Claim("id", user.UserId.ToString()), new Claim("name", user.Username) }),
|
||||
Expires = DateTime.UtcNow.AddYears(1),
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
return tokenHandler.WriteToken(token);
|
||||
}
|
||||
|
||||
public User ValidateToken(string token, string secret)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(secret);
|
||||
try
|
||||
{
|
||||
tokenHandler.ValidateToken(token, new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||
ValidateIssuer = false,
|
||||
ValidateAudience = false,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
}, out SecurityToken validatedToken);
|
||||
|
||||
var jwtToken = (JwtSecurityToken)validatedToken;
|
||||
var user = new User
|
||||
{
|
||||
UserId = int.Parse(jwtToken.Claims.FirstOrDefault(item => item.Type == "id")?.Value),
|
||||
Username = jwtToken.Claims.FirstOrDefault(item => item.Type == "name")?.Value
|
||||
};
|
||||
return user;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// error validating token
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ namespace Oqtane.Security
|
|||
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
|
||||
{
|
||||
// permission is scoped based on entitynames and ids passed as querystring parameters or headers
|
||||
// permission is scoped based on entitynames and ids passed as querystring parameters
|
||||
var ctx = _httpContextAccessor.HttpContext;
|
||||
if (ctx != null)
|
||||
{
|
||||
|
|
|
@ -25,10 +25,11 @@ namespace Oqtane.Security
|
|||
var alias = context.HttpContext.GetAlias();
|
||||
if (alias != null)
|
||||
{
|
||||
// check if principal matches current site
|
||||
if (context.Principal.Claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid)?.Value != alias.SiteKey)
|
||||
var claims = context.Principal.Claims;
|
||||
|
||||
// check if principal has roles and matches current site
|
||||
if (!claims.Any(item => item.Type == ClaimTypes.Role) || claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid)?.Value != alias.SiteKey)
|
||||
{
|
||||
// principal does not match site
|
||||
var userRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRepository)) as IUserRepository;
|
||||
var userRoleRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
|
||||
var _logger = context.HttpContext.RequestServices.GetService(typeof(ILogManager)) as ILogManager;
|
||||
|
@ -39,28 +40,43 @@ namespace Oqtane.Security
|
|||
{
|
||||
// replace principal with roles for current site
|
||||
List<UserRole> userroles = userRoleRepository.GetUserRoles(user.UserId, alias.SiteId).ToList();
|
||||
if (userroles.Any())
|
||||
{
|
||||
var identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles);
|
||||
context.ReplacePrincipal(new ClaimsPrincipal(identity));
|
||||
context.ShouldRenew = true;
|
||||
if (!path.StartsWith("/api/")) // reduce log verbosity
|
||||
Log(_logger, alias, "Permissions Updated For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(alias.SiteId, LogLevel.Information, "LoginValidation", Enums.LogFunction.Security, "Permissions Updated For User {Username} Accessing Resource {Url}", context.Principal.Identity.Name, path);
|
||||
// user has no roles - remove principal
|
||||
context.RejectPrincipal();
|
||||
Log(_logger, alias, "Permissions Removed For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// user has no roles for site - remove principal
|
||||
// user does not exist - remove principal
|
||||
context.RejectPrincipal();
|
||||
if (!path.StartsWith("/api/")) // reduce log verbosity
|
||||
Log(_logger, alias, "Permissions Removed For User {Username} Accessing {Url}", context.Principal.Identity.Name, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(alias.SiteId, LogLevel.Information, "LoginValidation", Enums.LogFunction.Security, "Permissions Removed For User {Username} Accessing Resource {Url}", context.Principal.Identity.Name, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
// user is signed in but tenant cannot be determined
|
||||
}
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static void Log (ILogManager logger, Alias alias, string message, string username, string path)
|
||||
{
|
||||
if (!path.StartsWith("/api/")) // reduce log verbosity
|
||||
{
|
||||
logger.Log(alias.SiteId, LogLevel.Information, "LoginValidation", Enums.LogFunction.Security, message, username, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,14 @@ using Oqtane.Repository;
|
|||
|
||||
namespace Oqtane.Security
|
||||
{
|
||||
public interface IUserPermissions
|
||||
{
|
||||
bool IsAuthorized(ClaimsPrincipal user, string entityName, int entityId, string permissionName);
|
||||
bool IsAuthorized(ClaimsPrincipal user, string permissionName, string permissions);
|
||||
User GetUser(ClaimsPrincipal user);
|
||||
User GetUser();
|
||||
}
|
||||
|
||||
public class UserPermissions : IUserPermissions
|
||||
{
|
||||
private readonly IPermissionRepository _permissions;
|
||||
|
|
|
@ -167,6 +167,7 @@ namespace Oqtane
|
|||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
app.UseTenantResolution();
|
||||
app.UseJwtAuthorization();
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
|
|
|
@ -170,6 +170,11 @@ namespace Oqtane.Security
|
|||
{
|
||||
identity.RemoveClaim(claim);
|
||||
}
|
||||
var roles = identity.Claims.Where(item => item.Type == ClaimTypes.Role);
|
||||
foreach (var role in roles)
|
||||
{
|
||||
identity.RemoveClaim(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user