Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: OR-2035 recursively apply validation rules for requests and invalidate html fields #595

Merged
merged 11 commits into from
Jan 11, 2024
Merged
1 change: 0 additions & 1 deletion AssociationRegistry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Solution Items", ".Solutio
build.fsx = build.fsx
global.json = global.json
paket.dependencies = paket.dependencies
paket.lock = paket.lock
SolutionInfo.cs = SolutionInfo.cs
.gitignore = .gitignore
package.json = package.json
Expand Down
11 changes: 11 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ services:
ports:
- "5051:80"

seq:
image: datalust/seq
restart: always
ports:
- "9580:80"
environment:
ACCEPT_EULA: Y
volumes:
- seq-data:/data

elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.4.3
ports:
Expand Down Expand Up @@ -56,3 +66,4 @@ services:
volumes:
pg-data:
es-data:
seq-data:
5 changes: 4 additions & 1 deletion otel-collector-config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ receivers:
exporters:
logging:

otlphttp:
endpoint: http://seq/ingest/otlp

processors:
batch:

Expand All @@ -22,4 +25,4 @@ service:
logs:
receivers: [ otlp ]
processors: [ batch ]
exporters: [ logging ]
exporters: [ logging, otlphttp ]
1 change: 1 addition & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,4 @@ group Testing
nuget Microsoft.Extensions.Configuration.Json 6.0.0

nuget Microsoft.Extensions.DependencyInjection 6.0.0
nuget System.Collections.Immutable ~> 7.0
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
namespace AssociationRegistry.Admin.Api.Infrastructure.Extensions;

using ExceptionHandlers;
using FluentValidation;
using Microsoft.AspNetCore.Http;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;

public static class FluentValidatorExtensions
{
public static async Task NullValidateAndThrowAsync<T>(this IValidator<T> validator, [NotNull] T? instance, CancellationToken cancellationToken = default)
public static async Task NullValidateAndThrowAsync<T>(
this IValidator<T> validator,
[NotNull] T? instance,
CancellationToken cancellationToken = default)
{
if (instance is null) throw new CouldNotParseRequestException();
await validator.ValidateAndThrowAsync(instance, cancellationToken);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ namespace AssociationRegistry.Admin.Api.Infrastructure.Validation;
using System.Linq.Expressions;
using FluentValidation;
using FluentValidation.Internal;
using Microsoft.AspNetCore.Http;
using System.Text.RegularExpressions;

public static class ValidatorHelpers
{
Expand Down Expand Up @@ -35,4 +37,13 @@ public static void RequireValidKboNummer<T>(this AbstractValidator<T> validator,
.WithMessage($"'{expression.GetMember().Name}' moet 10 cijfers bevatten.")
.When(request => !string.IsNullOrEmpty(expression.Compile().Invoke(request)));
}

public static IRuleBuilder<T, string?> MustNotContainHtml<T>(this IRuleBuilder<T, string?> ruleBuilder)
=> ruleBuilder
.Must(NotContainHtml)
.WithErrorCode(StatusCodes.Status400BadRequest.ToString())
.WithMessage(ExceptionMessages.UnsupportedContent);

private static bool NotContainHtml(string? propertyValue)
=> propertyValue is null || !Regex.IsMatch(propertyValue, pattern: "<.*?>");
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Common;

using FluentValidation;
using Infrastructure.Extensions;
using Infrastructure.Validation;

public class AdresIdValidator : AbstractValidator<AdresId>
Expand All @@ -9,5 +10,8 @@ public AdresIdValidator()
{
this.RequireNotNullOrEmpty(adresId => adresId.Broncode);
this.RequireNotNullOrEmpty(adresId => adresId.Bronwaarde);

RuleFor(m => m.Broncode).MustNotContainHtml();
RuleFor(m => m.Bronwaarde).MustNotContainHtml();
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Common;

using FluentValidation;
using Infrastructure.Extensions;
using Infrastructure.Validation;

public class AdresValidator : AbstractValidator<Adres>
{
public AdresValidator()
{
this.RequireNotNullOrEmpty(adres => adres.Straatnaam);
this.RequireNotNullOrEmpty(locatie => locatie.Huisnummer);
this.RequireNotNullOrEmpty(locatie => locatie.Gemeente);
this.RequireNotNullOrEmpty(locatie => locatie.Land);
this.RequireNotNullOrEmpty(locatie => locatie.Postcode);
this.RequireNotNullOrEmpty(adres => adres.Huisnummer);
this.RequireNotNullOrEmpty(adres => adres.Gemeente);
this.RequireNotNullOrEmpty(adres => adres.Land);
this.RequireNotNullOrEmpty(adres => adres.Postcode);

RuleFor(adres => adres.Straatnaam).MustNotContainHtml();
RuleFor(adres => adres.Huisnummer).MustNotContainHtml();
RuleFor(adres => adres.Busnummer).MustNotContainHtml();
RuleFor(adres => adres.Postcode).MustNotContainHtml();
RuleFor(adres => adres.Gemeente).MustNotContainHtml();
RuleFor(adres => adres.Land).MustNotContainHtml();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Common;

using FluentValidation;
using Infrastructure.Extensions;
using Infrastructure.Validation;
using Vereniging;

Expand All @@ -11,6 +12,10 @@ public ToeTeVoegenContactgegevenValidator()
this.RequireNotNullOrEmpty(contactgegeven => contactgegeven.Waarde);
this.RequireNotNullOrEmpty(contactgegeven => contactgegeven.Contactgegeventype);

RuleFor(contactgegeven => contactgegeven.Beschrijving).MustNotContainHtml();
RuleFor(contactgegeven => contactgegeven.Contactgegeventype).MustNotContainHtml();
RuleFor(contactgegeven => contactgegeven.Waarde).MustNotContainHtml();

RuleFor(contactgegeven => contactgegeven.Contactgegeventype)
.Must(Contactgegeventype.CanParse)
.WithMessage(contactgegeven => $"De waarde {contactgegeven.Contactgegeventype} is geen gekend contactgegeven type.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ToeTeVoegenLocatie
/// <br />
/// Mogelijke waarden:<br />
/// - Activiteiten<br />
/// - Correspondentie - Slechtséén maal mogelijk<br />
/// - Correspondentie - Slechts één maal mogelijk<br />
/// </summary>
[DataMember]
public string Locatietype { get; set; } = null!;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Common;

using FluentValidation;
using Infrastructure.Extensions;
using Infrastructure.Validation;
using System;
using System.Linq;
using Infrastructure.Validation;
using FluentValidation;
using Vereniging;

public class ToeTeVoegenLocatieValidator : AbstractValidator<ToeTeVoegenLocatie>
Expand All @@ -12,6 +13,9 @@ public ToeTeVoegenLocatieValidator()
{
this.RequireNotNullOrEmpty(locatie => locatie.Locatietype);

RuleFor(locatie => locatie.Naam).MustNotContainHtml();
RuleFor(locatie => locatie.Locatietype).MustNotContainHtml();

RuleFor(locatie => locatie.Locatietype)
.Must(BeAValidLocationTypeValue)
.WithMessage($"'Locatietype' moet een geldige waarde hebben. ({Locatietype.Correspondentie}, {Locatietype.Activiteiten})")
Expand Down Expand Up @@ -49,6 +53,6 @@ private static object ToAnonymousObject(ToeTeVoegenLocatie l)
=> new
{
l.Locatietype, l.Naam, l.Adres?.Straatnaam, l.Adres?.Huisnummer, l.Adres?.Busnummer, l.Adres?.Postcode, l.Adres?.Gemeente,
l.Adres?.Land, l.AdresId?.Bronwaarde, l.AdresId?.Broncode
l.Adres?.Land, l.AdresId?.Bronwaarde, l.AdresId?.Broncode,
};
}
Original file line number Diff line number Diff line change
@@ -1,64 +1,78 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Common;

using System.Linq;
using Infrastructure.Validation;
using FluentValidation;
using Infrastructure.Extensions;
using Infrastructure.Validation;
using System.Linq;

public class ToeTeVoegenVertegenwoordigerValidator : AbstractValidator<ToeTeVoegenVertegenwoordiger>
{
public ToeTeVoegenVertegenwoordigerValidator()
{
this.RequireNotNullOrEmpty(vertegenwoordiger => vertegenwoordiger.Insz);

this.RequireNotNullOrEmpty(vertegenwoordiger => vertegenwoordiger.Voornaam);
this.RequireNotNullOrEmpty(vertegenwoordiger => vertegenwoordiger.Achternaam);

RuleFor(vertegenwoordiger => vertegenwoordiger.Voornaam).MustNotContainHtml();
RuleFor(vertegenwoordiger => vertegenwoordiger.Achternaam).MustNotContainHtml();
RuleFor(vertegenwoordiger => vertegenwoordiger.Insz).MustNotContainHtml();
RuleFor(vertegenwoordiger => vertegenwoordiger.Email).MustNotContainHtml();
RuleFor(vertegenwoordiger => vertegenwoordiger.SocialMedia).MustNotContainHtml();
RuleFor(vertegenwoordiger => vertegenwoordiger.Mobiel).MustNotContainHtml();
RuleFor(vertegenwoordiger => vertegenwoordiger.Roepnaam).MustNotContainHtml();
RuleFor(vertegenwoordiger => vertegenwoordiger.Rol).MustNotContainHtml();
RuleFor(vertegenwoordiger => vertegenwoordiger.Telefoon).MustNotContainHtml();

RuleFor(vertegenwoordiger => vertegenwoordiger.Voornaam)
.Must(NotContainNumbers)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Voornaam))
.WithMessage("'Voornaam' mag geen cijfers bevatten.");
.Must(NotContainNumbers)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Voornaam))
.WithMessage("'Voornaam' mag geen cijfers bevatten.");

RuleFor(vertegenwoordiger => vertegenwoordiger.Voornaam)
.Must(ContainAtLeastOneLetter)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Voornaam))
.WithMessage("'Voornaam' moet minstens een letter bevatten.");
.Must(ContainAtLeastOneLetter)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Voornaam))
.WithMessage("'Voornaam' moet minstens een letter bevatten.");

this.RequireNotNullOrEmpty(vertegenwoordiger => vertegenwoordiger.Achternaam);
RuleFor(vertegenwoordiger => vertegenwoordiger.Achternaam)
.Must(NotContainNumbers)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Achternaam))
.WithMessage("'Achternaam' mag geen cijfers bevatten.");
.Must(NotContainNumbers)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Achternaam))
.WithMessage("'Achternaam' mag geen cijfers bevatten.");

RuleFor(vertegenwoordiger => vertegenwoordiger.Achternaam)
.Must(ContainAtLeastOneLetter)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Achternaam))
.WithMessage("'Achternaam' moet minstens een letter bevatten.");
.Must(ContainAtLeastOneLetter)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Achternaam))
.WithMessage("'Achternaam' moet minstens een letter bevatten.");

RuleFor(vertegenwoordiger => vertegenwoordiger.Insz)
.Must(ContainOnlyNumbersDotsAndDashes)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Insz))
.WithMessage("Insz heeft incorrect formaat (00.00.00-000.00 of 00000000000)");
.Must(ContainOnlyNumbersDotsAndDashes)
.When(vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Insz))
.WithMessage("Insz heeft incorrect formaat (00.00.00-000.00 of 00000000000)");

RuleFor(vertegenwoordiger => vertegenwoordiger.Insz)
.Must(Have11Numbers)
.When(
.Must(Have11Numbers)
.When(
vertegenwoordiger => !string.IsNullOrEmpty(vertegenwoordiger.Insz) &&
ContainOnlyNumbersDotsAndDashes(vertegenwoordiger.Insz))
.WithMessage("Insz moet 11 cijfers bevatten");
.WithMessage("Insz moet 11 cijfers bevatten");
}

private bool ContainOnlyNumbersDotsAndDashes(string? insz)
{
insz = insz!.Replace(".", string.Empty).Replace("-", string.Empty);
insz = insz!.Replace(oldValue: ".", string.Empty).Replace(oldValue: "-", string.Empty);

return long.TryParse(insz, out _);
}

private bool Have11Numbers(string? insz)
{
insz = insz!.Replace(".", string.Empty).Replace("-", string.Empty);
insz = insz!.Replace(oldValue: ".", string.Empty).Replace(oldValue: "-", string.Empty);

return insz.Length == 11;
}

private static bool NotContainNumbers(string arg)
=> !arg.Any(char.IsDigit);


private static bool ContainAtLeastOneLetter(string arg)
=> arg.Any(char.IsLetter);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Locaties.FeitelijkeVereniging.WijzigLocatie.RequestModels;

using System.Runtime.Serialization;
using Acties.WijzigLocatie;
using Common;
using System.Runtime.Serialization;

/// <summary>Een locatie van een vereniging</summary>
[DataContract]
Expand All @@ -16,7 +16,7 @@ public class TeWijzigenLocatie
/// - Correspondentie - Slechts één maal mogelijk<br />
/// </summary>
[DataMember]
public string? Locatietype { get; set; } = null!;
public string? Locatietype { get; set; }

/// <summary>Duidt aan dat dit de primaire locatie is</summary>
[DataMember]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Locaties.VerenigingMetRechtspersoonlijkheid.WijzigMaatschappelijkeZetel.RequestModels;

using FluentValidation;
using Infrastructure.Extensions;
using Infrastructure.Validation;

public class TeWijzigenMaatschappelijkeZetelValidator : AbstractValidator<TeWijzigenMaatschappelijkeZetel>
{
public TeWijzigenMaatschappelijkeZetelValidator()
{
RuleFor(request => request)
.Must(HaveAtLeastOneValue)
.WithMessage("'Locatie' moet ingevuld zijn.");

RuleFor(maatschappelijkeZetel => maatschappelijkeZetel.Naam).MustNotContainHtml();
}

private bool HaveAtLeastOneValue(TeWijzigenMaatschappelijkeZetel locatie)
=> locatie.IsPrimair is not null ||
locatie.Naam is not null;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Locaties.VerenigingMetRechtspersoonlijkheid.WijzigMaatschappelijkeZetel;
namespace AssociationRegistry.Admin.Api.Verenigingen.Locaties.VerenigingMetRechtspersoonlijkheid.WijzigMaatschappelijkeZetel.RequestModels;

using FluentValidation;
using RequestModels;

// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
public class WijzigMaatschappelijkeZetelRequestValidator : AbstractValidator<WijzigMaatschappelijkeZetelRequest>
Expand All @@ -16,17 +15,3 @@ public WijzigMaatschappelijkeZetelRequestValidator()
.SetValidator(new TeWijzigenMaatschappelijkeZetelValidator());
}
}

public class TeWijzigenMaatschappelijkeZetelValidator : AbstractValidator<TeWijzigenMaatschappelijkeZetel>
{
public TeWijzigenMaatschappelijkeZetelValidator()
{
RuleFor(request => request)
.Must(HaveAtLeastOneValue)
.WithMessage("'Locatie' moet ingevuld zijn.");
}

private bool HaveAtLeastOneValue(TeWijzigenMaatschappelijkeZetel locatie)
=> locatie.IsPrimair is not null ||
locatie.Naam is not null;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Registreer;

using System.Runtime.Serialization;
using DuplicateVerenigingDetection;
using System.Runtime.Serialization;

/// <summary>Een activiteit van een vereniging</summary>
[DataContract]
Expand Down
Loading
Loading