factor out auth constants, remove TAlias is Alias is not an extensible type, improve SiteOptions cache clearing, improve principal validation, localization improvements
This commit is contained in:
parent
79f427e10a
commit
b92a888583
|
@ -42,7 +42,7 @@ else
|
|||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication" ResourceKey="TwoFactor"></Label>
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@twofactor" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
|
|
|
@ -135,8 +135,8 @@ else
|
|||
<div class="col-sm-9">
|
||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||
<option value="" selected>@Localizer["Not Specified"]</option>
|
||||
<option value="oidc">@Localizer["OpenID Connect"]</option>
|
||||
<option value="oauth2">@Localizer["OAuth 2.0"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OpenID Connect"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth 2.0"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -149,7 +149,7 @@ else
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == "oidc")
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authority" HelpText="The Authority Url or Issuer Url associated with the OpenID Connect provider" ResourceKey="Authority">Authority:</Label>
|
||||
|
@ -164,7 +164,7 @@ else
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == "oauth2")
|
||||
@if (_providertype == AuthenticationProviderTypes.OAuth2)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authorizationurl" HelpText="The endpoint for obtaining an Authorization Code" ResourceKey="AuthorizationUrl">Authorization Url:</Label>
|
||||
|
@ -220,7 +220,7 @@ else
|
|||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype == "oidc")
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="emailclaimtype" HelpText="The type name for the email address claim provided by the provider" ResourceKey="EmailClaimType">Email Claim Type:</Label>
|
||||
|
@ -440,7 +440,7 @@ else
|
|||
private void ProviderTypeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_providertype = (string)e.Value;
|
||||
if (_providertype == "oidc")
|
||||
if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
_scopes = "openid,profile,email";
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
||||
}
|
||||
</div>
|
||||
<div class="col mt-2 text-center">
|
||||
<div class="col mt-2 text-end">
|
||||
<button type="button" class="btn btn-success" @onclick="UploadFile">@SharedLocalizer["Upload"]</button>
|
||||
@if (ShowFiles && GetFileId() != -1)
|
||||
{
|
||||
|
|
|
@ -127,7 +127,7 @@
|
|||
<value>User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
|
||||
</data>
|
||||
<data name="Error.Login.Fail" xml:space="preserve">
|
||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In 3 Times Unsuccessfully, Your Account Will Be Locked Out For 10 Minutes. Note That User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email If You Are A New User.</value>
|
||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email If You Are A New User.</value>
|
||||
</data>
|
||||
<data name="Message.Required.UserInfo" xml:space="preserve">
|
||||
<value>Please Provide All Required Fields</value>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
@ -169,10 +169,10 @@
|
|||
<value>Identity</value>
|
||||
</data>
|
||||
<data name="Confirm.HelpText" xml:space="preserve">
|
||||
<value>If you are changing your password you must enter it again to confirm it matches</value>
|
||||
<value>If you are changing your password you must enter it again to confirm it matches the value entered above</value>
|
||||
</data>
|
||||
<data name="Confirm.Text" xml:space="preserve">
|
||||
<value>Confirm Password:</value>
|
||||
<value>Confirmation:</value>
|
||||
</data>
|
||||
<data name="DisplayName.HelpText" xml:space="preserve">
|
||||
<value>Your full name</value>
|
||||
|
@ -205,9 +205,9 @@
|
|||
<value>Username:</value>
|
||||
</data>
|
||||
<data name="TwoFactor.HelpText" xml:space="preserve">
|
||||
<value>Indicates if you are using two factor authentication</value>
|
||||
<value>Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in.</value>
|
||||
</data>
|
||||
<data name="TwoFactor.Text" xml:space="preserve">
|
||||
<value>Two Factor Authentication?</value>
|
||||
<value>Two Factor?</value>
|
||||
</data>
|
||||
</root>
|
|
@ -21,7 +21,9 @@ namespace Oqtane.Services
|
|||
_siteState = siteState;
|
||||
}
|
||||
|
||||
private string ApiUrl => CreateApiUrl("Installation", null, ControllerRoutes.ApiRoute); // tenant agnostic
|
||||
private string ApiUrl => (_siteState.Alias == null)
|
||||
? CreateApiUrl("Installation", null, ControllerRoutes.ApiRoute) // tenant agnostic needed for initial installation
|
||||
: CreateApiUrl("Installation", _siteState.Alias);
|
||||
|
||||
public async Task<Installation> IsInstalled()
|
||||
{
|
||||
|
|
|
@ -17,7 +17,6 @@ namespace Oqtane.Services
|
|||
|
||||
public SiteService(HttpClient http, SiteState siteState) : base(http)
|
||||
{
|
||||
|
||||
_siteState = siteState;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,9 +8,10 @@ using Oqtane.Enums;
|
|||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
|
@ -23,16 +24,16 @@ namespace Oqtane.Controllers
|
|||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
private readonly IOptionsMonitorCache<OpenIdConnectOptions> _optionsMonitorCache;
|
||||
private readonly IAliasAccessor _aliasAccessor;
|
||||
private readonly string _visitorCookie;
|
||||
|
||||
public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IOptionsMonitorCache<OpenIdConnectOptions> optionsMonitorCache, ILogManager logger)
|
||||
public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IAliasAccessor aliasAccessor, ILogManager logger)
|
||||
{
|
||||
_settings = settings;
|
||||
_pageModules = pageModules;
|
||||
_userPermissions = userPermissions;
|
||||
_syncManager = syncManager;
|
||||
_optionsMonitorCache = optionsMonitorCache;
|
||||
_aliasAccessor = aliasAccessor;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
_visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
|
||||
|
@ -141,7 +142,12 @@ namespace Oqtane.Controllers
|
|||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public void Clear(int id)
|
||||
{
|
||||
_optionsMonitorCache.Clear();
|
||||
var openIdConnectOptionsCache = new SiteOptionsCache<OpenIdConnectOptions>(_aliasAccessor);
|
||||
openIdConnectOptionsCache.Clear();
|
||||
var oAuthOptionsCache = new SiteOptionsCache<OAuthOptions>(_aliasAccessor);
|
||||
oAuthOptionsCache.Clear();
|
||||
var identityOptionsCache = new SiteOptionsCache<IdentityOptions>(_aliasAccessor);
|
||||
identityOptionsCache.Clear();
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared");
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ using System.Net;
|
|||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Extensions;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
|
|
|
@ -7,6 +7,8 @@ using System.Net.Http;
|
|||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
@ -15,7 +17,6 @@ using Microsoft.Extensions.Hosting;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
|
@ -59,10 +60,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
return services;
|
||||
}
|
||||
|
||||
public static OqtaneSiteOptionsBuilder<T> AddOqtaneSiteOptions<T>(this IServiceCollection services)
|
||||
where T : class, IAlias, new()
|
||||
public static OqtaneSiteOptionsBuilder AddOqtaneSiteOptions(this IServiceCollection services)
|
||||
{
|
||||
return new OqtaneSiteOptionsBuilder<T>(services);
|
||||
return new OqtaneSiteOptionsBuilder(services);
|
||||
}
|
||||
|
||||
internal static IServiceCollection AddOqtaneSingletonServices(this IServiceCollection services)
|
||||
|
@ -144,6 +144,15 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection ConfigureOqtaneAuthenticationOptions(this IServiceCollection services, IConfigurationRoot Configuration)
|
||||
{
|
||||
// settings defined in appsettings
|
||||
services.Configure<OAuthOptions>(Configuration);
|
||||
services.Configure<OpenIdConnectOptions>(Configuration);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection ConfigureOqtaneIdentityOptions(this IServiceCollection services, IConfigurationRoot Configuration)
|
||||
{
|
||||
// default settings
|
||||
|
|
|
@ -11,7 +11,6 @@ using System.Security.Claims;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Oqtane.Repository;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Security;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -24,30 +23,19 @@ namespace Oqtane.Extensions
|
|||
{
|
||||
public static class OqtaneSiteAuthenticationBuilderExtensions
|
||||
{
|
||||
public static OqtaneSiteOptionsBuilder<TAlias> WithSiteAuthentication<TAlias>(
|
||||
this OqtaneSiteOptionsBuilder<TAlias> builder)
|
||||
where TAlias : class, IAlias, new()
|
||||
{
|
||||
builder.WithSiteAuthenticationOptions();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static OqtaneSiteOptionsBuilder<TAlias> WithSiteAuthenticationOptions<TAlias>(
|
||||
this OqtaneSiteOptionsBuilder<TAlias> builder)
|
||||
where TAlias : class, IAlias, new()
|
||||
public static OqtaneSiteOptionsBuilder WithSiteAuthentication(this OqtaneSiteOptionsBuilder builder)
|
||||
{
|
||||
// site OpenIdConnect options
|
||||
builder.AddSiteOptions<OpenIdConnectOptions>((options, alias) =>
|
||||
{
|
||||
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == "oidc")
|
||||
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
// default options
|
||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||
options.RequireHttpsMetadata = true;
|
||||
options.SaveTokens = true;
|
||||
options.GetClaimsFromUserInfoEndpoint = true;
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-oidc" : "/" + alias.Path + "/signin-oidc";
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OpenIDConnect : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OpenIDConnect;
|
||||
options.ResponseType = OpenIdConnectResponseType.Code; // authorization code flow
|
||||
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
|
||||
|
||||
|
@ -77,11 +65,11 @@ namespace Oqtane.Extensions
|
|||
// site OAuth2.0 options
|
||||
builder.AddSiteOptions<OAuthOptions>((options, alias) =>
|
||||
{
|
||||
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == "oauth2")
|
||||
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OAuth2)
|
||||
{
|
||||
// default options
|
||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-oauth2" : "/" + alias.Path + "/signin-oauth2";
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OAuth2 : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OAuth2;
|
||||
options.SaveTokens = true;
|
||||
|
||||
// site options
|
||||
|
|
|
@ -7,9 +7,7 @@ namespace Oqtane.Extensions
|
|||
{
|
||||
public static class OqtaneSiteIdentityBuilderExtensions
|
||||
{
|
||||
public static OqtaneSiteOptionsBuilder<TAlias> WithSiteIdentity<TAlias>(
|
||||
this OqtaneSiteOptionsBuilder<TAlias> builder)
|
||||
where TAlias : class, IAlias, new()
|
||||
public static OqtaneSiteOptionsBuilder WithSiteIdentity(this OqtaneSiteOptionsBuilder builder)
|
||||
{
|
||||
// site identity options
|
||||
builder.AddSiteOptions<IdentityOptions>((options, alias) =>
|
||||
|
|
|
@ -6,7 +6,7 @@ using Oqtane.Models;
|
|||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public partial class OqtaneSiteOptionsBuilder<TSiteOptions> where TSiteOptions : class, IAlias, new()
|
||||
public partial class OqtaneSiteOptionsBuilder
|
||||
{
|
||||
public IServiceCollection Services { get; set; }
|
||||
|
||||
|
@ -15,13 +15,12 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
Services = services;
|
||||
}
|
||||
|
||||
public OqtaneSiteOptionsBuilder<TSiteOptions> AddSiteOptions<TOptions>(
|
||||
Action<TOptions, TSiteOptions> siteOptions) where TOptions : class, new()
|
||||
public OqtaneSiteOptionsBuilder AddSiteOptions<TOptions>(
|
||||
Action<TOptions, Alias> alias) where TOptions : class, new()
|
||||
{
|
||||
Services.TryAddSingleton<IOptionsMonitorCache<TOptions>, SiteOptionsCache<TOptions, TSiteOptions>>();
|
||||
Services.AddSingleton<ISiteOptions<TOptions, TSiteOptions>, SiteOptions<TOptions, TSiteOptions>>
|
||||
(sp => new SiteOptions<TOptions, TSiteOptions>(siteOptions));
|
||||
Services.TryAddTransient<IOptionsFactory<TOptions>, SiteOptionsFactory<TOptions, TSiteOptions>>();
|
||||
Services.TryAddSingleton<IOptionsMonitorCache<TOptions>, SiteOptionsCache<TOptions>>();
|
||||
Services.AddSingleton<ISiteOptions<TOptions>, SiteOptions<TOptions>> (sp => new SiteOptions<TOptions>(alias));
|
||||
Services.TryAddTransient<IOptionsFactory<TOptions>, SiteOptionsFactory<TOptions>>();
|
||||
Services.TryAddScoped<IOptionsSnapshot<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
|
||||
Services.TryAddSingleton<IOptions<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
|
||||
|
||||
|
@ -31,7 +30,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
private static SiteOptionsManager<TOptions> BuildOptionsManager<TOptions>(IServiceProvider sp)
|
||||
where TOptions : class, new()
|
||||
{
|
||||
var cache = ActivatorUtilities.CreateInstance(sp, typeof(SiteOptionsCache<TOptions, TSiteOptions>));
|
||||
var cache = ActivatorUtilities.CreateInstance(sp, typeof(SiteOptionsCache<TOptions>));
|
||||
return (SiteOptionsManager<TOptions>)ActivatorUtilities.CreateInstance(sp, typeof(SiteOptionsManager<TOptions>), new[] { cache });
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,9 @@ namespace Oqtane.Infrastructure
|
|||
{
|
||||
// check if framework is installed
|
||||
var config = context.RequestServices.GetService(typeof(IConfigManager)) as IConfigManager;
|
||||
if (config.IsInstalled())
|
||||
string path = context.Request.Path.ToString();
|
||||
|
||||
if (config.IsInstalled() && !path.StartsWith("/_blazor"))
|
||||
{
|
||||
// get alias (note that this also sets SiteState.Alias)
|
||||
var tenantManager = context.RequestServices.GetService(typeof(ITenantManager)) as ITenantManager;
|
||||
|
@ -28,7 +30,7 @@ namespace Oqtane.Infrastructure
|
|||
|
||||
if (alias != null)
|
||||
{
|
||||
// get site settings
|
||||
// add site settings to alias
|
||||
var cache = context.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;
|
||||
alias.SiteSettings = cache.GetOrCreate("sitesettings:" + alias.SiteKey, entry =>
|
||||
{
|
||||
|
@ -36,13 +38,14 @@ namespace Oqtane.Infrastructure
|
|||
return settingRepository.GetSettings(EntityNames.Site, alias.SiteId)
|
||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||
});
|
||||
// save alias in HttpContext
|
||||
// save alias in HttpContext for server-side usage
|
||||
context.Items.Add(Constants.HttpContextAliasKey, alias);
|
||||
// remove site settings so they are not available client-side
|
||||
alias.SiteSettings = null;
|
||||
|
||||
// rewrite path by removing alias path prefix from api and pages requests (for consistent routing)
|
||||
if (!string.IsNullOrEmpty(alias.Path))
|
||||
{
|
||||
string path = context.Request.Path.ToString();
|
||||
if (path.StartsWith("/" + alias.Path) && (path.Contains("/api/") || path.Contains("/pages/")))
|
||||
{
|
||||
context.Request.Path = path.Replace("/" + alias.Path, "");
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
public interface ISiteOptions<TOptions, TAlias>
|
||||
public interface ISiteOptions<TOptions>
|
||||
where TOptions : class, new()
|
||||
where TAlias : class, IAlias, new()
|
||||
{
|
||||
void Configure(TOptions options, TAlias siteOptions);
|
||||
void Configure(TOptions options, Alias alias);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,20 +3,19 @@ using Oqtane.Models;
|
|||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
public class SiteOptions<TOptions, TAlias> : ISiteOptions<TOptions, TAlias>
|
||||
public class SiteOptions<TOptions> : ISiteOptions<TOptions>
|
||||
where TOptions : class, new()
|
||||
where TAlias : class, IAlias, new()
|
||||
{
|
||||
private readonly Action<TOptions, TAlias> configureOptions;
|
||||
private readonly Action<TOptions, Alias> configureOptions;
|
||||
|
||||
public SiteOptions(Action<TOptions, TAlias> configureOptions)
|
||||
public SiteOptions(Action<TOptions, Alias> configureOptions)
|
||||
{
|
||||
this.configureOptions = configureOptions;
|
||||
}
|
||||
|
||||
public void Configure(TOptions options, TAlias siteOptions)
|
||||
public void Configure(TOptions options, Alias alias)
|
||||
{
|
||||
configureOptions(options, siteOptions);
|
||||
configureOptions(options, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,8 @@ using Oqtane.Models;
|
|||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
public class SiteOptionsCache<TOptions, TAlias> : IOptionsMonitorCache<TOptions>
|
||||
where TOptions : class
|
||||
where TAlias : class, IAlias, new()
|
||||
public class SiteOptionsCache<TOptions> : IOptionsMonitorCache<TOptions>
|
||||
where TOptions : class, new()
|
||||
{
|
||||
private readonly IAliasAccessor _aliasAccessor;
|
||||
private readonly ConcurrentDictionary<string, IOptionsMonitorCache<TOptions>> map = new ConcurrentDictionary<string, IOptionsMonitorCache<TOptions>>();
|
||||
|
|
|
@ -1,25 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
public class SiteOptionsFactory<TOptions, TAlias> : IOptionsFactory<TOptions>
|
||||
public class SiteOptionsFactory<TOptions> : IOptionsFactory<TOptions>
|
||||
where TOptions : class, new()
|
||||
where TAlias : class, IAlias, new()
|
||||
{
|
||||
private readonly IConfigureOptions<TOptions>[] _configureOptions;
|
||||
private readonly IPostConfigureOptions<TOptions>[] _postConfigureOptions;
|
||||
private readonly IValidateOptions<TOptions>[] _validations;
|
||||
private readonly ISiteOptions<TOptions, TAlias>[] _siteOptions;
|
||||
private readonly ISiteOptions<TOptions>[] _siteOptions;
|
||||
private readonly IAliasAccessor _aliasAccessor;
|
||||
|
||||
public SiteOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> configureOptions, IEnumerable<IPostConfigureOptions<TOptions>> postConfigureOptions, IEnumerable<IValidateOptions<TOptions>> validations, IEnumerable<ISiteOptions<TOptions, TAlias>> siteOptions, IAliasAccessor aliasAccessor)
|
||||
public SiteOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> configureOptions, IEnumerable<IPostConfigureOptions<TOptions>> postConfigureOptions, IEnumerable<ISiteOptions<TOptions>> siteOptions, IAliasAccessor aliasAccessor)
|
||||
{
|
||||
_configureOptions = configureOptions as IConfigureOptions<TOptions>[] ?? new List<IConfigureOptions<TOptions>>(configureOptions).ToArray();
|
||||
_postConfigureOptions = postConfigureOptions as IPostConfigureOptions<TOptions>[] ?? new List<IPostConfigureOptions<TOptions>>(postConfigureOptions).ToArray();
|
||||
_validations = validations as IValidateOptions<TOptions>[] ?? new List<IValidateOptions<TOptions>>(validations).ToArray();
|
||||
_siteOptions = siteOptions as ISiteOptions<TOptions, TAlias>[] ?? new List<ISiteOptions<TOptions, TAlias>>(siteOptions).ToArray();
|
||||
_siteOptions = siteOptions as ISiteOptions<TOptions>[] ?? new List<ISiteOptions<TOptions>>(siteOptions).ToArray();
|
||||
_aliasAccessor = aliasAccessor;
|
||||
}
|
||||
|
||||
|
@ -44,7 +40,7 @@ namespace Oqtane.Infrastructure
|
|||
{
|
||||
foreach (var siteOption in _siteOptions)
|
||||
{
|
||||
siteOption.Configure(options, _aliasAccessor.Alias as TAlias);
|
||||
siteOption.Configure(options, _aliasAccessor.Alias);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ using Oqtane.Infrastructure;
|
|||
using Oqtane.Repository;
|
||||
using Oqtane.Models;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Security
|
||||
{
|
||||
|
@ -13,50 +15,48 @@ namespace Oqtane.Security
|
|||
{
|
||||
public static Task ValidateAsync(CookieValidatePrincipalContext context)
|
||||
{
|
||||
if (context != null && context.Principal.Identity.IsAuthenticated)
|
||||
if (context != null && context.Principal.Identity.IsAuthenticated && context.Principal.Identity.Name != null)
|
||||
{
|
||||
// check if framework is installed
|
||||
var config = context.HttpContext.RequestServices.GetService(typeof(IConfigManager)) as IConfigManager;
|
||||
if (config.IsInstalled())
|
||||
{
|
||||
var tenantManager = context.HttpContext.RequestServices.GetService(typeof(ITenantManager)) as ITenantManager;
|
||||
var alias = tenantManager.GetAlias();
|
||||
// get current site
|
||||
var alias = context.HttpContext.GetAlias();
|
||||
if (alias != null)
|
||||
{
|
||||
// verify principal was authenticated for current tenant
|
||||
// check if principal matches current site
|
||||
if (context.Principal.Claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid)?.Value != alias.SiteKey)
|
||||
{
|
||||
// tenant agnostic requests must be ignored
|
||||
string path = context.Request.Path.ToString().ToLower();
|
||||
if (path.StartsWith("/_blazor") || path.StartsWith("/api/installation/"))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// refresh principal
|
||||
// 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;
|
||||
string path = context.Request.Path.ToString().ToLower();
|
||||
|
||||
if (context.Principal.Identity.Name != null)
|
||||
{
|
||||
User user = userRepository.GetUser(context.Principal.Identity.Name);
|
||||
if (user != null)
|
||||
{
|
||||
// replace principal with roles for current site
|
||||
List<UserRole> userroles = userRoleRepository.GetUserRoles(user.UserId, alias.SiteId).ToList();
|
||||
var identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles);
|
||||
context.ReplacePrincipal(new ClaimsPrincipal(identity));
|
||||
context.ShouldRenew = true;
|
||||
}
|
||||
else
|
||||
if (!path.StartsWith("/api/")) // reduce log verbosity
|
||||
{
|
||||
context.RejectPrincipal();
|
||||
}
|
||||
}
|
||||
_logger.Log(alias.SiteId, LogLevel.Information, "LoginValidation", Enums.LogFunction.Security, "Permissions Updated For User {Username} Accessing Resource {Url}", context.Principal.Identity.Name, path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// user has no roles for site - remove principal
|
||||
context.RejectPrincipal();
|
||||
if (!path.StartsWith("/api/")) // reduce log verbosity
|
||||
{
|
||||
_logger.Log(alias.SiteId, LogLevel.Information, "LoginValidation", Enums.LogFunction.Security, "Permissions Removed For User {Username} Accessing Resource {Url}", context.Principal.Identity.Name, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,12 +115,13 @@ namespace Oqtane
|
|||
options.DefaultChallengeScheme = Constants.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie(Constants.AuthenticationScheme)
|
||||
.AddOpenIdConnect("oidc", options => { })
|
||||
.AddOAuth("oauth2", options => { });
|
||||
.AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { })
|
||||
.AddOAuth(AuthenticationProviderTypes.OAuth2, options => { });
|
||||
|
||||
services.ConfigureOqtaneCookieOptions();
|
||||
services.ConfigureOqtaneAuthenticationOptions(Configuration);
|
||||
|
||||
services.AddOqtaneSiteOptions<Alias>()
|
||||
services.AddOqtaneSiteOptions()
|
||||
.WithSiteIdentity()
|
||||
.WithSiteAuthentication();
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ namespace Oqtane.Models
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Site-specific settings
|
||||
/// Site-specific settings (only available on the server via HttpContext for security reasons)
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public Dictionary<string, string> SiteSettings { get; set; }
|
||||
|
|
6
Oqtane.Shared/Shared/AuthenticationProviderTypes.cs
Normal file
6
Oqtane.Shared/Shared/AuthenticationProviderTypes.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Oqtane.Shared {
|
||||
public class AuthenticationProviderTypes {
|
||||
public const string OpenIDConnect = "oidc";
|
||||
public const string OAuth2 = "oauth2";
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user