diff --git a/src/Microsoft.Sbom.Api/Executors/ConcurrentSha256HashValidator.cs b/src/Microsoft.Sbom.Api/Executors/ConcurrentSha256HashValidator.cs index 4f394e7b3..381bc184f 100644 --- a/src/Microsoft.Sbom.Api/Executors/ConcurrentSha256HashValidator.cs +++ b/src/Microsoft.Sbom.Api/Executors/ConcurrentSha256HashValidator.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.Sbom.Api.Entities; using Microsoft.Sbom.Api.Manifest.FileHashes; +using Microsoft.Sbom.Common.Config; using Microsoft.Sbom.Contracts.Enums; using Microsoft.Sbom.Extensions.Entities; @@ -19,12 +20,16 @@ namespace Microsoft.Sbom.Api.Executors; public class ConcurrentSha256HashValidator { private readonly FileHashesDictionary fileHashesDictionary; + private readonly IConfiguration configuration; - public ConcurrentSha256HashValidator(FileHashesDictionary fileHashesDictionary) + public ConcurrentSha256HashValidator(FileHashesDictionary fileHashesDictionary, IConfiguration configuration) { this.fileHashesDictionary = fileHashesDictionary ?? throw new ArgumentNullException(nameof(fileHashesDictionary)); + this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); } + private AlgorithmName HashAlgorithmName => configuration.HashAlgorithm?.Value ?? AlgorithmName.SHA256; + public (ChannelReader output, ChannelReader errors) Validate(ChannelReader fileWithHash) { @@ -47,9 +52,9 @@ public ConcurrentSha256HashValidator(FileHashesDictionary fileHashesDictionary) private async Task Validate(InternalSbomFileInfo internalFileInfo, Channel output, Channel errors) { - var sha256Checksum = internalFileInfo.Checksum.FirstOrDefault(c => c.Algorithm == AlgorithmName.SHA256); + var checksum = internalFileInfo.Checksum.FirstOrDefault(c => c.Algorithm == HashAlgorithmName); var fileHashes = new FileHashes(); - fileHashes.SetHash(internalFileInfo.FileLocation, sha256Checksum); + fileHashes.SetHash(internalFileInfo.FileLocation, checksum); FileValidationResult failureResult = null; var newValue = fileHashesDictionary.FileHashes.AddOrUpdate(internalFileInfo.Path, fileHashes, (key, oldValue) => @@ -66,7 +71,7 @@ private async Task Validate(InternalSbomFileInfo internalFileInfo, Channel Get(IConfiguration configuration, ISbomConfig sbomConfig, string eventName) { var fileHashesDictionary = new FileHashesDictionary(new System.Collections.Concurrent.ConcurrentDictionary(osUtils.GetFileSystemStringComparer())); - var hashValidator = new ConcurrentSha256HashValidator(fileHashesDictionary); + var hashValidator = new ConcurrentSha256HashValidator(fileHashesDictionary, configuration); var filesValidator = new FilesValidator(directoryWalker, configuration, log, fileHasher, fileFilterer, hashValidator, enumeratorChannel, fileConverter, fileHashesDictionary, spdxFileFilterer); return new SbomParserBasedValidationWorkflow(recorder, signValidationProvider, log, manifestParserProvider, configuration, sbomConfig, filesValidator, validationResultGenerator, outputWriter, fileSystemUtils, osUtils, eventName); } diff --git a/test/Microsoft.Sbom.Api.Tests/Executors/ConcurrentSha256HashValidatorTests.cs b/test/Microsoft.Sbom.Api.Tests/Executors/ConcurrentSha256HashValidatorTests.cs new file mode 100644 index 000000000..67fa9d1f8 --- /dev/null +++ b/test/Microsoft.Sbom.Api.Tests/Executors/ConcurrentSha256HashValidatorTests.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Concurrent; +using System.Threading.Channels; +using System.Threading.Tasks; +using Microsoft.Sbom.Api.Manifest.FileHashes; +using Microsoft.Sbom.Common.Config; +using Microsoft.Sbom.Contracts; +using Microsoft.Sbom.Contracts.Enums; +using Microsoft.Sbom.Extensions.Entities; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using ErrorType = Microsoft.Sbom.Api.Entities.ErrorType; +using FileLocation = Microsoft.Sbom.Entities.FileLocation; + +namespace Microsoft.Sbom.Api.Executors.Tests; + +[TestClass] +public class ConcurrentSha256HashValidatorTests +{ + [TestMethod] + public async Task ChecksumForConfiguredAlgorithm_MatchesAsync() + { + var validationResults = BuildValidator(AlgorithmName.SHA256).Validate(await BuildFilesAsync(AlgorithmName.SHA256)); + + var validatedCount = 0; + await foreach (var output in validationResults.output.ReadAllAsync()) + { + validatedCount++; + Assert.AreEqual("/test/file", output.Path); + } + + var errorCount = 0; + await foreach (var error in validationResults.errors.ReadAllAsync()) + { + errorCount++; + } + + Assert.AreEqual(1, validatedCount); + Assert.AreEqual(0, errorCount); + } + + [TestMethod] + public async Task ChecksumMissingForConfiguredAlgorithm_DoesNotMatchAsync() + { + var validationResults = BuildValidator(AlgorithmName.SHA1).Validate(await BuildFilesAsync(AlgorithmName.SHA256)); + + var validatedCount = 0; + await foreach (var output in validationResults.output.ReadAllAsync()) + { + validatedCount++; + } + + var errorCount = 0; + await foreach (var error in validationResults.errors.ReadAllAsync()) + { + errorCount++; + Assert.AreEqual(ErrorType.InvalidHash, error.ErrorType); + } + + Assert.AreEqual(0, validatedCount); + Assert.AreEqual(1, errorCount); + } + + private static ConcurrentSha256HashValidator BuildValidator(AlgorithmName configuredAlgorithm) + { + var configuration = new Mock(); + configuration.SetupGet(c => c.HashAlgorithm).Returns(new ConfigurationSetting { Value = configuredAlgorithm }); + + var fileHashes = new FileHashesDictionary(new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase)); + return new ConcurrentSha256HashValidator(fileHashes, configuration.Object); + } + + private static async Task> BuildFilesAsync(AlgorithmName checksumAlgorithm) + { + var files = Channel.CreateUnbounded(); + foreach (var location in new[] { FileLocation.OnDisk, FileLocation.InSbomFile }) + { + await files.Writer.WriteAsync(new InternalSbomFileInfo + { + Path = "/test/file", + FileLocation = location, + Checksum = new Checksum[] { new Checksum { Algorithm = checksumAlgorithm, ChecksumValue = "hash" } } + }); + } + + files.Writer.Complete(); + return files; + } +} diff --git a/test/Microsoft.Sbom.Api.Tests/Workflows/SbomParserBasedValidationWorkflowTests.cs b/test/Microsoft.Sbom.Api.Tests/Workflows/SbomParserBasedValidationWorkflowTests.cs index 660246c60..4868564ad 100644 --- a/test/Microsoft.Sbom.Api.Tests/Workflows/SbomParserBasedValidationWorkflowTests.cs +++ b/test/Microsoft.Sbom.Api.Tests/Workflows/SbomParserBasedValidationWorkflowTests.cs @@ -164,7 +164,7 @@ public async Task SbomParserBasedValidationWorkflowTests_ReturnsSuccessAndValida var osUtilsMock = new Mock(MockBehavior.Strict); var fileHash = BuildFileHash(); - var hashValidator = new ConcurrentSha256HashValidator(fileHash); + var hashValidator = new ConcurrentSha256HashValidator(fileHash, configurationMock.Object); var enumeratorChannel = new EnumeratorChannel(mockLogger.Object); var fileConverter = new SbomFileToFileInfoConverter(new FileTypeUtils()); var spdxFileFilterer = new FileFilterer(rootFileFilterMock, mockLogger.Object, configurationMock.Object, fileSystemMock.Object); @@ -342,7 +342,7 @@ public async Task SbomParserBasedValidationWorkflowTests_ReturnsSuccessAndValida osUtilsMock.Setup(x => x.IsCaseSensitiveOS()).Returns(false); var fileHash = BuildFileHash(); - var hashValidator = new ConcurrentSha256HashValidator(fileHash); + var hashValidator = new ConcurrentSha256HashValidator(fileHash, configurationMock.Object); var enumeratorChannel = new EnumeratorChannel(mockLogger.Object); var fileConverter = new SbomFileToFileInfoConverter(new FileTypeUtils()); var spdxFileFilterer = new FileFilterer(rootFileFilterMock, mockLogger.Object, configurationMock.Object, fileSystemMock.Object); @@ -535,7 +535,7 @@ private FilesValidator GetFilesValidator(Mock fileSystemMock, rootFileFilterMock.Init(); var fileHash = BuildFileHash(); - var hashValidator = new ConcurrentSha256HashValidator(fileHash); + var hashValidator = new ConcurrentSha256HashValidator(fileHash, configurationMock.Object); var enumeratorChannel = new EnumeratorChannel(mockLogger.Object); var fileConverter = new SbomFileToFileInfoConverter(new FileTypeUtils()); var spdxFileFilterer = new FileFilterer(rootFileFilterMock, mockLogger.Object, configurationMock.Object, fileSystemMock.Object);