diff --git a/src/AssociationRegistry.Admin.Api/Infrastructure/Extensions/FluentValidatorExtensions.cs b/src/AssociationRegistry.Admin.Api/Infrastructure/Extensions/FluentValidatorExtensions.cs index a252073bc..1da1375f7 100644 --- a/src/AssociationRegistry.Admin.Api/Infrastructure/Extensions/FluentValidatorExtensions.cs +++ b/src/AssociationRegistry.Admin.Api/Infrastructure/Extensions/FluentValidatorExtensions.cs @@ -1,13 +1,16 @@ namespace AssociationRegistry.Admin.Api.Infrastructure.Extensions; using ExceptionHandlers; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; using FluentValidation; using HtmlValidation; -using System.Linq; +using JasperFx.Core.Reflection; +using Microsoft.AspNetCore.Http; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; public static class FluentValidatorExtensions { @@ -18,40 +21,72 @@ public static async Task NullValidateAndThrowAsync( { if (instance is null) throw new CouldNotParseRequestException(); - await new NoHtmlValidator().ValidateAndThrowAsync(instance, cancellationToken: cancellationToken); + await new NoHtmlValidator().ValidateAndThrowAsync(instance, cancellationToken); await validator.ValidateAndThrowAsync(instance, cancellationToken); } } public class NoHtmlValidator : AbstractValidator { + private const string TO_BE_STRIPPED_PREFIX = "to-be-stripped-request-prefix"; + public NoHtmlValidator() { // TODO: nested // TODO: es kijken naar 'I <3 ' als waarde? - var propertiesWithNoHtml = typeof(T).GetProperties() - .Where(p => p.GetCustomAttributes(typeof(NoHtmlAttribute), false).Any()); + RecursivelyApplyRule(typeof(T), TO_BE_STRIPPED_PREFIX); + } + + private void RecursivelyApplyRule(Type type, string propertyName) + { + var props = type.GetProperties(); - foreach (var property in propertiesWithNoHtml) + foreach (var prop in props) { - if (property.PropertyType == typeof(string)) + var currentPropertyName = $"{propertyName}.{prop.Name}"; + + if (prop.HasAttribute()) + { + ApplyRuleFor(prop, currentPropertyName); + } + else if (prop.PropertyType.IsArray) { - RuleFor(model => GetPropertyValue(model, property.Name) as string) - .Must(BeNoHtml!) - .WithMessage(ExceptionMessages.UnsupportedContent) - .When(model => GetPropertyValue(model, property.Name) != null); + if (prop.PropertyType.IsClass) + RecursivelyApplyRule(prop.PropertyType, currentPropertyName); + else + ApplyRuleForEach(prop, currentPropertyName); } - else if (property.PropertyType == typeof(string[])) + else if (prop.PropertyType.IsClass) { - RuleForEach(model => GetPropertyValue(model, property.Name) as string[]) - .Must(BeNoHtml!) - .WithName(property.Name) - .WithMessage(ExceptionMessages.UnsupportedContent) - .When(model => GetPropertyValue(model, property.Name) != null); + RecursivelyApplyRule(prop.PropertyType, currentPropertyName); } } } + private void ApplyRuleFor(PropertyInfo prop, string propertyName) + { + if (prop.PropertyType == typeof(string)) + RuleFor(model => GetPropertyValue(model, prop.Name) as string) + .Cascade(CascadeMode.Continue) + .Must(BeNoHtml!) + .WithName(propertyName.Replace($"{TO_BE_STRIPPED_PREFIX}.", newValue: "")) + .WithErrorCode(StatusCodes.Status400BadRequest.ToString()) + .WithMessage(ExceptionMessages.UnsupportedContent) + .When(model => GetPropertyValue(model, prop.Name) != null); + } + + private void ApplyRuleForEach(PropertyInfo prop, string propertyName) + { + if (prop.PropertyType == typeof(string[])) + RuleForEach(model => Convert.ChangeType(GetPropertyValue(model, prop.Name), prop.PropertyType) as string[]) + .Cascade(CascadeMode.Continue) + .Must(BeNoHtml!) + .WithName(propertyName.Replace($"{TO_BE_STRIPPED_PREFIX}.", newValue: "")) + .WithErrorCode(StatusCodes.Status400BadRequest.ToString()) + .WithMessage(ExceptionMessages.UnsupportedContent) + .When(model => GetPropertyValue(model, prop.Name) != null); + } + private static object? GetPropertyValue(T model, string propertyName) { var property = typeof(T).GetProperty(propertyName); @@ -60,5 +95,5 @@ public NoHtmlValidator() } private static bool BeNoHtml(string propertyValue) - => !Regex.IsMatch(propertyValue, "<.*?>"); + => !Regex.IsMatch(propertyValue, pattern: "<.*?>"); } diff --git a/test/AssociationRegistry.Test.Admin.Api/FeitelijkeVereniging/When_RegistreerFeitelijkeVereniging/With_Html_Fields.cs b/test/AssociationRegistry.Test.Admin.Api/FeitelijkeVereniging/When_RegistreerFeitelijkeVereniging/With_Html_Fields.cs index dca332e94..9e7661e7d 100644 --- a/test/AssociationRegistry.Test.Admin.Api/FeitelijkeVereniging/When_RegistreerFeitelijkeVereniging/With_Html_Fields.cs +++ b/test/AssociationRegistry.Test.Admin.Api/FeitelijkeVereniging/When_RegistreerFeitelijkeVereniging/With_Html_Fields.cs @@ -21,7 +21,25 @@ public IEnumerator GetEnumerator() var autoFixture = new Fixture().CustomizeAdminApi(); var request1 = autoFixture.Create(); - request1.Naam = $"

{autoFixture.Create()}

"; + + // request1.Naam = $"

{autoFixture.Create()}

"; + request1.Contactgegevens = new ToeTeVoegenContactgegeven[] + { + new() + { + Beschrijving = $"

{autoFixture.Create()}

", + Contactgegeventype = "Mumbo Jumbo", + Waarde = "Test", + IsPrimair = false, + }, + new() + { + Beschrijving = $"

{autoFixture.Create()}

", + Contactgegeventype = $"

{autoFixture.Create()}

", + Waarde = "Test", + IsPrimair = false, + }, + }; yield return new object[] { request1 };