From 14b0d7abf04803daecce7e46a37af7609f4833c2 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Fri, 2 May 2025 12:16:55 +0200 Subject: [PATCH 01/24] Updated to Bootstrap 5 Updated to Bootstrap 5.3.5 Update bootswatch Cyborg to 5.3.5 using https://cdn.jsdelivr.net because it is not available at https://cdnjs.com/libraries --- Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs | 4 ++-- Oqtane.Shared/Shared/Constants.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs b/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs index f0197b71..f03d7302 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs +++ b/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs @@ -16,8 +16,8 @@ namespace Oqtane.Themes.OqtaneTheme ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client", Resources = new List() { - // obtained from https://cdnjs.com/libraries - new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css", "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==", "anonymous"), + // obtained from https://www.jsdelivr.com/package/npm/bootswatch + new Stylesheet("https://cdn.jsdelivr.net/npm/bootswatch@5.3.5/dist/cyborg/bootstrap.min.css"), new Stylesheet("~/Theme.css"), new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous") } diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 6e70f5bc..bae50ff0 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -86,10 +86,10 @@ namespace Oqtane.Shared public static readonly string[] InternalPagePaths = { "login", "register", "reset", "404" }; public const string DefaultTextEditor = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client"; - public const string BootstrapScriptUrl = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js"; - public const string BootstrapScriptIntegrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg=="; - public const string BootstrapStylesheetUrl = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css"; - public const string BootstrapStylesheetIntegrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg=="; + public const string BootstrapScriptUrl = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js"; + public const string BootstrapScriptIntegrity = "sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq"; + public const string BootstrapStylesheetUrl = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css"; + public const string BootstrapStylesheetIntegrity = "sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7"; public const string CookieConsentCookieName = "Oqtane.CookieConsent"; public const string CookieConsentCookieValue = "yes"; From d81514e9bedcd8b653467c710347ea9b50fe3ba2 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Fri, 2 May 2025 12:19:58 +0200 Subject: [PATCH 02/24] Update for Blazor Theme --- Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor index 1622c5e7..e3b11560 100644 --- a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor @@ -37,7 +37,7 @@ public override List Resources => new List() { // obtained from https://cdnjs.com/libraries - new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", "anonymous"), + new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"), new Stylesheet(ThemePath() + "Theme.css"), new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous") }; From 3811b8f0c0622d4fde5934608f81612d1dfc285f Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Wed, 7 May 2025 11:46:07 +0200 Subject: [PATCH 03/24] Theme Template updated --- .../wwwroot/Themes/Templates/External/Client/ThemeInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/ThemeInfo.cs b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/ThemeInfo.cs index 9bbfed91..d4d395cd 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/ThemeInfo.cs +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/ThemeInfo.cs @@ -16,10 +16,10 @@ namespace [Owner].Theme.[Theme] ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane", Resources = new List() { - // obtained from https://cdnjs.com/libraries - new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", CrossOrigin = "anonymous" }, + // obtained from https://www.jsdelivr.com/ + new Script(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"), new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" }, - new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js", Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==", CrossOrigin = "anonymous" } + new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous") } }; From c13ce3d0f14580cd2c22badc35bcd91811faffea Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Tue, 3 Jun 2025 15:24:43 +0200 Subject: [PATCH 04/24] Update Index.razor Deprecated .text-muted will be replaced by .text-body-secondary in v6. --- Oqtane.Client/Modules/Admin/SearchResults/Index.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor index 80de5e11..bb58a9c1 100644 --- a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor +++ b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor @@ -46,7 +46,7 @@

@context.Title

-

@((MarkupString)context.Snippet)

+

@((MarkupString)context.Snippet)

From b63590d6c7a1a3c01c49de3aea7bd202bc31d8cb Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 3 Jul 2025 15:42:11 +0800 Subject: [PATCH 05/24] Fix #5363: update SettingService.MergeSettings. --- .../Services/Interfaces/ISettingService.cs | 2 +- Oqtane.Client/Services/SettingService.cs | 20 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Oqtane.Client/Services/Interfaces/ISettingService.cs b/Oqtane.Client/Services/Interfaces/ISettingService.cs index dcdc7386..2f4c4b94 100644 --- a/Oqtane.Client/Services/Interfaces/ISettingService.cs +++ b/Oqtane.Client/Services/Interfaces/ISettingService.cs @@ -256,7 +256,7 @@ namespace Oqtane.Services Dictionary SetSetting(Dictionary settings, string settingName, string settingValue, bool isPrivate); - Dictionary MergeSettings(Dictionary settings1, Dictionary settings2); + Dictionary MergeSettings(Dictionary baseSettings, Dictionary overwriteSettings); [Obsolete("GetSettingAsync(int settingId) is deprecated. Use GetSettingAsync(string entityName, int settingId) instead.", false)] diff --git a/Oqtane.Client/Services/SettingService.cs b/Oqtane.Client/Services/SettingService.cs index 4a43972e..a55eeeba 100644 --- a/Oqtane.Client/Services/SettingService.cs +++ b/Oqtane.Client/Services/SettingService.cs @@ -266,27 +266,25 @@ namespace Oqtane.Services return settings; } - public Dictionary MergeSettings(Dictionary settings1, Dictionary settings2) + public Dictionary MergeSettings(Dictionary baseSettings, Dictionary overwriteSettings) { - if (settings1 == null) + var settings = baseSettings != null ? new Dictionary(baseSettings) : new Dictionary(); + if (overwriteSettings != null) { - settings1 = new Dictionary(); - } - if (settings2 != null) - { - foreach (var setting in settings2) + foreach (var setting in overwriteSettings) { - if (settings1.ContainsKey(setting.Key)) + if (settings.ContainsKey(setting.Key)) { - settings1[setting.Key] = setting.Value; + settings[setting.Key] = setting.Value; } else { - settings1.Add(setting.Key, setting.Value); + settings.Add(setting.Key, setting.Value); } } } - return settings1; + + return settings; } [Obsolete("GetSettingAsync(int settingId) is deprecated. Use GetSettingAsync(string entityName, int settingId) instead.", false)] From 711de49571d62d39be042803d1aea109b225596e Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Fri, 4 Jul 2025 12:55:40 +0200 Subject: [PATCH 06/24] feat: replace System.Net.Mail with MailKit (#5372) --- .../Infrastructure/Jobs/NotificationJob.cs | 58 ++++++++++--------- Oqtane.Server/Oqtane.Server.csproj | 1 + 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 4147455b..57525d27 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Net.Mail; + +using MailKit.Net.Smtp; + using Microsoft.Extensions.DependencyInjection; + +using MimeKit; + using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; @@ -48,18 +52,17 @@ namespace Oqtane.Infrastructure settingRepository.GetSettingValue(settings, "SMTPPort", "") != "" && settingRepository.GetSettingValue(settings, "SMTPSender", "") != "") { - // construct SMTP Client - var client = new SmtpClient() - { - DeliveryMethod = SmtpDeliveryMethod.Network, - UseDefaultCredentials = false, - Host = settingRepository.GetSettingValue(settings, "SMTPHost", ""), - Port = int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")), - EnableSsl = bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False")) - }; + // construct SMTP Client + using var client = new SmtpClient(); + + client.Connect(host: settingRepository.GetSettingValue(settings, "SMTPHost", ""), + port: int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")), + options: MailKit.Security.SecureSocketOptions.Auto); + if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "") { - client.Credentials = new NetworkCredential(settingRepository.GetSettingValue(settings, "SMTPUsername", ""), settingRepository.GetSettingValue(settings, "SMTPPassword", "")); + client.Authenticate(settingRepository.GetSettingValue(settings, "SMTPUsername", ""), + settingRepository.GetSettingValue(settings, "SMTPPassword", "")); } // iterate through undelivered notifications @@ -88,7 +91,7 @@ namespace Oqtane.Infrastructure } // validate recipient - if (string.IsNullOrEmpty(notification.ToEmail) || !MailAddress.TryCreate(notification.ToEmail, out _)) + if (string.IsNullOrEmpty(notification.ToEmail) || !MailboxAddress.TryParse(notification.ToEmail, out _)) { log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}
"; notification.IsDeleted = true; @@ -96,50 +99,52 @@ namespace Oqtane.Infrastructure } else { - MailMessage mailMessage = new MailMessage(); + MimeMessage mailMessage = new MimeMessage(); // sender if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail)) { if (!string.IsNullOrEmpty(notification.FromDisplayName)) { - mailMessage.From = new MailAddress(notification.FromEmail, notification.FromDisplayName); + mailMessage.From.Add(new MailboxAddress(notification.FromDisplayName, notification.FromEmail)); } else { - mailMessage.From = new MailAddress(notification.FromEmail); + mailMessage.From.Add(new MailboxAddress("", notification.FromEmail)); } } else { - mailMessage.From = new MailAddress(settingRepository.GetSettingValue(settings, "SMTPSender", ""), (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name); + mailMessage.From.Add(new MailboxAddress((!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name, + settingRepository.GetSettingValue(settings, "SMTPSender", ""))); } // recipient if (!string.IsNullOrEmpty(notification.ToDisplayName)) { - mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName)); + mailMessage.To.Add(new MailboxAddress(notification.ToDisplayName, notification.ToEmail)); } else { - mailMessage.To.Add(new MailAddress(notification.ToEmail)); + mailMessage.To.Add(new MailboxAddress("", notification.ToEmail)); } // subject mailMessage.Subject = notification.Subject; //body - mailMessage.Body = notification.Body; - if (!mailMessage.Body.Contains("<") || !mailMessage.Body.Contains(">")) + var bodyText = notification.Body; + + if (!bodyText.Contains('<') || !bodyText.Contains('>')) { // plain text messages should convert line breaks to HTML tags to preserve formatting - mailMessage.Body = mailMessage.Body.Replace("\n", "
"); + bodyText = bodyText.Replace("\n", "
"); } - // encoding - mailMessage.SubjectEncoding = System.Text.Encoding.UTF8; - mailMessage.BodyEncoding = System.Text.Encoding.UTF8; - mailMessage.IsBodyHtml = true; + mailMessage.Body = new TextPart("html", System.Text.Encoding.UTF8) + { + Text = bodyText + }; // send mail try @@ -157,6 +162,7 @@ namespace Oqtane.Infrastructure } } } + client.Disconnect(true); log += "Notifications Delivered: " + sent + "
"; } else diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 56a682a9..c67e6a71 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -34,6 +34,7 @@ + From 6b567364f9fcdfad6fd6e4c7488f1e3a0a2e6b62 Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Fri, 4 Jul 2025 14:55:02 +0200 Subject: [PATCH 07/24] feat: use appropriate UseSSL equivalent in MailKit --- Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 57525d27..e1d3e17a 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -57,7 +57,7 @@ namespace Oqtane.Infrastructure client.Connect(host: settingRepository.GetSettingValue(settings, "SMTPHost", ""), port: int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")), - options: MailKit.Security.SecureSocketOptions.Auto); + options: bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False")) ? MailKit.Security.SecureSocketOptions.StartTls : MailKit.Security.SecureSocketOptions.None); if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "") { From cb5e4e076f5c23a3c2d22098962ea5151d1b4599 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 7 Jul 2025 12:42:35 -0400 Subject: [PATCH 08/24] remove unused variable --- Oqtane.Server/Controllers/SettingController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 3c67887b..2c33c958 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -43,7 +43,6 @@ namespace Oqtane.Controllers private readonly ILogManager _logger; private readonly Alias _alias; - private readonly string _visitorCookie; public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IOptions cookieOptions, IOptionsSnapshot cookieOptionsSnapshot, IOptionsMonitorCache cookieOptionsMonitorCache, From ac236607f5d24b78febdef63fdb30b0a36628c31 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 8 Jul 2025 13:09:10 -0400 Subject: [PATCH 09/24] update to .NET SDK 9.0.6 --- Oqtane.Client/Oqtane.Client.csproj | 8 ++++---- .../Oqtane.Database.PostgreSQL.csproj | 2 +- .../Oqtane.Database.SqlServer.csproj | 2 +- .../Oqtane.Database.Sqlite.csproj | 2 +- Oqtane.Server/Oqtane.Server.csproj | 20 +++++++++---------- Oqtane.Shared/Oqtane.Shared.csproj | 8 ++++---- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index bb7671d6..32c7deda 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -22,10 +22,10 @@ - - - - + + + + diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index 1238c9cb..3535cf59 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -34,7 +34,7 @@ - + diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index 6a594463..485ee95e 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index c13db7c9..4f60e7ed 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index c67e6a71..55ca2742 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -34,22 +34,22 @@ - - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - + - + + diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index fbffe080..13eaaf2e 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -19,11 +19,11 @@ - - - + + + - + From 85a376b17d4efb88c16129f982c85af788698963 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 8 Jul 2025 13:11:52 -0400 Subject: [PATCH 10/24] update to .NET SDK 9.0.6 --- Oqtane.Maui/Oqtane.Maui.csproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 6f0b4c92..8706acd4 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -67,14 +67,14 @@ - - - - - - - - + + + + + + + + From 668e0cb4ebf5110e15b76661bcf2258b1d08d3ec Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 8 Jul 2025 13:14:53 -0400 Subject: [PATCH 11/24] update to .NET SDK 9.0.6 --- .../Client/[Owner].Module.[Module].Client.csproj | 10 +++++----- .../Server/[Owner].Module.[Module].Server.csproj | 8 ++++---- .../Client/[Owner].Theme.[Theme].Client.csproj | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj index c40f7c41..688f5514 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj @@ -13,11 +13,11 @@ - - - - - + + + + + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj index 75da4858..a0610d56 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj @@ -19,10 +19,10 @@ - - - - + + + + diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj index 77ae8f52..2bfe83d8 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj @@ -13,9 +13,9 @@ - - - + + + From 17045073c81f5f640aae96147e127f8dee11fb23 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 8 Jul 2025 13:20:28 -0400 Subject: [PATCH 12/24] bump version to 6.1.4 --- Oqtane.Client/Oqtane.Client.csproj | 4 ++-- Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj | 4 ++-- .../Oqtane.Database.PostgreSQL.csproj | 4 ++-- Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj | 4 ++-- Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj | 4 ++-- Oqtane.Maui/Oqtane.Maui.csproj | 6 +++--- Oqtane.Package/Oqtane.Client.nuspec | 4 ++-- Oqtane.Package/Oqtane.Framework.nuspec | 6 +++--- Oqtane.Package/Oqtane.Server.nuspec | 4 ++-- Oqtane.Package/Oqtane.Shared.nuspec | 4 ++-- Oqtane.Package/Oqtane.Updater.nuspec | 4 ++-- Oqtane.Package/install.ps1 | 2 +- Oqtane.Package/upgrade.ps1 | 2 +- Oqtane.Server/Oqtane.Server.csproj | 4 ++-- Oqtane.Shared/Oqtane.Shared.csproj | 4 ++-- Oqtane.Shared/Shared/Constants.cs | 4 ++-- Oqtane.Updater/Oqtane.Updater.csproj | 4 ++-- 17 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 32c7deda..2b4fda7f 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -4,7 +4,7 @@ net9.0 Exe Debug;Release - 6.1.3 + 6.1.4 Oqtane Shaun Walker .NET Foundation @@ -12,7 +12,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj index e6703b08..beedbc8e 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -2,7 +2,7 @@ net9.0 - 6.1.3 + 6.1.4 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 https://github.com/oqtane/oqtane.framework Git true diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index 3535cf59..330bf115 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -2,7 +2,7 @@ net9.0 - 6.1.3 + 6.1.4 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 https://github.com/oqtane/oqtane.framework Git true diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index 485ee95e..98941039 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -2,7 +2,7 @@ net9.0 - 6.1.3 + 6.1.4 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 https://github.com/oqtane/oqtane.framework Git true diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index 4f60e7ed..104c2cbd 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -2,7 +2,7 @@ net9.0 - 6.1.3 + 6.1.4 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 https://github.com/oqtane/oqtane.framework Git true diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 8706acd4..7f2170fd 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -6,7 +6,7 @@ Exe - 6.1.3 + 6.1.4 Oqtane Shaun Walker .NET Foundation @@ -14,7 +14,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -30,7 +30,7 @@ com.oqtane.maui - 6.1.3 + 6.1.4 1 diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index b50b816c..6ff7ced3 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 6.1.3 + 6.1.4 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 readme.md icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index a9e739dc..919e7025 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 6.1.3 + 6.1.4 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v6.1.3/Oqtane.Framework.6.1.3.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/download/v6.1.4/Oqtane.Framework.6.1.4.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 readme.md icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index b5e58742..c0254edc 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 6.1.3 + 6.1.4 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 readme.md icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index 9c2041be..5b94b83b 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 6.1.3 + 6.1.4 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 readme.md icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 09453309..b94833cf 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 6.1.3 + 6.1.4 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 readme.md icon.png oqtane diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index fb0b6d7f..6426442d 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.3.Install.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.4.Install.zip" -Force diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index e2f26321..958e0029 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.3.Upgrade.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.4.Upgrade.zip" -Force diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 55ca2742..b23e57d4 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net9.0 Debug;Release - 6.1.3 + 6.1.4 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 13eaaf2e..77dc05d9 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -3,7 +3,7 @@ net9.0 Debug;Release - 6.1.3 + 6.1.4 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index c109af88..10be4133 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -4,8 +4,8 @@ namespace Oqtane.Shared { public class Constants { - public static readonly string Version = "6.1.3"; - public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3"; + public static readonly string Version = "6.1.4"; + public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3,6.1.4"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 7474506c..57fab560 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -3,7 +3,7 @@ net9.0 Exe - 6.1.3 + 6.1.4 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4 https://github.com/oqtane/oqtane.framework Git Oqtane From 461330773abea1ac09f7f12ab822b8e91db5d1c4 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 8 Jul 2025 16:04:19 -0400 Subject: [PATCH 13/24] resolve issue where IDP fails to provide email claim resulting in External Login Remote Failure due to dbo.AspNetUsers requiring a unique email value for each user --- Oqtane.Client/Modules/Admin/UserProfile/Index.razor | 5 ----- .../Resources/Modules/Admin/UserProfile/Index.resx | 3 --- Oqtane.Client/UI/ThemeBuilder.razor | 7 ------- .../OqtaneSiteAuthenticationBuilderExtensions.cs | 4 ++-- 4 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index 2517c538..377b27d8 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -414,11 +414,6 @@ _displayname = PageState.User.DisplayName; _timezoneid = PageState.User.TimeZoneId; - if (string.IsNullOrEmpty(_email)) - { - AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning); - } - // get user folder var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); if (folder != null) diff --git a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx index d6136ee8..2a6862bb 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx @@ -147,9 +147,6 @@ Current User Is Not Logged In - - You Must Provide An Email Address For Your User Account - Error Loading User Profile diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor index 6981b983..d22b21b4 100644 --- a/Oqtane.Client/UI/ThemeBuilder.razor +++ b/Oqtane.Client/UI/ThemeBuilder.razor @@ -20,13 +20,6 @@ return; } - // force authenticated user to provide email address (email may be missing if using external login) - if (PageState.User != null && PageState.User.IsAuthenticated && string.IsNullOrEmpty(PageState.User.Email) && PageState.Route.PagePath != "profile") - { - NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, "profile", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery))); - return; - } - // set page title if (!string.IsNullOrEmpty(PageState.Page.Title)) { diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index e98a0afa..f142c602 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -404,13 +404,13 @@ namespace Oqtane.Extensions else if (!string.IsNullOrEmpty(name)) // name claim provided { username = name.ToLower().Replace(" ", "") + DateTime.UtcNow.ToString("mmss"); - emailaddress = ""; // unknown - will need to be requested from user later + emailaddress = username + "@unknown.com"; displayname = name; } else // neither email nor name provided { username = Guid.NewGuid().ToString("N"); - emailaddress = ""; // unknown - will need to be requested from user later + emailaddress = username + "@unknown.com"; displayname = username; } From b0c1d36babc3cf99518bb2ea8f5dd035ccd342a6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 8 Jul 2025 16:27:35 -0400 Subject: [PATCH 14/24] update External Login default values for Facebook OAuth2 --- Oqtane.Shared/Shared/ExternalLoginProviders.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Oqtane.Shared/Shared/ExternalLoginProviders.cs b/Oqtane.Shared/Shared/ExternalLoginProviders.cs index 57cf9322..b368f83d 100644 --- a/Oqtane.Shared/Shared/ExternalLoginProviders.cs +++ b/Oqtane.Shared/Shared/ExternalLoginProviders.cs @@ -71,13 +71,15 @@ namespace Oqtane.Shared { "ExternalLogin:ProviderUrl", "https://developers.facebook.com" }, { "ExternalLogin:ProviderType", "oauth2" }, { "ExternalLogin:ProviderName", "Facebook" }, - { "ExternalLogin:AuthorizationUrl", "https://www.facebook.com/v18.0/dialog/oauth" }, - { "ExternalLogin:TokenUrl", "https://graph.facebook.com/v18.0/oauth/access_token" }, - { "ExternalLogin:UserInfoUrl", "https://graph.facebook.com/v18.0/me" }, + { "ExternalLogin:AuthorizationUrl", "https://www.facebook.com/v23.0/dialog/oauth" }, + { "ExternalLogin:TokenUrl", "https://graph.facebook.com/v23.0/oauth/access_token" }, + { "ExternalLogin:UserInfoUrl", "https://graph.facebook.com/v23.0/me?fields=id,name,email" }, { "ExternalLogin:ClientId", "YOUR CLIENT ID" }, { "ExternalLogin:ClientSecret", "YOUR CLIENT SECRET" }, - { "ExternalLogin:Scopes", "public_profile" }, - { "ExternalLogin:IdentifierClaimType", "id" } + { "ExternalLogin:Scopes", "public_profile,email" }, + { "ExternalLogin:IdentifierClaimType", "id" }, + { "ExternalLogin:NameClaimType", "name" }, + { "ExternalLogin:EmailClaimType", "email" } } } }; From 0a994afd675ba809379cd5aa7156c27503b87650 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Wed, 9 Jul 2025 02:52:30 +0200 Subject: [PATCH 15/24] Update References .NetCore 9.0.7 --- Oqtane.Client/Oqtane.Client.csproj | 8 ++++---- .../Oqtane.Database.PostgreSQL.csproj | 2 +- .../Oqtane.Database.SqlServer.csproj | 2 +- .../Oqtane.Database.Sqlite.csproj | 2 +- Oqtane.Server/Oqtane.Server.csproj | 16 ++++++++-------- .../Client/[Owner].Module.[Module].Client.csproj | 10 +++++----- .../Server/[Owner].Module.[Module].Server.csproj | 8 ++++---- .../Client/[Owner].Theme.[Theme].Client.csproj | 6 +++--- Oqtane.Shared/Oqtane.Shared.csproj | 8 ++++---- 9 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 2b4fda7f..79410dec 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -22,10 +22,10 @@ - - - - + + + + diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index 330bf115..b0a8c50d 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -34,7 +34,7 @@ - + diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index 98941039..8a8bfd66 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index 104c2cbd..fb57225e 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index b23e57d4..139dcc96 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -34,21 +34,21 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj index 688f5514..8dff70b0 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj @@ -13,11 +13,11 @@ - - - - - + + + + + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj index a0610d56..7d9645e0 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj @@ -19,10 +19,10 @@ - - - - + + + + diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj index 2bfe83d8..f3a6fe45 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj @@ -13,9 +13,9 @@ - - - + + + diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 77dc05d9..c0d42f2e 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -19,11 +19,11 @@ - - - + + + - + From 13d9cb461b7db355c0767c7dbd06afb0762a8a7c Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Wed, 9 Jul 2025 03:42:26 +0200 Subject: [PATCH 16/24] Update Oqtane Maui project to 9.0.7 --- Oqtane.Maui/Oqtane.Maui.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 7f2170fd..0983aa45 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -67,11 +67,11 @@ - - - - - + + + + + From bb52402a173e42bc9cb77649d3dc6d5efad9340d Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Wed, 9 Jul 2025 12:09:00 +0200 Subject: [PATCH 17/24] feat: handle timezones and conversions with NodaTime --- .../OqtaneServiceCollectionExtensions.cs | 1 - .../Modules/Admin/Register/Index.razor | 3 +- Oqtane.Client/Modules/Admin/Site/Index.razor | 3 +- .../Modules/Admin/UserProfile/Index.razor | 3 +- Oqtane.Client/Modules/Admin/Users/Add.razor | 3 +- Oqtane.Client/Modules/Admin/Users/Edit.razor | 3 +- Oqtane.Client/Modules/ModuleBase.cs | 40 ++---- .../Services/Interfaces/ITimeZoneService.cs | 18 --- Oqtane.Client/Services/TimeZoneService.cs | 22 ---- .../Controllers/TimeZoneController.cs | 29 ----- .../OqtaneServiceCollectionExtensions.cs | 1 - Oqtane.Shared/Oqtane.Shared.csproj | 1 + Oqtane.Shared/Shared/Utilities.cs | 123 +++++++++++++++++- 13 files changed, 142 insertions(+), 108 deletions(-) delete mode 100644 Oqtane.Client/Services/Interfaces/ITimeZoneService.cs delete mode 100644 Oqtane.Client/Services/TimeZoneService.cs delete mode 100644 Oqtane.Server/Controllers/TimeZoneController.cs diff --git a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs index 9a89bce8..c5590d51 100644 --- a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs @@ -54,7 +54,6 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); // providers services.AddScoped(); diff --git a/Oqtane.Client/Modules/Admin/Register/Index.razor b/Oqtane.Client/Modules/Admin/Register/Index.razor index 712ba186..025c86c0 100644 --- a/Oqtane.Client/Modules/Admin/Register/Index.razor +++ b/Oqtane.Client/Modules/Admin/Register/Index.razor @@ -3,7 +3,6 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService -@inject ITimeZoneService TimeZoneService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @inject ISettingService SettingService @@ -115,7 +114,7 @@ { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true")); - _timezones = await TimeZoneService.GetTimeZonesAsync(); + _timezones = Utilities.GetTimeZones(); _timezoneid = PageState.Site.TimeZoneId; _initialized = true; } diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 172c4225..fcc9557f 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -10,7 +10,6 @@ @inject IAliasService AliasService @inject IThemeService ThemeService @inject ISettingService SettingService -@inject ITimeZoneService TimeZoneService @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @inject INotificationService NotificationService @@ -508,7 +507,7 @@ Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null) { - _timezones = await TimeZoneService.GetTimeZonesAsync(); + _timezones = Utilities.GetTimeZones(); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index 377b27d8..f993a7c1 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -9,7 +9,6 @@ @inject INotificationService NotificationService @inject IFileService FileService @inject IFolderService FolderService -@inject ITimeZoneService TimeZoneService @inject IJSRuntime jsRuntime @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @@ -404,7 +403,7 @@ _togglepassword = SharedLocalizer["ShowPassword"]; _allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true"); _profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); - _timezones = await TimeZoneService.GetTimeZonesAsync(); + _timezones = Utilities.GetTimeZones(); if (PageState.User != null) { diff --git a/Oqtane.Client/Modules/Admin/Users/Add.razor b/Oqtane.Client/Modules/Admin/Users/Add.razor index 510c87e7..2387e88e 100644 --- a/Oqtane.Client/Modules/Admin/Users/Add.razor +++ b/Oqtane.Client/Modules/Admin/Users/Add.razor @@ -5,7 +5,6 @@ @inject IUserService UserService @inject IProfileService ProfileService @inject ISettingService SettingService -@inject ITimeZoneService TimeZoneService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -133,7 +132,7 @@ { try { - _timezones = await TimeZoneService.GetTimeZonesAsync(); + _timezones = Utilities.GetTimeZones(); _profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); _settings = new Dictionary(); _timezoneid = PageState.Site.TimeZoneId; diff --git a/Oqtane.Client/Modules/Admin/Users/Edit.razor b/Oqtane.Client/Modules/Admin/Users/Edit.razor index c6401a47..002e64df 100644 --- a/Oqtane.Client/Modules/Admin/Users/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Users/Edit.razor @@ -6,7 +6,6 @@ @inject IProfileService ProfileService @inject ISettingService SettingService @inject IFileService FileService -@inject ITimeZoneService TimeZoneService @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -204,7 +203,7 @@ _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _togglepassword = SharedLocalizer["ShowPassword"]; _profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); - _timezones = await TimeZoneService.GetTimeZonesAsync(); + _timezones = Utilities.GetTimeZones(); if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId)) { diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index a066d159..59721817 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -507,24 +507,18 @@ namespace Oqtane.Modules if (datetime == null) return null; - TimeZoneInfo timezone = null; - try + string timezoneId = null; + + if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId)) { - if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId)) - { - timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId); - } - else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId)) - { - timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId); - } + timezoneId = PageState.User.TimeZoneId; } - catch + else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId)) { - // The time zone ID was not found on the local computer + timezoneId = PageState.Site.TimeZoneId; } - return Utilities.UtcAsLocalDateTime(datetime, timezone); + return Utilities.UtcAsLocalDateTime(datetime, timezoneId); } public DateTime? LocalToUtc(DateTime? datetime) @@ -533,24 +527,18 @@ namespace Oqtane.Modules if (datetime == null) return null; - TimeZoneInfo timezone = null; - try + string timezoneId = null; + + if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId)) { - if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId)) - { - timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId); - } - else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId)) - { - timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId); - } + timezoneId = PageState.User.TimeZoneId; } - catch + else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId)) { - // The time zone ID was not found on the local computer + timezoneId = PageState.Site.TimeZoneId; } - return Utilities.LocalDateAndTimeAsUtc(datetime, timezone); + return Utilities.LocalDateAndTimeAsUtc(datetime, timezoneId); } // logging methods diff --git a/Oqtane.Client/Services/Interfaces/ITimeZoneService.cs b/Oqtane.Client/Services/Interfaces/ITimeZoneService.cs deleted file mode 100644 index c31f90b6..00000000 --- a/Oqtane.Client/Services/Interfaces/ITimeZoneService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Oqtane.Models; - -namespace Oqtane.Services -{ - /// - /// Service to store and retrieve entries - /// - public interface ITimeZoneService - { - /// - /// Get the list of time zones - /// - /// - Task> GetTimeZonesAsync(); - } -} diff --git a/Oqtane.Client/Services/TimeZoneService.cs b/Oqtane.Client/Services/TimeZoneService.cs deleted file mode 100644 index f7983b14..00000000 --- a/Oqtane.Client/Services/TimeZoneService.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Oqtane.Documentation; -using Oqtane.Models; -using Oqtane.Shared; - -namespace Oqtane.Services -{ - [PrivateApi("Don't show in the documentation, as everything should use the Interface")] - public class TimeZoneService : ServiceBase, ITimeZoneService - { - public TimeZoneService(HttpClient http, SiteState siteState) : base(http, siteState) { } - - private string Apiurl => CreateApiUrl("TimeZone"); - - public async Task> GetTimeZonesAsync() - { - return await GetJsonAsync>($"{Apiurl}"); - } - } -} diff --git a/Oqtane.Server/Controllers/TimeZoneController.cs b/Oqtane.Server/Controllers/TimeZoneController.cs deleted file mode 100644 index 158d9f72..00000000 --- a/Oqtane.Server/Controllers/TimeZoneController.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Oqtane.Models; -using Oqtane.Shared; - -namespace Oqtane.Controllers -{ - [Route(ControllerRoutes.ApiRoute)] - public class TimeZoneController : Controller - { - public TimeZoneController() {} - - // GET: api/ - [HttpGet] - public IEnumerable Get() - { - return TimeZoneInfo.GetSystemTimeZones() - .Select(item => new Models.TimeZone - { - Id = item.Id, - DisplayName = item.DisplayName - }) - .OrderBy(item => item.DisplayName); - } - } -} diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 98a392cc..9b4b09e9 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -104,7 +104,6 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); // providers services.AddScoped(); diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 77dc05d9..3f699bd6 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -22,6 +22,7 @@ + diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 95ae2cd1..bf2efe3e 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -1,4 +1,3 @@ -using Oqtane.Models; using System; using System.Collections.Generic; using System.Globalization; @@ -7,7 +6,14 @@ using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; + +using NodaTime; +using NodaTime.Extensions; + +using Oqtane.Models; + using File = Oqtane.Models.File; +using TimeZone = Oqtane.Models.TimeZone; namespace Oqtane.Shared { @@ -505,6 +511,7 @@ namespace Oqtane.Shared return $"[{@class.GetType()}] {message}"; } + //Time conversions with TimeZoneInfo public static DateTime? LocalDateAndTimeAsUtc(DateTime? date, string time, TimeZoneInfo localTimeZone = null) { if (date != null && !string.IsNullOrEmpty(time) && TimeSpan.TryParse(time, out TimeSpan timespan)) @@ -581,6 +588,120 @@ namespace Oqtane.Shared return (localDateTime?.Date, localTime); } + + //Time conversions with NodaTime (IANA) timezoneId + public static DateTime? LocalDateAndTimeAsUtc(DateTime? date, string time, string localTimeZoneId) + { + if (date != null && !string.IsNullOrEmpty(time) && TimeSpan.TryParse(time, out TimeSpan timespan)) + { + return LocalDateAndTimeAsUtc(date.Value.Date.Add(timespan), localTimeZoneId); + } + return null; + } + + public static DateTime? LocalDateAndTimeAsUtc(DateTime? date, DateTime? time, string localTimeZoneId) + { + if (date != null) + { + if (time != null) + { + return LocalDateAndTimeAsUtc(date.Value.Date.Add(time.Value.TimeOfDay), localTimeZoneId); + } + return LocalDateAndTimeAsUtc(date.Value.Date, localTimeZoneId); + } + return null; + } + + public static DateTime? LocalDateAndTimeAsUtc(DateTime? date, string localTimeZoneId) + { + if (date != null) + { + DateTimeZone localTimeZone; + + if (!string.IsNullOrEmpty(localTimeZoneId)) + { + localTimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(localTimeZoneId) ?? DateTimeZoneProviders.Tzdb.GetSystemDefault(); + } + else + { + localTimeZone = DateTimeZoneProviders.Tzdb.GetSystemDefault(); + } + + var localDateTime = LocalDateTime.FromDateTime(date.Value); + return localTimeZone.AtLeniently(localDateTime).ToDateTimeUtc(); + } + return null; + } + + public static DateTime? UtcAsLocalDate(DateTime? dateTime, string timeZoneId) + { + return UtcAsLocalDateAndTime(dateTime, timeZoneId).date; + } + + public static DateTime? UtcAsLocalDateTime(DateTime? dateTime, string timeZoneId) + { + var result = UtcAsLocalDateAndTime(dateTime, timeZoneId); + if (result.date != null && !string.IsNullOrEmpty(result.time) && TimeSpan.TryParse(result.time, out TimeSpan timespan)) + { + result.date = result.date.Value.Add(timespan); + } + return result.date; + } + + public static (DateTime? date, string time) UtcAsLocalDateAndTime(DateTime? dateTime, string timeZoneId) + { + DateTimeZone localTimeZone; + + if (!string.IsNullOrEmpty(timeZoneId)) + { + localTimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timeZoneId) ?? DateTimeZoneProviders.Tzdb.GetSystemDefault(); + } + else + { + localTimeZone = DateTimeZoneProviders.Tzdb.GetSystemDefault(); + } + + DateTime? localDateTime = null; + string localTime = string.Empty; + + if (dateTime.HasValue && dateTime?.Kind != DateTimeKind.Local) + { + Instant instant; + + if (dateTime?.Kind == DateTimeKind.Unspecified) + { + // Treat Unspecified as Utc not Local. This is due to EF Core, on some databases, after retrieval will have DateTimeKind as Unspecified. + // All values in database should be UTC. + // Normal .net conversion treats Unspecified as local. + // https://docs.microsoft.com/en-us/dotnet/api/system.timezoneinfo.converttime?view=net-6.0 + instant = Instant.FromDateTimeUtc(new DateTime(dateTime.Value.Ticks, DateTimeKind.Utc)); + } + else + { + instant = Instant.FromDateTimeUtc(dateTime.Value); + } + + localDateTime = instant.InZone(localTimeZone).ToDateTimeOffset().DateTime; + } + + if (localDateTime != null && localDateTime.Value.TimeOfDay.TotalSeconds != 0) + { + localTime = localDateTime.Value.ToString("HH:mm"); + } + + return (localDateTime?.Date, localTime); + } + + public static List GetTimeZones() + { + return [.. DateTimeZoneProviders.Tzdb.GetAllZones() + .Select(tz => new TimeZone() + { + Id = tz.Id, + DisplayName = tz.ToString() + })]; + } + public static bool IsEffectiveAndNotExpired(DateTime? effectiveDate, DateTime? expiryDate) { DateTime currentUtcTime = DateTime.UtcNow; From e9035df9d20022b9471fa085a518f67eaa36f852 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 14 Jul 2025 13:40:52 -0700 Subject: [PATCH 18/24] Update Package Dependencies --- Oqtane.Maui/Oqtane.Maui.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 0983aa45..8e683de5 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -72,9 +72,9 @@ - - - + + + From d2ff49fe73df0f0872c166938e324f74e98b20ad Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 14 Jul 2025 16:10:50 -0700 Subject: [PATCH 19/24] [FIX] #5164 - Set z-index for .dropdown-menu in .app-moduleactions --- Oqtane.Maui/wwwroot/css/app.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Oqtane.Maui/wwwroot/css/app.css b/Oqtane.Maui/wwwroot/css/app.css index 538e3a4c..fc0f07d0 100644 --- a/Oqtane.Maui/wwwroot/css/app.css +++ b/Oqtane.Maui/wwwroot/css/app.css @@ -75,6 +75,10 @@ app { color: gray; } +.app-moduleactions .dropdown-menu { + z-index: 9000; +} + .app-moduleactions .dropdown-submenu { position: relative; } @@ -270,4 +274,4 @@ app { .app-logo .navbar-brand { padding: 5px 20px 5px 20px; -} \ No newline at end of file +} From 5a24f872935f07316f479f593360cb7de6be5c6c Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 14 Jul 2025 17:07:39 -0700 Subject: [PATCH 20/24] =?UTF-8?q?[FIX]=20#5164=C2=A0=E2=80=91=20Set=20z?= =?UTF-8?q?=E2=80=91index=20for=20.dropdown=E2=80=91menu=20in=20.app?= =?UTF-8?q?=E2=80=91moduleactions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Oqtane.Server/wwwroot/css/app.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/wwwroot/css/app.css b/Oqtane.Server/wwwroot/css/app.css index 0b13a837..c7eec5f5 100644 --- a/Oqtane.Server/wwwroot/css/app.css +++ b/Oqtane.Server/wwwroot/css/app.css @@ -75,6 +75,10 @@ app { color: gray; } +.app-moduleactions .dropdown-menu { + z-index: 9000; +} + .app-moduleactions .dropdown-submenu { position: relative; } @@ -281,4 +285,4 @@ app { .gdpr-consent-bar .btn-hide{ top: 0; right: 5px; -} \ No newline at end of file +} From 9690f1df484f189c67795c117b7c27ea629b2ddc Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 14 Jul 2025 17:09:24 -0700 Subject: [PATCH 21/24] =?UTF-8?q?[FIX]=20oqtane#5164=20=E2=80=93=20Raise?= =?UTF-8?q?=20z=E2=80=91index=20for=20.app=E2=80=91moduleactions=20.dropdo?= =?UTF-8?q?wn=E2=80=91menu=20to=209999?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Oqtane.Server/wwwroot/css/app.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/wwwroot/css/app.css b/Oqtane.Server/wwwroot/css/app.css index c7eec5f5..00461f64 100644 --- a/Oqtane.Server/wwwroot/css/app.css +++ b/Oqtane.Server/wwwroot/css/app.css @@ -76,7 +76,7 @@ app { } .app-moduleactions .dropdown-menu { - z-index: 9000; + z-index: 9999; } .app-moduleactions .dropdown-submenu { From 948fab50ee944d240d178dc50055572ad1432f77 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 14 Jul 2025 17:10:07 -0700 Subject: [PATCH 22/24] =?UTF-8?q?[FIX]=20#5164=20=E2=80=93=20Raise=20z?= =?UTF-8?q?=E2=80=91index=20for=20.app=E2=80=91moduleactions=20.dropdown?= =?UTF-8?q?=E2=80=91menu=20to=209999?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Oqtane.Maui/wwwroot/css/app.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Maui/wwwroot/css/app.css b/Oqtane.Maui/wwwroot/css/app.css index fc0f07d0..ab9c6adb 100644 --- a/Oqtane.Maui/wwwroot/css/app.css +++ b/Oqtane.Maui/wwwroot/css/app.css @@ -76,7 +76,7 @@ app { } .app-moduleactions .dropdown-menu { - z-index: 9000; + z-index: 9999; } .app-moduleactions .dropdown-submenu { From 0be7f1bdb5c9f174ce9634cc647bcfcedf9778a2 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 21 Jul 2025 09:14:07 -0400 Subject: [PATCH 23/24] add new option to FileManager component to anonymize filenames during upload --- .../Modules/Controls/FileManager.razor | 5 ++- Oqtane.Client/UI/Interop.cs | 6 ++-- Oqtane.Server/Controllers/FileController.cs | 36 ++++++++++--------- Oqtane.Server/wwwroot/js/interop.js | 10 ++++-- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/FileManager.razor b/Oqtane.Client/Modules/Controls/FileManager.razor index 1e5fa1c5..9581c58a 100644 --- a/Oqtane.Client/Modules/Controls/FileManager.razor +++ b/Oqtane.Client/Modules/Controls/FileManager.razor @@ -157,6 +157,9 @@ [Parameter] public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false + [Parameter] + public bool AnonymizeUploadFilenames { get; set; } = false; // optional - indicate if file names should be anonymized on upload - default false + [Parameter] public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB @@ -408,7 +411,7 @@ } // upload files - var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token); + var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, AnonymizeUploadFilenames, tokenSource.Token); // reset progress indicators if (ShowProgress) diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index 8d547da7..3a56783f 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -224,17 +224,17 @@ namespace Oqtane.UI public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt) { - UploadFiles(posturl, folder, id, antiforgerytoken, jwt, 1); + UploadFiles(posturl, folder, id, antiforgerytoken, jwt, 1, false); return Task.CompletedTask; } - public ValueTask UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt, int chunksize, CancellationToken cancellationToken = default) + public ValueTask UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt, int chunksize, bool anonymizeuploadfilenames, CancellationToken cancellationToken = default) { try { return _jsRuntime.InvokeAsync( "Oqtane.Interop.uploadFiles", cancellationToken, - posturl, folder, id, antiforgerytoken, jwt, chunksize); + posturl, folder, id, antiforgerytoken, jwt, chunksize, anonymizeuploadfilenames); } catch { diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index f0b72f22..3db7ecae 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -444,9 +444,14 @@ namespace Oqtane.Controllers } // ensure filename is valid - if (!formfile.FileName.IsPathOrFileValid() || !HasValidFileExtension(formfile.FileName)) + string fileName = formfile.FileName; + if (Path.GetExtension(fileName).Contains(':')) { - _logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName); + fileName = fileName.Substring(0, fileName.LastIndexOf(':')); // remove invalid suffix from extension + } + if (!fileName.IsPathOrFileValid() || !HasValidFileExtension(fileName)) + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload File Name Is Invalid Or Contains Invalid Extension {File}", fileName); return StatusCode((int)HttpStatusCode.Forbidden); } @@ -458,8 +463,8 @@ namespace Oqtane.Controllers return StatusCode((int)HttpStatusCode.Forbidden); } - // create file name using header values - string fileName = formfile.FileName + ".part_" + partCount.ToString("000") + "_" + totalParts.ToString("000"); + // create file name using header part values + fileName += ".part_" + partCount.ToString("000") + "_" + totalParts.ToString("000"); string folderPath = ""; try @@ -532,13 +537,13 @@ namespace Oqtane.Controllers string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "001_999" int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1)); - filename = Path.GetFileNameWithoutExtension(filename); // base filename + filename = Path.GetFileNameWithoutExtension(filename); // base filename including original file extension string[] fileparts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts // if all of the file parts exist (note that file parts can arrive out of order) if (fileparts.Length == totalparts && CanAccessFiles(fileparts)) { - // merge file parts into temp file (in case another user is trying to get the file) + // merge file parts into temp file (in case another user is trying to read the file) bool success = true; using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create)) { @@ -559,25 +564,22 @@ namespace Oqtane.Controllers } // clean up file parts - foreach (var file in Directory.GetFiles(folder, "*" + token + "*")) + foreach (var file in fileparts) { - if (fileparts.Contains(file)) + try { - try - { - System.IO.File.Delete(file); - } - catch - { - // unable to delete part - ignore - } + System.IO.File.Delete(file); + } + catch + { + // unable to delete part - ignore } } // rename temp file if (success) { - // remove file if it already exists (as well as any thumbnails which may exist) + // remove existing file (as well as any thumbnails) foreach (var file in Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filename) + ".*")) { if (Path.GetExtension(file) != ".tmp") diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 191d9823..fecc4c99 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -311,7 +311,7 @@ Oqtane.Interop = { } return files; }, - uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize) { + uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize, anonymizeuploadfilenames) { var success = true; var fileinput = document.getElementById('FileInput_' + id); var progressinfo = document.getElementById('ProgressInfo_' + id); @@ -344,16 +344,22 @@ Oqtane.Interop = { const totalParts = Math.ceil(file.size / chunkSize); let partCount = 0; + let filename = file.name; + if (anonymizeuploadfilenames) { + filename = crypto.randomUUID() + '.' + filename.split('.').pop(); + } + const uploadPart = () => { const start = partCount * chunkSize; const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); return new Promise((resolve, reject) => { + let formdata = new FormData(); formdata.append('__RequestVerificationToken', antiforgerytoken); formdata.append('folder', folder); - formdata.append('formfile', chunk, file.name); + formdata.append('formfile', chunk, filename); var credentials = 'same-origin'; var headers = new Headers(); From a981dd0e978622ac12c1747071e0ec2786c83082 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 21 Jul 2025 16:34:34 -0400 Subject: [PATCH 24/24] fix Control Panel to initialize extended module permissions when module is added or copied --- .../Theme/ControlPanelInteractive.razor | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index 5dd940f8..a42a1f12 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -353,7 +353,7 @@ module.PageId = PageState.Page.PageId; module.ModuleDefinitionName = _moduleDefinitionName; module.AllPages = false; - module.PermissionList = GenerateDefaultPermissions(module.SiteId); + module.PermissionList = GenerateDefaultPermissions(module.SiteId, module.ModuleDefinitionName); module = await ModuleService.AddModuleAsync(module); newModuleId = module.ModuleId; @@ -365,7 +365,7 @@ module.SiteId = PageState.Page.SiteId; module.PageId = PageState.Page.PageId; module.AllPages = false; - module.PermissionList = GenerateDefaultPermissions(module.SiteId); + module.PermissionList = GenerateDefaultPermissions(module.SiteId, module.ModuleDefinitionName); module = await ModuleService.AddModuleAsync(module); var moduleContent = await ModuleService.ExportModuleAsync(int.Parse(_moduleId), PageState.Page.PageId); @@ -430,7 +430,7 @@ } } - private List GenerateDefaultPermissions(int siteId) + private List GenerateDefaultPermissions(int siteId, string moduleDefinitionName) { var permissions = new List(); if (_visibility == "view") @@ -443,8 +443,22 @@ // set module view permissions to page edit permissions permissions = SetPermissions(permissions, siteId, PermissionNames.View, PermissionNames.Edit); } - // set module edit permissions to page edit permissions - permissions = SetPermissions(permissions, siteId, PermissionNames.Edit, PermissionNames.Edit); + + // get module permissions + var permissionNames = $"{PermissionNames.View},{PermissionNames.Edit}"; + var moduleDefinition = _allModuleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == moduleDefinitionName); + if (moduleDefinition != null && !string.IsNullOrEmpty(moduleDefinition.PermissionNames)) + { + permissionNames = moduleDefinition.PermissionNames; + } + foreach (var permission in permissionNames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (permission != PermissionNames.View) + { + // set remaining module permissions to page edit permissions + permissions = SetPermissions(permissions, siteId, permission, PermissionNames.Edit); + } + } return permissions; }