Compare commits
39 Commits
feat/final
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fc23ba840e | |||
| 140d271b28 | |||
| a3b12a6957 | |||
| 16bdc7820d | |||
| 06e32b99ea | |||
| c7c78f96a6 | |||
| 5c232e61f2 | |||
| 24c9321c0f | |||
| c75c2b1dd5 | |||
| 8445757f34 | |||
| b088eb089f | |||
| e66c46767e | |||
| bc732d311c | |||
| c90d29d654 | |||
| 47a2e950ca | |||
| 6ef989213e | |||
| 2a27b6161b | |||
| efdc372b04 | |||
| 698b7ca1ac | |||
| bf6947a28c | |||
| e2e31e2e69 | |||
| 73f6221c3c | |||
| 10f730a833 | |||
| cf5a301942 | |||
| e364f1f592 | |||
| 8a488d4e71 | |||
| f0be1a5b03 | |||
| 773721b634 | |||
| 99e3e4c24d | |||
| b9c86ce3c6 | |||
| 637b45efe0 | |||
| 28b8c311f9 | |||
| 00c7fe5316 | |||
| e5a061d5b5 | |||
| 629b02863b | |||
| 3b24755c35 | |||
| 864e9e8164 | |||
| 7eff958d0a | |||
| 1f745ae79c |
@ -82,7 +82,7 @@ public record EnvelopeDto
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool? UseAccessCode { get; set; }
|
public bool UseAccessCode { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
|
|||||||
@ -0,0 +1,101 @@
|
|||||||
|
using DigitalData.Core.Abstraction.Application.Repository;
|
||||||
|
using DigitalData.Core.Exceptions;
|
||||||
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Histories.Queries;
|
||||||
|
|
||||||
|
//TODO: Add sender query
|
||||||
|
/// <summary>
|
||||||
|
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
|
||||||
|
/// </summary>
|
||||||
|
public record CountHistoryQuery : HistoryQueryBase, IRequest<int>;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public static class CountHistoryQueryExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="uuid"></param>
|
||||||
|
/// <param name="statuses"></param>
|
||||||
|
/// <param name="cancel"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task<bool> AnyHistoryAsync(this ISender sender, string uuid, IEnumerable<EnvelopeStatus> statuses, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var count = await sender.Send(new CountHistoryQuery
|
||||||
|
{
|
||||||
|
Envelope = new() { Uuid = uuid },
|
||||||
|
Statuses = new() { Include = statuses }
|
||||||
|
}, cancel);
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public class CountHistoryQueryHandler : IRequestHandler<CountHistoryQuery, int>
|
||||||
|
{
|
||||||
|
private readonly IRepository<History> _repo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
public CountHistoryQueryHandler(IRepository<History> repo)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="cancel"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotFoundException"></exception>
|
||||||
|
public Task<int> Handle(CountHistoryQuery request, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var query = _repo.Query;
|
||||||
|
|
||||||
|
if (request.Envelope.Id is int envId)
|
||||||
|
query = query.Where(e => e.Id == envId);
|
||||||
|
else if (request.Envelope.Uuid is string uuid)
|
||||||
|
query = query.Where(e => e.Envelope!.Uuid == uuid);
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
else if (request.EnvelopeId is not null)
|
||||||
|
query = query.Where(h => h.EnvelopeId == request.EnvelopeId);
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
else
|
||||||
|
throw new BadRequestException("Invalid request: An Envelope object or a valid EnvelopeId/UUID must be supplied.");
|
||||||
|
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
if (request.Status is not null)
|
||||||
|
query = query.Where(h => h.Status == request.Status);
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
|
||||||
|
if (request.Statuses is not null)
|
||||||
|
{
|
||||||
|
var status = request.Statuses;
|
||||||
|
if (status.Min is not null)
|
||||||
|
query = query.Where(er => er.Envelope!.Status >= status.Min);
|
||||||
|
|
||||||
|
if (status.Max is not null)
|
||||||
|
query = query.Where(er => er.Envelope!.Status <= status.Max);
|
||||||
|
|
||||||
|
if (status.Include?.Count() > 0)
|
||||||
|
query = query.Where(er => status.Include.Contains(er.Envelope!.Status));
|
||||||
|
|
||||||
|
if (status.Ignore is not null)
|
||||||
|
query = query.Where(er => !status.Ignore.Contains(er.Envelope!.Status));
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.CountAsync(cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
using EnvelopeGenerator.Application.Common.Query;
|
||||||
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Histories.Queries;
|
||||||
|
|
||||||
|
//TODO: Add sender query
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public record HistoryQueryBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Die eindeutige Kennung des Umschlags.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use Envelope property")]
|
||||||
|
public int? EnvelopeId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Der Include des Umschlags, der abgefragt werden soll. Kann optional angegeben werden, um die Ergebnisse zu filtern.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use statuses")]
|
||||||
|
public EnvelopeStatus? Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public EnvelopeStatusQuery Statuses { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public EnvelopeQueryBase Envelope { get; set; } = new EnvelopeQueryBase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public record EnvelopeStatusQuery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Der minimale Statuswert, der berücksichtigt werden.
|
||||||
|
/// </summary>
|
||||||
|
public EnvelopeStatus? Min { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Der maximale Statuswert, der berücksichtigt werden.
|
||||||
|
/// </summary>
|
||||||
|
public EnvelopeStatus? Max { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Eine Liste von Statuswerten, die einbezogen werden.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<EnvelopeStatus>? Include { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Eine Liste von Statuswerten, die ignoriert werden werden.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<EnvelopeStatus>? Ignore { get; init; }
|
||||||
|
}
|
||||||
@ -1,7 +1,10 @@
|
|||||||
using EnvelopeGenerator.Application.Common.Dto.History;
|
using AutoMapper;
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
using DigitalData.Core.Abstraction.Application.Repository;
|
||||||
|
using DigitalData.Core.Exceptions;
|
||||||
|
using EnvelopeGenerator.Application.Common.Dto.History;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.Histories.Queries;
|
namespace EnvelopeGenerator.Application.Histories.Queries;
|
||||||
|
|
||||||
@ -9,21 +12,81 @@ namespace EnvelopeGenerator.Application.Histories.Queries;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
|
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public record ReadHistoryQuery : IRequest<IEnumerable<HistoryDto>>
|
public record ReadHistoryQuery : HistoryQueryBase, IRequest<IEnumerable<HistoryDto>>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Die eindeutige Kennung des Umschlags.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
public int EnvelopeId { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Der Include des Umschlags, der abgefragt werden soll. Kann optional angegeben werden, um die Ergebnisse zu filtern.
|
|
||||||
/// </summary>
|
|
||||||
public EnvelopeStatus? Status { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abfrage zur Steuerung, ob nur der aktuelle Include oder der gesamte Datensatz zurückgegeben wird.
|
/// Abfrage zur Steuerung, ob nur der aktuelle Include oder der gesamte Datensatz zurückgegeben wird.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool? OnlyLast { get; init; } = true;
|
public bool OnlyLast { get; init; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public class ReadHistoryQueryHandler : IRequestHandler<ReadHistoryQuery, IEnumerable<HistoryDto>>
|
||||||
|
{
|
||||||
|
private readonly IRepository<History> _repo;
|
||||||
|
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="mapper"></param>
|
||||||
|
public ReadHistoryQueryHandler(IRepository<History> repo, IMapper mapper)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="cancel"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotFoundException"></exception>
|
||||||
|
public async Task<IEnumerable<HistoryDto>> Handle(ReadHistoryQuery request, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var query = _repo.Query;
|
||||||
|
|
||||||
|
if (request.Envelope.Id is int envId)
|
||||||
|
query = query.Where(e => e.Id == envId);
|
||||||
|
else if (request.Envelope.Uuid is string uuid)
|
||||||
|
query = query.Where(e => e.Envelope!.Uuid == uuid);
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
else if (request.EnvelopeId is not null)
|
||||||
|
query = query.Where(h => h.EnvelopeId == request.EnvelopeId);
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
else
|
||||||
|
throw new BadRequestException("Invalid request: An Envelope object or a valid EnvelopeId/UUID must be supplied.");
|
||||||
|
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
if (request.Status is not null)
|
||||||
|
query = query.Where(h => h.Status == request.Status);
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
|
||||||
|
if (request.Statuses is not null)
|
||||||
|
{
|
||||||
|
var status = request.Statuses;
|
||||||
|
if (status.Min is not null)
|
||||||
|
query = query.Where(er => er.Envelope!.Status >= status.Min);
|
||||||
|
|
||||||
|
if (status.Max is not null)
|
||||||
|
query = query.Where(er => er.Envelope!.Status <= status.Max);
|
||||||
|
|
||||||
|
if (status.Include?.Count() > 0)
|
||||||
|
query = query.Where(er => status.Include.Contains(er.Envelope!.Status));
|
||||||
|
|
||||||
|
if (status.Ignore is not null)
|
||||||
|
query = query.Where(er => !status.Ignore.Contains(er.Envelope!.Status));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.OnlyLast)
|
||||||
|
query = query.OrderByDescending(x => x.AddedWhen);
|
||||||
|
|
||||||
|
var hists = await query.ToListAsync(cancel);
|
||||||
|
return _mapper.Map<List<HistoryDto>>(hists);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,47 +0,0 @@
|
|||||||
using AutoMapper;
|
|
||||||
using DigitalData.Core.Abstraction.Application.Repository;
|
|
||||||
using DigitalData.Core.Exceptions;
|
|
||||||
using EnvelopeGenerator.Application.Common.Dto.History;
|
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.Histories.Queries;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class ReadHistoryQueryHandler : IRequestHandler<ReadHistoryQuery, IEnumerable<HistoryDto>>
|
|
||||||
{
|
|
||||||
private readonly IRepository<History> _repo;
|
|
||||||
|
|
||||||
private readonly IMapper _mapper;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="repo"></param>
|
|
||||||
/// <param name="mapper"></param>
|
|
||||||
public ReadHistoryQueryHandler(IRepository<History> repo, IMapper mapper)
|
|
||||||
{
|
|
||||||
_repo = repo;
|
|
||||||
_mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request"></param>
|
|
||||||
/// <param name="cancel"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <exception cref="NotFoundException"></exception>
|
|
||||||
public async Task<IEnumerable<HistoryDto>> Handle(ReadHistoryQuery request, CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
var query = _repo.ReadOnly().Where(h => h.EnvelopeId == request.EnvelopeId);
|
|
||||||
if (request.Status is not null)
|
|
||||||
query = query.Where(h => h.Status == request.Status);
|
|
||||||
|
|
||||||
var hists = await query.ToListAsync(cancel);
|
|
||||||
return _mapper.Map<List<HistoryDto>>(hists);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,6 +4,7 @@ using EnvelopeGenerator.Application.Common.Extensions;
|
|||||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||||
using EnvelopeGenerator.Application.Common.Notifications.DocSigned;
|
using EnvelopeGenerator.Application.Common.Notifications.DocSigned;
|
||||||
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||||
|
using EnvelopeGenerator.Application.Histories.Queries;
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
using EnvelopeGenerator.Web.Extensions;
|
using EnvelopeGenerator.Web.Extensions;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@ -11,7 +12,6 @@ using Microsoft.AspNetCore.Authentication;
|
|||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Dynamic;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Web.Controllers;
|
namespace EnvelopeGenerator.Web.Controllers;
|
||||||
|
|
||||||
@ -58,7 +58,9 @@ public class AnnotationController : ControllerBase
|
|||||||
|
|
||||||
// Again check if receiver has already signed
|
// Again check if receiver has already signed
|
||||||
if (await _mediator.IsSignedAsync(uuid, signature, cancel))
|
if (await _mediator.IsSignedAsync(uuid, signature, cancel))
|
||||||
return Problem(statusCode: 403);
|
return Problem(statusCode: 409);
|
||||||
|
else if (await _mediator.AnyHistoryAsync(uuid, new[] { EnvelopeStatus.EnvelopeRejected, EnvelopeStatus.DocumentRejected }, cancel))
|
||||||
|
return Problem(statusCode: 423);
|
||||||
|
|
||||||
var docSignedNotification = await _mediator
|
var docSignedNotification = await _mediator
|
||||||
.ReadEnvelopeReceiverAsync(uuid, signature, cancel)
|
.ReadEnvelopeReceiverAsync(uuid, signature, cancel)
|
||||||
@ -71,7 +73,7 @@ public class AnnotationController : ControllerBase
|
|||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Roles = ReceiverRole.FullyAuth)]
|
[Authorize(Roles = ReceiverRole.FullyAuth)]
|
||||||
[HttpPost("reject")]
|
[HttpPost("reject")]
|
||||||
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
|
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
|
||||||
|
|||||||
@ -95,6 +95,23 @@ public class EnvelopeController : ViewControllerBase
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region UseAccessCode
|
||||||
|
if (!er.Envelope!.UseAccessCode)
|
||||||
|
{
|
||||||
|
(string? uuid, string? signature) = decoded.ParseEnvelopeReceiverId();
|
||||||
|
var er_secret_res = await _envRcvService.ReadWithSecretByUuidSignatureAsync(uuid: uuid!, signature: signature!);
|
||||||
|
|
||||||
|
if (er_secret_res.IsFailed)
|
||||||
|
{
|
||||||
|
_logger.LogNotice(er_secret_res.Notices);
|
||||||
|
return this.ViewEnvelopeNotFound();
|
||||||
|
}
|
||||||
|
var er_secret = er_secret_res.Data;
|
||||||
|
await HttpContext.SignInEnvelopeAsync(er_secret, ReceiverRole.FullyAuth);
|
||||||
|
return await CreateShowEnvelopeView(er_secret);
|
||||||
|
}
|
||||||
|
#endregion UseAccessCode
|
||||||
|
|
||||||
#region Send Access Code
|
#region Send Access Code
|
||||||
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress);
|
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress);
|
||||||
if (!accessCodeAlreadyRequested)
|
if (!accessCodeAlreadyRequested)
|
||||||
@ -121,7 +138,7 @@ public class EnvelopeController : ViewControllerBase
|
|||||||
|
|
||||||
[HttpPost("{envelopeReceiverId}")]
|
[HttpPost("{envelopeReceiverId}")]
|
||||||
[Obsolete("Use MediatR")]
|
[Obsolete("Use MediatR")]
|
||||||
public async Task<IActionResult> LogInEnvelope([FromRoute] string envelopeReceiverId, [FromForm] Auth auth)
|
public async Task<IActionResult> LogInEnvelope([FromRoute] string envelopeReceiverId, [FromForm] Auth auth, CancellationToken cancel)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -145,6 +162,15 @@ public class EnvelopeController : ViewControllerBase
|
|||||||
}
|
}
|
||||||
var er_secret = er_secret_res.Data;
|
var er_secret = er_secret_res.Data;
|
||||||
|
|
||||||
|
//check rejection
|
||||||
|
var rejRcvrs = await _historyService.ReadRejectingReceivers(er_secret.Envelope!.Id);
|
||||||
|
if (rejRcvrs.Any())
|
||||||
|
{
|
||||||
|
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
ViewBag.IsExt = !rejRcvrs.Contains(er_secret.Receiver); //external if the current user is not rejected
|
||||||
|
return View("EnvelopeRejected", er_secret);
|
||||||
|
}
|
||||||
|
|
||||||
// show envelope if already logged in
|
// show envelope if already logged in
|
||||||
if (User.IsInRole(ReceiverRole.FullyAuth))
|
if (User.IsInRole(ReceiverRole.FullyAuth))
|
||||||
return await CreateShowEnvelopeView(er_secret);
|
return await CreateShowEnvelopeView(er_secret);
|
||||||
@ -190,7 +216,7 @@ public class EnvelopeController : ViewControllerBase
|
|||||||
return this.ViewInnerServiceError();
|
return this.ViewInnerServiceError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IActionResult> CreateEnvelopeLockedView(EnvelopeReceiverDto er, CancellationToken cancel)
|
private async Task<IActionResult> CreateEnvelopeLockedView(EnvelopeReceiverDto er, CancellationToken cancel)
|
||||||
{
|
{
|
||||||
var uuidClaim = User.GetAuthEnvelopeUuid();
|
var uuidClaim = User.GetAuthEnvelopeUuid();
|
||||||
|
|||||||
49
EnvelopeGenerator.Web/EnvelopeCookieManager.cs
Normal file
49
EnvelopeGenerator.Web/EnvelopeCookieManager.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Web;
|
||||||
|
|
||||||
|
public class EnvelopeCookieManager : ICookieManager
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<string> _envelopeKeyBasedCookieNames;
|
||||||
|
|
||||||
|
private readonly ChunkingCookieManager _inner = new();
|
||||||
|
|
||||||
|
public EnvelopeCookieManager(params string[] envelopeKeyBasedCookieNames)
|
||||||
|
{
|
||||||
|
_envelopeKeyBasedCookieNames = envelopeKeyBasedCookieNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCookieName(HttpContext context, string key)
|
||||||
|
{
|
||||||
|
if (!_envelopeKeyBasedCookieNames.Contains(key))
|
||||||
|
return key;
|
||||||
|
|
||||||
|
var envId = context.GetRouteValue("envelopeReceiverId")?.ToString();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(envId) && context.Request.Query.TryGetValue("envKey", out var envKeyValue))
|
||||||
|
envId = envKeyValue;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(envId))
|
||||||
|
return key;
|
||||||
|
|
||||||
|
return $"{key}-{envId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? GetRequestCookie(HttpContext context, string key)
|
||||||
|
{
|
||||||
|
var cookieName = GetCookieName(context, key);
|
||||||
|
return _inner.GetRequestCookie(context, cookieName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendResponseCookie(HttpContext context, string key, string? value, CookieOptions options)
|
||||||
|
{
|
||||||
|
var cookieName = GetCookieName(context, key);
|
||||||
|
_inner.AppendResponseCookie(context, cookieName, value, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
|
||||||
|
{
|
||||||
|
var cookieName = GetCookieName(context, key);
|
||||||
|
_inner.DeleteCookie(context, cookieName, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<PackageId>EnvelopeGenerator.Web</PackageId>
|
<PackageId>EnvelopeGenerator.Web</PackageId>
|
||||||
@ -12,9 +12,9 @@
|
|||||||
<PackageTags>digital data envelope generator web</PackageTags>
|
<PackageTags>digital data envelope generator web</PackageTags>
|
||||||
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
|
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
|
||||||
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
||||||
<Version>3.5.0</Version>
|
<Version>3.8.2</Version>
|
||||||
<AssemblyVersion>3.5.0</AssemblyVersion>
|
<AssemblyVersion>3.8.2</AssemblyVersion>
|
||||||
<FileVersion>3.5.0</FileVersion>
|
<FileVersion>3.8.2</FileVersion>
|
||||||
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
|
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@ -2094,20 +2094,13 @@
|
|||||||
<None Include="wwwroot\lib\bootstrap-icons\icons\zoom-out.svg" />
|
<None Include="wwwroot\lib\bootstrap-icons\icons\zoom-out.svg" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
|
||||||
<PackageReference Include="BuildBundlerMinifier2022" Version="2.9.9" />
|
<PackageReference Include="BuildBundlerMinifier2022" Version="2.9.9" />
|
||||||
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
|
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
|
||||||
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
|
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
|
||||||
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
|
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
|
||||||
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
|
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.15">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.20" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
|
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NLog" Version="5.2.5" />
|
<PackageReference Include="NLog" Version="5.2.5" />
|
||||||
@ -2126,6 +2119,56 @@
|
|||||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||||
|
<PackageReference Include="BuildBundlerMinifier2022" Version="2.9.9" />
|
||||||
|
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
|
||||||
|
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
|
||||||
|
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
|
||||||
|
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="NLog" Version="5.2.5" />
|
||||||
|
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
|
||||||
|
<PackageReference Include="Quartz" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Quartz.AspNetCore" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Quartz.Plugins" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Quartz.Serialization.Json" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.0.1" />
|
||||||
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" />
|
||||||
|
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
|
||||||
|
<PackageReference Include="System.DirectoryServices" Version="8.0.0" />
|
||||||
|
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="8.0.1" />
|
||||||
|
<PackageReference Include="System.DirectoryServices.Protocols" Version="8.0.1" />
|
||||||
|
<PackageReference Include="System.Drawing.Common" Version="8.0.16" />
|
||||||
|
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
|
<PackageReference Include="BuildBundlerMinifier2022" Version="2.9.9" />
|
||||||
|
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
|
||||||
|
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
|
||||||
|
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
|
||||||
|
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="NLog" Version="5.2.5" />
|
||||||
|
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
|
||||||
|
<PackageReference Include="Quartz" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Quartz.AspNetCore" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Quartz.Plugins" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Quartz.Serialization.Json" Version="3.8.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.0.1" />
|
||||||
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="9.0.11" />
|
||||||
|
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
|
||||||
|
<PackageReference Include="System.DirectoryServices" Version="9.0.4" />
|
||||||
|
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="9.0.4" />
|
||||||
|
<PackageReference Include="System.DirectoryServices.Protocols" Version="9.0.4" />
|
||||||
|
<PackageReference Include="System.Drawing.Common" Version="9.0.5" />
|
||||||
|
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
|
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
||||||
|
|||||||
@ -17,6 +17,7 @@ using EnvelopeGenerator.Web.Models.Annotation;
|
|||||||
using DigitalData.UserManager.DependencyInjection;
|
using DigitalData.UserManager.DependencyInjection;
|
||||||
using EnvelopeGenerator.Web.Middleware;
|
using EnvelopeGenerator.Web.Middleware;
|
||||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||||
|
using EnvelopeGenerator.Web;
|
||||||
|
|
||||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||||
logger.Info("Logging initialized!");
|
logger.Info("Logging initialized!");
|
||||||
@ -134,41 +135,22 @@ try
|
|||||||
options.ConsentCookie.Name = "cookie-consent-settings";
|
options.ConsentCookie.Name = "cookie-consent-settings";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var authCookieName = "env_auth";
|
||||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||||
.AddCookie(options =>
|
.AddCookie(options =>
|
||||||
{
|
{
|
||||||
options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security
|
options.Cookie.Name = authCookieName;
|
||||||
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Ensures cookies are sent over HTTPS only
|
options.CookieManager = new EnvelopeCookieManager(authCookieName);
|
||||||
options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites
|
options.Cookie.HttpOnly = true;
|
||||||
|
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
||||||
|
options.Cookie.SameSite = SameSiteMode.Strict;
|
||||||
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
|
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
|
||||||
|
|
||||||
options.Events = new CookieAuthenticationEvents
|
|
||||||
{
|
|
||||||
OnRedirectToLogin = context =>
|
|
||||||
{
|
|
||||||
// Dynamically calculate the redirection path, for example:
|
|
||||||
var envelopeReceiverId = context.HttpContext.Request.RouteValues["envelopeReceiverId"];
|
|
||||||
context.RedirectUri = $"/EnvelopeKey/{envelopeReceiverId}";
|
|
||||||
|
|
||||||
context.Response.Redirect(context.RedirectUri);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
},
|
|
||||||
OnRedirectToLogout = context =>
|
|
||||||
{
|
|
||||||
// Apply a similar redirection logic for logout
|
|
||||||
var envelopeReceiverId = context.HttpContext.Request.RouteValues["envelopeReceiverId"];
|
|
||||||
context.RedirectUri = $"/EnvelopeKey/{envelopeReceiverId}";
|
|
||||||
|
|
||||||
context.Response.Redirect(context.RedirectUri);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddSingleton(config.GetSection("ContactLink").Get<ContactLink>() ?? new());
|
builder.Services.AddSingleton(config.GetSection("ContactLink").Get<ContactLink>() ?? new());
|
||||||
|
|
||||||
builder.Services.AddCookieBasedLocalizer();
|
builder.Services.AddCookieBasedLocalizer();
|
||||||
|
|
||||||
builder.Services.AddSingleton(HtmlEncoder.Default);
|
builder.Services.AddSingleton(HtmlEncoder.Default);
|
||||||
builder.Services.AddSingleton(UrlEncoder.Default);
|
builder.Services.AddSingleton(UrlEncoder.Default);
|
||||||
builder.Services.AddSanitizer<HtmlSanitizer>();
|
builder.Services.AddSanitizer<HtmlSanitizer>();
|
||||||
@ -249,7 +231,7 @@ try
|
|||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
var cultures = app.Services.GetRequiredService<Cultures>();
|
var cultures = app.Services.GetRequiredService<Cultures>();
|
||||||
if(!cultures.Any())
|
if (!cultures.Any())
|
||||||
throw new InvalidOperationException(@"Languages section is missing in the appsettings. Please configure like following.
|
throw new InvalidOperationException(@"Languages section is missing in the appsettings. Please configure like following.
|
||||||
Language is both a name of the culture and the name of the resx file such as Resource.de-DE.resx
|
Language is both a name of the culture and the name of the resx file such as Resource.de-DE.resx
|
||||||
FIClass is the css class (in wwwroot/lib/flag-icons-main) for the flag of country.
|
FIClass is the css class (in wwwroot/lib/flag-icons-main) for the flag of country.
|
||||||
@ -264,7 +246,7 @@ try
|
|||||||
}
|
}
|
||||||
]");
|
]");
|
||||||
|
|
||||||
if(!config.GetValue<bool>("DisableMultiLanguage"))
|
if (!config.GetValue<bool>("DisableMultiLanguage"))
|
||||||
app.UseCookieBasedLocalizer(cultures.Languages.ToArray());
|
app.UseCookieBasedLocalizer(cultures.Languages.ToArray());
|
||||||
|
|
||||||
app.UseCors("SameOriginPolicy");
|
app.UseCors("SameOriginPolicy");
|
||||||
@ -273,7 +255,7 @@ try
|
|||||||
app.MapFallbackToController("Error404", "Home");
|
app.MapFallbackToController("Error404", "Home");
|
||||||
app.Run();
|
app.Run();
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.Error(ex, "Stopped program because of exception");
|
logger.Error(ex, "Stopped program because of exception");
|
||||||
throw;
|
throw;
|
||||||
|
|||||||
@ -11,12 +11,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
|||||||
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
||||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||||
<ProjectGuid>5e0e17c0-ff5a-4246-bf87-1add85376a27</ProjectGuid>
|
<ProjectGuid>5e0e17c0-ff5a-4246-bf87-1add85376a27</ProjectGuid>
|
||||||
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\signFLOW\Web\net9\win64\$(Version)\EnvelopeGenerator.Web.zip</DesktopBuildPackageLocation>
|
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\signFLOW\Web\net8\$(Version)\EnvelopeGenerator.Web.zip</DesktopBuildPackageLocation>
|
||||||
<PackageAsSingleFile>true</PackageAsSingleFile>
|
<PackageAsSingleFile>true</PackageAsSingleFile>
|
||||||
<DeployIisAppPath>EnvelopeGenerator</DeployIisAppPath>
|
<DeployIisAppPath>EnvelopeGenerator</DeployIisAppPath>
|
||||||
<_TargetId>IISWebDeployPackage</_TargetId>
|
<_TargetId>IISWebDeployPackage</_TargetId>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
|
||||||
<SelfContained>true</SelfContained>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -79,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<h6>@($"{@envelope?.Message}")</h6>
|
<div class="markdown">@(envelope?.Message)</div>
|
||||||
}
|
}
|
||||||
<p>
|
<p>
|
||||||
<small class="text-body-secondary">
|
<small class="text-body-secondary">
|
||||||
|
|||||||
@ -49,6 +49,7 @@
|
|||||||
<script src="~/lib/bootstrap/dist/js/bootstrap.min.js"></script>
|
<script src="~/lib/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||||
<script src="~/lib/sweetalert2/sweetalert2.min.js"></script>
|
<script src="~/lib/sweetalert2/sweetalert2.min.js"></script>
|
||||||
<script src="~/lib/alertifyjs/alertify.min.js"></script>
|
<script src="~/lib/alertifyjs/alertify.min.js"></script>
|
||||||
|
<script src="~/lib/marked/marked.umd.min.js"></script>
|
||||||
<script src="~/js/lazy.min.js" asp-append-version="true"></script>
|
<script src="~/js/lazy.min.js" asp-append-version="true"></script>
|
||||||
<script src="~/js/ui.min.js" asp-append-version="true"></script>
|
<script src="~/js/ui.min.js" asp-append-version="true"></script>
|
||||||
<script src="~/js/annotation.js" asp-append-version="true"></script>
|
<script src="~/js/annotation.js" asp-append-version="true"></script>
|
||||||
@ -95,5 +96,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<a href="/privacy-policy.@(_localizer.Culture()).html" target="_blank">@_localizer.Privacy()</a>
|
<a href="/privacy-policy.@(_localizer.Culture()).html" target="_blank">@_localizer.Privacy()</a>
|
||||||
</footer>
|
</footer>
|
||||||
|
<script src="~/js/markdown.min.js" asp-append-version="true"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -41,6 +41,12 @@
|
|||||||
"wwwroot/js/util.js"
|
"wwwroot/js/util.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"outputFileName": "wwwroot/js/markdown.min.js",
|
||||||
|
"inputFiles": [
|
||||||
|
"wwwroot/js/markdown.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"outputFileName": "wwwroot/css/error-space.min.css",
|
"outputFileName": "wwwroot/css/error-space.min.css",
|
||||||
"inputFiles": [
|
"inputFiles": [
|
||||||
|
|||||||
@ -255,7 +255,7 @@ class App {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
if (res.status === 403) {
|
if (res.status === 409) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Warnung',
|
title: 'Warnung',
|
||||||
text: 'Umschlag ist nicht mehr verfügbar.',
|
text: 'Umschlag ist nicht mehr verfügbar.',
|
||||||
@ -263,6 +263,17 @@ class App {
|
|||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
else if (res.status === 423) {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Info',
|
||||||
|
text: 'Dokument wurde von einem Empfänger abgelehnt. Sie werden weitergeleitet...',
|
||||||
|
icon: 'info',
|
||||||
|
timer: 2000,
|
||||||
|
showConfirmButton: false
|
||||||
|
}).then(() => {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error()
|
throw new Error()
|
||||||
}
|
}
|
||||||
|
|||||||
2
EnvelopeGenerator.Web/wwwroot/js/app.min.js
vendored
2
EnvelopeGenerator.Web/wwwroot/js/app.min.js
vendored
@ -1,3 +1,3 @@
|
|||||||
class App{constructor(n,t,i,r,u,f){this.container=f??`#${this.constructor.name.toLowerCase()}`;this.envelopeKey=n;this.pdfKit=null;this.currentDocument=t.envelope.documents[0];this.currentReceiver=t.receiver;this.signatureCount=t.envelope.documents[0].elements.length;this.envelopeReceiver=t;this.documentBytes=i;this.licenseKey=r;this.locale=u}async init(){this.pdfKit=await loadPSPDFKit(this.documentBytes,this.container,this.licenseKey,this.locale);addToolbarItems(this.pdfKit,this.handleClick.bind(this));this.pdfKit.addEventListener("annotations.load",this.handleAnnotationsLoad.bind(this));this.pdfKit.addEventListener("annotations.change",this.handleAnnotationsChange.bind(this));this.pdfKit.addEventListener("annotations.create",this.handleAnnotationsCreate.bind(this));this.pdfKit.addEventListener("annotations.willChange",()=>{Comp.ActPanel.Toggle()});try{let n=await createAnnotations(this.currentDocument,this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId);await this.pdfKit.create(n)}catch(n){console.error("Error loading annotations:",n)}[...document.getElementsByClassName("btn_refresh")].forEach(n=>n.addEventListener("click",()=>this.handleClick("RESET")));[...document.getElementsByClassName("btn_complete")].forEach(n=>n.addEventListener("click",()=>this.handleClick("FINISH")));[...document.getElementsByClassName("btn_reject")].forEach(n=>n.addEventListener("click",()=>this.handleClick("REJECT")))}handleAnnotationsLoad(n){n.toJS()}handleAnnotationsChange(){}async handleAnnotationsCreate(n){const t=n.toJS()[0],i=!!t.formFieldName,r=!!t.isSignature;if(i===!1&&r===!0){const r=t.boundingBox.left-20,u=t.boundingBox.top-20,n=150,i=75,f=new Date,e=await createAnnotationFrameBlob(this.envelopeReceiver.name,this.currentReceiver.signature,f,n,i),o=await fetch(e),s=await o.blob(),h=await this.pdfKit.createAttachment(s),c=createImageAnnotation(new PSPDFKit.Geometry.Rect({left:r,top:u,width:n,height:i}),t.pageIndex,h,generateId(this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId,this.fakeElementId--,"signed"));this.pdfKit.create(c)}}async handleClick(n){let t=!1;switch(n){case"RESET":t=await this.handleReset(null);Comp.SignatureProgress.SignedCount=0;t.isConfirmed&&Swal.fire({title:"Erfolg",text:"Dokument wurde zurückgesetzt",icon:"info"});break;case"FINISH":t=await this.handleFinish(null);t==!0&&(window.location.href=`/Envelope/${this.envelopeKey}`);break;case"REJECT":Swal.fire({title:localized.rejection,html:`<div class="text-start fs-6 p-0 m-0">${localized.rejectionReasonQ}</div>`,icon:"question",input:"text",inputAttributes:{autocapitalize:"off"},showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.complete,cancelButtonText:localized.back,showLoaderOnConfirm:!0,preConfirm:async n=>{try{return await rejectEnvelope(n)}catch(t){Swal.showValidationMessage(`
|
class App{constructor(n,t,i,r,u,f){this.container=f??`#${this.constructor.name.toLowerCase()}`;this.envelopeKey=n;this.pdfKit=null;this.currentDocument=t.envelope.documents[0];this.currentReceiver=t.receiver;this.signatureCount=t.envelope.documents[0].elements.length;this.envelopeReceiver=t;this.documentBytes=i;this.licenseKey=r;this.locale=u}async init(){this.pdfKit=await loadPSPDFKit(this.documentBytes,this.container,this.licenseKey,this.locale);addToolbarItems(this.pdfKit,this.handleClick.bind(this));this.pdfKit.addEventListener("annotations.load",this.handleAnnotationsLoad.bind(this));this.pdfKit.addEventListener("annotations.change",this.handleAnnotationsChange.bind(this));this.pdfKit.addEventListener("annotations.create",this.handleAnnotationsCreate.bind(this));this.pdfKit.addEventListener("annotations.willChange",()=>{Comp.ActPanel.Toggle()});try{let n=await createAnnotations(this.currentDocument,this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId);await this.pdfKit.create(n)}catch(n){console.error("Error loading annotations:",n)}[...document.getElementsByClassName("btn_refresh")].forEach(n=>n.addEventListener("click",()=>this.handleClick("RESET")));[...document.getElementsByClassName("btn_complete")].forEach(n=>n.addEventListener("click",()=>this.handleClick("FINISH")));[...document.getElementsByClassName("btn_reject")].forEach(n=>n.addEventListener("click",()=>this.handleClick("REJECT")))}handleAnnotationsLoad(n){n.toJS()}handleAnnotationsChange(){}async handleAnnotationsCreate(n){const t=n.toJS()[0],i=!!t.formFieldName,r=!!t.isSignature;if(i===!1&&r===!0){const r=t.boundingBox.left-20,u=t.boundingBox.top-20,n=150,i=75,f=new Date,e=await createAnnotationFrameBlob(this.envelopeReceiver.name,this.currentReceiver.signature,f,n,i),o=await fetch(e),s=await o.blob(),h=await this.pdfKit.createAttachment(s),c=createImageAnnotation(new PSPDFKit.Geometry.Rect({left:r,top:u,width:n,height:i}),t.pageIndex,h,generateId(this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId,this.fakeElementId--,"signed"));this.pdfKit.create(c)}}async handleClick(n){let t=!1;switch(n){case"RESET":t=await this.handleReset(null);Comp.SignatureProgress.SignedCount=0;t.isConfirmed&&Swal.fire({title:"Erfolg",text:"Dokument wurde zurückgesetzt",icon:"info"});break;case"FINISH":t=await this.handleFinish(null);t==!0&&(window.location.href=`/Envelope/${this.envelopeKey}`);break;case"REJECT":Swal.fire({title:localized.rejection,html:`<div class="text-start fs-6 p-0 m-0">${localized.rejectionReasonQ}</div>`,icon:"question",input:"text",inputAttributes:{autocapitalize:"off"},showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.complete,cancelButtonText:localized.back,showLoaderOnConfirm:!0,preConfirm:async n=>{try{return await rejectEnvelope(n)}catch(t){Swal.showValidationMessage(`
|
||||||
Request failed: ${t}
|
Request failed: ${t}
|
||||||
`)}},allowOutsideClick:()=>!Swal.isLoading()}).then(n=>{if(n.isConfirmed){const t=n.value;t.ok?reload():Swal.showValidationMessage(`Request failed: ${t.message}`)}});break;case"COPY_URL":const n=window.location.href.replace(/\/readonly/gi,"");navigator.clipboard.writeText(n).then(function(){bsNotify("Kopiert",{alert_type:"success",delay:4,icon_name:"check_circle"})}).catch(function(){bsNotify("Unerwarteter Fehler",{alert_type:"danger",delay:4,icon_name:"error"})});break;case"SHARE":Comp.ShareBackdrop.show();break;case"LOGOUT":await logout()}}async handleFinish(){const n=await this.pdfKit.exportInstantJSON(),t=n.formFieldValues,r=t.filter(n=>isFieldRequired(n)),u=r.some(n=>n.value===undefined||n.value===null||n.value==="");if(u)return Swal.fire({title:"Warnung",text:"Bitte füllen Sie alle Standortinformationen vollständig aus!",icon:"warning"}),!1;const f=new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$"),e=t.filter(n=>isCityField(n));for(var i of e)if(!IS_MOBILE_DEVICE&&!f.test(i.value))return Swal.fire({title:"Warnung",text:`Bitte überprüfen Sie die eingegebene Ortsangabe "${i.value}" auf korrekte Formatierung. Beispiele für richtige Formate sind: München, Île-de-France, Sauðárkrókur, San Francisco, St. Catharines usw.`,icon:"warning"}),!1;const o=await this.validateAnnotations(this.signatureCount);return o===!1?(Swal.fire({title:"Warnung",text:"Es wurden nicht alle Signaturfelder ausgefüllt!",icon:"warning"}),!1):Swal.fire({title:localized.confirmation,html:`<div class="text-start fs-6 p-0 m-0">${localized.sigAgree}</div>`,icon:"question",showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.finalize,cancelButtonText:localized.back}).then(async t=>{if(t.isConfirmed){try{await this.pdfKit.save()}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}try{const t=await signEnvelope({instant:n,structured:mapSignature(n)});if(t.ok)return!0;if(t.status===403)return Swal.fire({title:"Warnung",text:"Umschlag ist nicht mehr verfügbar.",icon:"warning"}),!1;throw new Error;}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}}else return!1})}async validateAnnotations(n){const t=await getAnnotations(this.pdfKit),i=t.map(n=>n.toJS()).filter(n=>n.isSignature);return n<=i.length}async handleReset(){const n=Swal.fire({title:"Sind sie sicher?",text:"Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?",icon:"question",showCancelButton:!0});if(n.isConfirmed){const n=await deleteAnnotations(this.pdfKit)}return n}fakeElementId=0;}
|
`)}},allowOutsideClick:()=>!Swal.isLoading()}).then(n=>{if(n.isConfirmed){const t=n.value;t.ok?reload():Swal.showValidationMessage(`Request failed: ${t.message}`)}});break;case"COPY_URL":const n=window.location.href.replace(/\/readonly/gi,"");navigator.clipboard.writeText(n).then(function(){bsNotify("Kopiert",{alert_type:"success",delay:4,icon_name:"check_circle"})}).catch(function(){bsNotify("Unerwarteter Fehler",{alert_type:"danger",delay:4,icon_name:"error"})});break;case"SHARE":Comp.ShareBackdrop.show();break;case"LOGOUT":await logout()}}async handleFinish(){const n=await this.pdfKit.exportInstantJSON(),t=n.formFieldValues,r=t.filter(n=>isFieldRequired(n)),u=r.some(n=>n.value===undefined||n.value===null||n.value==="");if(u)return Swal.fire({title:"Warnung",text:"Bitte füllen Sie alle Standortinformationen vollständig aus!",icon:"warning"}),!1;const f=new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$"),e=t.filter(n=>isCityField(n));for(var i of e)if(!IS_MOBILE_DEVICE&&!f.test(i.value))return Swal.fire({title:"Warnung",text:`Bitte überprüfen Sie die eingegebene Ortsangabe "${i.value}" auf korrekte Formatierung. Beispiele für richtige Formate sind: München, Île-de-France, Sauðárkrókur, San Francisco, St. Catharines usw.`,icon:"warning"}),!1;const o=await this.validateAnnotations(this.signatureCount);return o===!1?(Swal.fire({title:"Warnung",text:"Es wurden nicht alle Signaturfelder ausgefüllt!",icon:"warning"}),!1):Swal.fire({title:localized.confirmation,html:`<div class="text-start fs-6 p-0 m-0">${localized.sigAgree}</div>`,icon:"question",showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.finalize,cancelButtonText:localized.back}).then(async t=>{if(t.isConfirmed){try{await this.pdfKit.save()}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}try{const t=await signEnvelope({instant:n,structured:mapSignature(n)});if(t.ok)return!0;if(t.status===409)return Swal.fire({title:"Warnung",text:"Umschlag ist nicht mehr verfügbar.",icon:"warning"}),!1;if(t.status===423)Swal.fire({title:"Info",text:"Dokument wurde von einem Empfänger abgelehnt. Sie werden weitergeleitet...",icon:"info",timer:2e3,showConfirmButton:!1}).then(()=>{location.reload()});else throw new Error;}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}}else return!1})}async validateAnnotations(n){const t=await getAnnotations(this.pdfKit),i=t.map(n=>n.toJS()).filter(n=>n.isSignature);return n<=i.length}async handleReset(){const n=Swal.fire({title:"Sind sie sicher?",text:"Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?",icon:"question",showCancelButton:!0});if(n.isConfirmed){const n=await deleteAnnotations(this.pdfKit)}return n}fakeElementId=0;}
|
||||||
@ -1,106 +1,111 @@
|
|||||||
//#region parameters
|
//#region parameters
|
||||||
const env = Object.freeze({
|
const env = Object.freeze({
|
||||||
__lazyXsrfToken: new Lazy(() => document.getElementsByName('__RequestVerificationToken')[0].value),
|
__lazyXsrfToken: new Lazy(() => document.getElementsByName('__RequestVerificationToken')[0].value),
|
||||||
get xsrfToken() {
|
get xsrfToken() {
|
||||||
return this.__lazyXsrfToken.value;
|
return this.__lazyXsrfToken.value;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const url = Object.freeze({
|
const url = Object.freeze({
|
||||||
reject: `/api/annotation/reject`,
|
reject: `/api/annotation/reject`,
|
||||||
share: `/api/readonly`
|
share: `/api/readonly`
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region request helper methods
|
//#region request helper methods
|
||||||
function sendRequest(method, url, body = undefined) {
|
function sendRequest(method, url, body = undefined) {
|
||||||
const options = {
|
const urlObj = new URL(url, window.location.origin);
|
||||||
credentials: 'include',
|
if (!urlObj.searchParams.has("envKey")) {
|
||||||
method: method,
|
urlObj.searchParams.set("envKey", ENV_KEY);
|
||||||
headers: {
|
|
||||||
'X-XSRF-TOKEN': env.xsrfToken
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (body !== undefined) {
|
const options = {
|
||||||
options.body = JSON.stringify(body);
|
credentials: 'include',
|
||||||
options.headers['Content-Type'] = 'application/json';
|
method: method,
|
||||||
}
|
headers: {
|
||||||
|
'X-XSRF-TOKEN': env.xsrfToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return fetch(url, options);
|
if (body !== undefined) {
|
||||||
|
options.body = JSON.stringify(body);
|
||||||
|
options.headers['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(urlObj, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRequest(url) {
|
function getRequest(url) {
|
||||||
return sendRequest('GET', url);
|
return sendRequest('GET', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJson(url) {
|
function getJson(url) {
|
||||||
return sendRequest('GET', url).then(res => {
|
return sendRequest('GET', url).then(res => {
|
||||||
if (res.ok)
|
if (res.ok)
|
||||||
return res.json();
|
return res.json();
|
||||||
throw new Error(`Request failed with status ${res.status}`);
|
throw new Error(`Request failed with status ${res.status}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function postRequest(url, body = undefined) {
|
function postRequest(url, body = undefined) {
|
||||||
return sendRequest('POST', url, body);
|
return sendRequest('POST', url, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirect(url) {
|
function redirect(url) {
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region envelope
|
//#region envelope
|
||||||
function signEnvelope(annotations) {
|
function signEnvelope(annotations) {
|
||||||
return postRequest(`/api/annotation`, annotations)
|
return postRequest(`/api/annotation`, annotations)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAnnotationParams(leftInInch = 0, topInInch = 0, inchToPointFactor = 72) {
|
async function getAnnotationParams(leftInInch = 0, topInInch = 0, inchToPointFactor = 72) {
|
||||||
const annotParams = await getJson("/api/Config/Annotations");
|
const annotParams = await getJson("/api/Config/Annotations");
|
||||||
|
|
||||||
for (var key in annotParams) {
|
for (var key in annotParams) {
|
||||||
var annot = annotParams[key];
|
var annot = annotParams[key];
|
||||||
annot.width *= inchToPointFactor;
|
annot.width *= inchToPointFactor;
|
||||||
annot.height *= inchToPointFactor;
|
annot.height *= inchToPointFactor;
|
||||||
annot.left += leftInInch - 0.7;
|
annot.left += leftInInch - 0.7;
|
||||||
annot.left *= inchToPointFactor;
|
annot.left *= inchToPointFactor;
|
||||||
annot.top += topInInch - 0.5;
|
annot.top += topInInch - 0.5;
|
||||||
annot.top *= inchToPointFactor;
|
annot.top *= inchToPointFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
return annotParams;
|
return annotParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rejectEnvelope(reason) {
|
function rejectEnvelope(reason) {
|
||||||
return postRequest(url.reject, reason);
|
return postRequest(url.reject, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shareEnvelope(receiverMail, dateValid) {
|
function shareEnvelope(receiverMail, dateValid) {
|
||||||
return postRequest(url.share, { receiverMail: receiverMail, dateValid: dateValid });
|
return postRequest(url.share, { receiverMail: receiverMail, dateValid: dateValid });
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
async function setLanguage(language) {
|
async function setLanguage(language) {
|
||||||
const hasLang = await getJson('/api/localization/lang')
|
const hasLang = await getJson('/api/localization/lang')
|
||||||
.then(langs => langs.includes(language));
|
.then(langs => langs.includes(language));
|
||||||
|
|
||||||
if (hasLang)
|
if (hasLang)
|
||||||
postRequest(`/api/localization/lang/${language}`)
|
postRequest(`/api/localization/lang/${language}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.redirected)
|
if (response.redirected)
|
||||||
redirect(response.url);
|
redirect(response.url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
return postRequest(`/auth/logout`)
|
return postRequest(`/auth/logout`)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.ok)
|
if (res.ok)
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1 +1 @@
|
|||||||
function sendRequest(n,t,i=undefined){const r={credentials:"include",method:n,headers:{"X-XSRF-TOKEN":env.xsrfToken}};return i!==undefined&&(r.body=JSON.stringify(i),r.headers["Content-Type"]="application/json"),fetch(t,r)}function getRequest(n){return sendRequest("GET",n)}function getJson(n){return sendRequest("GET",n).then(n=>{if(n.ok)return n.json();throw new Error(`Request failed with status ${n.status}`);})}function postRequest(n,t=undefined){return sendRequest("POST",n,t)}function reload(){window.location.reload()}function redirect(n){window.location.href=n}function signEnvelope(n){return postRequest(`/api/annotation`,n)}async function getAnnotationParams(n=0,t=0,i=72){var f,r;const u=await getJson("/api/Config/Annotations");for(f in u)r=u[f],r.width*=i,r.height*=i,r.left+=n-.7,r.left*=i,r.top+=t-.5,r.top*=i;return u}function rejectEnvelope(n){return postRequest(url.reject,n)}function shareEnvelope(n,t){return postRequest(url.share,{receiverMail:n,dateValid:t})}async function setLanguage(n){const t=await getJson("/api/localization/lang").then(t=>t.includes(n));t&&postRequest(`/api/localization/lang/${n}`).then(n=>{n.redirected&&redirect(n.url)})}function logout(){return postRequest(`/auth/logout`).then(n=>{n.ok&&(window.location.href="/")})}const env=Object.freeze({__lazyXsrfToken:new Lazy(()=>document.getElementsByName("__RequestVerificationToken")[0].value),get xsrfToken(){return this.__lazyXsrfToken.value}}),url=Object.freeze({reject:`/api/annotation/reject`,share:`/api/readonly`});
|
function sendRequest(n,t,i=undefined){const r=new URL(t,window.location.origin);r.searchParams.has("envKey")||r.searchParams.set("envKey",ENV_KEY);const u={credentials:"include",method:n,headers:{"X-XSRF-TOKEN":env.xsrfToken}};return i!==undefined&&(u.body=JSON.stringify(i),u.headers["Content-Type"]="application/json"),fetch(r,u)}function getRequest(n){return sendRequest("GET",n)}function getJson(n){return sendRequest("GET",n).then(n=>{if(n.ok)return n.json();throw new Error(`Request failed with status ${n.status}`);})}function postRequest(n,t=undefined){return sendRequest("POST",n,t)}function reload(){window.location.reload()}function redirect(n){window.location.href=n}function signEnvelope(n){return postRequest(`/api/annotation`,n)}async function getAnnotationParams(n=0,t=0,i=72){var f,r;const u=await getJson("/api/Config/Annotations");for(f in u)r=u[f],r.width*=i,r.height*=i,r.left+=n-.7,r.left*=i,r.top+=t-.5,r.top*=i;return u}function rejectEnvelope(n){return postRequest(url.reject,n)}function shareEnvelope(n,t){return postRequest(url.share,{receiverMail:n,dateValid:t})}async function setLanguage(n){const t=await getJson("/api/localization/lang").then(t=>t.includes(n));t&&postRequest(`/api/localization/lang/${n}`).then(n=>{n.redirected&&redirect(n.url)})}function logout(){return postRequest(`/auth/logout`).then(n=>{n.ok&&(window.location.href="/")})}const env=Object.freeze({__lazyXsrfToken:new Lazy(()=>document.getElementsByName("__RequestVerificationToken")[0].value),get xsrfToken(){return this.__lazyXsrfToken.value}}),url=Object.freeze({reject:`/api/annotation/reject`,share:`/api/readonly`});
|
||||||
11
EnvelopeGenerator.Web/wwwroot/js/markdown.js
Normal file
11
EnvelopeGenerator.Web/wwwroot/js/markdown.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
marked.use({
|
||||||
|
async: true,
|
||||||
|
breaks: true,
|
||||||
|
gfm: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for (const el of document.querySelectorAll('.markdown')) {
|
||||||
|
el.innerHTML = await marked.parse(el.textContent.replace(/(\t| )/g, " "));
|
||||||
|
}
|
||||||
|
})();
|
||||||
1
EnvelopeGenerator.Web/wwwroot/js/markdown.min.js
vendored
Normal file
1
EnvelopeGenerator.Web/wwwroot/js/markdown.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
marked.use({"async":!0,breaks:!0,gfm:!0});(async()=>{for(const n of document.querySelectorAll(".markdown"))n.innerHTML=await marked.parse(n.textContent.replace(/(\t| )/g," "))})();
|
||||||
8
EnvelopeGenerator.Web/wwwroot/lib/marked/marked.umd.min.js
vendored
Normal file
8
EnvelopeGenerator.Web/wwwroot/lib/marked/marked.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -4,14 +4,14 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Datenschutzinformation für das Fernsignatursystem signFLOW</title>
|
<title>Datenschutzinformation für das Fernsignatursystem: signFLOW</title>
|
||||||
<link rel="stylesheet" href="css/privacy-policy.min.css">
|
<link rel="stylesheet" href="css/privacy-policy.min.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>Datenschutzinformation für das Fernsignatursystem signFLOW</h1>
|
<h1>Datenschutzinformation für das Fernsignatursystem signFLOW</h1>
|
||||||
<p><strong>Stand:</strong> 19.09.2024</p>
|
<p><strong>Stand:</strong> 18.11.2025</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
@ -55,7 +55,7 @@
|
|||||||
<h2>3. Datenerhebung</h2>
|
<h2>3. Datenerhebung</h2>
|
||||||
<h3>3.1 Die folgenden Kategorien personenbezogener Daten werden verarbeitet</h3>
|
<h3>3.1 Die folgenden Kategorien personenbezogener Daten werden verarbeitet</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Namen: Vor- und Zunamen sowie Ihre digitale Unterschrift</li>
|
<li>Namen: Benutzername, Vor- und Zunamen sowie Ihre digitale Unterschrift</li>
|
||||||
<li>Kontaktdaten: Telefonnummer, Mobilfunknummer und E-Mail-Adresse</li>
|
<li>Kontaktdaten: Telefonnummer, Mobilfunknummer und E-Mail-Adresse</li>
|
||||||
<li>Technische Daten: IP-Adresse, Zeitpunkt des Zugriffs oder Zugriffsversuchs</li>
|
<li>Technische Daten: IP-Adresse, Zeitpunkt des Zugriffs oder Zugriffsversuchs</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -162,138 +162,6 @@
|
|||||||
<a href="https://www.bfdi.bund.de/DE/Service/Anschriften/Laender/Laender-node.html">Laender-node.html</a>
|
<a href="https://www.bfdi.bund.de/DE/Service/Anschriften/Laender/Laender-node.html">Laender-node.html</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>6. Hinweisgebersystem</h2>
|
|
||||||
<p>
|
|
||||||
Die Einhaltung gesetzlicher Vorschriften und interner Richtlinien, einschließlich unseres Verhaltenskodexes
|
|
||||||
sowie des Verhaltenskodexes für Geschäftspartner, hat für uns (die verarbeitende Stelle) oberste Priorität.
|
|
||||||
Dies gilt sowohl für unseren eigenen Geschäftsbereich als auch für unsere Lieferketten.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Es ist uns wichtig, Risiken frühzeitig zu identifizieren und Verstöße zu vermeiden. Wir möchten rechtzeitig
|
|
||||||
geeignete Maßnahmen ergreifen, um mögliche Schäden für Betroffene, Kunden, Mitarbeiter, Geschäftspartner und
|
|
||||||
unsere Unternehmensgruppe zu verhindern.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Aus diesem Grund haben wir ein unabhängiges, neutrales und vertrauliches Hinweisgebersystem eingerichtet,
|
|
||||||
das es internen und externen Hinweisgebenden ermöglicht, auch anonym Meldungen abzugeben. Durch unser
|
|
||||||
transparentes Beschwerdeverfahren bieten wir insbesondere den Betroffenen, den Hinweisgebenden und den
|
|
||||||
Mitarbeitenden, die an der Aufklärung der gemeldeten Vorfälle mitwirken, den größtmöglichen Schutz.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Im Rahmen dieses Verfahrens können alle tatsächlichen und vermeintlichen Verstöße gegen gesetzliche
|
|
||||||
Vorgaben, unseren Verhaltenskodex sowie den Verhaltenskodex für Geschäftspartner gemeldet werden. Auch
|
|
||||||
menschenrechtliche oder umweltbezogene Risiken sowie Pflichtverletzungen entlang der gesamten Lieferkette
|
|
||||||
unserer Konzernunternehmen und in unserem eigenen Geschäftsbereich können Gegenstand einer Meldung sein.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Einheitliche und zügige Prozesse sowie eine vertrauliche und professionelle Bearbeitung der Hinweise durch
|
|
||||||
interne Experten bilden die Grundlage dieses fairen Verfahrens. Benachteiligungen oder Bestrafungen von
|
|
||||||
Hinweisgebenden sowie von Personen, die mit der Bearbeitung von Beschwerden und Hinweisen betraut sind,
|
|
||||||
werden nicht toleriert.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.1 Zweck und Rechtsgrundlage der Datenverarbeitung</h3>
|
|
||||||
<p>
|
|
||||||
Der Zweck der Verarbeitung personenbezogener Daten besteht in der Verwaltung des Hinweisgebersystems, das
|
|
||||||
auch die Aufdeckung schwerwiegender Verstöße oder potenzieller Verstöße gegen geltendes Recht sowie anderer
|
|
||||||
ernsthafter Angelegenheiten umfasst. Die Verarbeitung dieser Daten ist notwendig, um rechtlichen
|
|
||||||
Verpflichtungen nachzukommen, die uns auferlegt sind, gemäß Art. 6 Abs. 1 S. 1 lit. c) DSGVO. Dies
|
|
||||||
bezieht
|
|
||||||
sich auf das Gesetz, das den Schutz von Hinweisgebern verbessert (Hinweisgeberschutzgesetz - HinSchG).
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Zudem dient die Verarbeitung dem berechtigten Interesse, schwerwiegende Verstöße oder mögliche Verstöße
|
|
||||||
gegen geltendes Recht sowie andere ernsthafte Angelegenheiten aufzudecken, gemäß Art. 6 Abs. 1 S. 1 lit.
|
|
||||||
f)
|
|
||||||
DSGVO.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Im Hinblick auf die Verarbeitung besonderer Kategorien personenbezogener Daten ist diese auf Grundlage des
|
|
||||||
Hinweisgeberschutzgesetzes aus Gründen eines erheblichen öffentlichen Interesses erforderlich, gemäß Art. 9
|
|
||||||
Abs. 2 lit. g) DSGVO. Die Verarbeitung dieser besonderen Daten erfolgt gemäß Art. 9 Abs. 2 lit. f)
|
|
||||||
DSGVO in
|
|
||||||
Verbindung mit Art. 6 Abs. 1 S. 1 lit. f) DSGVO, um Rechtsansprüche festzustellen, auszuüben oder zu
|
|
||||||
verteidigen.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Betroffene Personen sind diejenigen, über die eine Meldung gemacht wird. Dies können Mitarbeiter,
|
|
||||||
Vertragspartner oder andere Personen sein, die in beruflicher Verbindung zu der verarbeitenden Stelle
|
|
||||||
stehen. Darüber hinaus verarbeiten wir personenbezogene Daten der hinweisgebenden Person, wenn diese ihre
|
|
||||||
Kontaktinformationen oder andere identifizierende Informationen übermittelt. Hinweisgebende Personen sollten
|
|
||||||
sich daher bewusst sein, dass wir im Rahmen der Bearbeitung des gemeldeten Falls personenbezogene Daten über
|
|
||||||
sie verarbeiten können.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.2 Kategorien personenbezogener Daten</h3>
|
|
||||||
<p>
|
|
||||||
Die Meldung kann anonym erfolgen, wodurch keine personenbezogenen Daten der meldenden Person verarbeitet
|
|
||||||
werden. Die Art der personenbezogenen Daten, die verarbeitet werden, hängt von den übermittelten
|
|
||||||
Informationen ab. Wenn die meldende Person personenbezogene Daten über eine andere Person, einschließlich
|
|
||||||
der gemeldeten Person oder Personen, angibt, werden auch diese Daten verarbeitet. Folgende Kategorien von
|
|
||||||
personenbezogenen Daten können verarbeitet werden:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>Allgemeine personenbezogene Daten (z.B.: Vorname, Nachname, Adresse, E-Mail-Adresse, Telefonnummer,
|
|
||||||
usw.)</li>
|
|
||||||
<li>Personenbezogene Daten zu strafrechtlichen Verurteilungen oder Verdachtsmomenten</li>
|
|
||||||
<li>Besondere Kategorien personenbezogener Daten (Informationen über rassische oder ethnische Herkunft,
|
|
||||||
politische Meinungen, religiöse oder philosophische Überzeugungen, Gewerkschaftszugehörigkeit,
|
|
||||||
Gesundheitsdaten sowie Informationen über das Sexualleben oder die sexuelle Orientierung einer Person)
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
Wir bitten die meldende Person, ausschließlich Informationen zu übermitteln, die für den jeweiligen Fall von
|
|
||||||
Bedeutung sind, und insbesondere keine sensiblen Informationen zu melden, es sei denn, diese sind für die
|
|
||||||
Bearbeitung des gemeldeten Falls von zentraler Relevanz.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.3 Verpflichtung zur Bereitstellung personenbezogener Daten</h3>
|
|
||||||
<p>
|
|
||||||
Es ist nicht erforderlich, die im Abschnitt 6.2 genannten personenbezogenen Daten bereitzustellen, da auch
|
|
||||||
eine anonyme Meldung möglich ist. Bitte beachte jedoch, dass wir möglicherweise nicht in der Lage sind, die
|
|
||||||
Meldung zu bearbeiten, wenn keine personenbezogenen Daten angegeben werden.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.4 Empfänger personenbezogener Daten</h3>
|
|
||||||
<p>Die Meldungen werden bei der verarbeitenden Stelle im System als Vorgänge erfasst. Nach einer Bewertung
|
|
||||||
werden diese Vorgänge intern an die zuständigen Fachabteilungen weitergeleitet, und gegebenenfalls werden
|
|
||||||
Folgemaßnahmen eingeleitet. Sollte eine Meldung eine der Konzerngesellschaften der verarbeitenden Stelle
|
|
||||||
betreffen, werden die relevanten Vorgänge an die zuständigen Personen der jeweiligen Gesellschaft
|
|
||||||
weitergegeben, die dann intern eine Bewertung vornehmen und gegebenenfalls Maßnahmen ergreifen. Bei der
|
|
||||||
Weitergabe personenbezogener Daten wird der Grundsatz der Datenminimierung beachtet, was bedeutet, dass nur
|
|
||||||
die unbedingt notwendigen Daten zur Bearbeitung der Meldung weitergegeben werden.</p>
|
|
||||||
<p>Personenbezogene Daten der hinweisgebenden Person werden an Behörden weitergeleitet, wenn dies erforderlich
|
|
||||||
ist, um schwerwiegende Verstöße oder Angelegenheiten zu behandeln oder das Recht auf Verteidigung der
|
|
||||||
betroffenen Personen zu sichern. In anderen Fällen erfolgt die Weitergabe personenbezogener Daten der
|
|
||||||
hinweisgebenden Person nur mit deren Zustimmung. Daten über andere Personen als die hinweisgebende Person
|
|
||||||
werden nur im Rahmen der Nachverfolgung eines gemeldeten Falls oder zur Bearbeitung schwerwiegender Verstöße
|
|
||||||
oder Angelegenheiten weitergegeben.</p>
|
|
||||||
<p>Die Meldeplattform wird von dem Auftragsverarbeiter WhistleB Whistleblowing Centre AB mit Sitz in Stockholm,
|
|
||||||
Schweden, bereitgestellt. Weitere Informationen zu WhistleB und den entsprechenden Nutzungsbedingungen sind
|
|
||||||
dort einsehbar.
|
|
||||||
<a
|
|
||||||
href="https://report.whistleb.com/content/documents/whistleb_terms_of_use.pdf">whistleb_terms_of_use.pdf</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.5 Speicherdauer</h3>
|
|
||||||
<p>Personenbezogene Daten, die sich als nicht relevant für die Bearbeitung eines gemeldeten Falls herausstellen,
|
|
||||||
sowie Meldungen, die wir als unbegründet ansehen, werden umgehend als "nicht relevant" eingestuft. In diesem
|
|
||||||
Fall wird der Personenbezug entfernt, es sei denn, es handelt sich bereits um eine anonyme Meldung. Um die
|
|
||||||
gesetzlich vorgeschriebene Dokumentationspflicht und die Löschfristen gemäß § 11 Abs. 1 und Abs. 5 HinSchG
|
|
||||||
zu erfüllen, wird die Meldung zunächst ohne Personenbezug archiviert, jedoch noch nicht gelöscht.
|
|
||||||
Archivierte Fälle dienen ausschließlich der Erfüllung dieser Dokumentationspflichten und können danach nicht
|
|
||||||
mehr zur Bearbeitung herangezogen werden.</p>
|
|
||||||
<p>Die Meldungen und personenbezogenen Daten, die im Zuge der Bearbeitung einer Meldung erfasst werden, bilden
|
|
||||||
die Grundlage für die weitere Bearbeitung und werden so schnell wie möglich anonymisiert. Sollte es
|
|
||||||
notwendig sein, Folgemaßnahmen gemäß §§ 3 Abs. 8 und 18 HinSchG zu ergreifen, kann es jedoch erforderlich
|
|
||||||
sein, von der Anonymisierung abzuweichen, sei es aufgrund behördlicher Anordnungen oder zur Wahrung von
|
|
||||||
Rechtsansprüchen. In solchen Fällen wird in der Regel eine Pseudonymisierung angestrebt, es sei denn, es
|
|
||||||
gibt andere Vorgaben, wie etwa eine richterliche Anordnung. Die Dokumentation wird drei Jahre nach Abschluss
|
|
||||||
des Verfahrens gelöscht. Sie kann jedoch länger aufbewahrt werden, um den Anforderungen dieses Gesetzes oder
|
|
||||||
anderer Rechtsvorschriften gerecht zu werden, solange dies notwendig und angemessen ist.</p>
|
|
||||||
</section>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>Data Protection Information for the Remote Signature System signFLOW</h1>
|
<h1>Data Protection Information for the Remote Signature System: signFLOW</h1>
|
||||||
<p><strong>As of:</strong> 19.09.2024</p>
|
<p><strong>As of:</strong> 18.11.2025</p>
|
||||||
</header>
|
</header>
|
||||||
<section>
|
<section>
|
||||||
<h2>1. General Information</h2>
|
<h2>1. General Information</h2>
|
||||||
@ -53,7 +53,7 @@
|
|||||||
<h2>3. Data Collection</h2>
|
<h2>3. Data Collection</h2>
|
||||||
<h3>3.1 The following categories of personal data are processed</h3>
|
<h3>3.1 The following categories of personal data are processed</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Names: First and last names as well as your digital signature</li>
|
<li>Names: Username, first and last names as well as your digital signature</li>
|
||||||
<li>Contact details: Phone number, mobile phone number, and email address</li>
|
<li>Contact details: Phone number, mobile phone number, and email address</li>
|
||||||
<li>Technical data: IP address, time of access, or access attempts</li>
|
<li>Technical data: IP address, time of access, or access attempts</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -153,133 +153,6 @@
|
|||||||
<a href="https://www.bfdi.bund.de/DE/Service/Anschriften/Laender/Laender-node.html">Laender-node.html</a>
|
<a href="https://www.bfdi.bund.de/DE/Service/Anschriften/Laender/Laender-node.html">Laender-node.html</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>6. Whistleblower System</h2>
|
|
||||||
<p>
|
|
||||||
Compliance with legal regulations and internal guidelines, including our Code of Conduct and the Code of
|
|
||||||
Conduct for Business Partners, is our (the data processing entity's) top priority. This applies both to our
|
|
||||||
own business operations and to our supply chains.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
It is important to us to identify risks early and avoid violations. We aim to take appropriate measures in a
|
|
||||||
timely manner to prevent potential harm to affected persons, customers, employees, business partners, and
|
|
||||||
our corporate group.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
For this reason, we have established an independent, neutral, and confidential whistleblower system that
|
|
||||||
enables internal and external whistleblowers to submit reports, including anonymously. Through our
|
|
||||||
transparent complaint procedure, we offer the greatest possible protection, especially to the affected
|
|
||||||
persons, whistleblowers, and employees involved in investigating reported incidents.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Under this procedure, any actual or alleged violations of legal requirements, our Code of Conduct, or the
|
|
||||||
Code of Conduct for Business Partners may be reported. Human rights or environmental risks, as well as
|
|
||||||
breaches of duty along the entire supply chain of our group companies and in our own business operations,
|
|
||||||
can also be the subject of a report.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Standardized and swift processes, as well as confidential and professional handling of the reports by
|
|
||||||
internal experts, form the basis of this fair procedure. Discrimination or punishment of whistleblowers and
|
|
||||||
individuals responsible for handling complaints and reports will not be tolerated.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.1 Purpose and Legal Basis of Data Processing</h3>
|
|
||||||
<p>
|
|
||||||
The purpose of processing personal data is to manage the whistleblower system, which also includes
|
|
||||||
identifying serious violations or potential violations of applicable law and other serious matters. The
|
|
||||||
processing of this data is necessary to comply with legal obligations imposed on us, in accordance with Art.
|
|
||||||
6 para. 1 sentence 1 lit. c) GDPR. This refers to the law that enhances the protection of whistleblowers
|
|
||||||
(Whistleblower Protection Act - HinSchG).
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Additionally, the processing serves the legitimate interest of identifying serious violations or potential
|
|
||||||
violations of applicable law and other serious matters, in accordance with Art. 6 para. 1 sentence 1 lit. f)
|
|
||||||
GDPR.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Regarding the processing of special categories of personal data, this is necessary based on the
|
|
||||||
Whistleblower Protection Act for reasons of significant public interest, in accordance with Art. 9 para. 2
|
|
||||||
lit. g) GDPR. The processing of such special data is carried out in accordance with Art. 9 para. 2 lit. f)
|
|
||||||
GDPR in conjunction with Art. 6 para. 1 sentence 1 lit. f) GDPR to establish, exercise, or defend legal
|
|
||||||
claims.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Affected persons are those about whom a report is made. These can be employees, contractors, or other
|
|
||||||
individuals in a business relationship with the data processing entity. Furthermore, we process personal
|
|
||||||
data of the whistleblower if they provide their contact details or other identifying information.
|
|
||||||
Whistleblowers should be aware that we may process personal data about them during the handling of the
|
|
||||||
reported case.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.2 Categories of Personal Data</h3>
|
|
||||||
<p>
|
|
||||||
Reports can be made anonymously, in which case no personal data of the reporting person will be processed.
|
|
||||||
The type of personal data processed depends on the information provided. If the reporting person provides
|
|
||||||
personal data about another individual, including the reported individual or persons, that data will also be
|
|
||||||
processed. The following categories of personal data may be processed:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>General personal data (e.g., first name, last name, address, email address, phone number, etc.)</li>
|
|
||||||
<li>Personal data related to criminal convictions or suspicions</li>
|
|
||||||
<li>Special categories of personal data (information about racial or ethnic origin, political opinions,
|
|
||||||
religious or philosophical beliefs, trade union membership, health data, and information about a
|
|
||||||
person's sex life or sexual orientation)</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
We ask the reporting person to only provide information relevant to the case and to avoid reporting
|
|
||||||
sensitive information unless it is essential for handling the reported case.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.3 Obligation to Provide Personal Data</h3>
|
|
||||||
<p>
|
|
||||||
It is not mandatory to provide the personal data mentioned in section 6.2, as anonymous reporting is also
|
|
||||||
possible. However, please note that we may be unable to process the report if no personal data is provided.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.4 Recipients of Personal Data</h3>
|
|
||||||
<p>
|
|
||||||
Reports are logged in the system of the data processing entity as cases. After evaluation, these cases are
|
|
||||||
forwarded internally to the relevant departments, and follow-up actions may be initiated. If a report
|
|
||||||
involves one of the group companies of the data processing entity, the relevant cases are forwarded to the
|
|
||||||
responsible individuals at the respective company, who will then conduct an internal evaluation and take
|
|
||||||
action if necessary. When transferring personal data, the principle of data minimization is observed,
|
|
||||||
meaning only the data strictly necessary for handling the report is shared.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Personal data of the whistleblower will be shared with authorities when necessary to address serious
|
|
||||||
violations or issues, or to safeguard the right to defense of the affected persons. In other cases, personal
|
|
||||||
data of the whistleblower will only be shared with their consent. Data about persons other than the
|
|
||||||
whistleblower will only be shared in connection with the investigation of a reported case or to address
|
|
||||||
serious violations or issues.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The reporting platform is provided by the processor WhistleB Whistleblowing Centre AB, based in Stockholm,
|
|
||||||
Sweden. Further information about WhistleB and the corresponding terms of use can be found at:
|
|
||||||
<a
|
|
||||||
href="https://report.whistleb.com/content/documents/whistleb_terms_of_use.pdf">whistleb_terms_of_use.pdf</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>6.5 Retention Period</h3>
|
|
||||||
<p>
|
|
||||||
Personal data that is found to be irrelevant to the processing of a reported case, as well as reports deemed
|
|
||||||
unfounded, will be immediately classified as "not relevant." In this case, the personal reference is removed
|
|
||||||
unless the report was anonymous from the outset. To meet the legally required documentation obligations and
|
|
||||||
deletion periods pursuant to § 11 para. 1 and para. 5 HinSchG, the report is initially archived without
|
|
||||||
personal reference but is not yet deleted. Archived cases serve solely to fulfill these documentation
|
|
||||||
obligations and can no longer be used for further processing.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Reports and personal data collected during the processing of a report form the basis for further handling
|
|
||||||
and are anonymized as soon as possible. However, if it is necessary to take follow-up actions pursuant to §§
|
|
||||||
3 para. 8 and 18 HinSchG, it may be necessary to deviate from anonymization, whether due to official orders
|
|
||||||
or to protect legal claims. In such cases, pseudonymization is generally sought, unless other directives
|
|
||||||
apply, such as a court order. Documentation is deleted three years after the conclusion of the process, but
|
|
||||||
it may be retained longer if required to meet the requirements of this law or other legal provisions, as
|
|
||||||
long as it remains necessary and appropriate.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user