From 3c0c5aed6059bd983d0506e298fa6f614958dc94 Mon Sep 17 00:00:00 2001 From: Hisham Bin Ateya Date: Wed, 11 Sep 2024 02:09:02 +0300 Subject: [PATCH 001/152] Add null operator in ModuleState.ModuleDefinition --- Oqtane.Client/Modules/Admin/Modules/Settings.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index e1183a82..a4d7950b 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -167,7 +167,7 @@ { SetModuleTitle(Localizer["ModuleSettings.Title"]); - _module = ModuleState.ModuleDefinition.Name; + _module = ModuleState.ModuleDefinition?.Name; _title = ModuleState.Title; _moduleSettingsTitle = Localizer["ModuleSettings.Heading"]; _pane = ModuleState.Pane; From 69bc06685fe0df5c7dc5f442070280eee7932673 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 12 Sep 2024 14:04:35 -0400 Subject: [PATCH 002/152] fix #4598 - user experience improvements for file upload --- .../Modules/Controls/FileManager.razor | 2 +- Oqtane.Server/Controllers/FileController.cs | 8 ++- Oqtane.Server/wwwroot/js/interop.js | 70 ++++++++++++------- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/FileManager.razor b/Oqtane.Client/Modules/Controls/FileManager.razor index b7c603ff..f75d8f29 100644 --- a/Oqtane.Client/Modules/Controls/FileManager.razor +++ b/Oqtane.Client/Modules/Controls/FileManager.razor @@ -387,7 +387,7 @@ var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes var megabits = (size / 1048576.0) * 8; // binary conversion - var uploadspeed = 2; // 2 Mbps (3G ranges from 300Kbps to 3Mbps) + var uploadspeed = (PageState.Alias.Name.Contains("localhost")) ? 100 : 3; // 3 Mbps is FCC minimum for broadband upload var uploadtime = (megabits / uploadspeed); // seconds var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds) var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 030005f5..5aa8a66e 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -425,11 +425,11 @@ namespace Oqtane.Controllers // POST api//upload [EnableCors(Constants.MauiCorsPolicy)] [HttpPost("upload")] - public async Task UploadFile(string folder, IFormFile formfile) + public async Task UploadFile(string folder, IFormFile formfile) { if (formfile == null || formfile.Length <= 0) { - return; + return NoContent(); } // ensure filename is valid @@ -437,7 +437,7 @@ namespace Oqtane.Controllers if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token) || !HasValidFileExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token)))) { _logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName); - return; + return NoContent(); } string folderPath = ""; @@ -492,6 +492,8 @@ namespace Oqtane.Controllers _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } + + return NoContent(); } private async Task MergeFile(string folder, string filename) diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index b457ad1d..07a761b8 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -293,41 +293,49 @@ Oqtane.Interop = { }, uploadFiles: function (posturl, folder, id, antiforgerytoken, jwt) { var fileinput = document.getElementById('FileInput_' + id); - var files = fileinput.files; var progressinfo = document.getElementById('ProgressInfo_' + id); var progressbar = document.getElementById('ProgressBar_' + id); if (progressinfo !== null && progressbar !== null) { progressinfo.setAttribute("style", "display: inline;"); + progressinfo.innerHTML = ''; progressbar.setAttribute("style", "width: 100%; display: inline;"); + progressbar.value = 0; } + var files = fileinput.files; + var totalSize = 0; for (var i = 0; i < files.length; i++) { - var FileChunk = []; - var file = files[i]; - var MaxFileSizeMB = 1; - var BufferChunkSize = MaxFileSizeMB * (1024 * 1024); - var FileStreamPos = 0; - var EndPos = BufferChunkSize; - var Size = file.size; + totalSize = totalSize + files[i].size; + } - while (FileStreamPos < Size) { - FileChunk.push(file.slice(FileStreamPos, EndPos)); - FileStreamPos = EndPos; - EndPos = FileStreamPos + BufferChunkSize; + var maxChunkSizeMB = 1; + var bufferChunkSize = maxChunkSizeMB * (1024 * 1024); + var uploadedSize = 0; + + for (var i = 0; i < files.length; i++) { + var fileChunk = []; + var file = files[i]; + var fileStreamPos = 0; + var endPos = bufferChunkSize; + + while (fileStreamPos < file.size) { + fileChunk.push(file.slice(fileStreamPos, endPos)); + fileStreamPos = endPos; + endPos = fileStreamPos + bufferChunkSize; } - var TotalParts = FileChunk.length; - var PartCount = 0; + var totalParts = fileChunk.length; + var partCount = 0; - while (Chunk = FileChunk.shift()) { - PartCount++; - var FileName = file.name + ".part_" + PartCount.toString().padStart(3, '0') + "_" + TotalParts.toString().padStart(3, '0'); + while (chunk = fileChunk.shift()) { + partCount++; + var fileName = file.name + ".part_" + partCount.toString().padStart(3, '0') + "_" + totalParts.toString().padStart(3, '0'); var data = new FormData(); data.append('__RequestVerificationToken', antiforgerytoken); data.append('folder', folder); - data.append('formfile', Chunk, FileName); + data.append('formfile', chunk, fileName); var request = new XMLHttpRequest(); request.open('POST', posturl, true); if (jwt !== "") { @@ -335,28 +343,36 @@ Oqtane.Interop = { request.withCredentials = true; } request.upload.onloadstart = function (e) { - if (progressinfo !== null && progressbar !== null) { - progressinfo.innerHTML = file.name + ' 0%'; - progressbar.value = 0; + if (progressinfo !== null && progressbar !== null && progressinfo.innerHTML === '') { + if (files.length === 1) { + progressinfo.innerHTML = file.name; + } + else { + progressinfo.innerHTML = file.name + ", ..."; + } } }; request.upload.onprogress = function (e) { if (progressinfo !== null && progressbar !== null) { - var percent = Math.ceil((e.loaded / e.total) * 100); - progressinfo.innerHTML = file.name + '[' + PartCount + '] ' + percent + '%'; + var percent = Math.ceil(((uploadedSize + e.loaded) / totalSize) * 100); progressbar.value = (percent / 100); } }; request.upload.onloadend = function (e) { if (progressinfo !== null && progressbar !== null) { - progressinfo.innerHTML = file.name + ' 100%'; - progressbar.value = 1; + uploadedSize = uploadedSize + e.total; + var percent = Math.ceil((uploadedSize / totalSize) * 100); + progressbar.value = (percent / 100); } }; request.upload.onerror = function() { if (progressinfo !== null && progressbar !== null) { - progressinfo.innerHTML = file.name + ' Error: ' + request.statusText; - progressbar.value = 0; + if (files.length === 1) { + progressinfo.innerHTML = file.name + ' Error: ' + request.statusText; + } + else { + progressinfo.innerHTML = ' Error: ' + request.statusText; + } } }; request.send(data); From d9466fe4bcb3ae2d6dd77226b85888c552efff13 Mon Sep 17 00:00:00 2001 From: Hisham Bin Ateya Date: Fri, 13 Sep 2024 14:15:34 +0300 Subject: [PATCH 003/152] Fix issue in toggle edit mode --- Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index 9c195681..a58e82b8 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -147,8 +147,7 @@ { if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)) { - PageState.EditMode = true; - NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false"))); + NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + PageState.EditMode.ToString())); } } } From caa2073d48639d0afc6c323c9a66b1df9aa6611d Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 13 Sep 2024 07:34:57 -0400 Subject: [PATCH 004/152] improve support for external login roles --- Oqtane.Client/Modules/Admin/Users/Index.razor | 29 +++++++++-- .../Resources/Modules/Admin/Users/Index.resx | 16 ++++++- ...taneSiteAuthenticationBuilderExtensions.cs | 48 +++++++++++++++++-- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index a04be4c3..fa493c36 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -333,12 +333,29 @@ else
- +
-
+
+ +
+ +
+
+
+ +
+
+ +
+
+
+
@@ -457,6 +474,8 @@ else private string _nameclaimtype; private string _emailclaimtype; private string _roleclaimtype; + private string _roleclaimmappings; + private string _synchronizeroles; private string _profileclaimtypes; private string _domainfilter; private string _createusers; @@ -521,6 +540,8 @@ else _nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name"); _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email"); _roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", ""); + _roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", ""); + _synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false"); _profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", ""); _domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", ""); _createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); @@ -614,7 +635,9 @@ else settings = SettingService.SetSetting(settings, "ExternalLogin:NameClaimType", _nameclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true); - settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true); settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true); settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true); diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index 6baae9c1..46c19999 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -385,10 +385,22 @@ Parameters: - Optionally provide the type name of the role claim provided by the identity provider. These roles will be used in addition to any internal user roles assigned within the site. + Optionally provide the type name of the roles claim provided by the identity provider (the standard default is 'roles'). If role names from the identity provider do not exactly match your site role names, please use the Role Claim Mappings. - Role Claim: + Roles Claim: + + + Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles. For example if the identity provider includes an 'Admin' role name and you want it to map to the 'Administrators' site role you should specify 'Admin:Administrators'. + + + Role Claim Mappings: + + + This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider for a user + + + Synchronize Roles? Optionally provide a comma delimited list of user profile claim type names provided by the identity provider, as well as mappings to your user profile definition. For example if the identity provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'. diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index 604270c1..28a69745 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; using System.Net; using System.Text.Json.Nodes; using System.Globalization; +using System.Net.WebSockets; namespace Oqtane.Extensions { @@ -529,7 +530,8 @@ namespace Oqtane.Extensions { // create claims identity var _userRoles = httpContext.RequestServices.GetRequiredService(); - identity = UserSecurity.CreateClaimsIdentity(alias, user, _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList()); + var userRoles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList(); + identity = UserSecurity.CreateClaimsIdentity(alias, user, userRoles); identity.Label = ExternalLoginStatus.Success; // update user @@ -540,13 +542,49 @@ namespace Oqtane.Extensions // external roles if (!string.IsNullOrEmpty(httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) { - if (claimsPrincipal.Claims.Any(item => item.Type == ClaimTypes.Role)) + if (claimsPrincipal.Claims.Any(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) { - foreach (var claim in claimsPrincipal.Claims.Where(item => item.Type == ClaimTypes.Role)) + var _roles = httpContext.RequestServices.GetRequiredService(); + var roles = _roles.GetRoles(user.SiteId).ToList(); // global roles excluded ie. host users cannot be added/deleted + + var mappings = httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimMappings", "").Split(','); + foreach (var claim in claimsPrincipal.Claims.Where(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) { - if (!identity.Claims.Any(item => item.Type == ClaimTypes.Role && item.Value == claim.Value)) + var rolename = claim.Value; + if (mappings.Any(item => item.StartsWith(rolename + ":"))) { - identity.AddClaim(new Claim(ClaimTypes.Role, claim.Value)); + rolename = mappings.First(item => item.StartsWith(rolename + ":")).Split(':')[1]; + } + var role = roles.FirstOrDefault(item => item.Name == rolename); + if (role != null) + { + if (!userRoles.Any(item => item.RoleId == role.RoleId && item.UserId == user.UserId)) + { + var userRole = new UserRole(); + userRole.RoleId = role.RoleId; + userRole.UserId = user.UserId; + _userRoles.AddUserRole(userRole); + } + } + } + if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:SynchronizeRoles", "false"))) + { + userRoles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList(); + foreach (var userRole in userRoles) + { + var role = roles.FirstOrDefault(item => item.RoleId == userRole.RoleId); + if (role != null) + { + var rolename = role.Name; + if (mappings.Any(item => item.EndsWith(":" + rolename))) + { + rolename = mappings.First(item => item.EndsWith(":" + rolename)).Split(':')[0]; + } + if (!claimsPrincipal.Claims.Any(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", "") && item.Value == rolename)) + { + _userRoles.DeleteUserRole(userRole.UserRoleId); + } + } } } } From 26e4398905d2977d89c45e932b5a5181c77a18b0 Mon Sep 17 00:00:00 2001 From: Hisham Bin Ateya Date: Fri, 13 Sep 2024 21:12:18 +0300 Subject: [PATCH 005/152] Address feedback --- Oqtane.Client/Modules/Admin/Modules/Settings.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index a4d7950b..d2ab823e 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -167,7 +167,6 @@ { SetModuleTitle(Localizer["ModuleSettings.Title"]); - _module = ModuleState.ModuleDefinition?.Name; _title = ModuleState.Title; _moduleSettingsTitle = Localizer["ModuleSettings.Heading"]; _pane = ModuleState.Pane; @@ -186,6 +185,7 @@ if (ModuleState.ModuleDefinition != null) { + _module = ModuleState.ModuleDefinition.Name; _permissionNames = ModuleState.ModuleDefinition?.PermissionNames; if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType)) From d196402dd0c8f741703bc4c385d1cc09df929408 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 13 Sep 2024 15:18:12 -0400 Subject: [PATCH 006/152] fix #4607 - Site HeadContent scripts being added twice --- Oqtane.Client/UI/ThemeBuilder.razor | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor index a563dfd8..c02c74c8 100644 --- a/Oqtane.Client/UI/ThemeBuilder.razor +++ b/Oqtane.Client/UI/ThemeBuilder.razor @@ -66,17 +66,18 @@ { if (!string.IsNullOrEmpty(content)) { - if (PageState.RenderMode == RenderModes.Interactive) + var elements = content.Split('<', StringSplitOptions.RemoveEmptyEntries); + foreach (var element in elements) { - // remove scripts - var index = content.IndexOf("= 0) + if (PageState.RenderMode == RenderModes.Static || (!element.ToLower().StartsWith("script") && !element.ToLower().StartsWith("/script"))) { - content = content.Remove(index, content.IndexOf("") + 9 - index); - index = content.IndexOf(" Date: Fri, 13 Sep 2024 16:13:01 -0400 Subject: [PATCH 007/152] fix #4606 - allow logo to show site name as fallback (credit @JanOlsmar) --- .../Themes/BlazorTheme/Themes/Default.razor | 2 +- Oqtane.Client/Themes/Controls/Theme/Logo.razor | 17 ++++++++++++++++- .../Themes/OqtaneTheme/Themes/Default.razor | 2 +- Oqtane.Server/wwwroot/css/app.css | 6 ++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor index ef853a79..a4f651f5 100644 --- a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor @@ -9,7 +9,7 @@
diff --git a/Oqtane.Client/Themes/Controls/Theme/Logo.razor b/Oqtane.Client/Themes/Controls/Theme/Logo.razor index 594649f1..50beea35 100644 --- a/Oqtane.Client/Themes/Controls/Theme/Logo.razor +++ b/Oqtane.Client/Themes/Controls/Theme/Logo.razor @@ -8,4 +8,19 @@ @PageState.Site.Name -} \ No newline at end of file +} +else +{ + if (ShowName) + { + + } +} + +@code { + [Parameter] + public bool ShowName { get; set; } = false; +} + diff --git a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor index e00dd363..d56c6f78 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor @@ -4,7 +4,7 @@