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

119 overzetten code hc historie #22

Merged
merged 3 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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