From 58e515da1152d85b528fa0968361d06d1be199b7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 7 Aug 2024 13:38:32 +0200 Subject: [PATCH 1/8] Do not allow save of invalid domains (#16880) --- .../Document/UpdateDomainsController.cs | 4 ++++ src/Umbraco.Core/Services/DomainService.cs | 6 ++++++ .../OperationStatus/DomainOperationStatus.cs | 3 ++- .../UrlAndDomains/DomainAndUrlsTests.cs | 16 ++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDomainsController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDomainsController.cs index a7c7256c6b..a44289b4c6 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDomainsController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDomainsController.cs @@ -66,6 +66,10 @@ public class UpdateDomainsController : DocumentControllerBase .WithDetail("One or more of the specified domain names were conflicting with domain assignments to other content items.") .WithExtension("conflictingDomainNames", _domainPresentationFactory.CreateDomainAssignmentModels(result.Result.ConflictingDomains.EmptyNull())) .Build()), + DomainOperationStatus.InvalidDomainName => BadRequest(problemDetailsBuilder + .WithTitle("Invalid domain name detected") + .WithDetail("One or more of the specified domain names were invalid.") + .Build()), _ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder .WithTitle("Unknown domain update operation status.") .Build()), diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs index b4e103f1a4..c527e40b82 100644 --- a/src/Umbraco.Core/Services/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -6,6 +6,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Extensions; @@ -201,6 +202,11 @@ public class DomainService : RepositoryService, IDomainService foreach (DomainModel domainModel in updateModel.Domains) { domainModel.DomainName = domainModel.DomainName.ToLowerInvariant(); + + if(Uri.IsWellFormedUriString(domainModel.DomainName, UriKind.RelativeOrAbsolute) is false) + { + return Attempt.FailWithStatus(DomainOperationStatus.InvalidDomainName, new DomainUpdateResult()); + } } // make sure we're not attempting to assign duplicate domains diff --git a/src/Umbraco.Core/Services/OperationStatus/DomainOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/DomainOperationStatus.cs index a752684b2e..ba19e2bc3f 100644 --- a/src/Umbraco.Core/Services/OperationStatus/DomainOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/DomainOperationStatus.cs @@ -7,5 +7,6 @@ public enum DomainOperationStatus ContentNotFound, LanguageNotFound, DuplicateDomainName, - ConflictingDomainName + ConflictingDomainName, + InvalidDomainName } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs index abc4e3894c..462205b231 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs @@ -332,6 +332,22 @@ public class DomainAndUrlsTests : UmbracoIntegrationTest Assert.AreEqual(DomainOperationStatus.DuplicateDomainName, result.Status); } + [TestCase("https://*.umbraco.com")] + [TestCase("&#€%#€")] + [TestCase("¢”$¢”¢$≈{")] + public async Task Cannot_Assign_Invalid_Domains(string domainName) + { + var domainService = GetRequiredService(); + var updateModel = new DomainsUpdateModel + { + Domains = new DomainModel { DomainName = domainName, IsoCode = Cultures.First() }.Yield() + }; + + var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel); + Assert.IsFalse(result.Success); + Assert.AreEqual(DomainOperationStatus.InvalidDomainName, result.Status); + } + [Test] public async Task Cannot_Assign_Already_Used_Domains() { From cae00d49d8d4fa5d674b2530ed90b3ae9c4d08d0 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 8 Aug 2024 09:04:34 +0100 Subject: [PATCH 2/8] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 97f256a195..574c6fc86f 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 97f256a195d8301bcc68ba21ed879240cd0d663d +Subproject commit 574c6fc86f96df65989fa24b760ad38db179217d From d3d057db6098d6ac40b83a780b90f4e374e651ce Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Mon, 12 Aug 2024 09:04:37 +0200 Subject: [PATCH 3/8] bumb version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index cbb26a6556..92356395ea 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "14.2.0-rc", + "version": "14.2.0-rc2", "assemblyVersion": { "precision": "build" }, From 35d6fc2f39a8b953661a31a315c250c3b54a8fc2 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 12 Aug 2024 08:16:20 +0100 Subject: [PATCH 4/8] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 574c6fc86f..bb6abdc884 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 574c6fc86f96df65989fa24b760ad38db179217d +Subproject commit bb6abdc88452bbd3a47bf867dcb1332f536ad264 From c288d038dd974e6d3aebb737f79cc9425a739513 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:38:28 +0200 Subject: [PATCH 5/8] v14: TryGetUmbracouser default implementation (#16899) * Add default implementation * Update src/Umbraco.Core/Security/Authorization/IAuthorizationHelper.cs Co-authored-by: Bjarke Berg --------- Co-authored-by: Bjarke Berg --- .../Security/Authorization/IAuthorizationHelper.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Security/Authorization/IAuthorizationHelper.cs b/src/Umbraco.Core/Security/Authorization/IAuthorizationHelper.cs index fb8d1cda35..d197770a00 100644 --- a/src/Umbraco.Core/Security/Authorization/IAuthorizationHelper.cs +++ b/src/Umbraco.Core/Security/Authorization/IAuthorizationHelper.cs @@ -24,5 +24,17 @@ public interface IAuthorizationHelper /// The current user's principal. /// The resulting , if the conversion is successful. /// True if the conversion is successful, false otherwise - bool TryGetUmbracoUser(IPrincipal currentUser, [NotNullWhen(true)] out IUser? user); + bool TryGetUmbracoUser(IPrincipal currentUser, [NotNullWhen(true)] out IUser? user) + { + try + { + user = GetUmbracoUser(currentUser); + return true; + } + catch + { + user = null; + return false; + } + } } From 506d3b3a9cc0e88bbcfea09a4b7c00f83703dd48 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 12 Aug 2024 12:41:37 +0200 Subject: [PATCH 6/8] Fix date conversion on the server-side (#16841) (cherry picked from commit ceddf8681be2c5ea8ae823ac44be953c0cecf016) --- .../Extensions/ObjectExtensions.cs | 18 ++++++ .../DatePickerValueConverter.cs | 18 +++--- .../CoreThings/ObjectExtensionsTests.cs | 62 +++++++++++++++++++ 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Extensions/ObjectExtensions.cs b/src/Umbraco.Core/Extensions/ObjectExtensions.cs index f0f10d8cc6..2ef1f9194f 100644 --- a/src/Umbraco.Core/Extensions/ObjectExtensions.cs +++ b/src/Umbraco.Core/Extensions/ObjectExtensions.cs @@ -219,6 +219,24 @@ public static class ObjectExtensions } } + if (target == typeof(DateTime) && input is DateTimeOffset dateTimeOffset) + { + // IMPORTANT: for compatability with various editors, we must discard any Offset information and assume UTC time here + return Attempt.Succeed((object?)new DateTime( + new DateOnly(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day), + new TimeOnly(dateTimeOffset.Hour, dateTimeOffset.Minute, dateTimeOffset.Second, dateTimeOffset.Millisecond, dateTimeOffset.Microsecond), + DateTimeKind.Utc)); + } + + if (target == typeof(DateTimeOffset) && input is DateTime dateTime) + { + // IMPORTANT: for compatability with various editors, we must discard any DateTimeKind information and assume UTC time here + return Attempt.Succeed((object?)new DateTimeOffset( + new DateOnly(dateTime.Year, dateTime.Month, dateTime.Day), + new TimeOnly(dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, dateTime.Microsecond), + TimeSpan.Zero)); + } + TypeConverter? inputConverter = GetCachedSourceTypeConverter(inputType, target); if (inputConverter != null) { diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs index 740e1b8f8e..6f9238f01f 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs @@ -20,23 +20,19 @@ public class DatePickerValueConverter : PropertyValueConverterBase internal static DateTime ParseDateTimeValue(object? source) { - if (source == null) + if (source is null) { return DateTime.MinValue; } - // in XML a DateTime is: string - format "yyyy-MM-ddTHH:mm:ss" - // Actually, not always sometimes it is formatted in UTC style with 'Z' suffixed on the end but that is due to this bug: - // http://issues.umbraco.org/issue/U4-4145, http://issues.umbraco.org/issue/U4-3894 - // We should just be using TryConvertTo instead. - if (source is string sourceString) + if (source is DateTime dateTimeValue) { - Attempt attempt = sourceString.TryConvertTo(); - return attempt.Success == false ? DateTime.MinValue : attempt.Result; + return dateTimeValue; } - // in the database a DateTime is: DateTime - // default value is: DateTime.MinValue - return source is DateTime dateTimeValue ? dateTimeValue : DateTime.MinValue; + Attempt attempt = source.TryConvertTo(); + return attempt.Success + ? attempt.Result + : DateTime.MinValue; } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs index bfbe581f2e..7aae5e54ee 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs @@ -11,6 +11,7 @@ using NUnit.Framework; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Tests.Common.TestHelpers; using Umbraco.Extensions; +using DateTimeOffset = System.DateTimeOffset; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.CoreThings; @@ -332,6 +333,50 @@ public class ObjectExtensionsTests Assert.AreEqual("This is a string", conv.Result); } + [Test] + public void CanConvertDateTimeOffsetToDateTime() + { + var dateTimeOffset = new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), TimeSpan.Zero); + var result = dateTimeOffset.TryConvertTo(); + Assert.IsTrue(result.Success); + Assert.Multiple(() => + { + Assert.AreEqual(new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03)), result.Result); + Assert.AreEqual(DateTimeKind.Utc, result.Result.Kind); + }); + } + + [Test] + public void CanConvertDateTimeToDateTimeOffset() + { + var dateTime = new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), DateTimeKind.Utc); + var result = dateTime.TryConvertTo(); + Assert.IsTrue(result.Success); + Assert.AreEqual(new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), TimeSpan.Zero), result.Result); + } + + [Test] + public void DiscardsOffsetWhenConvertingDateTimeOffsetToDateTime() + { + var dateTimeOffset = new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), TimeSpan.FromHours(2)); + var result = dateTimeOffset.TryConvertTo(); + Assert.IsTrue(result.Success); + Assert.Multiple(() => + { + Assert.AreEqual(new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03)), result.Result); + Assert.AreEqual(DateTimeKind.Utc, result.Result.Kind); + }); + } + + [Test] + public void DiscardsDateTimeKindWhenConvertingDateTimeToDateTimeOffset() + { + var dateTime = new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), DateTimeKind.Local); + var result = dateTime.TryConvertTo(); + Assert.IsTrue(result.Success); + Assert.AreEqual(new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30, 01, 02, 03), TimeSpan.Zero), result.Result); + } + [Test] public void Value_Editor_Can_Convert_Decimal_To_Decimal_Clr_Type() { @@ -342,6 +387,23 @@ public class ObjectExtensionsTests Assert.AreEqual(12.34d, result.Result); } + [Test] + public void Value_Editor_Can_Convert_DateTimeOffset_To_DateTime_Clr_Type() + { + var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Date); + + var result = valueEditor.TryConvertValueToCrlType(new DateTimeOffset(new DateOnly(2024, 07, 05), new TimeOnly(12, 30), TimeSpan.Zero)); + Assert.IsTrue(result.Success); + Assert.IsTrue(result.Result is DateTime); + + var dateTime = (DateTime)result.Result; + Assert.Multiple(() => + { + Assert.AreEqual(new DateTime(new DateOnly(2024, 07, 05), new TimeOnly(12, 30)), dateTime); + Assert.AreEqual(DateTimeKind.Utc, dateTime.Kind); + }); + } + private class MyTestObject { public override string ToString() => "Hello world"; From 79807999eed1d58fdb95b75e58340cec388b3484 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 12 Aug 2024 14:25:27 +0200 Subject: [PATCH 7/8] Add the default date-with-time configuration if missing (#16902) --- .../Migrations/Upgrade/UmbracoPlan.cs | 3 ++ .../AddMissingDateTimeConfiguration.cs | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_2_0/AddMissingDateTimeConfiguration.cs diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 8502e4c529..d2bed38ce3 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -88,5 +88,8 @@ public class UmbracoPlan : MigrationPlan // To 14.1.0 To("{FEF2DAF4-5408-4636-BB0E-B8798DF8F095}"); To("{A385C5DF-48DC-46B4-A742-D5BB846483BC}"); + + // To 14.2.0 + To("{20ED404C-6FF9-4F91-8AC9-2B298E0002EB}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_2_0/AddMissingDateTimeConfiguration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_2_0/AddMissingDateTimeConfiguration.cs new file mode 100644 index 0000000000..6691ed7e5b --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_2_0/AddMissingDateTimeConfiguration.cs @@ -0,0 +1,50 @@ +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_14_2_0; + +public class AddMissingDateTimeConfiguration : MigrationBase +{ + private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer; + + public AddMissingDateTimeConfiguration(IMigrationContext context, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) + : base(context) + => _configurationEditorJsonSerializer = configurationEditorJsonSerializer; + + protected override void Migrate() + { + Sql sql = Sql() + .Select() + .From() + .Where(dto => + dto.NodeId == Constants.DataTypes.DateTime + && dto.EditorAlias.Equals(Constants.PropertyEditors.Aliases.DateTime)); + + DataTypeDto? dataTypeDto = Database.FirstOrDefault(sql); + if (dataTypeDto is null) + { + return; + } + + Dictionary configurationData = dataTypeDto.Configuration.IsNullOrWhiteSpace() + ? new Dictionary() + : _configurationEditorJsonSerializer + .Deserialize>(dataTypeDto.Configuration)? + .Where(item => item.Value is not null) + .ToDictionary(item => item.Key, item => item.Value!) + ?? new Dictionary(); + + // only proceed with the migration if the data-type has no format assigned + if (configurationData.TryAdd("format", "YYYY-MM-DD HH:mm:ss") is false) + { + return; + } + + dataTypeDto.Configuration = _configurationEditorJsonSerializer.Serialize(configurationData); + Database.Update(dataTypeDto); + } +} From a36535b3543d506eb120760d3a982b2a6d103819 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 13 Aug 2024 08:56:18 +0200 Subject: [PATCH 8/8] bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 92356395ea..2d3aca5f9d 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "14.2.0-rc2", + "version": "14.2.0-rc3", "assemblyVersion": { "precision": "build" },