Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<PackageVersion Include="Dapper" Version="2.1.72" />
<PackageVersion Include="EFCore.BulkExtensions" Version="10.0.1" />
<PackageVersion Include="EFCore.BulkExtensions.PostgreSql" Version="10.0.1" />
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.73.0.4120" />
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.74.0.4134" />
<PackageVersion Include="itext" Version="9.6.0" />
<PackageVersion Include="itext.bouncy-castle-adapter" Version="9.6.0" />
<PackageVersion Include="itext.bouncy-castle-fips-adapter" Version="9.6.0" />
Expand All @@ -43,7 +43,7 @@
<PackageVersion Include="Npgsql" Version="10.0.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageVersion Include="Quartz" Version="3.18.1" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.14.9" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.14.11" />
<PackageVersion Include="Scrutor" Version="7.0.0" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.25.0.139117" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.18.0" />
Expand Down
10 changes: 10 additions & 0 deletions InvoiceReminder.API/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ EXPOSE 8081
USER root
RUN apt-get update && apt-get install -y \
libgssapi-krb5-2 \
locales \
&& locale-gen pt_BR.UTF-8 \
&& update-locale LANG=pt_BR.UTF-8 LC_ALL=pt_BR.UTF-8 \
&& rm -rf /var/lib/apt/lists/*
USER ${APP_UID}

Expand Down Expand Up @@ -46,4 +49,11 @@ RUN dotnet publish "./InvoiceReminder.API.csproj" -c $BUILD_CONFIGURATION -o /ap
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

COPY ./InvoiceReminder.API/certificate.pfx /app/certs/certificate.pfx

ENV LANG=pt_BR.UTF-8
ENV LANGUAGE=pt_BR:pt
ENV LC_ALL=pt_BR.UTF-8

ENTRYPOINT ["dotnet", "InvoiceReminder.API.dll"]
3 changes: 3 additions & 0 deletions InvoiceReminder.API/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"SecretKey": "SECRET_KEY"
},
"Security": {
"CertificateFileName": "SECRET_CERT_FILE_NAME",
"CertificateFilePath": "SECRET_CERT_FILE_PATH",
"CertificatePassword": "SECRET_CERT_PASSWORD",
"ParallelismFactor": 2
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,66 @@
using InvoiceReminder.Application.Interfaces;
using InvoiceReminder.Application.ViewModels;
using InvoiceReminder.Authentication.Extensions;
using InvoiceReminder.Data.Interfaces;
using InvoiceReminder.Domain.Abstractions;
using InvoiceReminder.Domain.Entities;
using InvoiceReminder.Domain.Services.Configuration;
using Mapster;

namespace InvoiceReminder.Application.AppServices;

public class ScanEmailDefinitionAppService : BaseAppService<ScanEmailDefinition, ScanEmailDefinitionViewModel>,
IScanEmailDefinitionAppService
{
private readonly string _certificateFilePath;
private readonly string _certificatePassword;
private readonly IScanEmailDefinitionRepository _repository;

public ScanEmailDefinitionAppService(IScanEmailDefinitionRepository repository, IUnitOfWork unitOfWork)
: base(repository, unitOfWork)
public ScanEmailDefinitionAppService(
IConfigurationService configuration,
IScanEmailDefinitionRepository repository,
IUnitOfWork unitOfWork) : base(repository, unitOfWork)
{
var fileName = configuration.GetAppSetting("Security:CertificateFileName");
var filePath = configuration.GetAppSetting("Security:CertificateFilePath");

_repository = repository;
_certificateFilePath = Path.Combine(filePath, fileName);
_certificatePassword = configuration.GetAppSetting("Security:CertificatePassword");
}

public override async Task<Result<ScanEmailDefinitionViewModel>> AddAsync(
ScanEmailDefinitionViewModel viewModel,
CancellationToken cancellationToken = default)
{
if (viewModel is null)
{
return Result<ScanEmailDefinitionViewModel>.Failure($"Parameter {nameof(viewModel)} was Null.");
}

if (!string.IsNullOrWhiteSpace(viewModel.FilePassword))
{
viewModel.FilePassword = viewModel.FilePassword.X509_Encrypt(_certificateFilePath, _certificatePassword);
}

return await base.AddAsync(viewModel, cancellationToken);
}

public override async Task<Result<ScanEmailDefinitionViewModel>> UpdateAsync(
ScanEmailDefinitionViewModel viewModel,
CancellationToken cancellationToken = default)
{
if (viewModel is null)
{
return Result<ScanEmailDefinitionViewModel>.Failure($"Parameter {nameof(viewModel)} was Null.");
}

if (!string.IsNullOrWhiteSpace(viewModel.FilePassword))
{
viewModel.FilePassword = viewModel.FilePassword.X509_Encrypt(_certificateFilePath, _certificatePassword);
}

return await base.UpdateAsync(viewModel, cancellationToken);
}

public async Task<Result<ScanEmailDefinitionViewModel>> GetBySenderBeneficiaryAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class ScanEmailDefinitionViewModel : ViewModelDefaults
[JsonPropertyOrder(7)]
public string AttachmentFileName { get; set; }

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWriting)]
public string FilePassword { get; set; }

public ScanEmailDefinitionViewModel()
{
Id = Guid.NewGuid();
Expand Down
46 changes: 46 additions & 0 deletions InvoiceReminder.Authentication/Extensions/StringHashExtension.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
using Konscious.Security.Cryptography;
using System.Globalization;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace InvoiceReminder.Authentication.Extensions;

public static class StringHashExtension
{
const string CERT_NOT_FOUND = "Certificado de segurança não encontrado no servidor.";
const string NO_RSA_KEY = "O certificado não possui uma chave RSA válida.";

public static (string Hash, string Salt) HashPassword(this string inputString, int parallelismFactor = 2)
{
ArgumentException.ThrowIfNullOrWhiteSpace(inputString);
Expand Down Expand Up @@ -51,6 +55,48 @@ public static bool VerifyPassword(this string inputString, string storedHash, st
);
}

public static string X509_Encrypt(this string inputString, string certFilePath, string password = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(inputString);
ArgumentException.ThrowIfNullOrWhiteSpace(certFilePath);

if (!File.Exists(certFilePath))
{
throw new FileNotFoundException(CERT_NOT_FOUND, certFilePath);
}

using var cert = X509CertificateLoader.LoadPkcs12FromFile(certFilePath, password);
using var rsa = cert.GetRSAPublicKey() ?? throw new InvalidDataException(NO_RSA_KEY);
var encryptedData = rsa.Encrypt(Encoding.UTF8.GetBytes(inputString), RSAEncryptionPadding.OaepSHA256);

return Convert.ToBase64String(encryptedData);
}

public static string X509_Decrypt(this string encryptedString, string certFilePath, string password = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(encryptedString);
ArgumentException.ThrowIfNullOrWhiteSpace(certFilePath);

if (!File.Exists(certFilePath))
{
throw new FileNotFoundException(CERT_NOT_FOUND, certFilePath);
}

using var cert = X509CertificateLoader.LoadPkcs12FromFile(certFilePath, password);
using var rsa = cert.GetRSAPrivateKey() ?? throw new InvalidDataException(NO_RSA_KEY);

try
{
var decryptedData = rsa.Decrypt(Convert.FromBase64String(encryptedString), RSAEncryptionPadding.OaepSHA256);

return Encoding.UTF8.GetString(decryptedData);
}
catch (CryptographicException ex)
{
throw new CryptographicException("Falha ao descriptografar: chave incorreta ou dados corrompidos.", ex);
}
}

public static string ToSHA256(this string inputString)
{
var bytes = Encoding.UTF8.GetBytes(inputString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Diagnostics.CodeAnalysis;

#nullable disable
#pragma warning disable S1192

namespace InvoiceReminder.Data.Migrations;

Expand Down Expand Up @@ -192,6 +191,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
description = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
sender_email_address = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
attachment_filename = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
file_password = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
Expand Down Expand Up @@ -274,4 +274,3 @@ protected override void Down(MigrationBuilder migrationBuilder)
schema: "invoice_reminder");
}
}
#pragma warning restore S1192
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <auto-generated />
// <auto-generated />
using System;
using InvoiceReminder.Data.Persistence;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -199,6 +199,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("character varying(255)")
.HasColumnName("sender_email_address");

b.Property<string>("FilePassword")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("file_password");

b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public void Configure(EntityTypeBuilder<ScanEmailDefinition> builder)
.HasMaxLength(255)
.IsRequired();

_ = builder.Property(x => x.FilePassword)
.HasColumnName("file_password")
.HasMaxLength(1024);

_ = builder.Property(x => x.CreatedAt)
.HasColumnName("created_at")
.HasColumnType("timestamp with time zone")
Expand Down
1 change: 1 addition & 0 deletions InvoiceReminder.Domain/Entities/ScanEmailDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public class ScanEmailDefinition : EntityDefaults
public string Description { get; set; }
public string SenderEmailAddress { get; set; }
public string AttachmentFileName { get; set; }
public string FilePassword { get; set; }
Comment thread
jldsilva marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using InvoiceReminder.Domain.Entities;
using System.Globalization;
using System.Text.RegularExpressions;

namespace InvoiceReminder.ExternalServices.BarcodeReader;
Expand Down Expand Up @@ -26,27 +27,36 @@ public Invoice CreateInvoice(string content, string beneficiary)
{
ArgumentException.ThrowIfNullOrWhiteSpace(content);

var barcode = FilterContent(content);
var bankId = int.Parse(barcode.Split("\n")[0][..3]);
var paymentCode = barcode.Split("\n")[1];
var (bankId, barcode) = FilterContent(content);
var bankCode = int.Parse(bankId[..3], CultureInfo.InvariantCulture);

return new Invoice
{
Bank = $"[{bankId}] - {knowBanks[bankId]}",
Bank = knowBanks.TryGetValue(bankCode, out var bankName)
? $"[{bankId}] - {bankName}"
: $"[{bankId}]",
Beneficiary = beneficiary,
Amount = GetPaymentValue(paymentCode),
Barcode = paymentCode,
DueDate = GetPaymentDueDate(paymentCode)
Amount = GetPaymentValue(barcode),
Barcode = barcode,
DueDate = GetPaymentDueDate(barcode)
Comment thread
jldsilva marked this conversation as resolved.
};
}

private static string FilterContent(string content)
private static (string, string) FilterContent(string content)
{
var pattern = @"(\d{3}-\d)\s(\d+\.\d{5})\s(\d+\.\d{6})\s(\d+\.\d{6})\s(\d)\s(\d+)";
var rawPattern = @"(\d{3}-\d)\s(\d+\.\d{5})\s(\d+\.\d{6})\s(\d+\.\d{6})\s(\d)\s(\d+)";
var rawMatch = Regex.Match(content, rawPattern);
var rawValue = rawMatch.Value;

var match = Regex.Match(content, pattern);
var bankIdPattern = @"(\d{3}-\d)";
var bankIdMatch = Regex.Match(rawValue, bankIdPattern);

return match.Groups[0].Value;
var barcodePattern = @"(\d+\.\d{5})\s(\d+\.\d{6})\s(\d+\.\d{6})\s(\d)\s(\d+)";
var barcodeMatch = Regex.Match(rawValue, barcodePattern);

return bankIdMatch.Success && barcodeMatch.Success
? (bankIdMatch.Value, barcodeMatch.Value)
: throw new FormatException("Não foi possível identificar o banco e a linha digitável no mesmo trecho.");
}

private static DateTime GetPaymentDueDate(string content)
Expand All @@ -63,6 +73,6 @@ private static decimal GetPaymentValue(string content)
var value = content.Replace(".", "").Replace(" ", "");
var valorStr = value.Substring(37, 10);

return Convert.ToDecimal(valorStr) / 100;
return Convert.ToDecimal(valorStr, CultureInfo.InvariantCulture) / 100;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public BarcodeReaderService(ILogger<BarcodeReaderService> logger)
_logger = logger;
}

public Invoice ReadTextContentFromPdf(byte[] byteStream, string beneficiary, InvoiceType invoiceType)
public Invoice ReadTextContentFromPdf(byte[] byteStream, string beneficiary, string password, InvoiceType invoiceType)
{
if (byteStream.Length == 0)
{
Expand All @@ -32,14 +32,21 @@ public Invoice ReadTextContentFromPdf(byte[] byteStream, string beneficiary, Inv
throw exception;
}

StringBuilder content = new();
ReaderProperties props = new();

if (!string.IsNullOrWhiteSpace(password))
{
_ = props.SetPassword(Encoding.UTF8.GetBytes(password));
}

using MemoryStream memory = new(byteStream);
using PdfReader iTextReader = new(memory);
using PdfReader iTextReader = new(memory, props);
using PdfDocument pdfDoc = new(iTextReader);

var numberofpages = pdfDoc.GetNumberOfPages();
StringBuilder content = new();
var numberOfPages = pdfDoc.GetNumberOfPages();

for (var page = 1; page <= numberofpages; page++)
for (var page = 1; page <= numberOfPages; page++)
{
ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
var currentText = PdfTextExtractor.GetTextFromPage(pdfDoc.GetPage(page), strategy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace InvoiceReminder.ExternalServices.BarcodeReader;

public interface IBarcodeReaderService
{
Invoice ReadTextContentFromPdf(byte[] byteStream, string beneficiary, InvoiceType invoiceType);
Invoice ReadTextContentFromPdf(byte[] byteStream, string beneficiary, string password, InvoiceType invoiceType);
}
Loading