More improvements to OIDC support

This commit is contained in:
Shaun Walker 2022-03-19 13:42:19 -04:00
parent 39dfc00693
commit 1a86b80c61
12 changed files with 230 additions and 93 deletions

View File

@ -19,11 +19,13 @@
{ {
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))"> <div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
@if (PageState.Site.Settings.ContainsKey("OpenIdConnectOptions:Provider") && !string.IsNullOrEmpty(PageState.Site.Settings["OpenIdConnectOptions:Provider"])) @if (_allowexternallogin)
{ {
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">Use @PageState.Site.Settings["OpenIdConnectOptions:Provider"]</button> <button type="button" class="btn btn-primary" @onclick="ExternalLogin">Use @PageState.Site.Settings["OpenIdConnectOptions:Provider"]</button>
<hr /> <br /><br />
} }
@if (_allowsitelogin)
{
<div class="form-group"> <div class="form-group">
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label> <Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required /> <input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required />
@ -45,6 +47,7 @@
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<br /><br /> <br /><br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button> <button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
}
</div> </div>
</form> </form>
} }
@ -66,6 +69,8 @@
</AuthorizeView> </AuthorizeView>
@code { @code {
private bool _allowsitelogin = true;
private bool _allowexternallogin = false;
private ElementReference login; private ElementReference login;
private bool validated = false; private bool validated = false;
private bool twofactor = false; private bool twofactor = false;
@ -90,6 +95,16 @@
{ {
_togglepassword = Localizer["ShowPassword"]; _togglepassword = Localizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["AllowSiteLogin"]))
{
_allowsitelogin = bool.Parse(PageState.Site.Settings["AllowSiteLogin"]);
}
if (PageState.Site.Settings.ContainsKey("OpenIdConnectOptions:Provider") && !string.IsNullOrEmpty(PageState.Site.Settings["OpenIdConnectOptions:Provider"]))
{
_allowexternallogin = true;
}
if (PageState.QueryString.ContainsKey("returnurl")) if (PageState.QueryString.ContainsKey("returnurl"))
{ {
_returnUrl = PageState.QueryString["returnurl"]; _returnUrl = PageState.QueryString["returnurl"];

View File

@ -56,7 +56,7 @@ else
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <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 the site" ResourceKey="AllowRegistration">Allow User Registration? </Label> <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"> <div class="col-sm-9">
<select id="allowregistration" class="form-select" @bind="@_allowregistration" required> <select id="allowregistration" class="form-select" @bind="@_allowregistration" required>
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
@ -129,15 +129,15 @@ else
</div> </div>
</div> </div>
</Section> </Section>
<Section Name="OpenIDConnect" Heading="OpenID Connect Settings" ResourceKey="OpenIDConnectSettings"> <Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="provider" HelpText="The OpenID Connect Provider Name" ResourceKey="Provider">Provider:</Label> <Label Class="col-sm-3" For="provider" HelpText="The OpenID Connect Provider Name. This Name Will Be Displayed On The Login Page" ResourceKey="Provider">Provider:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="provider" class="form-control" @bind="@_provider" /> <input id="provider" class="form-control" @bind="@_provider" />
</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="authority" HelpText="The OpenID Connect Authority" ResourceKey="Authority">Authority:</Label> <Label Class="col-sm-3" For="authority" HelpText="The Authority Or Issuer URL Associated With The OpenID Connect Provider. " ResourceKey="Authority">Authority:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="authority" class="form-control" @bind="@_authority" /> <input id="authority" class="form-control" @bind="@_authority" />
</div> </div>
@ -154,6 +154,27 @@ else
<input id="clientsecret" class="form-control" @bind="@_clientsecret" /> <input id="clientsecret" class="form-control" @bind="@_clientsecret" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="metadata" HelpText="The Discovery Endpoint For Obtaining Metadata. Only Specify If The OpenID Connect Provider Does Not Use The Standard Approach (ie. /.well-known/openid-configuration)" ResourceKey="Metadata">Metadata Address:</Label>
<div class="col-sm-9">
<input id="metadata" class="form-control" @bind="@_metadata" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logouturl" HelpText="The Url For Logging Out The User From The OpenID Connect Provider. Only Specify If The OpenID Connect Provider Supports This Feature And You Do Not Want The User To Remain Signed In To The OpenID Connect Provider After Logging Out From The Site." ResourceKey="LogoutUrl">Logout Url:</Label>
<div class="col-sm-9">
<input id="logouturl" class="form-control" @bind="@_logouturl" />
</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 This Site." ResourceKey="AllowSiteLogin">Allow Site Login? </Label>
<div class="col-sm-9">
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</Section> </Section>
</div> </div>
<br /> <br />
@ -180,6 +201,9 @@ else
private string _authority; private string _authority;
private string _clientid; private string _clientid;
private string _clientsecret; private string _clientsecret;
private string _metadata;
private string _logouturl;
private string _allowsitelogin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -203,6 +227,9 @@ else
_authority = SettingService.GetSetting(settings, "OpenIdConnectOptions:Authority", ""); _authority = SettingService.GetSetting(settings, "OpenIdConnectOptions:Authority", "");
_clientid = SettingService.GetSetting(settings, "OpenIdConnectOptions:ClientId", ""); _clientid = SettingService.GetSetting(settings, "OpenIdConnectOptions:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "OpenIdConnectOptions:ClientSecret", ""); _clientsecret = SettingService.GetSetting(settings, "OpenIdConnectOptions:ClientSecret", "");
_metadata = SettingService.GetSetting(settings, "OpenIdConnectOptions:MetadataAddress", "");
_logouturl = SettingService.GetSetting(settings, "OpenIdConnectOptions:LogoutUrl", "");
_allowsitelogin = SettingService.GetSetting(settings, "AllowSiteLogin", "True");
} }
private List<UserRole> Search(string search) private List<UserRole> Search(string search)
@ -285,7 +312,11 @@ else
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:Authority", _authority, true); settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:Authority", _authority, true);
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:ClientId", _clientid, true); settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:ClientSecret", _clientsecret, true); settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:MetadataAddress", _metadata, true);
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:LogoutUrl", _logouturl, true);
settings = SettingService.SetSetting(settings, "AllowSiteLogin", _allowsitelogin, false);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await SettingService.ClearSiteSettingsCacheAsync(site.SiteId);
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
} }
@ -295,5 +326,4 @@ else
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
} }
} }
} }

View File

@ -38,6 +38,12 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task UpdateSiteSettingsAsync(Dictionary<string, string> siteSettings, int siteId); Task UpdateSiteSettingsAsync(Dictionary<string, string> siteSettings, int siteId);
/// <summary>
/// Clears site option cache
/// </summary>
/// <returns></returns>
Task ClearSiteSettingsCacheAsync(int siteId);
/// <summary> /// <summary>
/// Returns a key-value dictionary of all page settings for the given page /// Returns a key-value dictionary of all page settings for the given page
/// </summary> /// </summary>
@ -149,7 +155,6 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId); Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId);
/// <summary> /// <summary>
/// Updates settings for a given entityName and Id /// Updates settings for a given entityName and Id
/// </summary> /// </summary>
@ -166,7 +171,6 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<Setting> GetSettingAsync(string entityName, int settingId); Task<Setting> GetSettingAsync(string entityName, int settingId);
/// <summary> /// <summary>
/// Creates a new setting /// Creates a new setting
/// </summary> /// </summary>

View File

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

View File

@ -8,6 +8,9 @@ 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;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -20,14 +23,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 string _visitorCookie; private readonly string _visitorCookie;
public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IOptionsMonitorCache<OpenIdConnectOptions> optionsMonitorCache, ILogManager logger)
{ {
_settings = settings; _settings = settings;
_pageModules = pageModules; _pageModules = pageModules;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_syncManager = syncManager; _syncManager = syncManager;
_optionsMonitorCache = optionsMonitorCache;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
_visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString(); _visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
@ -131,6 +136,15 @@ namespace Oqtane.Controllers
} }
} }
// DELETE api/<controller>/clear
[HttpDelete("clear/{id}")]
[Authorize(Roles = RoleNames.Admin)]
public void Clear(int id)
{
_optionsMonitorCache.Clear();
_logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared");
}
private bool IsAuthorized(string entityName, int entityId, string permissionName) private bool IsAuthorized(string entityName, int entityId, string permissionName)
{ {
bool authorized = false; bool authorized = false;

View File

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Oqtane.Extensions
{
public static class DictionaryExtensions
{
public static TValue GetValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue, bool nullOrEmptyValueIsValid = false)
{
if (dictionary != null && key != null && dictionary.ContainsKey(key))
{
if (nullOrEmptyValueIsValid || (dictionary[key] != null && !string.IsNullOrEmpty(dictionary[key].ToString())))
{
return dictionary[key];
}
}
return defaultValue;
}
}
}

View File

@ -15,6 +15,8 @@ using Oqtane.Repository;
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Security; using Oqtane.Security;
using System.Net;
using Microsoft.AspNetCore.Http;
namespace Oqtane.Extensions namespace Oqtane.Extensions
{ {
@ -47,38 +49,41 @@ namespace Oqtane.Extensions
// site OpenIdConnect options // site OpenIdConnect options
builder.AddSiteOptions<OpenIdConnectOptions>((options, alias) => builder.AddSiteOptions<OpenIdConnectOptions>((options, alias) =>
{ {
if (alias.SiteSettings.ContainsKey("OpenIdConnectOptions:Authority"))
{
options.Authority = alias.SiteSettings["OpenIdConnectOptions:Authority"];
}
if (alias.SiteSettings.ContainsKey("OpenIdConnectOptions:ClientId"))
{
options.ClientId = alias.SiteSettings["OpenIdConnectOptions:ClientId"];
}
if (alias.SiteSettings.ContainsKey("OpenIdConnectOptions:ClientSecret"))
{
options.ClientSecret = alias.SiteSettings["OpenIdConnectOptions:ClientSecret"];
}
// 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.GetClaimsFromUserInfoEndpoint = true;
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-oidc" : "/" + alias.Path + "/signin-oidc";
options.ResponseType = OpenIdConnectResponseType.Code; // authorization code flow
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
options.UsePkce = true; options.UsePkce = true;
options.Scope.Add("openid"); // core claims options.Scope.Add("openid"); // core claims
options.Scope.Add("profile"); // name claims options.Scope.Add("profile"); // name claims
options.Scope.Add("email"); // email claim options.Scope.Add("email"); // email claim
//options.Scope.Add("offline_access"); // get refresh token //options.Scope.Add("offline_access"); // refresh token
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true; // cookie config is required to avoid Correlation Failed errors
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-oidc" : "/" + alias.Path + "/signin-oidc"; options.NonceCookie.SameSite = SameSiteMode.Unspecified;
options.ResponseType = OpenIdConnectResponseType.Code; options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
// site options
options.Authority = alias.SiteSettings.GetValue("OpenIdConnectOptions:Authority", options.Authority);
options.ClientId = alias.SiteSettings.GetValue("OpenIdConnectOptions:ClientId", options.ClientId);
options.ClientSecret = alias.SiteSettings.GetValue("OpenIdConnectOptions:ClientSecret", options.ClientSecret);
options.MetadataAddress = alias.SiteSettings.GetValue("OpenIdConnectOptions:MetadataAddress", options.MetadataAddress);
// openid connect events
options.Events.OnTokenValidated = OnTokenValidated; options.Events.OnTokenValidated = OnTokenValidated;
options.Events.OnRedirectToIdentityProvider = OnRedirectToIdentityProvider;
options.Events.OnRedirectToIdentityProviderForSignOut = OnRedirectToIdentityProviderForSignOut;
options.Events.OnRemoteFailure = OnRemoteFailure;
}); });
// site ChallengeScheme options // site ChallengeScheme options
builder.AddSiteOptions<AuthenticationOptions>((options, alias) => builder.AddSiteOptions<AuthenticationOptions>((options, alias) =>
{ {
if (alias.SiteSettings.ContainsKey("OpenIdConnectOptions:Authority") && !string.IsNullOrEmpty(alias.SiteSettings["OpenIdConnectOptions:Authority"])) if (alias.SiteSettings.GetValue("OpenIdConnectOptions:Authority", "") != "")
{ {
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
} }
@ -175,7 +180,7 @@ namespace Oqtane.Extensions
else else
{ {
// provider keys do not match // provider keys do not match
_logger.Log(LogLevel.Error, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Server Provider Key Does Not Match For User {Email}", email); _logger.Log(LogLevel.Error, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Provider Key Does Not Match For User {Email}", email);
} }
} }
else else
@ -208,14 +213,53 @@ namespace Oqtane.Extensions
List<UserRole> userroles = _userRoles.GetUserRoles(user.UserId, context.HttpContext.GetAlias().SiteId).ToList(); List<UserRole> userroles = _userRoles.GetUserRoles(user.UserId, context.HttpContext.GetAlias().SiteId).ToList();
var identity = UserSecurity.CreateClaimsIdentity(context.HttpContext.GetAlias(), user, userroles); var identity = UserSecurity.CreateClaimsIdentity(context.HttpContext.GetAlias(), user, userroles);
principal.AddClaims(identity.Claims); principal.AddClaims(identity.Claims);
// add provider
principal.AddClaim(new Claim("Provider", context.HttpContext.GetAlias().SiteSettings["OpenIdConnectOptions:Authority"]));
} }
} }
else else
{ {
_logger.Log(LogLevel.Information, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Server Did Not Return An Email Claim"); _logger.Log(LogLevel.Information, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Provider Did Not Return An Email Claim");
} }
} }
private static Task OnRedirectToIdentityProvider(RedirectContext context)
{
//context.ProtocolMessage.SetParameter("key", "value");
return Task.CompletedTask;
}
private static Task OnRedirectToIdentityProviderForSignOut(RedirectContext context)
{
var logoutUrl = context.HttpContext.GetAlias().SiteSettings.GetValue("OpenIdConnectOptions:LogoutUrl", "");
if (logoutUrl != "")
{
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
var request = context.Request;
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
}
logoutUrl += $"&returnTo={Uri.EscapeDataString(postLogoutUri)}";
}
context.Response.Redirect(logoutUrl);
context.HandleResponse();
}
return Task.CompletedTask;
}
private static Task OnRemoteFailure(RemoteFailureContext context)
{
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
_logger.Log(LogLevel.Error, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Remote Failure {Error}", context.Failure.Message);
context.Response.Redirect(context.Properties.RedirectUri);
context.HandleResponse();
return Task.CompletedTask;
}
public static bool DecorateService<TService, TImpl>(this IServiceCollection services, params object[] parameters) public static bool DecorateService<TService, TImpl>(this IServiceCollection services, params object[] parameters)
{ {
var existingService = services.SingleOrDefault(s => s.ServiceType == typeof(TService)); var existingService = services.SingleOrDefault(s => s.ServiceType == typeof(TService));

View File

@ -15,40 +15,16 @@ namespace Oqtane.Extensions
builder.AddSiteOptions<IdentityOptions>((options, alias) => builder.AddSiteOptions<IdentityOptions>((options, alias) =>
{ {
// password options // password options
if (alias.SiteSettings.ContainsKey("IdentityOptions:Password:RequiredLength")) options.Password.RequiredLength = int.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequiredLength", options.Password.RequiredLength.ToString()));
{ options.Password.RequiredUniqueChars = int.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequiredUniqueChars", options.Password.RequiredUniqueChars.ToString()));
options.Password.RequiredLength = int.Parse(alias.SiteSettings["IdentityOptions:Password:RequiredLength"]); options.Password.RequireDigit = bool.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequireDigit", options.Password.RequireDigit.ToString()));
} options.Password.RequireUppercase = bool.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequireUppercase", options.Password.RequireUppercase.ToString()));
if (alias.SiteSettings.ContainsKey("IdentityOptions:Password:RequiredUniqueChars")) options.Password.RequireLowercase = bool.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequireLowercase", options.Password.RequireLowercase.ToString()));
{ options.Password.RequireNonAlphanumeric = bool.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:RequireNonAlphanumeric", options.Password.RequireNonAlphanumeric.ToString()));
options.Password.RequiredUniqueChars = int.Parse(alias.SiteSettings["IdentityOptions:Password:RequiredUniqueChars"]);
}
if (alias.SiteSettings.ContainsKey("IdentityOptions:Password:RequireDigit"))
{
options.Password.RequireDigit = bool.Parse(alias.SiteSettings["IdentityOptions:Password:RequireDigit"]);
}
if (alias.SiteSettings.ContainsKey("IdentityOptions:Password:RequireUppercase"))
{
options.Password.RequireUppercase = bool.Parse(alias.SiteSettings["IdentityOptions:Password:RequireUppercase"]);
}
if (alias.SiteSettings.ContainsKey("IdentityOptions:Password:RequireLowercase"))
{
options.Password.RequireLowercase = bool.Parse(alias.SiteSettings["IdentityOptions:Password:RequireLowercase"]);
}
if (alias.SiteSettings.ContainsKey("IdentityOptions:Password:RequireNonAlphanumeric"))
{
options.Password.RequireNonAlphanumeric = bool.Parse(alias.SiteSettings["IdentityOptions:Password:RequireNonAlphanumeric"]);
}
// lockout options // lockout options
if (alias.SiteSettings.ContainsKey("IdentityOptions:Password:MaxFailedAccessAttempts")) options.Lockout.MaxFailedAccessAttempts = int.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:MaxFailedAccessAttempts", options.Lockout.MaxFailedAccessAttempts.ToString()));
{ options.Lockout.DefaultLockoutTimeSpan = TimeSpan.Parse(alias.SiteSettings.GetValue("IdentityOptions:Password:DefaultLockoutTimeSpan", options.Lockout.DefaultLockoutTimeSpan.ToString()));
options.Lockout.MaxFailedAccessAttempts = int.Parse(alias.SiteSettings["IdentityOptions:Password:MaxFailedAccessAttempts"]);
}
if (alias.SiteSettings.ContainsKey("IdentityOptions:Password:DefaultLockoutTimeSpan"))
{
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.Parse(alias.SiteSettings["IdentityOptions:Password:DefaultLockoutTimeSpan"]);
}
}); });
return builder; return builder;

View File

@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared; using Oqtane.Shared;
@ -27,10 +28,15 @@ namespace Oqtane.Infrastructure
if (alias != null) if (alias != null)
{ {
// get site settings and store alias in HttpContext // get site settings
var cache = context.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache;
alias.SiteSettings = cache.GetOrCreate("sitesettings:" + alias.SiteKey, entry =>
{
var settingRepository = context.RequestServices.GetService(typeof(ISettingRepository)) as ISettingRepository; var settingRepository = context.RequestServices.GetService(typeof(ISettingRepository)) as ISettingRepository;
alias.SiteSettings = settingRepository.GetSettings(EntityNames.Site) return settingRepository.GetSettings(EntityNames.Site)
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
});
// save alias in HttpContext
context.Items.Add(Constants.HttpContextAliasKey, alias); context.Items.Add(Constants.HttpContextAliasKey, alias);
// 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)
@ -42,9 +48,7 @@ namespace Oqtane.Infrastructure
context.Request.Path = path.Replace("/" + alias.Path, ""); context.Request.Path = path.Replace("/" + alias.Path, "");
} }
} }
} }
} }
// continue processing // continue processing

View File

@ -1,32 +1,37 @@
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Oqtane.Extensions;
using Oqtane.Shared; using Oqtane.Shared;
namespace Oqtane.Pages namespace Oqtane.Pages
{ {
[AllowAnonymous] [Authorize]
public class LogoutModel : PageModel public class LogoutModel : PageModel
{ {
public async Task<IActionResult> OnGetAsync(string returnurl) public async Task<IActionResult> OnGetAsync(string returnurl)
{
if (HttpContext.User.Identity.IsAuthenticated)
{ {
await HttpContext.SignOutAsync(Constants.AuthenticationScheme); await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
}
if (returnurl == null) returnurl = (returnurl == null) ? "/" : returnurl;
{ returnurl = (!returnurl.StartsWith("/")) ? "/" + returnurl : returnurl;
returnurl = "";
}
if (!returnurl.StartsWith("/"))
{
returnurl = "/" + returnurl;
}
var provider = HttpContext.User.Claims.FirstOrDefault(item => item.Type == "Provider");
var authority = HttpContext.GetAlias().SiteSettings.GetValue("OpenIdConnectOptions:Authority", "");
var logoutUrl = HttpContext.GetAlias().SiteSettings.GetValue("OpenIdConnectOptions:LogoutUrl", "");
if (provider != null && provider.Value == authority && logoutUrl != "")
{
return new SignOutResult(OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties { RedirectUri = returnurl });
}
else
{
return LocalRedirect(Url.Content("~" + returnurl)); return LocalRedirect(Url.Content("~" + returnurl));
} }
} }
} }
}

View File

@ -9,7 +9,11 @@ namespace Oqtane.Pages
{ {
public IActionResult OnGetAsync(string returnurl) public IActionResult OnGetAsync(string returnurl)
{ {
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = !string.IsNullOrEmpty(returnurl) ? returnurl : "/" }); returnurl = (returnurl == null) ? "/" : returnurl;
returnurl = (!returnurl.StartsWith("/")) ? "/" + returnurl : returnurl;
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties { RedirectUri = returnurl });
} }
} }
} }

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
@ -10,11 +11,15 @@ namespace Oqtane.Repository
{ {
private TenantDBContext _tenant; private TenantDBContext _tenant;
private MasterDBContext _master; private MasterDBContext _master;
private readonly SiteState _siteState;
private readonly IMemoryCache _cache;
public SettingRepository(TenantDBContext tenant, MasterDBContext master) public SettingRepository(TenantDBContext tenant, MasterDBContext master, SiteState siteState, IMemoryCache cache)
{ {
_tenant = tenant; _tenant = tenant;
_master = master; _master = master;
_siteState = siteState;
_cache = cache;
} }
public IEnumerable<Setting> GetSettings(string entityName) public IEnumerable<Setting> GetSettings(string entityName)
@ -47,6 +52,7 @@ namespace Oqtane.Repository
_tenant.Setting.Add(setting); _tenant.Setting.Add(setting);
_tenant.SaveChanges(); _tenant.SaveChanges();
} }
ManageCache(setting.EntityName);
return setting; return setting;
} }
@ -62,6 +68,7 @@ namespace Oqtane.Repository
_tenant.Entry(setting).State = EntityState.Modified; _tenant.Entry(setting).State = EntityState.Modified;
_tenant.SaveChanges(); _tenant.SaveChanges();
} }
ManageCache(setting.EntityName);
return setting; return setting;
} }
@ -103,6 +110,7 @@ namespace Oqtane.Repository
_tenant.Setting.Remove(setting); _tenant.Setting.Remove(setting);
_tenant.SaveChanges(); _tenant.SaveChanges();
} }
ManageCache(entityName);
} }
public void DeleteSettings(string entityName, int entityId) public void DeleteSettings(string entityName, int entityId)
@ -129,11 +137,20 @@ namespace Oqtane.Repository
} }
_tenant.SaveChanges(); _tenant.SaveChanges();
} }
ManageCache(entityName);
} }
private bool IsMaster(string EntityName) private bool IsMaster(string EntityName)
{ {
return (EntityName == EntityNames.ModuleDefinition || EntityName == EntityNames.Host); return (EntityName == EntityNames.ModuleDefinition || EntityName == EntityNames.Host);
} }
private void ManageCache(string EntityName)
{
if (EntityName == EntityNames.Site)
{
_cache.Remove("sitesettings:" + _siteState.Alias.SiteKey);
}
}
} }
} }