fix udi leaking in the management api (#15684)

* [WIP] Stop Udi leaking on ConterPicker

* Refined Udi conversion for contentPicker

Cleaned up base construcor usage to move away from the obsoleted one.

* Fixed Udi lieaking in MNTP

* Stopped Udi bleeding for MultiUrlPicker

* Remove unused assignment

* Resolved namespace issue

* Use correct configuration value for MNTP udi parsing

* Turn helper auto props into local helper function to avoid unnecesary serialization

* Remove Newtonsoft.Json from Multi URL picker

* Fixed MNTP configuration serialization

* Changed MNTP editor data from csv guid to EditorEntityReference[]

* Added remarks for the MNTP editor conversion logic

* Reworked MNTPPropertyEditor Unittests

changed intent of one
fixed bug because of 1 rework

* Update OpenApi.json

---------

Co-authored-by: Sven Geusens <sge@umbraco.dk>
Co-authored-by: Elitsa <elm@umbraco.dk>
Co-authored-by: kjac <kja@umbraco.dk>
This commit is contained in:
Sven Geusens
2024-03-14 11:06:46 +01:00
committed by GitHub
parent f41a4f1f06
commit ffcc4ac170
8 changed files with 325 additions and 94 deletions

View File

@@ -1,6 +1,7 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Text.Json.Nodes;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Editors;
@@ -31,8 +32,16 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor
protected override IDataValueEditor CreateValueEditor() =>
DataValueEditorFactory.Create<MultiNodeTreePickerPropertyValueEditor>(Attribute!);
/// <remarks>
/// At first glance, the fromEditor and toEditor methods might seem strange.
/// This is because we wanted to stop the leaking of UDI's to the frontend while not having to do database migrations
/// so we opted to, for now, translate the udi string in the database into a structured format unique to the client
/// This way, for now, no migration is needed and no changes outside of the editor logic needs to be touched to stop the leaking.
/// </remarks>
public class MultiNodeTreePickerPropertyValueEditor : DataValueEditor, IDataValueReference
{
private readonly IJsonSerializer _jsonSerializer;
public MultiNodeTreePickerPropertyValueEditor(
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
@@ -40,6 +49,7 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor
DataEditorAttribute attribute)
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
{
_jsonSerializer = jsonSerializer;
}
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
@@ -56,23 +66,41 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor
}
}
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
=> editorValue.Value is JsonArray jsonArray
? EntityReferencesToUdis(_jsonSerializer.Deserialize<IEnumerable<EditorEntityReference>>(jsonArray.ToJsonString()) ?? Enumerable.Empty<EditorEntityReference>())
: null;
public override object? ToEditor(IProperty property, string? culture = null, string? segment = null)
{
var value = property.GetValue(culture, segment);
return value is string stringValue
? ParseValidUdis(stringValue.Split(Constants.CharArrays.Comma))
: null;
? UdisToEntityReferences(stringValue.Split(Constants.CharArrays.Comma)).ToArray()
: null;
}
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
=> editorValue.Value is IEnumerable<string> stringValues
? string.Join(",", ParseValidUdis(stringValues))
: null;
private IEnumerable<EditorEntityReference> UdisToEntityReferences(IEnumerable<string> stringUdis)
{
foreach (var stringUdi in stringUdis)
{
if (UdiParser.TryParse(stringUdi, out GuidUdi? guidUdi) is false)
{
continue;
}
private string[] ParseValidUdis(IEnumerable<string> stringValues)
=> stringValues
.Select(s => UdiParser.TryParse(s, out Udi? udi) && udi is GuidUdi guidUdi ? guidUdi.ToString() : null)
.WhereNotNull()
.ToArray();
yield return new EditorEntityReference() { Type = guidUdi.EntityType, Unique = guidUdi.Guid };
}
}
private string EntityReferencesToUdis(IEnumerable<EditorEntityReference> nodeReferences)
=> string.Join(",", nodeReferences.Select(entityReference => Udi.Create(entityReference.Type, entityReference.Unique).ToString()));
public class EditorEntityReference
{
public required string Type { get; set; }
public required Guid Unique { get; set; }
}
}
}

View File

@@ -130,7 +130,9 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
Trashed = trashed,
Published = published,
QueryString = dto.QueryString,
Udi = udi,
Type = dto.Udi is null ? LinkDisplay.Types.External
: dto.Udi.EntityType,
Unique = dto.Udi?.Guid,
Url = url ?? string.Empty,
});
}
@@ -169,8 +171,8 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
Name = link.Name,
QueryString = link.QueryString,
Target = link.Target,
Udi = link.Udi,
Url = link.Udi == null ? link.Url : null, // only save the URL for external links
Udi = TypeIsUdiBased(link) ? new GuidUdi(link.Type!, link.Unique!.Value) : null,
Url = TypeIsExternal(link) ? link.Url : null, // only save the URL for external links
}));
}
catch (Exception ex)
@@ -181,6 +183,14 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
return base.FromEditor(editorValue, currentValue);
}
private static bool TypeIsExternal(LinkDisplay link) =>
link.Type is not null && link.Type.Equals(LinkDisplay.Types.External, StringComparison.InvariantCultureIgnoreCase);
private static bool TypeIsUdiBased(LinkDisplay link) =>
link.Type is not null && link.Unique is not null &&
(link.Type.Equals(LinkDisplay.Types.Document, StringComparison.InvariantCultureIgnoreCase)
|| link.Type.Equals(LinkDisplay.Types.Media, StringComparison.InvariantCultureIgnoreCase));
[DataContract]
public class LinkDto
{