diff --git a/src/DNS-BLM.Api/Controllers/DnsBlocklistScanController.cs b/src/DNS-BLM.Api/Controllers/DnsBlocklistScanController.cs index 8eff3a0..a84e943 100644 --- a/src/DNS-BLM.Api/Controllers/DnsBlocklistScanController.cs +++ b/src/DNS-BLM.Api/Controllers/DnsBlocklistScanController.cs @@ -18,7 +18,7 @@ public async Task ScanConfiguredDomains() if (domains == null) throw new Exception("Domains not found"); - return await mediator.Send(new ScanBlacklistCommand(domains, false)); + return await mediator.Send(new ScanBlacklistCommand(domains)); } // [HttpGet("ScanCustomDomain", Name = "ScanCustomDomain")] diff --git a/src/DNS-BLM.Application/Commands/ScanBlacklistCommand.cs b/src/DNS-BLM.Application/Commands/ScanBlacklistCommand.cs index ed7f73a..d585ee4 100644 --- a/src/DNS-BLM.Application/Commands/ScanBlacklistCommand.cs +++ b/src/DNS-BLM.Application/Commands/ScanBlacklistCommand.cs @@ -31,7 +31,7 @@ public async Task Handle(ScanBlacklistCommand request, CancellationToken var results = messageService.GetResults(); messageService.Clear(); - if (request.SendMail && results is not null) + if (request.SendMail && !string.IsNullOrWhiteSpace(results)) { await notificationService.Notify("DNS-BLM Scanning Results", results); } @@ -39,7 +39,6 @@ public async Task Handle(ScanBlacklistCommand request, CancellationToken { if (results is null) results = "No blacklisted Domains."; - logger.LogInformation("Mail notification skipped as per request"); } return results; diff --git a/src/DNS-BLM.Domain/Configuration/MailConfiguration.cs b/src/DNS-BLM.Domain/Configuration/MailConfiguration.cs index 3ac763c..1ec1a04 100644 --- a/src/DNS-BLM.Domain/Configuration/MailConfiguration.cs +++ b/src/DNS-BLM.Domain/Configuration/MailConfiguration.cs @@ -22,4 +22,6 @@ public class MailConfiguration [Required] public bool EnableSsl { get; init; } = true; + public string MailTemplate { get; init; } = string.Empty; + } \ No newline at end of file diff --git a/src/DNS-BLM.Infrastructure/DNS-BLM.Infrastructure.csproj b/src/DNS-BLM.Infrastructure/DNS-BLM.Infrastructure.csproj index e197d77..406c81a 100644 --- a/src/DNS-BLM.Infrastructure/DNS-BLM.Infrastructure.csproj +++ b/src/DNS-BLM.Infrastructure/DNS-BLM.Infrastructure.csproj @@ -8,6 +8,7 @@ + diff --git a/src/DNS-BLM.Infrastructure/ModuleRegistry.cs b/src/DNS-BLM.Infrastructure/ModuleRegistry.cs index 46ec55e..3a49c3d 100644 --- a/src/DNS-BLM.Infrastructure/ModuleRegistry.cs +++ b/src/DNS-BLM.Infrastructure/ModuleRegistry.cs @@ -15,6 +15,7 @@ public static void AddInfrastructureModule(this IServiceCollection services, App services.AddSingleton(); services.AddScoped(); services.AddSingleton(); + services.AddSingleton(); string? virusTotalApiKey = appConfiguration.ApiCredentials.VirusTotal; diff --git a/src/DNS-BLM.Infrastructure/Services/MessageService.cs b/src/DNS-BLM.Infrastructure/Services/MessageService.cs index 553add0..02af9e2 100644 --- a/src/DNS-BLM.Infrastructure/Services/MessageService.cs +++ b/src/DNS-BLM.Infrastructure/Services/MessageService.cs @@ -1,10 +1,12 @@ using System.Collections.Concurrent; +using DNS_BLM.Domain.Configuration; using DNS_BLM.Infrastructure.Dtos; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace DNS_BLM.Infrastructure.Services; -public class MessageService(ILogger logger) +public class MessageService(ILogger logger, TemplateService templateService, IOptions appConfiguration) { private ConcurrentBag _results = []; private HashSet _domains = []; @@ -34,21 +36,9 @@ public void AddResult(ScanResult result) { logger.LogDebug($"{result.Domain}: {result.ScannerName} - {result.IsBlacklisted}"); } - - List allResults = new List(); - foreach (var domain in _domains) - { - var domainResults = _results.Where(r => r.Domain == domain && r.IsBlacklisted).ToArray(); - var result = ArrangeResults(domainResults); - if (result == null) continue; - allResults.Add(result); - } - - var results = string.Join(Environment.NewLine, allResults); - if (String.IsNullOrWhiteSpace(results)) - logger.LogError("No results available to return, despite passing previous checks!"); - ArgumentException.ThrowIfNullOrWhiteSpace(results, nameof(results)); + var domainResults = _results.Where(r => r.IsBlacklisted).ToArray(); + var results = templateService.RenderTemplate(domainResults.ToList(), appConfiguration.Value.Mail.MailTemplate); return results; } diff --git a/src/DNS-BLM.Infrastructure/Services/NotificationServices/MailNotificationService.cs b/src/DNS-BLM.Infrastructure/Services/NotificationServices/MailNotificationService.cs index bd0ae39..9c84ff3 100644 --- a/src/DNS-BLM.Infrastructure/Services/NotificationServices/MailNotificationService.cs +++ b/src/DNS-BLM.Infrastructure/Services/NotificationServices/MailNotificationService.cs @@ -31,7 +31,7 @@ public async Task Notify(string subject, string message) mailMessage.Subject = subject; var builder = new BodyBuilder (); - builder.HtmlBody = message.Replace("\n", "
"); + builder.HtmlBody = message; mailMessage.Body = builder.ToMessageBody(); diff --git a/src/DNS-BLM.Infrastructure/Services/TemplateService.cs b/src/DNS-BLM.Infrastructure/Services/TemplateService.cs new file mode 100644 index 0000000..f066549 --- /dev/null +++ b/src/DNS-BLM.Infrastructure/Services/TemplateService.cs @@ -0,0 +1,97 @@ +using System.Collections.Concurrent; +using DNS_BLM.Infrastructure.Dtos; +using HandlebarsDotNet; +using Microsoft.Extensions.Logging; + +namespace DNS_BLM.Infrastructure.Services; + +public class TemplateService() // ILogger logger +{ + private readonly ConcurrentDictionary> _compiledTemplates = new(); + + public string RenderTemplate(List model, string? template = null) + { + string defaultTemplate = + """ + + + + + DNS-BLM Results + + +
+ + + + +
+ + + + + + + + + + +
+ DNS-BLM Results +
+ + + + + + + + + + {{#each this}} + + + + + + {{/each}} + +
DomainScannerDetails
{{Domain}}{{ScannerName}} + {{#if ScanResultUrl}} + View Report + {{else}} + N/A + {{/if}} +
+
+ Sent by DNS-BLM +
+
+
+ + + """; + try + { + var usableTemplate = string.IsNullOrWhiteSpace(template) ? defaultTemplate : template; + var compiledTemplate = _compiledTemplates.GetOrAdd( + (usableTemplate), _ => Handlebars.Compile(usableTemplate)); + + var result = compiledTemplate(model); + return result; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to render template", ex); + } + } + + /// + /// Deletes all precompiled Templates. + /// Only needs to be used if a Template is changed + /// + public void ClearCompiledTemplates() + { + _compiledTemplates.Clear(); + } +} \ No newline at end of file diff --git a/test/Tests/Test/MessageServiceTest.cs b/test/Tests/Test/MessageServiceTest.cs deleted file mode 100644 index bfb44b8..0000000 --- a/test/Tests/Test/MessageServiceTest.cs +++ /dev/null @@ -1,135 +0,0 @@ -using DNS_BLM.Infrastructure.Dtos; -using DNS_BLM.Infrastructure.Services; -using Microsoft.Extensions.Logging; -using Moq; - -namespace Tests.Test; - -public class MessageServiceTest -{ - private readonly MessageService messageService; - private readonly Mock> mockLogger; - - public MessageServiceTest() - { - mockLogger = new Mock>(); - messageService = new MessageService(mockLogger.Object); - } - - [Fact] - public void AddResult_ThrowsArgumentNullException_ForNullResult() - { - Assert.Throws(() => messageService.AddResult(null)); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void AddResult_ThrowsArgumentException_ForInvalidDomain(string domain) - { - var result = new ScanResult() - { - Domain = domain, - ScannerName = "Scanner1", - ScanResultUrl = "http://url.com", - IsBlacklisted = false - }; - - - var caughtException = (Exception)null; - try - { - messageService.AddResult(result); - } - catch (Exception ex) - { - caughtException = ex; - } - - Assert.NotNull(caughtException); - Assert.True(caughtException is ArgumentNullException || caughtException is ArgumentException); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void AddResult_ThrowsArgumentException_ForInvalidScannerName(string scannerName) - { - var result = new ScanResult() - { - Domain = "example.ch", - ScannerName = scannerName, - ScanResultUrl = "http://url.com", - IsBlacklisted = false - }; - - var caughtException = (Exception)null; - try - { - messageService.AddResult(result); - } - catch (Exception ex) - { - caughtException = ex; - } - - Assert.NotNull(caughtException); - Assert.True(caughtException is ArgumentNullException || caughtException is ArgumentException); - } - - [Fact] - public void GetResults_ReturnsNull_WhenNoBlacklistedResults() - { - var scanResult1 = new ScanResult() { Domain = "example.ch", ScannerName = "Scanner1", ScanResultUrl = "http://url.com", IsBlacklisted = false }; - var scanResult2 = new ScanResult() { Domain = "example.swiss", ScannerName = "Scanner2", ScanResultUrl = "http://url.com", IsBlacklisted = false }; - - messageService.AddResult(scanResult1); - messageService.AddResult(scanResult2); - - var result = messageService.GetResults(); - - Assert.Null(result); - } - - [Fact] - public void GetResults_ReturnsFormattedString_WhenBlacklistedResultsExist() - { - var scanResult1 = new ScanResult() { Domain = "example.ch", ScannerName = "Scanner1", ScanResultUrl = "http://url.com", IsBlacklisted = true }; - var scanResult2 = new ScanResult() { Domain = "example.swiss", ScannerName = "Scanner2", ScanResultUrl = "http://url.com", IsBlacklisted = false }; - var scanResult3 = new ScanResult() { Domain = "example.net", ScannerName = "Scanner3", ScanResultUrl = "http://url.com", IsBlacklisted = true }; - - messageService.AddResult(scanResult1); - messageService.AddResult(scanResult2); - messageService.AddResult(scanResult3); - - var expected = """ - example.ch - | Scanner1: Listed - | URL: http://url.com - - example.net - | Scanner3: Listed - | URL: http://url.com - - """; - - var result = messageService.GetResults(); - - Assert.Equal(expected, result); - } - - [Fact] - public void Clear_ResetsInternalState() - { - var scanResult1 = new ScanResult() { Domain = "example.ch", ScannerName = "Scanner1", ScanResultUrl = "http://url.com", IsBlacklisted = true }; - messageService.AddResult(scanResult1); - - Assert.NotNull(messageService.GetResults()); - - messageService.Clear(); - - Assert.Null(messageService.GetResults()); - } -} \ No newline at end of file