Refaktorisierung: Absicherung von DB-Operationen und Verbesserung der Geschäftslogik

- Implementierung von LINQ-Abfragen innerhalb der Core-Bibliothek zur Minderung von SQL-Injection-Anfälligkeiten für DB-Operationen von Umschlägen und Empfängern.
- Aktualisierung der Geschäftslogik in der Service-Schicht für verbessertes Transaktionshandling.
- Erweiterung der ServiceMessage um eine neue Flag-Funktion zum Verfolgen von Cybersecurity- und Datenintegritätsproblemen.
- Hinzufügen spezifischer Benutzerverhaltensflags zur besseren Erkennung und Behandlung potenzieller Datenverletzungen.
This commit is contained in:
Developer 02
2024-04-24 13:45:03 +02:00
parent f2e718565d
commit 6338b81571
47 changed files with 644 additions and 310 deletions

View File

@@ -2,10 +2,14 @@
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeHistoryService : IBasicCRUDService<IEnvelopeHistoryRepository, EnvelopeHistoryDto, EnvelopeHistory, long>
{
Task<int> CountAsync(int? envelopeId = null, string? userReference = null, int? status = null);
Task<bool> AccessCodeAlreadyRequested(int envelopeId, string userReference);
}
}

View File

@@ -7,6 +7,17 @@ namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeReceiverService : IBasicCRUDService<IEnvelopeReceiverRepository, EnvelopeReceiverDto, EnvelopeReceiver, int>
{
Task<IServiceMessage> VerifyAccessCode(string envelopeUuid, string accessCode);
Task<IServiceResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false);
Task<IServiceResult<IEnumerable<EnvelopeReceiverDto>>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true);
Task<IServiceResult<EnvelopeReceiverDto>> ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true);
Task<IServiceResult<EnvelopeReceiverDto>> ReadByEnvelopeReceiverIdAsync(string envelopeReceiverId, bool withEnvelope = true, bool withReceiver = true);
Task<IServiceResult<bool>> VerifyAccessCodeAsync(string uuid, string signature, string accessCode);
Task<IServiceResult<bool>> VerifyAccessCodeAsync(string envelopeReceiverId, string accessCode);
}
}

View File

@@ -7,8 +7,8 @@ namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeService : IBasicCRUDService<IEnvelopeRepository, EnvelopeDto, Envelope, int>
{
Task<IServiceResult<IEnumerable<EnvelopeDto>>> ReadAllWithAsync(bool documents = false, bool receivers = false, bool history = false, bool documentReceiverElement = false);
Task<IServiceResult<IEnumerable<EnvelopeDto>>> ReadAllWithAsync(bool documents = false, bool envelopeReceivers = false, bool history = false, bool documentReceiverElement = false);
Task<IServiceResult<EnvelopeDto>> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false);
Task<IServiceResult<EnvelopeDto>> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withEnvelopeReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withUser = false, bool withAll = false);
}
}

View File

@@ -1,4 +1,5 @@
using EnvelopeGenerator.Domain.Entities;
using DigitalData.UserManager.Domain.Entities;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.DTOs
{
@@ -26,13 +27,13 @@ namespace EnvelopeGenerator.Application.DTOs
int? ExpiresWhenDays,
int? ExpiresWarningWhenDays,
bool DmzMoved,
ReceiverDto? User,
User? User,
EnvelopeType? EnvelopeType,
string? EnvelopeTypeTitle,
bool IsAlreadySent,
string? StatusTranslated,
string? ContractTypeTranslated,
IEnumerable<EnvelopeDocumentDto>? Documents,
IEnumerable<EnvelopeReceiverDto>? Receivers,
IEnumerable<EnvelopeReceiverDto>? EnvelopeReceivers,
IEnumerable<EnvelopeHistoryDto>? History);
}

View File

@@ -6,10 +6,10 @@ namespace EnvelopeGenerator.Application.DTOs
int EnvelopeId,
int ReceiverId,
int Sequence,
string Name,
string JobTitle,
string CompanyName,
string PrivateMessage,
string? Name,
string? JobTitle,
string? CompanyName,
string? PrivateMessage,
DateTime AddedWhen,
DateTime? ChangedWhen,
Envelope? Envelope,

View File

@@ -4,6 +4,6 @@
int Id,
string EmailAddress,
string Signature,
DateTime AddedWhen,
IEnumerable<EnvelopeReceiverDto>? EnvelopeReceivers);
DateTime AddedWhen
);
}

View File

@@ -0,0 +1,7 @@
namespace EnvelopeGenerator.Application
{
public enum EnvelopeFlag
{
EnvelopeOrReceiverNonexists
}
}

View File

@@ -22,6 +22,18 @@
<Reference Include="DigitalData.Core.Contracts">
<HintPath>..\..\WebCoreModules\DigitalData.Core.Application\bin\Debug\net7.0\DigitalData.Core.Contracts.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Core.Infrastructure">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.Core.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Application">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Domain">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Infrastructure">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,39 @@
using EnvelopeGenerator.Application.Services;
using Microsoft.Extensions.Logging;
using System.Text;
namespace EnvelopeGenerator.Application
{
public static class EnvelopeGeneratorExtensions
{
public static void LogEnvelopeError(this ILogger logger, string receiverId, string? message, params object?[] args)
{
(string? envelopeUuid, string? receiverSignature) = receiverId.DecodeEnvelopeReceiverId();
var sb = new StringBuilder($"Envelope Uuid: {envelopeUuid}\nReceiver Signature: {receiverSignature}");
if (message is not null)
sb.AppendLine().Append(message);
logger.Log(LogLevel.Error, sb.ToString(), args);
}
public static void LogEnvelopeError(this ILogger logger, string uuid, string? signature = null, string? message = null, params object?[] args)
{
var sb = new StringBuilder($"Envelope Uuid: {uuid}");
if(signature is not null)
sb.AppendLine().Append($"Receiver Signature: {signature}");
if (message is not null)
sb.AppendLine().Append(message);
logger.Log(LogLevel.Error, sb.ToString(), args);
}
public static string ToTitle(this (string? UUID, string? Signature) envelopeReceiverTuple)
{
return $"Envelope UUID: {envelopeReceiverTuple.UUID}\n Receiver Signature: {envelopeReceiverTuple.Signature}";
}
}
}

View File

@@ -0,0 +1,19 @@
namespace EnvelopeGenerator.Application
{
public enum MessageKey
{
EnvelopeNotFound,
EnvelopeReceiverNotFound,
AccessCodeNull2Client,
AccessCodeNull2Logger,
WrongAccessCode,
DataIntegrityIssue,
SecurityBreachOrDataIntegrity,
PossibleDataIntegrityIssue,
SecurityBreach,
PossibleSecurityBreach,
WrongEnvelopeReceiverId2Client, //Do not leak information about the creation of the url. For example, the envelope you are looking for does not exist
WrongEnvelopeReceiverId2Logger,
EnvelopeOrReceiverNonexists
}
}

View File

@@ -5,13 +5,76 @@
/// </summary>
public static class EnvelopeGeneratorExtensions
{
/// <summary>
/// Validates whether a given string is a correctly formatted Base-64 encoded string.
/// </summary>
/// <remarks>
/// This method checks the string for proper Base-64 formatting, which includes validating
/// the length of the string (must be divisible by 4). It also checks each character to ensure
/// it belongs to the Base-64 character set (A-Z, a-z, 0-9, '+', '/', and '=' for padding).
/// The method ensures that padding characters ('=') only appear at the end of the string and
/// are in a valid configuration (either one '=' at the end if the string's length % 4 is 3,
/// or two '==' if the length % 4 is 2).
/// </remarks>
/// <param name="input">The Base-64 encoded string to validate.</param>
/// <returns>
/// <c>true</c> if the string is a valid Base-64 encoded string; otherwise, <c>false</c>.
/// </returns>
/// <example>
/// <code>
/// string testString = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk=";
/// bool isValid = IsValidBase64String(testString);
/// Console.WriteLine(isValid); // Output: true
/// </code>
/// </example>
public static bool IsBase64String(this string input)
{
// Check if the string is null or empty
if (string.IsNullOrEmpty(input))
{
return false;
}
// Replace valid base-64 padding
input = input.Trim();
int mod4 = input.Length % 4;
if (mod4 > 0)
{
// Base-64 string lengths should be divisible by 4
return false;
}
// Check each character to ensure it is valid base-64
foreach (char c in input)
{
if (!char.IsLetterOrDigit(c) && c != '+' && c != '/' && c != '=')
{
// Invalid character detected
return false;
}
}
// Ensure no invalid padding scenarios exist
if (input.EndsWith("==") && (input.Length % 4 == 0) ||
input.EndsWith("=") && (input.Length % 4 == 3))
{
return true;
}
return input.IndexOf('=') == -1; // No padding allowed except at the end
}
/// <summary>
/// Decodes the envelope receiver ID and extracts the envelope UUID and receiver signature.
/// </summary>
/// <param name="envelopeReceiverId">The base64 encoded string containing the envelope UUID and receiver signature.</param>
/// <returns>A tuple containing the envelope UUID and receiver signature.</returns>
public static (string EnvelopeUuid, string ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId)
public static (string? EnvelopeUuid, string? ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId)
{
if (!envelopeReceiverId.IsBase64String())
{
return (null, null);
}
byte[] bytes = Convert.FromBase64String(envelopeReceiverId);
string decodedString = System.Text.Encoding.UTF8.GetString(bytes);
string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None);

View File

@@ -5,6 +5,7 @@ using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Application.Services
{
@@ -14,5 +15,9 @@ namespace EnvelopeGenerator.Application.Services
: base(repository, translationService, mapper)
{
}
public async Task<int> CountAsync(int? envelopeId = null, string? userReference = null, int? status = null) => await _repository.CountAsync(envelopeId: envelopeId, userReference: userReference, status: status);
public async Task<bool> AccessCodeAlreadyRequested(int envelopeId, string userReference) => await _repository.CountAsync(envelopeId: envelopeId, userReference:userReference, status: (int) EnvelopeStatus.AccessCodeRequested) > 0;
}
}

View File

@@ -6,7 +6,6 @@ using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using Microsoft.EntityFrameworkCore;
namespace EnvelopeGenerator.Application.Services
{
@@ -17,10 +16,84 @@ namespace EnvelopeGenerator.Application.Services
{
}
public async Task<IServiceMessage> VerifyAccessCode(string envelopeUuid, string accessCode)
public async Task<IServiceResult<IEnumerable<EnvelopeReceiverDto>>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true)
{
var envelopeAccessCode = await _repository.ReadAccessCodeByEnvelopeUuid(envelopeUuid);
return CreateMessage(isSuccess: accessCode == envelopeAccessCode) ;
var env_rcvs = await _repository.ReadBySignatureAsync(signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver);
return Successful(_mapper.MapOrThrow<IEnumerable<EnvelopeReceiverDto>>(env_rcvs));
}
public async Task<IServiceResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false)
{
var env_rcvs = await _repository.ReadByUuidAsync(uuid: uuid, withEnvelope: withEnvelope, withReceiver: withReceiver);
return Successful(_mapper.MapOrThrow<IEnumerable<EnvelopeReceiverDto>>(env_rcvs));
}
public async Task<IServiceResult<EnvelopeReceiverDto>> ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true)
{
var env_rcv = await _repository.ReadByUuidSignatureAsync(uuid: uuid, signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver);
if (env_rcv is null)
return Failed<EnvelopeReceiverDto>()
.WithClientMessageKey(MessageKey.EnvelopeReceiverNotFound);
return Successful(_mapper.MapOrThrow<EnvelopeReceiverDto>(env_rcv));
}
public async Task<IServiceResult<EnvelopeReceiverDto>> ReadByEnvelopeReceiverIdAsync(string envelopeReceiverId, bool withEnvelope = true, bool withReceiver = true)
{
(string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId();
if (uuid is null || signature is null)
return Failed<EnvelopeReceiverDto>()
.WithClientMessageKey(MessageKey.WrongEnvelopeReceiverId2Client)
.WithWarningMessage((uuid, signature).ToTitle())
.WithWarningMessageKey(MessageKey.WrongEnvelopeReceiverId2Logger)
.WithWarningMessageKey(MessageKey.PossibleSecurityBreach)
.WithFlag(Flag.PossibleSecurityBreach);
return await ReadByUuidSignatureAsync(uuid: uuid, signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver);
}
public async Task<IServiceResult<bool>> VerifyAccessCodeAsync(string uuid, string signature, string accessCode)
{
var er = await _repository.ReadByUuidSignatureAsync(uuid: uuid, signature: signature);
if (er is null)
return Failed<bool>()
.WithClientMessageKey(MessageKey.EnvelopeOrReceiverNonexists)
.WithWarningMessage((uuid, signature).ToTitle())
.WithWarningMessageKey(MessageKey.EnvelopeOrReceiverNonexists)
.WithWarningMessageKey(MessageKey.PossibleDataIntegrityIssue)
.WithFlag(MessageKey.PossibleDataIntegrityIssue);
var actualAccessCode = er.AccessCode;
if (actualAccessCode is null)
return Failed<bool>()
.WithClientMessageKey(MessageKey.AccessCodeNull2Client)
.WithCriticalMessage((uuid, signature).ToTitle())
.WithCriticalMessageKey(MessageKey.AccessCodeNull2Logger)
.WithCriticalMessageKey(MessageKey.DataIntegrityIssue)
.WithFlag(Flag.DataIntegrityIssue);
else if(accessCode != actualAccessCode)
return Successful(false).WithClientMessageKey(MessageKey.WrongAccessCode);
else
return Successful(true);
}
public async Task<IServiceResult<bool>> VerifyAccessCodeAsync(string envelopeReceiverId, string accessCode)
{
(string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId();
if (uuid is null || signature is null)
return Failed<bool>()
.WithClientMessageKey(MessageKey.WrongEnvelopeReceiverId2Client)
.WithCriticalMessageKey(MessageKey.WrongEnvelopeReceiverId2Logger)
.WithCriticalMessageKey(MessageKey.SecurityBreach)
.WithCriticalMessage("Attempt to verify access code detected. Such actions are generally not initiated by well-intentioned users. Potential security breach suspected. Immediate investigation required.")
.WithFlag(Flag.SecurityBreach);
return await VerifyAccessCodeAsync(uuid: uuid, signature: signature, accessCode: accessCode);
}
}
}

View File

@@ -6,26 +6,29 @@ using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using Microsoft.Extensions.Logging;
namespace EnvelopeGenerator.Application.Services
{
public class EnvelopeService : BasicCRUDService<IEnvelopeRepository, EnvelopeDto, Envelope, int>, IEnvelopeService
{
public EnvelopeService(IEnvelopeRepository repository, IKeyTranslationService translationService, IMapper mapper)
private readonly ILogger _logger;
public EnvelopeService(IEnvelopeRepository repository, IKeyTranslationService translationService, IMapper mapper, ILogger<EnvelopeService> logger)
: base(repository, translationService, mapper)
{
_logger = logger;
}
public async Task<IServiceResult<IEnumerable<EnvelopeDto>>> ReadAllWithAsync(bool documents = false, bool receivers = false, bool history = false, bool documentReceiverElement = false)
public async Task<IServiceResult<IEnumerable<EnvelopeDto>>> ReadAllWithAsync(bool documents = false, bool envelopeReceivers = false, bool history = false, bool documentReceiverElement = false)
{
var envelopes = await _repository.ReadAllWithAsync(documents: documents, receivers: receivers, history: history, documentReceiverElement: documentReceiverElement);
var envelopes = await _repository.ReadAllWithAsync(documents: documents, envelopeReceivers: envelopeReceivers, history: history, documentReceiverElement: documentReceiverElement);
var readDto = _mapper.MapOrThrow<IEnumerable<EnvelopeDto>>(envelopes);
return Successful(readDto);
}
public async Task<IServiceResult<EnvelopeDto>> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false)
public async Task<IServiceResult<EnvelopeDto>> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withEnvelopeReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withUser = false, bool withAll = false)
{
var envelope = await _repository.ReadByUuidAsync(uuid: uuid, signature: signature, withDocuments: withDocuments, withReceivers: withReceivers, withHistory: withHistory, withDocumentReceiverElement: withDocumentReceiverElement, withAll:withAll);
var envelope = await _repository.ReadByUuidAsync(uuid: uuid, signature: signature, withDocuments: withDocuments, withEnvelopeReceivers: withEnvelopeReceivers, withHistory: withHistory, withDocumentReceiverElement: withDocumentReceiverElement, withUser:withUser, withAll:withAll);
if (envelope is null)
return Failed<EnvelopeDto>();