diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index a73350f0..d3eb9bc7 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -59,29 +59,23 @@ else + @if (_allowregistration == "true") + { +
+ +
+ +
+
+ } +
+ +
+ +
+
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { - @if (_providertype != "") - { -
- -
- -
-
- } - else - { -
- -
- -
-
- }
@@ -432,6 +426,15 @@ else
+
+ +
+ +
+
} } @@ -485,7 +488,8 @@ else private List users; private string _allowregistration; - private string _allowsitelogin; + private string _registerurl; + private string _profileurl; private string _twofactor; private string _cookiename; private string _cookieexpiration; @@ -533,6 +537,7 @@ else private string _createusers; private string _verifyusers; private string _allowhostrole; + private string _allowsitelogin; private string _secret; private string _secrettype = "password"; @@ -553,7 +558,8 @@ else var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); _allowregistration = PageState.Site.AllowRegistration.ToString().ToLower(); - _allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true"); + _registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", ""); + _profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", ""); if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { @@ -616,6 +622,7 @@ else _createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); _verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true"); _allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false"); + _allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true"); } private async Task LoadUsersAsync(bool load) @@ -673,10 +680,11 @@ else await SiteService.UpdateSiteAsync(site); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); - settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false); if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { + settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false); + settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false); settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true); @@ -720,6 +728,7 @@ else settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true); settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, true); + settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false); settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true); settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true); diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index b97b229b..5bbfc855 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -17,7 +17,7 @@ namespace Oqtane.Modules public abstract class ModuleBase : ComponentBase, IModuleControl { private Logger _logger; - private string _urlparametersstate; + private string _urlparametersstate = string.Empty; private Dictionary _urlparameters; private bool _scriptsloaded = false; @@ -62,7 +62,7 @@ namespace Oqtane.Modules public Dictionary UrlParameters { get { - if (_urlparametersstate == null || _urlparametersstate != PageState.UrlParameters) + if (string.IsNullOrEmpty(_urlparametersstate) || _urlparametersstate != PageState.UrlParameters) { _urlparametersstate = PageState.UrlParameters; _urlparameters = GetUrlParameters(UrlParametersTemplate); diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index a0a7c1f4..c48ec4fa 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -132,6 +132,18 @@ Allow Registration? + + Optionally provide a custom registration url + + + Register Url: + + + Optionally provide a custom user profile url + + + Profile Url: + Error Saving Settings @@ -208,7 +220,7 @@ 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. - Allow Login? + Allow Local Login? The authority url or issuer url associated with the identity provider diff --git a/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor b/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor index d408b2ae..e99eada5 100644 --- a/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor +++ b/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor @@ -1,6 +1,7 @@ @namespace Oqtane.Themes.Controls @using System.Net @inherits ThemeControlBase +@inject ISettingService SettingService @inject IStringLocalizer Localizer @inject NavigationManager NavigationManager @@ -51,20 +52,38 @@ if (!string.IsNullOrEmpty(RegisterUrl)) { - _registerurl = RegisterUrl + "?returnurl=" + (RegisterUrl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl); + _registerurl = RegisterUrl; + _registerurl += (!_registerurl.Contains("?") ? "?" : "&") + "returnurl=" + (_registerurl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl); } else { - _registerurl = NavigateUrl("register", "returnurl=" + _returnurl); + if (!string.IsNullOrEmpty(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", ""))) + { + _registerurl = SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", ""); + _registerurl += (!_registerurl.Contains("?") ? "?" : "&") + "returnurl=" + (_registerurl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl); + } + else + { + _registerurl = NavigateUrl("register", "returnurl=" + _returnurl); + } } if (!string.IsNullOrEmpty(ProfileUrl)) { - _profileurl = ProfileUrl + "?returnurl=" + (ProfileUrl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl); + _profileurl = ProfileUrl; + _profileurl += (!_profileurl.Contains("?") ? "?" : "&") + "returnurl=" + (_profileurl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl); } else { - _profileurl = NavigateUrl("profile", "returnurl=" + _returnurl); + if (!string.IsNullOrEmpty(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:ProfileUrl", ""))) + { + _profileurl = SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:ProfileUrl", ""); + _profileurl += (!_profileurl.Contains("?") ? "?" : "&") + "returnurl=" + (_profileurl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl); + } + else + { + _profileurl = NavigateUrl("profile", "returnurl=" + _returnurl); + } } } } diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index d65acb0a..4147455b 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -40,27 +40,26 @@ namespace Oqtane.Infrastructure log += "Processing Notifications For Site: " + site.Name + "
"; // get site settings - List sitesettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList(); - Dictionary settings = GetSettings(sitesettings); - if (!site.IsDeleted && (!settings.ContainsKey("SMTPEnabled") || settings["SMTPEnabled"] == "True")) + var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1); + + if (!site.IsDeleted && settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True") { - if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && - settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" && - settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" && - settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "") + if (settingRepository.GetSettingValue(settings, "SMTPHost", "") != "" && + settingRepository.GetSettingValue(settings, "SMTPPort", "") != "" && + settingRepository.GetSettingValue(settings, "SMTPSender", "") != "") { // construct SMTP Client var client = new SmtpClient() { DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, - Host = settings["SMTPHost"], - Port = int.Parse(settings["SMTPPort"]), - EnableSsl = bool.Parse(settings["SMTPSSL"]) + Host = settingRepository.GetSettingValue(settings, "SMTPHost", ""), + Port = int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")), + EnableSsl = bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False")) }; - if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "") + if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "") { - client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]); + client.Credentials = new NetworkCredential(settingRepository.GetSettingValue(settings, "SMTPUsername", ""), settingRepository.GetSettingValue(settings, "SMTPPassword", "")); } // iterate through undelivered notifications @@ -100,7 +99,7 @@ namespace Oqtane.Infrastructure MailMessage mailMessage = new MailMessage(); // sender - if (settings.ContainsKey("SMTPRelay") && settings["SMTPRelay"] == "True" && !string.IsNullOrEmpty(notification.FromEmail)) + if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail)) { if (!string.IsNullOrEmpty(notification.FromDisplayName)) { @@ -113,7 +112,7 @@ namespace Oqtane.Infrastructure } else { - mailMessage.From = new MailAddress(settings["SMTPSender"], (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name); + mailMessage.From = new MailAddress(settingRepository.GetSettingValue(settings, "SMTPSender", ""), (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name); } // recipient @@ -162,7 +161,7 @@ namespace Oqtane.Infrastructure } else { - log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "
"; + log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "
"; } } else @@ -173,15 +172,5 @@ namespace Oqtane.Infrastructure return log; } - - private Dictionary GetSettings(List settings) - { - Dictionary dictionary = new Dictionary(); - foreach (Setting setting in settings.OrderBy(item => item.SettingName).ToList()) - { - dictionary.Add(setting.SettingName, setting.SettingValue); - } - return dictionary; - } } } diff --git a/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs b/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs index 5cf4c59c..89a2238d 100644 --- a/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs @@ -40,18 +40,13 @@ namespace Oqtane.Infrastructure foreach (Site site in sites) { log += "
Processing Site: " + site.Name + "
"; - int retention; int count; // get site settings - Dictionary settings = GetSettings(settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList()); + var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1); // purge event log - retention = 30; // 30 days - if (settings.ContainsKey("LogRetention") && !string.IsNullOrEmpty(settings["LogRetention"])) - { - retention = int.Parse(settings["LogRetention"]); - } + var retention = int.Parse(settingRepository.GetSettingValue(settings, "LogRetention", "30")); // 30 day default try { count = logRepository.DeleteLogs(site.SiteId, retention); @@ -65,11 +60,7 @@ namespace Oqtane.Infrastructure // purge visitors if (site.VisitorTracking) { - retention = 30; // 30 days - if (settings.ContainsKey("VisitorRetention") && !string.IsNullOrEmpty(settings["VisitorRetention"])) - { - retention = int.Parse(settings["VisitorRetention"]); - } + retention = int.Parse(settingRepository.GetSettingValue(settings, "VisitorRetention", "30")); // 30 day default try { count = visitorRepository.DeleteVisitors(site.SiteId, retention); @@ -82,11 +73,7 @@ namespace Oqtane.Infrastructure } // purge notifications - retention = 30; // 30 days - if (settings.ContainsKey("NotificationRetention") && !string.IsNullOrEmpty(settings["NotificationRetention"])) - { - retention = int.Parse(settings["NotificationRetention"]); - } + retention = int.Parse(settingRepository.GetSettingValue(settings, "NotificationRetention", "30")); // 30 day default try { count = notificationRepository.DeleteNotifications(site.SiteId, retention); @@ -98,11 +85,7 @@ namespace Oqtane.Infrastructure } // purge broken urls - retention = 30; // 30 days - if (settings.ContainsKey("UrlMappingRetention") && !string.IsNullOrEmpty(settings["UrlMappingRetention"])) - { - retention = int.Parse(settings["UrlMappingRetention"]); - } + retention = int.Parse(settingRepository.GetSettingValue(settings, "UrlMappingRetention", "30")); // 30 day default try { count = urlMappingRepository.DeleteUrlMappings(site.SiteId, retention); @@ -127,15 +110,5 @@ namespace Oqtane.Infrastructure return log; } - - private Dictionary GetSettings(List settings) - { - Dictionary dictionary = new Dictionary(); - foreach (Setting setting in settings.OrderBy(item => item.SettingName).ToList()) - { - dictionary.Add(setting.SettingName, setting.SettingValue); - } - return dictionary; - } } } diff --git a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs index 1c2ec3c9..13b74176 100644 --- a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs +++ b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs @@ -23,10 +23,6 @@ namespace Oqtane.Infrastructure var config = context.RequestServices.GetService(typeof(IConfigManager)) as IConfigManager; string path = context.Request.Path.ToString(); - // note that in order to support Alias subfolders we used to ignore Blazor framework requests... - // but this does not work in static rendering as the web UI request originates from /_blazor - //if (config.IsInstalled() && !path.StartsWith("/_")) - if (config.IsInstalled()) { // get alias (note that this also sets SiteState.Alias) @@ -43,7 +39,7 @@ namespace Oqtane.Infrastructure var sitesettings = cache.GetOrCreate(Constants.HttpContextSiteSettingsKey + alias.SiteKey, entry => { var settingRepository = context.RequestServices.GetService(typeof(ISettingRepository)) as ISettingRepository; - return settingRepository.GetSettings(EntityNames.Site, alias.SiteId) + return settingRepository.GetSettings(EntityNames.Site, alias.SiteId, EntityNames.Host, -1) .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); }); context.Items.Add(Constants.HttpContextSiteSettingsKey, sitesettings); diff --git a/Oqtane.Server/Repository/Interfaces/ISettingRepository.cs b/Oqtane.Server/Repository/Interfaces/ISettingRepository.cs index 5c231806..e74e8eda 100644 --- a/Oqtane.Server/Repository/Interfaces/ISettingRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISettingRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Repository { @@ -7,11 +8,13 @@ namespace Oqtane.Repository { IEnumerable GetSettings(string entityName); IEnumerable GetSettings(string entityName, int entityId); + IEnumerable GetSettings(string entityName1, int entityId1, string entityName2, int entityId2); Setting AddSetting(Setting setting); Setting UpdateSetting(Setting setting); Setting GetSetting(string entityName, int settingId); Setting GetSetting(string entityName, int entityId, string settingName); void DeleteSetting(string entityName, int settingId); void DeleteSettings(string entityName, int entityId); + string GetSettingValue(IEnumerable settings, string settingName, string defaultValue); } } diff --git a/Oqtane.Server/Repository/SettingRepository.cs b/Oqtane.Server/Repository/SettingRepository.cs index 0ca28d50..735ec981 100644 --- a/Oqtane.Server/Repository/SettingRepository.cs +++ b/Oqtane.Server/Repository/SettingRepository.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Oqtane.Infrastructure; @@ -39,24 +38,27 @@ namespace Oqtane.Repository public IEnumerable GetSettings(string entityName, int entityId) { - var settings = GetSettings(entityName).ToList(); - if (entityName == EntityNames.Site) + return GetSettings(entityName).Where(item => item.EntityId == entityId); + } + + public IEnumerable GetSettings(string entityName1, int entityId1, string entityName2, int entityId2) + { + // merge settings from entity2 into entity1 + var settings1 = GetSettings(entityName1, entityId1).ToList(); + foreach (var setting2 in GetSettings(entityName2, entityId2)) { - // site settings can be overridden by host settings - var hostsettings = GetSettings(EntityNames.Host); - foreach (var hostsetting in hostsettings) + var setting1 = settings1.FirstOrDefault(item => item.SettingName == setting2.SettingName); + if (setting1 == null) { - if (settings.Any(item => item.SettingName == hostsetting.SettingName)) - { - settings.First(item => item.SettingName == hostsetting.SettingName).SettingValue = hostsetting.SettingValue; - } - else - { - settings.Add(new Setting { SettingId = -1, EntityName = entityName, EntityId = entityId, SettingName = hostsetting.SettingName, SettingValue = hostsetting.SettingValue, IsPrivate = hostsetting.IsPrivate }); - } + settings1.Add(new Setting { EntityName = entityName1, EntityId = entityId1, SettingName = setting2.SettingName, SettingValue = setting2.SettingValue, IsPrivate = setting2.IsPrivate }); + } + else + { + setting1.SettingValue = setting2.SettingValue; + setting1.IsPrivate = setting2.IsPrivate; } } - return settings.Where(item => item.EntityId == entityId); + return settings1; } public Setting AddSetting(Setting setting) @@ -165,6 +167,19 @@ namespace Oqtane.Repository ManageCache(entityName); } + public string GetSettingValue(IEnumerable settings, string settingName, string defaultValue) + { + var setting = settings.FirstOrDefault(item => item.SettingName == settingName); + if (setting != null) + { + return setting.SettingValue; + } + else + { + return defaultValue; + } + } + private bool IsMaster(string EntityName) { return (EntityName == EntityNames.ModuleDefinition || EntityName == EntityNames.Host); diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index 11c624c3..cc0991d8 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -111,7 +111,7 @@ namespace Oqtane.Services if (site != null && site.SiteId == alias.SiteId) { // site settings - site.Settings = _settings.GetSettings(EntityNames.Site, site.SiteId) + site.Settings = _settings.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1) .ToDictionary(setting => setting.SettingName, setting => (setting.IsPrivate ? _private : "") + setting.SettingValue); // populate file extensions diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 51b60555..b51609ae 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -195,9 +195,6 @@ namespace Oqtane app.UseHsts(); } - // execute any IServerStartup logic - app.ConfigureOqtaneAssemblies(env); - // allow oqtane localization middleware app.UseOqtaneLocalization(); @@ -248,6 +245,9 @@ namespace Oqtane .AddAdditionalAssemblies(typeof(SiteRouter).Assembly); }); + // execute any IServerStartup logic + app.ConfigureOqtaneAssemblies(env); + // simulate the fallback routing approach of traditional Blazor - allowing the custom SiteRouter to handle all routing concerns app.UseEndpoints(endpoints => {