Skip to content

Commit

Permalink
Merge pull request #22 from BRP-API/119-overzetten-code-hc-historie
Browse files Browse the repository at this point in the history
119 overzetten code hc historie
  • Loading branch information
MelvLee authored Oct 31, 2024
2 parents 637895e + 23d1f62 commit fe3ef2a
Show file tree
Hide file tree
Showing 231 changed files with 13,355 additions and 5 deletions.
8 changes: 3 additions & 5 deletions .docker/historie-data-service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@ version: '3.7'
services:
historie-data-service:
container_name: historie-data-service
image: ghcr.io/brp-api/historie-data-service:2.0.4-latest
image: ghcr.io/brp-api/historie-data-service:latest
environment:
- ASPNETCORE_URLS=http://+;
- Database__Host=postgres
- Database__Username=root
- Database__Password=root
- Database__Database=rvig_haalcentraal_testdata
- HaalcentraalApi__MaxSearchResults=10
- ASPNETCORE_URLS=http://+;
- ProtocolleringAuthorization__UseAuthorizationChecks=false
- ProtocolleringAuthorization__UseProtocollering=false
- Ecs__Path=/var/log/historie-data-service.json
- Ecs__SecuredPath=/var/log/historie-data-service-secured.json
- Ecs__FileSizeLimitBytes=1073741824
- Ecs__RetainedFileCountLimit=10
- Serilog__MinimumLevel__Default=Warning
- Serilog__MinimumLevel__Override__Serilog=Information
# - Ecs__RetainedFileCountLimit=10
ports:
- "8000:80"
volumes:
Expand Down
11 changes: 11 additions & 0 deletions scripts/containers-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

MODE=$1

if [ "$MODE" = "ci" ]; then
# gebruik docker compose up -d om te forceren dat de container image wordt aangemaakt in de lokale registry
docker compose -f .docker/docker-compose-ci.yml up -d
docker compose -f .docker/docker-compose-ci.yml down
else
docker compose -f src/docker-compose.yml build
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Brp.Shared.Infrastructure.Autorisatie;

public class AuthorisationFailure
{
public string? Title { get; set; }
public string? Detail { get; set; }
public string? Code { get; set; }
public string? Reason { get; set; }
}
13 changes: 13 additions & 0 deletions src/Brp.Shared.Infrastructure/Autorisatie/AuthorisationResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Brp.Shared.Infrastructure.Autorisatie;

public class AuthorisationResult
{
public AuthorisationResult(bool isValid, IEnumerable<AuthorisationFailure> errors)
{
IsValid = isValid;
Errors = new(errors);
}

public bool IsValid { get; }
public List<AuthorisationFailure> Errors { get; }
}
6 changes: 6 additions & 0 deletions src/Brp.Shared.Infrastructure/Autorisatie/IAuthorisation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Brp.Shared.Infrastructure.Autorisatie;

public interface IAuthorisation
{
AuthorisationResult Authorize(int afnemerCode, int? gemeenteCode, string requestBody);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Authentication;
using Newtonsoft.Json.Linq;
using Serilog;
using System.Security.Claims;

namespace Brp.Shared.Infrastructure.Autorisatie;

public class RvIGClaimsTransformation : IClaimsTransformation
{
private readonly IDiagnosticContext _diagnosticContext;

public RvIGClaimsTransformation(IDiagnosticContext diagnosticContext)
{
_diagnosticContext = diagnosticContext;
}

public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
List<Claim> claims = new();
JObject jObject = new();

var claimKeyValuePairs = from claim in principal.Claims
where claim.Type == "claims"
let values = claim.Value.Split('=')
select values;
foreach (var claimKeyValue in claimKeyValuePairs )
{
jObject.Add(claimKeyValue[0], claimKeyValue[1]);
claims.Add(new Claim(claimKeyValue[0], claimKeyValue[1]));
}

_diagnosticContext.Set("Claims", jObject, true);
principal.AddIdentity(new ClaimsIdentity(claims));

return Task.FromResult(principal);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Logging;
using Microsoft.Extensions.Hosting;

namespace Brp.Shared.Infrastructure.Autorisatie;

public static class SetupAuthenticationHelpers
{
public static void SetupAuthentication(this WebApplicationBuilder builder, Serilog.ILogger logger)
{
if (builder.Environment.IsDevelopment())
{
IdentityModelEventSource.ShowPII = true;
}

var authority = builder.Configuration["OAuth:Authority"];
if(string.IsNullOrWhiteSpace(authority))
{
throw new InvalidOperationException("Authority setting is niet gezet");
}
if (authority.StartsWith("http:"))
{
logger.Warning($"Schema van authority url '{authority}' is NIET https. RequireHttpsMetadata wordt gezet op false (Is dit een DEV omgeving?)");
}
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
if (authority.StartsWith("http:"))
{
options.RequireHttpsMetadata = false;
}

options.TokenValidationParameters.ValidateAudience = false;

// signature van token wordt gevalideerd wanneer Authority wordt gevuld met de url van de IDP
// key management (refreshen) wordt automatisch afgehandeld. Zie: https://zhiliaxu.github.io/how-do-aspnet-core-services-validate-jwt-signature-signed-by-aad.html#configuration
options.Authority = authority;

//options.TokenValidationParameters.ValidTypes = new[] { "jwt" };

options.SetupJwtBearerEventsHandler();
});
builder.Services.AddTransient<IClaimsTransformation, RvIGClaimsTransformation>();
}

private static void SetupJwtBearerEventsHandler(this JwtBearerOptions options)
{
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = LogAuthenticationFailedReason,
OnForbidden = LogAuthorizationFailed,
};
}

private static Task LogAuthenticationFailedReason(AuthenticationFailedContext context)
{
context.HttpContext.Items.Add("AuthenticationFailedException", context.Exception);

return Task.CompletedTask;
}

private static Task LogAuthorizationFailed(ForbiddenContext context)
{
context.HttpContext.Items.Add("Forbidden", "for some reason");

return Task.CompletedTask;
}
}
27 changes: 27 additions & 0 deletions src/Brp.Shared.Infrastructure/Brp.Shared.Infrastructure.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="8.0.1" />
<PackageReference Include="Destructurama.Attributed" Version="4.0.0" />
<PackageReference Include="Destructurama.JsonNet" Version="3.0.0" />
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="8.11.0" />
<PackageReference Include="FluentValidation" Version="11.9.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.29" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="7.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Serilog.Sinks.PersistentFile\Serilog.Sinks.PersistentFile.csproj" />
</ItemGroup>

</Project>
55 changes: 55 additions & 0 deletions src/Brp.Shared.Infrastructure/HealthCheck/HealthCheckHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Brp.Shared.Infrastructure.HealthCheck;

public static class HealthCheckHelpers
{
public static IHealthChecksBuilder AddOcelotDownstreamEndpointCheck(this IHealthChecksBuilder builder, ConfigurationManager configuration)
{
var downstreamPathTemplate = configuration["Routes:0:DownstreamPathTemplate"];
var downstreamScheme = configuration["Routes:0:DownstreamScheme"];
var downstreamHost = configuration["Routes:0:DownstreamHostAndPorts:0:Host"];
var downstreamPort = configuration["Routes:0:DownstreamHostAndPorts:0:Port"];
var downstreamEndpoint = new Uri($"{downstreamScheme}://{downstreamHost}:{downstreamPort}{downstreamPathTemplate}");

return builder.AddUrlGroup(options =>
{
options.ExpectHttpCodes(400, 403);
options.UsePost();
options.AddUri(downstreamEndpoint, options =>
{
options.AddCustomHeader("x-healthcheck", "true");
});
}, name: $"Downstream endpoint: {downstreamEndpoint}");
}

public static void SetupHealthCheckEndpoints(this WebApplication app, ConfigurationManager configuration, Serilog.ILogger logger)
{
var healthBaseUrl = configuration["HealthEndpointBase"];

var startupHealthEndpoint = $"{healthBaseUrl}/startup";
logger.Information("Setup startup healthcheck endpoint: {StartupHealthEndpoint}", startupHealthEndpoint);
app.MapHealthChecks(startupHealthEndpoint, new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

var readyHealthEndpoint = $"{healthBaseUrl}/ready";
logger.Information("Setup ready healthcheck endpoint: {ReadyHealthEndpoint}", readyHealthEndpoint);
app.MapHealthChecks(readyHealthEndpoint, new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

var liveHealthEndpoint = $"{healthBaseUrl}/live";
logger.Information("Setup live healthcheck endpoint: {LiveHealthEndpoint}", liveHealthEndpoint);
app.MapHealthChecks(liveHealthEndpoint, new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
}
}
68 changes: 68 additions & 0 deletions src/Brp.Shared.Infrastructure/Http/HttpRequestExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Microsoft.AspNetCore.Http;
using System.IO.Compression;

namespace Brp.Shared.Infrastructure.Http;

public static class HttpRequestExtensions
{
public static bool UseGzip(this HttpRequest request) => request.Headers.ContentEncoding.Contains("gzip");

public static async Task<string> ReadBodyAsync(this HttpRequest request)
{
if (!request.Body.CanSeek)
{
request.EnableBuffering();
}

try
{
if (request.UseGzip())
{
return await ReadCompressedBodyAsync(request);
}
else
{
return await ReadUncompressedBodyAsync(request);
}
}
catch (InvalidDataException)
{
return await ReadUncompressedBodyAsync(request);
}
}

private static async Task<string> ReadCompressedBodyAsync(this HttpRequest request)
{

try
{
request.Body.Seek(0, SeekOrigin.Begin);

GZipStream gzipStream = new(request.Body, CompressionMode.Decompress);
StreamReader streamReader = new(gzipStream, leaveOpen: true);

return await streamReader.ReadToEndAsync();
}
finally
{
request.Body.Seek(0, SeekOrigin.Begin);
}

}

private static async Task<string> ReadUncompressedBodyAsync(this HttpRequest request)
{
try
{
request.Body.Seek(0, SeekOrigin.Begin);

StreamReader streamReader = new(request.Body, leaveOpen: true);

return await streamReader.ReadToEndAsync();
}
finally
{
request.Body.Seek(0, SeekOrigin.Begin);
}
}
}
Loading

0 comments on commit fe3ef2a

Please sign in to comment.