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>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<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">
|
<div class="col-sm-9">
|
||||||
<select id="twofactor" class="form-select" @bind="@twofactor" required>
|
<select id="twofactor" class="form-select" @bind="@twofactor" required>
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
|
|
@ -135,8 +135,8 @@ else
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||||
<option value="" selected>@Localizer["Not Specified"]</option>
|
<option value="" selected>@Localizer["Not Specified"]</option>
|
||||||
<option value="oidc">@Localizer["OpenID Connect"]</option>
|
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OpenID Connect"]</option>
|
||||||
<option value="oauth2">@Localizer["OAuth 2.0"]</option>
|
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth 2.0"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -149,7 +149,7 @@ else
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (_providertype == "oidc")
|
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (_providertype == "oauth2")
|
@if (_providertype == AuthenticationProviderTypes.OAuth2)
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<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 />
|
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (_providertype == "oidc")
|
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<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)
|
private void ProviderTypeChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_providertype = (string)e.Value;
|
_providertype = (string)e.Value;
|
||||||
if (_providertype == "oidc")
|
if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||||
{
|
{
|
||||||
_scopes = "openid,profile,email";
|
_scopes = "openid,profile,email";
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
||||||
}
|
}
|
||||||
</div>
|
</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>
|
<button type="button" class="btn btn-success" @onclick="UploadFile">@SharedLocalizer["Upload"]</button>
|
||||||
@if (ShowFiles && GetFileId() != -1)
|
@if (ShowFiles && GetFileId() != -1)
|
||||||
{
|
{
|
||||||
|
|
|
@ -127,7 +127,7 @@
|
||||||
<value>User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
|
<value>User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error.Login.Fail" xml:space="preserve">
|
<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>
|
||||||
<data name="Message.Required.UserInfo" xml:space="preserve">
|
<data name="Message.Required.UserInfo" xml:space="preserve">
|
||||||
<value>Please Provide All Required Fields</value>
|
<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>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
@ -169,10 +169,10 @@
|
||||||
<value>Identity</value>
|
<value>Identity</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Confirm.HelpText" xml:space="preserve">
|
<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>
|
||||||
<data name="Confirm.Text" xml:space="preserve">
|
<data name="Confirm.Text" xml:space="preserve">
|
||||||
<value>Confirm Password:</value>
|
<value>Confirmation:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DisplayName.HelpText" xml:space="preserve">
|
<data name="DisplayName.HelpText" xml:space="preserve">
|
||||||
<value>Your full name</value>
|
<value>Your full name</value>
|
||||||
|
@ -205,9 +205,9 @@
|
||||||
<value>Username:</value>
|
<value>Username:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TwoFactor.HelpText" xml:space="preserve">
|
<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>
|
||||||
<data name="TwoFactor.Text" xml:space="preserve">
|
<data name="TwoFactor.Text" xml:space="preserve">
|
||||||
<value>Two Factor Authentication?</value>
|
<value>Two Factor?</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -21,7 +21,9 @@ namespace Oqtane.Services
|
||||||
_siteState = siteState;
|
_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()
|
public async Task<Installation> IsInstalled()
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,7 +17,6 @@ namespace Oqtane.Services
|
||||||
|
|
||||||
public SiteService(HttpClient http, SiteState siteState) : base(http)
|
public SiteService(HttpClient http, SiteState siteState) : base(http)
|
||||||
{
|
{
|
||||||
|
|
||||||
_siteState = siteState;
|
_siteState = siteState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,10 @@ using Oqtane.Enums;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace Oqtane.Controllers
|
namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
|
@ -23,16 +24,16 @@ namespace Oqtane.Controllers
|
||||||
private readonly ISyncManager _syncManager;
|
private readonly ISyncManager _syncManager;
|
||||||
private readonly ILogManager _logger;
|
private readonly ILogManager _logger;
|
||||||
private readonly Alias _alias;
|
private readonly Alias _alias;
|
||||||
private readonly IOptionsMonitorCache<OpenIdConnectOptions> _optionsMonitorCache;
|
private readonly IAliasAccessor _aliasAccessor;
|
||||||
private readonly string _visitorCookie;
|
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;
|
_settings = settings;
|
||||||
_pageModules = pageModules;
|
_pageModules = pageModules;
|
||||||
_userPermissions = userPermissions;
|
_userPermissions = userPermissions;
|
||||||
_syncManager = syncManager;
|
_syncManager = syncManager;
|
||||||
_optionsMonitorCache = optionsMonitorCache;
|
_aliasAccessor = aliasAccessor;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_alias = tenantManager.GetAlias();
|
_alias = tenantManager.GetAlias();
|
||||||
_visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
|
_visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
|
||||||
|
@ -141,7 +142,12 @@ namespace Oqtane.Controllers
|
||||||
[Authorize(Roles = RoleNames.Admin)]
|
[Authorize(Roles = RoleNames.Admin)]
|
||||||
public void Clear(int id)
|
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");
|
_logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ using System.Net;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
using Oqtane.Extensions;
|
|
||||||
|
|
||||||
namespace Oqtane.Controllers
|
namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,6 +7,8 @@ using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Loader;
|
using System.Runtime.Loader;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
@ -15,7 +17,6 @@ using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Models;
|
|
||||||
using Oqtane.Modules;
|
using Oqtane.Modules;
|
||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
using Oqtane.Security;
|
using Oqtane.Security;
|
||||||
|
@ -59,10 +60,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OqtaneSiteOptionsBuilder<T> AddOqtaneSiteOptions<T>(this IServiceCollection services)
|
public static OqtaneSiteOptionsBuilder AddOqtaneSiteOptions(this IServiceCollection services)
|
||||||
where T : class, IAlias, new()
|
|
||||||
{
|
{
|
||||||
return new OqtaneSiteOptionsBuilder<T>(services);
|
return new OqtaneSiteOptionsBuilder(services);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IServiceCollection AddOqtaneSingletonServices(this IServiceCollection services)
|
internal static IServiceCollection AddOqtaneSingletonServices(this IServiceCollection services)
|
||||||
|
@ -144,6 +144,15 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
return services;
|
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)
|
public static IServiceCollection ConfigureOqtaneIdentityOptions(this IServiceCollection services, IConfigurationRoot Configuration)
|
||||||
{
|
{
|
||||||
// default settings
|
// default settings
|
||||||
|
|
|
@ -11,7 +11,6 @@ using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
using System.IO;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Oqtane.Security;
|
using Oqtane.Security;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
@ -24,30 +23,19 @@ namespace Oqtane.Extensions
|
||||||
{
|
{
|
||||||
public static class OqtaneSiteAuthenticationBuilderExtensions
|
public static class OqtaneSiteAuthenticationBuilderExtensions
|
||||||
{
|
{
|
||||||
public static OqtaneSiteOptionsBuilder<TAlias> WithSiteAuthentication<TAlias>(
|
public static OqtaneSiteOptionsBuilder WithSiteAuthentication(this OqtaneSiteOptionsBuilder builder)
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
// site OpenIdConnect options
|
// site OpenIdConnect options
|
||||||
builder.AddSiteOptions<OpenIdConnectOptions>((options, alias) =>
|
builder.AddSiteOptions<OpenIdConnectOptions>((options, alias) =>
|
||||||
{
|
{
|
||||||
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == "oidc")
|
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OpenIDConnect)
|
||||||
{
|
{
|
||||||
// default options
|
// default options
|
||||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||||
options.RequireHttpsMetadata = true;
|
options.RequireHttpsMetadata = true;
|
||||||
options.SaveTokens = true;
|
options.SaveTokens = true;
|
||||||
options.GetClaimsFromUserInfoEndpoint = 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.ResponseType = OpenIdConnectResponseType.Code; // authorization code flow
|
||||||
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
|
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
|
||||||
|
|
||||||
|
@ -77,11 +65,11 @@ namespace Oqtane.Extensions
|
||||||
// site OAuth2.0 options
|
// site OAuth2.0 options
|
||||||
builder.AddSiteOptions<OAuthOptions>((options, alias) =>
|
builder.AddSiteOptions<OAuthOptions>((options, alias) =>
|
||||||
{
|
{
|
||||||
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == "oauth2")
|
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OAuth2)
|
||||||
{
|
{
|
||||||
// default options
|
// default options
|
||||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
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;
|
options.SaveTokens = true;
|
||||||
|
|
||||||
// site options
|
// site options
|
||||||
|
|
|
@ -7,9 +7,7 @@ namespace Oqtane.Extensions
|
||||||
{
|
{
|
||||||
public static class OqtaneSiteIdentityBuilderExtensions
|
public static class OqtaneSiteIdentityBuilderExtensions
|
||||||
{
|
{
|
||||||
public static OqtaneSiteOptionsBuilder<TAlias> WithSiteIdentity<TAlias>(
|
public static OqtaneSiteOptionsBuilder WithSiteIdentity(this OqtaneSiteOptionsBuilder builder)
|
||||||
this OqtaneSiteOptionsBuilder<TAlias> builder)
|
|
||||||
where TAlias : class, IAlias, new()
|
|
||||||
{
|
{
|
||||||
// site identity options
|
// site identity options
|
||||||
builder.AddSiteOptions<IdentityOptions>((options, alias) =>
|
builder.AddSiteOptions<IdentityOptions>((options, alias) =>
|
||||||
|
|
|
@ -6,7 +6,7 @@ using Oqtane.Models;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
public partial class OqtaneSiteOptionsBuilder<TSiteOptions> where TSiteOptions : class, IAlias, new()
|
public partial class OqtaneSiteOptionsBuilder
|
||||||
{
|
{
|
||||||
public IServiceCollection Services { get; set; }
|
public IServiceCollection Services { get; set; }
|
||||||
|
|
||||||
|
@ -15,13 +15,12 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
Services = services;
|
Services = services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OqtaneSiteOptionsBuilder<TSiteOptions> AddSiteOptions<TOptions>(
|
public OqtaneSiteOptionsBuilder AddSiteOptions<TOptions>(
|
||||||
Action<TOptions, TSiteOptions> siteOptions) where TOptions : class, new()
|
Action<TOptions, Alias> alias) where TOptions : class, new()
|
||||||
{
|
{
|
||||||
Services.TryAddSingleton<IOptionsMonitorCache<TOptions>, SiteOptionsCache<TOptions, TSiteOptions>>();
|
Services.TryAddSingleton<IOptionsMonitorCache<TOptions>, SiteOptionsCache<TOptions>>();
|
||||||
Services.AddSingleton<ISiteOptions<TOptions, TSiteOptions>, SiteOptions<TOptions, TSiteOptions>>
|
Services.AddSingleton<ISiteOptions<TOptions>, SiteOptions<TOptions>> (sp => new SiteOptions<TOptions>(alias));
|
||||||
(sp => new SiteOptions<TOptions, TSiteOptions>(siteOptions));
|
Services.TryAddTransient<IOptionsFactory<TOptions>, SiteOptionsFactory<TOptions>>();
|
||||||
Services.TryAddTransient<IOptionsFactory<TOptions>, SiteOptionsFactory<TOptions, TSiteOptions>>();
|
|
||||||
Services.TryAddScoped<IOptionsSnapshot<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
|
Services.TryAddScoped<IOptionsSnapshot<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
|
||||||
Services.TryAddSingleton<IOptions<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)
|
private static SiteOptionsManager<TOptions> BuildOptionsManager<TOptions>(IServiceProvider sp)
|
||||||
where TOptions : class, new()
|
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 });
|
return (SiteOptionsManager<TOptions>)ActivatorUtilities.CreateInstance(sp, typeof(SiteOptionsManager<TOptions>), new[] { cache });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,9 @@ namespace Oqtane.Infrastructure
|
||||||
{
|
{
|
||||||
// check if framework is installed
|
// check if framework is installed
|
||||||
var config = context.RequestServices.GetService(typeof(IConfigManager)) as IConfigManager;
|
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)
|
// get alias (note that this also sets SiteState.Alias)
|
||||||
var tenantManager = context.RequestServices.GetService(typeof(ITenantManager)) as ITenantManager;
|
var tenantManager = context.RequestServices.GetService(typeof(ITenantManager)) as ITenantManager;
|
||||||
|
@ -28,7 +30,7 @@ namespace Oqtane.Infrastructure
|
||||||
|
|
||||||
if (alias != null)
|
if (alias != null)
|
||||||
{
|
{
|
||||||
// get site settings
|
// add site settings to alias
|
||||||
var cache = context.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;
|
var cache = context.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;
|
||||||
alias.SiteSettings = cache.GetOrCreate("sitesettings:" + alias.SiteKey, entry =>
|
alias.SiteSettings = cache.GetOrCreate("sitesettings:" + alias.SiteKey, entry =>
|
||||||
{
|
{
|
||||||
|
@ -36,13 +38,14 @@ namespace Oqtane.Infrastructure
|
||||||
return settingRepository.GetSettings(EntityNames.Site, alias.SiteId)
|
return settingRepository.GetSettings(EntityNames.Site, alias.SiteId)
|
||||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
.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);
|
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)
|
// rewrite path by removing alias path prefix from api and pages requests (for consistent routing)
|
||||||
if (!string.IsNullOrEmpty(alias.Path))
|
if (!string.IsNullOrEmpty(alias.Path))
|
||||||
{
|
{
|
||||||
string path = context.Request.Path.ToString();
|
|
||||||
if (path.StartsWith("/" + alias.Path) && (path.Contains("/api/") || path.Contains("/pages/")))
|
if (path.StartsWith("/" + alias.Path) && (path.Contains("/api/") || path.Contains("/pages/")))
|
||||||
{
|
{
|
||||||
context.Request.Path = path.Replace("/" + alias.Path, "");
|
context.Request.Path = path.Replace("/" + alias.Path, "");
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
|
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
|
|
||||||
namespace Oqtane.Infrastructure
|
namespace Oqtane.Infrastructure
|
||||||
{
|
{
|
||||||
public interface ISiteOptions<TOptions, TAlias>
|
public interface ISiteOptions<TOptions>
|
||||||
where TOptions : class, new()
|
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
|
namespace Oqtane.Infrastructure
|
||||||
{
|
{
|
||||||
public class SiteOptions<TOptions, TAlias> : ISiteOptions<TOptions, TAlias>
|
public class SiteOptions<TOptions> : ISiteOptions<TOptions>
|
||||||
where TOptions : class, new()
|
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;
|
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
|
namespace Oqtane.Infrastructure
|
||||||
{
|
{
|
||||||
public class SiteOptionsCache<TOptions, TAlias> : IOptionsMonitorCache<TOptions>
|
public class SiteOptionsCache<TOptions> : IOptionsMonitorCache<TOptions>
|
||||||
where TOptions : class
|
where TOptions : class, new()
|
||||||
where TAlias : class, IAlias, new()
|
|
||||||
{
|
{
|
||||||
private readonly IAliasAccessor _aliasAccessor;
|
private readonly IAliasAccessor _aliasAccessor;
|
||||||
private readonly ConcurrentDictionary<string, IOptionsMonitorCache<TOptions>> map = new ConcurrentDictionary<string, IOptionsMonitorCache<TOptions>>();
|
private readonly ConcurrentDictionary<string, IOptionsMonitorCache<TOptions>> map = new ConcurrentDictionary<string, IOptionsMonitorCache<TOptions>>();
|
||||||
|
|
|
@ -1,25 +1,21 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Oqtane.Models;
|
|
||||||
|
|
||||||
namespace Oqtane.Infrastructure
|
namespace Oqtane.Infrastructure
|
||||||
{
|
{
|
||||||
public class SiteOptionsFactory<TOptions, TAlias> : IOptionsFactory<TOptions>
|
public class SiteOptionsFactory<TOptions> : IOptionsFactory<TOptions>
|
||||||
where TOptions : class, new()
|
where TOptions : class, new()
|
||||||
where TAlias : class, IAlias, new()
|
|
||||||
{
|
{
|
||||||
private readonly IConfigureOptions<TOptions>[] _configureOptions;
|
private readonly IConfigureOptions<TOptions>[] _configureOptions;
|
||||||
private readonly IPostConfigureOptions<TOptions>[] _postConfigureOptions;
|
private readonly IPostConfigureOptions<TOptions>[] _postConfigureOptions;
|
||||||
private readonly IValidateOptions<TOptions>[] _validations;
|
private readonly ISiteOptions<TOptions>[] _siteOptions;
|
||||||
private readonly ISiteOptions<TOptions, TAlias>[] _siteOptions;
|
|
||||||
private readonly IAliasAccessor _aliasAccessor;
|
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();
|
_configureOptions = configureOptions as IConfigureOptions<TOptions>[] ?? new List<IConfigureOptions<TOptions>>(configureOptions).ToArray();
|
||||||
_postConfigureOptions = postConfigureOptions as IPostConfigureOptions<TOptions>[] ?? new List<IPostConfigureOptions<TOptions>>(postConfigureOptions).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>[] ?? new List<ISiteOptions<TOptions>>(siteOptions).ToArray();
|
||||||
_siteOptions = siteOptions as ISiteOptions<TOptions, TAlias>[] ?? new List<ISiteOptions<TOptions, TAlias>>(siteOptions).ToArray();
|
|
||||||
_aliasAccessor = aliasAccessor;
|
_aliasAccessor = aliasAccessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +40,7 @@ namespace Oqtane.Infrastructure
|
||||||
{
|
{
|
||||||
foreach (var siteOption in _siteOptions)
|
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.Repository;
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Oqtane.Extensions;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.Security
|
namespace Oqtane.Security
|
||||||
{
|
{
|
||||||
|
@ -13,51 +15,49 @@ namespace Oqtane.Security
|
||||||
{
|
{
|
||||||
public static Task ValidateAsync(CookieValidatePrincipalContext context)
|
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
|
// check if framework is installed
|
||||||
var config = context.HttpContext.RequestServices.GetService(typeof(IConfigManager)) as IConfigManager;
|
var config = context.HttpContext.RequestServices.GetService(typeof(IConfigManager)) as IConfigManager;
|
||||||
if (config.IsInstalled())
|
if (config.IsInstalled())
|
||||||
{
|
{
|
||||||
var tenantManager = context.HttpContext.RequestServices.GetService(typeof(ITenantManager)) as ITenantManager;
|
// get current site
|
||||||
var alias = tenantManager.GetAlias();
|
var alias = context.HttpContext.GetAlias();
|
||||||
if (alias != null)
|
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)
|
if (context.Principal.Claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid)?.Value != alias.SiteKey)
|
||||||
{
|
{
|
||||||
// tenant agnostic requests must be ignored
|
// principal does not match site
|
||||||
string path = context.Request.Path.ToString().ToLower();
|
|
||||||
if (path.StartsWith("/_blazor") || path.StartsWith("/api/installation/"))
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// refresh principal
|
|
||||||
var userRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRepository)) as IUserRepository;
|
var userRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRepository)) as IUserRepository;
|
||||||
var userRoleRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
|
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)
|
||||||
{
|
{
|
||||||
User user = userRepository.GetUser(context.Principal.Identity.Name);
|
// replace principal with roles for current site
|
||||||
if (user != null)
|
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;
|
||||||
|
if (!path.StartsWith("/api/")) // reduce log verbosity
|
||||||
{
|
{
|
||||||
List<UserRole> userroles = userRoleRepository.GetUserRoles(user.UserId, alias.SiteId).ToList();
|
_logger.Log(alias.SiteId, LogLevel.Information, "LoginValidation", Enums.LogFunction.Security, "Permissions Updated For User {Username} Accessing Resource {Url}", context.Principal.Identity.Name, path);
|
||||||
var identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles);
|
|
||||||
context.ReplacePrincipal(new ClaimsPrincipal(identity));
|
|
||||||
context.ShouldRenew = true;
|
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// user has no roles for site - remove principal
|
||||||
|
context.RejectPrincipal();
|
||||||
|
if (!path.StartsWith("/api/")) // reduce log verbosity
|
||||||
{
|
{
|
||||||
context.RejectPrincipal();
|
_logger.Log(alias.SiteId, LogLevel.Information, "LoginValidation", Enums.LogFunction.Security, "Permissions Removed For User {Username} Accessing Resource {Url}", context.Principal.Identity.Name, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
context.RejectPrincipal();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
|
@ -115,12 +115,13 @@ namespace Oqtane
|
||||||
options.DefaultChallengeScheme = Constants.AuthenticationScheme;
|
options.DefaultChallengeScheme = Constants.AuthenticationScheme;
|
||||||
})
|
})
|
||||||
.AddCookie(Constants.AuthenticationScheme)
|
.AddCookie(Constants.AuthenticationScheme)
|
||||||
.AddOpenIdConnect("oidc", options => { })
|
.AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { })
|
||||||
.AddOAuth("oauth2", options => { });
|
.AddOAuth(AuthenticationProviderTypes.OAuth2, options => { });
|
||||||
|
|
||||||
services.ConfigureOqtaneCookieOptions();
|
services.ConfigureOqtaneCookieOptions();
|
||||||
|
services.ConfigureOqtaneAuthenticationOptions(Configuration);
|
||||||
|
|
||||||
services.AddOqtaneSiteOptions<Alias>()
|
services.AddOqtaneSiteOptions()
|
||||||
.WithSiteIdentity()
|
.WithSiteIdentity()
|
||||||
.WithSiteAuthentication();
|
.WithSiteAuthentication();
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ namespace Oqtane.Models
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Site-specific settings
|
/// Site-specific settings (only available on the server via HttpContext for security reasons)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public Dictionary<string, string> SiteSettings { get; set; }
|
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