diff --git a/EnvelopeGenerator.Web/Controllers/EnvelopeController.cs b/EnvelopeGenerator.Web/Controllers/EnvelopeController.cs index 9cfe0335..e9eb97c5 100644 --- a/EnvelopeGenerator.Web/Controllers/EnvelopeController.cs +++ b/EnvelopeGenerator.Web/Controllers/EnvelopeController.cs @@ -1,10 +1,9 @@ - -using EnvelopeGenerator.Application.Contracts; -using EnvelopeGenerator.Application.Services; +using EnvelopeGenerator.Application.Services; using EnvelopeGenerator.Common; using EnvelopeGenerator.Web.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using System.Text.Encodings.Web; namespace EnvelopeGenerator.Web.Controllers { @@ -13,13 +12,13 @@ namespace EnvelopeGenerator.Web.Controllers { private readonly EnvelopeOldService envelopeService; private readonly ActionService? actionService; - private readonly IEnvelopeService _envelopeService; + private readonly UrlEncoder _urlEncoder; - public EnvelopeController(DatabaseService database, EnvelopeOldService envelope, ILogger logger, IEnvelopeService envService) : base(database, logger) + public EnvelopeController(DatabaseService database, EnvelopeOldService envelope, ILogger logger, UrlEncoder urlEncoder) : base(database, logger) { envelopeService = envelope; actionService = database?.Services?.actionService; - _envelopeService = envService; + _urlEncoder = urlEncoder; } [NonAction] @@ -28,6 +27,8 @@ namespace EnvelopeGenerator.Web.Controllers { try { + envelopeKey = _urlEncoder.Encode(envelopeKey); + // Validate Envelope Key and load envelope envelopeService.EnsureValidEnvelopeKey(envelopeKey); @@ -52,6 +53,8 @@ namespace EnvelopeGenerator.Web.Controllers { try { + envelopeKey = _urlEncoder.Encode(envelopeKey); + var authSignature = this.GetAuthenticatedReceiverSignature(); if (authSignature != envelopeKey.GetReceiverSignature()) diff --git a/EnvelopeGenerator.Web/Controllers/HomeController.cs b/EnvelopeGenerator.Web/Controllers/HomeController.cs index e60699a4..8ad913ed 100644 --- a/EnvelopeGenerator.Web/Controllers/HomeController.cs +++ b/EnvelopeGenerator.Web/Controllers/HomeController.cs @@ -16,6 +16,9 @@ using EnvelopeGenerator.Application.DTOs; using Microsoft.AspNetCore.Localization; using Newtonsoft.Json.Linq; using Microsoft.Extensions.Configuration; +using Ganss.Xss; +using System.Text.Encodings.Web; +using EnvelopeGenerator.Domain.Entities; namespace EnvelopeGenerator.Web.Controllers { @@ -26,22 +29,26 @@ namespace EnvelopeGenerator.Web.Controllers private readonly IEnvelopeHistoryService _historyService; private readonly IStringLocalizer _localizer; private readonly IConfiguration _configuration; + private readonly UrlEncoder _urlEncoder; - public HomeController(DatabaseService databaseService, EnvelopeOldService envelopeOldService, ILogger logger, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeHistoryService historyService, IStringLocalizer localizer, IConfiguration configuration) : base(databaseService, logger) + public HomeController(DatabaseService databaseService, EnvelopeOldService envelopeOldService, ILogger logger, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeHistoryService historyService, IStringLocalizer localizer, IConfiguration configuration, UrlEncoder urlEncoder) : base(databaseService, logger) { this.envelopeOldService = envelopeOldService; _envRcvService = envelopeReceiverService; _historyService = historyService; _localizer = localizer; _configuration = configuration; + _urlEncoder = urlEncoder; } [HttpGet("/EnvelopeKey/{envelopeReceiverId}")] public async Task SendAccessCode([FromRoute] string envelopeReceiverId) { - ViewData["EnvelopeKey"] = envelopeReceiverId; try { + envelopeReceiverId = _urlEncoder.Encode(envelopeReceiverId); + ViewData["EnvelopeKey"] = envelopeReceiverId; + return await _envRcvService.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId: envelopeReceiverId).ThenAsync( SuccessAsync: async er => { @@ -77,6 +84,7 @@ namespace EnvelopeGenerator.Web.Controllers { try { + envelopeReceiverId = _urlEncoder.Encode(envelopeReceiverId); ViewData["Languages"] = _configuration.GetSection("Languages").Get()!; ViewData["UserLanguage"] = UserLanguage; @@ -100,6 +108,7 @@ namespace EnvelopeGenerator.Web.Controllers { try { + envelopeReceiverId = _urlEncoder.Encode(envelopeReceiverId); (string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId(); if(uuid is null || signature is null) @@ -187,6 +196,7 @@ namespace EnvelopeGenerator.Web.Controllers { try { + envelopeReceiverId = _urlEncoder.Encode(envelopeReceiverId); return await _envRcvService.IsExisting(envelopeReceiverId: envelopeReceiverId).ThenAsync( SuccessAsync: async isExisting => { @@ -231,6 +241,7 @@ namespace EnvelopeGenerator.Web.Controllers { try { + language = _urlEncoder.Encode(language); var cookieOptions = new CookieOptions() { Expires = DateTimeOffset.UtcNow.AddYears(1), diff --git a/EnvelopeGenerator.Web/Controllers/Test/TestSanitizeController.cs b/EnvelopeGenerator.Web/Controllers/Test/TestSanitizeController.cs new file mode 100644 index 00000000..5ea4e3be --- /dev/null +++ b/EnvelopeGenerator.Web/Controllers/Test/TestSanitizeController.cs @@ -0,0 +1,37 @@ +using Ganss.Xss; +using Microsoft.AspNetCore.Mvc; +using System.Text.Encodings.Web; + +namespace EnvelopeGenerator.Web.Controllers.Test +{ + [ApiController] + [Route("api/test/[controller]")] + public class TestSanitizeController : ControllerBase + { + private readonly HtmlEncoder _htmlEncoder; + private readonly HtmlSanitizer _sanitizer; + + public TestSanitizeController(HtmlEncoder htmlEncoder, HtmlSanitizer sanitizer) + { + _htmlEncoder = htmlEncoder; + _sanitizer = sanitizer; + } + + [HttpGet("sanitize")] + public IActionResult Sanitize([FromQuery] string? input = null) => Ok(new + { + input, + Sanitized = _sanitizer.Sanitize(input), + SanitizedDocument = _sanitizer.SanitizeDocument(input), + SanitizedDom = _sanitizer.SanitizeDom(input) + }); + + + [HttpGet("encode")] + public IActionResult Encoder([FromQuery] string? input = null) => Ok(new + { + input, + Encoded = _htmlEncoder.Encode(input) + }); + } +} diff --git a/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj b/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj index e2114c7b..6f4e9152 100644 --- a/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj +++ b/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj @@ -12,6 +12,7 @@ + diff --git a/EnvelopeGenerator.Web/Program.cs b/EnvelopeGenerator.Web/Program.cs index c4286df3..52028ab2 100644 --- a/EnvelopeGenerator.Web/Program.cs +++ b/EnvelopeGenerator.Web/Program.cs @@ -14,6 +14,8 @@ using Microsoft.AspNetCore.Authentication.Cookies; using DigitalData.UserManager.Application.MappingProfiles; using EnvelopeGenerator.Web.Models; using DigitalData.Core.DTO; +using System.Text.Encodings.Web; +using Ganss.Xss; var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); logger.Info("Logging initialized!"); @@ -151,6 +153,15 @@ try builder.Services.AddCookieBasedLocalizer(); + builder.Services.AddSingleton(HtmlEncoder.Default); + builder.Services.AddSingleton(UrlEncoder.Default); + builder.Services.AddSingleton(_ => + { + var sanitizer = new HtmlSanitizer(); + //configure sanitzer + return sanitizer; + }); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -166,6 +177,15 @@ try app.UseHttpsRedirection(); app.UseStaticFiles(); + + var csp = builder.Configuration["Content-Security-Policy"]; + if(csp is not null) + app.Use(async (context, next) => + { + context.Response.Headers.Add("Content-Security-Policy", csp); + await next(); + }); + app.UseCookiePolicy(); app.UseRouting(); diff --git a/EnvelopeGenerator.Web/Views/Home/EnvelopeLocked.cshtml b/EnvelopeGenerator.Web/Views/Home/EnvelopeLocked.cshtml index 8533e09b..02eb5038 100644 --- a/EnvelopeGenerator.Web/Views/Home/EnvelopeLocked.cshtml +++ b/EnvelopeGenerator.Web/Views/Home/EnvelopeLocked.cshtml @@ -1,25 +1,8 @@ @{ ViewData["Title"] = "Dokument geschützt"; - string userLanguage = ViewData["UserLanguage"] as string; - string[] languages = ViewData["Languages"] as string[]; + var userLanguage = ViewData["UserLanguage"] as string; + var languages = ViewData["Languages"] as string[]; } -@* @if(ViewData["UserLanguage"] == null){ - -} *@ -
@@ -30,18 +13,15 @@

Dokument erfordert einen Zugriffscode

-

Wir haben Ihnen gerade den Zugriffscode an die hinterlegte Email Adresse gesendet. Dies kann evtl. einige Minuten dauern.

-
-
@@ -49,15 +29,15 @@
-
Sie haben keinen Zugriffscode erhalten? @@ -65,5 +45,4 @@
- \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Views/Home/ShowEnvelope.cshtml b/EnvelopeGenerator.Web/Views/Home/ShowEnvelope.cshtml index e9df6852..b603db80 100644 --- a/EnvelopeGenerator.Web/Views/Home/ShowEnvelope.cshtml +++ b/EnvelopeGenerator.Web/Views/Home/ShowEnvelope.cshtml @@ -18,7 +18,7 @@ - +
...
@@ -33,9 +33,9 @@
-
@($"{envelope?.Title}")
-

@($"Sie haben {(pages.Count())} Briefe zu unterschreiben. Bitte prüfen Sie die Seiten {stPageIndexes}.")

-

Erstellt am @envelope?.AddedWhen von @sender?.Prename @sender?.Name. Sie können den Absender über @sender?.Email kontaktieren.

+
@($"{envelope?.Title.TrySanitize(_sanitizer)}")
+

@($"Sie haben {(pages.Count())} Briefe zu unterschreiben. Bitte prüfen Sie die Seiten {stPageIndexes.TrySanitize(_sanitizer)}.")

+

Erstellt am @envelope?.AddedWhen von @sender?.Prename.TrySanitize(_sanitizer) @sender?.Name.TrySanitize(_sanitizer). Sie können den Absender über @sender?.Email.TryEncode(_encoder) kontaktieren.

@@ -66,8 +66,10 @@ var documentBase64String = Convert.ToBase64String(documentBytes); + var envelopeKey = ViewData["EnvelopeKey"] as string; + diff --git a/EnvelopeGenerator.Web/Views/TestView/DebugEnvelopes.cshtml b/EnvelopeGenerator.Web/Views/TestView/DebugEnvelopes.cshtml index 8c6179fc..ba6773b8 100644 --- a/EnvelopeGenerator.Web/Views/TestView/DebugEnvelopes.cshtml +++ b/EnvelopeGenerator.Web/Views/TestView/DebugEnvelopes.cshtml @@ -31,7 +31,7 @@
@envelope.Title -
Ersteller @envelope.User.Email
+
Ersteller @envelope.User.Email.TrySanitize(_sanitizer)
Datum @envelope.AddedWhen
diff --git a/EnvelopeGenerator.Web/Views/_ViewImports.cshtml b/EnvelopeGenerator.Web/Views/_ViewImports.cshtml index b74b04e5..131d7f99 100644 --- a/EnvelopeGenerator.Web/Views/_ViewImports.cshtml +++ b/EnvelopeGenerator.Web/Views/_ViewImports.cshtml @@ -3,4 +3,6 @@ @using Microsoft.Extensions.Localization; @using EnvelopeGenerator.Application.Resources; @inject IStringLocalizer _localizer; +@inject System.Text.Encodings.Web.UrlEncoder _encoder +@inject Ganss.Xss.HtmlSanitizer _sanitizer @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/EnvelopeGenerator.Web/XSSExtensions.cs b/EnvelopeGenerator.Web/XSSExtensions.cs new file mode 100644 index 00000000..9c019dc2 --- /dev/null +++ b/EnvelopeGenerator.Web/XSSExtensions.cs @@ -0,0 +1,12 @@ +using Ganss.Xss; +using System.Text.Encodings.Web; + +namespace EnvelopeGenerator.Web +{ + public static class XSSExtensions + { + public static string? TryEncode(this string? value, UrlEncoder encoder) => value is null ? value : encoder.Encode(value); + + public static string? TrySanitize(this string? html, HtmlSanitizer sanitizer) => html is null ? html : sanitizer.Sanitize(html); + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Web/appsettings.json b/EnvelopeGenerator.Web/appsettings.json index c0a2b657..2c82090f 100644 --- a/EnvelopeGenerator.Web/appsettings.json +++ b/EnvelopeGenerator.Web/appsettings.json @@ -9,6 +9,9 @@ } }, "PSPDFKitLicenseKey": null, + /* recommended Content-Security-Policy for production: + "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self';" */ + "Content-Security-Policy": null, "AdminPassword": "dd", "AllowedOrigins": [ "https://localhost:7202" ], "NLog": { @@ -57,10 +60,10 @@ ] }, - "AddTestControllers": false, + "AddTestControllers": true, "Jwt": { - "Issuer": "https://localhost:7202", - "Audience": "https://localhost:7202", + "Issuer": null, + "Audience": null, "Key": "8RGnd7x0G2TYLOIW4m_qlIls7MfbAIGNrpQJzMAUIvULHOLiG723znRa_MG-Z4yw3SErusOU4hTui2rVBMcCaQ" }, "AuthCookieConfig": {