Skip to content

Commit

Permalink
add benchmark recommendations to threats in report (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
anmalkov authored Jan 31, 2024
1 parent f9d96f2 commit 95cc665
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/Crisp.Core.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public async Task Test3()
var stream = new MemoryStream();
stream.Write(wordTemplate, 0, wordTemplate.Length);

OpenXmlHelper.AddThreats(stream, recommendations);
OpenXmlHelper.AddThreats(stream, recommendations, null);

File.WriteAllBytes("result2.docx", stream.ToArray());
}
Expand Down
85 changes: 85 additions & 0 deletions src/Crisp.Core/Helpers/MarkdownReportHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Crisp.Core.Models;
using System.Text;

namespace Crisp.Core.Helpers;

public static class MarkdownReportHelper
{
public static string GenerateThreatModelPropertiesSection(ThreatModel threatModel,
IDictionary<string, IEnumerable<SecurityBenchmark>>? benchmarks)
{
var section = new StringBuilder();
var index = 1;
foreach (var threat in threatModel.Threats)
{
if (index > 1)
{
section.AppendLine();
}
section.AppendLine("---");
section.AppendLine($"**Threat #:** {index} ");
section.AppendLine(threat.Description.Trim());
if (threatModel.AddResourcesRecommendations)
{
var resourcesRecommendations = GenerateResourcesRecommendationsForThreat(threat, benchmarks);
if (!string.IsNullOrEmpty(resourcesRecommendations))
{
section.AppendLine(resourcesRecommendations);
}
}
index++;
}
return section.ToString().TrimEnd(Environment.NewLine.ToCharArray());
}

public static string GenerateResourcesRecommendationsForThreat(Recommendation threat,
IDictionary<string, IEnumerable<SecurityBenchmark>>? benchmarks)
{
if (threat.BenchmarkIds is null || !threat.BenchmarkIds.Any() || benchmarks is null)
{
return "";
}

var section = new StringBuilder();
section.AppendLine();
section.AppendLine($"**Recommendations for resources:**");
section.AppendLine();
foreach (var resourceName in benchmarks.Keys)
{
var resourceBenchmarks = benchmarks[resourceName];
if (resourceBenchmarks is null)
{
continue;
}
section.AppendLine($"**{resourceName}:**");
section.AppendLine();
var index = 1;
foreach (var benchmarkId in threat.BenchmarkIds)
{
var benchmark = resourceBenchmarks.FirstOrDefault(b => b.Id == benchmarkId);
if (benchmark is null)
{
continue;
}
section.AppendLine($"**Recommendation #:** {index}");
section.AppendLine();
section.AppendLine(benchmark.Description);
section.AppendLine();
index++;
}
}
section.AppendLine();

return section.ToString();
}

public static string GenerateDataflowAttributeSection(ThreatModel threatModel)
{
var section = new StringBuilder();
foreach (var a in threatModel.DataflowAttributes)
{
section.AppendLine($"| {a.Number.Trim()} | {a.Transport.Trim()} | {a.DataClassification.Trim()} | {a.Authentication.Trim()} | {a.Authorization.Trim()} | {a.Notes.Trim()} |");
}
return section.ToString().TrimEnd(Environment.NewLine.ToCharArray());
}
}
11 changes: 9 additions & 2 deletions src/Crisp.Core/Helpers/OpenXmlHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ public static void AddDataflowAttributes(Stream stream, IEnumerable<DataflowAttr
}
}

public static void AddThreats(Stream stream, IEnumerable<Recommendation> threats)
public static void AddThreats(Stream stream, IEnumerable<Recommendation> threats,
IDictionary<string, IEnumerable<SecurityBenchmark>>? benchmarks)
{
using var document = WordprocessingDocument.Open(stream, isEditable: true);
var body = document.MainDocumentPart.Document.Body;
Expand All @@ -88,7 +89,13 @@ public static void AddThreats(Stream stream, IEnumerable<Recommendation> threats
new Run(new Text($" {threatIndex}") { Space = SpaceProcessingModeValues.Preserve })
)
};
paragraphs.AddRange(GetParagraphsFromMarkdown(threat.Description));

var description = threat.Description +
(benchmarks is not null
? MarkdownReportHelper.GenerateResourcesRecommendationsForThreat(threat, benchmarks).Replace("\n", Environment.NewLine)
: "");
paragraphs.AddRange(GetParagraphsFromMarkdown(description));

foreach (var paragraph in paragraphs.ToArray().Reverse())
{
var hyperlinks = paragraph.Descendants<Hyperlink>();
Expand Down
1 change: 1 addition & 0 deletions src/Crisp.Core/Services/IRecommendationsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public interface IRecommendationsService
{
Task<IEnumerable<string>> GetResourcesAsync();
Task<Category> GetRecommendationsAsync(IEnumerable<string> resources);
Task<IEnumerable<SecurityBenchmark>> GetBenchmarksAsync(string resourceName);
}
9 changes: 9 additions & 0 deletions src/Crisp.Core/Services/RecommendationsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ public async Task<Category> GetRecommendationsAsync(IEnumerable<string> resource
Recommendations: Enumerable.Empty<Recommendation>());
}

public async Task<IEnumerable<SecurityBenchmark>?> GetBenchmarksAsync(string resourceName)
{
var repositoryDirectoryPath = await gitHubRepository.CloneAsync(GitHubAccountName, GitHubRepositoryName);

var benchmarks = await securityBenchmarksRepository.GetSecurityBenchmarksForResourceAsync(resourceName, repositoryDirectoryPath);

return benchmarks;
}

public async Task<IEnumerable<string>> GetResourcesAsync()
{
var repositoryDirectoryPath = await gitHubRepository.CloneAsync(GitHubAccountName, GitHubRepositoryName);
Expand Down
56 changes: 24 additions & 32 deletions src/Crisp.Core/Services/ThreatModelsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using Category = Crisp.Core.Models.Category;
using Crisp.Core.Helpers;
using System.Text.RegularExpressions;
using System.Dynamic;
using System.Runtime.InteropServices;

namespace Crisp.Core.Services;

Expand All @@ -30,15 +32,18 @@ public class ThreatModelsService : IThreatModelsService
private readonly IThreatModelCategoriesRepository _threatModelCategoriesRepository;
private readonly IMemoryCache _memoryCache;
private readonly IReportsRepository _reportsRepository;
private readonly IRecommendationsService _recommendationsService;

public ThreatModelsService(IGitHubRepository gitHubRepository, IThreatModelsRepository threatModelsRepository,
IThreatModelCategoriesRepository threatModelCategoriesRepository, IMemoryCache memoryCache, IReportsRepository reportsRepository)
IThreatModelCategoriesRepository threatModelCategoriesRepository, IMemoryCache memoryCache, IReportsRepository reportsRepository,
IRecommendationsService recommendationsService)
{
_gitHubRepository = gitHubRepository;
_threatModelsRepository = threatModelsRepository;
_threatModelCategoriesRepository = threatModelCategoriesRepository;
_memoryCache = memoryCache;
_reportsRepository = reportsRepository;
_recommendationsService = recommendationsService;
}


Expand Down Expand Up @@ -169,10 +174,18 @@ public async Task DeleteAsync(string id)
}

mdReport = mdReport.Replace(ProjectNamePlaceholder, threatModel.ProjectName);
var dataflowAttributeSection = GenerateDataflowAttributeSection(threatModel);

var dataflowAttributeSection = MarkdownReportHelper.GenerateDataflowAttributeSection(threatModel);
mdReport = mdReport.Replace(DataflowAttributesPlaceholder, dataflowAttributeSection);
var threatModelPropertiesSection = GenerateThreatModelPropertiesSection(threatModel);

IDictionary<string, IEnumerable<SecurityBenchmark>>? benchmarks = threatModel.AddResourcesRecommendations && threatModel.Resources is not null
? (await Task.WhenAll(
threatModel.Resources.Select(async r => new KeyValuePair<string, IEnumerable<SecurityBenchmark>>(r, await _recommendationsService.GetBenchmarksAsync(r)))
)).ToDictionary(pair => pair.Key, pair => pair.Value)
: null;
var threatModelPropertiesSection = MarkdownReportHelper.GenerateThreatModelPropertiesSection(threatModel, benchmarks);
mdReport = mdReport.Replace(ThreatPropertiesPlaceholder, threatModelPropertiesSection);

if (threatModel.Images is not null)
{
mdReport = RemoveHeadersForUnusedImages(mdReport, threatModel.Images);
Expand Down Expand Up @@ -258,7 +271,14 @@ private static string RemoveHeadersForUnusedImages(string report, IDictionary<st

await OpenXmlHelper.ReplaceAsync(stream, ProjectNamePlaceholder, threatModel.ProjectName);
OpenXmlHelper.AddDataflowAttributes(stream, threatModel.DataflowAttributes);
OpenXmlHelper.AddThreats(stream, threatModel.Threats);

IDictionary<string, IEnumerable<SecurityBenchmark>>? benchmarks = threatModel.AddResourcesRecommendations && threatModel.Resources is not null
? (await Task.WhenAll(
threatModel.Resources.Select(async r => new KeyValuePair<string, IEnumerable<SecurityBenchmark>>(r, await _recommendationsService.GetBenchmarksAsync(r)))
)).ToDictionary(pair => pair.Key, pair => pair.Value)
: null;
OpenXmlHelper.AddThreats(stream, threatModel.Threats, benchmarks);

if (threatModel.Images is not null)
{
OpenXmlHelper.RemoveParagraphForUnusedImages(stream, threatModel.Images);
Expand All @@ -276,34 +296,6 @@ private static string RemoveHeadersForUnusedImages(string report, IDictionary<st
return stream.ToArray();
}

private static string GenerateThreatModelPropertiesSection(ThreatModel threatModel)
{
var section = new StringBuilder();
var index = 1;
foreach (var threat in threatModel.Threats)
{
if (index > 1)
{
section.AppendLine();
}
section.AppendLine("---");
section.AppendLine($"**Threat #:** {index} ");
section.AppendLine(threat.Description.Trim());
index++;
}
return section.ToString().TrimEnd(Environment.NewLine.ToCharArray());
}

private static string GenerateDataflowAttributeSection(ThreatModel threatModel)
{
var section = new StringBuilder();
foreach (var a in threatModel.DataflowAttributes)
{
section.AppendLine($"| {a.Number.Trim()} | {a.Transport.Trim()} | {a.DataClassification.Trim()} | {a.Authentication.Trim()} | {a.Authorization.Trim()} | {a.Notes.Trim()} |");
}
return section.ToString().TrimEnd(Environment.NewLine.ToCharArray());
}

private async Task<Category> GetRecommendationsFromGitHubAsync()
{
var directory = await _gitHubRepository.GetContentAsync(GitHubAccountName, GitHubRepositoryName, GitHubThreatModelFolderName);
Expand Down
2 changes: 1 addition & 1 deletion src/Crisp.Ui/ClientApp/src/components/Recommendation.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Recommendation = forwardRef(({ recommendation, level, isSelected, toggleSe
const toggleIsSelect = (category) => {
toggleSelectability(category);
}

const open = () => {
setIsOpen(true);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Crisp.Ui/Handlers/CreateThreatModelHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private static Recommendation MapDtoToRecommendation(RecommendationDto dto)
dto.Id,
dto.Title,
dto.Description,
null
dto.BenchmarkIds
);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/Crisp.Ui/Handlers/GetCategoriesHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ namespace Crisp.Ui.Handlers
public record RecommendationDto(
string Id,
string Title,
string Description
string Description,
IEnumerable<string>? BenchmarkIds
);

public record CategoryDto(
Expand Down Expand Up @@ -62,7 +63,8 @@ private static RecommendationDto MapRecommendationToDto(Recommendation recommend
return new RecommendationDto(
recommendation.Id,
recommendation.Title,
recommendation.Description
recommendation.Description,
recommendation.BenchmarkIds
);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Crisp.Ui/Handlers/GetThreatModelCategoriesHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ private static RecommendationDto MapRecommendationToDto(Recommendation recommend
return new RecommendationDto(
recommendation.Id,
recommendation.Title,
recommendation.Description
recommendation.Description,
recommendation.BenchmarkIds
);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Crisp.Ui/Handlers/GetThreatModelsHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ private static RecommendationDto MapRecommendationToDto(Recommendation recommend
return new RecommendationDto(
recommendation.Id,
recommendation.Title,
recommendation.Description
recommendation.Description,
recommendation.BenchmarkIds
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Crisp.Ui/Handlers/UpdateThreatModelHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private static Recommendation MapDtoToRecommendation(RecommendationDto dto)
dto.Id,
dto.Title,
dto.Description,
null
dto.BenchmarkIds
);
}
}
Expand Down

0 comments on commit 95cc665

Please sign in to comment.