This commit is contained in:
Shaun Walker 2022-03-30 22:08:32 -04:00
parent 8ddaf57e17
commit a70f1ee1e0
12 changed files with 134 additions and 94 deletions

View File

@ -95,9 +95,9 @@
{
_togglepassword = Localizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("ExternalLogin:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:AllowSiteLogin"]))
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
{
_allowsitelogin = bool.Parse(PageState.Site.Settings["ExternalLogin:AllowSiteLogin"]);
_allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]);
}
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))

View File

@ -41,15 +41,18 @@ else
<input id="confirm" type="password" class="form-control" @bind="@confirm" autocomplete="new-password" />
</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. 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>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
@if (allowtwofactor)
{
<div class="row mb-1 align-items-center">
<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>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
@ -216,6 +219,7 @@ else
private string username = string.Empty;
private string password = string.Empty;
private string confirm = string.Empty;
private bool allowtwofactor = false;
private string twofactor = "False";
private string email = string.Empty;
private string displayname = string.Empty;
@ -235,6 +239,11 @@ else
{
try
{
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
{
allowtwofactor = bool.Parse(PageState.Site.Settings["LoginOptions:TwoFactor"]);
}
if (PageState.User != null)
{
username = PageState.User.Username;

View File

@ -55,16 +55,56 @@ else
</TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allowregistration" HelpText="Do You Want To Allow Visitors To Be Able To Register For A User Account On This Site?" ResourceKey="AllowRegistration">Allow User Registration?</Label>
<div class="col-sm-9">
<select id="allowregistration" class="form-select" @bind="@_allowregistration" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
<div class="col-sm-9">
<select id="allowregistration" class="form-select" @bind="@_allowregistration">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<br />
@if (_providertype != "")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Login?</Label>
<div class="col-sm-9">
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Login?</Label>
<div class="col-sm-9">
<input id="allowsitelogin" class="form-control" value="@SharedLocalizer["Yes"]" readonly />
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want to allow users to use two factor authentication? Note that the Notification Job in Scheduled Jobs needs to be enabled for this option to work properly." ResourceKey="TwoFactor">Allow Two Factor?</Label>
<div class="col-sm-9">
<select id="twofactor" class="form-select" @bind="@_twofactor">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cookietype" HelpText="Cookies are usually managed per domain. 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="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
@ -129,17 +169,6 @@ 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 usually managed per domain. 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,15 +284,6 @@ else
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Site Login?</Label>
<div class="col-sm-9">
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
</Section>
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
@ -314,6 +334,10 @@ else
private string _search;
private string _allowregistration;
private string _allowsitelogin;
private string _twofactor;
private string _cookietype;
private string _minimumlength;
private string _uniquecharacters;
private string _requiredigit;
@ -323,8 +347,6 @@ else
private string _maximumfailures;
private string _lockoutduration;
private string _cookietype;
private string _providertype;
private string _providername;
private string _authority;
@ -340,7 +362,6 @@ else
private string _emailclaimtype;
private string _domainfilter;
private string _createusers;
private string _allowsitelogin;
private string _secret;
private string _issuer;
@ -356,8 +377,11 @@ else
await LoadSettingsAsync();
userroles = Search(_search);
_allowregistration = PageState.Site.AllowRegistration.ToString();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_allowregistration = PageState.Site.AllowRegistration.ToString();
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookietype = SettingService.GetSetting(settings, "LoginOptions:CookieType", "domain");
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
@ -369,8 +393,6 @@ else
_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", "");
@ -386,7 +408,6 @@ else
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
_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", "");
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
@ -462,6 +483,10 @@ else
await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieType", _cookietype, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true);
@ -472,8 +497,6 @@ else
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);
@ -488,7 +511,6 @@ else
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
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);
@ -497,7 +519,7 @@ else
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await SettingService.ClearSiteSettingsCacheAsync(site.SiteId);
await SettingService.ClearSiteSettingsCacheAsync();
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}

View File

@ -28,9 +28,9 @@ namespace Oqtane.Client
var builder = WebAssemblyHostBuilder.CreateDefault(args);
var httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
builder.Services.AddSingleton(httpClient);
builder.Services.AddHttpClient(); // IHttpClientFactory for calling remote services via RemoteServiceBase
builder.Services.AddSingleton(httpClient);
builder.Services.AddHttpClient("Remote");
builder.Services.AddOptions();
// Register localization services

View File

@ -208,7 +208,7 @@
<value>Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site.</value>
</data>
<data name="AllowSiteLogin.Text" xml:space="preserve">
<value>Allow Site Login?</value>
<value>Allow Login?</value>
</data>
<data name="Authority.HelpText" xml:space="preserve">
<value>The Authority Url or Issuer Url associated with the OpenID Connect provider</value>
@ -315,14 +315,14 @@
<data name="Audience.Text" xml:space="preserve">
<value>Audience:</value>
</data>
<data name="CookieSettings.Heading" xml:space="preserve">
<value>Cookie Settings</value>
<data name="UserSettings.Heading" xml:space="preserve">
<value>User Settings</value>
</data>
<data name="CookieType.HelpText" xml:space="preserve">
<value>Cookies are usually managed per domain. However you can also choose to have distinct cookies for each site.</value>
<value>Login cookies are usually managed per domain. However you can also choose to have distinct cookies for each site.</value>
</data>
<data name="CookieType.Text" xml:space="preserve">
<value>Cookie Type:</value>
<value>Login Cookie Type:</value>
</data>
<data name="CreateToken" xml:space="preserve">
<value>Create Token</value>
@ -354,4 +354,10 @@
<data name="TokenSettings.Heading" xml:space="preserve">
<value>Token Settings</value>
</data>
<data name="TwoFactor.HelpText" xml:space="preserve">
<value>Do you want to allow users to use two factor authentication? Note that the Notification Job in Scheduled Jobs needs to be enabled and your SMTP options need to be configured in Site Settings for this option to work properly.</value>
</data>
<data name="TwoFactor.Text" xml:space="preserve">
<value>Allow Two Factor?</value>
</data>
</root>

View File

@ -42,7 +42,7 @@ namespace Oqtane.Services
/// Clears site option cache
/// </summary>
/// <returns></returns>
Task ClearSiteSettingsCacheAsync(int siteId);
Task ClearSiteSettingsCacheAsync();
/// <summary>
/// Returns a key-value dictionary of all page settings for the given page

View File

@ -42,9 +42,9 @@ namespace Oqtane.Services
await UpdateSettingsAsync(siteSettings, EntityNames.Site, siteId);
}
public async Task ClearSiteSettingsCacheAsync(int siteId)
public async Task ClearSiteSettingsCacheAsync()
{
await DeleteAsync($"{Apiurl}/clear/{siteId}");
await DeleteAsync($"{Apiurl}/clear");
}
public async Task<Dictionary<string, string>> GetPageSettingsAsync(int pageId)

View File

@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Options;
namespace Oqtane.Controllers
{
@ -23,18 +24,26 @@ namespace Oqtane.Controllers
private readonly IPageModuleRepository _pageModules;
private readonly IUserPermissions _userPermissions;
private readonly ISyncManager _syncManager;
private readonly IAliasAccessor _aliasAccessor;
private readonly IOptionsMonitorCache<CookieAuthenticationOptions> _cookieCache;
private readonly IOptionsMonitorCache<OpenIdConnectOptions> _oidcCache;
private readonly IOptionsMonitorCache<OAuthOptions> _oauthCache;
private readonly IOptionsMonitorCache<IdentityOptions> _identityCache;
private readonly ILogManager _logger;
private readonly Alias _alias;
private readonly IAliasAccessor _aliasAccessor;
private readonly string _visitorCookie;
public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IAliasAccessor aliasAccessor, ILogManager logger)
public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IAliasAccessor aliasAccessor, IOptionsMonitorCache<CookieAuthenticationOptions> cookieCache, IOptionsMonitorCache<OpenIdConnectOptions> oidcCache, IOptionsMonitorCache<OAuthOptions> oauthCache, IOptionsMonitorCache<IdentityOptions> identityCache, ILogManager logger)
{
_settings = settings;
_pageModules = pageModules;
_userPermissions = userPermissions;
_syncManager = syncManager;
_aliasAccessor = aliasAccessor;
_cookieCache = cookieCache;
_oidcCache = oidcCache;
_oauthCache = oauthCache;
_identityCache = identityCache;
_logger = logger;
_alias = tenantManager.GetAlias();
_visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
@ -139,18 +148,26 @@ namespace Oqtane.Controllers
}
// DELETE api/<controller>/clear
[HttpDelete("clear/{id}")]
[HttpDelete("clear")]
[Authorize(Roles = RoleNames.Admin)]
public void Clear(int id)
public void Clear()
{
var cookieAuthenticationOptionsCache = new SiteOptionsCache<CookieAuthenticationOptions>(_aliasAccessor);
cookieAuthenticationOptionsCache.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();
// clear SiteOptionsCache for each option type
var cookieCache = new SiteOptionsCache<CookieAuthenticationOptions>(_aliasAccessor);
cookieCache.Clear();
var oidcCache = new SiteOptionsCache<OpenIdConnectOptions>(_aliasAccessor);
oidcCache.Clear();
var oauthCache = new SiteOptionsCache<OAuthOptions>(_aliasAccessor);
oauthCache.Clear();
var identityCache = new SiteOptionsCache<IdentityOptions>(_aliasAccessor);
identityCache.Clear();
// clear IOptionsMonitorCache for each option type
_cookieCache.Clear();
_oidcCache.Clear();
_oauthCache.Clear();
_identityCache.Clear();
_logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared");
}

View File

@ -28,7 +28,7 @@ namespace Oqtane.Extensions
// site cookie authentication options
builder.AddSiteOptions<CookieAuthenticationOptions>((options, alias, sitesettings) =>
{
if (sitesettings.GetValue("CookieOptions:CookieType", "domain") == "domain")
if (sitesettings.GetValue("LoginOptions:CookieType", "domain") == "domain")
{
options.Cookie.Name = ".AspNetCore.Identity.Application";
}

View File

@ -36,7 +36,7 @@ namespace Oqtane.Infrastructure
var user = jwtManager.ValidateToken(token, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""));
if (user != null)
{
// populate principal (reload user roles to ensure most accurate permission assigments)
// populate principal (reload user roles to ensure most accurate permissions)
var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
var principal = (ClaimsIdentity)context.User.Identity;
UserSecurity.ResetClaimsIdentity(principal);
@ -52,7 +52,8 @@ namespace Oqtane.Infrastructure
}
}
await _next(context);
// continue processing
if (_next != null) await _next(context);
}
}
}

View File

@ -9,11 +9,11 @@ namespace Oqtane.Infrastructure
{
internal class TenantMiddleware
{
private readonly RequestDelegate next;
private readonly RequestDelegate _next;
public TenantMiddleware(RequestDelegate next)
{
this.next = next;
_next = next;
}
public async Task Invoke(HttpContext context)
@ -55,7 +55,7 @@ namespace Oqtane.Infrastructure
}
// continue processing
if (next != null) await next(context);
if (_next != null) await _next(context);
}
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
@ -20,21 +19,7 @@ namespace Oqtane.Infrastructure
{
var cache = map.GetOrAdd(GetKey(), new OptionsCache<TOptions>());
cache.Clear();
}
public void Clear(Alias alias)
{
var cache = map.GetOrAdd(alias.SiteKey, new OptionsCache<TOptions>());
cache.Clear();
}
public void ClearAll()
{
foreach (var cache in map.Values)
{
cache.Clear();
}
}
public TOptions GetOrAdd(string name, Func<TOptions> createOptions)