From d1e80cb86aec0746e501d694d3a8c800c112217b Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 29 Aug 2023 14:01:00 -0400 Subject: [PATCH 01/53] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b2370579..e5dba005 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[4.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2) was released on Aug 9, 2023 and is primary focused on stabilization. This release includes 50 pull requests by 9 different contributors, pushing the total number of project commits all-time over 3900. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[4.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3) was released on Aug 29, 2023 and is primary focused on stabilization. This release includes 50 pull requests by 6 different contributors, pushing the total number of project commits all-time over 4000. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json) @@ -53,6 +53,9 @@ Backlog (TBD) 5.0.0 (Q4 2023) - [ ] Migration to .NET 8 +[4.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3) ( Aug 29, 2023 ) +- [x] Stabilization improvements + [4.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2) ( Aug 9, 2023 ) - [x] Stabilization improvements From 0d56d3564649f02641b7bd544651113a9c19d720 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 31 Aug 2023 13:54:56 -0400 Subject: [PATCH 02/53] Marketplace UI consistency --- .../Modules/Admin/ModuleDefinitions/Add.razor | 21 ++++++++++------ Oqtane.Client/Modules/Admin/Themes/Add.razor | 25 +++++++++++-------- .../Modules/Admin/ModuleDefinitions/Add.resx | 2 +- .../Resources/Modules/Admin/Themes/Add.resx | 2 +- .../Modules/[Owner].Module.[Module]/Module.js | 2 +- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor index e1b6387a..358fb1c5 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor @@ -48,7 +48,10 @@ - + @if (_price == "free") + { + + } @if (_price == "paid") { @@ -74,13 +77,19 @@
@SharedLocalizer["Search.Version"]: @context.Version -
@SharedLocalizer["Search.Downloads"]: @(String.Format("{0:n0}", context.Downloads))
@SharedLocalizer["Search.Released"]: @context.ReleaseDate.ToString("MM/dd/yyyy") @if (!string.IsNullOrEmpty(context.PackageUrl)) { -
- - @SharedLocalizer["Search.Source"]: @(new Uri(context.PackageUrl).Host) +
@SharedLocalizer["Search.Source"]: @(new Uri(context.PackageUrl).Host) + } + @if (_price == "free") + { +
@SharedLocalizer["Search.Downloads"]: @(String.Format("{0:n0}", context.Downloads)) + } + else + { +
@SharedLocalizer["From"]: @context.Price.Value.ToString("$#,##0.00") + @((MarkupString)(context.TrialPeriod > 0 ? " (" + context.TrialPeriod + " Day Trial)" : "")) }
@@ -89,11 +98,6 @@

@context.Name


@SharedLocalizer["Search.By"]: @context.Owner
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)
- @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) - { - @SharedLocalizer["From"]: @context.Price.Value.ToString("$#,##0.00") - @((MarkupString)(context.TrialPeriod > 0 ? " (" + context.TrialPeriod + " Day Trial)" : "")) - }
@if (!string.IsNullOrEmpty(context.PackageUrl)) { @@ -215,6 +219,7 @@ private async void PriceChanged(string price) { _price = price; + _sort = "popularity"; await LoadThemes(); StateHasChanged(); } diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Add.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Add.resx index 1f28095e..a75079fd 100644 --- a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Add.resx @@ -124,7 +124,7 @@ Error Loading Packages - Module Package Saved Successfully. You Must <a href={0}>Restart</a> Your Application To Complete The Installation. + Module Package Downloaded Successfully. You Must <a href={0}>Restart</a> Your Application To Complete The Installation. Error Downloading Module diff --git a/Oqtane.Client/Resources/Modules/Admin/Themes/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Themes/Add.resx index 08723fba..93712015 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Themes/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Themes/Add.resx @@ -124,7 +124,7 @@ Theme: - Theme Package Saved Successfully. You Must <a href={0}>Restart</a> Your Application To Complete The Installation. + Theme Package Downloaded Successfully. You Must <a href={0}>Restart</a> Your Application To Complete The Installation. Error Downloading Theme diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.js b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.js index 11d3ded6..8f072470 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.js +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.js @@ -1,5 +1,5 @@ /* Module Script */ var [Owner] = [Owner] || {}; -[Owner].Module.[Module] = { +[Owner].[Module] = { }; \ No newline at end of file From 211d8c7f19d8611c9af5c7614647f9e0936067fa Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 31 Aug 2023 14:18:19 -0400 Subject: [PATCH 03/53] need to use context rather than filter --- Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor | 2 +- Oqtane.Client/Modules/Admin/Themes/Add.razor | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor index 358fb1c5..15cc93bd 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor @@ -82,7 +82,7 @@ {
@SharedLocalizer["Search.Source"]: @(new Uri(context.PackageUrl).Host) } - @if (_price == "free") + @if (context.Price == null) {
@SharedLocalizer["Search.Downloads"]: @(String.Format("{0:n0}", context.Downloads)) } diff --git a/Oqtane.Client/Modules/Admin/Themes/Add.razor b/Oqtane.Client/Modules/Admin/Themes/Add.razor index 6dabcb8c..2c9ecb0d 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Add.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Add.razor @@ -82,7 +82,7 @@ {
@SharedLocalizer["Search.Source"]: @(new Uri(context.PackageUrl).Host) } - @if (_price == "free") + @if (context.Price == null) {
@SharedLocalizer["Search.Downloads"]: @(String.Format("{0:n0}", context.Downloads)) } From e91db18677e58602c8df4849a46f826199489b75 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 1 Sep 2023 12:14:04 -0400 Subject: [PATCH 04/53] allow an administratot to browse to the SiteMap, handle default module panes and ordering for PageTemplates, allow trial products to be purchased --- Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor | 2 +- Oqtane.Client/Modules/Admin/Site/Index.razor | 7 +++++-- Oqtane.Client/Modules/Admin/Themes/Add.razor | 2 +- Oqtane.Server/Repository/SiteRepository.cs | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor index 15cc93bd..059b4a9b 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor @@ -103,7 +103,7 @@ { } - @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl) && string.IsNullOrEmpty(context.PackageUrl)) + @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) { @SharedLocalizer["Buy"] } diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index ce8a4fc4..b70abd9b 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -49,8 +49,11 @@
- -
+ +
diff --git a/Oqtane.Client/Modules/Admin/Themes/Add.razor b/Oqtane.Client/Modules/Admin/Themes/Add.razor index 2c9ecb0d..c7364095 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Add.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Add.razor @@ -103,7 +103,7 @@ { } - @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl) && string.IsNullOrEmpty(context.PackageUrl)) + @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) { @SharedLocalizer["Buy"] } diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 2adff634..b583d90b 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -424,8 +424,8 @@ namespace Oqtane.Repository pageModule.Module.ModuleDefinitionName = pageTemplateModule.ModuleDefinitionName; } pageModule.Title = pageTemplateModule.Title; - pageModule.Pane = pageTemplateModule.Pane; - pageModule.Order = pageTemplateModule.Order; + pageModule.Pane = (string.IsNullOrEmpty(pageTemplateModule.Pane)) ? PaneNames.Default : pageTemplateModule.Pane; + pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order; pageModule.ContainerType = pageTemplateModule.ContainerType; pageModule.IsDeleted = pageTemplateModule.IsDeleted; pageModule.Module.PermissionList = pageTemplateModule.PermissionList; From f40f3f934f6509a837a36c8e26f411852dfe3f04 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 1 Sep 2023 13:02:44 -0400 Subject: [PATCH 05/53] Fix #3215 - module creator creates incorrect ServerManagerType value --- Oqtane.Server/Controllers/ModuleDefinitionController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 314ec0e3..7afc125e 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -139,14 +139,14 @@ namespace Oqtane.Controllers if (moduleDefinition.Template.ToLower().Contains("internal")) { rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString()); - moduleDefinition.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName + ", Oqtane.Client"; moduleDefinition.ServerManagerType = moduleDefinition.ModuleDefinitionName + ".Manager." + moduleDefinition.Name + "Manager, Oqtane.Server"; + moduleDefinition.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName + ", Oqtane.Client"; } else { rootPath = Utilities.PathCombine(rootFolder.Parent.FullName, moduleDefinition.Owner + ".Module." + moduleDefinition.Name, Path.DirectorySeparatorChar.ToString()); - moduleDefinition.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName + ", " + moduleDefinition.ModuleDefinitionName + ".Client.Oqtane"; moduleDefinition.ServerManagerType = moduleDefinition.ModuleDefinitionName + ".Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.ModuleDefinitionName + ".Server.Oqtane"; + moduleDefinition.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName + ", " + moduleDefinition.ModuleDefinitionName + ".Client.Oqtane"; } ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition); From d491aeeba628d2969aec631db86352b83e0b5656 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 1 Sep 2023 13:16:39 -0400 Subject: [PATCH 06/53] include User Settings when calling UserService --- Oqtane.Server/Controllers/UserController.cs | 16 +++++++++++++++- Oqtane.Shared/Models/User.cs | 7 +++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index f26ece36..e57635cb 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -26,16 +26,18 @@ namespace Oqtane.Controllers private readonly IUserManager _userManager; private readonly ISiteRepository _sites; private readonly IUserPermissions _userPermissions; + private readonly ISettingRepository _settings; private readonly IJwtManager _jwtManager; private readonly ILogManager _logger; - public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, ILogManager logger) + public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, ISettingRepository settings, IJwtManager jwtManager, ILogManager logger) { _users = users; _tenantManager = tenantManager; _userManager = userManager; _sites = sites; _userPermissions = userPermissions; + _settings = settings; _jwtManager = jwtManager; _logger = logger; } @@ -52,6 +54,12 @@ namespace Oqtane.Controllers { HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; } + else + { + List settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList(); + user.Settings = settings.Where(item => !item.IsPrivate || _userPermissions.GetUser(User).UserId == user.UserId) + .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); + } return Filter(user); } else @@ -75,6 +83,12 @@ namespace Oqtane.Controllers { HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; } + else + { + List settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList(); + user.Settings = settings.Where(item => !item.IsPrivate || _userPermissions.GetUser(User).UserId == user.UserId) + .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); + } return Filter(user); } else diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs index d7436b4c..1b9a518d 100644 --- a/Oqtane.Shared/Models/User.cs +++ b/Oqtane.Shared/Models/User.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; namespace Oqtane.Models @@ -105,5 +106,11 @@ namespace Oqtane.Models /// [NotMapped] public bool EmailConfirmed { get; set; } + + /// + /// Public User Settings + /// + [NotMapped] + public Dictionary Settings { get; set; } } } From 7513ae28f0ac7c01a7e1e6afddd67eac2ea5b6ef Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 1 Sep 2023 13:57:14 -0400 Subject: [PATCH 07/53] fix paths in Edit Page / Modules tab / Edit option --- Oqtane.Client/Modules/Admin/Pages/Edit.razor | 7 +- .../Modules/Controls/ActionLink.razor | 185 ++++++++++-------- 2 files changed, 106 insertions(+), 86 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 4af2a227..e25fc4dd 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -209,7 +209,7 @@ @Localizer["ModuleDefinition"] - + @context.Title @context.ModuleDefinition?.Name @@ -292,6 +292,7 @@ private int _childid = -1; private string _isnavigation; private string _isclickable; + private string _actualpath; private string _path; private string _url; private string _ispersonalizable; @@ -344,7 +345,8 @@ _currentparentid = _parentid; _isnavigation = _page.IsNavigation.ToString(); _isclickable = _page.IsClickable.ToString(); - _path = _page.Path; + _actualpath = _page.Path; + _path = _actualpath; if (string.IsNullOrEmpty(_path)) { _path = "/"; @@ -672,5 +674,4 @@ { _icon = NewIcon; } - } diff --git a/Oqtane.Client/Modules/Controls/ActionLink.razor b/Oqtane.Client/Modules/Controls/ActionLink.razor index 60eed4ca..5afcb874 100644 --- a/Oqtane.Client/Modules/Controls/ActionLink.razor +++ b/Oqtane.Client/Modules/Controls/ActionLink.razor @@ -24,116 +24,135 @@ } @code { - private string _text = string.Empty; - private string _parameters = string.Empty; - private string _url = string.Empty; - private List _permissions; - private bool _editmode = false; - private bool _authorized = false; - private string _classname = "btn btn-primary"; - private string _style = string.Empty; - private string _iconSpan = string.Empty; + private string _text = string.Empty; + private int _moduleId = -1; + private string _path = string.Empty; + private string _parameters = string.Empty; + private string _url = string.Empty; + private List _permissions; + private bool _editmode = false; + private bool _authorized = false; + private string _classname = "btn btn-primary"; + private string _style = string.Empty; + private string _iconSpan = string.Empty; - [Parameter] - public string Action { get; set; } // required + [Parameter] + public string Action { get; set; } // required - [Parameter] - public string Text { get; set; } // optional - defaults to Action if not specified + [Parameter] + public string Text { get; set; } // optional - defaults to Action if not specified - [Parameter] - public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y" + [Parameter] + public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid - [Parameter] - public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid + [Parameter] + public string Path { get; set; } = null; // optional - allows the link to target a specific page - [Parameter] - public Action OnClick { get; set; } = null; // optional - executes a method in the calling component + [Parameter] + public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y" - [Parameter] - public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel + [Parameter] + public Action OnClick { get; set; } = null; // optional - executes a method in the calling component - [Parameter] - public string Permissions { get; set; } // deprecated - use PermissionList instead + [Parameter] + public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel - [Parameter] - public List PermissionList { get; set; } // optional - can be used to specify permissions + [Parameter] + public string Permissions { get; set; } // deprecated - use PermissionList instead - [Parameter] - public bool Disabled { get; set; } // optional + [Parameter] + public List PermissionList { get; set; } // optional - can be used to specify permissions - [Parameter] - public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false. + [Parameter] + public bool Disabled { get; set; } // optional - [Parameter] - public string Class { get; set; } // optional - defaults to primary if not specified + [Parameter] + public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false. - [Parameter] - public string Style { get; set; } // optional + [Parameter] + public string Class { get; set; } // optional - defaults to primary if not specified - [Parameter] - public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon + [Parameter] + public string Style { get; set; } // optional - [Parameter] - public bool IconOnly { get; set; } // optional - specifies only icon in link + [Parameter] + public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon - [Parameter] - public string ReturnUrl { get; set; } // optional - used to set a url to redirect to + [Parameter] + public bool IconOnly { get; set; } // optional - specifies only icon in link - protected override void OnInitialized() - { - if (!string.IsNullOrEmpty(Permissions)) - { - PermissionList = JsonSerializer.Deserialize>(Permissions); - } - } + [Parameter] + public string ReturnUrl { get; set; } // optional - used to set a url to redirect to - protected override void OnParametersSet() - { - base.OnParametersSet(); - _text = Action; - if (!string.IsNullOrEmpty(Text)) - { - _text = Text; - } + protected override void OnInitialized() + { + if (!string.IsNullOrEmpty(Permissions)) + { + PermissionList = JsonSerializer.Deserialize>(Permissions); + } + } - if (IconOnly && !string.IsNullOrEmpty(IconName)) - { - _text = string.Empty; - } + protected override void OnParametersSet() + { + base.OnParametersSet(); - if (!string.IsNullOrEmpty(Parameters)) - { - _parameters = Parameters; - } + _text = Action; + if (!string.IsNullOrEmpty(Text)) + { + _text = Text; + } - if (!string.IsNullOrEmpty(Class)) - { - _classname = Class; - } + if (IconOnly && !string.IsNullOrEmpty(IconName)) + { + _text = string.Empty; + } - if (!string.IsNullOrEmpty(Style)) - { - _style = Style; - } + _moduleId = ModuleState.ModuleId; + if (ModuleId != -1) + { + _moduleId = ModuleId; + } - if (!string.IsNullOrEmpty(EditMode)) - { - _editmode = bool.Parse(EditMode); - } + _path = PageState.Page.Path; + if (Path != null) + { + _path = Path; + } - if (!string.IsNullOrEmpty(IconName)) - { - if (!IconName.Contains(" ")) - { - IconName = "oi oi-" + IconName; - } - _iconSpan = $"{(IconOnly ? "" : " ")}"; - } + if (!string.IsNullOrEmpty(Parameters)) + { + _parameters = Parameters; + } + + if (!string.IsNullOrEmpty(Class)) + { + _classname = Class; + } + + if (!string.IsNullOrEmpty(Style)) + { + _style = Style; + } + + if (!string.IsNullOrEmpty(EditMode)) + { + _editmode = bool.Parse(EditMode); + } + + if (!string.IsNullOrEmpty(IconName)) + { + if (!IconName.Contains(" ")) + { + IconName = "oi oi-" + IconName; + } + _iconSpan = $"{(IconOnly ? "" : " ")}"; + } _permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList; _text = Localize(nameof(Text), _text); - _url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters); + + _url = EditUrl(_path, _moduleId, Action, _parameters); if (!string.IsNullOrEmpty(ReturnUrl)) { _url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}"; From 1f4ae5dbfb6af58f71cb5d9506dcaa4a4c5dd4c2 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 1 Sep 2023 14:04:27 -0400 Subject: [PATCH 08/53] improve help text for paclage name --- Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor | 2 +- Oqtane.Client/Modules/Admin/Themes/Edit.razor | 2 +- .../Resources/Modules/Admin/ModuleDefinitions/Edit.resx | 2 +- Oqtane.Client/Resources/Modules/Admin/Themes/Edit.resx | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor index ec3cecb7..f7da5778 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor @@ -59,7 +59,7 @@
- +
diff --git a/Oqtane.Client/Modules/Admin/Themes/Edit.razor b/Oqtane.Client/Modules/Admin/Themes/Edit.razor index 2b84c09d..53cbae7e 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Edit.razor @@ -42,7 +42,7 @@
- +
diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx index 6611338c..5cd18d11 100644 --- a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx @@ -196,7 +196,7 @@ Information - The unique name of the package from which this module was installed + The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification. Package Name: diff --git a/Oqtane.Client/Resources/Modules/Admin/Themes/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Themes/Edit.resx index ee0d30cc..c18f9761 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Themes/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Themes/Edit.resx @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Select or upload a CSV file containing user information. The CSV file must be in the Template format specified. + + + User File: + + + Error Importing Users + + + Import + + + User Import Failed + + + Users Imported Successfully + + + You Must Specify A User File For Import + + + Template + + \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/IUserService.cs b/Oqtane.Client/Services/Interfaces/IUserService.cs index f2a7a83b..04661e8f 100644 --- a/Oqtane.Client/Services/Interfaces/IUserService.cs +++ b/Oqtane.Client/Services/Interfaces/IUserService.cs @@ -142,5 +142,12 @@ namespace Oqtane.Services /// ID of a /// Task GetPasswordRequirementsAsync(int siteId); + + /// + /// Bulk import of users + /// + /// ID of a + /// + Task ImportUsersAsync(int siteId, int fileId); } } diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index 033b585d..770c4eb1 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -6,6 +6,9 @@ using Oqtane.Documentation; using System.Net; using System.Collections.Generic; using Microsoft.Extensions.Localization; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Oqtane.Modules.Admin.Roles; +using System.Xml.Linq; namespace Oqtane.Services { @@ -123,5 +126,10 @@ namespace Oqtane.Services // format requirements return string.Format(passwordValidationCriteriaTemplate, minimumlength, uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement); } + + public async Task ImportUsersAsync(int siteId, int fileId) + { + return await PostJsonAsync($"{Apiurl}/import?siteid={siteId}&fileid={fileId}", true); + } } } diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index e57635cb..3788817c 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -28,9 +28,10 @@ namespace Oqtane.Controllers private readonly IUserPermissions _userPermissions; private readonly ISettingRepository _settings; private readonly IJwtManager _jwtManager; + private readonly IFileRepository _files; private readonly ILogManager _logger; - public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, ISettingRepository settings, IJwtManager jwtManager, ILogManager logger) + public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, ISettingRepository settings, IJwtManager jwtManager, IFileRepository files, ILogManager logger) { _users = users; _tenantManager = tenantManager; @@ -39,6 +40,7 @@ namespace Oqtane.Controllers _userPermissions = userPermissions; _settings = settings; _jwtManager = jwtManager; + _files = files; _logger = logger; } @@ -369,5 +371,22 @@ namespace Oqtane.Controllers return requirements; } + + // POST api//import?siteid=x&fileid=y + [HttpPost("import")] + [Authorize(Roles = RoleNames.Admin)] + public async Task Import(string siteid, string fileid) + { + if (int.TryParse(siteid, out int SiteId) && SiteId == _tenantManager.GetAlias().SiteId && int.TryParse(fileid, out int FileId)) + { + return await _userManager.ImportUsers(SiteId, FileId); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return false; + } + } } } diff --git a/Oqtane.Server/Managers/Interfaces/IUserManager.cs b/Oqtane.Server/Managers/Interfaces/IUserManager.cs index 82cf7090..7eb79e78 100644 --- a/Oqtane.Server/Managers/Interfaces/IUserManager.cs +++ b/Oqtane.Server/Managers/Interfaces/IUserManager.cs @@ -18,5 +18,6 @@ namespace Oqtane.Managers User VerifyTwoFactor(User user, string token); Task LinkExternalAccount(User user, string token, string type, string key, string name); Task ValidatePassword(string password); + Task ImportUsers(int siteId, int fileId); } } diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 28e5b751..1a856607 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -16,24 +17,32 @@ namespace Oqtane.Managers public class UserManager : IUserManager { private readonly IUserRepository _users; + private readonly IRoleRepository _roles; private readonly IUserRoleRepository _userRoles; private readonly UserManager _identityUserManager; private readonly SignInManager _identitySignInManager; private readonly ITenantManager _tenantManager; private readonly INotificationRepository _notifications; private readonly IFolderRepository _folders; + private readonly IFileRepository _files; + private readonly IProfileRepository _profiles; + private readonly ISettingRepository _settings; private readonly ISyncManager _syncManager; private readonly ILogManager _logger; - public UserManager(IUserRepository users, IUserRoleRepository userRoles, UserManager identityUserManager, SignInManager identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ILogManager logger) + public UserManager(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager identityUserManager, SignInManager identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, IFileRepository files, IProfileRepository profiles, ISettingRepository settings, ISyncManager syncManager, ILogManager logger) { _users = users; + _roles = roles; _userRoles = userRoles; _identityUserManager = identityUserManager; _identitySignInManager = identitySignInManager; _tenantManager = tenantManager; _notifications = notifications; _folders = folders; + _files = files; + _profiles = profiles; + _settings = settings; _syncManager = syncManager; _logger = logger; } @@ -95,6 +104,12 @@ namespace Oqtane.Managers IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); if (identityuser == null) { + if (string.IsNullOrEmpty(user.Password)) + { + // create random password ie. Jan-01-2023+12:00:00! + Random rnd = new Random(); + user.Password = DateTime.UtcNow.ToString("MMM-dd-yyyy+HH:mm:ss", CultureInfo.InvariantCulture) + (char)rnd.Next(33, 47); + } identityuser = new IdentityUser(); identityuser.UserName = user.Username; identityuser.Email = user.Email; @@ -443,5 +458,144 @@ namespace Oqtane.Managers var result = await validator.ValidateAsync(_identityUserManager, null, password); return result.Succeeded; } + + public async Task ImportUsers(int siteId, int fileId) + { + var success = true; + int users = 0; + + var file = _files.GetFile(fileId); + if (file != null) + { + var path = _files.GetFilePath(file); + if (System.IO.File.Exists(path)) + { + var roles = _roles.GetRoles(siteId).ToList(); + var profiles = _profiles.GetProfiles(siteId).ToList(); + + try + { + string row; + using (var reader = new StreamReader(path)) + { + // get header row + row = reader.ReadLine(); + var header = row.Replace("\"", "").Split(','); + + row = reader.ReadLine(); + while (row != null) + { + var values = row.Replace("\"", "").Split(','); + + if (values.Length > 3) + { + // user + var user = _users.GetUser(values[1], values[0]); + if (user == null) + { + user = new User(); + user.SiteId = siteId; + user.Email = values[0]; + user.Username = (!string.IsNullOrEmpty(values[1])) ? values[1] : user.Email; + user.DisplayName = (!string.IsNullOrEmpty(values[2])) ? values[2] : user.Username; + user = await AddUser(user); + if (user == null) + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, "Error Creating User {Email}", values[0]); + success = false; + } + } + + if (user != null && !string.IsNullOrEmpty(values[3])) + { + // roles (comma delimited) + foreach (var rolename in values[3].Split(',')) + { + var role = roles.FirstOrDefault(item => item.Name == rolename); + if (role == null) + { + role = new Role(); + role.SiteId = siteId; + role.Name = rolename; + role.Description = rolename; + role = _roles.AddRole(role); + roles.Add(role); + } + if (role != null) + { + var userrole = _userRoles.GetUserRole(user.UserId, role.RoleId, false); + if (userrole == null) + { + userrole = new UserRole(); + userrole.UserId = user.UserId; + userrole.RoleId = role.RoleId; + _userRoles.AddUserRole(userrole); + } + } + } + } + + if (user != null && values.Length > 4) + { + var settings = _settings.GetSettings(EntityNames.User, user.UserId); + for (int index = 4; index < values.Length - 1; index++) + { + if (header.Length > index && !string.IsNullOrEmpty(values[index])) + { + var profile = profiles.FirstOrDefault(item => item.Name == header[index]); + if (profile != null) + { + var setting = settings.FirstOrDefault(item => item.SettingName == profile.Name); + if (setting == null) + { + setting = new Setting(); + setting.EntityName = EntityNames.User; + setting.EntityId = user.UserId; + setting.SettingName = profile.Name; + setting.SettingValue = values[index]; + _settings.AddSetting(setting); + } + else + { + if (setting.SettingValue != values[index]) + { + setting.SettingValue = values[index]; + _settings.UpdateSetting(setting); + } + } + } + } + } + } + + users++; + } + + row = reader.ReadLine(); + } + } + + _logger.Log(LogLevel.Information, this, LogFunction.Create, "{Users} Users Imported", users); + } + catch (Exception ex) + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Error Importing User Import File {SiteId} {FileId}", siteId, fileId); + success = false; + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Create,"User Import File Does Not Exist {Path}", path); + success = false; + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Does Not Exist {SiteId} {FileId}", siteId, fileId); + success = false; + } + + return success; + } } } diff --git a/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs b/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs index 0c4a2290..7bed5f51 100644 --- a/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs @@ -11,6 +11,8 @@ namespace Oqtane.Repository UserRole UpdateUserRole(UserRole userRole); UserRole GetUserRole(int userRoleId); UserRole GetUserRole(int userRoleId, bool tracking); + UserRole GetUserRole(int userId, int roleId); + UserRole GetUserRole(int userId, int roleId, bool tracking); void DeleteUserRole(int userRoleId); void DeleteUserRoles(int userId); } diff --git a/Oqtane.Server/Repository/UserRoleRepository.cs b/Oqtane.Server/Repository/UserRoleRepository.cs index 93acbe18..730009e1 100644 --- a/Oqtane.Server/Repository/UserRoleRepository.cs +++ b/Oqtane.Server/Repository/UserRoleRepository.cs @@ -78,6 +78,29 @@ namespace Oqtane.Repository } } + public UserRole GetUserRole(int userId, int roleId) + { + return GetUserRole(userId, roleId, true); + } + + public UserRole GetUserRole(int userId, int roleId, bool tracking) + { + if (tracking) + { + return _db.UserRole + .Include(item => item.Role) // eager load roles + .Include(item => item.User) // eager load users + .FirstOrDefault(item => item.UserId == userId && item.RoleId == roleId); + } + else + { + return _db.UserRole.AsNoTracking() + .Include(item => item.Role) // eager load roles + .Include(item => item.User) // eager load users + .FirstOrDefault(item => item.UserId == userId && item.RoleId == roleId); + } + } + public void DeleteUserRole(int userRoleId) { UserRole userRole = _db.UserRole.Find(userRoleId); diff --git a/Oqtane.Server/wwwroot/users.csv b/Oqtane.Server/wwwroot/users.csv new file mode 100644 index 00000000..ff6dcff3 --- /dev/null +++ b/Oqtane.Server/wwwroot/users.csv @@ -0,0 +1 @@ +Email,Username,DisplayName,Roles,FirstName,LastName,Street,City,Region,Country,PostalCode,Phone From 8daf38654dea267f74f68e85b50045ff5214ba68 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 20 Sep 2023 12:33:43 -0400 Subject: [PATCH 26/53] prevent System Update in development environment --- .../Modules/Admin/Upgrade/Index.razor | 78 +++++++++++-------- .../Modules/Admin/Upgrade/Index.resx | 5 +- Oqtane.Server/Managers/UserManager.cs | 6 +- 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor index 2ea14802..2c91688e 100644 --- a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor +++ b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor @@ -7,34 +7,38 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer - - - @if (_package != null && _upgradeavailable) - { - - - - } - else - { - - } - - - -
-
- -
- +@if (_initialized) +{ + + + @if (_package != null && _upgradeavailable) + { + + + + } + else + { + + } + + + +
+
+ +
+ +
-
- - - + + + +} @code { + private bool _initialized = false; private Package _package; private bool _upgradeavailable = false; @@ -44,18 +48,26 @@ { try { - List packages = await PackageService.GetPackagesAsync("framework", "", "", ""); - if (packages != null) + if (NavigationManager.BaseUri.Contains("localhost:")) { - _package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault(); - if (_package != null) + AddModuleMessage(Localizer["Localhost.Text"], MessageType.Info); + } + else + { + List packages = await PackageService.GetPackagesAsync("framework", "", "", ""); + if (packages != null) { - _upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0); - } - else - { - _package = new Package { Name = Constants.PackageId, Version = Constants.Version }; + _package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault(); + if (_package != null) + { + _upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0); + } + else + { + _package = new Package { Name = Constants.PackageId, Version = Constants.Version }; + } } + _initialized = true; } } catch diff --git a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx index 5f1fa146..40d8af4c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx @@ -144,7 +144,10 @@ Framework Is Already Up To Date - + Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade + + You Cannot Perform A System Update In A Development Environment + \ No newline at end of file diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 1a856607..49f2cf01 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -106,9 +107,10 @@ namespace Oqtane.Managers { if (string.IsNullOrEmpty(user.Password)) { - // create random password ie. Jan-01-2023+12:00:00! + // create random interal password based on random date and punctuation ie. Jan-23-1981+14:43:12! Random rnd = new Random(); - user.Password = DateTime.UtcNow.ToString("MMM-dd-yyyy+HH:mm:ss", CultureInfo.InvariantCulture) + (char)rnd.Next(33, 47); + var date = DateTime.UtcNow.AddDays(-rnd.Next(50 * 365)).AddHours(rnd.Next(0, 24)).AddMinutes(rnd.Next(0, 60)).AddSeconds(rnd.Next(0, 60)); + user.Password = date.ToString("MMM-dd-yyyy+HH:mm:ss", CultureInfo.InvariantCulture) + (char)rnd.Next(33, 47); } identityuser = new IdentityUser(); identityuser.UserName = user.Username; From e0ebf7090742e2d13871fe2c211845078dec6a6a Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 20 Sep 2023 13:45:19 -0400 Subject: [PATCH 27/53] if site login is disabled redirect Login to external identity provider --- Oqtane.Client/Modules/Admin/Login/Index.razor | 14 ++++---------- .../Modules/Admin/UserProfile/Index.razor | 5 +---- Oqtane.Client/Themes/Controls/Theme/LoginBase.cs | 12 +++++++++++- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index e8ffd3c6..b8e3c8f2 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -3,6 +3,7 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService +@inject ISettingService SettingService @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -94,15 +95,8 @@ { _togglepassword = SharedLocalizer["ShowPassword"]; - if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"])) - { - _allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]); - } - - if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"])) - { - _allowexternallogin = true; - } + _allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true")); + _allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false; if (PageState.QueryString.ContainsKey("returnurl")) { @@ -220,7 +214,7 @@ } else { - if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired) + if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired) { twofactor = true; validated = false; diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index b763b08b..a10202ee 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -299,10 +299,7 @@ else _togglepassword = SharedLocalizer["ShowPassword"]; - if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"])) - { - allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true"); - } + allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true"); if (PageState.User != null) { diff --git a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs index 468e398e..40f8ed4b 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs +++ b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs @@ -17,13 +17,23 @@ namespace Oqtane.Themes.Controls { [Inject] public NavigationManager NavigationManager { get; set; } [Inject] public IUserService UserService { get; set; } + [Inject] public ISettingService SettingService { get; set; } [Inject] public IJSRuntime jsRuntime { get; set; } [Inject] public IServiceProvider ServiceProvider { get; set; } + private bool _allowsitelogin = true; + protected void LoginUser() { Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path); - NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); + if (bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"))) + { + NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); + } + else + { + NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)), true); + } } protected async Task LogoutUser() From a6ca843ced5bea43e9bfd703111d89e08a6ded97 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 20 Sep 2023 14:29:06 -0400 Subject: [PATCH 28/53] fix #3231 - validate module description --- .../Admin/ModuleDefinitions/Create.razor | 28 ++- .../Modules/Admin/Themes/Create.razor | 5 +- .../Modules/Admin/ModuleCreator/Index.resx | 177 ------------------ .../Admin/ModuleDefinitions/Create.resx | 3 + .../Themes/Controls/Theme/LoginBase.cs | 2 - 5 files changed, 29 insertions(+), 186 deletions(-) delete mode 100644 Oqtane.Client/Resources/Modules/Admin/ModuleCreator/Index.resx diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor index 40a9b1e2..30dda63a 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor @@ -89,7 +89,10 @@ protected override void OnInitialized() { - AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info); + if (!NavigationManager.BaseUri.Contains("localhost:")) + { + AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info); + } } protected override async Task OnParametersSetAsync() @@ -115,11 +118,18 @@ { if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-") { - var template = _templates.FirstOrDefault(item => item.Name == _template); - var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace }; - moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); - GetLocation(); - AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success); + if (IsValidXML(_description)) + { + var template = _templates.FirstOrDefault(item => item.Name == _template); + var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace }; + moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); + GetLocation(); + AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success); + } + else + { + AddModuleMessage(Localizer["Message.Require.ValidDescription"], MessageType.Warning); + } } else { @@ -143,6 +153,12 @@ return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); } + private bool IsValidXML(string description) + { + // must contain letters, digits, or spaces + return Regex.IsMatch(description, "^[A-Za-z0-9 ]+$"); + } + private void TemplateChanged(ChangeEventArgs e) { _template = (string)e.Value; diff --git a/Oqtane.Client/Modules/Admin/Themes/Create.razor b/Oqtane.Client/Modules/Admin/Themes/Create.razor index 04fa0049..127a0415 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Create.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Create.razor @@ -80,7 +80,10 @@ protected override void OnInitialized() { - AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info); + if (!NavigationManager.BaseUri.Contains("localhost:")) + { + AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info); + } } protected override async Task OnParametersSetAsync() diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleCreator/Index.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleCreator/Index.resx deleted file mode 100644 index 16000d3e..00000000 --- a/Oqtane.Client/Resources/Modules/Admin/ModuleCreator/Index.resx +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Template: - - - Select Template - - - Create Module - - - Activate Module - - - Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment - - - Once You Have Compiled The Module And Restarted The Application You Can Activate The Module Below - - - The Source Code For Your Module Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must <a href={0}>Restart</a> Your Application To Apply These Changes. - - - You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template - - - Enter the name of the organization who is developing this module. It should not contain spaces or punctuation. - - - Enter a name for this module. It should not contain spaces or punctuation. - - - Enter a short description for the module - - - Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server. - - - Select a framework reference version - - - Location where the module will be created - - - Owner Name: - - - Module Name: - - - Description: - - - Framework Reference: - - - Location: - - \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Create.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Create.resx index 8ab77a00..cac13d97 100644 --- a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Create.resx +++ b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Create.resx @@ -132,6 +132,9 @@ You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template + + You Must Provide A Valid Description (ie. No Punctuation) + Enter the name of the organization who is developing this module. It should not contain spaces or punctuation. diff --git a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs index 40f8ed4b..37d57547 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs +++ b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs @@ -21,8 +21,6 @@ namespace Oqtane.Themes.Controls [Inject] public IJSRuntime jsRuntime { get; set; } [Inject] public IServiceProvider ServiceProvider { get; set; } - private bool _allowsitelogin = true; - protected void LoginUser() { Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path); From 6b9de40442769ab9d8c5c9e3bcfc746aa055ca50 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 20 Sep 2023 14:58:54 -0400 Subject: [PATCH 29/53] prepare for 4.0.4 release --- Oqtane.Client/Oqtane.Client.csproj | 4 ++-- Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj | 4 ++-- Oqtane.Database.MySQL/Oqtane.Database.MySQL.nuspec | 4 ++-- .../Oqtane.Database.PostgreSQL.csproj | 4 ++-- .../Oqtane.Database.PostgreSQL.nuspec | 4 ++-- Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj | 4 ++-- Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.nuspec | 4 ++-- Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj | 4 ++-- Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.nuspec | 4 ++-- Oqtane.Maui/Oqtane.Maui.csproj | 6 +++--- Oqtane.Package/Oqtane.Client.nuspec | 4 ++-- Oqtane.Package/Oqtane.Framework.nuspec | 6 +++--- Oqtane.Package/Oqtane.Server.nuspec | 4 ++-- Oqtane.Package/Oqtane.Shared.nuspec | 4 ++-- Oqtane.Package/Oqtane.Updater.nuspec | 4 ++-- Oqtane.Package/install.ps1 | 2 +- Oqtane.Package/upgrade.ps1 | 2 +- Oqtane.Server/Oqtane.Server.csproj | 4 ++-- Oqtane.Shared/Oqtane.Shared.csproj | 4 ++-- Oqtane.Shared/Shared/Constants.cs | 4 ++-- Oqtane.Updater/Oqtane.Updater.csproj | 4 ++-- 21 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index ecf1b61c..a893979f 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -4,7 +4,7 @@ net7.0 Exe Debug;Release - 4.0.3 + 4.0.4 Oqtane Shaun Walker .NET Foundation @@ -12,7 +12,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj index 2fb4ff4b..7f22da7b 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -2,7 +2,7 @@ net7.0 - 4.0.3 + 4.0.4 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 https://github.com/oqtane/oqtane.framework Git true diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.nuspec b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.nuspec index f747cbe7..d4321a56 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.nuspec +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.nuspec @@ -2,7 +2,7 @@ Oqtane.Database.MySQL - 4.0.3 + 4.0.4 Shaun Walker .NET Foundation Oqtane MySQL Provider @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 icon.png oqtane diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index 89c8ce79..9c4e4140 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -2,7 +2,7 @@ net7.0 - 4.0.3 + 4.0.4 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 https://github.com/oqtane/oqtane.framework Git true diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.nuspec b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.nuspec index a5c98576..f474c926 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.nuspec +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.nuspec @@ -2,7 +2,7 @@ Oqtane.Database.PostgreSQL - 4.0.3 + 4.0.4 Shaun Walker .NET Foundation Oqtane PostgreSQL Provider @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 icon.png oqtane diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index a243d942..6f479f72 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -2,7 +2,7 @@ net7.0 - 4.0.3 + 4.0.4 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 https://github.com/oqtane/oqtane.framework Git true diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.nuspec b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.nuspec index bec63e51..70b39b3d 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.nuspec +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.nuspec @@ -2,7 +2,7 @@ Oqtane.Database.SqlServer - 4.0.3 + 4.0.4 Shaun Walker .NET Foundation Oqtane SQL Server Provider @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 icon.png oqtane diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index d9268bc4..c4d39492 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -2,7 +2,7 @@ net7.0 - 4.0.3 + 4.0.4 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 https://github.com/oqtane/oqtane.framework Git true diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.nuspec b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.nuspec index 6d5358a9..be088dce 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.nuspec +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.nuspec @@ -2,7 +2,7 @@ Oqtane.Database.Sqlite - 4.0.3 + 4.0.4 Shaun Walker .NET Foundation Oqtane SQLite Provider @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 icon.png oqtane diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 5b64061f..cfb3825d 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -6,7 +6,7 @@ Exe - 4.0.3 + 4.0.4 Oqtane Shaun Walker .NET Foundation @@ -14,7 +14,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -31,7 +31,7 @@ 0E29FC31-1B83-48ED-B6E0-9F3C67B775D4 - 4.0.3 + 4.0.4 1 14.2 diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 878d5496..47c1ccaf 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 4.0.3 + 4.0.4 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index b3ccb5eb..6c5dcf42 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 4.0.3 + 4.0.4 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v4.0.3/Oqtane.Framework.4.0.3.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/download/v4.0.4/Oqtane.Framework.4.0.4.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index b8e56b64..fb794ea0 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 4.0.3 + 4.0.4 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index afa99300..87a60165 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 4.0.3 + 4.0.4 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 81d106e8..92d03b52 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 4.0.3 + 4.0.4 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 icon.png oqtane diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 6e6a50ca..be89e07e 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.3.Install.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.4.Install.zip" -Force \ No newline at end of file diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index eaaf892a..db4dc391 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.3.Upgrade.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.4.Upgrade.zip" -Force \ No newline at end of file diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index cfd7a1e7..fe3f8733 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net7.0 Debug;Release - 4.0.3 + 4.0.4 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 7dd6677a..3e373d55 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -3,7 +3,7 @@ net7.0 Debug;Release - 4.0.3 + 4.0.4 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index a4c68174..fa0cab3e 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -7,8 +7,8 @@ namespace Oqtane.Shared { public class Constants { - public static readonly string Version = "4.0.3"; - public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3"; + public static readonly string Version = "4.0.4"; + public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 69bf2b6a..1cfbd0e1 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -3,7 +3,7 @@ net7.0 Exe - 4.0.3 + 4.0.4 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4 https://github.com/oqtane/oqtane.framework Git Oqtane From 441324d35494ff14caffe5b7acd5ceb1ccf6468f Mon Sep 17 00:00:00 2001 From: Jon Welfringer <7365166+W6HBR@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:59:44 -0700 Subject: [PATCH 30/53] Update to Profiles field list to include Title, Category and ViewOrder When adding additional profile fields, this enhancement makes it easier to see details of the profile fields for the purpose of field organization. --- Oqtane.Client/Modules/Admin/Profiles/Index.razor | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Oqtane.Client/Modules/Admin/Profiles/Index.razor b/Oqtane.Client/Modules/Admin/Profiles/Index.razor index c6b7cb8a..fcc19353 100644 --- a/Oqtane.Client/Modules/Admin/Profiles/Index.razor +++ b/Oqtane.Client/Modules/Admin/Profiles/Index.razor @@ -17,11 +17,17 @@ else     @SharedLocalizer["Name"] + @SharedLocalizer["Title"] + @SharedLocalizer["Category"] + @SharedLocalizer["ViewOrder"] @context.Name + @context.Title + @context.Category + @context.ViewOrder } From 5ea5c6ff7d906805731435cf1727704beb08fbd7 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 20 Sep 2023 17:12:04 -0700 Subject: [PATCH 31/53] Updates Child Page Link Color in Theme.css --- .../Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css index 0d325604..129a0b9c 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css @@ -79,6 +79,10 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu color: #000000; } +.dropdown-menu span { + mix-blend-mode: difference; +} + @media (max-width: 767px) { .app-menu { From 98257de0058b56da52b41406a51ba127d923a209 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 21 Sep 2023 09:37:29 -0400 Subject: [PATCH 32/53] user import improvements --- Oqtane.Client/Modules/Admin/Users/Users.razor | 13 +- .../Resources/Modules/Admin/Users/Users.resx | 12 +- .../Services/Interfaces/IUserService.cs | 2 +- Oqtane.Client/Services/UserService.cs | 4 +- Oqtane.Server/Controllers/UserController.cs | 4 +- .../Managers/Interfaces/IUserManager.cs | 3 +- Oqtane.Server/Managers/UserManager.cs | 185 +++++++++++------- Oqtane.Server/wwwroot/users.csv | 1 - Oqtane.Server/wwwroot/users.txt | 1 + 9 files changed, 131 insertions(+), 94 deletions(-) delete mode 100644 Oqtane.Server/wwwroot/users.csv create mode 100644 Oqtane.Server/wwwroot/users.txt diff --git a/Oqtane.Client/Modules/Admin/Users/Users.razor b/Oqtane.Client/Modules/Admin/Users/Users.razor index 9f6b3401..cc7c6d93 100644 --- a/Oqtane.Client/Modules/Admin/Users/Users.razor +++ b/Oqtane.Client/Modules/Admin/Users/Users.razor @@ -7,16 +7,16 @@
- +
- +

  @SharedLocalizer["Cancel"]  -@Localizer["Template"] +@Localizer["Template"] @code { private FileManager _filemanager; @@ -32,14 +32,17 @@ var fileid = _filemanager.GetFileId(); if (fileid != -1) { - if (await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid)) + ShowProgressIndicator(); + var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid); + if (bool.Parse(results["Success"])) { - AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success); + AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Rows"], results["Users"]), MessageType.Success); } else { AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error); } + HideProgressIndicator(); } else { diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx index b18df840..86c51ccc 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx @@ -117,11 +117,11 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Select or upload a CSV file containing user information. The CSV file must be in the Template format specified. + + Upload or select a tab delimited text file containing user information. The file must be in the Template format specified (Roles can be specified as a comma delimited list). - - User File: + + Import File: Error Importing Users @@ -130,10 +130,10 @@ Import - User Import Failed + User Import Failed. Please Review Your Event Log For More Detailed Information. - Users Imported Successfully + Users Imported Successfully. {0} Rows Processed, {1} Users Imported. You Must Specify A User File For Import diff --git a/Oqtane.Client/Services/Interfaces/IUserService.cs b/Oqtane.Client/Services/Interfaces/IUserService.cs index 04661e8f..7fe46e52 100644 --- a/Oqtane.Client/Services/Interfaces/IUserService.cs +++ b/Oqtane.Client/Services/Interfaces/IUserService.cs @@ -148,6 +148,6 @@ namespace Oqtane.Services /// /// ID of a /// - Task ImportUsersAsync(int siteId, int fileId); + Task> ImportUsersAsync(int siteId, int fileId); } } diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index 770c4eb1..08700cb8 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -127,9 +127,9 @@ namespace Oqtane.Services return string.Format(passwordValidationCriteriaTemplate, minimumlength, uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement); } - public async Task ImportUsersAsync(int siteId, int fileId) + public async Task> ImportUsersAsync(int siteId, int fileId) { - return await PostJsonAsync($"{Apiurl}/import?siteid={siteId}&fileid={fileId}", true); + return await PostJsonAsync>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}", null); } } } diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 3788817c..1ec787cf 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -375,7 +375,7 @@ namespace Oqtane.Controllers // POST api//import?siteid=x&fileid=y [HttpPost("import")] [Authorize(Roles = RoleNames.Admin)] - public async Task Import(string siteid, string fileid) + public async Task> Import(string siteid, string fileid) { if (int.TryParse(siteid, out int SiteId) && SiteId == _tenantManager.GetAlias().SiteId && int.TryParse(fileid, out int FileId)) { @@ -385,7 +385,7 @@ namespace Oqtane.Controllers { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; - return false; + return null; } } } diff --git a/Oqtane.Server/Managers/Interfaces/IUserManager.cs b/Oqtane.Server/Managers/Interfaces/IUserManager.cs index 7eb79e78..1a4c971f 100644 --- a/Oqtane.Server/Managers/Interfaces/IUserManager.cs +++ b/Oqtane.Server/Managers/Interfaces/IUserManager.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Oqtane.Models; @@ -18,6 +19,6 @@ namespace Oqtane.Managers User VerifyTwoFactor(User user, string token); Task LinkExternalAccount(User user, string token, string type, string key, string name); Task ValidatePassword(string password); - Task ImportUsers(int siteId, int fileId); + Task> ImportUsers(int siteId, int fileId); } } diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 49f2cf01..fdf4f065 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -461,9 +460,10 @@ namespace Oqtane.Managers return result.Succeeded; } - public async Task ImportUsers(int siteId, int fileId) + public async Task> ImportUsers(int siteId, int fileId) { var success = true; + int rows = 0; int users = 0; var file = _files.GetFile(fileId); @@ -477,127 +477,160 @@ namespace Oqtane.Managers try { - string row; + string row = ""; using (var reader = new StreamReader(path)) { - // get header row - row = reader.ReadLine(); - var header = row.Replace("\"", "").Split(','); - - row = reader.ReadLine(); - while (row != null) + // header row + if (reader.Peek() > -1) { - var values = row.Replace("\"", "").Split(','); + row = reader.ReadLine(); + } - if (values.Length > 3) + if (!string.IsNullOrEmpty(row.Trim())) + { + var header = row.Replace("\"", "").Split('\t'); + + // detail rows + while (reader.Peek() > -1) { - // user - var user = _users.GetUser(values[1], values[0]); - if (user == null) + row = reader.ReadLine(); + rows++; + + if (!string.IsNullOrEmpty(row.Trim())) { - user = new User(); - user.SiteId = siteId; - user.Email = values[0]; - user.Username = (!string.IsNullOrEmpty(values[1])) ? values[1] : user.Email; - user.DisplayName = (!string.IsNullOrEmpty(values[2])) ? values[2] : user.Username; - user = await AddUser(user); + var values = row.Replace("\"", "").Split('\t'); + + // user + var email = (values.Length > 0) ? values[0].Trim() : ""; + var username = (values.Length > 1) ? values[1].Trim() : ""; + var displayname = (values.Length > 2) ? values[2].Trim() : ""; + + var user = _users.GetUser(username, email); if (user == null) { - _logger.Log(LogLevel.Error, this, LogFunction.Create, "Error Creating User {Email}", values[0]); - success = false; - } - } - - if (user != null && !string.IsNullOrEmpty(values[3])) - { - // roles (comma delimited) - foreach (var rolename in values[3].Split(',')) - { - var role = roles.FirstOrDefault(item => item.Name == rolename); - if (role == null) + user = new User(); + user.SiteId = siteId; + user.Email = values[0]; + user.Username = (!string.IsNullOrEmpty(username)) ? username : user.Email; + user.DisplayName = (!string.IsNullOrEmpty(displayname)) ? displayname : user.Username; + user = await AddUser(user); + if (user == null) { - role = new Role(); - role.SiteId = siteId; - role.Name = rolename; - role.Description = rolename; - role = _roles.AddRole(role); - roles.Add(role); + _logger.Log(LogLevel.Error, this, LogFunction.Create, "Error Importing User {Email} {Username} {DisplayName}", email, username, displayname); + success = false; } - if (role != null) + } + else + { + if (!string.IsNullOrEmpty(displayname)) { - var userrole = _userRoles.GetUserRole(user.UserId, role.RoleId, false); - if (userrole == null) + user.DisplayName = displayname; + user = await UpdateUser(user); + } + } + + var rolenames = (values.Length > 3) ? values[3].Trim() : ""; + if (user != null && !string.IsNullOrEmpty(rolenames)) + { + // roles (comma delimited) + foreach (var rolename in rolenames.Split(',')) + { + var role = roles.FirstOrDefault(item => item.Name == rolename.Trim()); + if (role == null) { - userrole = new UserRole(); - userrole.UserId = user.UserId; - userrole.RoleId = role.RoleId; - _userRoles.AddUserRole(userrole); + role = new Role(); + role.SiteId = siteId; + role.Name = rolename.Trim(); + role.Description = rolename.Trim(); + role = _roles.AddRole(role); + roles.Add(role); + } + if (role != null) + { + var userrole = _userRoles.GetUserRole(user.UserId, role.RoleId, false); + if (userrole == null) + { + userrole = new UserRole(); + userrole.UserId = user.UserId; + userrole.RoleId = role.RoleId; + _userRoles.AddUserRole(userrole); + } } } } - } - if (user != null && values.Length > 4) - { - var settings = _settings.GetSettings(EntityNames.User, user.UserId); - for (int index = 4; index < values.Length - 1; index++) + if (user != null && values.Length > 4) { - if (header.Length > index && !string.IsNullOrEmpty(values[index])) + // profiles + var settings = _settings.GetSettings(EntityNames.User, user.UserId); + for (int index = 4; index < values.Length - 1; index++) { - var profile = profiles.FirstOrDefault(item => item.Name == header[index]); - if (profile != null) + if (header.Length > index && !string.IsNullOrEmpty(values[index].Trim())) { - var setting = settings.FirstOrDefault(item => item.SettingName == profile.Name); - if (setting == null) + var profile = profiles.FirstOrDefault(item => item.Name == header[index].Trim()); + if (profile != null) { - setting = new Setting(); - setting.EntityName = EntityNames.User; - setting.EntityId = user.UserId; - setting.SettingName = profile.Name; - setting.SettingValue = values[index]; - _settings.AddSetting(setting); - } - else - { - if (setting.SettingValue != values[index]) + var setting = settings.FirstOrDefault(item => item.SettingName == profile.Name); + if (setting == null) { - setting.SettingValue = values[index]; - _settings.UpdateSetting(setting); + setting = new Setting(); + setting.EntityName = EntityNames.User; + setting.EntityId = user.UserId; + setting.SettingName = profile.Name; + setting.SettingValue = values[index].Trim(); + _settings.AddSetting(setting); + } + else + { + if (setting.SettingValue != values[index].Trim()) + { + setting.SettingValue = values[index].Trim(); + _settings.UpdateSetting(setting); + } } } } } } + + users++; } - - users++; } - - row = reader.ReadLine(); + } + else + { + success = false; + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Contains No Header Row"); } } - _logger.Log(LogLevel.Information, this, LogFunction.Create, "{Users} Users Imported", users); + _logger.Log(LogLevel.Information, this, LogFunction.Create, "User Import: {Rows} Rows Processed, {Users} Users Imported", rows, users); } catch (Exception ex) { - _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Error Importing User Import File {SiteId} {FileId}", siteId, fileId); success = false; + _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Error Importing User Import File {SiteId} {FileId}", siteId, fileId); } } else { - _logger.Log(LogLevel.Error, this, LogFunction.Create,"User Import File Does Not Exist {Path}", path); success = false; + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Does Not Exist {Path}", path); } } else { - _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Does Not Exist {SiteId} {FileId}", siteId, fileId); success = false; + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Does Not Exist {SiteId} {FileId}", siteId, fileId); } - return success; + // return results + var result = new Dictionary(); + result.Add("Success", success.ToString()); + result.Add("Rows", rows.ToString()); + result.Add("Users", users.ToString()); + + return result; } } } diff --git a/Oqtane.Server/wwwroot/users.csv b/Oqtane.Server/wwwroot/users.csv deleted file mode 100644 index ff6dcff3..00000000 --- a/Oqtane.Server/wwwroot/users.csv +++ /dev/null @@ -1 +0,0 @@ -Email,Username,DisplayName,Roles,FirstName,LastName,Street,City,Region,Country,PostalCode,Phone diff --git a/Oqtane.Server/wwwroot/users.txt b/Oqtane.Server/wwwroot/users.txt new file mode 100644 index 00000000..b1d55c74 --- /dev/null +++ b/Oqtane.Server/wwwroot/users.txt @@ -0,0 +1 @@ +Email Username DisplayName Roles FirstName LastName Street City Region Country PostalCode Phone From 836b174d15601ebd479d8404ce4b505f2df9c4b1 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 21 Sep 2023 10:02:21 -0400 Subject: [PATCH 33/53] fix localization in Profile Management --- Oqtane.Client/Modules/Admin/Profiles/Index.razor | 6 +++--- .../Resources/Modules/Admin/Profiles/Index.resx | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Profiles/Index.razor b/Oqtane.Client/Modules/Admin/Profiles/Index.razor index fcc19353..f554c6c5 100644 --- a/Oqtane.Client/Modules/Admin/Profiles/Index.razor +++ b/Oqtane.Client/Modules/Admin/Profiles/Index.razor @@ -17,9 +17,9 @@ else     @SharedLocalizer["Name"] - @SharedLocalizer["Title"] - @SharedLocalizer["Category"] - @SharedLocalizer["ViewOrder"] + @Localizer["Title"] + @Localizer["Category"] + @Localizer["Order"] diff --git a/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx index ebd495eb..98f8530c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx @@ -138,4 +138,13 @@ Edit + + Category + + + Order + + + Title + \ No newline at end of file From c6a8f5305af9b74aa77f52b9544bab93d78a771e Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 21 Sep 2023 14:44:57 -0400 Subject: [PATCH 34/53] set DefaultScheme for authentication --- Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs | 2 ++ Oqtane.Server/Pages/Login.cshtml.cs | 1 + Oqtane.Server/Startup.cs | 4 +--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 22d04941..120b2d11 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -121,8 +121,10 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection ConfigureOqtaneCookieOptions(this IServiceCollection services) { + // note that ConfigureApplicationCookie internally uses an ApplicationScheme of "Identity.Application" services.ConfigureApplicationCookie(options => { + options.Cookie.Domain = "localhost"; options.Cookie.HttpOnly = false; options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; diff --git a/Oqtane.Server/Pages/Login.cshtml.cs b/Oqtane.Server/Pages/Login.cshtml.cs index faf1989c..87fceedb 100644 --- a/Oqtane.Server/Pages/Login.cshtml.cs +++ b/Oqtane.Server/Pages/Login.cshtml.cs @@ -46,6 +46,7 @@ namespace Oqtane.Pages if (validuser) { + // note that .NET Identity uses a hardcoded ApplicationScheme of "Identity.Application" in SignInAsync await _identitySignInManager.SignInAsync(identityuser, remember); } } diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 9e008722..8277c378 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -115,9 +115,7 @@ namespace Oqtane services.AddAuthentication(options => { - options.DefaultAuthenticateScheme = Constants.AuthenticationScheme; - options.DefaultChallengeScheme = Constants.AuthenticationScheme; - options.DefaultSignOutScheme = Constants.AuthenticationScheme; + options.DefaultScheme = Constants.AuthenticationScheme; }) .AddCookie(Constants.AuthenticationScheme) .AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { }) From 65782e87c1eff5837bbf1d58636b8402f026dfb0 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 21 Sep 2023 16:53:52 -0400 Subject: [PATCH 35/53] add support for named site options --- ...taneSiteAuthenticationBuilderExtensions.cs | 2 +- .../Extensions/OqtaneSiteOptionsBuilder.cs | 16 +++++++++-- .../Options/ISiteNamedOptions.cs | 11 ++++++++ .../Options/SiteNamedOptions.cs | 28 +++++++++++++++++++ Oqtane.Server/Startup.cs | 12 ++++---- 5 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 Oqtane.Server/Infrastructure/Options/ISiteNamedOptions.cs create mode 100644 Oqtane.Server/Infrastructure/Options/SiteNamedOptions.cs diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index f89d8bde..cad695a4 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -28,7 +28,7 @@ namespace Oqtane.Extensions public static OqtaneSiteOptionsBuilder WithSiteAuthentication(this OqtaneSiteOptionsBuilder builder) { // site cookie authentication options - builder.AddSiteOptions((options, alias, sitesettings) => + builder.AddSiteNamedOptions(Constants.AuthenticationScheme, (options, alias, sitesettings) => { options.Cookie.Name = sitesettings.GetValue("LoginOptions:CookieName", ".AspNetCore.Identity.Application"); }); diff --git a/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs b/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs index 8f281883..d34cd319 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteOptionsBuilder.cs @@ -17,10 +17,22 @@ namespace Microsoft.Extensions.DependencyInjection } public OqtaneSiteOptionsBuilder AddSiteOptions( - Action> action) where TOptions : class, new() + Action> configureOptions) where TOptions : class, new() { Services.TryAddSingleton, SiteOptionsCache>(); - Services.AddSingleton, SiteOptions> (sp => new SiteOptions(action)); + Services.AddSingleton, SiteOptions> (_ => new SiteOptions(configureOptions)); + Services.TryAddTransient, SiteOptionsFactory>(); + Services.TryAddScoped>(sp => BuildOptionsManager(sp)); + Services.TryAddSingleton>(sp => BuildOptionsManager(sp)); + + return this; + } + + public OqtaneSiteOptionsBuilder AddSiteNamedOptions(string name, + Action> configureOptions) where TOptions : class, new() + { + Services.TryAddSingleton, SiteOptionsCache>(); + Services.AddSingleton, SiteNamedOptions>(_ => new SiteNamedOptions(name, configureOptions)); Services.TryAddTransient, SiteOptionsFactory>(); Services.TryAddScoped>(sp => BuildOptionsManager(sp)); Services.TryAddSingleton>(sp => BuildOptionsManager(sp)); diff --git a/Oqtane.Server/Infrastructure/Options/ISiteNamedOptions.cs b/Oqtane.Server/Infrastructure/Options/ISiteNamedOptions.cs new file mode 100644 index 00000000..41ed97c4 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Options/ISiteNamedOptions.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + public interface ISiteNamedOptions + where TOptions : class, new() + { + void Configure(string name, TOptions options, Alias alias, Dictionary sitesettings); + } +} diff --git a/Oqtane.Server/Infrastructure/Options/SiteNamedOptions.cs b/Oqtane.Server/Infrastructure/Options/SiteNamedOptions.cs new file mode 100644 index 00000000..1027cc54 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Options/SiteNamedOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + public class SiteNamedOptions : ISiteNamedOptions + where TOptions : class, new() + { + public string Name { get; } + + private readonly Action> configureOptions; + + public SiteNamedOptions(string name, Action> configureOptions) + { + Name = name; + this.configureOptions = configureOptions; + } + + public void Configure(string name, TOptions options, Alias alias, Dictionary sitesettings) + { + if (name == Name) + { + configureOptions(options, alias, sitesettings); + } + } + } +} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 8277c378..8d8026d6 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -114,12 +114,12 @@ namespace Oqtane services.ConfigureOqtaneIdentityOptions(Configuration); services.AddAuthentication(options => - { - options.DefaultScheme = Constants.AuthenticationScheme; - }) - .AddCookie(Constants.AuthenticationScheme) - .AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { }) - .AddOAuth(AuthenticationProviderTypes.OAuth2, options => { }); + { + options.DefaultScheme = Constants.AuthenticationScheme; + }) + .AddCookie(Constants.AuthenticationScheme) + .AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { }) + .AddOAuth(AuthenticationProviderTypes.OAuth2, options => { }); services.ConfigureOqtaneCookieOptions(); services.ConfigureOqtaneAuthenticationOptions(Configuration); From 29741c0ec876b2202dc9f164b80bc420aec917be Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 21 Sep 2023 17:17:46 -0400 Subject: [PATCH 36/53] add named site option support to factory --- .../Infrastructure/Options/SiteOptionsFactory.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs b/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs index 05236eb7..f68ed438 100644 --- a/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs +++ b/Oqtane.Server/Infrastructure/Options/SiteOptionsFactory.cs @@ -11,13 +11,15 @@ namespace Oqtane.Infrastructure private readonly IConfigureOptions[] _configureOptions; private readonly IPostConfigureOptions[] _postConfigureOptions; private readonly ISiteOptions[] _siteOptions; + private readonly ISiteNamedOptions[] _siteNamedOptions; private readonly IHttpContextAccessor _accessor; - public SiteOptionsFactory(IEnumerable> configureOptions, IEnumerable> postConfigureOptions, IEnumerable> siteOptions, IHttpContextAccessor accessor) + public SiteOptionsFactory(IEnumerable> configureOptions, IEnumerable> postConfigureOptions, IEnumerable> siteOptions, IEnumerable> siteNamedOptions, IHttpContextAccessor accessor) { _configureOptions = configureOptions as IConfigureOptions[] ?? new List>(configureOptions).ToArray(); _postConfigureOptions = postConfigureOptions as IPostConfigureOptions[] ?? new List>(postConfigureOptions).ToArray(); _siteOptions = siteOptions as ISiteOptions[] ?? new List>(siteOptions).ToArray(); + _siteNamedOptions = siteNamedOptions as ISiteNamedOptions[] ?? new List>(siteNamedOptions).ToArray(); _accessor = accessor; } @@ -44,6 +46,11 @@ namespace Oqtane.Infrastructure { siteOption.Configure(options, _accessor.HttpContext.GetAlias(), _accessor.HttpContext.GetSiteSettings()); } + + foreach (var siteNamedOption in _siteNamedOptions) + { + siteNamedOption.Configure(name, options, _accessor.HttpContext.GetAlias(), _accessor.HttpContext.GetSiteSettings()); + } } // post configuration From a46836e2a644462d97bfdf903f0f84ab72c4989c Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 21 Sep 2023 16:34:57 -0700 Subject: [PATCH 37/53] Fix typo in app.css --- Oqtane.Server/wwwroot/css/app.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/wwwroot/css/app.css b/Oqtane.Server/wwwroot/css/app.css index 35deeb07..c1d0b44b 100644 --- a/Oqtane.Server/wwwroot/css/app.css +++ b/Oqtane.Server/wwwroot/css/app.css @@ -214,7 +214,7 @@ app { top: 0.5rem; } -/* Oqtane Conrol Styles */ +/* Oqtane Control Styles */ /* Pager */ .app-pager-pointer { @@ -227,4 +227,4 @@ app { .app-fas { margin-left: 5px; -} \ No newline at end of file +} From d3c248cf5c292cf1bb87ffc632715a1aabc29c5b Mon Sep 17 00:00:00 2001 From: Jon Welfringer <7365166+W6HBR@users.noreply.github.com> Date: Thu, 21 Sep 2023 16:57:01 -0700 Subject: [PATCH 38/53] Update Index.resx for Email text. --- Oqtane.Client/Resources/Modules/Admin/Users/Index.resx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index a8ede28b..fcfb9f8b 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -402,4 +402,7 @@ Name - \ No newline at end of file + + Email + + From dbf2cddd872c196cbebbf826005abd22fa28ba28 Mon Sep 17 00:00:00 2001 From: Jon Welfringer <7365166+W6HBR@users.noreply.github.com> Date: Thu, 21 Sep 2023 16:58:48 -0700 Subject: [PATCH 39/53] Update Index.razor to include Email column. --- Oqtane.Client/Modules/Admin/Users/Index.razor | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index d88b8a25..4606df9b 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -37,7 +37,8 @@ else     @Localizer["Username"] - @Localizer["Name"] + @Localizer["Name"] + @Localizer["Email"] @Localizer["LastLoginOn"] @@ -52,6 +53,7 @@ else @context.User.Username @((MarkupString)string.Format("{1}", @context.User.Email, @context.User.DisplayName)) + @((MarkupString)string.Format("{1}", @context.User.Email, @context.User.Email)) @((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "") From 916663b4939aa0562f04d794f88f2ace4062f46c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 22 Sep 2023 14:12:18 -0400 Subject: [PATCH 40/53] fix issue where module migrations were not being executed on upgrade due to version not being overridden --- Oqtane.Server/Repository/ModuleDefinitionRepository.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 1607b811..935525ee 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using System.Xml; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Oqtane.Infrastructure; @@ -148,10 +147,11 @@ namespace Oqtane.Repository if (moduledefinition == null) { - // new module definition (version is explicitly not set because it is updated as part of module migrations at startup) + // new module definition moduledefinition = new ModuleDefinition { ModuleDefinitionName = ModuleDefinition.ModuleDefinitionName }; _db.ModuleDefinition.Add(moduledefinition); _db.SaveChanges(); + // version is explicitly not set because it is updated as part of module migrations at startup ModuleDefinition.Version = ""; } else @@ -160,6 +160,8 @@ namespace Oqtane.Repository ModuleDefinition.Name = (!string.IsNullOrEmpty(moduledefinition.Name)) ? moduledefinition.Name : ModuleDefinition.Name; ModuleDefinition.Description = (!string.IsNullOrEmpty(moduledefinition.Description)) ? moduledefinition.Description : ModuleDefinition.Description; ModuleDefinition.Categories = (!string.IsNullOrEmpty(moduledefinition.Categories)) ? moduledefinition.Categories : ModuleDefinition.Categories; + // get current version + ModuleDefinition.Version = moduledefinition.Version; // remove module definition from list as it is already synced moduledefinitions.Remove(moduledefinition); From abe1b93e3c4a548a061ace0ab6d9169992f45cd8 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 22 Sep 2023 14:44:25 -0400 Subject: [PATCH 41/53] removing href link from DisplayName as Email is now included --- Oqtane.Client/Modules/Admin/Users/Index.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index 4606df9b..dafe740c 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -52,7 +52,7 @@ else @context.User.Username - @((MarkupString)string.Format("{1}", @context.User.Email, @context.User.DisplayName)) + @context.User.Email @((MarkupString)string.Format("{1}", @context.User.Email, @context.User.Email)) @((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "") From 30ad442dd173cbea7006662c2815d1e3cf04bf88 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 22 Sep 2023 15:03:45 -0400 Subject: [PATCH 42/53] fix reference to DisplayName --- Oqtane.Client/Modules/Admin/Users/Index.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index dafe740c..c0334d6e 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -52,7 +52,7 @@ else @context.User.Username - @context.User.Email + @context.User.DisplayName @((MarkupString)string.Format("{1}", @context.User.Email, @context.User.Email)) @((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "") From e9bed5903237ee247ad271aeb3e0152535265936 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Sat, 23 Sep 2023 10:42:39 +0200 Subject: [PATCH 43/53] Fix for Site 'SMTP retention' is not in resource file (Issue #3301) Fix for Site 'SMTP retention' is not in resource file (Issue #3301) --- Oqtane.Client/Resources/Modules/Admin/Site/Index.resx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 5a05fd80..0d9b6c65 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -396,4 +396,10 @@ ID: + + Number of days of notifications to retain + + + Retention (Days): + \ No newline at end of file From 8b23b386f7af60ed5ed07489e1cf0a79250181dc Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Sat, 23 Sep 2023 10:56:17 +0200 Subject: [PATCH 44/53] ImportUsers Added to Resx and Razor resource key --- Oqtane.Client/Modules/Admin/Users/Index.razor | 2 +- Oqtane.Client/Resources/Modules/Admin/Users/Index.resx | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index c0334d6e..57f10221 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -21,7 +21,7 @@ else
  - +
diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index fcfb9f8b..19757ae8 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -390,7 +390,7 @@ Role Claim: - + Optionally provide a comma delimited list of user profile claims provided by the identity provider, as well as mappings to your user profile definition. For example if the identity provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'. @@ -405,4 +405,7 @@ Email - + + Import Users + + \ No newline at end of file From edac046fcdddfdfb9126db8480d406cca24db7ba Mon Sep 17 00:00:00 2001 From: sbwalker Date: Sat, 23 Sep 2023 09:04:18 -0400 Subject: [PATCH 45/53] improvements based on user import testing --- Oqtane.Client/Modules/Admin/Users/Add.razor | 42 ++-- Oqtane.Client/Modules/Admin/Users/Users.razor | 15 +- .../Resources/Modules/Admin/Users/Add.resx | 6 + .../Resources/Modules/Admin/Users/Users.resx | 10 +- .../Services/Interfaces/IUserService.cs | 4 +- Oqtane.Client/Services/UserService.cs | 4 +- Oqtane.Server/Controllers/UserController.cs | 8 +- .../Managers/Interfaces/IUserManager.cs | 2 +- Oqtane.Server/Managers/UserManager.cs | 203 ++++++++++-------- Oqtane.Shared/Models/User.cs | 6 + 10 files changed, 184 insertions(+), 116 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Users/Add.razor b/Oqtane.Client/Modules/Admin/Users/Add.razor index b1c34d4f..817bf5e1 100644 --- a/Oqtane.Client/Modules/Admin/Users/Add.razor +++ b/Oqtane.Client/Modules/Admin/Users/Add.razor @@ -17,7 +17,7 @@
- +
@@ -33,7 +33,7 @@
- +
@@ -41,17 +41,25 @@
- +
- + +
+
+
+ +
+
- } @@ -96,13 +104,14 @@ @code { private string _passwordrequirements; - private string username = string.Empty; + private string _username = string.Empty; private string _password = string.Empty; private string _passwordtype = "password"; private string _togglepassword = string.Empty; - private string confirm = string.Empty; - private string email = string.Empty; - private string displayname = string.Empty; + private string _confirm = string.Empty; + private string _email = string.Empty; + private string _displayname = string.Empty; + private string _notify = "True"; private List profiles; private Dictionary settings; private string category = string.Empty; @@ -139,17 +148,18 @@ { try { - if (username != string.Empty && _password != string.Empty && confirm != string.Empty && email != string.Empty && ValidateProfiles()) + if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty && _email != string.Empty && ValidateProfiles()) { - if (_password == confirm) + if (_password == _confirm) { var user = new User(); user.SiteId = PageState.Site.SiteId; - user.Username = username; + user.Username = _username; user.Password = _password; - user.Email = email; - user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; + user.Email = _email; + user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname; user.PhotoFileId = null; + user.SuppressNotification = !bool.Parse(_notify); user = await UserService.AddUserAsync(user); @@ -161,7 +171,7 @@ } else { - await logger.LogError("Error Adding User {Username} {Email}", username, email); + await logger.LogError("Error Adding User {Username} {Email}", _username, _email); AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error); } } @@ -177,7 +187,7 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", username, email, ex.Message); + await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message); AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error); } } diff --git a/Oqtane.Client/Modules/Admin/Users/Users.razor b/Oqtane.Client/Modules/Admin/Users/Users.razor index cc7c6d93..a3ac65d3 100644 --- a/Oqtane.Client/Modules/Admin/Users/Users.razor +++ b/Oqtane.Client/Modules/Admin/Users/Users.razor @@ -12,6 +12,15 @@
+
+ +
+ +
+

  @@ -25,6 +34,8 @@ public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + private string _notify = "True"; + private async Task ImportUsers() { try @@ -33,10 +44,10 @@ if (fileid != -1) { ShowProgressIndicator(); - var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid); + var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify)); if (bool.Parse(results["Success"])) { - AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Rows"], results["Users"]), MessageType.Success); + AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success); } else { diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx index 8ce03a9a..1ad645d9 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx @@ -171,4 +171,10 @@ Password + + Indicate if new users should receive an email notification + + + Notify? + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx index 86c51ccc..02e73f23 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Users.resx @@ -1,4 +1,4 @@ - +