From fde53a2d8308e3c57ff8db6146b8e1ca74a33603 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 23 Sep 2024 16:33:41 -0400 Subject: [PATCH 01/74] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 088f1c71..72d6b86b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[5.2.2](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2) was released on September 23, 2024 and is a maintenance release including 55 pull requests by 8 different contributors, pushing the total number of project commits all-time to over 5800. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[5.2.3](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.3) was released on September 23, 2024 and is a maintenance release including 55 pull requests by 8 different contributors, pushing the total number of project commits all-time to over 5800. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json) @@ -83,6 +83,9 @@ Backlog (TBD) - [ ] Folder Providers - [ ] Generative AI Integration +[5.2.3](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.3) (Sep 23, 2024) +- [x] Stabilization improvements + [5.2.2](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.2) (Sep 23, 2024) - [x] Stabilization improvements - [x] Support for Security Stamp to faciliate Logout Everywhere From 4511acf273234bd3bace684b89fdd334c573de5b Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 25 Sep 2024 17:05:53 -0400 Subject: [PATCH 02/74] fix #4666 - scroll position in enhanced navigation --- Oqtane.Server/Components/App.razor | 2 +- Oqtane.Server/Controllers/NotificationController.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index c1684977..658ecfce 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -522,7 +522,7 @@ " let currentUrl = window.location.pathname;" + Environment.NewLine + " Blazor.addEventListener('enhancedload', () => {" + Environment.NewLine + " let newUrl = window.location.pathname;" + Environment.NewLine + - " if (currentUrl !== newUrl || window.location.hash === '') {" + Environment.NewLine + + " if (currentUrl !== newUrl || window.location.hash === '#top') {" + Environment.NewLine + " window.scrollTo({ top: 0, left: 0, behavior: 'instant' });" + Environment.NewLine + " }" + Environment.NewLine + " currentUrl = newUrl;" + Environment.NewLine + diff --git a/Oqtane.Server/Controllers/NotificationController.cs b/Oqtane.Server/Controllers/NotificationController.cs index 44bc7a93..5f7ee353 100644 --- a/Oqtane.Server/Controllers/NotificationController.cs +++ b/Oqtane.Server/Controllers/NotificationController.cs @@ -8,10 +8,6 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; using System.Net; -using System.Reflection.Metadata; -using Microsoft.Extensions.Localization; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using System.Linq; namespace Oqtane.Controllers { From 28b6b03d06b3655362dc1fc7cda682d950ce022a Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 25 Sep 2024 15:13:06 -0700 Subject: [PATCH 03/74] Remove unnecessary usings --- Oqtane.Server/Databases/Interfaces/IMultiDatabase.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Oqtane.Server/Databases/Interfaces/IMultiDatabase.cs b/Oqtane.Server/Databases/Interfaces/IMultiDatabase.cs index 01d926f5..033678d1 100644 --- a/Oqtane.Server/Databases/Interfaces/IMultiDatabase.cs +++ b/Oqtane.Server/Databases/Interfaces/IMultiDatabase.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; using Oqtane.Databases.Interfaces; -using Oqtane.Interfaces; namespace Oqtane.Repository.Databases.Interfaces { From e1ada78c1f16bc68db67263d2f00e3d529041144 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 26 Sep 2024 13:33:16 -0400 Subject: [PATCH 04/74] fix #4667 - installation issues when running on IIS --- .../Infrastructure/DatabaseManager.cs | 41 +++++++++---------- Oqtane.Server/Repository/UserRepository.cs | 1 + .../Repository/UserRoleRepository.cs | 25 ++++++++--- Oqtane.Shared/Models/UserRole.cs | 8 ++++ 4 files changed, 48 insertions(+), 27 deletions(-) diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 3d9be5de..71126d11 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -375,7 +375,6 @@ namespace Oqtane.Infrastructure AddEFMigrationsHistory(sql, _configManager.GetSetting($"{SettingKeys.ConnectionStringsSection}:{tenant.DBConnectionString}", ""), tenant.DBType, tenant.Version, false); // push latest model into database tenantDbContext.Database.Migrate(); - result.Success = true; } } catch (Exception ex) @@ -384,35 +383,35 @@ namespace Oqtane.Infrastructure _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } - // execute any version specific upgrade logic - var version = tenant.Version; - var index = Array.FindIndex(versions, item => item == version); - if (index != (versions.Length - 1)) + if (string.IsNullOrEmpty(result.Message)) { - try + // execute any version specific upgrade logic + var version = tenant.Version; + var index = Array.FindIndex(versions, item => item == version); + if (index != (versions.Length - 1)) { - for (var i = (index + 1); i < versions.Length; i++) + try { - upgrades.Upgrade(tenant, versions[i]); + for (var i = (index + 1); i < versions.Length; i++) + { + upgrades.Upgrade(tenant, versions[i]); + } + tenant.Version = versions[versions.Length - 1]; + db.Entry(tenant).State = EntityState.Modified; + db.SaveChanges(); + } + catch (Exception ex) + { + result.Message = "An Error Occurred Executing Upgrade Logic On Tenant " + tenant.Name + ". " + ex.ToString(); + _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } - tenant.Version = versions[versions.Length - 1]; - db.Entry(tenant).State = EntityState.Modified; - db.SaveChanges(); - } - catch (Exception ex) - { - result.Message = "An Error Occurred Executing Upgrade Logic On Tenant " + tenant.Name + ". " + ex.ToString(); - _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } } } } } - if (string.IsNullOrEmpty(result.Message)) - { - result.Success = true; - } + result.Success = string.IsNullOrEmpty(result.Message); return result; } @@ -588,7 +587,7 @@ namespace Oqtane.Infrastructure // add host role var hostRoleId = roles.GetRoles(user.SiteId, true).FirstOrDefault(item => item.Name == RoleNames.Host)?.RoleId ?? 0; - var userRole = new UserRole { UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null }; + var userRole = new UserRole { UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null, IgnoreSecurityStamp = true }; userRoles.AddUserRole(userRole); } } diff --git a/Oqtane.Server/Repository/UserRepository.cs b/Oqtane.Server/Repository/UserRepository.cs index ffbf7412..3c0a40ad 100644 --- a/Oqtane.Server/Repository/UserRepository.cs +++ b/Oqtane.Server/Repository/UserRepository.cs @@ -75,6 +75,7 @@ namespace Oqtane.Repository userrole.RoleId = role.RoleId; userrole.EffectiveDate = null; userrole.ExpiryDate = null; + userrole.IgnoreSecurityStamp = true; _userroles.AddUserRole(userrole); } diff --git a/Oqtane.Server/Repository/UserRoleRepository.cs b/Oqtane.Server/Repository/UserRoleRepository.cs index c438bdb4..8af62274 100644 --- a/Oqtane.Server/Repository/UserRoleRepository.cs +++ b/Oqtane.Server/Repository/UserRoleRepository.cs @@ -72,8 +72,13 @@ namespace Oqtane.Repository DeleteUserRoles(userRole.UserId); } - UpdateSecurityStamp(userRole.UserId); - + if (!userRole.IgnoreSecurityStamp) + { + UpdateSecurityStamp(userRole.UserId); + } + + RefreshCache(userRole.UserId); + return userRole; } @@ -83,7 +88,12 @@ namespace Oqtane.Repository db.Entry(userRole).State = EntityState.Modified; db.SaveChanges(); - UpdateSecurityStamp(userRole.UserId); + if (!userRole.IgnoreSecurityStamp) + { + UpdateSecurityStamp(userRole.UserId); + } + + RefreshCache(userRole.UserId); return userRole; } @@ -144,6 +154,7 @@ namespace Oqtane.Repository db.SaveChanges(); UpdateSecurityStamp(userRole.UserId); + RefreshCache(userRole.UserId); } public void DeleteUserRoles(int userId) @@ -156,11 +167,11 @@ namespace Oqtane.Repository db.SaveChanges(); UpdateSecurityStamp(userId); + RefreshCache(userId); } private void UpdateSecurityStamp(int userId) { - // update user security stamp using var db = _dbContextFactory.CreateDbContext(); var user = db.User.Find(userId); if (user != null) @@ -168,11 +179,13 @@ namespace Oqtane.Repository var identityuser = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult(); if (identityuser != null) { - _identityUserManager.UpdateSecurityStampAsync(identityuser); + _identityUserManager.UpdateSecurityStampAsync(identityuser).GetAwaiter().GetResult(); } } + } - // refresh cache + private void RefreshCache(int userId) + { var alias = _tenantManager.GetAlias(); if (alias != null) { diff --git a/Oqtane.Shared/Models/UserRole.cs b/Oqtane.Shared/Models/UserRole.cs index b3597ae2..2c891126 100644 --- a/Oqtane.Shared/Models/UserRole.cs +++ b/Oqtane.Shared/Models/UserRole.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; namespace Oqtane.Models { @@ -26,11 +27,18 @@ namespace Oqtane.Models /// Start of when this assignment is valid. See also /// public DateTime? EffectiveDate { get; set; } + /// /// End of when this assignment is valid. See also /// public DateTime? ExpiryDate { get; set; } + /// + /// Indicates that the User Security Stamp should not be updated when this user role is added or updated + /// + [NotMapped] + public bool IgnoreSecurityStamp { get; set; } + /// /// Direct reference to the object. /// TODO: todoc - is this always populated? From 3e50deecb7694dfefd520c9b1d97fcd059c9b861 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 26 Sep 2024 13:37:39 -0400 Subject: [PATCH 05/74] fix remote login issue which could occut if multiple users have the same email address --- .../Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index 1729358b..aa593f83 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -592,7 +592,7 @@ namespace Oqtane.Extensions } // create claims identity - identityuser = await _identityUserManager.FindByEmailAsync(user.Username); + identityuser = await _identityUserManager.FindByNameAsync(user.Username); user.SecurityStamp = identityuser.SecurityStamp; identity = UserSecurity.CreateClaimsIdentity(alias, user, userRoles); identity.Label = ExternalLoginStatus.Success; @@ -645,7 +645,7 @@ namespace Oqtane.Extensions } } - _logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress, providerName); + _logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName); } } else // claims invalid From 7f4087e3de49cecf149fa8202d989c8b42c9a55b Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 26 Sep 2024 13:46:29 -0400 Subject: [PATCH 06/74] fix localization spelling mistake --- .../Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index aa593f83..cb0303e6 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -651,7 +651,7 @@ namespace Oqtane.Extensions else // claims invalid { identity.Label = ExternalLoginStatus.MissingClaims; - _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return All Of The Claims Types Specified Or Email Address Does Not Saitisfy Domain Filter. The Actual Claims Returned Were {Claims}. Login Was Denied.", claims); + _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return All Of The Claims Types Specified Or Email Address Does Not Satisfy Domain Filter. The Actual Claims Returned Were {Claims}. Login Was Denied.", claims); } return identity; From d468e675c2617afab598621b20636ae1c6c05376 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 26 Sep 2024 14:06:51 -0400 Subject: [PATCH 07/74] fix #4657 - Cannot add new site to existing installation using separate database On IIS --- Oqtane.Server/Infrastructure/DatabaseManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 71126d11..30593b3b 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -155,7 +155,7 @@ namespace Oqtane.Infrastructure // add new site if (install.TenantName != TenantNames.Master && install.ConnectionString.Contains("=")) { - _configManager.AddOrUpdateSetting($"{SettingKeys.ConnectionStringsSection}:{install.TenantName}", install.ConnectionString, false); + _configManager.AddOrUpdateSetting($"{SettingKeys.ConnectionStringsSection}:{install.TenantName}", install.ConnectionString, true); } if (install.TenantName == TenantNames.Master && !install.ConnectionString.Contains("=")) { From df71dd14f7d651da1a456b09902bb19558f82bac Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 26 Sep 2024 15:53:14 -0400 Subject: [PATCH 08/74] sign out the principal when it is rejected due to security stamp changes --- .../Extensions/OqtaneServiceCollectionExtensions.cs | 1 + Oqtane.Server/Security/PrincipalValidator.cs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index d9b31fd9..274a0aab 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -169,6 +169,7 @@ namespace Microsoft.Extensions.DependencyInjection options.Cookie.HttpOnly = true; options.Cookie.SameSite = SameSiteMode.Lax; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + options.LoginPath = "/login"; // overrides .NET Identity default of /Account/Login options.Events.OnRedirectToLogin = context => { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; diff --git a/Oqtane.Server/Security/PrincipalValidator.cs b/Oqtane.Server/Security/PrincipalValidator.cs index dc6d7256..710ac970 100644 --- a/Oqtane.Server/Security/PrincipalValidator.cs +++ b/Oqtane.Server/Security/PrincipalValidator.cs @@ -7,13 +7,15 @@ using Oqtane.Models; using Oqtane.Extensions; using Oqtane.Shared; using Oqtane.Managers; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authentication; namespace Oqtane.Security { public static class PrincipalValidator { - public static Task ValidateAsync(CookieValidatePrincipalContext context) + public static async Task ValidateAsync(CookieValidatePrincipalContext context) { if (context != null && context.Principal.Identity.IsAuthenticated && context.Principal.Identity.Name != null) { @@ -49,6 +51,7 @@ namespace Oqtane.Security // remove principal (ie. log user out) Log(_logger, alias, "Permissions Removed For User {Username} Accessing {Url}", context.Principal.Identity.Name, path); context.RejectPrincipal(); + await context.HttpContext.SignOutAsync(Constants.AuthenticationScheme); } } else @@ -58,7 +61,6 @@ namespace Oqtane.Security } } } - return Task.CompletedTask; } private static void Log (ILogManager logger, Alias alias, string message, string username, string path) From 39c79ea68e9af8ba6433ed6ac3eb9542e2ed0360 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 26 Sep 2024 15:54:22 -0400 Subject: [PATCH 09/74] remove unnecessary using statement --- Oqtane.Server/Security/PrincipalValidator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Oqtane.Server/Security/PrincipalValidator.cs b/Oqtane.Server/Security/PrincipalValidator.cs index 710ac970..45e99c34 100644 --- a/Oqtane.Server/Security/PrincipalValidator.cs +++ b/Oqtane.Server/Security/PrincipalValidator.cs @@ -7,10 +7,8 @@ using Oqtane.Models; using Oqtane.Extensions; using Oqtane.Shared; using Oqtane.Managers; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authentication; - namespace Oqtane.Security { public static class PrincipalValidator From ea2846973ac2e7460f4c0fcafe8f53152e1deaf0 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 27 Sep 2024 09:00:04 -0400 Subject: [PATCH 10/74] add disclaimer to System Update feature --- Oqtane.Client/Modules/Admin/Upgrade/Index.razor | 2 ++ Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor index 2c91688e..f018fbb5 100644 --- a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor +++ b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor @@ -54,6 +54,8 @@ } else { + AddModuleMessage(Localizer["Disclaimer.Text"], MessageType.Warning); + List packages = await PackageService.GetPackagesAsync("framework", "", "", ""); if (packages != null) { diff --git a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx index 40d8af4c..e9a376e1 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx @@ -150,4 +150,7 @@ You Cannot Perform A System Update In A Development Environment + + Please Note That The System Update Capability Is A Simplified Upgrade Process Intended For Small To Medium Sized Installations. For Larger Enterprise Installations You Will Want To Use A Manual Upgrade Process. Also Note That The System Update Capability Is Not Recommended When Using Microsoft Azure Due To The Limitations Of That Environment. + \ No newline at end of file From b98535810b177cc13f4668a3319e041800512491 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 27 Sep 2024 12:00:49 -0400 Subject: [PATCH 11/74] fix #4654 - show progress indicator during download --- Oqtane.Client/Modules/Admin/Upgrade/Index.razor | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor index f018fbb5..c43ae566 100644 --- a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor +++ b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor @@ -99,13 +99,16 @@ { try { + ShowProgressIndicator(); await PackageService.DownloadPackageAsync(packageid, version); await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version); + HideProgressIndicator(); AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success); } catch (Exception ex) { await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message); + HideProgressIndicator(); AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error); } } From be0754f5684bfea464c5a110c675dfecd6189778 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 27 Sep 2024 16:21:06 -0400 Subject: [PATCH 12/74] simplify configuration of external login providers --- Oqtane.Client/Modules/Admin/Users/Index.razor | 251 ++++++++++-------- .../Resources/Modules/Admin/Users/Index.resx | 13 +- Oqtane.Shared/Models/ExternalLoginProvider.cs | 11 + .../Shared/ExternalLoginProviders.cs | 56 ++++ 4 files changed, 223 insertions(+), 108 deletions(-) create mode 100644 Oqtane.Shared/Models/ExternalLoginProvider.cs create mode 100644 Oqtane.Shared/Shared/ExternalLoginProviders.cs diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index fa493c36..8892c6b3 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -182,11 +182,29 @@ else
-
+
+ +
+
+ + @if (!string.IsNullOrEmpty(_providerurl)) + { + @Localizer["Info"] + } +
+ +
+
+
@@ -452,6 +470,8 @@ else private string _maximumfailures; private string _lockoutduration; + private string _provider; + private string _providerurl; private string _providertype; private string _providername; private string _authority; @@ -519,33 +539,7 @@ else _maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5"); _lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString(); - _providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", ""); - _providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", ""); - _authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", ""); - _metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", ""); - _authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", ""); - _tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", ""); - _userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", ""); - _clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", ""); - _clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", ""); - _toggleclientsecret = SharedLocalizer["ShowPassword"]; - _authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code"); - _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); - _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); - _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); - _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; - _reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false"); - _externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external"); - _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub"); - _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"); - _verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true"); + LoadExternalLoginSettings(settings); _secret = SettingService.GetSetting(settings, "JwtOptions:Secret", ""); _togglesecret = SharedLocalizer["ShowPassword"]; @@ -555,6 +549,39 @@ else } } + private void LoadExternalLoginSettings(Dictionary settings) + { + _provider = SettingService.GetSetting(settings, "ExternalLogin:Provider", "Custom"); + _providerurl = SettingService.GetSetting(settings, "ExternalLogin:ProviderUrl", ""); + _providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", ""); + _providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", ""); + _authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", ""); + _metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", ""); + _authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", ""); + _tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", ""); + _userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", ""); + _clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", ""); + _clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", ""); + _toggleclientsecret = SharedLocalizer["ShowPassword"]; + _authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code"); + _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); + _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); + _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); + _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; + _reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false"); + _externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external"); + _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub"); + _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"); + _verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true"); + } + private async Task LoadUsersAsync(bool load) { if (load) @@ -567,105 +594,117 @@ else users = users.OrderBy(u => u.User.DisplayName).ToList(); } } - } + } - private async Task DeleteUser(UserRole UserRole) - { - try - { - var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId); - if (user != null) - { - await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); - await logger.LogInformation("User Deleted {User}", UserRole.User); - await LoadUsersAsync(true); - StateHasChanged(); - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message); - AddModuleMessage(ex.Message, MessageType.Error); - } - } + private async Task DeleteUser(UserRole UserRole) + { + try + { + var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId); + if (user != null) + { + await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); + await logger.LogInformation("User Deleted {User}", UserRole.User); + await LoadUsersAsync(true); + StateHasChanged(); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message); + AddModuleMessage(ex.Message, MessageType.Error); + } + } - private async Task SaveSiteSettings() - { - try - { - var site = PageState.Site; - site.AllowRegistration = bool.Parse(_allowregistration); - await SiteService.UpdateSiteAsync(site); + private async Task SaveSiteSettings() + { + try + { + var site = PageState.Site; + site.AllowRegistration = bool.Parse(_allowregistration); + await SiteService.UpdateSiteAsync(site); - var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); - settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false); + var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); + settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false); - if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) - { - settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); - settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); - settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true); - settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false); + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + { + settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); + settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); + settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true); + settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false); - settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true); - settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true); - settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true); - settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true); - settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true); - settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true); + settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true); + settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true); + settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true); + settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true); + settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true); + settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true); - settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true); - settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true); + settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true); + settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true); - settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false); - settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false); - settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true); - settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true); - settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true); - settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true); - settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true); - settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true); - settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:Provider", _provider, false); + settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false); + settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false); + settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true); settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true); - settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); - settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); settings = SettingService.SetSetting(settings, "ExternalLogin:ReviewClaims", _reviewclaims, true); settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true); 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:RoleClaimType", _roleclaimtype, 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:DomainFilter", _domainfilter, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true); - settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true); - settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true); - settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true); - settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true); - } + settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true); + settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true); + settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true); + settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true); + } - await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); - await SettingService.ClearSiteSettingsCacheAsync(); + await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); + await SettingService.ClearSiteSettingsCacheAsync(); - if (!string.IsNullOrEmpty(_secret)) - { - SiteState.AuthorizationToken = await UserService.GetTokenAsync(); - } + if (!string.IsNullOrEmpty(_secret)) + { + SiteState.AuthorizationToken = await UserService.GetTokenAsync(); + } - AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message); - AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); - } + AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message); + AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); + } + } + + private void ProviderChanged(ChangeEventArgs e) + { + _provider = (string)e.Value; + var provider = Shared.ExternalLoginProviders.Providers.FirstOrDefault(item => item.Name == _provider); + if (provider != null) + { + LoadExternalLoginSettings(provider.Settings); + } + StateHasChanged(); } - - private void ProviderTypeChanged(ChangeEventArgs e) + + private void ProviderTypeChanged(ChangeEventArgs e) { _providertype = (string)e.Value; if (string.IsNullOrEmpty(_providername)) diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index 46c19999..d168b641 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -471,13 +471,22 @@ Review Claims? - + This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled. Optionally specify the type name of the user's name claim provided by the identity provider. The typical value is 'name'. - + Name Claim: + + Select the external login provider + + + Provider: + + + Info + \ No newline at end of file diff --git a/Oqtane.Shared/Models/ExternalLoginProvider.cs b/Oqtane.Shared/Models/ExternalLoginProvider.cs new file mode 100644 index 00000000..8cda01d4 --- /dev/null +++ b/Oqtane.Shared/Models/ExternalLoginProvider.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Oqtane.Models +{ + public class ExternalLoginProvider + { + public string Name { get; set; } + + public Dictionary Settings { get; set; } + } +} diff --git a/Oqtane.Shared/Shared/ExternalLoginProviders.cs b/Oqtane.Shared/Shared/ExternalLoginProviders.cs new file mode 100644 index 00000000..643c4ea4 --- /dev/null +++ b/Oqtane.Shared/Shared/ExternalLoginProviders.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Shared +{ + public class ExternalLoginProviders + { + public static List Providers + { + get + { + var providers = new List + { + new ExternalLoginProvider + { + Name = "Custom", + Settings = new Dictionary() + }, + new ExternalLoginProvider + { + Name = "Microsoft Entra", + Settings = new Dictionary() + { + { "ExternalLogin:ProviderUrl", "https://entra.microsoft.com" }, + { "ExternalLogin:ProviderType", "oidc" }, + { "ExternalLogin:ProviderName", "Microsoft Entra" }, + { "ExternalLogin:Authority", "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0" }, + { "ExternalLogin:ClientId", "YOUR CLIENT ID" }, + { "ExternalLogin:ClientSecret", "YOUR CLIENT SECRET" } + } + }, + new ExternalLoginProvider + { + Name = "GitHub", + Settings = new Dictionary() + { + { "ExternalLogin:ProviderUrl", "https://github.com/settings/developers#oauth-apps" }, + { "ExternalLogin:ProviderType", "oauth2" }, + { "ExternalLogin:ProviderName", "GitHub" }, + { "ExternalLogin:AuthorizationUrl", "https://github.com/login/oauth/authorize" }, + { "ExternalLogin:TokenUrl", "https://github.com/login/oauth/access_token" }, + { "ExternalLogin:UserInfoUrl", "https://api.github.com/user/emails" }, + { "ExternalLogin:ClientId", "YOUR CLIENT ID" }, + { "ExternalLogin:ClientSecret", "YOUR CLIENT SECRET" }, + { "ExternalLogin:Scopes", "user:email" }, + { "ExternalLogin:IdentifierClaimType", "email" }, + { "ExternalLogin:DomainFilter", "!users.noreply.github.com" } + } + } + }; + + return providers; + } + } + } +} From 947aa08c42adc33f45bd9fda5fe58608b86199f7 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 30 Sep 2024 11:27:47 -0400 Subject: [PATCH 13/74] add validation of recipient email address to Notification job --- Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index b47c5732..ab9cc058 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -89,9 +89,9 @@ namespace Oqtane.Infrastructure } // validate recipient - if (string.IsNullOrEmpty(notification.ToEmail)) + if (string.IsNullOrEmpty(notification.ToEmail) || !MailAddress.TryCreate(notification.ToEmail, out _)) { - log += "Recipient Missing For NotificationId: " + notification.NotificationId + "
"; + log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}
"; notification.IsDeleted = true; notificationRepository.UpdateNotification(notification); } From 341ca5a330d04b3cf52f2daedf04cfe2351165ab Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 30 Sep 2024 13:31:57 -0400 Subject: [PATCH 14/74] add some clarity to the database fields help text --- Oqtane.Client/Modules/Admin/Site/Index.razor | 4 ++-- Oqtane.Client/Modules/Admin/Sites/Add.razor | 2 +- Oqtane.Client/Resources/Modules/Admin/Site/Index.resx | 4 ++-- Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 405adea4..c906b19c 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -376,7 +376,7 @@
- +
@@ -388,7 +388,7 @@
- +
diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index 300aafcb..47f19b04 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -109,7 +109,7 @@ else
- +
diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index b232fe61..1f3bf2c6 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 - The name of the database used for the site + The name of the database used for the site. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database. The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder). @@ -307,7 +307,7 @@ Type: - The connection information for the database + The name of the connection string in appsettings.json which will be used to connect to the database The type of database diff --git a/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx index e0390b08..d32522e9 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx @@ -187,7 +187,7 @@ Select the database for the site - Enter the name for the database + Enter the name for the database. Note that this will be the tenant name which is used within the framework to identify the database. Select the database type From 9dede84d2074e78dddbffdd90e2146fccdce3c25 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 05:57:17 -0700 Subject: [PATCH 15/74] Update Dependencies and Prepare Release 5.2.4 --- Oqtane.Maui/Oqtane.Maui.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 0784cc2f..be7a5b0f 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -6,7 +6,7 @@ Exe - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -31,7 +31,7 @@ 0E29FC31-1B83-48ED-B6E0-9F3C67B775D4 - 5.2.3 + 5.2.4 1 14.2 @@ -71,9 +71,9 @@ - - - + + + From 3e3c973679ceac04e7ed092cf019766dc77efc7e Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 05:59:47 -0700 Subject: [PATCH 16/74] Prepare Release 5.2.4 --- Oqtane.Updater/Oqtane.Updater.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 5b50daf5..85f39cdb 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -3,7 +3,7 @@ net8.0 Exe - 5.2.3 + 5.2.4 Oqtane Shaun Walker .NET Foundation From 1a61a58d28752f02add10301399ec3d9c2a4a387 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:00:20 -0700 Subject: [PATCH 17/74] Prepare Release 5.2.4 --- Oqtane.Updater/Oqtane.Updater.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 85f39cdb..aab4be52 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 https://github.com/oqtane/oqtane.framework Git Oqtane From 1ad79874c839d56e731570ba1f9c5690212fcd22 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:02:40 -0700 Subject: [PATCH 18/74] Update Dependencies and Prepare Release 5.2.4 --- .../Oqtane.Database.PostgreSQL.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index aebd561c..789a6611 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -2,7 +2,7 @@ net8.0 - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 https://github.com/oqtane/oqtane.framework Git true @@ -35,7 +35,7 @@ - + From 14a463382b4388a51daef4837943666ed7a1726b Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:03:20 -0700 Subject: [PATCH 19/74] Prepare Release 5.2.4 --- Oqtane.Client/Oqtane.Client.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index ff8b2a1d..034ec87b 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -4,7 +4,7 @@ net8.0 Exe Debug;Release - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 https://github.com/oqtane/oqtane.framework Git Oqtane From ef33bdb65e3d6ef4b9988f99d031fa03507ee4f1 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:04:11 -0700 Subject: [PATCH 20/74] Prepare Release 5.2.4 --- Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj index da0d7064..f50eee92 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -2,7 +2,7 @@ net8.0 - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 https://github.com/oqtane/oqtane.framework Git true From 5caa1fe7d4182a752ea1a3f2c610a0554447ef44 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:05:07 -0700 Subject: [PATCH 21/74] Prepare Release 5.2.4 --- Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index c6bc4b37..61124187 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -2,7 +2,7 @@ net8.0 - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 https://github.com/oqtane/oqtane.framework Git true From 6a98999105d0df6208e466d2ee4e63de89aa4f12 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:06:03 -0700 Subject: [PATCH 22/74] Prepare Release 5.2.4 --- Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index 14a44514..2a41a397 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -2,7 +2,7 @@ net8.0 - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 https://github.com/oqtane/oqtane.framework Git true From d4239fe7e0626e4043457d4dfecb947d3c2c53a8 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:07:55 -0700 Subject: [PATCH 23/74] Prepare Release 5.2.4 --- Oqtane.Package/Oqtane.Client.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index e9e2d38f..79a8088a 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 readme.md icon.png oqtane From d441b31dc72cc5f2ec671dcdbbc60cf1ef96e657 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:08:46 -0700 Subject: [PATCH 24/74] Prepare Release 5.2.4 --- Oqtane.Package/Oqtane.Framework.nuspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index 196bf08e..5ddceeef 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 5.2.3 + 5.2.4 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v5.2.3/Oqtane.Framework.5.2.3.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/download/v5.2.4/Oqtane.Framework.5.2.4.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 readme.md icon.png oqtane framework From fb5a2ce178fa2f97fef15d00cc17c6111c5a7018 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:09:12 -0700 Subject: [PATCH 25/74] Prepare Release 5.2.4 --- Oqtane.Package/Oqtane.Server.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 3ff7011d..d43e9a3d 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 readme.md icon.png oqtane From a9882cc96a09a633da545993552708fcb9536c54 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:09:35 -0700 Subject: [PATCH 26/74] Update Oqtane.Shared.nuspec --- Oqtane.Package/Oqtane.Shared.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index b0a2538e..70d25b27 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 readme.md icon.png oqtane From eb6dc80b5056e5eb284664f70e1c0d9b61394302 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:10:21 -0700 Subject: [PATCH 27/74] Prepare Release 5.2.4 - removed whitespace --- Oqtane.Package/Oqtane.Shared.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index 70d25b27..b9f466f8 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -18,8 +18,8 @@ oqtane - - + + From 660e164ff8808b9667f3c58c9e8f072b77e2f60b Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:11:04 -0700 Subject: [PATCH 28/74] Prepare Release 5.2.4 --- Oqtane.Package/Oqtane.Updater.nuspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 1b6eddd3..8e3cc368 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 5.2.3 + 5.2.4 Shaun Walker .NET Foundation Oqtane Framework @@ -12,13 +12,13 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 readme.md icon.png oqtane - + From b2ad1010ac61d44846d9e9a1002a124190e97f35 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:11:36 -0700 Subject: [PATCH 29/74] Prepare Release 5.2.4 --- Oqtane.Package/install.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 7ca6ed4a..23cd8498 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.3.Install.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.4.Install.zip" -Force From f7de4c567b3e88486426180d6ad030776d3d76e6 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:12:07 -0700 Subject: [PATCH 30/74] Prepare Release 5.2.4 --- Oqtane.Package/upgrade.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index 1d909b81..19cc36ec 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.3.Upgrade.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.4.Upgrade.zip" -Force From 004ff1e91d9d3ea32a48ba3710eb276b246d9f6f Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:14:07 -0700 Subject: [PATCH 31/74] Update Dependencies and Prepare Release 5.2.4 --- Oqtane.Server/Oqtane.Server.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 84a04860..7530c9c0 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net8.0 Debug;Release - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -33,10 +33,10 @@ - + - + all @@ -44,10 +44,10 @@ - + - + From 6c5a1dc2e152d96352b9a506cb31bbdd2d6e2069 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:14:57 -0700 Subject: [PATCH 32/74] Prepare Release 5.2.4 --- Oqtane.Shared/Oqtane.Shared.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index acff7959..1b61bcef 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -3,7 +3,7 @@ net8.0 Debug;Release - 5.2.3 + 5.2.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/v5.2.3 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4 https://github.com/oqtane/oqtane.framework Git Oqtane From 352c23f389c73fdddd4004762f20d2ffe5fb1383 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 1 Oct 2024 06:15:46 -0700 Subject: [PATCH 33/74] Prepare Release 5.2.4 --- Oqtane.Shared/Shared/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index bdf195ea..615f4b9e 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -5,7 +5,7 @@ namespace Oqtane.Shared public class Constants { public static readonly string Version = "5.2.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"; + 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"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; From c458a77d27b11402d8acbcd533269ca82b4aa6f7 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 1 Oct 2024 22:13:32 +0800 Subject: [PATCH 34/74] Fix #4690: prevent invalid parsing. --- Oqtane.Client/Modules/Controls/Pager.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index ce14eed6..ab1db740 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -452,9 +452,9 @@ _displayPages = int.Parse(DisplayPages); } - if (PageState.QueryString.ContainsKey("page")) + if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page)) { - _page = int.Parse(PageState.QueryString["page"]); + _page = page; } else { From 1c95967b314dbe62f42d794b65293b4361cd2cc8 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 2 Oct 2024 08:30:34 -0400 Subject: [PATCH 35/74] fix #4695 - null reference exception deleting a setting which does not exist --- Oqtane.Server/Controllers/SettingController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 30a01330..d8a95cbe 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -189,7 +189,7 @@ namespace Oqtane.Controllers public void Delete(string entityName, int entityId, string settingName) { Setting setting = _settings.GetSetting(entityName, entityId, settingName); - if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) + if (setting != null && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) { _settings.DeleteSetting(setting.EntityName, setting.SettingId); AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Delete); @@ -199,7 +199,7 @@ namespace Oqtane.Controllers { if (entityName != EntityNames.Visitor) { - _logger.Log(LogLevel.Error, this, LogFunction.Delete, "User Not Authorized To Delete Setting {Setting}", setting); + _logger.Log(LogLevel.Error, this, LogFunction.Delete, "Setting Does Not Exist Or User Not Authorized To Delete Setting For Entity {EntityName} Id {EntityId} Name {SettingName}", entityName, entityId, settingName); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } From 2c262d065585a78afeb6598b64723b6f9e3c08b9 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 2 Oct 2024 16:39:31 -0400 Subject: [PATCH 36/74] updated version to 5.2.4 --- Oqtane.Shared/Shared/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 615f4b9e..12edb80b 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -4,7 +4,7 @@ namespace Oqtane.Shared { public class Constants { - public static readonly string Version = "5.2.3"; + public static readonly string Version = "5.2.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"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; From 3df45ca20f011c60fa9b13f4506d56423307b6ee Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 2 Oct 2024 16:53:36 -0400 Subject: [PATCH 37/74] add defensive logic if ModuleState is null in ModuleMessage --- .../Modules/Controls/ModuleMessage.razor | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/ModuleMessage.razor b/Oqtane.Client/Modules/Controls/ModuleMessage.razor index c42b40cb..440feff0 100644 --- a/Oqtane.Client/Modules/Controls/ModuleMessage.razor +++ b/Oqtane.Client/Modules/Controls/ModuleMessage.razor @@ -10,13 +10,16 @@ { View Details } - @if (ModuleState.RenderMode == RenderModes.Static) + @if (ModuleState != null) { - - } - else - { - + @if (ModuleState.RenderMode == RenderModes.Static) + { + + } + else + { + + } }
} From 29a1e77da8fa5e302ec1b11559bc974da3c5a6a3 Mon Sep 17 00:00:00 2001 From: mauroc Date: Fri, 4 Oct 2024 22:46:40 +0200 Subject: [PATCH 38/74] Hard deletion of page more robust use of contexts (fixes issue: presence of stale records of deleted page on db). --- Oqtane.Server/Repository/PageRepository.cs | 31 +++++++++++++++------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/Oqtane.Server/Repository/PageRepository.cs b/Oqtane.Server/Repository/PageRepository.cs index 4bad39e3..e3bc27d0 100644 --- a/Oqtane.Server/Repository/PageRepository.cs +++ b/Oqtane.Server/Repository/PageRepository.cs @@ -91,18 +91,29 @@ namespace Oqtane.Repository public void DeletePage(int pageId) { using var db = _dbContextFactory.CreateDbContext(); - var page = db.Page.Find(pageId); - _permissions.DeletePermissions(page.SiteId, EntityNames.Page, pageId); - _settings.DeleteSettings(EntityNames.Page, pageId); - // remove page modules for page - var pageModules = db.PageModule.Where(item => item.PageId == pageId).ToList(); - foreach (var pageModule in pageModules) { - _pageModules.DeletePageModule(pageModule.PageModuleId); + var page = db.Page.Find(pageId); + _permissions.DeletePermissions(page.SiteId, EntityNames.Page, pageId); + _settings.DeleteSettings(EntityNames.Page, pageId); + // remove page modules for page + var pageModules = db.PageModule.Where(item => item.PageId == pageId).ToList(); + foreach (var pageModule in pageModules) + { + _pageModules.DeletePageModule(pageModule.PageModuleId); + } + + // At this point the page item is unaware of changes happened in other + // contexts (i.e.: the contex opened and closed in each DeletePageModule). + // Workin on page item may result in unxpected behaviour: + // better close and reopen context to work on a fresh page item. + } + + using var dbContext = _dbContextFactory.CreateDbContext(); + { + var page = dbContext.Page.Find(pageId); + dbContext.Page.Remove(page); + dbContext.SaveChanges(); } - // must occur after page modules are deleted because of cascading delete relationship - db.Page.Remove(page); - db.SaveChanges(); } } } From d1e73571a1d4fd4533e8615bfd6866a100ceeabb Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 11:12:01 -0700 Subject: [PATCH 39/74] fix typo --- Oqtane.Server/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 164d8661..d873bd24 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -159,7 +159,7 @@ namespace Oqtane } }).AddHubOptions(options => { - options.MaximumReceiveMessageSize = null; // no limit (for large amnounts of data ie. textarea components) + options.MaximumReceiveMessageSize = null; // no limit (for large amounts of data ie. textarea components) }) .AddInteractiveWebAssemblyComponents(); From f9fbe5adc2a204f1b9399253f19883165c53a81b Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 12:11:52 -0700 Subject: [PATCH 40/74] Update NavigateTo() to use "true" instead of "forceLoad: true" --- Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor index c02548e0..38594fea 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor @@ -56,7 +56,7 @@ var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions { Path = "/", Expires = DateTimeOffset.UtcNow.AddYears(365) }); } - NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""), forceLoad: true); + NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""), true); } } @@ -67,7 +67,7 @@ var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); var interop = new Interop(JSRuntime); await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360); - NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); + NavigationManager.NavigateTo(NavigationManager.Uri, true); } } } From b65f165dcfa65844cb5468cb9667c7d92ad3f33f Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 13:17:31 -0700 Subject: [PATCH 41/74] Update adds SameSite, Secure and httpOnly SetCookie Settings --- Oqtane.Client/UI/Interop.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index cc379dc6..cdf88133 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -16,13 +16,13 @@ namespace Oqtane.UI _jsRuntime = jsRuntime; } - public Task SetCookie(string name, string value, int days) + public Task SetCookie(string name, string value, int days, bool secure, bool httpOnly, string sameSite) { try { _jsRuntime.InvokeVoidAsync( "Oqtane.Interop.setCookie", - name, value, days); + name, value, days, secure, httpOnly, sameSite); return Task.CompletedTask; } catch From e526deac208661ae97b8e8f975721f2387ff1af4 Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 13:19:52 -0700 Subject: [PATCH 42/74] Update Cookie Settings Secure, httpOnly, sameSite --- Oqtane.Server/wwwroot/js/interop.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 9bc74bb8..725e8ce5 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -1,11 +1,25 @@ var Oqtane = Oqtane || {}; Oqtane.Interop = { - setCookie: function (name, value, days) { + setCookie: function (name, value, days, secure, httpOnly, sameSite) { var d = new Date(); d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); - document.cookie = name + "=" + value + ";" + expires + ";path=/"; + var cookieString = name + "=" + value + ";" + expires + ";path=/"; + + // Add SameSite attribute + if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") { + cookieString += `; SameSite=${sameSite}`; + } + + // Add Secure attribute + if (secure) { + cookieString += "; Secure"; + } + + // Note: HttpOnly cannot be set here; it needs to be handled server-side. + + document.cookie = cookieString; }, getCookie: function (name) { name = name + "="; From bd2153a0ed4cbfee326aa3d996f005bacdd94c9e Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 13:23:09 -0700 Subject: [PATCH 43/74] Update cookie options to set SameSite, HttpOnly, Secure settings --- .../Themes/Controls/Theme/LanguageSwitcher.razor | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor index 38594fea..2af5f6ac 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor @@ -54,7 +54,16 @@ if (_supportedCultures.Any(item => item.Name == culture)) { var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); - HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions { Path = "/", Expires = DateTimeOffset.UtcNow.AddYears(365) }); + + HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions + { + Path = "/", + Expires = DateTimeOffset.UtcNow.AddYears(365), + SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute + Secure = true, // Ensure the cookie is only sent over HTTPS + HttpOnly = true // Optional: Helps mitigate XSS attacks + }); + } NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""), true); } @@ -66,7 +75,7 @@ { var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); var interop = new Interop(JSRuntime); - await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360); + await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, true, "Lax"); NavigationManager.NavigateTo(NavigationManager.Uri, true); } } From e6038be6f7b7a37c458f1e2539748d6391cf0e6e Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 13:27:14 -0700 Subject: [PATCH 44/74] Update `SetCookie` Option Settings Secure, HttpOnly, SameSite --- Oqtane.Client/Modules/Admin/Languages/Edit.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Languages/Edit.razor b/Oqtane.Client/Modules/Admin/Languages/Edit.razor index 5929edd9..be5daeb5 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Edit.razor @@ -103,7 +103,7 @@ else { var interop = new Interop(JSRuntime); var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); - await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360); + await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, true, "Lax"); } } From 9d0ab34274e46db726bdc75273200bb183a4336e Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 13:28:33 -0700 Subject: [PATCH 45/74] Update 'SetCookie" option settings "secure, httpOnly, sameSite" --- Oqtane.Client/Modules/Admin/Languages/Add.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor index 23af68e5..625c98e6 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -130,7 +130,7 @@ else { var interop = new Interop(JSRuntime); var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); - await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360); + await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, true, "Lax"); } } From dd0f8f4772f3993806a97f61981ce40de5a3ca0b Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 13:55:35 -0700 Subject: [PATCH 46/74] Update SetCookie function to include secure, httpOnly and sameSite --- Oqtane.Maui/wwwroot/js/interop.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Oqtane.Maui/wwwroot/js/interop.js b/Oqtane.Maui/wwwroot/js/interop.js index 9bc74bb8..998559c5 100644 --- a/Oqtane.Maui/wwwroot/js/interop.js +++ b/Oqtane.Maui/wwwroot/js/interop.js @@ -1,11 +1,18 @@ var Oqtane = Oqtane || {}; Oqtane.Interop = { - setCookie: function (name, value, days) { + setCookie: function (name, value, days, secure, httpOnly, sameSite) { var d = new Date(); d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); - document.cookie = name + "=" + value + ";" + expires + ";path=/"; + var cookieString = name + "=" + value + ";" + expires + ";path=/"; + if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") { + cookieString += `; SameSite=${sameSite}`; + } + if (secure) { + cookieString += "; Secure"; + } + document.cookie = cookieString; }, getCookie: function (name) { name = name + "="; From b5ea0dfbc706d81e2d019e54d170d8cf5f054e3a Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 13:57:36 -0700 Subject: [PATCH 47/74] Update Cleanup "setCookie" function notes options: secure, httpOnly, Samesite --- Oqtane.Server/wwwroot/js/interop.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 725e8ce5..998559c5 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -6,19 +6,12 @@ Oqtane.Interop = { d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); var cookieString = name + "=" + value + ";" + expires + ";path=/"; - - // Add SameSite attribute if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") { cookieString += `; SameSite=${sameSite}`; } - - // Add Secure attribute if (secure) { cookieString += "; Secure"; } - - // Note: HttpOnly cannot be set here; it needs to be handled server-side. - document.cookie = cookieString; }, getCookie: function (name) { From ce7570dae251e7b69960d2bd1b70eafb04d270a0 Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 13:58:55 -0700 Subject: [PATCH 48/74] Remove Unnecessary httpOnly setting setCookie Option --- Oqtane.Server/wwwroot/js/interop.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 998559c5..9a11b7e6 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -1,7 +1,7 @@ var Oqtane = Oqtane || {}; Oqtane.Interop = { - setCookie: function (name, value, days, secure, httpOnly, sameSite) { + setCookie: function (name, value, days, secure, sameSite) { var d = new Date(); d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); From 3121cf5b75d80cba73700a0b5a4ea814ada0c651 Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 14:00:18 -0700 Subject: [PATCH 49/74] Remove unnecessary httpOnly setCookie option --- Oqtane.Maui/wwwroot/js/interop.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Maui/wwwroot/js/interop.js b/Oqtane.Maui/wwwroot/js/interop.js index 998559c5..9a11b7e6 100644 --- a/Oqtane.Maui/wwwroot/js/interop.js +++ b/Oqtane.Maui/wwwroot/js/interop.js @@ -1,7 +1,7 @@ var Oqtane = Oqtane || {}; Oqtane.Interop = { - setCookie: function (name, value, days, secure, httpOnly, sameSite) { + setCookie: function (name, value, days, secure, sameSite) { var d = new Date(); d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); From 485b7748764bc5eddc427ac4013a5a1f0f3b001f Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 14:03:20 -0700 Subject: [PATCH 50/74] Remove httpOnly cooking attribute from SetCookie --- Oqtane.Client/UI/Interop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index cdf88133..f2b59054 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -16,7 +16,7 @@ namespace Oqtane.UI _jsRuntime = jsRuntime; } - public Task SetCookie(string name, string value, int days, bool secure, bool httpOnly, string sameSite) + public Task SetCookie(string name, string value, int days, bool secure, string sameSite) { try { From 906ae0a43e74e70bacb1256b1bfe2eb4c8544996 Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 14:06:18 -0700 Subject: [PATCH 51/74] Remove extra attribute for SetCookie --- Oqtane.Client/Modules/Admin/Languages/Add.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor index 625c98e6..17a02d67 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -130,7 +130,7 @@ else { var interop = new Interop(JSRuntime); var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); - await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, true, "Lax"); + await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax"); } } From 12f5d7b846f766cc7a3d0dc69de25bda629f339d Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 14:06:35 -0700 Subject: [PATCH 52/74] Remove extra attribute for SetCookie --- Oqtane.Client/Modules/Admin/Languages/Edit.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Languages/Edit.razor b/Oqtane.Client/Modules/Admin/Languages/Edit.razor index be5daeb5..7aa074c7 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Edit.razor @@ -103,7 +103,7 @@ else { var interop = new Interop(JSRuntime); var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); - await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, true, "Lax"); + await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax"); } } From 998dc95cb2b0ff7bb314372b838db3674722482f Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 14:08:16 -0700 Subject: [PATCH 53/74] Removed extra attribute for interop.SetCookie --- Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor index 2af5f6ac..fda0851a 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor @@ -75,7 +75,7 @@ { var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); var interop = new Interop(JSRuntime); - await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, true, "Lax"); + await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax"); NavigationManager.NavigateTo(NavigationManager.Uri, true); } } From f60f7a4dc1b977e67dc58d806ad3cd75f115fb8a Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 5 Oct 2024 14:17:20 -0700 Subject: [PATCH 54/74] Remove httpOnly setting from setCookie --- Oqtane.Client/UI/Interop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index f2b59054..d3d1841f 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -22,7 +22,7 @@ namespace Oqtane.UI { _jsRuntime.InvokeVoidAsync( "Oqtane.Interop.setCookie", - name, value, days, secure, httpOnly, sameSite); + name, value, days, secure, sameSite); return Task.CompletedTask; } catch From ed6054b082990fc1985954658772c3f1bbab4440 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Wed, 9 Oct 2024 14:35:27 +0200 Subject: [PATCH 55/74] Updated Microsoft Components for 8.0.8 to 8.0.10 Tested on upgrade and new Oqtane instance creation. --- Oqtane.Client/Oqtane.Client.csproj | 8 +-- .../Oqtane.Database.PostgreSQL.csproj | 2 +- .../Oqtane.Database.SqlServer.csproj | 2 +- .../Oqtane.Database.Sqlite.csproj | 2 +- Oqtane.Server/--appsettings.json | 63 +++++++++++++++++++ Oqtane.Server/--appsettings.release.json | 48 ++++++++++++++ Oqtane.Server/Oqtane.Server.csproj | 14 ++--- Oqtane.Server/appsettings.release.json | 2 +- Oqtane.Shared/Oqtane.Shared.csproj | 8 +-- 9 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 Oqtane.Server/--appsettings.json create mode 100644 Oqtane.Server/--appsettings.release.json diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 034ec87b..72c6447c 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 789a6611..111a1251 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 61124187..48f7282f 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 2a41a397..1fa10f5b 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/--appsettings.json b/Oqtane.Server/--appsettings.json new file mode 100644 index 00000000..d670835f --- /dev/null +++ b/Oqtane.Server/--appsettings.json @@ -0,0 +1,63 @@ +{ + "RenderMode": "Interactive", + "Runtime": "Server", + "Database": { + "DefaultDBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" + }, + "ConnectionStrings": { + "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\Oqtane-Fork.mdf;Initial Catalog=Oqtane-Fork;Integrated Security=SSPI;Encrypt=false;" + }, + "Installation": { + "DefaultAlias": "", + "HostPassword": "", + "HostEmail": "", + "SiteTemplate": "", + "DefaultTheme": "", + "DefaultContainer": "" + }, + "Localization": { + "DefaultCulture": "en" + }, + "AvailableDatabases": [ + { + "Name": "LocalDB", + "ControlType": "Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client", + "DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" + }, + { + "Name": "SQL Server", + "ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client", + "DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" + }, + { + "Name": "SQLite", + "ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client", + "DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Database.Sqlite" + }, + { + "Name": "MySQL", + "ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client", + "DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Database.MySQL" + }, + { + "Name": "PostgreSQL", + "ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client", + "DBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL" + } + ], + "Logging": { + "FileLogger": { + "LogLevel": { + "Default": "Error" + } + }, + "LogLevel": { + "Default": "Information" + } + }, + "InstallationId": "b9ab2e37-8983-40ef-83cd-1e3285129a6c", + "ReCaptcha": { + "SiteKey": "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI", + "SecretKey": "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe" + } +} \ No newline at end of file diff --git a/Oqtane.Server/--appsettings.release.json b/Oqtane.Server/--appsettings.release.json new file mode 100644 index 00000000..3b74ae04 --- /dev/null +++ b/Oqtane.Server/--appsettings.release.json @@ -0,0 +1,48 @@ +{ + "RenderMode": "Interactive", + "Runtime": "Server", + "Database": { + "DefaultDBType": "" + }, + "ConnectionStrings": { + "DefaultConnection": "" + }, + "Installation": { + "DefaultAlias": "", + "HostPassword": "", + "HostEmail": "", + "SiteTemplate": "", + "DefaultTheme": "", + "DefaultContainer": "" + }, + "Localization": { + "DefaultCulture": "en" + }, + "AvailableDatabases": [ + { + "Name": "LocalDB", + "ControlType": "Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client", + "DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" + }, + { + "Name": "SQL Server", + "ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client", + "DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" + }, + { + "Name": "SQLite", + "ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client", + "DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Database.Sqlite" + }, + { + "Name": "MySQL", + "ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client", + "DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Database.MySQL" + }, + { + "Name": "PostgreSQL", + "ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client", + "DBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL" + } + ] +} diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 7530c9c0..d5c51ea8 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -34,19 +34,19 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + diff --git a/Oqtane.Server/appsettings.release.json b/Oqtane.Server/appsettings.release.json index 3b74ae04..8549fa0b 100644 --- a/Oqtane.Server/appsettings.release.json +++ b/Oqtane.Server/appsettings.release.json @@ -1,6 +1,6 @@ { - "RenderMode": "Interactive", "Runtime": "Server", + "RenderMode": "ServerPrerendered", "Database": { "DefaultDBType": "" }, diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 1b61bcef..ab34bf21 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -19,11 +19,11 @@ - - - + + + - + From 4c4255be6b47316f84511ec153a1bace59d6c77d Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Wed, 9 Oct 2024 14:42:07 +0200 Subject: [PATCH 56/74] Updated Template files with new component version 8.0.10 --- .../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 9d3a0460..21ef0312 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 e8540965..8a9cfe06 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 2dca8a8e..48056a8a 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 3d83fccbf19671128199e23dc6f6504dc242b879 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Wed, 9 Oct 2024 15:07:57 +0200 Subject: [PATCH 57/74] remove appsettings --- Oqtane.Server/--appsettings.json | 63 ------------------------ Oqtane.Server/--appsettings.release.json | 48 ------------------ Oqtane.Server/appsettings.release.json | 2 +- 3 files changed, 1 insertion(+), 112 deletions(-) delete mode 100644 Oqtane.Server/--appsettings.json delete mode 100644 Oqtane.Server/--appsettings.release.json diff --git a/Oqtane.Server/--appsettings.json b/Oqtane.Server/--appsettings.json deleted file mode 100644 index d670835f..00000000 --- a/Oqtane.Server/--appsettings.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "RenderMode": "Interactive", - "Runtime": "Server", - "Database": { - "DefaultDBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" - }, - "ConnectionStrings": { - "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\Oqtane-Fork.mdf;Initial Catalog=Oqtane-Fork;Integrated Security=SSPI;Encrypt=false;" - }, - "Installation": { - "DefaultAlias": "", - "HostPassword": "", - "HostEmail": "", - "SiteTemplate": "", - "DefaultTheme": "", - "DefaultContainer": "" - }, - "Localization": { - "DefaultCulture": "en" - }, - "AvailableDatabases": [ - { - "Name": "LocalDB", - "ControlType": "Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client", - "DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" - }, - { - "Name": "SQL Server", - "ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client", - "DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" - }, - { - "Name": "SQLite", - "ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client", - "DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Database.Sqlite" - }, - { - "Name": "MySQL", - "ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client", - "DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Database.MySQL" - }, - { - "Name": "PostgreSQL", - "ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client", - "DBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL" - } - ], - "Logging": { - "FileLogger": { - "LogLevel": { - "Default": "Error" - } - }, - "LogLevel": { - "Default": "Information" - } - }, - "InstallationId": "b9ab2e37-8983-40ef-83cd-1e3285129a6c", - "ReCaptcha": { - "SiteKey": "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI", - "SecretKey": "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe" - } -} \ No newline at end of file diff --git a/Oqtane.Server/--appsettings.release.json b/Oqtane.Server/--appsettings.release.json deleted file mode 100644 index 3b74ae04..00000000 --- a/Oqtane.Server/--appsettings.release.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "RenderMode": "Interactive", - "Runtime": "Server", - "Database": { - "DefaultDBType": "" - }, - "ConnectionStrings": { - "DefaultConnection": "" - }, - "Installation": { - "DefaultAlias": "", - "HostPassword": "", - "HostEmail": "", - "SiteTemplate": "", - "DefaultTheme": "", - "DefaultContainer": "" - }, - "Localization": { - "DefaultCulture": "en" - }, - "AvailableDatabases": [ - { - "Name": "LocalDB", - "ControlType": "Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client", - "DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" - }, - { - "Name": "SQL Server", - "ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client", - "DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer" - }, - { - "Name": "SQLite", - "ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client", - "DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Database.Sqlite" - }, - { - "Name": "MySQL", - "ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client", - "DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Database.MySQL" - }, - { - "Name": "PostgreSQL", - "ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client", - "DBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL" - } - ] -} diff --git a/Oqtane.Server/appsettings.release.json b/Oqtane.Server/appsettings.release.json index 8549fa0b..3b74ae04 100644 --- a/Oqtane.Server/appsettings.release.json +++ b/Oqtane.Server/appsettings.release.json @@ -1,6 +1,6 @@ { + "RenderMode": "Interactive", "Runtime": "Server", - "RenderMode": "ServerPrerendered", "Database": { "DefaultDBType": "" }, From aa5b84a214c46df8c11a64c4ef86da9da397e215 Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Sun, 13 Oct 2024 12:38:43 +0200 Subject: [PATCH 58/74] Implements Image Manipulation in Files Page via QueryString - Extracts the image creation into a service - Refactors Files Page GET action for better readability and cyclomatic complexity --- Oqtane.Server/Controllers/FileController.cs | 80 +---- .../OqtaneServiceCollectionExtensions.cs | 1 + Oqtane.Server/Pages/Files.cshtml.cs | 280 ++++++++++++------ Oqtane.Server/Services/ImageService.cs | 93 ++++++ Oqtane.Shared/Interfaces/IImageService.cs | 13 + 5 files changed, 296 insertions(+), 171 deletions(-) create mode 100644 Oqtane.Server/Services/ImageService.cs create mode 100644 Oqtane.Shared/Interfaces/IImageService.cs diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 5aa8a66e..883a69e2 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -17,11 +17,10 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Extensions; using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Formats.Png; using System.Net.Http; using Microsoft.AspNetCore.Cors; using System.IO.Compression; +using Oqtane.Services; // ReSharper disable StringIndexOfIsCultureSpecific.1 @@ -38,7 +37,9 @@ namespace Oqtane.Controllers private readonly ILogManager _logger; private readonly Alias _alias; private readonly ISettingRepository _settingRepository; - public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISettingRepository settingRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) + private readonly IImageService _imageService; + + public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISettingRepository settingRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager, IImageService imageService) { _environment = environment; _files = files; @@ -48,6 +49,7 @@ namespace Oqtane.Controllers _logger = logger; _alias = tenantManager.GetAlias(); _settingRepository = settingRepository; + _imageService = imageService; } // GET: api/?folder=x @@ -681,12 +683,6 @@ namespace Oqtane.Controllers var filepath = _files.GetFilePath(file); if (System.IO.File.Exists(filepath)) { - // validation - if (!Enum.TryParse(mode, true, out ResizeMode _)) mode = "crop"; - if (!Enum.TryParse(position, true, out AnchorPositionMode _)) position = "center"; - if (!Color.TryParseHex("#" + background, out _)) background = "transparent"; - if (!int.TryParse(rotate, out _)) rotate = "0"; - rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate; if (!bool.TryParse(recreate, out _)) recreate = "false"; string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png"); @@ -696,7 +692,7 @@ namespace Oqtane.Controllers if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) || (!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString())))) { - imagepath = CreateImage(filepath, width, height, mode, position, background, rotate, imagepath); + imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, imagepath); } else { @@ -743,70 +739,6 @@ namespace Oqtane.Controllers return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null; } - private string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath) - { - try - { - using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read)) - { - stream.Position = 0; - using (var image = Image.Load(stream)) - { - int.TryParse(rotate, out int angle); - Enum.TryParse(mode, true, out ResizeMode resizemode); - Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode); - - PngEncoder encoder; - - if (background != "transparent") - { - image.Mutate(x => x - .AutoOrient() // auto orient the image - .Rotate(angle) - .Resize(new ResizeOptions - { - Mode = resizemode, - Position = anchorpositionmode, - Size = new Size(width, height), - PadColor = Color.ParseHex("#" + background) - })); - - encoder = new PngEncoder(); - } - else - { - image.Mutate(x => x - .AutoOrient() // auto orient the image - .Rotate(angle) - .Resize(new ResizeOptions - { - Mode = resizemode, - Position = anchorpositionmode, - Size = new Size(width, height) - })); - - encoder = new PngEncoder - { - ColorType = PngColorType.RgbWithAlpha, - TransparentColorMode = PngTransparentColorMode.Preserve, - BitDepth = PngBitDepth.Bit8, - CompressionLevel = PngCompressionLevel.BestSpeed - }; - } - - image.Save(imagepath, encoder); - } - } - } - catch (Exception ex) - { - _logger.Log(LogLevel.Error, this, LogFunction.Security, ex, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Rotate} {Error}", filepath, width, height, mode, rotate, ex.Message); - imagepath = ""; - } - - return imagepath; - } - private string GetFolderPath(string folder) { return Utilities.PathCombine(_environment.ContentRootPath, folder); diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 274a0aab..2b03eec6 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -102,6 +102,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // providers services.AddScoped(); diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index d8cee171..f27776bc 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; @@ -14,6 +15,7 @@ using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Security; +using Oqtane.Services; using Oqtane.Shared; namespace Oqtane.Pages @@ -28,8 +30,10 @@ namespace Oqtane.Pages private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; + private readonly IImageService _imageService; + private readonly ISettingRepository _settingRepository; - public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) + public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager, IImageService imageService, ISettingRepository settingRepository) { _environment = environment; _files = files; @@ -38,111 +42,193 @@ namespace Oqtane.Pages _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); + _imageService = imageService; + _settingRepository = settingRepository; } public IActionResult OnGet(string path) { - if (!string.IsNullOrEmpty(path)) - { - path = path.Replace("\\", "/"); - var folderpath = ""; - var filename = ""; - - bool download = false; - if (Request.Query.ContainsKey("download")) - { - download = true; - } - - var segments = path.Split('/'); - if (segments.Length > 0) - { - filename = segments[segments.Length - 1].ToLower(); - if (segments.Length > 1) - { - folderpath = string.Join("/", segments, 0, segments.Length - 1).ToLower() + "/"; - } - } - - Models.File file; - if (folderpath == "id/" && int.TryParse(filename, out int fileid)) - { - file = _files.GetFile(fileid, false); - } - else - { - file = _files.GetFile(_alias.SiteId, folderpath, filename); - } - - if (file != null) - { - if (file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList)) - { - // calculate ETag using last modified date and file size - var etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); - - var header = ""; - if (HttpContext.Request.Headers.ContainsKey(HeaderNames.IfNoneMatch)) - { - header = HttpContext.Request.Headers[HeaderNames.IfNoneMatch].ToString(); - } - - if (!header.Equals(etag)) - { - var filepath = _files.GetFilePath(file); - if (System.IO.File.Exists(filepath)) - { - if (download) - { - _syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, "Download"); - return PhysicalFile(filepath, file.GetMimeType(), file.Name); - } - else - { - HttpContext.Response.Headers.Append(HeaderNames.ETag, etag); - return PhysicalFile(filepath, file.GetMimeType()); - } - } - else - { - _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath); - HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; - } - } - else - { - HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified; - return Content(String.Empty); - } - } - else - { - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt For Site {SiteId} And Path {Path}", _alias.SiteId, path); - HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; - } - } - else - { - // look for url mapping - var urlMapping = _urlMappings.GetUrlMapping(_alias.SiteId, "files/" + folderpath + filename); - if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) - { - var url = urlMapping.MappedUrl; - if (!url.StartsWith("http")) - { - var uri = new Uri(HttpContext.Request.GetEncodedUrl()); - url = uri.Scheme + "://" + uri.Authority + ((!string.IsNullOrEmpty(_alias.Path)) ? "/" + _alias.Path : "") + "/" + url; - } - return RedirectPermanent(url); - } - } - } - else + if (string.IsNullOrWhiteSpace(path)) { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt - Path Not Specified For Site {SiteId}", _alias.SiteId); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return BrokenFile(); } + path = path.Replace("\\", "/"); + var folderpath = ""; + var filename = ""; + + bool download = false; + if (Request.Query.ContainsKey("download")) + { + download = true; + } + + var segments = path.Split('/'); + if (segments.Length > 0) + { + filename = segments[segments.Length - 1].ToLower(); + if (segments.Length > 1) + { + folderpath = string.Join("/", segments, 0, segments.Length - 1).ToLower() + "/"; + } + } + + Models.File file; + if (folderpath == "id/" && int.TryParse(filename, out int fileid)) + { + file = _files.GetFile(fileid, false); + } + else + { + file = _files.GetFile(_alias.SiteId, folderpath, filename); + } + + if (file == null) + { + // look for url mapping + + var urlMapping = _urlMappings.GetUrlMapping(_alias.SiteId, "files/" + folderpath + filename); + if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) + { + var url = urlMapping.MappedUrl; + if (!url.StartsWith("http")) + { + var uri = new Uri(HttpContext.Request.GetEncodedUrl()); + url = uri.Scheme + "://" + uri.Authority + ((!string.IsNullOrEmpty(_alias.Path)) ? "/" + _alias.Path : "") + "/" + url; + } + + // appends the query string to the redirect url + if (Request.QueryString.HasValue && !string.IsNullOrWhiteSpace(Request.QueryString.Value)) + { + url += Request.QueryString.Value; + } + + return RedirectPermanent(url); + } + + return BrokenFile(); + } + + if (file.Folder.SiteId != _alias.SiteId || !_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList)) + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt For Site {SiteId} And Path {Path}", _alias.SiteId, path); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return BrokenFile(); + } + + string etag; + string downloadName = file.Name; + string filepath = _files.GetFilePath(file); + + bool hasWidthParam = Request.Query.TryGetValue("width", out var widthStr); + bool hasHeightParam = Request.Query.TryGetValue("height", out var heightStr); + + int width = 0; + int height = 0; + + bool isRequestingImageResize = + hasWidthParam && int.TryParse(widthStr, out width) && width > 0 && + hasHeightParam && int.TryParse(heightStr, out height) && height > 0; + + if (isRequestingImageResize) + { + etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size ^ (width * 31) ^ (height * 17), 16); + } + else + { + etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); + } + + var header = ""; + if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) + { + header = ifNoneMatch.ToString(); + } + + if (header.Equals(etag)) + { + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified; + return Content(String.Empty); + } + + if (!System.IO.File.Exists(filepath)) + { + _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath); + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + return BrokenFile(); + } + + if (isRequestingImageResize) + { + var _ImageFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "ImageFiles")?.SettingValue; + _ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles; + + if (!_ImageFiles.Split(',').Contains(file.Extension.ToLower())) + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "File Is Not An Image {File}", file); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return BrokenFile(); + } + + Request.Query.TryGetValue("mode", out var mode); + Request.Query.TryGetValue("position", out var position); + Request.Query.TryGetValue("background", out var background); + Request.Query.TryGetValue("rotate", out var rotate); + Request.Query.TryGetValue("recreate", out var recreate); + + if (!bool.TryParse(recreate, out _)) recreate = "false"; + + string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png"); + if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate)) + { + // user has edit access to folder or folder supports the image size being created + if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) || + (!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString())))) + { + imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, imagepath); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Invalid Image Size For Folder {Folder} {Width} {Height}", file.Folder, width, height); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return BrokenFile(); + } + } + + if (string.IsNullOrWhiteSpace(imagepath)) + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, "Error Displaying Image For File {File} {Width} {Height}", file, widthStr, heightStr); + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + return BrokenFile(); + } + + downloadName = file.Name.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png"); + filepath = imagepath; + } + + if (!System.IO.File.Exists(filepath)) + { + _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath); + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + return BrokenFile(); + } + + if (download) + { + _syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, "Download"); + return PhysicalFile(filepath, file.GetMimeType(), downloadName); + } + else + { + HttpContext.Response.Headers.Append(HeaderNames.ETag, etag); + return PhysicalFile(filepath, file.GetMimeType()); + } + } + + private PhysicalFileResult BrokenFile() + { // broken link string errorPath = Path.Combine(Utilities.PathCombine(_environment.ContentRootPath, "wwwroot/images"), "error.png"); return PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)); diff --git a/Oqtane.Server/Services/ImageService.cs b/Oqtane.Server/Services/ImageService.cs new file mode 100644 index 00000000..34fbd613 --- /dev/null +++ b/Oqtane.Server/Services/ImageService.cs @@ -0,0 +1,93 @@ +using Oqtane.Enums; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Processing; +using System.IO; +using System; +using SixLabors.ImageSharp; +using Oqtane.Infrastructure; +using Oqtane.Interfaces; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + public class ImageService : IImageService + { + private readonly ILogManager _logger; + + public ImageService(ILogManager logger) + { + _logger = logger; + } + + public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath) + { + try + { + // params validation + if (!Enum.TryParse(mode, true, out ResizeMode _)) mode = "crop"; + if (!Enum.TryParse(position, true, out AnchorPositionMode _)) position = "center"; + if (!Color.TryParseHex("#" + background, out _)) background = "transparent"; + if (!int.TryParse(rotate, out _)) rotate = "0"; + rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate; + + using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read)) + { + stream.Position = 0; + using (var image = Image.Load(stream)) + { + int.TryParse(rotate, out int angle); + Enum.TryParse(mode, true, out ResizeMode resizemode); + Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode); + + PngEncoder encoder; + + if (background != "transparent") + { + image.Mutate(x => x + .AutoOrient() // auto orient the image + .Rotate(angle) + .Resize(new ResizeOptions + { + Mode = resizemode, + Position = anchorpositionmode, + Size = new Size(width, height), + PadColor = Color.ParseHex("#" + background) + })); + + encoder = new PngEncoder(); + } + else + { + image.Mutate(x => x + .AutoOrient() // auto orient the image + .Rotate(angle) + .Resize(new ResizeOptions + { + Mode = resizemode, + Position = anchorpositionmode, + Size = new Size(width, height) + })); + + encoder = new PngEncoder + { + ColorType = PngColorType.RgbWithAlpha, + TransparentColorMode = PngTransparentColorMode.Preserve, + BitDepth = PngBitDepth.Bit8, + CompressionLevel = PngCompressionLevel.BestSpeed + }; + } + + image.Save(imagepath, encoder); + } + } + } + catch (Exception ex) + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, ex, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Rotate} {Error}", filepath, width, height, mode, rotate, ex.Message); + imagepath = ""; + } + + return imagepath; + } + } +} diff --git a/Oqtane.Shared/Interfaces/IImageService.cs b/Oqtane.Shared/Interfaces/IImageService.cs new file mode 100644 index 00000000..e20b6b04 --- /dev/null +++ b/Oqtane.Shared/Interfaces/IImageService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Oqtane.Services +{ + public interface IImageService + { + public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath); + } +} From 3adb7ecb1cabcc579b68350e51061299f86b20e9 Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Sun, 13 Oct 2024 17:20:18 +0200 Subject: [PATCH 59/74] Enhances image manipulation with format (webp encoder, defaults to png) - computes etag with all manipulation parameters --- Oqtane.Server/Controllers/FileController.cs | 6 +- Oqtane.Server/Pages/Files.cshtml.cs | 75 ++++++++++++----- Oqtane.Server/Services/ImageService.cs | 93 ++++++++++++++------- Oqtane.Shared/Interfaces/IImageService.cs | 4 +- 4 files changed, 124 insertions(+), 54 deletions(-) diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 883a69e2..1e8a1740 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -685,14 +685,16 @@ namespace Oqtane.Controllers { if (!bool.TryParse(recreate, out _)) recreate = "false"; - string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png"); + string format = "png"; + + string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format); if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate)) { // user has edit access to folder or folder supports the image size being created if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) || (!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString())))) { - imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, imagepath); + imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, format, imagepath); } else { diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index f27776bc..463534fc 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -102,7 +102,16 @@ namespace Oqtane.Pages // appends the query string to the redirect url if (Request.QueryString.HasValue && !string.IsNullOrWhiteSpace(Request.QueryString.Value)) { - url += Request.QueryString.Value; + if (url.Contains('?')) + { + url += "&"; + } + else + { + url += "?"; + } + + url += Request.QueryString.Value.Substring(1); } return RedirectPermanent(url); @@ -122,25 +131,49 @@ namespace Oqtane.Pages string downloadName = file.Name; string filepath = _files.GetFilePath(file); - bool hasWidthParam = Request.Query.TryGetValue("width", out var widthStr); - bool hasHeightParam = Request.Query.TryGetValue("height", out var heightStr); + var etagValue = file.ModifiedOn.Ticks ^ file.Size; + + bool isRequestingImageManipulation = false; int width = 0; int height = 0; - - bool isRequestingImageResize = - hasWidthParam && int.TryParse(widthStr, out width) && width > 0 && - hasHeightParam && int.TryParse(heightStr, out height) && height > 0; - - if (isRequestingImageResize) + if (Request.Query.TryGetValue("width", out var widthStr) && int.TryParse(widthStr, out width) && width > 0) { - etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size ^ (width * 31) ^ (height * 17), 16); + isRequestingImageManipulation = true; + etagValue ^= (width * 31); } - else + if (Request.Query.TryGetValue("height", out var heightStr) && int.TryParse(heightStr, out height) && height > 0) { - etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); + isRequestingImageManipulation = true; + etagValue ^= (height * 17); } + Request.Query.TryGetValue("mode", out var mode); + Request.Query.TryGetValue("position", out var position); + Request.Query.TryGetValue("background", out var background); + + if (width > 0 || height > 0) + { + if (!string.IsNullOrWhiteSpace(mode)) etagValue ^= mode.ToString().GetHashCode(); + if (!string.IsNullOrWhiteSpace(position)) etagValue ^= position.ToString().GetHashCode(); + if (!string.IsNullOrWhiteSpace(background)) etagValue ^= background.ToString().GetHashCode(); + } + + int rotate; + if (Request.Query.TryGetValue("rotate", out var rotateStr) && int.TryParse(rotateStr, out rotate) && 360 > rotate && rotate > 0) + { + isRequestingImageManipulation = true; + etagValue ^= (rotate * 13); + } + + if (Request.Query.TryGetValue("format", out var format) && _imageService.GetAvailableFormats().Contains(format.ToString())) + { + isRequestingImageManipulation = true; + etagValue ^= format.ToString().GetHashCode(); + } + + etag = Convert.ToString(etagValue, 16); + var header = ""; if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) { @@ -160,7 +193,7 @@ namespace Oqtane.Pages return BrokenFile(); } - if (isRequestingImageResize) + if (isRequestingImageManipulation) { var _ImageFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "ImageFiles")?.SettingValue; _ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles; @@ -172,22 +205,24 @@ namespace Oqtane.Pages return BrokenFile(); } - Request.Query.TryGetValue("mode", out var mode); - Request.Query.TryGetValue("position", out var position); - Request.Query.TryGetValue("background", out var background); - Request.Query.TryGetValue("rotate", out var rotate); Request.Query.TryGetValue("recreate", out var recreate); if (!bool.TryParse(recreate, out _)) recreate = "false"; + if (!_imageService.GetAvailableFormats().Contains(format.ToString())) format = "png"; + if (width == 0 && height == 0) + { + width = file.ImageWidth; + height = file.ImageHeight; + } - string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png"); + string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format); if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate)) { // user has edit access to folder or folder supports the image size being created if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) || (!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString())))) { - imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, imagepath); + imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotateStr, format, imagepath); } else { @@ -204,7 +239,7 @@ namespace Oqtane.Pages return BrokenFile(); } - downloadName = file.Name.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png"); + downloadName = file.Name.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format); filepath = imagepath; } diff --git a/Oqtane.Server/Services/ImageService.cs b/Oqtane.Server/Services/ImageService.cs index 34fbd613..e22f3b8e 100644 --- a/Oqtane.Server/Services/ImageService.cs +++ b/Oqtane.Server/Services/ImageService.cs @@ -5,21 +5,29 @@ using System.IO; using System; using SixLabors.ImageSharp; using Oqtane.Infrastructure; -using Oqtane.Interfaces; using Oqtane.Shared; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Webp; +using System.Linq; namespace Oqtane.Services { public class ImageService : IImageService { private readonly ILogManager _logger; + private static readonly string[] _formats = ["png", "webp"]; public ImageService(ILogManager logger) { _logger = logger; } - public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath) + public string[] GetAvailableFormats() + { + return _formats; + } + + public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string format, string imagepath) { try { @@ -29,6 +37,7 @@ namespace Oqtane.Services if (!Color.TryParseHex("#" + background, out _)) background = "transparent"; if (!int.TryParse(rotate, out _)) rotate = "0"; rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate; + if (!_formats.Contains(format)) format = "png"; using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read)) { @@ -39,43 +48,34 @@ namespace Oqtane.Services Enum.TryParse(mode, true, out ResizeMode resizemode); Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode); - PngEncoder encoder; + if (width == 0 && height == 0) + { + width = image.Width; + height = image.Height; + } + + IImageEncoder encoder; + var resizeOptions = new ResizeOptions + { + Mode = resizemode, + Position = anchorpositionmode, + Size = new Size(width, height) + }; if (background != "transparent") { - image.Mutate(x => x - .AutoOrient() // auto orient the image - .Rotate(angle) - .Resize(new ResizeOptions - { - Mode = resizemode, - Position = anchorpositionmode, - Size = new Size(width, height), - PadColor = Color.ParseHex("#" + background) - })); - - encoder = new PngEncoder(); + resizeOptions.PadColor = Color.ParseHex("#" + background); + encoder = GetEncoder(format, transparent: false); } else { - image.Mutate(x => x + encoder = GetEncoder(format, transparent: true); + } + + image.Mutate(x => x .AutoOrient() // auto orient the image .Rotate(angle) - .Resize(new ResizeOptions - { - Mode = resizemode, - Position = anchorpositionmode, - Size = new Size(width, height) - })); - - encoder = new PngEncoder - { - ColorType = PngColorType.RgbWithAlpha, - TransparentColorMode = PngTransparentColorMode.Preserve, - BitDepth = PngBitDepth.Bit8, - CompressionLevel = PngCompressionLevel.BestSpeed - }; - } + .Resize(resizeOptions)); image.Save(imagepath, encoder); } @@ -89,5 +89,36 @@ namespace Oqtane.Services return imagepath; } + + private static IImageEncoder GetEncoder(string format, bool transparent) + { + return format switch + { + "png" => GetPngEncoder(transparent), + "webp" => GetWebpEncoder(transparent), + _ => GetPngEncoder(transparent), + }; + } + + private static PngEncoder GetPngEncoder(bool transparent) + { + return new PngEncoder() + { + ColorType = transparent ? PngColorType.RgbWithAlpha : PngColorType.Rgb, + TransparentColorMode = transparent ? PngTransparentColorMode.Preserve : PngTransparentColorMode.Clear, + BitDepth = PngBitDepth.Bit8, + CompressionLevel = PngCompressionLevel.BestSpeed + }; + } + + private static WebpEncoder GetWebpEncoder(bool transparent) + { + return new WebpEncoder() + { + FileFormat = WebpFileFormatType.Lossy, + Quality = 60, + TransparentColorMode = transparent ? WebpTransparentColorMode.Preserve : WebpTransparentColorMode.Clear, + }; + } } } diff --git a/Oqtane.Shared/Interfaces/IImageService.cs b/Oqtane.Shared/Interfaces/IImageService.cs index e20b6b04..f872b72d 100644 --- a/Oqtane.Shared/Interfaces/IImageService.cs +++ b/Oqtane.Shared/Interfaces/IImageService.cs @@ -8,6 +8,8 @@ namespace Oqtane.Services { public interface IImageService { - public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath); + public string[] GetAvailableFormats(); + + public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string format, string imagepath); } } From ec8433eb4536984824b0f209a031d8de81ad8c7d Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 14 Oct 2024 13:11:55 -0400 Subject: [PATCH 60/74] update MAUI project to .NET 8.0.10 --- 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 be7a5b0f..9697adab 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -65,12 +65,12 @@ - - + + - - - + + + From 93bc1cd5af034718538d31d884d4be59f1824519 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 14 Oct 2024 15:05:46 -0400 Subject: [PATCH 61/74] fix #4714 as well as breaking change in #4712 --- Oqtane.Client/UI/Interop.cs | 5 +++++ Oqtane.Maui/wwwroot/js/interop.js | 8 ++++---- Oqtane.Server/Components/App.razor | 2 +- Oqtane.Server/wwwroot/js/interop.js | 8 ++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index d3d1841f..c5964c96 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -16,6 +16,11 @@ namespace Oqtane.UI _jsRuntime = jsRuntime; } + public async Task SetCookie(string name, string value, int days) + { + await SetCookie(name, value, days, true, "Lax"); + } + public Task SetCookie(string name, string value, int days, bool secure, string sameSite) { try diff --git a/Oqtane.Maui/wwwroot/js/interop.js b/Oqtane.Maui/wwwroot/js/interop.js index 9a11b7e6..ef6043f9 100644 --- a/Oqtane.Maui/wwwroot/js/interop.js +++ b/Oqtane.Maui/wwwroot/js/interop.js @@ -6,11 +6,11 @@ Oqtane.Interop = { d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); var cookieString = name + "=" + value + ";" + expires + ";path=/"; - if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") { - cookieString += `; SameSite=${sameSite}`; - } if (secure) { - cookieString += "; Secure"; + cookieString += "; secure"; + } + if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") { + cookieString += "; SameSite=" + sameSite; } document.cookie = cookieString; }, diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index 658ecfce..76ef58f0 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -609,7 +609,7 @@ Expires = DateTimeOffset.UtcNow.AddYears(1), SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute Secure = true, // Ensure the cookie is only sent over HTTPS - HttpOnly = true // Optional: Helps mitigate XSS attacks + HttpOnly = false // cookie is updated using JS Interop }; Context.Response.Cookies.Append( diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 9a11b7e6..ef6043f9 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -6,11 +6,11 @@ Oqtane.Interop = { d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); var cookieString = name + "=" + value + ";" + expires + ";path=/"; - if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") { - cookieString += `; SameSite=${sameSite}`; - } if (secure) { - cookieString += "; Secure"; + cookieString += "; secure"; + } + if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") { + cookieString += "; SameSite=" + sameSite; } document.cookie = cookieString; }, From 04b38444ce73b0728308228d89699a3cd1c79acd Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 14 Oct 2024 15:36:32 -0400 Subject: [PATCH 62/74] fix #4722 - support PrincipalSchema when creating foreign keys (credit @Hypnodude) --- .../EntityBuilders/BaseEntityBuilder.cs | 15 +++++++++++++++ Oqtane.Server/Migrations/Framework/ForeignKey.cs | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs index 8f2cb20a..cfb58b72 100644 --- a/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs +++ b/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs @@ -319,6 +319,19 @@ namespace Oqtane.Migrations.EntityBuilders schema: Schema); } + public virtual void AddForeignKey(string foreignKeyName, string columnName, string principalTable, string principalColumn, string principalSchema, ReferentialAction onDelete) + { + _migrationBuilder.AddForeignKey( + name: RewriteName(foreignKeyName), + table: RewriteName(EntityTableName), + column: RewriteName(columnName), + principalTable: RewriteName(principalTable), + principalColumn: RewriteName(principalColumn), + principalSchema: RewriteName(principalSchema), + onDelete: onDelete, + schema: Schema); + } + /// /// Creates a Migration to add an Index to the Entity (table) /// @@ -368,6 +381,7 @@ namespace Oqtane.Migrations.EntityBuilders column: foreignKey.Column, principalTable: RewriteName(foreignKey.PrincipalTable), principalColumn: RewriteName(foreignKey.PrincipalColumn), + principalSchema: RewriteName(foreignKey.PrincipalSchema), onDelete: foreignKey.OnDeleteAction); } @@ -381,6 +395,7 @@ namespace Oqtane.Migrations.EntityBuilders column: RewriteName(foreignKey.ColumnName), principalTable: RewriteName(foreignKey.PrincipalTable), principalColumn: RewriteName(foreignKey.PrincipalColumn), + principalSchema: RewriteName(foreignKey.PrincipalSchema), onDelete: foreignKey.OnDeleteAction, schema: Schema); } diff --git a/Oqtane.Server/Migrations/Framework/ForeignKey.cs b/Oqtane.Server/Migrations/Framework/ForeignKey.cs index 533518ab..5f1bcd75 100644 --- a/Oqtane.Server/Migrations/Framework/ForeignKey.cs +++ b/Oqtane.Server/Migrations/Framework/ForeignKey.cs @@ -16,6 +16,16 @@ namespace Oqtane.Migrations OnDeleteAction = onDeleteAction; } + public ForeignKey(string name, Expression> column, string principalTable, string principalColumn, string principalSchema, ReferentialAction onDeleteAction) + { + Name = name; + Column = column; + PrincipalTable = principalTable; + PrincipalColumn = principalColumn; + PrincipalSchema = principalSchema; + OnDeleteAction = onDeleteAction; + } + public string Name { get; } public Expression> Column { get;} @@ -34,6 +44,8 @@ namespace Oqtane.Migrations public string PrincipalColumn { get; } + public string PrincipalSchema { get; } + } } From 93d4bfcd7a1bf6d3744267f4cc4dd8057991fec0 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 14 Oct 2024 16:21:03 -0400 Subject: [PATCH 63/74] When displaying Database Type use SQL Server rather than LocalDB to avoid confusion --- Oqtane.Client/Modules/Admin/Site/Index.razor | 2 +- Oqtane.Client/Modules/Admin/Sql/Index.razor | 27 +++++++------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index c906b19c..ff94804a 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -571,7 +571,7 @@ if (tenant != null) { _tenant = tenant.Name; - _database = _databases.Find(item => item.DBType == tenant.DBType)?.Name; + _database = _databases.Find(item => item.DBType == tenant.DBType && item.Name != "LocalDB")?.Name; _connectionstring = tenant.DBConnectionString; } } diff --git a/Oqtane.Client/Modules/Admin/Sql/Index.razor b/Oqtane.Client/Modules/Admin/Sql/Index.razor index 6d0dee92..476ebd1e 100644 --- a/Oqtane.Client/Modules/Admin/Sql/Index.razor +++ b/Oqtane.Client/Modules/Admin/Sql/Index.razor @@ -83,24 +83,15 @@ else { @if (_connection != "-") { -
- -
- @if (_databases != null) - { - - } -
-
@if (!string.IsNullOrEmpty(_tenant)) { -
+
+ +
+ +
+
+
@@ -204,12 +195,12 @@ else { _connectionstring = _connections[_connection].ToString(); _tenant = ""; - _databasetype = "-"; + _databasetype = ""; var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection); if (tenant != null) { _tenant = tenant.Name; - _databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType).Name; + _databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name; } } else From 62d59a09cf202d62190bad85ee97592799c46ad1 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 14 Oct 2024 16:49:14 -0400 Subject: [PATCH 64/74] set HttpOnly to false for Localization cookie in static rendering --- .../Themes/Controls/Theme/LanguageSwitcher.razor | 16 ++++++++-------- Oqtane.Server/Components/App.razor | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor index fda0851a..819c8d72 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor @@ -56,16 +56,16 @@ var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions - { - Path = "/", - Expires = DateTimeOffset.UtcNow.AddYears(365), - SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute - Secure = true, // Ensure the cookie is only sent over HTTPS - HttpOnly = true // Optional: Helps mitigate XSS attacks - }); + { + Path = "/", + Expires = DateTimeOffset.UtcNow.AddYears(365), + SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute + Secure = true, // Ensure the cookie is only sent over HTTPS + HttpOnly = false // cookie is updated using JS Interop in Interactive render mode + }); } - NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""), true); + NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", "")); } } diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index 76ef58f0..35d71322 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -609,7 +609,7 @@ Expires = DateTimeOffset.UtcNow.AddYears(1), SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute Secure = true, // Ensure the cookie is only sent over HTTPS - HttpOnly = false // cookie is updated using JS Interop + HttpOnly = false // cookie is updated using JS Interop in Interactive render mode }; Context.Response.Cookies.Append( From 52f552b4dec5da603d6ed6eae1c8ad2d67771ffe Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 14 Oct 2024 17:17:54 -0400 Subject: [PATCH 65/74] localize names of languages based on user's UI culture --- Oqtane.Server/Services/SiteService.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index 08e64147..13007742 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -92,6 +92,12 @@ namespace Oqtane.Services } site.Pages = pages; + // get language display name for user + foreach (Language language in site.Languages) + { + language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName; + } + return Task.FromResult(site); } @@ -130,7 +136,10 @@ namespace Oqtane.Services // languages site.Languages = _languages.GetLanguages(site.SiteId).ToList(); var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture); - site.Languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName, Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) }); + if (!site.Languages.Exists(item => item.Code == defaultCulture.Name)) + { + site.Languages.Add(new Language { Code = defaultCulture.Name, Name = "", Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) }); + } // themes site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList()); From b3071b927259cda7b450f5ffa985c29a28b792e4 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 15 Oct 2024 07:55:54 -0400 Subject: [PATCH 66/74] fix #4716 - sort recycle bin items by DeletedOn date descending --- Oqtane.Client/Modules/Admin/RecycleBin/Index.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/RecycleBin/Index.razor b/Oqtane.Client/Modules/Admin/RecycleBin/Index.razor index 91cf3669..4e4e2607 100644 --- a/Oqtane.Client/Modules/Admin/RecycleBin/Index.razor +++ b/Oqtane.Client/Modules/Admin/RecycleBin/Index.razor @@ -22,7 +22,7 @@ else } else { - +
    @@ -50,7 +50,7 @@ else } else { - +
    From c31c88ed1fafaf2e982d9d4e587a77da82c0a8c8 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 15 Oct 2024 08:31:54 -0400 Subject: [PATCH 67/74] fix #4711 - full page refresh required to affect language changes --- Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor index 819c8d72..6124fed6 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor @@ -22,7 +22,7 @@ } else { - @culture.DisplayName + @culture.DisplayName } }
From f57676a22b3edc9cf65b28c24a14176b88305262 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 15 Oct 2024 08:15:52 -0700 Subject: [PATCH 68/74] Update MySQL.Data to 9.1.0 --- Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj index f50eee92..d9911acd 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -34,7 +34,7 @@ - + From 7d6c10befb398255ea59bcae9351b0d47c60066a Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 15 Oct 2024 10:17:01 -0700 Subject: [PATCH 69/74] Add Discord community button to README.md - Added a button for joining the Oqtane Discord server - Included a brief description encouraging community engagement --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 72d6b86b..34fa8a74 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,14 @@ Explore and enhance your Oqtane experience by visiting the Oqtane Marketplace. D # Documentation There is a separate [Documentation repository](https://github.com/oqtane/oqtane.docs) which contains a variety of types of documentation for Oqtane, including API documentation that is auto generated using Docfx. The contents of the repository is published to Githib Pages and is available at [https://docs.oqtane.org](https://docs.oqtane.org/) +# Join the Community + +Connect with other developers, get support, and share ideas by joining the Oqtane community on Discord! + + + Join Oqtane Discord + + # Roadmap This project is open source, and therefore is a work in progress... From 088d665942d6b7f2ba29e32a662185120e4ade78 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 15 Oct 2024 10:27:59 -0700 Subject: [PATCH 70/74] Update Discord Community Link For Consistency --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 34fa8a74..8e9d7884 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,7 @@ There is a separate [Documentation repository](https://github.com/oqtane/oqtane. Connect with other developers, get support, and share ideas by joining the Oqtane community on Discord! - - Join Oqtane Discord - +[![Join our Discord](https://img.shields.io/badge/Join%20Discord-7289DA?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/BnPny88avK) # Roadmap This project is open source, and therefore is a work in progress... From bcf7866fe205b811e676e82d97c60fe36763c57c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 15 Oct 2024 15:58:17 -0400 Subject: [PATCH 71/74] fix #4733 - remove Name column from Language table and populate value dynamically --- .../Modules/Admin/Languages/Add.razor | 1 - .../Controllers/LanguageController.cs | 5 ++++ .../Tenant/05020401_RemoveLanguageName.cs | 28 +++++++++++++++++++ Oqtane.Shared/Models/Language.cs | 11 ++++---- 4 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor index 17a02d67..52a958c2 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -94,7 +94,6 @@ else var language = new Language { SiteId = PageState.Page.SiteId, - Name = CultureInfo.GetCultureInfo(_code).DisplayName, Code = _code, IsDefault = (_default == null ? false : Boolean.Parse(_default)) }; diff --git a/Oqtane.Server/Controllers/LanguageController.cs b/Oqtane.Server/Controllers/LanguageController.cs index 6ee66cac..36750b83 100644 --- a/Oqtane.Server/Controllers/LanguageController.cs +++ b/Oqtane.Server/Controllers/LanguageController.cs @@ -55,6 +55,10 @@ namespace Oqtane.Controllers else { languages = _languages.GetLanguages(SiteId).ToList(); + foreach (Language language in languages) + { + language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName; + } if (!string.IsNullOrEmpty(packagename)) { foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) @@ -85,6 +89,7 @@ namespace Oqtane.Controllers var language = _languages.GetLanguage(id); if (language != null && language.SiteId == _alias.SiteId) { + language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName; return language; } else diff --git a/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs b/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs new file mode 100644 index 00000000..063b7027 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs @@ -0,0 +1,28 @@ +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.05.02.04.01")] + public class RemoveLanguageName : MultiDatabaseMigration + { + public RemoveLanguageName(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); + languageEntityBuilder.DropColumn("Name"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Shared/Models/Language.cs b/Oqtane.Shared/Models/Language.cs index 82a837ce..b64a9381 100644 --- a/Oqtane.Shared/Models/Language.cs +++ b/Oqtane.Shared/Models/Language.cs @@ -19,11 +19,6 @@ namespace Oqtane.Models ///
public int? SiteId { get; set; } - /// - /// Language Name - corresponds to , _not_ - /// - public string Name { get; set; } - /// /// Language / Culture code, like 'en-US' - corresponds to /// @@ -34,6 +29,12 @@ namespace Oqtane.Models /// public bool IsDefault { get; set; } + [NotMapped] + /// + /// Language Name - corresponds to , _not_ + /// + public string Name { get; set; } + [NotMapped] /// /// Version of the satellite assembly From 56cfb2ce06ab41ca76086894725807a9ddf98aac Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 15 Oct 2024 16:46:05 -0400 Subject: [PATCH 72/74] fix sorting of Site.Languages property --- Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor | 3 +-- Oqtane.Server/Services/SiteService.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor index 6124fed6..78a6a99c 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor @@ -45,8 +45,7 @@ { MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty; - var languages = PageState.Languages; - _supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name }); + _supportedCultures = PageState.Languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name }); if (PageState.QueryString.ContainsKey("culture")) { diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index 13007742..84d752c2 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -97,6 +97,7 @@ namespace Oqtane.Services { language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName; } + site.Languages = site.Languages.OrderBy(item => item.Name).ToList(); return Task.FromResult(site); } From 8113a754a150e63a603b388e3887873aabbe00e2 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 16 Oct 2024 07:52:36 -0400 Subject: [PATCH 73/74] add missing localization keys --- Oqtane.Client/Modules/Admin/Users/Index.razor | 4 ++-- Oqtane.Client/Resources/Modules/Admin/Users/Index.resx | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index 8892c6b3..b7592212 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -205,8 +205,8 @@ else
diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index d168b641..381eff20 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -489,4 +489,10 @@ Info + + OAuth 2.0 + + + OpenID Connect (OIDC) + \ No newline at end of file From 51d244f3aa7e586d3d0e5a2c0965c2ba251becc4 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 16 Oct 2024 08:35:01 -0400 Subject: [PATCH 74/74] add additional external login providers --- Oqtane.Client/Modules/Admin/Users/Index.razor | 2 +- .../Shared/ExternalLoginProviders.cs | 37 ++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index b7592212..a59b161d 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -551,7 +551,7 @@ else private void LoadExternalLoginSettings(Dictionary settings) { - _provider = SettingService.GetSetting(settings, "ExternalLogin:Provider", "Custom"); + _provider = SettingService.GetSetting(settings, "ExternalLogin:Provider", ""); _providerurl = SettingService.GetSetting(settings, "ExternalLogin:ProviderUrl", ""); _providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", ""); _providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", ""); diff --git a/Oqtane.Shared/Shared/ExternalLoginProviders.cs b/Oqtane.Shared/Shared/ExternalLoginProviders.cs index 643c4ea4..8b62bad9 100644 --- a/Oqtane.Shared/Shared/ExternalLoginProviders.cs +++ b/Oqtane.Shared/Shared/ExternalLoginProviders.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Oqtane.Models; namespace Oqtane.Shared @@ -13,9 +14,10 @@ namespace Oqtane.Shared { new ExternalLoginProvider { - Name = "Custom", + Name = "", Settings = new Dictionary() }, + // OIDC new ExternalLoginProvider { Name = "Microsoft Entra", @@ -30,6 +32,20 @@ namespace Oqtane.Shared } }, new ExternalLoginProvider + { + Name = "Auth0 (by Okta)", + Settings = new Dictionary() + { + { "ExternalLogin:ProviderUrl", "https://auth0.com/docs/get-started" }, + { "ExternalLogin:ProviderType", "oidc" }, + { "ExternalLogin:ProviderName", "Auth0" }, + { "ExternalLogin:Authority", "YOUR DOMAIN" }, + { "ExternalLogin:ClientId", "YOUR CLIENT ID" }, + { "ExternalLogin:ClientSecret", "YOUR CLIENT SECRET" } + } + }, + // OAuth2 + new ExternalLoginProvider { Name = "GitHub", Settings = new Dictionary() @@ -46,10 +62,27 @@ namespace Oqtane.Shared { "ExternalLogin:IdentifierClaimType", "email" }, { "ExternalLogin:DomainFilter", "!users.noreply.github.com" } } + }, + new ExternalLoginProvider + { + Name = "Facebook", + Settings = new Dictionary() + { + { "ExternalLogin:ProviderUrl", "https://developers.facebook.com/apps/" }, + { "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:ClientId", "YOUR CLIENT ID" }, + { "ExternalLogin:ClientSecret", "YOUR CLIENT SECRET" }, + { "ExternalLogin:Scopes", "public_profile" }, + { "ExternalLogin:IdentifierClaimType", "id" } + } } }; - return providers; + return providers.OrderBy(item => item.Name).ToList(); } } }