From f596795792d33f05fff22ca151969b6581b66fde Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sat, 14 Jan 2023 15:03:46 -0500 Subject: [PATCH 01/62] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3959db52..97f91c43 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[3.3.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.0) was released on Jan 12, 2023 and is primarily focused on flexibility, as the permissions system has been enhanced to support a new type of API permissions that provide developers with additional opportunities to create sophisticated modern web applications. This release also includes performance optimizations, enhancements to file management, and numerous user experience improvements including the addition of a new AutoComplete component. This release includes 51 pull requests by 3 different contributors, pushing the total number of project commits all-time over 3200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) was released on Jan 14, 2023 and is primarily focused on flexibility, as the permissions system has been enhanced to support a new type of API permissions that provide developers with additional opportunities to create sophisticated modern web applications. This release also includes performance optimizations, enhancements to file management, and numerous user experience improvements including the addition of a new AutoComplete component. This release includes 51 pull requests by 3 different contributors, pushing the total number of project commits all-time over 3200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. # Oqtane Framework From 08ec46637f6100f96cc61b6cdaae17215584f8f4 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sat, 14 Jan 2023 15:04:31 -0500 Subject: [PATCH 02/62] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97f91c43..409bbd38 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ This project is open source, and therefore is a work in progress... - [ ] Permission serialization optimization - [ ] Folder Providers -[3.3.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.3) ( Jan 12, 2023 ) +[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) ( Jan 14, 2023 ) - [x] Dynamic Authorization Policies - [x] Entity-Level Permissions - [x] Extended Module Permissions From d6cce9e2d8d08afaaa7da1ad1d6738adacff6efd Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sat, 14 Jan 2023 15:08:20 -0500 Subject: [PATCH 03/62] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 409bbd38..0ba4e114 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ This project is open source, and therefore is a work in progress... - [ ] Permission serialization optimization - [ ] Folder Providers -[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) ( Jan 14, 2023 ) +[3.3.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.0) ( Jan 12, 2023 ) - [x] Dynamic Authorization Policies - [x] Entity-Level Permissions - [x] Extended Module Permissions From d5ffb56fa8fdd0b88ff9adc3199faf1385e8fd68 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sat, 14 Jan 2023 15:15:33 -0500 Subject: [PATCH 04/62] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ba4e114..a202a3a4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) was released on Jan 14, 2023 and is primarily focused on flexibility, as the permissions system has been enhanced to support a new type of API permissions that provide developers with additional opportunities to create sophisticated modern web applications. This release also includes performance optimizations, enhancements to file management, and numerous user experience improvements including the addition of a new AutoComplete component. This release includes 51 pull requests by 3 different contributors, pushing the total number of project commits all-time over 3200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) was released on Jan 14, 2023 and is a patch for the 3.3.0 release which was primarily focused on flexibility, as the permissions system has been enhanced to support a new type of API permissions that provide developers with additional opportunities to create sophisticated modern web applications. This release also includes performance optimizations, enhancements to file management, and numerous user experience improvements including the addition of a new AutoComplete component. This release includes 51 pull requests by 3 different contributors, pushing the total number of project commits all-time over 3200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. # Oqtane Framework From fd0519b9552e31fe5eb7cdad5c1dcf358e447d1d Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sat, 14 Jan 2023 15:17:38 -0500 Subject: [PATCH 05/62] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a202a3a4..300e2ec9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) was released on Jan 14, 2023 and is a patch for the 3.3.0 release which was primarily focused on flexibility, as the permissions system has been enhanced to support a new type of API permissions that provide developers with additional opportunities to create sophisticated modern web applications. This release also includes performance optimizations, enhancements to file management, and numerous user experience improvements including the addition of a new AutoComplete component. This release includes 51 pull requests by 3 different contributors, pushing the total number of project commits all-time over 3200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) was released on Jan 14, 2023 and is primarily focused on flexibility, as the permissions system has been enhanced to support a new type of API permissions that provide developers with additional opportunities to create sophisticated modern web applications. This release also includes performance optimizations, enhancements to file management, and numerous user experience improvements including the addition of a new AutoComplete component. This release includes 51 pull requests by 3 different contributors, pushing the total number of project commits all-time over 3200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. # Oqtane Framework @@ -50,6 +50,9 @@ This project is open source, and therefore is a work in progress... - [ ] Permission serialization optimization - [ ] Folder Providers +[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) ( Jan 14, 2023 ) +- [x] Stabilization improvements + [3.3.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.0) ( Jan 12, 2023 ) - [x] Dynamic Authorization Policies - [x] Entity-Level Permissions From 7a105047e95e9803dec6eb00bc2ea6ce16206960 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 23 Jan 2023 15:16:08 -0500 Subject: [PATCH 06/62] Fixed issue where TenantMiddleware was not rewriting the Url path for the new File Server when running on an Alias Path which resulted in a 404 when serving files --- Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs index 87dc89c8..c3386bdd 100644 --- a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs +++ b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs @@ -43,10 +43,10 @@ namespace Oqtane.Infrastructure }); context.Items.Add(Constants.HttpContextSiteSettingsKey, sitesettings); - // rewrite path by removing alias path prefix from api and pages requests (for consistent routing) + // rewrite path by removing alias path prefix from reserved route (api,pages,files) requests for consistent routes if (!string.IsNullOrEmpty(alias.Path)) { - if (path.StartsWith("/" + alias.Path) && (path.Contains("/api/") || path.Contains("/pages/"))) + if (path.StartsWith("/" + alias.Path) && (Constants.ReservedRoutes.Any(item => path.Contains("/" + item + "/")))) { context.Request.Path = path.Replace("/" + alias.Path, ""); } From 1f2ad4e8847e835da2371897a978fafb94707dd3 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Fri, 3 Feb 2023 16:12:13 -0500 Subject: [PATCH 07/62] Suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies --- .../Controllers/SettingController.cs | 38 ++++++++++++++----- Oqtane.Server/Pages/_Host.cshtml.cs | 1 - Oqtane.Shared/Shared/SiteState.cs | 2 +- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 0938e5c0..3f38259e 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -64,8 +64,12 @@ namespace Oqtane.Controllers } else { - _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId); - HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + // suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies + if (entityName != EntityNames.Visitor) + { + _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } } return settings; } @@ -85,8 +89,11 @@ namespace Oqtane.Controllers } else { - _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Setting {EntityName} {SettingId}", entityName, id); - HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + if (entityName != EntityNames.Visitor) + { + _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Setting {EntityName} {SettingId}", entityName, id); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } return null; } } @@ -103,8 +110,11 @@ namespace Oqtane.Controllers } else { - _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Add Setting {Setting}", setting); - HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + if (setting.EntityName != EntityNames.Visitor) + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Add Setting {Setting}", setting); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } setting = null; } return setting; @@ -122,8 +132,11 @@ namespace Oqtane.Controllers } else { - _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Setting {Setting}", setting); - HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + if (setting.EntityName != EntityNames.Visitor) + { + _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Setting {Setting}", setting); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } setting = null; } return setting; @@ -142,8 +155,11 @@ namespace Oqtane.Controllers } else { - _logger.Log(LogLevel.Error, this, LogFunction.Delete, "User Not Authorized To Delete Setting {Setting}", setting); - HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + if (entityName != EntityNames.Visitor) + { + _logger.Log(LogLevel.Error, this, LogFunction.Delete, "User Not Authorized To Delete Setting {Setting}", setting); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } } } @@ -219,10 +235,12 @@ namespace Oqtane.Controllers authorized = User.IsInRole(RoleNames.Admin); if (!authorized) { + // a visitor may have cookies disabled if (int.TryParse(Request.Cookies[_visitorCookie], out int visitorId)) { authorized = (visitorId == entityId); } + authorized = false; } break; default: // custom entity diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 8b3044da..135493fc 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -20,7 +20,6 @@ using Oqtane.Enums; using Oqtane.Security; using Oqtane.Extensions; using Oqtane.Themes; -using Oqtane.UI; namespace Oqtane.Pages { diff --git a/Oqtane.Shared/Shared/SiteState.cs b/Oqtane.Shared/Shared/SiteState.cs index 411c60ab..7dcea082 100644 --- a/Oqtane.Shared/Shared/SiteState.cs +++ b/Oqtane.Shared/Shared/SiteState.cs @@ -8,7 +8,7 @@ namespace Oqtane.Shared public Alias Alias { get; set; } public string AntiForgeryToken { get; set; } // passed from server for use in service calls on client public string AuthorizationToken { get; set; } // passed from server for use in service calls on client - public string RemoteIPAddress { get; set; } // passed from server as cannot be reliable retrieved on client + public string RemoteIPAddress { get; set; } // passed from server as cannot be reliably retrieved on client private dynamic _properties; From 85ac8dd7010b23099de1f7cbeeedbf11b2043003 Mon Sep 17 00:00:00 2001 From: Mark Davis <311063+markdav-is@users.noreply.github.com> Date: Sat, 4 Feb 2023 09:04:54 -0800 Subject: [PATCH 08/62] Make ActiveDatabase setter public We have two cases where we need to override the active database: Unit Testing and added GraphQL. In both of these cases, we have a database context that is in a different scope than the automatically assigned active database during normal Oqtane startup. Our work-around has been to make this setter public. Unless there is a better solution to our cases, I feel this change would be useful for others as well. --- Oqtane.Server/Repository/Context/DBContextBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Repository/Context/DBContextBase.cs b/Oqtane.Server/Repository/Context/DBContextBase.cs index 926bdcf2..6d876d47 100644 --- a/Oqtane.Server/Repository/Context/DBContextBase.cs +++ b/Oqtane.Server/Repository/Context/DBContextBase.cs @@ -31,7 +31,7 @@ namespace Oqtane.Repository _accessor = httpContextAccessor; } - public IDatabase ActiveDatabase { get; private set; } + public IDatabase ActiveDatabase { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { From 56e4dcc11e7ff671dea761a8d6c7e68c3c9e6b1f Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 6 Feb 2023 16:44:25 -0500 Subject: [PATCH 09/62] fix #2578 - error notification sent via email includes direct link to specific log item, however redirect was causing an infinite loop. This resolves the problem and also preserves url querystring parameters during login/logout. --- Oqtane.Client/Modules/Admin/Login/Index.razor | 3 ++- Oqtane.Client/Modules/Admin/Logs/Index.razor | 17 +++++++++++------ .../Themes/Controls/Theme/LoginBase.cs | 16 ++++++++-------- Oqtane.Client/UI/SiteRouter.razor | 2 +- Oqtane.Server/Pages/Login.cshtml.cs | 5 +++++ Oqtane.Shared/Models/Route.cs | 6 ++++++ 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index d237190b..22fa249f 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -1,3 +1,4 @@ +@using System.Net @namespace Oqtane.Modules.Admin.Login @inherits ModuleBase @inject NavigationManager NavigationManager @@ -205,7 +206,7 @@ var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider .GetService(typeof(IdentityAuthenticationStateProvider)); authstateprovider.NotifyAuthenticationChanged(); - NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true)); + NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true)); } else { diff --git a/Oqtane.Client/Modules/Admin/Logs/Index.razor b/Oqtane.Client/Modules/Admin/Logs/Index.razor index d763a5de..ed0c586c 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Index.razor @@ -106,12 +106,6 @@ else { try { - // external link to log item will display Details component - if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id)) - { - NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"/{id}")); - } - if (UrlParameters.ContainsKey("level")) { _level = UrlParameters["level"]; @@ -241,4 +235,15 @@ else _page = page; } + protected override void OnAfterRender(bool firstRender) + { + if (!firstRender) + { + // external link to log item will display Details component + if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id)) + { + NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"/{id}")); + } + } + } } diff --git a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs index 2e66f96c..9c0f2192 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs +++ b/Oqtane.Client/Themes/Controls/Theme/LoginBase.cs @@ -1,8 +1,10 @@ using System; +using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using Oqtane.Enums; +using Oqtane.Models; using Oqtane.Providers; using Oqtane.Security; using Oqtane.Services; @@ -22,20 +24,18 @@ namespace Oqtane.Themes.Controls protected void LoginUser() { - var returnurl = PageState.Alias.Path; - if (PageState.Page.Path != "/") - { - returnurl += "/" + PageState.Page.Path; - } - NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + returnurl)); + Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path); + NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); } protected async Task LogoutUser() { await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User?.Username); - // check if anonymous user can access page - var url = PageState.Alias.Path + "/" + PageState.Page.Path; + Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path); + var url = route.PathAndQuery; + + // verify if anonymous users can access page if (!UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.Permissions)) { url = PageState.Alias.Path; diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index f003bd49..9c7dedb9 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -250,7 +250,7 @@ if (user == null) { // redirect to login page if user not logged in as they may need to be authenticated - NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + route.AbsolutePath)); + NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); } else { diff --git a/Oqtane.Server/Pages/Login.cshtml.cs b/Oqtane.Server/Pages/Login.cshtml.cs index 40202a16..7fa4bb87 100644 --- a/Oqtane.Server/Pages/Login.cshtml.cs +++ b/Oqtane.Server/Pages/Login.cshtml.cs @@ -1,3 +1,4 @@ +using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -43,6 +44,10 @@ namespace Oqtane.Pages { returnurl = ""; } + else + { + returnurl = WebUtility.UrlDecode(returnurl); + } if (!returnurl.StartsWith("/")) { returnurl = "/" + returnurl; diff --git a/Oqtane.Shared/Models/Route.cs b/Oqtane.Shared/Models/Route.cs index 9b4cc683..43385d8f 100644 --- a/Oqtane.Shared/Models/Route.cs +++ b/Oqtane.Shared/Models/Route.cs @@ -24,6 +24,7 @@ namespace Oqtane.Models Query = uri.Query; Fragment = uri.Fragment; AbsolutePath = uri.AbsolutePath; + PathAndQuery = uri.PathAndQuery; AliasPath = aliaspath; PagePath = AbsolutePath; ModuleId = ""; @@ -90,6 +91,11 @@ namespace Oqtane.Models /// public string AbsolutePath { get; set; } + /// + /// The absolute path for the route including the querystring + /// + public string PathAndQuery { get; set; } + /// /// An absolute path may contain an alias path /// From 33bc6adcb5b092ccfd7027a62f82d95052766114 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 6 Feb 2023 16:49:45 -0500 Subject: [PATCH 10/62] fix #2574 - check for null ModuleDefinition reference when loding permissions in PageModuleRepository (credit @beolafsen) --- Oqtane.Server/Repository/PageModuleRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Repository/PageModuleRepository.cs b/Oqtane.Server/Repository/PageModuleRepository.cs index 7685f19f..76e63766 100644 --- a/Oqtane.Server/Repository/PageModuleRepository.cs +++ b/Oqtane.Server/Repository/PageModuleRepository.cs @@ -104,7 +104,7 @@ namespace Oqtane.Repository // moduledefinition permissionnames can specify permissions for other entities (ie. API permissions) pageModule.Module.ModuleDefinition = moduleDefinitions.Find(item => item.ModuleDefinitionName == pageModule.Module.ModuleDefinitionName); - if (!string.IsNullOrEmpty(pageModule.Module.ModuleDefinition.PermissionNames) && pageModule.Module.ModuleDefinition.PermissionNames.Contains(":")) + if (pageModule.Module.ModuleDefinition != null && !string.IsNullOrEmpty(pageModule.Module.ModuleDefinition.PermissionNames) && pageModule.Module.ModuleDefinition.PermissionNames.Contains(":")) { foreach (var permissionname in pageModule.Module.ModuleDefinition.PermissionNames.Split(",", System.StringSplitOptions.RemoveEmptyEntries)) { From ffca1d24860b2777c22c0e56e0df353ac9af9574 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 7 Feb 2023 16:26:23 -0500 Subject: [PATCH 11/62] refactor visitor cookie name into a shared constant --- Oqtane.Client/Modules/Admin/Logs/Index.razor | 2 +- Oqtane.Server/Controllers/SettingController.cs | 2 +- Oqtane.Server/Controllers/VisitorController.cs | 2 +- Oqtane.Server/Pages/_Host.cshtml.cs | 2 +- Oqtane.Shared/Shared/Constants.cs | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Logs/Index.razor b/Oqtane.Client/Modules/Admin/Logs/Index.razor index ed0c586c..79f2a7b9 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Index.razor @@ -237,7 +237,7 @@ else protected override void OnAfterRender(bool firstRender) { - if (!firstRender) + if (firstRender) { // external link to log item will display Details component if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id)) diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 3f38259e..d4da807a 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -46,7 +46,7 @@ namespace Oqtane.Controllers _identityCache = identityCache; _logger = logger; _alias = tenantManager.GetAlias(); - _visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString(); + _visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString(); } // GET: api/ diff --git a/Oqtane.Server/Controllers/VisitorController.cs b/Oqtane.Server/Controllers/VisitorController.cs index dfb9a63c..c4a8de73 100644 --- a/Oqtane.Server/Controllers/VisitorController.cs +++ b/Oqtane.Server/Controllers/VisitorController.cs @@ -50,7 +50,7 @@ namespace Oqtane.Controllers bool authorized = User.IsInRole(RoleNames.Admin); if (!authorized) { - var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString(); + var visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString(); if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId)) { authorized = (visitorId == id); diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 135493fc..bf49849b 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -284,7 +284,7 @@ namespace Oqtane.Pages // check if cookie already exists Visitor visitor = null; bool addcookie = false; - var VisitorCookie = "APP_VISITOR_" + SiteId.ToString(); + var VisitorCookie = Constants.VisitorCookiePrefix + SiteId.ToString(); if (!int.TryParse(Request.Cookies[VisitorCookie], out VisitorId)) { // if enabled use IP Address correlation diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 3ec91104..15e9af81 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -73,6 +73,7 @@ namespace Oqtane.Shared public static readonly string HttpContextSiteSettingsKey = "SiteSettings"; public static readonly string MauiUserAgent = "MAUI"; + public static readonly string VisitorCookiePrefix = "APP_VISITOR_"; // Obsolete constants From 475894b680757f4eb9c8bcb180b976cba4308cf6 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 8 Feb 2023 08:05:25 -0500 Subject: [PATCH 12/62] fix #2584 - added IsDeleted columns back to Folder and File tables to preserve compatibility for SQLite --- .../Infrastructure/DatabaseManager.cs | 20 +++++------ .../03030201_AddFolderFileIsDeletedColumns.cs | 35 +++++++++++++++++++ Oqtane.Server/Repository/FileRepository.cs | 1 + Oqtane.Server/Repository/FolderRepository.cs | 1 + Oqtane.Shared/Models/File.cs | 5 +++ Oqtane.Shared/Models/Folder.cs | 5 +++ 6 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 0889a84c..82bfffd4 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -62,7 +62,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - result.Message = "Master Database Not Installed Correctly. " + ex.Message; + result.Message = "Master Database Not Installed Correctly. " + ex.ToString(); } } else // cannot connect @@ -74,7 +74,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - result.Message = "Cannot Connect To Master Database. " + ex.Message; + result.Message = "Cannot Connect To Master Database. " + ex.ToString(); } } } @@ -247,7 +247,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - result.Message = ex.Message; + result.Message = ex.ToString(); _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } @@ -286,7 +286,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - result.Message = "An Error Occurred Creating The Database. This Is Usually Related To Your User Not Having Sufficient Rights To Perform This Operation. Please Note That You Can Also Create The Database Manually Prior To Initiating The Install Wizard. " + ex.Message; + result.Message = "An Error Occurred Creating The Database. This Is Usually Related To Your User Not Having Sufficient Rights To Perform This Operation. Please Note That You Can Also Create The Database Manually Prior To Initiating The Install Wizard. " + ex.ToString(); _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } } @@ -327,7 +327,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - result.Message = "An Error Occurred Provisioning The Master Database. This Is Usually Related To The Master Database Not Being In A Supported State. " + ex.Message; + result.Message = "An Error Occurred Provisioning The Master Database. This Is Usually Related To The Master Database Not Being In A Supported State. " + ex.ToString(); _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } } @@ -435,7 +435,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - result.Message = "An Error Occurred Migrating A Tenant Database. This Is Usually Related To A Tenant Database Not Being In A Supported State. " + ex.Message; + result.Message = "An Error Occurred Migrating A Tenant Database. This Is Usually Related To A Tenant Database Not Being In A Supported State. " + ex.ToString(); _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } @@ -456,7 +456,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - result.Message = "An Error Occurred Executing Upgrade Logic. " + ex.Message; + result.Message = "An Error Occurred Executing Upgrade Logic. " + ex.ToString(); _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } } @@ -526,7 +526,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " - " + ex.Message; + result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " - " + ex.ToString(); } } } @@ -664,7 +664,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - result.Message = "An Error Occurred Creating Site. " + ex.Message; + result.Message = "An Error Occurred Creating Site. " + ex.ToString(); } } @@ -737,7 +737,7 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - logger.Log(alias.SiteId, Shared.LogLevel.Error, "Site Migration", LogFunction.Other, "An Error Occurred Executing Site Migration {Type} For {Alias} And Version {Version} {Error}", upgrade.Value, alias.Name, version, ex.Message); + logger.Log(alias.SiteId, Shared.LogLevel.Error, "Site Migration", LogFunction.Other, ex, "An Error Occurred Executing Site Migration {Type} For {Alias} And Version {Version}", upgrade.Value, alias.Name, version); } } } diff --git a/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs b/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs new file mode 100644 index 00000000..d012a5b7 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.03.03.02.01")] + public class AddFolderFileIsDeletedColumns : MultiDatabaseMigration + { + public AddFolderFileIsDeletedColumns(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + // IsDeleted columns were removed in 3.2.2 however SQLite does not support column removal so they had to be restored + if (ActiveDatabase.Name != "Sqlite") + { + var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); + folderEntityBuilder.AddBooleanColumn("IsDeleted"); + + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + fileEntityBuilder.AddBooleanColumn("IsDeleted"); + } + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Repository/FileRepository.cs b/Oqtane.Server/Repository/FileRepository.cs index bd76e18f..da233e29 100644 --- a/Oqtane.Server/Repository/FileRepository.cs +++ b/Oqtane.Server/Repository/FileRepository.cs @@ -41,6 +41,7 @@ namespace Oqtane.Repository public File AddFile(File file) { + file.IsDeleted = false; _db.File.Add(file); _db.SaveChanges(); file.Folder = _folderRepository.GetFolder(file.FolderId); diff --git a/Oqtane.Server/Repository/FolderRepository.cs b/Oqtane.Server/Repository/FolderRepository.cs index d82ac0e4..111f7c79 100644 --- a/Oqtane.Server/Repository/FolderRepository.cs +++ b/Oqtane.Server/Repository/FolderRepository.cs @@ -37,6 +37,7 @@ namespace Oqtane.Repository public Folder AddFolder(Folder folder) { + folder.IsDeleted = false; _db.Folder.Add(folder); _db.SaveChanges(); _permissions.UpdatePermissions(folder.SiteId, EntityNames.Folder, folder.FolderId, folder.Permissions); diff --git a/Oqtane.Shared/Models/File.cs b/Oqtane.Shared/Models/File.cs index 2e240403..9fdae1a0 100644 --- a/Oqtane.Shared/Models/File.cs +++ b/Oqtane.Shared/Models/File.cs @@ -55,6 +55,11 @@ namespace Oqtane.Models /// public string Description { get; set; } + /// + /// Deprecated - not used + /// + public bool IsDeleted { get; set; } + /// /// Object reference to the object. /// Use this if you need to determine what the file belongs to. diff --git a/Oqtane.Shared/Models/Folder.cs b/Oqtane.Shared/Models/Folder.cs index c7627714..f6530c23 100644 --- a/Oqtane.Shared/Models/Folder.cs +++ b/Oqtane.Shared/Models/Folder.cs @@ -59,6 +59,11 @@ namespace Oqtane.Models /// public bool IsSystem { get; set; } + /// + /// Deprecated - not used + /// + public bool IsDeleted { get; set; } + /// /// TODO: todoc what would this contain? /// From 2a12744cd57fc2badfe91277340397cdc2964a51 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 8 Feb 2023 08:29:50 -0500 Subject: [PATCH 13/62] added toggle to show/hide connection string in Site Settings --- Oqtane.Client/Modules/Admin/Site/Index.razor | 22 +++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 664aa686..bb9a7660 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -284,7 +284,10 @@
- +
+ + +
@@ -339,6 +342,8 @@ private string _tenant = string.Empty; private string _database = string.Empty; private string _connectionstring = string.Empty; + private string _connectionstringtype = "password"; + private string _connectionstringtoggle = string.Empty; private string _createdby; private DateTime _createdon; private string _modifiedby; @@ -353,6 +358,7 @@ { try { + _connectionstringtoggle = SharedLocalizer["ShowPassword"]; _themeList = await ThemeService.GetThemesAsync(); Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null) @@ -460,6 +466,20 @@ } } + private void ToggleConnectionString() + { + if (_connectionstringtype == "password") + { + _connectionstringtype = "text"; + _connectionstringtoggle = SharedLocalizer["HidePassword"]; + } + else + { + _connectionstringtype = "password"; + _connectionstringtoggle = SharedLocalizer["ShowPassword"]; + } + } + private async Task SaveSite() { validated = true; From db73052ee58c0de12aa51ac6e9e19b26a2fbc209 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 8 Feb 2023 14:45:20 -0500 Subject: [PATCH 14/62] allow system log to be cleared --- .../Modules/Admin/SystemInfo/Index.razor | 53 ++++++++++++------- .../Modules/Admin/SystemInfo/Index.resx | 9 ++++ Oqtane.Server/Controllers/SystemController.cs | 21 +++++++- .../03030201_AddFolderFileIsDeletedColumns.cs | 4 +- Oqtane.Shared/Models/File.cs | 2 +- Oqtane.Shared/Models/Folder.cs | 2 +- 6 files changed, 67 insertions(+), 24 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index fc1160df..239d3e9b 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -156,6 +156,8 @@ +

+

@@ -222,24 +224,39 @@ } private async Task SaveConfig() - { - try - { - var settings = new Dictionary(); - settings.Add("DetailedErrors", _detailederrors); - settings.Add("Logging:LogLevel:Default", _logginglevel); - settings.Add("Logging:LogLevel:Notify", _notificationlevel); - settings.Add("UseSwagger", _swagger); - settings.Add("PackageService", _packageservice); - await SystemService.UpdateSystemInfoAsync(settings); - AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Saving Configuration"); - AddModuleMessage(Localizer["Error.UpdateConfig"], MessageType.Error); - } - } + { + try + { + var settings = new Dictionary(); + settings.Add("DetailedErrors", _detailederrors); + settings.Add("Logging:LogLevel:Default", _logginglevel); + settings.Add("Logging:LogLevel:Notify", _notificationlevel); + settings.Add("UseSwagger", _swagger); + settings.Add("PackageService", _packageservice); + await SystemService.UpdateSystemInfoAsync(settings); + AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving Configuration"); + AddModuleMessage(Localizer["Error.UpdateConfig"], MessageType.Error); + } + } + + private async Task ClearLog() + { + try + { + await SystemService.UpdateSystemInfoAsync("Log", "Clear"); + _log = string.Empty; + AddModuleMessage(Localizer["Success.ClearLog"], MessageType.Success); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Clearing Log"); + AddModuleMessage(Localizer["Error.ClearLog"], MessageType.Error); + } + } private async Task RestartApplication() { diff --git a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx index fc84a50a..c5c328cd 100644 --- a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx @@ -285,4 +285,13 @@ System log information for current day + + Clear + + + System Log Has Been Successfully Cleared + + + Ann Error Occurred Clearing The System Log + \ No newline at end of file diff --git a/Oqtane.Server/Controllers/SystemController.cs b/Oqtane.Server/Controllers/SystemController.cs index 6e77094d..e6fb098c 100644 --- a/Oqtane.Server/Controllers/SystemController.cs +++ b/Oqtane.Server/Controllers/SystemController.cs @@ -84,7 +84,7 @@ namespace Oqtane.Controllers { foreach(KeyValuePair kvp in settings) { - _configManager.AddOrUpdateSetting(kvp.Key, kvp.Value, false); + UpdateSetting(kvp.Key, kvp.Value); } } @@ -93,7 +93,24 @@ namespace Oqtane.Controllers [Authorize(Roles = RoleNames.Host)] public void Put(string key, object value) { - _configManager.AddOrUpdateSetting(key, value, false); + UpdateSetting(key, value); + } + + private void UpdateSetting(string key, object value) + { + switch (key) + { + case "Log": + string path = Path.Combine(_environment.ContentRootPath, "Content", "Log", "error.log"); + if (System.IO.File.Exists(path)) + { + System.IO.File.Delete(path); + } + break; + default: + _configManager.AddOrUpdateSetting(key, value, false); + break; + } } } } diff --git a/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs b/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs index d012a5b7..ad954a7d 100644 --- a/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs +++ b/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs @@ -20,10 +20,10 @@ namespace Oqtane.Migrations.Tenant if (ActiveDatabase.Name != "Sqlite") { var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); - folderEntityBuilder.AddBooleanColumn("IsDeleted"); + folderEntityBuilder.AddBooleanColumn("IsDeleted", true); var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); - fileEntityBuilder.AddBooleanColumn("IsDeleted"); + fileEntityBuilder.AddBooleanColumn("IsDeleted", true); } } diff --git a/Oqtane.Shared/Models/File.cs b/Oqtane.Shared/Models/File.cs index 9fdae1a0..e90b6af1 100644 --- a/Oqtane.Shared/Models/File.cs +++ b/Oqtane.Shared/Models/File.cs @@ -58,7 +58,7 @@ namespace Oqtane.Models /// /// Deprecated - not used /// - public bool IsDeleted { get; set; } + public bool? IsDeleted { get; set; } /// /// Object reference to the object. diff --git a/Oqtane.Shared/Models/Folder.cs b/Oqtane.Shared/Models/Folder.cs index f6530c23..90bf87bd 100644 --- a/Oqtane.Shared/Models/Folder.cs +++ b/Oqtane.Shared/Models/Folder.cs @@ -62,7 +62,7 @@ namespace Oqtane.Models /// /// Deprecated - not used /// - public bool IsDeleted { get; set; } + public bool? IsDeleted { get; set; } /// /// TODO: todoc what would this contain? From 0883a8dbff0f94580e343d32e172d2c21d548ea7 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 8 Feb 2023 16:51:45 -0500 Subject: [PATCH 15/62] optimize Split() statements for consistency --- Oqtane.Client/Modules/Admin/Sites/Add.razor | 2 +- Oqtane.Client/UI/SiteRouter.razor | 6 +++--- Oqtane.Server/Infrastructure/DatabaseManager.cs | 8 ++++---- Oqtane.Server/Infrastructure/TenantManager.cs | 2 +- Oqtane.Server/Repository/AliasRepository.cs | 2 +- Oqtane.Server/Repository/PermissionRepository.cs | 2 +- Oqtane.Shared/Security/UserSecurity.cs | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index a8b7d495..509928f8 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -315,7 +315,7 @@ else _urls = Regex.Replace(_urls, @"\r\n?|\n", ","); var duplicates = new List(); var aliases = await AliasService.GetAliasesAsync(); - foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (string name in _urls.Split(',', StringSplitOptions.RemoveEmptyEntries)) { if (aliases.Exists(item => item.Name == name)) { diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 9c7dedb9..1c1998ad 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -299,7 +299,7 @@ { query = query.Substring(1); // ignore "?" } - foreach (string kvp in query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (string kvp in query.Split('&', StringSplitOptions.RemoveEmptyEntries)) { if (kvp != "") { @@ -358,7 +358,7 @@ } if (!string.IsNullOrEmpty(panes)) { - page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList(); if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin)) { _error = "The Current Theme Does Not Contain A Default Or Admin Pane"; @@ -407,7 +407,7 @@ // check if the module defines custom action routes if (module.ModuleDefinition.ControlTypeRoutes != "") { - foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries)) { if (route.StartsWith(action + "=")) { diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 82bfffd4..ed4a730c 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -370,7 +370,7 @@ namespace Oqtane.Infrastructure tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName); } - var aliasNames = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray(); + var aliasNames = install.Aliases.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray(); var firstAlias = aliasNames[0]; foreach (var aliasName in aliasNames) { @@ -406,7 +406,7 @@ namespace Oqtane.Infrastructure { var result = new Installation { Success = false, Message = string.Empty }; - var versions = Constants.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var versions = Constants.ReleaseVersions.Split(',', StringSplitOptions.RemoveEmptyEntries); using (var scope = _serviceScopeFactory.CreateScope()) { @@ -486,7 +486,7 @@ namespace Oqtane.Infrastructure { if (!string.IsNullOrEmpty(moduleDefinition.ReleaseVersions)) { - var versions = moduleDefinition.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var versions = moduleDefinition.ReleaseVersions.Split(',', StringSplitOptions.RemoveEmptyEntries); using (var db = GetInstallationContext()) { if (!string.IsNullOrEmpty(moduleDefinition.ServerManagerType)) @@ -575,7 +575,7 @@ namespace Oqtane.Infrastructure { // set the alias explicitly so the tenant can be resolved var aliases = scope.ServiceProvider.GetRequiredService(); - var aliasNames = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray(); + var aliasNames = install.Aliases.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray(); var firstAlias = aliasNames[0]; var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstAlias); var tenantManager = scope.ServiceProvider.GetRequiredService(); diff --git a/Oqtane.Server/Infrastructure/TenantManager.cs b/Oqtane.Server/Infrastructure/TenantManager.cs index 53dd8d73..226f1d0a 100644 --- a/Oqtane.Server/Infrastructure/TenantManager.cs +++ b/Oqtane.Server/Infrastructure/TenantManager.cs @@ -37,7 +37,7 @@ namespace Oqtane.Infrastructure { // legacy support for client api requests which would include the alias as a path prefix ( ie. {alias}/api/[controller] ) int aliasId; - string[] segments = httpcontext.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + string[] segments = httpcontext.Request.Path.Value.Split('/', StringSplitOptions.RemoveEmptyEntries); if (segments.Length > 1 && Shared.Constants.ReservedRoutes.Contains(segments[1]) && int.TryParse(segments[0], out aliasId)) { alias = _aliasRepository.GetAliases().ToList().FirstOrDefault(item => item.AliasId == aliasId); diff --git a/Oqtane.Server/Repository/AliasRepository.cs b/Oqtane.Server/Repository/AliasRepository.cs index cbe3bd9c..1f914e6c 100644 --- a/Oqtane.Server/Repository/AliasRepository.cs +++ b/Oqtane.Server/Repository/AliasRepository.cs @@ -67,7 +67,7 @@ namespace Oqtane.Repository Alias alias = null; List aliases = GetAliases().ToList(); - var segments = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + var segments = url.Split('/', StringSplitOptions.RemoveEmptyEntries); // iterate segments to find keywords int start = segments.Length; diff --git a/Oqtane.Server/Repository/PermissionRepository.cs b/Oqtane.Server/Repository/PermissionRepository.cs index 55e96399..d6e3d75f 100644 --- a/Oqtane.Server/Repository/PermissionRepository.cs +++ b/Oqtane.Server/Repository/PermissionRepository.cs @@ -225,7 +225,7 @@ namespace Oqtane.Repository string securityid = ""; foreach (PermissionString permissionstring in JsonSerializer.Deserialize>(permissionStrings)) { - foreach (string id in permissionstring.Permissions.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (string id in permissionstring.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries)) { securityid = id; Permission permission = new Permission(); diff --git a/Oqtane.Shared/Security/UserSecurity.cs b/Oqtane.Shared/Security/UserSecurity.cs index 16169495..36cb21c9 100644 --- a/Oqtane.Shared/Security/UserSecurity.cs +++ b/Oqtane.Shared/Security/UserSecurity.cs @@ -62,7 +62,7 @@ namespace Oqtane.Security if (permissions != null) { - foreach (string permission in permissions.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (string permission in permissions.Split(';', StringSplitOptions.RemoveEmptyEntries)) { bool? allowed = VerifyPermission(userId, roles, permission); if (allowed.HasValue) @@ -155,7 +155,7 @@ namespace Oqtane.Security identity.AddClaim(new Claim(ClaimTypes.Role, RoleNames.Admin)); identity.AddClaim(new Claim(ClaimTypes.Role, RoleNames.Registered)); } - foreach (string role in user.Roles.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (string role in user.Roles.Split(';', StringSplitOptions.RemoveEmptyEntries)) { if (!identity.Claims.Any(item => item.Type == ClaimTypes.Role && item.Value == role)) { From fa8d0c91fc7573dcf4474444159ee46f47a0b152 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 8 Feb 2023 17:43:55 -0500 Subject: [PATCH 16/62] added new methods for managing visitor settings (for personalization) --- .../Services/Interfaces/ISettingService.cs | 21 ++++++++++++++++--- Oqtane.Client/Services/SettingService.cs | 20 ++++++++++++++++++ .../Controllers/SettingController.cs | 1 - 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Oqtane.Client/Services/Interfaces/ISettingService.cs b/Oqtane.Client/Services/Interfaces/ISettingService.cs index b2d7908f..949db56d 100644 --- a/Oqtane.Client/Services/Interfaces/ISettingService.cs +++ b/Oqtane.Client/Services/Interfaces/ISettingService.cs @@ -62,7 +62,7 @@ namespace Oqtane.Services /// /// Returns a key-value dictionary of all page module settings for the given page module /// - /// + /// /// Task> GetPageModuleSettingsAsync(int pageModuleId); @@ -107,7 +107,7 @@ namespace Oqtane.Services /// /// Returns a key-value dictionary of all user settings for the given user /// - /// + /// /// Task> GetUserSettingsAsync(int userId); @@ -122,7 +122,7 @@ namespace Oqtane.Services /// /// Returns a key-value dictionary of all folder settings for the given folder /// - /// + /// /// Task> GetFolderSettingsAsync(int folderId); @@ -148,6 +148,21 @@ namespace Oqtane.Services /// Task UpdateHostSettingsAsync(Dictionary hostSettings); + /// + /// Returns a key-value dictionary of all settings for the given visitor + /// + /// + /// + Task> GetVisitorSettingsAsync(int visitorId); + + /// + /// Updates a visitor setting + /// + /// + /// + /// + Task UpdateVisitorSettingsAsync(Dictionary visitorSettings, int visitorId); + /// /// Returns a key-value dictionary of all settings for the given entityName /// diff --git a/Oqtane.Client/Services/SettingService.cs b/Oqtane.Client/Services/SettingService.cs index b06a2d41..4b4cbc12 100644 --- a/Oqtane.Client/Services/SettingService.cs +++ b/Oqtane.Client/Services/SettingService.cs @@ -111,6 +111,26 @@ namespace Oqtane.Services await UpdateSettingsAsync(hostSettings, EntityNames.Host, -1); } + public async Task> GetVisitorSettingsAsync(int visitorId) + { + if (visitorId != -1) + { + return await GetSettingsAsync(EntityNames.Visitor, visitorId); + } + else + { + return new Dictionary(); + } + } + + public async Task UpdateVisitorSettingsAsync(Dictionary visitorSettings, int visitorId) + { + if (visitorId != -1) + { + await UpdateSettingsAsync(visitorSettings, EntityNames.Visitor, visitorId); + } + } + public async Task> GetSettingsAsync(string entityName, int entityId) { var dictionary = new Dictionary(); diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index d4da807a..f60a5add 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -240,7 +240,6 @@ namespace Oqtane.Controllers { authorized = (visitorId == entityId); } - authorized = false; } break; default: // custom entity From aa80f31e520571fcf2b4cc3b95e3db7fdd5d0ae4 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 9 Feb 2023 16:26:20 -0500 Subject: [PATCH 17/62] fix #2570 - do not allow the term "oqtane" to be used as an organization or module/theme name (to avoid namespace issues). --- Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor | 2 +- Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor | 2 +- Oqtane.Client/Modules/Admin/Themes/Create.razor | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor index e003128e..bd5689f6 100644 --- a/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor @@ -174,7 +174,7 @@ else private bool IsValid(string name) { // must contain letters, underscores and digits and first character must be letter or underscore - return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); + return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); } private void TemplateChanged(ChangeEventArgs e) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor index 78aac9fa..c1c50169 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Create.razor @@ -139,7 +139,7 @@ private bool IsValid(string name) { // must contain letters, underscores and digits and first character must be letter or underscore - return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); + return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); } private void TemplateChanged(ChangeEventArgs e) diff --git a/Oqtane.Client/Modules/Admin/Themes/Create.razor b/Oqtane.Client/Modules/Admin/Themes/Create.razor index ef3ab372..e685e3e1 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Create.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Create.razor @@ -121,7 +121,7 @@ private bool IsValid(string name) { // must contain letters, underscores and digits and first character must be letter or underscore - return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); + return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$"); } private void TemplateChanged(ChangeEventArgs e) From 11dd3ce1100cae26e415db4e1adc7d2828e3ab59 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 9 Feb 2023 17:45:45 -0500 Subject: [PATCH 18/62] adding Oqtane.Server project back to module and theme external template solutions --- .../wwwroot/Modules/Templates/External/[Owner].[Module].sln | 6 ++++++ .../wwwroot/Themes/Templates/External/[Owner].[Theme].sln | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].[Module].sln b/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].[Module].sln index 6171a1c3..5c90cbac 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].[Module].sln +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].[Module].sln @@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28621.142 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Server", "..\oqtane.framework\Oqtane.Server\Oqtane.Server.csproj", "{3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module].Client", "Client\[Owner].[Module].Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Module].Server", "Server\[Owner].[Module].Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}" @@ -17,6 +19,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Release|Any CPU.Build.0 = Release|Any CPU {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].[Theme].sln b/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].[Theme].sln index c38196bb..842aaf4f 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].[Theme].sln +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].[Theme].sln @@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28621.142 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Server", "..\oqtane.framework\Oqtane.Server\Oqtane.Server.csproj", "{3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Theme].Client", "Client\[Owner].[Theme].Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].[Theme].Package", "Package\[Owner].[Theme].Package.csproj", "{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}" @@ -13,6 +15,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Release|Any CPU.Build.0 = Release|Any CPU {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU From 2e61a43e4f89a2098cdc9b1a04b85077c1b224e0 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 15 Feb 2023 12:43:18 -0500 Subject: [PATCH 19/62] fix #2596 - fix EF Core tracking error when updating a file in a folder which has a Capacity specified --- Oqtane.Server/Controllers/FileController.cs | 9 ++++----- Oqtane.Server/Repository/FileRepository.cs | 18 +++++++++++++++--- .../Repository/Interfaces/IFileRepository.cs | 1 + 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index cd6a9087..5473b154 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -20,7 +20,6 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Formats.Png; using System.Net.Http; -using Oqtane.Migrations.Tenant; // ReSharper disable StringIndexOfIsCultureSpecific.1 @@ -142,7 +141,7 @@ namespace Oqtane.Controllers { if (File.Name != file.Name || File.FolderId != file.FolderId) { - file.Folder = _folders.GetFolder(file.FolderId); + file.Folder = _folders.GetFolder(file.FolderId, false); string folderpath = _folders.GetFolderPath(file.Folder); if (!Directory.Exists(folderpath)) { @@ -151,7 +150,7 @@ namespace Oqtane.Controllers System.IO.File.Move(_files.GetFilePath(File), Path.Combine(folderpath, file.Name)); } - var newfile = CreateFile(file.Name, file.Folder.FolderId, _files.GetFilePath(file)); + var newfile = CreateFile(File.Name, file.Folder.FolderId, _files.GetFilePath(file)); if (newfile != null) { file.Extension = newfile.Extension; @@ -659,10 +658,10 @@ namespace Oqtane.Controllers var file = _files.GetFile(folderid, filename); int size = 0; - var folder = _folders.GetFolder(folderid); + var folder = _folders.GetFolder(folderid, false); if (folder.Capacity != 0) { - foreach (var f in _files.GetFiles(folderid)) + foreach (var f in _files.GetFiles(folderid, false)) { size += f.Size; } diff --git a/Oqtane.Server/Repository/FileRepository.cs b/Oqtane.Server/Repository/FileRepository.cs index da233e29..cd56a3e4 100644 --- a/Oqtane.Server/Repository/FileRepository.cs +++ b/Oqtane.Server/Repository/FileRepository.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -27,10 +26,23 @@ namespace Oqtane.Repository } public IEnumerable GetFiles(int folderId) + { + return GetFiles(folderId, true); + } + + public IEnumerable GetFiles(int folderId, bool tracking) { var alias = _tenants.GetAlias(); IEnumerable permissions = _permissions.GetPermissions(alias.SiteId, EntityNames.Folder, folderId).ToList(); - IEnumerable files = _db.File.Where(item => item.FolderId == folderId).Include(item => item.Folder); + IEnumerable files; + if (tracking) + { + files = _db.File.Where(item => item.FolderId == folderId).Include(item => item.Folder); + } + else + { + files = _db.File.AsNoTracking().Where(item => item.FolderId == folderId).Include(item => item.Folder); + } foreach (File file in files) { file.Folder.Permissions = permissions.EncodePermissions(); @@ -135,7 +147,7 @@ namespace Oqtane.Repository public string GetFilePath(File file) { if (file == null) return null; - var folder = file.Folder ?? _db.Folder.Find(file.FolderId); + var folder = file.Folder ?? _db.Folder.AsNoTracking().FirstOrDefault(item => item.FolderId == file.FolderId); var filepath = Path.Combine(_folderRepository.GetFolderPath(folder), file.Name); return filepath; } diff --git a/Oqtane.Server/Repository/Interfaces/IFileRepository.cs b/Oqtane.Server/Repository/Interfaces/IFileRepository.cs index 556d02de..214b03d8 100644 --- a/Oqtane.Server/Repository/Interfaces/IFileRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IFileRepository.cs @@ -6,6 +6,7 @@ namespace Oqtane.Repository public interface IFileRepository { IEnumerable GetFiles(int folderId); + IEnumerable GetFiles(int folderId, bool tracking); File AddFile(File file); File UpdateFile(File file); File GetFile(int fileId); From a50a13374ffa055707fc635e641621e1b6f429fc Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 15 Feb 2023 15:06:50 -0500 Subject: [PATCH 20/62] improve initialization logic in FileManager which could sometimes result in Upload button not being displayed when the component was initially loaded --- Oqtane.Client/Modules/Controls/FileManager.razor | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Controls/FileManager.razor b/Oqtane.Client/Modules/Controls/FileManager.razor index 8ce2e9a5..e0c3b5a7 100644 --- a/Oqtane.Client/Modules/Controls/FileManager.razor +++ b/Oqtane.Client/Modules/Controls/FileManager.razor @@ -6,7 +6,7 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer -@if (_folders != null) +@if (_initialized) {
@@ -87,6 +87,7 @@ } @code { + private bool _initialized = false; private List _folders; private List _files = new List(); private string _fileinputid = string.Empty; @@ -205,6 +206,8 @@ _fileinputid = "FileInput_" + _guid; _progressinfoid = "ProgressInfo_" + _guid; _progressbarid = "ProgressBar_" + _guid; + + _initialized = true; } private async Task GetFiles() From 7db6b82a1a876a91cd0d95fbfb795e6c2b033d7f Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 15 Feb 2023 15:21:50 -0500 Subject: [PATCH 21/62] add defensive logic to querystring parser to handle duplicate parameters --- Oqtane.Client/UI/SiteRouter.razor | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 1c1998ad..5545758b 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -306,11 +306,17 @@ if (kvp.Contains("=")) { string[] pair = kvp.Split('='); - querystring.Add(pair[0], pair[1]); + if (!querystring.ContainsKey(pair[0])) + { + querystring.Add(pair[0], pair[1]); + } } else { - querystring.Add(kvp, "true"); // default parameter when no value is provided + if (!querystring.ContainsKey(kvp)) + { + querystring.Add(kvp, "true"); // default parameter when no value is provided + } } } } From 52300e680a7887515aedaf0501e09a80ebec0f7f Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Thu, 16 Feb 2023 13:38:42 +0100 Subject: [PATCH 22/62] Fix for Missing Delete ModuleDefinition settings #1966 Added ISettingRepository _settings to the public ModuleDefinitionRepository method and updated the DeleteModuleDefinition with _settings.DeleteSettings(EntityNames.ModuleDefinition, moduleDefinitionId); --- Oqtane.Server/Repository/ModuleDefinitionRepository.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index c137d958..1b73132d 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -18,12 +18,14 @@ namespace Oqtane.Repository private MasterDBContext _db; private readonly IMemoryCache _cache; private readonly IPermissionRepository _permissions; + private readonly ISettingRepository _settings; - public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions) + public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ISettingRepository settings) { _db = context; _cache = cache; _permissions = permissions; + _settings = settings; } public IEnumerable GetModuleDefinitions() @@ -52,6 +54,7 @@ namespace Oqtane.Repository public void DeleteModuleDefinition(int moduleDefinitionId) { ModuleDefinition moduleDefinition = _db.ModuleDefinition.Find(moduleDefinitionId); + _settings.DeleteSettings(EntityNames.ModuleDefinition, moduleDefinitionId); _db.ModuleDefinition.Remove(moduleDefinition); _db.SaveChanges(); _cache.Remove("moduledefinitions"); From f333b573100a640560a900ca18dc2d9a771dde51 Mon Sep 17 00:00:00 2001 From: Ben Emamian Date: Mon, 20 Feb 2023 00:20:19 +1100 Subject: [PATCH 23/62] add AddByteColumn to add tinyint to the database table --- .../EntityBuilders/BaseEntityBuilder.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs index 3637dc54..5bf10e0a 100644 --- a/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs +++ b/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs @@ -122,6 +122,26 @@ namespace Oqtane.Migrations.EntityBuilders return table.Column(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue); } + public void AddByteColumn(string name, bool nullable = false) + { + _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema); + } + + public void AddByteColumn(string name, bool nullable, int defaultValue) + { + _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, defaultValue: defaultValue, schema: Schema); + } + + protected OperationBuilder AddByteColumn(ColumnsBuilder table, string name, bool nullable = false) + { + return table.Column(name: RewriteName(name), nullable: nullable); + } + + protected OperationBuilder AddByteColumn(ColumnsBuilder table, string name, bool nullable, int defaultValue) + { + return table.Column(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue); + } + public void AddIntegerColumn(string name, bool nullable = false) { _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema); From 8c6c66fb118b8ea8c91b09d49b56048b85339c6a Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 20 Feb 2023 08:35:46 -0500 Subject: [PATCH 24/62] add sitemap generator which outputs all public pages and also includes an ISitemap interface for modules --- Oqtane.Client/Modules/ModuleBase.cs | 7 +- Oqtane.Server/Modules/IPortable.cs | 4 +- Oqtane.Server/Modules/ISitemap.cs | 12 +++ Oqtane.Server/Pages/Sitemap.cshtml | 3 + Oqtane.Server/Pages/Sitemap.cshtml.cs | 112 ++++++++++++++++++++++++++ Oqtane.Shared/Models/Sitemap.cs | 20 +++++ Oqtane.Shared/Shared/Utilities.cs | 10 +++ 7 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 Oqtane.Server/Modules/ISitemap.cs create mode 100644 Oqtane.Server/Pages/Sitemap.cshtml create mode 100644 Oqtane.Server/Pages/Sitemap.cshtml.cs create mode 100644 Oqtane.Shared/Models/Sitemap.cs diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 42963461..9ba0d264 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -190,12 +190,7 @@ namespace Oqtane.Modules public string AddUrlParameters(params object[] parameters) { - var url = ""; - for (var i = 0; i < parameters.Length; i++) - { - url += "/" + parameters[i].ToString(); - } - return url; + return Utilities.AddUrlParameters(parameters); } // template is in the form of a standard route template ie. "/{id}/{name}" and produces dictionary of key/value pairs diff --git a/Oqtane.Server/Modules/IPortable.cs b/Oqtane.Server/Modules/IPortable.cs index b4c89735..bdc0c2d2 100644 --- a/Oqtane.Server/Modules/IPortable.cs +++ b/Oqtane.Server/Modules/IPortable.cs @@ -1,10 +1,10 @@ -using Oqtane.Models; +using Oqtane.Models; namespace Oqtane.Modules { public interface IPortable { - // You Must Set The "ServerAssemblyName" In Your IModule Interface + // You Must Set The "ServerManagerType" In Your IModule Interface string ExportModule(Module module); diff --git a/Oqtane.Server/Modules/ISitemap.cs b/Oqtane.Server/Modules/ISitemap.cs new file mode 100644 index 00000000..558e42e4 --- /dev/null +++ b/Oqtane.Server/Modules/ISitemap.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Modules +{ + public interface ISitemap + { + // You Must Set The "ServerManagerType" In Your IModule Interface + + List GetUrls(string alias, string path, Module module); + } +} diff --git a/Oqtane.Server/Pages/Sitemap.cshtml b/Oqtane.Server/Pages/Sitemap.cshtml new file mode 100644 index 00000000..9b0bdca5 --- /dev/null +++ b/Oqtane.Server/Pages/Sitemap.cshtml @@ -0,0 +1,3 @@ +@page "/pages/sitemap.xml" +@namespace Oqtane.Pages +@model Oqtane.Pages.SitemapModel diff --git a/Oqtane.Server/Pages/Sitemap.cshtml.cs b/Oqtane.Server/Pages/Sitemap.cshtml.cs new file mode 100644 index 00000000..55f08d40 --- /dev/null +++ b/Oqtane.Server/Pages/Sitemap.cshtml.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Enums; +using Oqtane.Infrastructure; +using Oqtane.Models; +using Oqtane.Modules; +using Oqtane.Repository; +using Oqtane.Security; +using Oqtane.Shared; + +namespace Oqtane.Pages +{ + [AllowAnonymous] + public class SitemapModel : PageModel + { + private readonly IServiceProvider _serviceProvider; + private readonly IPageRepository _pages; + private readonly IPageModuleRepository _pageModules; + private readonly IModuleDefinitionRepository _moduleDefinitions; + private readonly IUserPermissions _userPermissions; + private readonly ILogManager _logger; + private readonly Alias _alias; + + public SitemapModel(IServiceProvider serviceProvider, IPageRepository pages, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) + { + _serviceProvider = serviceProvider; + _pages = pages; + _pageModules = pageModules; + _moduleDefinitions = moduleDefinitions; + _userPermissions = userPermissions; + _logger = logger; + _alias = tenantManager.GetAlias(); + } + + public IActionResult OnGet() + { + var sitemap = new List(); + + // build site map + var moduleDefinitions = _moduleDefinitions.GetModuleDefinitions(_alias.SiteId).ToList(); + var pageModules = _pageModules.GetPageModules(_alias.SiteId); + foreach (var page in _pages.GetPages(_alias.SiteId)) + { + if (_userPermissions.IsAuthorized(null, PermissionNames.View, page.Permissions)) + { + sitemap.Add(new Sitemap { Url = _alias.Protocol + _alias.Name + Utilities.NavigateUrl(_alias.Path, page.Path, ""), ModifiedOn = page.ModifiedOn }); + + foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId)) + { + if (_userPermissions.IsAuthorized(null, PermissionNames.View, pageModule.Module.Permissions)) + { + var moduleDefinition = moduleDefinitions.Where(item => item.ModuleDefinitionName == pageModule.Module.ModuleDefinitionName).FirstOrDefault(); + if (moduleDefinition != null && moduleDefinition.ServerManagerType != "") + { + Type moduletype = Type.GetType(moduleDefinition.ServerManagerType); + if (moduletype != null && moduletype.GetInterface("ISitemap") != null) + { + try + { + var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); + var urls = ((ISitemap)moduleobject).GetUrls(_alias.Path, page.Path, pageModule.Module); + foreach (var url in urls) + { + sitemap.Add(new Sitemap { Url = _alias.Protocol + _alias.Name + url.Url, ModifiedOn = url.ModifiedOn }); + } + } + catch (Exception ex) + { + _logger.Log(LogLevel.Error, this, LogFunction.Other, ex, "Error Retrieving SiteMap For {Name} Module", moduleDefinition.Name); + } + } + } + } + } + } + } + + // write XML + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.IndentChars = (" "); + settings.CloseOutput = true; + settings.OmitXmlDeclaration = true; + settings.WriteEndDocumentOnClose = true; + + StringBuilder builder = new StringBuilder(); + using (XmlWriter writer = XmlWriter.Create(builder, settings)) + { + writer.WriteStartDocument(); + writer.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9"); + + foreach (var url in sitemap) + { + writer.WriteStartElement("url"); + writer.WriteElementString("loc", url.Url); + writer.WriteElementString("lastmod", url.ModifiedOn.ToString("yyyy-MM-dd")); + writer.WriteEndElement(); + } + writer.Close(); + } + + return Content(builder.ToString()); + } + } +} diff --git a/Oqtane.Shared/Models/Sitemap.cs b/Oqtane.Shared/Models/Sitemap.cs new file mode 100644 index 00000000..15d0cedc --- /dev/null +++ b/Oqtane.Shared/Models/Sitemap.cs @@ -0,0 +1,20 @@ +using System; + +namespace Oqtane.Models +{ + /// + /// Describes a Sitemap + /// + public class Sitemap + { + /// + /// Url + /// + public string Url { get; set; } + + /// + /// Url + /// + public DateTime ModifiedOn { get; set; } + } +} diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 34b8e88b..7235af3d 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -143,6 +143,16 @@ namespace Oqtane.Shared return $"{alias?.BaseUrl}{url}"; } + public static string AddUrlParameters(params object[] parameters) + { + var url = ""; + for (var i = 0; i < parameters.Length; i++) + { + url += "/" + parameters[i].ToString(); + } + return url; + } + public static string FormatContent(string content, Alias alias, string operation) { var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : ""; From f2df8e96db1cacc17d9f5d22b3ab25a062d01213 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 23 Feb 2023 16:29:15 -0500 Subject: [PATCH 25/62] fix #2567 - migrate tenant connection string details from database to appsettings.json --- Oqtane.Client/Installer/Installer.razor | 2 +- Oqtane.Client/Modules/Admin/Site/Index.razor | 28 +- Oqtane.Client/Modules/Admin/Sites/Add.razor | 10 +- Oqtane.Client/Modules/Admin/Sql/Index.razor | 383 +++++++++++++----- .../Modules/Admin/SystemInfo/Index.razor | 6 +- .../Resources/Installer/Installer.resx | 2 +- .../Resources/Modules/Admin/Site/Index.resx | 10 +- .../Resources/Modules/Admin/Sites/Add.resx | 51 +-- .../Resources/Modules/Admin/Sql/Index.resx | 79 +++- .../Services/Interfaces/ISystemService.cs | 6 - Oqtane.Client/Services/SystemService.cs | 5 +- .../Controllers/InstallationController.cs | 2 +- Oqtane.Server/Controllers/SqlController.cs | 18 +- Oqtane.Server/Controllers/SystemController.cs | 18 +- .../OqtaneServiceCollectionExtensions.cs | 1 + Oqtane.Server/Infrastructure/ConfigManager.cs | 12 + .../Infrastructure/DatabaseManager.cs | 72 +++- .../Interfaces/IConfigManager.cs | 2 + .../HtmlText/Manager/HtmlTextManager.cs | 13 +- .../HtmlText/Repository/HtmlTextContext.cs | 2 +- .../Repository/Context/DBContextBase.cs | 46 +-- .../Context/DBContextDependencies.cs | 20 + .../Repository/Context/InstallationContext.cs | 5 +- .../Repository/Context/MasterDBContext.cs | 4 +- .../Repository/Context/TenantDBContext.cs | 4 +- .../Interfaces/IDBContextDependencies.cs | 13 + .../Repository/Interfaces/ISqlRepository.cs | 4 +- Oqtane.Server/Repository/SqlRepository.cs | 31 +- Oqtane.Server/Startup.cs | 2 +- .../Server/Manager/[Module]Manager.cs | 14 +- .../Server/Repository/[Module]Context.cs | 2 +- Oqtane.Shared/Models/SqlQuery.cs | 3 +- Oqtane.Shared/Shared/Constants.cs | 1 + 33 files changed, 562 insertions(+), 309 deletions(-) create mode 100644 Oqtane.Server/Repository/Context/DBContextDependencies.cs create mode 100644 Oqtane.Server/Repository/Interfaces/IDBContextDependencies.cs diff --git a/Oqtane.Client/Installer/Installer.razor b/Oqtane.Client/Installer/Installer.razor index 18146904..5f10a74b 100644 --- a/Oqtane.Client/Installer/Installer.razor +++ b/Oqtane.Client/Installer/Installer.razor @@ -55,7 +55,7 @@ else {
- +
diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index bb9a7660..30a586a1 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -267,16 +267,16 @@
-
+
- +
- +
@@ -284,10 +284,7 @@
-
- - -
+
@@ -342,8 +339,6 @@ private string _tenant = string.Empty; private string _database = string.Empty; private string _connectionstring = string.Empty; - private string _connectionstringtype = "password"; - private string _connectionstringtoggle = string.Empty; private string _createdby; private DateTime _createdon; private string _modifiedby; @@ -358,7 +353,6 @@ { try { - _connectionstringtoggle = SharedLocalizer["ShowPassword"]; _themeList = await ThemeService.GetThemesAsync(); Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null) @@ -466,20 +460,6 @@ } } - private void ToggleConnectionString() - { - if (_connectionstringtype == "password") - { - _connectionstringtype = "text"; - _connectionstringtoggle = SharedLocalizer["HidePassword"]; - } - else - { - _connectionstringtype = "password"; - _connectionstringtoggle = SharedLocalizer["ShowPassword"]; - } - } - private async Task SaveSite() { validated = true; diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index 509928f8..4b61f801 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -103,7 +103,7 @@ else
- +
- +
@if (_databases != null) { @@ -160,7 +160,7 @@ else else {
- +
@@ -329,7 +329,7 @@ else if (_tenantid == "+") { - if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null) + if (!string.IsNullOrEmpty(_tenantName) && !_tenants.Exists(item => item.Name == _tenantName)) { // validate host credentials var user = new User(); diff --git a/Oqtane.Client/Modules/Admin/Sql/Index.razor b/Oqtane.Client/Modules/Admin/Sql/Index.razor index c6bc3408..6d0dee92 100644 --- a/Oqtane.Client/Modules/Admin/Sql/Index.razor +++ b/Oqtane.Client/Modules/Admin/Sql/Index.razor @@ -1,6 +1,7 @@ @namespace Oqtane.Modules.Admin.Sql @inherits ModuleBase @inject NavigationManager NavigationManager +@inject ISystemService SystemService @inject ITenantService TenantService @inject IDatabaseService DatabaseService @inject ISqlService SqlService @@ -14,123 +15,284 @@ else {
- -
- -
- -
-
- @if (_tenantid != "-1") - { -
- -
- -
-
-
- -
-
- - -
+
+ +
+ +
+
+ @if (_connection == "+") + { +
+ +
+
-
- -
- -
-
- } -
-
- -
-
- @if (_results != null) - { - @if (_results.Count > 0) - { - -
- @foreach (KeyValuePair kvp in _results.First()) - { - @kvp.Key - } -
- - @foreach (KeyValuePair kvp in context) - { - @kvp.Value - } - -
- } - else - { - @Localizer["Return.NoResult"] - } -
-
- } +
+ +
+ @if (_databases != null) + { +
+ + @if (!_showConnectionString) + { + + } + else + { + + } +
+ } +
+
+ @if (!_showConnectionString) + { + if (_databaseConfigType != null) + { + @DatabaseConfigComponent + } + } + else + { +
+ +
+ +
+
+ } +
+ + } + else + { + @if (_connection != "-") + { +
+ +
+ @if (_databases != null) + { + + } +
+
+ @if (!string.IsNullOrEmpty(_tenant)) + { +
+ +
+ +
+
+ } +
+ +
+
+ + +
+
+
+
+ +
+ +
+
+
+ +
+
+ @if (_results != null) + { + @if (_results.Count > 0) + { + +
+ @foreach (KeyValuePair kvp in _results.First()) + { + @kvp.Key + } +
+ + @foreach (KeyValuePair kvp in context) + { + @kvp.Value + } + +
+ } + else + { + @Localizer["Return.NoResult"] + } +
+ +
+ } + } + } +
} @code { - private List _tenants; - private string _tenantid = "-1"; - private string _database = string.Empty; + private string _connection = "-"; + private Dictionary _connections; + private List _tenants; + private List _databases; + + private string _name = string.Empty; + private string _databasetype = string.Empty; + private Type _databaseConfigType; + private object _databaseConfig; + private RenderFragment DatabaseConfigComponent { get; set; } + private bool _showConnectionString = false; + private string _tenant = string.Empty; private string _connectionstring = string.Empty; private string _connectionstringtype = "password"; private string _connectionstringtoggle = string.Empty; private string _sql = string.Empty; - private List> _results; + private List> _results; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - protected override async Task OnInitializedAsync() - { - try - { - _tenants = await TenantService.GetTenantsAsync(); + protected override async Task OnInitializedAsync() + { + try + { + _connections = await SystemService.GetSystemInfoAsync("connectionstrings"); + _tenants = await TenantService.GetTenantsAsync(); + _databases = await DatabaseService.GetDatabasesAsync(); _connectionstringtoggle = SharedLocalizer["ShowPassword"]; } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message); - AddModuleMessage(ex.Message, MessageType.Error); - } - } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message); + AddModuleMessage(ex.Message, MessageType.Error); + } + } - private async void TenantChanged(ChangeEventArgs e) - { - try - { - _tenantid = (string)e.Value; - var tenants = await TenantService.GetTenantsAsync(); - var _databases = await DatabaseService.GetDatabasesAsync(); - var tenant = tenants.Find(item => item.TenantId == int.Parse(_tenantid)); - if (tenant != null) - { - _database = _databases.Find(item => item.DBType == tenant.DBType)?.Name; - _connectionstring = tenant.DBConnectionString; - } - StateHasChanged(); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading Tenant {TenantId} {Error}", _tenantid, ex.Message); - AddModuleMessage(ex.Message, MessageType.Error); - } - } + private async void ConnectionChanged(ChangeEventArgs e) + { + try + { + _connection = (string)e.Value; + if (_connection != "-" && _connection != "+") + { + _connectionstring = _connections[_connection].ToString(); + _tenant = ""; + _databasetype = "-"; + var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection); + if (tenant != null) + { + _tenant = tenant.Name; + _databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType).Name; + } + } + else + { + if (_databases.Exists(item => item.IsDefault)) + { + _databasetype = _databases.Find(item => item.IsDefault).Name; + } + else + { + _databasetype = "LocalDB"; + } + _showConnectionString = false; + LoadDatabaseConfigComponent(); + } + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message); + AddModuleMessage(ex.Message, MessageType.Error); + } + } + + private void DatabaseTypeChanged(ChangeEventArgs eventArgs) + { + try + { + _databasetype = (string)eventArgs.Value; + _showConnectionString = false; + LoadDatabaseConfigComponent(); + } + catch + { + AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error); + } + } + + private void LoadDatabaseConfigComponent() + { + var database = _databases.SingleOrDefault(d => d.Name == _databasetype); + if (database != null) + { + _databaseConfigType = Type.GetType(database.ControlType); + DatabaseConfigComponent = builder => + { + builder.OpenComponent(0, _databaseConfigType); + builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); }); + builder.CloseComponent(); + }; + } + } + + private void ShowConnectionString() + { + if (_databaseConfig is IDatabaseConfigControl databaseConfigControl) + { + _connectionstring = databaseConfigControl.GetConnectionString(); + } + _showConnectionString = !_showConnectionString; + } + + private async Task Add() + { + var connectionstring = _connectionstring; + if (!_showConnectionString && _databaseConfig is IDatabaseConfigControl databaseConfigControl) + { + connectionstring = databaseConfigControl.GetConnectionString(); + } + if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring)) + { + var settings = new Dictionary(); + settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring); + await SystemService.UpdateSystemInfoAsync(settings); + _connections = await SystemService.GetSystemInfoAsync("connectionstrings"); + _connection = "-"; + AddModuleMessage(Localizer["Message.Connection.Added"], MessageType.Success); + } + else + { + AddModuleMessage(Localizer["Message.Required.Connection"], MessageType.Warning); + } + } private void ToggleConnectionString() { @@ -145,14 +307,15 @@ else _connectionstringtoggle = SharedLocalizer["ShowPassword"]; } } - + private async Task Execute() - { - try - { - if (_tenantid != "-1" && !string.IsNullOrEmpty(_sql)) - { - var sqlquery = new SqlQuery { TenantId = int.Parse(_tenantid), Query = _sql }; + { + try + { + if (_databasetype != "-" && !string.IsNullOrEmpty(_sql)) + { + var dbtype = _databases.FirstOrDefault(item => item.Name == _databasetype).DBType; + var sqlquery = new SqlQuery { DBConnectionString = _connection, DBType = dbtype, Query = _sql }; sqlquery = await SqlService.ExecuteQueryAsync(sqlquery); _results = sqlquery.Results; AddModuleMessage(Localizer["Success.QueryExecuted"], MessageType.Success); diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index 239d3e9b..c27f9282 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -190,7 +190,7 @@ { _version = Constants.Version; - Dictionary systeminfo = await SystemService.GetSystemInfoAsync("environment"); + var systeminfo = await SystemService.GetSystemInfoAsync("environment"); if (systeminfo != null) { _clrversion = systeminfo["CLRVersion"].ToString(); @@ -247,7 +247,9 @@ { try { - await SystemService.UpdateSystemInfoAsync("Log", "Clear"); + var settings = new Dictionary(); + settings.Add("clearlog", "true"); + await SystemService.UpdateSystemInfoAsync(settings); _log = string.Empty; AddModuleMessage(Localizer["Success.ClearLog"], MessageType.Success); } diff --git a/Oqtane.Client/Resources/Installer/Installer.resx b/Oqtane.Client/Resources/Installer/Installer.resx index 4d9afff5..ec83b9bb 100644 --- a/Oqtane.Client/Resources/Installer/Installer.resx +++ b/Oqtane.Client/Resources/Installer/Installer.resx @@ -172,7 +172,7 @@ Enter a complete connection string including all parameters and delimiters - String: + Settings: Enter Connection Parameters diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index e74d0343..00708582 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -163,7 +163,7 @@ Enter the site name - Enter the tenant for the site + The name of the database used for the site The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). @@ -214,7 +214,7 @@ Include a splash icon for your PWA. It should be a PNG which is 512 X 512 pixels in dimension. - Tenant: + Database: Aliases: @@ -292,7 +292,7 @@ Browse - Tenant Information + Database PWA Settings @@ -304,13 +304,13 @@ Connection: - Database: + Type: The connection information for the database - The database for the tenant + The type of database Delete Site diff --git a/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx index 79980b5d..f42efc99 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx @@ -123,9 +123,6 @@ SQL Server - - Server: - Select Container @@ -145,7 +142,7 @@ Select the default container for the site - Tenant: + Database: Aliases: @@ -157,10 +154,10 @@ Select Site Template - Select Tenant + Select Database - Create New Tenant + Create Database Error Loading Containers For Theme @@ -172,19 +169,19 @@ Invalid Host Password - Tenant Name Is Missing Or Already Exists + Database Name Is Missing Or Already Exists {0} Already Used For Another Site - You Must Provide A Tenant, Site Name, Alias, Default Theme/Container, And Site Template + You Must Provide A Database, Site Name, Alias, Default Theme/Container, And Site Template Enter the name of the site - Select the default theme for the website + Select the default theme for the site Select the admin container for the site @@ -193,28 +190,13 @@ Select the site template - Select the tenant for the site + Select the database for the site - Enter the name for the tenant + Enter the name for the database - Select the database type for the tenant - - - Enter the server for the tenant - - - Enter the database for the tenant - - - Select if you want integrated security or not - - - Enter the username for the integrated security - - - Enter the password for the integrated security + Select the database type Enter the username of an existing host user @@ -232,23 +214,14 @@ Site Template: - Tenant Name: + Name: - Database Type: + Type: Database: - - Integrated Security: - - - Database Username: - - - Database Password: - Host Username: @@ -274,7 +247,7 @@ Enter a complete connection string including all parameters and delimiters - String: + Settings: Enter Connection Parameters diff --git a/Oqtane.Client/Resources/Modules/Admin/Sql/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Sql/Index.resx index ab2f651f..4cc9e688 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Sql/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Sql/Index.resx @@ -117,30 +117,75 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Connection: + + + Select a database connection (from appsettings.json) + + + Select Connection + + + Add Connection + + + Name: + + + Enter the name of the connection + + + Type: + + + Select the database type + + + Select Type + + + Enter Connection Parameters + + + Enter Connection String + + + Settings: + + + A complete connection string including all parameters and delimiters + + + Add + - Tenant: - - - Select Tenant - - - Execute - - - You Must Select A Tenant And Provide A Valid SQL Query - - - No Results Returned + Database: - Select the tenant associated with the database server - - - Enter the SQL query for the database server + The database using this connection SQL Query: + + Enter a valid SQL query for the database + + + Execute + + + You Must Select A Database Type And Provide A Valid SQL Query + + + You Must Provide A Connection Name And Settings + + + Connection Added Successfully + + + No Results Returned + SQL Query Executed diff --git a/Oqtane.Client/Services/Interfaces/ISystemService.cs b/Oqtane.Client/Services/Interfaces/ISystemService.cs index 8648cc4a..4ecc757c 100644 --- a/Oqtane.Client/Services/Interfaces/ISystemService.cs +++ b/Oqtane.Client/Services/Interfaces/ISystemService.cs @@ -32,11 +32,5 @@ namespace Oqtane.Services /// /// Task UpdateSystemInfoAsync(Dictionary settings); - - /// - /// updates a config value - /// - /// - Task UpdateSystemInfoAsync(string settingKey, object settingValue); } } diff --git a/Oqtane.Client/Services/SystemService.cs b/Oqtane.Client/Services/SystemService.cs index 41605da2..1d632dc5 100644 --- a/Oqtane.Client/Services/SystemService.cs +++ b/Oqtane.Client/Services/SystemService.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using System.Collections.Generic; using Oqtane.Documentation; using Oqtane.Shared; +using System.Net; namespace Oqtane.Services { @@ -32,9 +33,5 @@ namespace Oqtane.Services { await PostJsonAsync(Apiurl, settings); } - public async Task UpdateSystemInfoAsync(string settingKey, object settingValue) - { - await PutJsonAsync($"{Apiurl}/{settingKey}/{settingValue}", ""); - } } } diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index 17398958..25976fc9 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -52,7 +52,7 @@ namespace Oqtane.Controllers { var installation = new Installation { Success = false, Message = "" }; - if (ModelState.IsValid && (User.IsInRole(RoleNames.Host) || string.IsNullOrEmpty(_configManager.GetSetting("ConnectionStrings:" + SettingKeys.ConnectionStringKey, "")))) + if (ModelState.IsValid && (User.IsInRole(RoleNames.Host) || string.IsNullOrEmpty(_configManager.GetSetting($"{SettingKeys.ConnectionStringsSection}:{SettingKeys.ConnectionStringKey}", "")))) { installation = _databaseManager.Install(config); diff --git a/Oqtane.Server/Controllers/SqlController.cs b/Oqtane.Server/Controllers/SqlController.cs index d755ed84..97ee4798 100644 --- a/Oqtane.Server/Controllers/SqlController.cs +++ b/Oqtane.Server/Controllers/SqlController.cs @@ -32,13 +32,23 @@ namespace Oqtane.Controllers { var results = new List>(); Dictionary row; - Tenant tenant = _tenants.GetTenant(sqlquery.TenantId); + + if (string.IsNullOrEmpty(sqlquery.DBType) || string.IsNullOrEmpty(sqlquery.DBConnectionString)) + { + Tenant tenant = _tenants.GetTenant(sqlquery.TenantId); + if (tenant != null) + { + sqlquery.DBType = tenant.DBType; + sqlquery.DBConnectionString = tenant.DBConnectionString; + } + } + try { foreach (string query in sqlquery.Query.Split("GO", StringSplitOptions.RemoveEmptyEntries)) { - IDataReader dr = _sql.ExecuteReader(tenant, query); - _logger.Log(LogLevel.Information, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId}", query, sqlquery.TenantId); + IDataReader dr = _sql.ExecuteReader(sqlquery.DBType, sqlquery.DBConnectionString, query); + _logger.Log(LogLevel.Information, this, LogFunction.Other, "Sql Query {Query} Executed on Database {DBType} and Connection {DBConnectionString}", query, sqlquery.DBType, sqlquery.DBConnectionString); while (dr.Read()) { row = new Dictionary(); @@ -53,7 +63,7 @@ namespace Oqtane.Controllers catch (Exception ex) { results.Add(new Dictionary() { { "Error", ex.Message } }); - _logger.Log(LogLevel.Warning, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId} Resulted In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message); + _logger.Log(LogLevel.Warning, this, LogFunction.Other, "Sql Query {Query} Executed on Database {DBType} and Connection {DBConnectionString} Resulted In An Error {Error}", sqlquery.Query, sqlquery.DBType, sqlquery.DBConnectionString, ex.Message); } sqlquery.Results = results; return sqlquery; diff --git a/Oqtane.Server/Controllers/SystemController.cs b/Oqtane.Server/Controllers/SystemController.cs index e6fb098c..ca95428a 100644 --- a/Oqtane.Server/Controllers/SystemController.cs +++ b/Oqtane.Server/Controllers/SystemController.cs @@ -63,6 +63,12 @@ namespace Oqtane.Controllers } systeminfo.Add("Log", log); break; + case "connectionstrings": + foreach (var kvp in _configManager.GetSettings(SettingKeys.ConnectionStringsSection)) + { + systeminfo.Add(kvp.Key, kvp.Value); + } + break; } return systeminfo; @@ -88,19 +94,11 @@ namespace Oqtane.Controllers } } - // PUT: api/ - [HttpPut("{key}/{value}")] - [Authorize(Roles = RoleNames.Host)] - public void Put(string key, object value) - { - UpdateSetting(key, value); - } - private void UpdateSetting(string key, object value) { - switch (key) + switch (key.ToLower()) { - case "Log": + case "clearlog": string path = Path.Combine(_environment.ContentRootPath, "Content", "Log", "error.log"); if (System.IO.File.Exists(path)) { diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 719463ae..2bb65c38 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -72,6 +72,7 @@ namespace Microsoft.Extensions.DependencyInjection internal static IServiceCollection AddOqtaneTransientServices(this IServiceCollection services) { + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Oqtane.Server/Infrastructure/ConfigManager.cs b/Oqtane.Server/Infrastructure/ConfigManager.cs index 85278929..8b96569d 100644 --- a/Oqtane.Server/Infrastructure/ConfigManager.cs +++ b/Oqtane.Server/Infrastructure/ConfigManager.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.Extensions.Configuration; @@ -42,6 +44,16 @@ namespace Oqtane.Infrastructure return value; } + public Dictionary GetSettings(string sectionKey) + { + var settings = new Dictionary(); + foreach (var kvp in _config.GetSection(sectionKey).GetChildren().AsEnumerable()) + { + settings.Add(kvp.Key, kvp.Value); + } + return settings; + } + public void AddOrUpdateSetting(string key, T value, bool reload) { AddOrUpdateSetting("appsettings.json", key, value, reload); diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index ed4a730c..69776d0c 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -162,6 +162,16 @@ namespace Oqtane.Infrastructure { install.DefaultContainer = GetInstallationConfig(SettingKeys.DefaultContainerKey, Constants.DefaultContainer); } + + // add new site + if (install.TenantName != TenantNames.Master && install.ConnectionString.Contains("=")) + { + _configManager.AddOrUpdateSetting($"{SettingKeys.ConnectionStringsSection}:{install.TenantName}", install.ConnectionString, false); + } + if (install.TenantName == TenantNames.Master && !install.ConnectionString.Contains("=")) + { + install.ConnectionString = _config.GetConnectionString(install.ConnectionString); + } } else { @@ -273,7 +283,7 @@ namespace Oqtane.Infrastructure var database = Activator.CreateInstance(type) as IDatabase; // create data directory if does not exist - var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); + var dataDirectory = AppDomain.CurrentDomain.GetData(Constants.DataDirectory)?.ToString(); if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory ?? String.Empty); var dbOptions = new DbContextOptionsBuilder().UseOqtaneDatabase(database, NormalizeConnectionString(install.ConnectionString)).Options; @@ -316,10 +326,7 @@ namespace Oqtane.Infrastructure using (var masterDbContext = new MasterDBContext(new DbContextOptions(), null, _config)) { - if (installation.Success && (install.DatabaseType == Constants.DefaultDBType)) - { - UpgradeSqlServer(sql, install.ConnectionString, install.DatabaseType, true); - } + AddEFMigrationsHistory(sql, install.ConnectionString, install.DatabaseType, "", true); // push latest model into database masterDbContext.Database.Migrate(); result.Success = true; @@ -354,7 +361,7 @@ namespace Oqtane.Infrastructure tenant = new Tenant { Name = install.TenantName, - DBConnectionString = DenormalizeConnectionString(install.ConnectionString), + DBConnectionString = (install.TenantName == TenantNames.Master) ? SettingKeys.ConnectionStringKey : install.TenantName, DBType = install.DatabaseType, CreatedBy = "", CreatedOn = DateTime.UtcNow, @@ -413,21 +420,19 @@ namespace Oqtane.Infrastructure var upgrades = scope.ServiceProvider.GetRequiredService(); var sql = scope.ServiceProvider.GetRequiredService(); var tenantManager = scope.ServiceProvider.GetRequiredService(); + var DBContextDependencies = scope.ServiceProvider.GetRequiredService(); using (var db = GetInstallationContext()) { foreach (var tenant in db.Tenant.ToList()) { tenantManager.SetTenant(tenant.TenantId); + tenant.DBConnectionString = MigrateConnectionString(db, tenant); try { - using (var tenantDbContext = new TenantDBContext(tenantManager, null)) + using (var tenantDbContext = new TenantDBContext(DBContextDependencies)) { - if (install.DatabaseType == Constants.DefaultDBType) - { - UpgradeSqlServer(sql, tenant.DBConnectionString, tenant.DBType, false); - } - + AddEFMigrationsHistory(sql, _configManager.GetSetting($"{SettingKeys.ConnectionStringsSection}:{tenant.DBConnectionString}", ""), tenant.DBType, tenant.Version, false); // push latest model into database tenantDbContext.Database.Migrate(); result.Success = true; @@ -753,8 +758,8 @@ namespace Oqtane.Infrastructure private string DenormalizeConnectionString(string connectionString) { - var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); - connectionString = connectionString.Replace(dataDirectory ?? String.Empty, "|DataDirectory|"); + var dataDirectory = AppDomain.CurrentDomain.GetData(Constants.DataDirectory)?.ToString(); + connectionString = connectionString.Replace(dataDirectory ?? String.Empty, $"|{Constants.DataDirectory}|"); return connectionString; } @@ -780,8 +785,8 @@ namespace Oqtane.Infrastructure private string NormalizeConnectionString(string connectionString) { - var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); - connectionString = connectionString.Replace("|DataDirectory|", dataDirectory); + var dataDirectory = AppDomain.CurrentDomain.GetData(Constants.DataDirectory)?.ToString(); + connectionString = connectionString.Replace($"|{Constants.DataDirectory}|", dataDirectory); return connectionString; } @@ -799,14 +804,39 @@ namespace Oqtane.Infrastructure _configManager.AddOrUpdateSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", databaseType, true); } - public void UpgradeSqlServer(ISqlRepository sql, string connectionString, string databaseType, bool isMaster) + public void AddEFMigrationsHistory(ISqlRepository sql, string connectionString, string databaseType, string version, bool isMaster) { - var script = (isMaster) ? "MigrateMaster.sql" : "MigrateTenant.sql"; + // in version 2.1.0 the __EFMigrationsHistory tables were introduced and must be added to existing SQL Server installations + if ((isMaster || (version != null && Version.Parse(version).CompareTo(Version.Parse("2.1.0")) < 0)) && databaseType == Constants.DefaultDBType) + { + var script = (isMaster) ? "MigrateMaster.sql" : "MigrateTenant.sql"; - var query = sql.GetScriptFromAssembly(Assembly.GetExecutingAssembly(), script); - query = query.Replace("{{Version}}", Constants.Version); + var query = sql.GetScriptFromAssembly(Assembly.GetExecutingAssembly(), script); + query = query.Replace("{{Version}}", Constants.Version); - sql.ExecuteNonQuery(connectionString, databaseType, query); + sql.ExecuteNonQuery(connectionString, databaseType, query); + } + } + + public string MigrateConnectionString(InstallationContext db, Tenant tenant) + { + // migrate connection strings from the Tenant table to appsettings + if (tenant.DBConnectionString.Contains("=")) + { + var defaultConnection = _configManager.GetConnectionString(SettingKeys.ConnectionStringKey); + if (tenant.DBConnectionString == defaultConnection) + { + tenant.DBConnectionString = SettingKeys.ConnectionStringKey; + } + else + { + _configManager.AddOrUpdateSetting($"{SettingKeys.ConnectionStringsSection}:{tenant.Name}", tenant.DBConnectionString, false); + tenant.DBConnectionString = tenant.Name; + } + db.Entry(tenant).State = EntityState.Modified; + db.SaveChanges(); + } + return tenant.DBConnectionString; } private void ValidateConfiguration() diff --git a/Oqtane.Server/Infrastructure/Interfaces/IConfigManager.cs b/Oqtane.Server/Infrastructure/Interfaces/IConfigManager.cs index 5bff86b9..2f5ba875 100644 --- a/Oqtane.Server/Infrastructure/Interfaces/IConfigManager.cs +++ b/Oqtane.Server/Infrastructure/Interfaces/IConfigManager.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.Extensions.Configuration; namespace Oqtane.Infrastructure @@ -7,6 +8,7 @@ namespace Oqtane.Infrastructure public IConfigurationSection GetSection(string sectionKey); public T GetSetting(string settingKey, T defaultValue); public T GetSetting(string sectionKey, string settingKey, T defaultValue); + public Dictionary GetSettings(string sectionKey); void AddOrUpdateSetting(string key, T value, bool reload); void AddOrUpdateSetting(string file, string key, T value, bool reload); void RemoveSetting(string key, bool reload); diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index 6179c40c..959f4d07 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -2,7 +2,6 @@ using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Modules.HtmlText.Repository; using System.Net; -using Microsoft.AspNetCore.Http; using Oqtane.Enums; using Oqtane.Repository; using Oqtane.Shared; @@ -17,15 +16,13 @@ namespace Oqtane.Modules.HtmlText.Manager public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable { private readonly IHtmlTextRepository _htmlText; - private readonly ITenantManager _tenantManager; - private readonly IHttpContextAccessor _accessor; + private readonly IDBContextDependencies _DBContextDependencies; private readonly ISqlRepository _sqlRepository; - public HtmlTextManager(IHtmlTextRepository htmlText, ITenantManager tenantManager, IHttpContextAccessor httpContextAccessor, ISqlRepository sqlRepository) + public HtmlTextManager(IHtmlTextRepository htmlText, IDBContextDependencies DBContextDependencies, ISqlRepository sqlRepository) { _htmlText = htmlText; - _tenantManager = tenantManager; - _accessor = httpContextAccessor; + _DBContextDependencies = DBContextDependencies; _sqlRepository = sqlRepository; } @@ -56,12 +53,12 @@ namespace Oqtane.Modules.HtmlText.Manager // version 1.0.0 used SQL scripts rather than migrations, so we need to seed the migration history table _sqlRepository.ExecuteNonQuery(tenant, MigrationUtils.BuildInsertScript("HtmlText.01.00.00.00")); } - return Migrate(new HtmlTextContext(_tenantManager, _accessor), tenant, MigrationType.Up); + return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Up); } public bool Uninstall(Tenant tenant) { - return Migrate(new HtmlTextContext(_tenantManager, _accessor), tenant, MigrationType.Down); + return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Down); } } } diff --git a/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextContext.cs b/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextContext.cs index 97f95772..9f7e93de 100644 --- a/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextContext.cs +++ b/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextContext.cs @@ -13,7 +13,7 @@ namespace Oqtane.Modules.HtmlText.Repository [PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")] public class HtmlTextContext : DBContextBase, ITransientService, IMultiDatabase { - public HtmlTextContext(ITenantManager tenantManager, IHttpContextAccessor httpContextAccessor) : base(tenantManager, httpContextAccessor) { } + public HtmlTextContext(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies) { } public virtual DbSet HtmlText { get; set; } } diff --git a/Oqtane.Server/Repository/Context/DBContextBase.cs b/Oqtane.Server/Repository/Context/DBContextBase.cs index 6d876d47..d1ded765 100644 --- a/Oqtane.Server/Repository/Context/DBContextBase.cs +++ b/Oqtane.Server/Repository/Context/DBContextBase.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -6,11 +7,13 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.Configuration; using Oqtane.Databases.Interfaces; using Oqtane.Extensions; using Oqtane.Infrastructure; using Oqtane.Migrations.Framework; using Oqtane.Models; +using Oqtane.Shared; // ReSharper disable BuiltInTypeReferenceStyleForMemberAccess @@ -18,17 +21,17 @@ namespace Oqtane.Repository { public class DBContextBase : IdentityUserContext { - private readonly ITenantResolver _tenantResolver; private readonly ITenantManager _tenantManager; private readonly IHttpContextAccessor _accessor; - private string _connectionString; - private string _databaseType; + private readonly IConfigurationRoot _config; + private string _connectionString = ""; + private string _databaseType = ""; - public DBContextBase(ITenantManager tenantManager, IHttpContextAccessor httpContextAccessor) + public DBContextBase(IDBContextDependencies DBContextDependencies) { - _connectionString = String.Empty; - _tenantManager = tenantManager; - _accessor = httpContextAccessor; + _tenantManager = DBContextDependencies.TenantManager; + _accessor = DBContextDependencies.Accessor; + _config = DBContextDependencies.Config; } public IDatabase ActiveDatabase { get; set; } @@ -39,21 +42,11 @@ namespace Oqtane.Repository if (string.IsNullOrEmpty(_connectionString)) { - - Tenant tenant; - if (_tenantResolver != null) - { - tenant = _tenantResolver.GetTenant(); - } - else - { - tenant = _tenantManager.GetTenant(); - } - + Tenant tenant = _tenantManager.GetTenant(); if (tenant != null) { - _connectionString = tenant.DBConnectionString - .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()); + _connectionString = _config.GetConnectionString(tenant.DBConnectionString) + .Replace($"|{Constants.DataDirectory}|", AppDomain.CurrentDomain.GetData(Constants.DataDirectory)?.ToString()); _databaseType = tenant.DBType; } } @@ -93,12 +86,17 @@ namespace Oqtane.Repository return base.SaveChangesAsync(cancellationToken); } - [Obsolete("This constructor is obsolete. Use DBContextBase(ITenantManager tenantManager, IHttpContextAccessor httpContextAccessor) instead.", false)] - public DBContextBase(ITenantResolver tenantResolver, IHttpContextAccessor httpContextAccessor) + [Obsolete("This constructor is obsolete. Use DBContextBase(IDBContextDependencies DBContextDependencies) instead.", false)] + public DBContextBase(ITenantManager tenantManager, IHttpContextAccessor httpContextAccessor) { - _connectionString = String.Empty; - _tenantResolver = tenantResolver; + _tenantManager = tenantManager; _accessor = httpContextAccessor; + + // anti-pattern used to reference config service in base class without causing breaking change + _config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); } } } diff --git a/Oqtane.Server/Repository/Context/DBContextDependencies.cs b/Oqtane.Server/Repository/Context/DBContextDependencies.cs new file mode 100644 index 00000000..f48d4fcd --- /dev/null +++ b/Oqtane.Server/Repository/Context/DBContextDependencies.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Oqtane.Infrastructure; + +namespace Oqtane.Repository +{ + public class DBContextDependencies : IDBContextDependencies + { + public DBContextDependencies(ITenantManager tenantManager, IHttpContextAccessor httpContextAccessor, IConfigurationRoot config) + { + TenantManager = tenantManager; + Accessor = httpContextAccessor; + Config = config; + } + + public ITenantManager TenantManager { get; } + public IHttpContextAccessor Accessor { get; } + public IConfigurationRoot Config { get; } + } +} diff --git a/Oqtane.Server/Repository/Context/InstallationContext.cs b/Oqtane.Server/Repository/Context/InstallationContext.cs index 20644def..ad6912c3 100644 --- a/Oqtane.Server/Repository/Context/InstallationContext.cs +++ b/Oqtane.Server/Repository/Context/InstallationContext.cs @@ -1,8 +1,5 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; -using Oqtane.Databases.Interfaces; +using Microsoft.EntityFrameworkCore; using Oqtane.Extensions; -using Oqtane.Interfaces; using Oqtane.Models; using IDatabase = Oqtane.Databases.Interfaces.IDatabase; diff --git a/Oqtane.Server/Repository/Context/MasterDBContext.cs b/Oqtane.Server/Repository/Context/MasterDBContext.cs index d607749d..a50c8fe2 100644 --- a/Oqtane.Server/Repository/Context/MasterDBContext.cs +++ b/Oqtane.Server/Repository/Context/MasterDBContext.cs @@ -41,8 +41,8 @@ namespace Oqtane.Repository { if (_config.IsInstalled()) { - _connectionString = _config.GetConnectionString("DefaultConnection") - .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()); + _connectionString = _config.GetConnectionString(SettingKeys.ConnectionStringKey) + .Replace($"|{Constants.DataDirectory}|", AppDomain.CurrentDomain.GetData(Constants.DataDirectory)?.ToString()); } _databaseType = _config.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey]; diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index dc2c219b..2efad3f7 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -1,6 +1,4 @@ -using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; -using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Repository.Databases.Interfaces; @@ -12,7 +10,7 @@ namespace Oqtane.Repository { public class TenantDBContext : DBContextBase, IMultiDatabase { - public TenantDBContext(ITenantManager tenantManager, IHttpContextAccessor httpContextAccessor) : base(tenantManager, httpContextAccessor) { } + public TenantDBContext(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies) { } public virtual DbSet Site { get; set; } public virtual DbSet Page { get; set; } diff --git a/Oqtane.Server/Repository/Interfaces/IDBContextDependencies.cs b/Oqtane.Server/Repository/Interfaces/IDBContextDependencies.cs new file mode 100644 index 00000000..5e93f0e1 --- /dev/null +++ b/Oqtane.Server/Repository/Interfaces/IDBContextDependencies.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Oqtane.Infrastructure; + +namespace Oqtane.Repository +{ + public interface IDBContextDependencies + { + ITenantManager TenantManager { get; } + IHttpContextAccessor Accessor { get; } + IConfigurationRoot Config { get; } + } +} diff --git a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs index ed1b96b0..d4d87201 100644 --- a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs @@ -1,4 +1,4 @@ -using System.Data; +using System.Data; using System.Reflection; using Oqtane.Models; @@ -18,6 +18,8 @@ namespace Oqtane.Repository IDataReader ExecuteReader(Tenant tenant, string query); + IDataReader ExecuteReader(string DBType, string DBConnectionString, string query); + string GetScriptFromAssembly(Assembly assembly, string fileName); } } diff --git a/Oqtane.Server/Repository/SqlRepository.cs b/Oqtane.Server/Repository/SqlRepository.cs index 59cb2a46..31e0aa10 100644 --- a/Oqtane.Server/Repository/SqlRepository.cs +++ b/Oqtane.Server/Repository/SqlRepository.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; +using System; using System.Data; using System.IO; using System.Linq; using System.Reflection; +using Microsoft.Extensions.Configuration; using Oqtane.Databases.Interfaces; -using Oqtane.Interfaces; using Oqtane.Models; // ReSharper disable ConvertToUsingDeclaration // ReSharper disable InvertIf @@ -15,6 +14,13 @@ namespace Oqtane.Repository { public class SqlRepository : ISqlRepository { + private IConfigurationRoot _config; + + public SqlRepository(IConfigurationRoot config) + { + _config = config; + } + public void ExecuteScript(Tenant tenant, string script) { // execute script in current tenant @@ -75,13 +81,19 @@ namespace Oqtane.Repository public IDataReader ExecuteReader(Tenant tenant, string query) { var db = GetActiveDatabase(tenant.DBType); - return db.ExecuteReader(tenant.DBConnectionString, query); + return db.ExecuteReader(GetConnectionString(tenant.DBConnectionString), query); + } + + public IDataReader ExecuteReader(string DBType, string DBConnectionString, string query) + { + var db = GetActiveDatabase(DBType); + return db.ExecuteReader(GetConnectionString(DBConnectionString), query); } public int ExecuteNonQuery(string connectionString, string databaseType, string query) { var db = GetActiveDatabase(databaseType); - return db.ExecuteNonQuery(connectionString, query); + return db.ExecuteNonQuery(GetConnectionString(connectionString), query); } public string GetScriptFromAssembly(Assembly assembly, string fileName) @@ -119,5 +131,14 @@ namespace Oqtane.Repository return activeDatabase; } + + private string GetConnectionString(string connectionString) + { + if (!connectionString.Contains("=")) + { + connectionString = _config.GetConnectionString(connectionString); + } + return connectionString; + } } } diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index a8eb7e25..188f95e3 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -40,7 +40,7 @@ namespace Oqtane //add possibility to switch off swagger on production. _useSwagger = Configuration.GetSection("UseSwagger").Value != "false"; - AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data")); + AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(env.ContentRootPath, "Data")); _env = env; } diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs index e9092589..a04db0cd 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs @@ -12,25 +12,23 @@ namespace [Owner].[Module].Manager { public class [Module]Manager : MigratableModuleBase, IInstallable, IPortable { - private I[Module]Repository _[Module]Repository; - private readonly ITenantManager _tenantManager; - private readonly IHttpContextAccessor _accessor; + private readonly I[Module]Repository _[Module]Repository; + private readonly IDBContextDependencies _DBContextDependencies; - public [Module]Manager(I[Module]Repository [Module]Repository, ITenantManager tenantManager, IHttpContextAccessor accessor) + public [Module]Manager(I[Module]Repository [Module]Repository, IDBContextDependencies DBContextDependencies) { _[Module]Repository = [Module]Repository; - _tenantManager = tenantManager; - _accessor = accessor; + _DBContextDependencies = DBContextDependencies; } public bool Install(Tenant tenant, string version) { - return Migrate(new [Module]Context(_tenantManager, _accessor), tenant, MigrationType.Up); + return Migrate(new [Module]Context(_DBContextDependencies), tenant, MigrationType.Up); } public bool Uninstall(Tenant tenant) { - return Migrate(new [Module]Context(_tenantManager, _accessor), tenant, MigrationType.Down); + return Migrate(new [Module]Context(_DBContextDependencies), tenant, MigrationType.Down); } public string ExportModule(Module module) diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Repository/[Module]Context.cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Repository/[Module]Context.cs index 44e1336b..90103d5c 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Repository/[Module]Context.cs +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Repository/[Module]Context.cs @@ -11,7 +11,7 @@ namespace [Owner].[Module].Repository { public virtual DbSet [Module] { get; set; } - public [Module]Context(ITenantManager tenantManager, IHttpContextAccessor accessor) : base(tenantManager, accessor) + public [Module]Context(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies) { } { // ContextBase handles multi-tenant database connections } diff --git a/Oqtane.Shared/Models/SqlQuery.cs b/Oqtane.Shared/Models/SqlQuery.cs index 9949d2ef..5b8f2b4c 100644 --- a/Oqtane.Shared/Models/SqlQuery.cs +++ b/Oqtane.Shared/Models/SqlQuery.cs @@ -8,7 +8,8 @@ namespace Oqtane.Models /// Reference to the this belongs to ///
public int TenantId { get; set; } - + public string DBType { get; set; } + public string DBConnectionString { get; set; } public string Query { get; set; } public List> Results { get; set; } } diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 15e9af81..56bd1dbf 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -11,6 +11,7 @@ namespace Oqtane.Shared public const string UpdaterPackageId = "Oqtane.Updater"; public const string PackageRegistryUrl = "https://www.oqtane.net"; + public const string DataDirectory = "DataDirectory"; public const string DefaultDBType = "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer"; public const string PageComponent = "Oqtane.UI.ThemeBuilder, Oqtane.Client"; From 6e04281b034103a346def7d7eb699e2bab9a4d18 Mon Sep 17 00:00:00 2001 From: Ben Emamian Date: Fri, 24 Feb 2023 11:20:05 +1100 Subject: [PATCH 26/62] extends watching dll files extends watching group to include *.dll files and exclude the ones cause an infinite loop. --- Oqtane.Server/Oqtane.Server.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 405c4d36..41d1fc47 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -60,4 +60,8 @@ + + + + From b49d011edfd82f029de87a7b844fa9c9e75f2b3d Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Fri, 24 Feb 2023 11:44:12 +0100 Subject: [PATCH 27/62] Fix for deleting a ModuleDefinition and related records #2602 We then find all Module items that have a ModuleDefinitionName property that matches the ModuleDefinitionName of the item to be removed, and remove them one by one. For each Module item to be removed, we find the PageModule items associated with it, remove them from the pageModules list, and then remove the Module item itself from the modules list. --- .../Controllers/ModuleDefinitionController.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index fae9a6a0..2c8a31bd 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -15,6 +15,7 @@ using System; using Microsoft.Extensions.DependencyInjection; using System.Text.Json; using System.Net; +using Oqtane.Modules; namespace Oqtane.Controllers { @@ -22,6 +23,8 @@ namespace Oqtane.Controllers public class ModuleDefinitionController : Controller { private readonly IModuleDefinitionRepository _moduleDefinitions; + private readonly IModuleRepository _modules; + private readonly IPageModuleRepository _pagemodules; private readonly ITenantRepository _tenants; private readonly ISqlRepository _sql; private readonly IUserPermissions _userPermissions; @@ -33,9 +36,11 @@ namespace Oqtane.Controllers private readonly ILogManager _logger; private readonly Alias _alias; - public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, ITenantRepository tenants, ISqlRepository sql, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) + public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository module,IPageModuleRepository pageModule, ITenantRepository tenants, ISqlRepository sql, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) { _moduleDefinitions = moduleDefinitions; + _modules = module; + _pagemodules = pageModule; _tenants = tenants; _sql = sql; _userPermissions = userPermissions; @@ -228,6 +233,24 @@ namespace Oqtane.Controllers _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Folder Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName); } + // remove PageModule and Module + List modulesToRemove = _modules.GetModules(moduledefinition.SiteId).Where(m => m.ModuleDefinitionName == moduledefinition.ModuleDefinitionName).ToList(); + foreach (Models.Module moduleToRemove in modulesToRemove) + { + // Get the PageModule items associated with the Module item to be removed + List pageModulesToRemove = _pagemodules.GetPageModules(moduledefinition.SiteId).Where(pm => pm.ModuleId == moduleToRemove.ModuleId).ToList(); + + foreach(PageModule pageModule in pageModulesToRemove) + { + // Remove the PageModule item + _pagemodules.DeletePageModule(pageModule.PageModuleId); + } + + // Remove the Module item + _modules.DeleteModule(moduleToRemove.ModuleId); + } + + // remove module definition _moduleDefinitions.DeleteModuleDefinition(id); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, SyncEventActions.Delete); From 4913fab0b36a2947e73b3d3fa175556e9ce7cf60 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Fri, 24 Feb 2023 14:21:03 -0500 Subject: [PATCH 28/62] explicity specify optional and reload parameters --- Oqtane.Server/Repository/Context/DBContextBase.cs | 2 +- Oqtane.Server/Startup.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Repository/Context/DBContextBase.cs b/Oqtane.Server/Repository/Context/DBContextBase.cs index d1ded765..0b9bf821 100644 --- a/Oqtane.Server/Repository/Context/DBContextBase.cs +++ b/Oqtane.Server/Repository/Context/DBContextBase.cs @@ -95,7 +95,7 @@ namespace Oqtane.Repository // anti-pattern used to reference config service in base class without causing breaking change _config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json") + .AddJsonFile("appsettings.json", false, false) .Build(); } } diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 188f95e3..6ee68ec6 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -31,7 +31,7 @@ namespace Oqtane { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile("appsettings.json", false, true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true); Configuration = builder.Build(); From 8605e3ca5ab7a853af5904f8e4cb8b307dd938c4 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 28 Feb 2023 17:59:21 -0500 Subject: [PATCH 29/62] Major refactoring replacing permission strings with permission collections. These changes will require extensive regression testing. These changes may include breaking changes which will need to be identified and resolved to provide backward compatibility. --- Oqtane.Client/Modules/Admin/Files/Edit.razor | 2 +- .../Admin/ModuleDefinitions/Edit.razor | 2 +- .../Modules/Admin/Modules/Settings.razor | 2 +- Oqtane.Client/Modules/Admin/Pages/Add.razor | 3 +- Oqtane.Client/Modules/Admin/Pages/Edit.razor | 2 +- .../Modules/Controls/ActionDialog.razor | 6 +- .../Modules/Controls/ActionLink.razor | 6 +- .../Modules/Controls/PermissionGrid.razor | 238 +++++++++--------- .../Controls/Container/ModuleActionsBase.cs | 36 ++- .../Themes/Controls/Theme/ControlPanel.razor | 48 ++-- Oqtane.Server/Controllers/FolderController.cs | 2 +- Oqtane.Server/Controllers/PageController.cs | 7 +- .../Extensions/PermissionExtension.cs | 56 +---- .../Interfaces/IPermissionRepository.cs | 6 +- .../Repository/PermissionRepository.cs | 142 ++--------- Oqtane.Server/Security/UserPermissions.cs | 11 +- Oqtane.Shared/Models/Folder.cs | 3 +- Oqtane.Shared/Models/Module.cs | 2 +- Oqtane.Shared/Models/ModuleDefinition.cs | 3 +- Oqtane.Shared/Models/Page.cs | 2 +- Oqtane.Shared/Models/Permission.cs | 35 ++- Oqtane.Shared/Models/PermissionString.cs | 23 -- Oqtane.Shared/Models/SiteTemplate.cs | 4 +- Oqtane.Shared/Security/UserSecurity.cs | 110 +++----- 24 files changed, 274 insertions(+), 477 deletions(-) delete mode 100644 Oqtane.Shared/Models/PermissionString.cs diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor index 4f53443f..e474f597 100644 --- a/Oqtane.Client/Modules/Admin/Files/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor @@ -99,7 +99,7 @@ private string _imagesizes = string.Empty; private string _capacity = "0"; private bool _isSystem; - private string _permissions = string.Empty; + private List _permissions; private string _createdBy; private DateTime _createdOn; private string _modifiedBy; diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor index a8a78168..5da79f52 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor @@ -206,7 +206,7 @@ private string _contact = ""; private string _license = ""; private string _runtimes = ""; - private string _permissions; + private List _permissions; private string _createdby; private DateTime _createdon; private string _modifiedby; diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index ca84fd46..8195b54a 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -101,7 +101,7 @@ private string _containerType; private string _allPages = "false"; private string _permissionNames = ""; - private string _permissions = null; + private List _permissions; private string _pageId; private PermissionGrid _permissionGrid; private Type _moduleSettingsType; diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index bd0cd704..716677fd 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -183,7 +183,7 @@ private string _themetype = string.Empty; private string _containertype = string.Empty; private string _icon = string.Empty; - private string _permissions = string.Empty; + private List _permissions = null; private PermissionGrid _permissionGrid; private Type _themeSettingsType; private object _themeSettings; @@ -202,7 +202,6 @@ _containers = ThemeService.GetContainerControls(_themeList, _themetype); _containertype = PageState.Site.DefaultContainerType; _children = PageState.Pages.Where(item => item.ParentId == null).ToList(); - _permissions = string.Empty; ThemeSettings(); } catch (Exception ex) diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 144a4dad..0cfc24b6 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -221,7 +221,7 @@ private string _themetype; private string _containertype = "-"; private string _icon; - private string _permissions = null; + private List _permissions = null; private string _createdby; private DateTime _createdon; private string _modifiedby; diff --git a/Oqtane.Client/Modules/Controls/ActionDialog.razor b/Oqtane.Client/Modules/Controls/ActionDialog.razor index 4e21b91a..13cdf176 100644 --- a/Oqtane.Client/Modules/Controls/ActionDialog.razor +++ b/Oqtane.Client/Modules/Controls/ActionDialog.razor @@ -40,7 +40,7 @@ @code { private bool _visible = false; - private string _permissions = string.Empty; + private List _permissions; private bool _editmode = false; private bool _authorized = false; private string _iconSpan = string.Empty; @@ -61,7 +61,7 @@ public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel [Parameter] - public string Permissions { get; set; } // optional - can be used to specify a permission string + public List Permissions { get; set; } // optional - can be used to specify permissions [Parameter] public string Class { get; set; } // optional @@ -109,7 +109,7 @@ Header = Localize(nameof(Header), Header); Message = Localize(nameof(Message), Message); - _permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions; + _permissions = (Permissions == null) ? ModuleState.Permissions : Permissions; _authorized = IsAuthorized(); } diff --git a/Oqtane.Client/Modules/Controls/ActionLink.razor b/Oqtane.Client/Modules/Controls/ActionLink.razor index 6f3a29bf..2bc3bc52 100644 --- a/Oqtane.Client/Modules/Controls/ActionLink.razor +++ b/Oqtane.Client/Modules/Controls/ActionLink.razor @@ -26,7 +26,7 @@ private string _text = string.Empty; private string _parameters = string.Empty; private string _url = string.Empty; - private string _permissions = string.Empty; + private List _permissions; private bool _editmode = false; private bool _authorized = false; private string _classname = "btn btn-primary"; @@ -52,7 +52,7 @@ public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel [Parameter] - public string Permissions { get; set; } // optional - can be used to specify a permission string + public List Permissions { get; set; } // optional - can be used to specify permissions [Parameter] public bool Disabled { get; set; } // optional @@ -119,7 +119,7 @@ _iconSpan = $"{(IconOnly ? "" : " ")}"; } - _permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions; + _permissions = (Permissions == null) ? ModuleState.Permissions : Permissions; _text = Localize(nameof(Text), _text); _url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters); if (!string.IsNullOrEmpty(ReturnUrl)) diff --git a/Oqtane.Client/Modules/Controls/PermissionGrid.razor b/Oqtane.Client/Modules/Controls/PermissionGrid.razor index 4fcdc5ac..b8d5443c 100644 --- a/Oqtane.Client/Modules/Controls/PermissionGrid.razor +++ b/Oqtane.Client/Modules/Controls/PermissionGrid.razor @@ -15,20 +15,19 @@ @Localizer["Role"] - @foreach (PermissionString permission in _permissions) + @foreach (var permissionname in _permissionnames) { - @((MarkupString)GetPermissionName(permission).Replace(" ", "
")) + @((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "
")) } @foreach (Role role in _roles) { @role.Name - @foreach (PermissionString permission in _permissions) + @foreach (var permissionname in _permissionnames) { - var p = permission; - + } @@ -50,23 +49,21 @@ @Localizer["User"] - @foreach (PermissionString permission in _permissions) - { - @Localizer[permission.PermissionName] - } - + @foreach (var permissionname in _permissionnames) + { + @((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "
")) + } + @foreach (User user in _users) { - string userid = "[" + user.UserId.ToString() + "]"; @user.DisplayName - @foreach (PermissionString permission in _permissions) + @foreach (var permissionname in _permissionnames) { - var p = permission; - + } @@ -94,9 +91,9 @@ } @code { - private string _permissionnames = string.Empty; + private List _permissionnames; + private List _permissions; private List _roles; - private List _permissions; private List _users = new List(); private AutoComplete _user; private string _message = string.Empty; @@ -108,28 +105,31 @@ public string PermissionNames { get; set; } [Parameter] - public string Permissions { get; set; } + public List Permissions { get; set; } protected override async Task OnInitializedAsync() { - if (string.IsNullOrEmpty(PermissionNames)) - { - _permissionnames = Shared.PermissionNames.View + "," + Shared.PermissionNames.Edit; - } - else - { - _permissionnames = PermissionNames; - } - _roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true); if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { _roles.RemoveAll(item => item.Name == RoleNames.Host); } - _permissions = new List(); + // get permission names + if (string.IsNullOrEmpty(PermissionNames)) + { + _permissionnames = new List(); + _permissionnames.Add(Shared.PermissionNames.View); + _permissionnames.Add(Shared.PermissionNames.Edit); + } + else + { + _permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList(); + } - foreach (string permissionname in _permissionnames.Split(',', StringSplitOptions.RemoveEmptyEntries)) + // initialize permissions + _permissions = new List(); + foreach (string permissionname in _permissionnames) { // permission names can be in the form of "EntityName:PermissionName:Roles" if (permissionname.Contains(":")) @@ -137,78 +137,83 @@ var segments = permissionname.Split(':'); if (segments.Length == 3) { - if (!segments[2].Contains(RoleNames.Admin)) + foreach (var role in segments[2].Split(';')) { - segments[2] = RoleNames.Admin + ";" + segments[2]; // ensure admin access + _permissions.Add(new Permission(segments[0], segments[1], role, null, true)); + } + // ensure admin access + if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.Role.Name == RoleNames.Admin)) + { + _permissions.Add(new Permission(segments[0], segments[1], RoleNames.Admin, null, true)); } - _permissions.Add(new PermissionString { EntityName = segments[0], PermissionName = segments[1], Permissions = segments[2] }); } } else { - _permissions.Add(new PermissionString { EntityName = EntityName, PermissionName = permissionname, Permissions = RoleNames.Admin }); + _permissions.Add(new Permission(EntityName, permissionname, RoleNames.Admin, null, true)); } } - if (!string.IsNullOrEmpty(Permissions)) + // populate permissions and users + if (Permissions.Any()) { - // populate permissions - foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions)) + foreach (var permission in Permissions) { - int index = _permissions.FindIndex(item => item.EntityName == permissionstring.EntityName && item.PermissionName == permissionstring.PermissionName); - if (index != -1) + if (!_permissions.Any(item => item.EntityName == permission.EntityName && item.PermissionName == permission.PermissionName && item.Role.Name == permission.Role.Name)) { - _permissions[index].Permissions = permissionstring.Permissions; + _permissions.Add(permission); } - - if (permissionstring.Permissions.Contains("[")) + if (permission.UserId != null) { - foreach (string user in permissionstring.Permissions.Split('[', StringSplitOptions.RemoveEmptyEntries)) + if (!_users.Any(item => item.UserId == permission.UserId.Value)) { - if (user.Contains("]")) - { - var userid = int.Parse(user.Substring(0, user.IndexOf("]"))); - if (_users.Where(item => item.UserId == userid).FirstOrDefault() == null) - { - _users.Add(await UserService.GetUserAsync(userid, ModuleState.SiteId)); - } - } + _users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId)); } } } } } - private string GetPermissionName(PermissionString permission) + private string GetPermissionName(string permissionName) { - var permissionname = Localizer[permission.PermissionName].ToString(); - if (!string.IsNullOrEmpty(EntityName)) - { - permissionname += " " + Localizer[permission.EntityName].ToString(); - } - return permissionname; + return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName; } - private bool? GetPermissionValue(string permissions, string securityKey) + private string GetEntityName(string permissionName) { - if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";")) + return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName; + } + + private string DisplayPermissionName(string permissionName) + { + var name = Localizer[GetPermissionName(permissionName)].ToString(); + name += " " + Localizer[GetEntityName(permissionName)].ToString(); + return name; + } + + private bool? GetPermissionValue(string permissionName, string roleName, int userId) + { + bool? isauthorized = null; + if (roleName != "") { - return false; // deny permission + var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.Role.Name == roleName); + if (permission != null) + { + isauthorized = permission.IsAuthorized; + } } else { - if ((";" + permissions + ";").Contains(";" + securityKey + ";")) + var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId); + if (permission != null) { - return true; // grant permission - } - else - { - return null; // not specified - } + isauthorized = permission.IsAuthorized; + } } + return isauthorized; } - private bool GetPermissionDisabled(string entityName, string permissionName, string roleName) + private bool GetPermissionDisabled(string permissionName, string roleName) { if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { @@ -216,7 +221,7 @@ } else { - if (entityName != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) + if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { return true; } @@ -227,6 +232,34 @@ } } + private void PermissionChanged(bool? value, string permissionName, string roleName, int userId) + { + if (roleName != "") + { + var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.Role.Name == roleName); + if (permission == null) + { + _permissions.Remove(permission); + } + if (value != null) + { + _permissions.Add(new Permission(GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value)); + } + } + else + { + var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId); + if (permission == null) + { + _permissions.Remove(permission); + } + if (value != null) + { + _permissions.Add(new Permission(GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value)); + } + } + } + private async Task> GetUsers(string filter) { var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered); @@ -251,62 +284,39 @@ _user.Clear(); } - private void PermissionChanged(bool? value, string entityName, string permissionName, string securityId) - { - var selected = value; - int index = _permissions.FindIndex(item => item.EntityName == entityName && item.PermissionName == permissionName); - if (index != -1) - { - var permission = _permissions[index]; - - var ids = permission.Permissions.Split(';').ToList(); - ids.Remove(securityId); // remove grant permission - ids.Remove("!" + securityId); // remove deny permission - - switch (selected) - { - case true: - ids.Add(securityId); // add grant permission - break; - case false: - ids.Add("!" + securityId); // add deny permission - break; - case null: - break; // permission not specified - } - - _permissions[index].Permissions = string.Join(";", ids.ToArray()); - } - } - - public string GetPermissions() + public List GetPermissions() { ValidatePermissions(); - return UserSecurity.SetPermissionStrings(_permissions); + return _permissions; } private void ValidatePermissions() { - PermissionString permission; - for (int index = 0; index < _permissions.Count; index++) + // remove deny all users, unauthenticated, and registered users + var permissions = _permissions.Where(item => !item.IsAuthorized && + (item.Role.Name == RoleNames.Everyone || item.Role.Name == RoleNames.Unauthenticated || item.Role.Name == RoleNames.Registered)); + foreach (var permission in permissions) { - permission = _permissions[index]; - List ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList(); - ids.Remove("!" + RoleNames.Everyone); // remove deny all users - ids.Remove("!" + RoleNames.Unauthenticated); // remove deny unauthenticated - ids.Remove("!" + RoleNames.Registered); // remove deny registered users - if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + _permissions.Remove(permission); + } + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + { + // remove deny administrators and host users + permissions = _permissions.Where(item => !item.IsAuthorized && + (item.Role.Name == RoleNames.Admin || item.Role.Name == RoleNames.Host)); + foreach (var permission in permissions) { - ids.Remove("!" + RoleNames.Admin); // remove deny administrators - ids.Remove("!" + RoleNames.Host); // remove deny host users - if (!ids.Contains(RoleNames.Host) && !ids.Contains(RoleNames.Admin)) + _permissions.Remove(permission); + } + foreach (var permissionname in _permissionnames) + { + // add administrators role if neither host or administrator is assigned + if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) && + (item.Role.Name == RoleNames.Admin || item.Role.Name == RoleNames.Host))) { - // add administrators role if host user role is not assigned - ids.Add(RoleNames.Admin); + _permissions.Add(new Permission(GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Admin, null, true)); } } - permission.Permissions = string.Join(";", ids.ToArray()); - _permissions[index] = permission; - } + } } } diff --git a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs index 8714b938..d08d0a44 100644 --- a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs +++ b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs @@ -136,36 +136,32 @@ namespace Oqtane.Themes.Controls private async Task Publish(string url, PageModule pagemodule) { - var permissions = UserSecurity.GetPermissionStrings(pagemodule.Module.Permissions); - foreach (var permissionstring in permissions) + var permissions = pagemodule.Module.Permissions; + if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.Role.Name == RoleNames.Everyone)) { - if (permissionstring.PermissionName == PermissionNames.View) - { - List ids = permissionstring.Permissions.Split(';').ToList(); - if (!ids.Contains(RoleNames.Everyone)) ids.Add(RoleNames.Everyone); - if (!ids.Contains(RoleNames.Registered)) ids.Add(RoleNames.Registered); - permissionstring.Permissions = string.Join(";", ids.ToArray()); - } + permissions.Add(new Permission(EntityNames.Page, pagemodule.PageId, PermissionNames.View, RoleNames.Everyone, null, true)); } - pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions); + if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.Role.Name == RoleNames.Registered)) + { + permissions.Add(new Permission(EntityNames.Page, pagemodule.PageId, PermissionNames.View, RoleNames.Registered, null, true)); + } + pagemodule.Module.Permissions = permissions; await ModuleService.UpdateModuleAsync(pagemodule.Module); return url; } private async Task Unpublish(string url, PageModule pagemodule) { - var permissions = UserSecurity.GetPermissionStrings(pagemodule.Module.Permissions); - foreach (var permissionstring in permissions) + var permissions = pagemodule.Module.Permissions; + if (permissions.Any(item => item.PermissionName == PermissionNames.View && item.Role.Name == RoleNames.Everyone)) { - if (permissionstring.PermissionName == PermissionNames.View) - { - List ids = permissionstring.Permissions.Split(';').ToList(); - ids.Remove(RoleNames.Everyone); - ids.Remove(RoleNames.Registered); - permissionstring.Permissions = string.Join(";", ids.ToArray()); - } + permissions.Remove(permissions.First(item => item.PermissionName == PermissionNames.View && item.Role.Name == RoleNames.Everyone)); } - pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions); + if (permissions.Any(item => item.PermissionName == PermissionNames.View && item.Role.Name == RoleNames.Registered)) + { + permissions.Remove(permissions.First(item => item.PermissionName == PermissionNames.View && item.Role.Name == RoleNames.Registered)); + } + pagemodule.Module.Permissions = permissions; await ModuleService.UpdateModuleAsync(pagemodule.Module); return url; } diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index e72a20d4..a49527c6 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -392,20 +392,21 @@ module.ModuleDefinitionName = ModuleDefinitionName; module.AllPages = false; - List permissions = UserSecurity.GetPermissionStrings(PageState.Page.Permissions); + var permissions = new List(); if (Visibility == "view") { // set module view permissions to page view permissions - permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions = permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions; + permissions = PageState.Page.Permissions.Where(item => item.PermissionName == PermissionNames.View).ToList(); } else { // set module view permissions to page edit permissions - permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions = permissions.Find(p => p.PermissionName == PermissionNames.Edit).Permissions; + permissions = PageState.Page.Permissions.Where(item => item.PermissionName == PermissionNames.Edit).ToList(); } - // set entityname + // set entity name and permission name permissions.ForEach(item => item.EntityName = EntityNames.Module); - module.Permissions = UserSecurity.SetPermissionStrings(permissions); + permissions.ForEach(item => item.PermissionName = PermissionNames.View); + module.Permissions = permissions; module = await ModuleService.AddModuleAsync(module); ModuleId = module.ModuleId.ToString(); @@ -527,32 +528,17 @@ { if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) { - List permissions; - - // publish/unpublish page - var page = PageState.Page; - permissions = UserSecurity.GetPermissionStrings(page.Permissions); - foreach (var permissionstring in permissions) - { - if (permissionstring.PermissionName == PermissionNames.View) - { - List ids = permissionstring.Permissions.Split(';').ToList(); - switch (action) - { - case "publish": - if (!ids.Contains(RoleNames.Everyone)) ids.Add(RoleNames.Everyone); - if (!ids.Contains(RoleNames.Registered)) ids.Add(RoleNames.Registered); - break; - case "unpublish": - ids.Remove(RoleNames.Everyone); - ids.Remove(RoleNames.Registered); - break; - } - permissionstring.Permissions = string.Join(";", ids.ToArray()); - } - } - page.Permissions = UserSecurity.SetPermissionStrings(permissions); - await PageService.UpdatePageAsync(page); + var permissions = PageState.Page.Permissions; + if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.Role.Name == RoleNames.Everyone)) + { + permissions.Add(new Permission(EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Everyone, null, true)); + } + if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.Role.Name == RoleNames.Registered)) + { + permissions.Add(new Permission(EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Registered, null, true)); + } + PageState.Page.Permissions = permissions; + await PageService.UpdatePageAsync(PageState.Page); NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true)); } } diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs index 839c8e1f..445d41c5 100644 --- a/Oqtane.Server/Controllers/FolderController.cs +++ b/Oqtane.Server/Controllers/FolderController.cs @@ -104,7 +104,7 @@ namespace Oqtane.Controllers { if (ModelState.IsValid && folder.SiteId == _alias.SiteId) { - string permissions; + List permissions; if (folder.ParentId != null) { permissions = _folders.GetFolder(folder.ParentId.Value).Permissions; diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index 80d56d78..fd12234e 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -128,7 +128,7 @@ namespace Oqtane.Controllers { if (ModelState.IsValid && page.SiteId == _alias.SiteId) { - string permissions; + List permissions; if (page.ParentId != null) { permissions = _pages.GetPage(page.ParentId.Value).Permissions; @@ -274,9 +274,8 @@ namespace Oqtane.Controllers } // get differences between current and new page permissions - var newPermissions = _permissionRepository.DecodePermissions(page.Permissions, page.SiteId, EntityNames.Page, page.PageId).ToList(); - var added = GetPermissionsDifferences(newPermissions, currentPermissions); - var removed = GetPermissionsDifferences(currentPermissions, newPermissions); + var added = GetPermissionsDifferences(page.Permissions, currentPermissions); + var removed = GetPermissionsDifferences(currentPermissions, page.Permissions); // synchronize module permissions if (added.Count > 0 || removed.Count > 0) diff --git a/Oqtane.Server/Extensions/PermissionExtension.cs b/Oqtane.Server/Extensions/PermissionExtension.cs index ba02e294..bc2c4705 100644 --- a/Oqtane.Server/Extensions/PermissionExtension.cs +++ b/Oqtane.Server/Extensions/PermissionExtension.cs @@ -1,66 +1,14 @@ using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Text.Json; using Oqtane.Models; namespace Oqtane.Extensions { public static class PermissionExtension { - public static string EncodePermissions(this IEnumerable permissionList) + public static List EncodePermissions(this IEnumerable permissionList) { - List permissionstrings = new List(); - string entityname = ""; - string permissionname = ""; - string permissions = ""; - StringBuilder permissionsbuilder = new StringBuilder(); - string securityid = ""; - foreach (Permission permission in permissionList.OrderBy(item => item.EntityName).ThenBy(item => item.PermissionName)) - { - // permission collections are grouped by entityname and permissionname - if (entityname != permission.EntityName || permissionname != permission.PermissionName) - { - permissions = permissionsbuilder.ToString(); - if (permissions != "") - { - permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); - } - entityname = permission.EntityName; - permissionname = permission.PermissionName; - permissionsbuilder = new StringBuilder(); - } - - // deny permissions are prefixed with a "!" - string prefix = !permission.IsAuthorized ? "!" : ""; - - // encode permission - if (permission.UserId == null) - { - securityid = prefix + permission.Role.Name + ";"; - } - else - { - securityid = prefix + "[" + permission.UserId + "];"; - } - - // insert deny permissions at the beginning and append grant permissions at the end - if (prefix == "!") - { - permissionsbuilder.Insert(0, securityid); - } - else - { - permissionsbuilder.Append(securityid); - } - } - - permissions = permissionsbuilder.ToString(); - if (permissions != "") - { - permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); - } - return JsonSerializer.Serialize(permissionstrings); + return permissionList.ToList(); } } } diff --git a/Oqtane.Server/Repository/Interfaces/IPermissionRepository.cs b/Oqtane.Server/Repository/Interfaces/IPermissionRepository.cs index 8ba2c9b0..a3dc7358 100644 --- a/Oqtane.Server/Repository/Interfaces/IPermissionRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IPermissionRepository.cs @@ -10,14 +10,12 @@ namespace Oqtane.Repository IEnumerable GetPermissions(int siteId, string entityName); IEnumerable GetPermissions(int siteId, string entityName, string permissionName); IEnumerable GetPermissions(int siteId, string entityName, int entityId); - IEnumerable GetPermissions(int siteId, string entityName, int entityId, string permissionName); - + IEnumerable GetPermissions(int siteId, string entityName, int entityId, string permissionName); Permission AddPermission(Permission permission); Permission UpdatePermission(Permission permission); - void UpdatePermissions(int siteId, string entityName, int entityId, string permissionStrings); + void UpdatePermissions(int siteId, string entityName, int entityId, List permissions); Permission GetPermission(int permissionId); void DeletePermission(int permissionId); void DeletePermissions(int siteId, string entityName, int entityId); - IEnumerable DecodePermissions(string permissions, int siteId, string entityName, int entityId); } } diff --git a/Oqtane.Server/Repository/PermissionRepository.cs b/Oqtane.Server/Repository/PermissionRepository.cs index d6e3d75f..c3f4acc6 100644 --- a/Oqtane.Server/Repository/PermissionRepository.cs +++ b/Oqtane.Server/Repository/PermissionRepository.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore; using Oqtane.Models; using Microsoft.Extensions.Caching.Memory; using Oqtane.Infrastructure; +using Oqtane.Modules.Admin.Users; namespace Oqtane.Repository { @@ -77,11 +78,28 @@ namespace Oqtane.Repository return permission; } - public void UpdatePermissions(int siteId, string entityName, int entityId, string permissionStrings) + public void UpdatePermissions(int siteId, string entityName, int entityId, List permissions) { + // ensure permissions are fully populated + List roles = _roles.GetRoles(siteId, true).ToList(); + foreach (var permission in permissions) + { + permission.SiteId = siteId; + permission.EntityName = (string.IsNullOrEmpty(permission.EntityName)) ? entityName : permission.EntityName; + permission.EntityId = (permission.EntityName == entityName) ? entityId : -1; + if (permission.RoleId == null && permission.Role != null && !string.IsNullOrEmpty(permission.Role.Name)) + { + var role = roles.FirstOrDefault(item => item.Name == permission.Role.Name); + if (role != null) + { + permission.RoleId = role.RoleId; + } + } + permission.Role = null; + } + // add or update permissions bool modified = false; var existing = new List(); - var permissions = DecodePermissions(permissionStrings, siteId, entityName, entityId); foreach (var permission in permissions) { if (!existing.Any(item => item.EntityName == permission.EntityName && item.PermissionName == permission.PermissionName)) @@ -108,6 +126,7 @@ namespace Oqtane.Repository modified = true; } } + // delete permissions foreach (var permission in existing) { if (!permissions.Any(item => item.EntityName == permission.EntityName && item.PermissionName == permission.PermissionName @@ -163,122 +182,5 @@ namespace Oqtane.Repository _cache.Remove($"permissions:{alias.TenantId}:{siteId}:{entityName}"); } } - - // permissions are stored in the format "{permissionname:!rolename1;![userid1];rolename2;rolename3;[userid2];[userid3]}" where "!" designates Deny permissions - public string EncodePermissions(IEnumerable permissionList) - { - List permissionstrings = new List(); - string permissionname = ""; - string permissions = ""; - StringBuilder permissionsbuilder = new StringBuilder(); - string securityid = ""; - foreach (Permission permission in permissionList.OrderBy(item => item.PermissionName)) - { - // permission collections are grouped by permissionname - if (permissionname != permission.PermissionName) - { - permissions = permissionsbuilder.ToString(); - if (permissions != "") - { - permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); - } - permissionname = permission.PermissionName; - permissionsbuilder = new StringBuilder(); - } - - // deny permissions are prefixed with a "!" - string prefix = !permission.IsAuthorized ? "!" : ""; - - // encode permission - if (permission.UserId == null) - { - securityid = prefix + permission.Role.Name + ";"; - } - else - { - securityid = prefix + "[" + permission.UserId + "];"; - } - - // insert deny permissions at the beginning and append grant permissions at the end - if (prefix == "!") - { - permissionsbuilder.Insert(0, securityid); - } - else - { - permissionsbuilder.Append(securityid); - } - } - - permissions = permissionsbuilder.ToString(); - if (permissions != "") - { - permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); - } - return JsonSerializer.Serialize(permissionstrings); - } - - public IEnumerable DecodePermissions(string permissionStrings, int siteId, string entityName, int entityId) - { - List permissions = new List(); - List roles = _roles.GetRoles(siteId, true).ToList(); - string securityid = ""; - foreach (PermissionString permissionstring in JsonSerializer.Deserialize>(permissionStrings)) - { - foreach (string id in permissionstring.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries)) - { - securityid = id; - Permission permission = new Permission(); - permission.SiteId = siteId; - if (!string.IsNullOrEmpty(permissionstring.EntityName)) - { - permission.EntityName = permissionstring.EntityName; - } - else - { - permission.EntityName = entityName; - } - if (permission.EntityName == entityName) - { - permission.EntityId = entityId; - } - else - { - permission.EntityId = -1; - } - permission.PermissionName = permissionstring.PermissionName; - permission.RoleId = null; - permission.UserId = null; - permission.IsAuthorized = true; - - if (securityid.StartsWith("!")) - { - // deny permission - securityid = securityid.Replace("!", ""); - permission.IsAuthorized = false; - } - if (securityid.StartsWith("[") && securityid.EndsWith("]")) - { - // user id - securityid = securityid.Replace("[", "").Replace("]", ""); - permission.UserId = int.Parse(securityid); - } - else - { - // role name - Role role = roles.SingleOrDefault(item => item.Name == securityid); - if (role != null) - { - permission.RoleId = role.RoleId; - } - } - if (permission.UserId != null || permission.RoleId != null) - { - permissions.Add(permission); - } - } - } - return permissions; - } - } + } } diff --git a/Oqtane.Server/Security/UserPermissions.cs b/Oqtane.Server/Security/UserPermissions.cs index cf0217c4..91d2cc90 100644 --- a/Oqtane.Server/Security/UserPermissions.cs +++ b/Oqtane.Server/Security/UserPermissions.cs @@ -5,6 +5,7 @@ using System.Security.Claims; using Oqtane.Repository; using Oqtane.Extensions; using System; +using System.Collections.Generic; namespace Oqtane.Security { @@ -12,7 +13,7 @@ namespace Oqtane.Security { bool IsAuthorized(ClaimsPrincipal user, int siteId, string entityName, int entityId, string permissionName, string roles); bool IsAuthorized(ClaimsPrincipal user, int siteId, string entityName, int entityId, string permissionName); - bool IsAuthorized(ClaimsPrincipal user, string permissionName, string permissions); + bool IsAuthorized(ClaimsPrincipal user, string permissionName, List permissions); User GetUser(ClaimsPrincipal user); User GetUser(); @@ -36,7 +37,7 @@ namespace Oqtane.Security var permissions = _permissions.GetPermissions(siteId, entityName, entityId, permissionName).ToList(); if (permissions != null && permissions.Count != 0) { - return IsAuthorized(principal, permissionName, permissions.EncodePermissions()); + return IsAuthorized(principal, permissionName, permissions.ToList()); } else { @@ -46,10 +47,10 @@ namespace Oqtane.Security public bool IsAuthorized(ClaimsPrincipal principal, int siteId, string entityName, int entityId, string permissionName) { - return IsAuthorized(principal, permissionName, _permissions.GetPermissions(siteId, entityName, entityId, permissionName)?.EncodePermissions()); + return IsAuthorized(principal, permissionName, _permissions.GetPermissions(siteId, entityName, entityId, permissionName).ToList()); } - public bool IsAuthorized(ClaimsPrincipal principal, string permissionName, string permissions) + public bool IsAuthorized(ClaimsPrincipal principal, string permissionName, List permissions) { return UserSecurity.IsAuthorized(GetUser(principal), permissionName, permissions); } @@ -96,7 +97,7 @@ namespace Oqtane.Security // deprecated public bool IsAuthorized(ClaimsPrincipal principal, string entityName, int entityId, string permissionName) { - return IsAuthorized(principal, permissionName, _permissions.GetPermissions(_accessor.HttpContext.GetAlias().SiteId, entityName, entityId, permissionName)?.EncodePermissions()); + return IsAuthorized(principal, permissionName, _permissions.GetPermissions(_accessor.HttpContext.GetAlias().SiteId, entityName, entityId, permissionName).ToList()); } } } diff --git a/Oqtane.Shared/Models/Folder.cs b/Oqtane.Shared/Models/Folder.cs index 90bf87bd..46e5fa90 100644 --- a/Oqtane.Shared/Models/Folder.cs +++ b/Oqtane.Shared/Models/Folder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; namespace Oqtane.Models @@ -68,7 +69,7 @@ namespace Oqtane.Models /// TODO: todoc what would this contain? ///
[NotMapped] - public string Permissions { get; set; } + public List Permissions { get; set; } /// /// Folder Depth diff --git a/Oqtane.Shared/Models/Module.cs b/Oqtane.Shared/Models/Module.cs index d6f219fd..0c8b5f97 100644 --- a/Oqtane.Shared/Models/Module.cs +++ b/Oqtane.Shared/Models/Module.cs @@ -42,7 +42,7 @@ namespace Oqtane.Models #endregion [NotMapped] - public string Permissions { get; set; } + public List Permissions { get; set; } [NotMapped] public Dictionary Settings { get; set; } diff --git a/Oqtane.Shared/Models/ModuleDefinition.cs b/Oqtane.Shared/Models/ModuleDefinition.cs index aec53d88..796611b1 100644 --- a/Oqtane.Shared/Models/ModuleDefinition.cs +++ b/Oqtane.Shared/Models/ModuleDefinition.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using Oqtane.Documentation; @@ -99,7 +100,7 @@ namespace Oqtane.Models [NotMapped] public string AssemblyName { get; set; } [NotMapped] - public string Permissions { get; set; } + public List Permissions { get; set; } [NotMapped] public string Template { get; set; } } diff --git a/Oqtane.Shared/Models/Page.cs b/Oqtane.Shared/Models/Page.cs index 94c1d8f0..e24a3268 100644 --- a/Oqtane.Shared/Models/Page.cs +++ b/Oqtane.Shared/Models/Page.cs @@ -98,7 +98,7 @@ namespace Oqtane.Models public List Resources { get; set; } [NotMapped] - public string Permissions { get; set; } + public List Permissions { get; set; } [NotMapped] public Dictionary Settings { get; set; } diff --git a/Oqtane.Shared/Models/Permission.cs b/Oqtane.Shared/Models/Permission.cs index 4c93d8c0..f53e699c 100644 --- a/Oqtane.Shared/Models/Permission.cs +++ b/Oqtane.Shared/Models/Permission.cs @@ -1,4 +1,5 @@ using System; +using Oqtane.Shared; namespace Oqtane.Models { @@ -66,15 +67,41 @@ namespace Oqtane.Models public Permission(string permissionName, string roleName, bool isAuthorized) { - PermissionName = permissionName; - Role = new Role { Name = roleName }; - IsAuthorized = isAuthorized; + Initialize("", -1, permissionName, roleName, null, isAuthorized); } public Permission(string permissionName, int userId, bool isAuthorized) { + Initialize("", -1, permissionName, "", userId, isAuthorized); + } + + public Permission(string entityName, string permissionName, string roleName, int? userId, bool isAuthorized) + { + Initialize(entityName, -1, permissionName, roleName, userId, isAuthorized); + } + + public Permission(string entityName, int entityId, string permissionName, string roleName, int? userId, bool isAuthorized) + { + Initialize(entityName, entityId, permissionName, roleName, userId, isAuthorized); + } + + private void Initialize(string entityName, int entityId, string permissionName, string roleName, int? userId, bool isAuthorized) + { + EntityName = entityName; + EntityId = entityId; PermissionName = permissionName; - UserId = userId; + if (!string.IsNullOrEmpty(roleName)) + { + Role = new Role { Name = roleName }; + RoleId = null; + UserId = null; + } + else + { + Role = null; + RoleId = null; + UserId = userId; + } IsAuthorized = isAuthorized; } } diff --git a/Oqtane.Shared/Models/PermissionString.cs b/Oqtane.Shared/Models/PermissionString.cs deleted file mode 100644 index 44bcfc36..00000000 --- a/Oqtane.Shared/Models/PermissionString.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Oqtane.Models -{ - /// - /// Use this to define a which addresses a set of multiple permissions. - /// - public class PermissionString - { - /// - /// A term describing the entity - /// - public string EntityName { get; set; } - - /// - /// A term describing a set of permissions - /// - public string PermissionName { get; set; } - - /// - /// The permissions - /// - public string Permissions { get; set; } - } -} diff --git a/Oqtane.Shared/Models/SiteTemplate.cs b/Oqtane.Shared/Models/SiteTemplate.cs index 7ef52696..dba0302f 100644 --- a/Oqtane.Shared/Models/SiteTemplate.cs +++ b/Oqtane.Shared/Models/SiteTemplate.cs @@ -18,7 +18,7 @@ namespace Oqtane.Models public string Icon { get; set; } public bool IsNavigation { get; set; } public bool IsPersonalizable { get; set; } - public string PagePermissions { get; set; } + public List PagePermissions { get; set; } public List PageTemplateModules { get; set; } [Obsolete("This property is obsolete", false)] @@ -30,7 +30,7 @@ namespace Oqtane.Models public string ModuleDefinitionName { get; set; } public string Title { get; set; } public string Pane { get; set; } - public string ModulePermissions { get; set; } + public List ModulePermissions { get; set; } public string Content { get; set; } } } diff --git a/Oqtane.Shared/Security/UserSecurity.cs b/Oqtane.Shared/Security/UserSecurity.cs index 36cb21c9..9c550f4a 100644 --- a/Oqtane.Shared/Security/UserSecurity.cs +++ b/Oqtane.Shared/Security/UserSecurity.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; -using System.Text.Json; using Oqtane.Models; using Oqtane.Shared; @@ -10,38 +9,25 @@ namespace Oqtane.Security { public class UserSecurity { - public static List GetPermissionStrings(string permissionStrings) + public static bool IsAuthorized(User user, string roles) { - return JsonSerializer.Deserialize>(permissionStrings); - } - - public static string SetPermissionStrings(List permissionStrings) - { - return JsonSerializer.Serialize(permissionStrings); - } - - public static string GetPermissions(string permissionName, string permissionStrings) - { - string permissions = ""; - List permissionstrings = JsonSerializer.Deserialize>(permissionStrings); - PermissionString permissionstring = permissionstrings.FirstOrDefault(item => item.PermissionName == permissionName); - if (permissionstring != null) + var permissions = new List(); + foreach (var role in roles.Split(';', StringSplitOptions.RemoveEmptyEntries)) { - permissions = permissionstring.Permissions; + permissions.Add(new Permission("", role, true)); } - return permissions; + return IsAuthorized(user, permissions); } - public static bool IsAuthorized(User user, string permissionName, string permissionStrings) + public static bool IsAuthorized(User user, string permissionName, List permissions) { - return IsAuthorized(user, GetPermissions(permissionName, permissionStrings)); + return IsAuthorized(user, permissions.Where(item => item.PermissionName == permissionName).ToList()); } - // permissions are stored in the format "!rolename1;![userid1];rolename2;rolename3;[userid2];[userid3]" where "!" designates Deny permissions - public static bool IsAuthorized(User user, string permissions) + public static bool IsAuthorized(User user, List permissions) { bool authorized = false; - if (permissions != "") + if (permissions != null && permissions.Any()) { if (user == null) { @@ -56,77 +42,43 @@ namespace Oqtane.Security return authorized; } - private static bool IsAuthorized(int userId, string roles, string permissions) + private static bool IsAuthorized(int userId, string roles, List permissions) { bool isAuthorized = false; - if (permissions != null) + if (permissions != null && permissions.Any()) { - foreach (string permission in permissions.Split(';', StringSplitOptions.RemoveEmptyEntries)) + // check if denied first + isAuthorized = !permissions.Where(item => !item.IsAuthorized && ( + (item.Role != null && ( + (item.Role.Name == RoleNames.Everyone) || + (item.Role.Name == RoleNames.Unauthenticated && userId == -1) || + roles.Split(';', StringSplitOptions.RemoveEmptyEntries).Contains(item.Role.Name))) || + (item.UserId != null && item.UserId.Value == userId))).Any(); + + if (isAuthorized) { - bool? allowed = VerifyPermission(userId, roles, permission); - if (allowed.HasValue) - { - isAuthorized = allowed.Value; - break; - } + // then check if authorized + isAuthorized = permissions.Where(item => item.IsAuthorized && ( + (item.Role != null && ( + (item.Role.Name == RoleNames.Everyone) || + (item.Role.Name == RoleNames.Unauthenticated && userId == -1) || + roles.Split(';', StringSplitOptions.RemoveEmptyEntries).Contains(item.Role.Name))) || + (item.UserId != null && item.UserId.Value == userId))).Any(); } } return isAuthorized; } - private static bool? VerifyPermission(int userId, string roles, string permission) + public static bool ContainsRole(List permissions, string permissionName, string roleName) { - bool? allowed = null; - //permissions strings are encoded with deny permissions at the beginning and grant permissions at the end for optimal performance - if (!String.IsNullOrEmpty(permission)) - { - // deny permission - if (permission.StartsWith("!")) - { - string denyRole = permission.Replace("!", ""); - if (denyRole == RoleNames.Everyone || IsAllowed(userId, roles, denyRole)) - { - allowed = false; - } - } - else // grant permission - { - if (permission == RoleNames.Everyone || IsAllowed(userId, roles, permission)) - { - allowed = true; - } - } - } - return allowed; + return permissions.Any(item => item.PermissionName == permissionName && item.Role.Name == roleName); } - private static bool IsAllowed(int userId, string roles, string permission) + public static bool ContainsUser(List permissions, string permissionName, int userId) { - if (permission == RoleNames.Unauthenticated) - { - return userId == -1; - } - if ("[" + userId + "]" == permission) - { - return true; - } - if (roles != null) - { - return roles.IndexOf(";" + permission + ";") != -1; - } - return false; - } - - public static bool ContainsRole(string permissionStrings, string permissionName, string roleName) - { - return GetPermissionStrings(permissionStrings).FirstOrDefault(item => item.PermissionName == permissionName).Permissions.Split(';').Contains(roleName); - } - - public static bool ContainsUser(string permissionStrings, string permissionName, int userId) - { - return GetPermissionStrings(permissionStrings).FirstOrDefault(item => item.PermissionName == permissionName).Permissions.Split(';').Contains($"[{userId}]"); + return permissions.Any(item => item.PermissionName == permissionName && item.UserId == userId); } public static ClaimsIdentity CreateClaimsIdentity(Alias alias, User user, List userroles) From 465b7850b7c0403e31df0c6e1a238b11ac1ef327 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 1 Mar 2023 10:05:14 -0500 Subject: [PATCH 30/62] Fix #2614 - ability to add module to page --- .../Themes/Controls/Theme/ControlPanel.razor | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index a49527c6..f2c1731d 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -396,16 +396,15 @@ if (Visibility == "view") { // set module view permissions to page view permissions - permissions = PageState.Page.Permissions.Where(item => item.PermissionName == PermissionNames.View).ToList(); + permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View); } else { // set module view permissions to page edit permissions - permissions = PageState.Page.Permissions.Where(item => item.PermissionName == PermissionNames.Edit).ToList(); + permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit); } - // set entity name and permission name - permissions.ForEach(item => item.EntityName = EntityNames.Module); - permissions.ForEach(item => item.PermissionName = PermissionNames.View); + // set module edit permissions to page edit permissions + permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit); module.Permissions = permissions; module = await ModuleService.AddModuleAsync(module); @@ -457,6 +456,15 @@ } } + private List SetPermissions(List permissions, int siteId, string modulePermission, string pagePermission) + { + foreach (var permission in PageState.Page.Permissions.Where(item => item.PermissionName == pagePermission)) + { + permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized }); + } + return permissions; + } + private async Task ToggleEditMode(bool EditMode) { if (_showEditMode) From e23a9f22dd45d0c4becb317b757d263636dce85e Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Thu, 2 Mar 2023 06:58:19 +0100 Subject: [PATCH 31/62] Fix Correct Permission Delete when ModuleDef is deleted #2619 Added PermissionsRepository to delete the Module permissions when the Module Definition is deleted. --- Oqtane.Server/Controllers/ModuleDefinitionController.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 2c8a31bd..074dad9d 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -25,6 +25,7 @@ namespace Oqtane.Controllers private readonly IModuleDefinitionRepository _moduleDefinitions; private readonly IModuleRepository _modules; private readonly IPageModuleRepository _pagemodules; + private readonly IPermissionRepository _permissions; private readonly ITenantRepository _tenants; private readonly ISqlRepository _sql; private readonly IUserPermissions _userPermissions; @@ -36,11 +37,12 @@ namespace Oqtane.Controllers private readonly ILogManager _logger; private readonly Alias _alias; - public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository module,IPageModuleRepository pageModule, ITenantRepository tenants, ISqlRepository sql, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) + public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository module, IPageModuleRepository pageModule, IPermissionRepository permission, ITenantRepository tenants, ISqlRepository sql, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) { _moduleDefinitions = moduleDefinitions; _modules = module; _pagemodules = pageModule; + _permissions = permission; _tenants = tenants; _sql = sql; _userPermissions = userPermissions; @@ -246,6 +248,9 @@ namespace Oqtane.Controllers _pagemodules.DeletePageModule(pageModule.PageModuleId); } + // Remove Permissions + _permissions.DeletePermissions(moduledefinition.SiteId, EntityNames.Module, moduleToRemove.ModuleId); + // Remove the Module item _modules.DeleteModule(moduleToRemove.ModuleId); } From 2b41909d474ab54a8fd0d4942093f898218ebc66 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 2 Mar 2023 15:34:42 -0500 Subject: [PATCH 32/62] #2618 - add backward compatibility for permissions optimizations --- .../Modules/Admin/Dashboard/Index.razor | 2 +- Oqtane.Client/Modules/Admin/Files/Edit.razor | 9 +- .../Admin/ModuleDefinitions/Edit.razor | 8 +- .../Modules/Admin/Modules/Settings.razor | 10 +- Oqtane.Client/Modules/Admin/Pages/Add.razor | 8 +- Oqtane.Client/Modules/Admin/Pages/Edit.razor | 14 +- Oqtane.Client/Modules/Admin/Site/Index.razor | 2 +- .../Modules/Controls/ActionDialog.razor | 18 +- .../Modules/Controls/ActionLink.razor | 16 +- .../Modules/Controls/FileManager.razor | 2 +- .../Modules/Controls/PermissionGrid.razor | 23 +- Oqtane.Client/Modules/Controls/TabStrip.razor | 4 +- .../Controls/Container/ModuleActions.razor | 2 +- .../Controls/Container/ModuleActionsBase.cs | 14 +- .../Themes/Controls/Theme/ControlPanel.razor | 30 +-- .../Themes/Controls/Theme/LoginBase.cs | 2 +- .../Themes/Controls/Theme/MenuBase.cs | 2 +- Oqtane.Client/UI/ContainerBuilder.razor | 2 +- Oqtane.Client/UI/Pane.razor | 12 +- Oqtane.Client/UI/SiteRouter.razor | 2 +- Oqtane.Server/Controllers/FileController.cs | 16 +- Oqtane.Server/Controllers/FolderController.cs | 10 +- Oqtane.Server/Controllers/ModuleController.cs | 10 +- .../Controllers/ModuleDefinitionController.cs | 4 +- Oqtane.Server/Controllers/PageController.cs | 28 +-- .../Controllers/PageModuleController.cs | 4 +- Oqtane.Server/Controllers/SiteController.cs | 10 +- .../Extensions/PermissionExtension.cs | 5 +- .../SiteTemplates/DefaultSiteTemplate.cs | 44 ++-- .../SiteTemplates/EmptySiteTemplate.cs | 4 +- .../Infrastructure/UpgradeManager.cs | 24 +- Oqtane.Server/Pages/Files.cshtml.cs | 2 +- Oqtane.Server/Pages/Sitemap.cshtml.cs | 4 +- Oqtane.Server/Repository/FileRepository.cs | 10 +- Oqtane.Server/Repository/FolderRepository.cs | 10 +- .../Repository/ModuleDefinitionRepository.cs | 16 +- Oqtane.Server/Repository/ModuleRepository.cs | 6 +- .../Repository/PageModuleRepository.cs | 2 +- Oqtane.Server/Repository/PageRepository.cs | 12 +- Oqtane.Server/Repository/SiteRepository.cs | 208 +++++++++--------- Oqtane.Server/Repository/UserRepository.cs | 4 +- Oqtane.Server/Security/UserPermissions.cs | 7 + Oqtane.Shared/Models/Folder.cs | 17 +- Oqtane.Shared/Models/Module.cs | 17 +- Oqtane.Shared/Models/ModuleDefinition.cs | 17 +- Oqtane.Shared/Models/Page.cs | 15 +- Oqtane.Shared/Models/SiteTemplate.cs | 32 ++- Oqtane.Shared/Security/UserSecurity.cs | 6 + 48 files changed, 431 insertions(+), 295 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Dashboard/Index.razor b/Oqtane.Client/Modules/Admin/Dashboard/Index.razor index e491042a..eb46b301 100644 --- a/Oqtane.Client/Modules/Admin/Dashboard/Index.razor +++ b/Oqtane.Client/Modules/Admin/Dashboard/Index.razor @@ -7,7 +7,7 @@
@foreach (var p in _pages) { - if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions)) + if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) { string url = NavigateUrl(p.Path);
diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor index e474f597..4a252b11 100644 --- a/Oqtane.Client/Modules/Admin/Files/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor @@ -62,8 +62,7 @@
- - +
@@ -99,7 +98,7 @@ private string _imagesizes = string.Empty; private string _capacity = "0"; private bool _isSystem; - private List _permissions; + private List _permissions = null; private string _createdBy; private DateTime _createdOn; private string _modifiedBy; @@ -131,7 +130,7 @@ _imagesizes = folder.ImageSizes; _capacity = folder.Capacity.ToString(); _isSystem = folder.IsSystem; - _permissions = folder.Permissions; + _permissions = folder.PermissionList; _createdBy = folder.CreatedBy; _createdOn = folder.CreatedOn; _modifiedBy = folder.ModifiedBy; @@ -196,7 +195,7 @@ folder.ImageSizes = _imagesizes; folder.Capacity = int.Parse(_capacity); folder.IsSystem = _isSystem; - folder.Permissions = _permissionGrid.GetPermissions(); + folder.PermissionList = _permissionGrid.GetPermissionList(); if (_folderId != -1) { diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor index 5da79f52..f427cd27 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor @@ -95,7 +95,7 @@
- +
@@ -206,7 +206,7 @@ private string _contact = ""; private string _license = ""; private string _runtimes = ""; - private List _permissions; + private List _permissions = null; private string _createdby; private DateTime _createdon; private string _modifiedby; @@ -242,7 +242,7 @@ _contact = moduleDefinition.Contact; _license = moduleDefinition.License; _runtimes = moduleDefinition.Runtimes; - _permissions = moduleDefinition.Permissions; + _permissions = moduleDefinition.PermissionList; _createdby = moduleDefinition.CreatedBy; _createdon = moduleDefinition.CreatedOn; _modifiedby = moduleDefinition.ModifiedBy; @@ -292,7 +292,7 @@ { moduledefinition.Categories = _categories; } - moduledefinition.Permissions = _permissionGrid.GetPermissions(); + moduledefinition.PermissionList = _permissionGrid.GetPermissionList(); await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition); await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition); NavigationManager.NavigateTo(NavigateUrl()); diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index 8195b54a..68df3167 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -46,7 +46,7 @@