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
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 ]
3 changes: 3 additions & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ nuget Be.Vlaanderen.Basisregisters.DataDog.Tracing.Sql 5.0.0
nuget Be.Vlaanderen.Basisregisters.DataDog.Tracing.SqlStreamStore 5.0.0

nuget Destructurama.JsonNet 2.0.0
nuget HtmlSanitizer
QuintenGreenstack marked this conversation as resolved.
Show resolved Hide resolved

// FIX SWASHBUCKLE ASSEMBLY VERSION MISMATCH
nuget Swashbuckle.AspNetCore.SwaggerGen 6.3.0
Expand Down Expand Up @@ -133,3 +134,5 @@ group Testing
nuget Microsoft.Extensions.Configuration.Json 6.0.0

nuget Microsoft.Extensions.DependencyInjection 6.0.0
nuget System.Collections.Immutable ~> 7.0

10 changes: 9 additions & 1 deletion paket.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
RESTRICTION: || (== net472) (== net6.0)
NUGET
remote: https://api.nuget.org/v3/index.json
AngleSharp (0.16.1)
System.Buffers (>= 4.5.1)
System.Text.Encoding.CodePages (>= 5.0)
AngleSharp.Css (0.16.1)
AngleSharp (>= 0.16)
QuintenGreenstack marked this conversation as resolved.
Show resolved Hide resolved
AspNetCore.HealthChecks.SqlServer (6.0.2)
Microsoft.Data.SqlClient (>= 3.0.1)
Microsoft.Extensions.Diagnostics.HealthChecks (>= 6.0)
Expand Down Expand Up @@ -464,6 +469,9 @@ NUGET
Microsoft.Extensions.Logging.Abstractions (>= 3.0.3)
Grpc.Net.Common (2.50) - restriction: || (&& (== net472) (>= netstandard2.1)) (== net6.0)
Grpc.Core.Api (>= 2.50)
HtmlSanitizer (6.0.453)
QuintenGreenstack marked this conversation as resolved.
Show resolved Hide resolved
AngleSharp (0.16.1)
AngleSharp.Css (0.16.1)
Humanizer.Core (2.14.1) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0)
IdentityModel (6.0) - restriction: || (&& (== net472) (>= netcoreapp3.1)) (== net6.0)
IdentityModel.AspNetCore.OAuth2Introspection (6.0)
Expand Down Expand Up @@ -1415,7 +1423,7 @@ NUGET
Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0)
Microsoft.NETCore.Targets (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0)
System.Runtime (>= 4.3) - restriction: || (&& (== net472) (< net45)) (== net6.0)
System.Text.Encoding.CodePages (6.0) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0)
System.Text.Encoding.CodePages (6.0)
System.Runtime.CompilerServices.Unsafe (>= 6.0)
System.Text.Encoding.Extensions (4.3) - restriction: || (&& (== net472) (>= net6.0)) (== net6.0)
Microsoft.NETCore.Platforms (>= 1.1) - restriction: || (&& (== net472) (< net45)) (== net6.0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
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);
}

public static IRuleBuilder<T, string?> MustNotContainHtml<T>(this IRuleBuilder<T, string?> ruleBuilder)
QuintenGreenstack marked this conversation as resolved.
Show resolved Hide resolved
=> ruleBuilder
.Must(NotContainHtml)
.WithErrorCode(StatusCodes.Status400BadRequest.ToString())
.WithMessage(ExceptionMessages.UnsupportedContent);

private static bool NotContainHtml(string? propertyValue)
=> propertyValue is null ? true : !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,20 @@
namespace AssociationRegistry.Admin.Api.Verenigingen.Locaties.VerenigingMetRechtspersoonlijkheid.WijzigMaatschappelijkeZetel.RequestModels;

using FluentValidation;
using Infrastructure.Extensions;

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;
}
Loading
Loading