Refactor PDF signing logic and appearance handling
Refactored `Pkcs11Options` by removing `DefaultCertificationLevel` and improving property documentation. Replaced `VisualSignature` in `PdfSignOptionsDto` with `Appearance` using the new `PdfSignatureAppearanceDto` class, which encapsulates signature appearance details, including font and color settings. Enhanced `PdfSignatureCertificationLevel` enum with more descriptive values and added XML documentation. Updated `IPdfDigitalSignatureService` to support optional signature images and removed redundant methods. Refactored `PdfDigitalSignatureService` to streamline appearance handling, validate inputs, and improve maintainability. Removed unused methods and added support for `PdfSignatureAppearanceDto`. Simplified `PdfSignatureController` by consolidating form properties into `PdfSignOptionsDto`. Improved code readability and flexibility while removing redundant logic.
This commit is contained in:
@@ -91,9 +91,4 @@ public sealed class Pkcs11Options
|
||||
/// <c>true</c> uses <see cref="DateTimeOffset.UtcNow"/>, <c>false</c> uses <see cref="DateTimeOffset.Now"/>.
|
||||
/// </summary>
|
||||
public bool UseUtcSigningTime { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Optional global default certification level used when request-level options do not specify a value.
|
||||
/// </summary>
|
||||
public PdfSignatureCertificationLevel? DefaultCertificationLevel { get; set; }
|
||||
}
|
||||
|
||||
@@ -50,5 +50,5 @@ public sealed record PdfSignOptionsDto
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public PdfVisualSignatureOptionsDto VisualSignature { get; set; } = null!;
|
||||
public PdfSignatureAppearanceDto? Appearance { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ImageBytes"></param>
|
||||
/// <param name="PageNumber"></param>
|
||||
/// <param name="Left"></param>
|
||||
/// <param name="Bottom"></param>
|
||||
/// <param name="Width"></param>
|
||||
/// <param name="Height"></param>
|
||||
/// <param name="ShowLabels"></param>
|
||||
/// <param name="ShowName"></param>
|
||||
/// <param name="ShowDate"></param>
|
||||
/// <param name="ShowReason"></param>
|
||||
/// <param name="ShowLocation"></param>
|
||||
/// <param name="ShowDistinguishedName"></param>
|
||||
/// <param name="DateTimeFormat"></param>
|
||||
public sealed record PdfSignatureAppearanceDto(
|
||||
int PageNumber,
|
||||
double Left,
|
||||
double Bottom,
|
||||
double Width,
|
||||
double Height,
|
||||
bool ShowLabels = false,
|
||||
bool ShowName = false,
|
||||
bool ShowDate = false,
|
||||
bool ShowReason = false,
|
||||
bool ShowLocation = false,
|
||||
bool ShowDistinguishedName = false,
|
||||
string DateTimeFormat = "dd.MM.yyyy HH:mm:ss zzz")
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="FamilyName"></param>
|
||||
/// <param name="Size"></param>
|
||||
/// <param name="IsBold"></param>
|
||||
/// <param name="IsItalic"></param>
|
||||
/// <param name="IsUnderline"></param>
|
||||
public sealed record FontDto(string FamilyName = "Helvetica", float Size = 10, bool IsBold = false, bool IsItalic = false, bool IsUnderline = false)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public RgbaColorDto Color { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="R"></param>
|
||||
/// <param name="G"></param>
|
||||
/// <param name="B"></param>
|
||||
public sealed record RgbaColorDto(double R = 0, double G = 0, double B = 0);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public FontDto Font { get; init; } = new();
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ImageBytes"></param>
|
||||
/// <param name="PageNumber"></param>
|
||||
/// <param name="Left"></param>
|
||||
/// <param name="Bottom"></param>
|
||||
/// <param name="Width"></param>
|
||||
/// <param name="Height"></param>
|
||||
/// <param name="ShowLabels"></param>
|
||||
public sealed record PdfVisualSignatureOptionsDto(
|
||||
byte[] ImageBytes,
|
||||
int PageNumber,
|
||||
double Left,
|
||||
double Bottom,
|
||||
double Width,
|
||||
double Height,
|
||||
bool ShowLabels = false);
|
||||
@@ -1,9 +1,27 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Enums;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum PdfSignatureCertificationLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// All changes are allowed
|
||||
/// </summary>
|
||||
NoCertification = 0,
|
||||
NoChangesAllowed = 1,
|
||||
FormFillingAndSigning = 2,
|
||||
FormFillingAndAnnotations = 3
|
||||
|
||||
/// <summary>
|
||||
/// No changes are allowed
|
||||
/// </summary>
|
||||
NoChangesAllowed,
|
||||
|
||||
/// <summary>
|
||||
/// Allows users to fill in forms and sign the document.
|
||||
/// </summary>
|
||||
FillForms,
|
||||
|
||||
/// <summary>
|
||||
/// Allows users to fill in forms, create, modify and delete annotations, and sign the document.
|
||||
/// </summary>
|
||||
FillFormsAndAnnotate
|
||||
}
|
||||
|
||||
@@ -14,18 +14,9 @@ public interface IPdfDigitalSignatureService
|
||||
/// <param name="inputPdf">Source PDF content stream.</param>
|
||||
/// <param name="outputPdf">Destination stream for signed PDF output.</param>
|
||||
/// <param name="options">Optional signing options that map to PDF signature metadata fields.</param>
|
||||
/// <param name="image">Optional image stream for signature appearance.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for cooperative cancellation.</param>
|
||||
Task SignPdfAsync(Stream inputPdf, Stream outputPdf, PdfSignOptionsDto? options = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Applies multiple signatures to the provided PDF in sequence and returns
|
||||
/// the final signed PDF document.
|
||||
/// </summary>
|
||||
/// <param name="inputPdf">Source PDF content stream.</param>
|
||||
/// <param name="outputPdf">Destination stream for signed PDF output.</param>
|
||||
/// <param name="optionsList">Ordered signing options; one signature is applied per item.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for cooperative cancellation.</param>
|
||||
Task SignPdfAsync(Stream inputPdf, Stream outputPdf, IReadOnlyList<PdfSignOptionsDto> optionsList, CancellationToken cancellationToken = default);
|
||||
Task SignPdfAsync(Stream inputPdf, Stream outputPdf, PdfSignOptionsDto? options = null, Stream? image = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the provided PDF contains signature field(s) and whether
|
||||
@@ -35,13 +26,4 @@ public interface IPdfDigitalSignatureService
|
||||
/// <param name="cancellationToken">Cancellation token for cooperative cancellation.</param>
|
||||
/// <returns>A structured result containing signature presence and per-signature details.</returns>
|
||||
Task<PdfSignatureCheckResultDto> CheckPdfSignatureAsync(Stream inputPdf, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the original signed revision from a signed PDF.
|
||||
/// For multi-revision files this returns the first signed revision bytes.
|
||||
/// </summary>
|
||||
/// <param name="inputPdf">Signed PDF content stream.</param>
|
||||
/// <param name="outputPdf">Destination stream for original revision output.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for cooperative cancellation.</param>
|
||||
Task ExtractOriginalPdfAsync(Stream inputPdf, Stream outputPdf, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using DevExpress.Pdf;
|
||||
using EnvelopeGenerator.Application.Common.Enums;
|
||||
|
||||
namespace EnvelopeGenerator.Infrastructure.Crypt.Helper;
|
||||
|
||||
public static class PdfCertificationLevelExtensions
|
||||
{
|
||||
public static PdfCertificationLevel ToDevExpress(this PdfSignatureCertificationLevel value) => value switch
|
||||
{
|
||||
PdfSignatureCertificationLevel.NoChangesAllowed => PdfCertificationLevel.NoChangesAllowed,
|
||||
PdfSignatureCertificationLevel.FillForms => PdfCertificationLevel.FillForms,
|
||||
PdfSignatureCertificationLevel.NoCertification => PdfCertificationLevel.NoCertification,
|
||||
PdfSignatureCertificationLevel.FillFormsAndAnnotate => PdfCertificationLevel.FillFormsAndAnnotate,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(value), value, null),
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
using EnvelopeGenerator.Application.Common.Configurations;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.Common.Enums;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using DevExpress.Office.DigitalSignatures;
|
||||
using DevExpress.Pdf;
|
||||
using DigitalData.Core.Exceptions;
|
||||
using EnvelopeGenerator.Application.Common.Configurations;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using EnvelopeGenerator.Infrastructure.Crypt.Helper;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Formats.Asn1;
|
||||
using System.Security.Cryptography.Pkcs;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.Pkcs;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -18,7 +20,7 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
{
|
||||
private static readonly Regex SignatureReasonMetaRegex = new(@"\|\s*SFMETA:v1;doc=(?<doc>[0-9a-fA-F-]{36});sha256=(?<sha>[A-Fa-f0-9]{64})$", RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
|
||||
public async Task SignPdfAsync(Stream inputPdf, Stream outputPdf, PdfSignOptionsDto request, CancellationToken cancellationToken = default)
|
||||
public async Task SignPdfAsync(Stream inputPdf, Stream outputPdf, PdfSignOptionsDto request, Stream? image = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -32,48 +34,66 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
|
||||
var pkcs11Options = options.Value;
|
||||
|
||||
var uuid = ResolveUuid(request.UUID);
|
||||
var reasonWithMeta = BuildReasonWithMeta(request.Reason ?? pkcs11Options.DefaultReason, uuid, sourceHashHex);
|
||||
var reasonWithMeta = BuildReasonWithMeta(request.Reason ?? pkcs11Options.DefaultReason, request.UUID, sourceHashHex);
|
||||
|
||||
using var signer = new PdfDocumentSigner(workingInput);
|
||||
var signaturePageNumber = ResolveSignaturePageNumber(request.VisualSignature?.PageNumber, signer.PageCount);
|
||||
|
||||
var digestCalculator = new DigestCalculator(HashAlgorithmType.SHA256);
|
||||
var signerInfo = new ExternalSignerInfo(PdfSignatureType.Pkcs7, 32768, digestCalculator);
|
||||
var signatureFieldInfo = new PdfSignatureFieldInfo(signaturePageNumber)
|
||||
{
|
||||
Name = $"Sig_{DateTimeOffset.UtcNow:yyyyMMddHHmmss}"
|
||||
};
|
||||
|
||||
var left = request.VisualSignature!.Left;
|
||||
var bottom = request.VisualSignature.Bottom;
|
||||
signatureFieldInfo.SignatureBounds = new PdfRectangle(left, bottom, left + request.VisualSignature.Width, bottom + request.VisualSignature.Height);
|
||||
|
||||
var builder = new PdfDeferredSignatureBuilder(signerInfo, signatureFieldInfo)
|
||||
var builder = request.Appearance is null ? new PdfDeferredSignatureBuilder(signerInfo) : new (signerInfo, new PdfSignatureFieldInfo(request.Appearance.PageNumber)
|
||||
{
|
||||
Name = request.Name ?? pkcs11Options.CertificateLabel,
|
||||
Reason = reasonWithMeta,
|
||||
Location = request.Location,
|
||||
ContactInfo = BuildContactInfo(request.Email, request.Phone),
|
||||
SigningTime = pkcs11Options.UseUtcSigningTime ? DateTimeOffset.UtcNow : DateTimeOffset.Now,
|
||||
ApplicationName = pkcs11Options.ApplicationName,
|
||||
CertificationLevel = ToDevExpressCertificationLevel(request.CertificationLevel)
|
||||
?? ToDevExpressCertificationLevel(pkcs11Options.DefaultCertificationLevel)
|
||||
?? PdfCertificationLevel.NoCertification
|
||||
};
|
||||
|
||||
builder.SetSignatureAppearance(new PdfSignatureAppearance
|
||||
{
|
||||
AppearanceType = PdfSignatureAppearanceType.Name,
|
||||
ShowLabels = request.VisualSignature.ShowLabels,
|
||||
ShowName = true,
|
||||
ShowDate = false,
|
||||
ShowReason = false,
|
||||
ShowLocation = false,
|
||||
ShowDistinguishedName = false
|
||||
Name = $"Sig_{DateTimeOffset.UtcNow:yyyyMMddHHmmss}",
|
||||
SignatureBounds = new PdfRectangle(request.Appearance.Left, request.Appearance.Bottom, request.Appearance.Left + request.Appearance.Width, request.Appearance.Bottom + request.Appearance.Height)
|
||||
});
|
||||
builder.SetImageData(request.VisualSignature.ImageBytes);
|
||||
|
||||
|
||||
builder.Name = request.Name;
|
||||
builder.Reason = reasonWithMeta;
|
||||
builder.Location = request.Location;
|
||||
builder.ContactInfo = BuildContactInfo(request.Email, request.Phone);
|
||||
builder.SigningTime = pkcs11Options.UseUtcSigningTime ? DateTimeOffset.UtcNow : DateTimeOffset.Now;
|
||||
builder.ApplicationName = pkcs11Options.ApplicationName;
|
||||
builder.CertificationLevel = request.CertificationLevel.ToDevExpress();
|
||||
|
||||
if (request.Appearance is not null)
|
||||
{
|
||||
var fColor = request.Appearance.Font.Color;
|
||||
var font = new PdfSignatureFontSettings(request.Appearance.Font.FamilyName)
|
||||
{
|
||||
Size = request.Appearance.Font.Size,
|
||||
Color = new PdfRGBColor(fColor.R / 255.0, fColor.G / 255.0, fColor.B / 255.0),
|
||||
Bold = false,
|
||||
Italic = true
|
||||
};
|
||||
|
||||
var appearance = new PdfSignatureAppearance
|
||||
{
|
||||
ShowLabels = request.Appearance.ShowLabels,
|
||||
ShowName = request.Appearance.ShowName,
|
||||
ShowDate = request.Appearance.ShowDate,
|
||||
ShowReason = request.Appearance.ShowReason,
|
||||
ShowLocation = request.Appearance.ShowLocation,
|
||||
ShowDistinguishedName = request.Appearance.ShowDistinguishedName,
|
||||
DateTimeFormat = request.Appearance.DateTimeFormat,
|
||||
SignatureDetailsFont = font,
|
||||
SignatureNameFont = font,
|
||||
AppearanceType = PdfSignatureAppearanceType.Image,
|
||||
};
|
||||
|
||||
|
||||
if (image is not null)
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
image.CopyTo(memoryStream);
|
||||
var bytes = memoryStream.ToArray();
|
||||
appearance.SetBackgroundImageData(bytes);
|
||||
}
|
||||
|
||||
builder.SetSignatureAppearance(appearance);
|
||||
}
|
||||
else if (image is not null)
|
||||
throw new BadRequestException("Signature appearance is not specified, but signature image is provided.");
|
||||
|
||||
var deferredSigner = signer.SignDeferred(builder);
|
||||
var externalSigner = new Pkcs11ExternalSigner(pkcs11Options);
|
||||
deferredSigner.Sign(outputPdf, externalSigner.BuildSignature(deferredSigner.HashValue, digestCalculator.AlgorithmOid));
|
||||
@@ -85,49 +105,7 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SignPdfAsync(Stream inputPdf, Stream outputPdf, IReadOnlyList<PdfSignOptionsDto> optionsList, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (optionsList.Count == 0)
|
||||
{
|
||||
await inputPdf.CopyToAsync(outputPdf, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
var (currentInput, ownsCurrentInput) = await EnsureSeekableInputAsync(inputPdf, cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < optionsList.Count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var isLast = i == optionsList.Count - 1;
|
||||
|
||||
if (isLast)
|
||||
{
|
||||
await SignPdfAsync(currentInput, outputPdf, optionsList[i], cancellationToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
var intermediateOutput = new MemoryStream();
|
||||
await SignPdfAsync(currentInput, intermediateOutput, optionsList[i], cancellationToken);
|
||||
|
||||
if (ownsCurrentInput)
|
||||
await currentInput.DisposeAsync();
|
||||
|
||||
intermediateOutput.Position = 0;
|
||||
currentInput = intermediateOutput;
|
||||
ownsCurrentInput = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ownsCurrentInput)
|
||||
await currentInput.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: verify and optimize (remove memory stream etc.) the check algortihm
|
||||
public async Task<PdfSignatureCheckResultDto> CheckPdfSignatureAsync(Stream inputPdf, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -135,7 +113,7 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
var pdfBytes = await ReadAllBytesAsync(inputPdf, cancellationToken);
|
||||
|
||||
using var pdfStream = new MemoryStream(pdfBytes);
|
||||
using var signer = new PdfDocumentSigner(pdfStream);
|
||||
using var signer = new PdfDocumentSigner(inputPdf);
|
||||
var signatures = signer.GetSignatureInfo();
|
||||
var byteRanges = ParseByteRanges(pdfBytes);
|
||||
|
||||
@@ -235,31 +213,6 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
Signatures: items);
|
||||
}
|
||||
|
||||
public async Task ExtractOriginalPdfAsync(Stream inputPdf, Stream outputPdf, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var pdfBytes = await ReadAllBytesAsync(inputPdf, cancellationToken);
|
||||
|
||||
var byteRanges = ParseByteRanges(pdfBytes);
|
||||
if (byteRanges.Count == 0)
|
||||
{
|
||||
await outputPdf.WriteAsync(pdfBytes, cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
var revisionLength = byteRanges
|
||||
.Select(GetSignedRevisionLength)
|
||||
.Where(x => x > 0)
|
||||
.DefaultIfEmpty(pdfBytes.LongLength)
|
||||
.Min();
|
||||
|
||||
if (revisionLength > pdfBytes.LongLength)
|
||||
throw new InvalidOperationException("Invalid ByteRange in signed PDF.");
|
||||
|
||||
await outputPdf.WriteAsync(pdfBytes.AsMemory(0, (int)revisionLength), cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<(Stream Stream, bool OwnsStream)> EnsureSeekableInputAsync(Stream input, CancellationToken cancellationToken)
|
||||
{
|
||||
if (input.CanSeek)
|
||||
@@ -398,11 +351,6 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
return new PdfSignaturePrincipalDto(commonName, organization, country, dn);
|
||||
}
|
||||
|
||||
private static PdfCertificationLevel? ToDevExpressCertificationLevel(PdfSignatureCertificationLevel? value)
|
||||
{
|
||||
return value is null ? null : (PdfCertificationLevel)(int)value.Value;
|
||||
}
|
||||
|
||||
private static string? ToColonSerial(string? raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
@@ -544,25 +492,6 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
return start + length;
|
||||
}
|
||||
|
||||
private static string ResolveUuid(string? uuid)
|
||||
{
|
||||
return Guid.TryParse(uuid, out var parsed)
|
||||
? parsed.ToString()
|
||||
: Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
private static int ResolveSignaturePageNumber(int? requestedPageNumber, int pageCount)
|
||||
{
|
||||
var pageNumber = requestedPageNumber ?? 1;
|
||||
if (pageNumber < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(requestedPageNumber), "SignaturePageNumber must be greater than or equal to 1.");
|
||||
|
||||
if (pageCount > 0 && pageNumber > pageCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(requestedPageNumber), $"SignaturePageNumber {pageNumber} exceeds page count {pageCount}.");
|
||||
|
||||
return pageNumber;
|
||||
}
|
||||
|
||||
private static string BuildReasonWithMeta(string? businessReason, string uuid, string sourceHashHex)
|
||||
{
|
||||
var trimmedReason = string.IsNullOrWhiteSpace(businessReason) ? null : businessReason.Trim();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.Common.Enums;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -33,59 +32,12 @@ public class PdfSignatureController(IPdfDigitalSignatureService signatureService
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required IFormFile SignatureImageFile { get; init; }
|
||||
public required IFormFile SignatureImage { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string? UUID { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string? Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string? Reason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string? Location { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string? Email { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string? Phone { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required VSignature Signature { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="PageNumber"></param>
|
||||
/// <param name="Left"></param>
|
||||
/// <param name="Bottom"></param>
|
||||
/// <param name="Width"></param>
|
||||
/// <param name="Height"></param>
|
||||
/// <param name="ShowLabels"></param>
|
||||
public record VSignature(
|
||||
int PageNumber = 1,
|
||||
double Left = 100,
|
||||
double Bottom = 100,
|
||||
double Width = 220,
|
||||
double Height = 80,
|
||||
bool ShowLabels = true);
|
||||
public PdfSignOptionsDto Options { get; init; } = new PdfSignOptionsDto();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -99,32 +51,14 @@ public class PdfSignatureController(IPdfDigitalSignatureService signatureService
|
||||
[Consumes("multipart/form-data")]
|
||||
public async Task<IActionResult> Sign([FromForm] SignPdfForm request, CancellationToken cancel)
|
||||
{
|
||||
await using var signatureImageInput = request.SignatureImageFile.OpenReadStream();
|
||||
using var signatureImageBuffer = new MemoryStream();
|
||||
await signatureImageInput.CopyToAsync(signatureImageBuffer, cancel);
|
||||
|
||||
var signOptions = new PdfSignOptionsDto
|
||||
{
|
||||
UUID = request.UUID,
|
||||
Name = request.Name,
|
||||
Reason = request.Reason,
|
||||
Location = request.Location,
|
||||
Email = request.Email,
|
||||
Phone = request.Phone,
|
||||
CertificationLevel = PdfSignatureCertificationLevel.FormFillingAndAnnotations,
|
||||
VisualSignature = new PdfVisualSignatureOptionsDto(
|
||||
ImageBytes: signatureImageBuffer.ToArray(),
|
||||
PageNumber: request.Signature.PageNumber,
|
||||
Left: request.Signature.Left,
|
||||
Bottom: request.Signature.Bottom,
|
||||
Width: request.Signature.Width,
|
||||
Height: request.Signature.Height,
|
||||
ShowLabels: request.Signature.ShowLabels)
|
||||
};
|
||||
await using var signatureImage = request.SignatureImage.OpenReadStream();
|
||||
|
||||
await using var input = request.File.OpenReadStream();
|
||||
|
||||
var output = new MemoryStream();
|
||||
await signatureService.SignPdfAsync(input, output, signOptions, cancel);
|
||||
|
||||
await signatureService.SignPdfAsync(input, output, request.Options, signatureImage, cancel);
|
||||
|
||||
output.Position = 0;
|
||||
var fileName = Path.GetFileNameWithoutExtension(request.File.FileName);
|
||||
return File(output, "application/pdf", $"{fileName}-signed.pdf");
|
||||
|
||||
Reference in New Issue
Block a user