Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0dae98b
improve compliance standard validation + add more unit tests
Mar 21, 2025
ca673f4
update unit tests
Mar 21, 2025
1cf3cee
move compliance standard validation to config sanitizer + add unit tests
Mar 25, 2025
23ceac3
add e2e tests
Mar 26, 2025
ce44787
add validation workflow UT
Mar 26, 2025
ca43bcd
merge conflict
Mar 26, 2025
d2875d2
punctuation
Mar 26, 2025
7d77802
revert to main version
Mar 26, 2025
438ceac
fix UTs + revert extra integrationtests changes
Mar 26, 2025
d5921c8
fix ut
Mar 26, 2025
69fa772
fix e2e tests
Mar 26, 2025
c7c6a3e
add support for none compliance standard
Mar 27, 2025
3e6df30
reword compliance standard error type to make it more generic
Mar 27, 2025
f93902a
fix tests
Mar 27, 2025
02cb33a
update error message
Mar 27, 2025
3d88351
updating errors and messaging
Mar 27, 2025
9af76aa
remove extra double quotes, fix punctuation
Mar 31, 2025
533d843
fix grammar
Mar 31, 2025
ba1eec8
fix error message
Mar 31, 2025
bb4a9e5
grammar
Mar 31, 2025
6a5c128
update unit test and remove quotes
Apr 2, 2025
2c7e84f
Refactor compliance standard validation (#993)
pragnya17 Apr 2, 2025
81d21cb
Merge branch 'main' into ppandrate_complianceStandardValidation
pragnya17 Apr 2, 2025
badd8bc
simplify method
Apr 2, 2025
defe97d
Merge branch 'ppandrate_complianceStandardValidation' of https://gith…
Apr 2, 2025
10ead12
Merge branch 'main' into ppandrate_complianceStandardValidation
pragnya17 Apr 3, 2025
544771c
Merge branch 'main' into ppandrate_complianceStandardValidation
pragnya17 Apr 3, 2025
75cbb91
fix merge conflict errors
Apr 3, 2025
331cc06
fix merge conflict errors
Apr 3, 2025
35415ad
update e2e test
Apr 3, 2025
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
18 changes: 18 additions & 0 deletions src/Microsoft.Sbom.Api/Config/ArgRevivers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Sbom.Api.Utils;
using Microsoft.Sbom.Contracts.Enums;
using Microsoft.Sbom.Extensions.Entities;
using PowerArgs;
Expand Down Expand Up @@ -54,4 +55,21 @@ public static AlgorithmName ReviveAlgorithmName(string _, string value)
throw new ValidationArgException($"Unable to parse algorithm name: {value}. Error: {e.Message}");
}
}

/// <summary>
/// Creates an <see cref="ComplianceStandardType"/> object from a string value.
/// </summary>
[ArgReviver]
public static ComplianceStandardType ReviveComplianceStandard(string _, string value)
{
try
{
return ComplianceStandardType.FromString(value);
}
catch (ArgumentException)
{
var supportedComplianceStandards = string.Join(", ", Constants.SupportedComplianceStandards);
throw new ValidationArgException($"Unknown Compliance Standard '{value}'. Options are {supportedComplianceStandards}.");
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.Sbom.Api/Config/Args/ValidationArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,5 @@ public class ValidationArgs : GenerationAndValidationCommonArgs
/// </summary>
[ArgDescription("The compliance standard to validate against")]
[ArgShortcut("cs")]
public string ComplianceStandard { get; set; }
public ComplianceStandardType ComplianceStandard { get; set; }
}
2 changes: 1 addition & 1 deletion src/Microsoft.Sbom.Api/Config/ConfigFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,5 @@ public class ConfigFile
/// <summary>
/// The compliance standard to validate against
/// </summary>
public string ComplianceStandard { get; set; }
public ComplianceStandardType ComplianceStandard { get; set; }
}
25 changes: 25 additions & 0 deletions src/Microsoft.Sbom.Api/Config/ConfigSanitizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Sbom.Api.Hashing;
using Microsoft.Sbom.Api.Utils;
Expand Down Expand Up @@ -81,6 +82,8 @@ public IConfiguration SanitizeConfig(IConfiguration configuration)
// Set default package supplier if not provided in configuration.
configuration.PackageSupplier = GetPackageSupplierFromAssembly(configuration, logger);

configuration.ComplianceStandard = GetComplianceStandard(configuration);

// Prevent null value for LicenseInformationTimeoutInSeconds.
// Values of (0, Constants.MaxLicenseFetchTimeoutInSeconds] are allowed. Negative values are replaced with the default, and
// the higher values are truncated to the maximum of Common.Constants.MaxLicenseFetchTimeoutInSeconds
Expand Down Expand Up @@ -225,6 +228,28 @@ private ConfigurationSetting<AlgorithmName> GetHashAlgorithmName(IConfiguration
};
}

private ConfigurationSetting<ComplianceStandardType> GetComplianceStandard(IConfiguration configuration)
{
// Convert to ComplianceStandard enum value.
var oldValue = configuration.ComplianceStandard;
var newValue = ComplianceStandardType.FromString(oldValue?.Value?.ToString());

// Compliance standard is only supported for ManifestInfo value of SPDX 3.0 and above.
if (!newValue.Equals(ComplianceStandardType.None) && !configuration.ManifestInfo.Value.Any(mi => mi.Equals(Constants.SPDX30ManifestInfo)))
{
throw new ValidationArgException($"Compliance standard {newValue.Name} is not supported with ManifestInfo value of {configuration.ManifestInfo.Value.First()}." +
$"Please use a supported combination.");
}
else
{
return new ConfigurationSetting<ComplianceStandardType>
{
Source = oldValue != null ? oldValue.Source : SettingSource.Default,
Value = newValue
};
}
}

private ConfigurationSetting<string> GetNamespaceBaseUri(IConfiguration configuration, ILogger logger)
{
// If assembly name is not defined but a namespace was provided, then return the current value.
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.Sbom.Api/Config/Validators/ConfigValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ public void Validate(string propertyName, object propertyValue, AttributeCollect
return;
}

// Skip validation of the ComplianceStandard. This case is handled in the ConfigSanitizer.
if (propertyName == nameof(IConfiguration.ComplianceStandard))
{
return;
}

switch (propertyValue)
{
case null:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using AutoMapper;
using Microsoft.Sbom.Common.Config;
using Microsoft.Sbom.Contracts.Enums;

namespace Microsoft.Sbom.Api.Config.ValueConverters;

/// <summary>
/// Converts a ComplianceStandard member to a ConfigurationSetting decorated string member.
/// </summary>
internal class ComplianceStandardConfigurationSettingAddingConverter : IValueConverter<ComplianceStandardType?, ConfigurationSetting<ComplianceStandardType>>
{
private SettingSource settingSource;

public ComplianceStandardConfigurationSettingAddingConverter(SettingSource settingSource)
{
this.settingSource = settingSource;
}

public ConfigurationSetting<ComplianceStandardType> Convert(ComplianceStandardType? sourceMember, ResolutionContext context)
{
if (sourceMember == null)
{
settingSource = SettingSource.Default;
}

return new ConfigurationSetting<ComplianceStandardType>
{
Source = settingSource,
Value = sourceMember ?? ComplianceStandardType.None
};
}
}
3 changes: 3 additions & 0 deletions src/Microsoft.Sbom.Api/ConfigurationProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ public ConfigurationProfile()
ForAllPropertyMaps(
p => p.SourceType == typeof(AlgorithmName),
(c, memberOptions) => memberOptions.ConvertUsing(new HashAlgorithmNameConfigurationSettingAddingConverter(GetSettingSourceFor(c.SourceMember.ReflectedType))));
ForAllPropertyMaps(
p => p.SourceType == typeof(ComplianceStandardType),
(c, memberOptions) => memberOptions.ConvertUsing(new ComplianceStandardConfigurationSettingAddingConverter(GetSettingSourceFor(c.SourceMember.ReflectedType))));
}

// Based on the type of source, return the settings type.
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.Sbom.Api/Entities/ErrorType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ public enum ErrorType
[EnumMember(Value = "Invalid input file")]
InvalidInputFile = 13,

[EnumMember(Value = "Invalid manifest SPDX version")]
InvalidManifestVersion = 14
[EnumMember(Value = "Compliance standard error")]
ComplianceStandardError = 14,
}
3 changes: 3 additions & 0 deletions src/Microsoft.Sbom.Api/Entities/FileValidationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public EntityError ToEntityError()
errorType = EntityErrorType.PackageError;
entityType = EntityType.Package;
break;
case ErrorType.ComplianceStandardError:
errorType = EntityErrorType.ComplianceStandardError;
break;
case ErrorType.Other:
errorType = EntityErrorType.Other;
break;
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.Sbom.Api/Utils/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ public static class Constants
SPDX30ManifestInfo
};

public static Collection<ComplianceStandardType> SupportedComplianceStandards = new()
{
ComplianceStandardType.NTIA,
ComplianceStandardType.None
};

public static List<Entities.ErrorType> SkipFailureReportingForErrors = new()
{
Entities.ErrorType.ManifestFolder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
using Microsoft.Sbom.Api.Executors;
using Microsoft.Sbom.Api.Manifest.FileHashes;
using Microsoft.Sbom.Common.Config;
using Microsoft.Sbom.Common.Spdx30Entities;
using Microsoft.Sbom.Entities;
using Microsoft.Sbom.Parsers.Spdx22SbomParser.Entities;
using Microsoft.Sbom.Parsers.Spdx30SbomParser.Entities;
using Microsoft.Sbom.Utils;
using Serilog;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Microsoft.Sbom.Api.Utils;
using Microsoft.Sbom.Api.Workflows.Helpers;
using Microsoft.Sbom.Common;
using Microsoft.Sbom.Common.ComplianceStandard;
using Microsoft.Sbom.Common.Config;
using Microsoft.Sbom.Extensions;
using Microsoft.Sbom.JsonAsynchronousNodeKit;
Expand Down Expand Up @@ -76,7 +77,7 @@ public async Task<bool> RunAsync()
using var stream = fileSystemUtils.OpenRead(sbomConfig.ManifestJsonFilePath);
var manifestInterface = manifestParserProvider.Get(sbomConfig.ManifestInfo);
var sbomParser = manifestInterface.CreateParser(stream);
sbomParser.SetComplianceStandard(configuration.ComplianceStandard?.Value);
sbomParser.EnforceComplianceStandard(configuration.ComplianceStandard?.Value);

// Validate signature
if (configuration.ValidateSignature != null && configuration.ValidateSignature.Value)
Expand Down Expand Up @@ -131,6 +132,7 @@ public async Task<bool> RunAsync()
totalNumberOfPackages = elementsResult.PackagesCount;

(successfullyValidatedFiles, fileValidationFailures) = await filesValidator.Validate(elementsResult.Files);
AddInvalidComplianceStandardElementsToFailures(fileValidationFailures, elementsResult.InvalidComplianceStandardElements);
ThrowOnInvalidInputFiles(fileValidationFailures);
break;
default:
Expand Down Expand Up @@ -238,6 +240,15 @@ private void LogIndividualFileResults(IEnumerable<FileValidationResult> validFai
Console.WriteLine(string.Empty);
validFailures.Where(vf => vf.ErrorType == ErrorType.MissingFile).ForEach(f => Console.WriteLine(f.Path));
Console.WriteLine("------------------------------------------------------------");

if (!NoOpComplianceStandard(configuration.ComplianceStandard))
{
Console.WriteLine($"Elements in the manifest that are non-compliant with {configuration.ComplianceStandard}:");
Console.WriteLine(string.Empty);
validFailures.Where(vf => vf.ErrorType == ErrorType.ComplianceStandardError).ForEach(f => Console.WriteLine(f.Path));
Console.WriteLine("------------------------------------------------------------");
}

Console.WriteLine("Unknown file failures:");
Console.WriteLine(string.Empty);
validFailures.Where(vf => vf.ErrorType == ErrorType.Other).ForEach(f => Console.WriteLine(f.Path));
Expand Down Expand Up @@ -276,6 +287,11 @@ private void LogResultsSummary(ValidationResult validationResultOutput, IEnumera
Console.WriteLine($"Additional files not in the manifest . . . . . . {validFailures.Count(v => v.ErrorType == ErrorType.AdditionalFile)}");
Console.WriteLine($"Files with invalid hashes . . . . . . . . . . . .{validFailures.Count(v => v.ErrorType == ErrorType.InvalidHash)}");
Console.WriteLine($"Files in the manifest missing from the disk . . .{validFailures.Count(v => v.ErrorType == ErrorType.MissingFile)}");
if (!NoOpComplianceStandard(configuration.ComplianceStandard))
{
Console.WriteLine($"Elements in the manifest that are non-compliant with {configuration.ComplianceStandard} . . . " +
$"{validFailures.Count(v => v.ErrorType == ErrorType.ComplianceStandardError)}");
}

if (validFailures.Any(vf => vf.ErrorType == ErrorType.NoPackagesFound))
{
Expand All @@ -293,4 +309,40 @@ private void ThrowOnInvalidInputFiles(List<FileValidationResult> fileValidationF
throw new InvalidDataException($"Your manifest file is malformed. {invalidInputFiles.First().Path}");
}
}

private void AddInvalidComplianceStandardElementsToFailures(List<FileValidationResult> fileValidationFailures, HashSet<InvalidElementInfo> invalidElements)
{
if (invalidElements == null || !invalidElements.Any())
{
return;
}

switch (configuration.ComplianceStandard?.Value?.Name)
{
case "NTIA":
AddInvalidNTIAElementsToFailures(fileValidationFailures, invalidElements);
break;
case "None":
break;
default:
break;
}
}

private void AddInvalidNTIAElementsToFailures(List<FileValidationResult> fileValidationFailures, HashSet<InvalidElementInfo> invalidElements)
{
foreach (var invalidElementInfo in invalidElements)
{
fileValidationFailures.Add(new FileValidationResult
{
Path = invalidElementInfo.ToString(),
ErrorType = ErrorType.ComplianceStandardError,
});
}
}

private bool NoOpComplianceStandard(ConfigurationSetting<Contracts.Enums.ComplianceStandardType> complianceStandard)
{
return complianceStandard?.Value?.Name == "None";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.Sbom.Common.ComplianceStandard;

internal static class ComplianceExtensions
{
/// <summary>
/// Gets the common entity type that is used by the parser.
/// </summary>
internal static string GetCommonEntityType(this string entityType)
{
// For these special cases, remove the prefix from the type.
switch (entityType)
{
case "software_File":
return "File";
case "software_Package":
return "Package";
default:
return entityType;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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 Microsoft.Sbom.Contracts.Enums;
using Microsoft.Sbom.Parsers.Spdx30SbomParser.ComplianceStandard.Interfaces;

namespace Microsoft.Sbom.Common.ComplianceStandard;

public static class ComplianceStandardEnforcerFactory
{
public static IComplianceStandardEnforcer Create(ComplianceStandardType complianceStandard)
{
return complianceStandard.Name switch
{
"NTIA" => new NTIAComplianceStandardEnforcer(),
"None" => new NoneComplianceStandardEnforcer(),
_ => throw new ArgumentException($"Unsupported compliance standard: {complianceStandard.Name}")
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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 Microsoft.Sbom.Parsers.Spdx30SbomParser.ComplianceStandard.Interfaces;

namespace Microsoft.Sbom.Common.ComplianceStandard.Enums;

public class NTIAErrorType : IComplianceStandardErrorType, IEquatable<NTIAErrorType>
{
public string Name { get; set; }

public NTIAErrorType(string name)
{
Name = name;
}

public override string ToString()
{
return Name ?? string.Empty;
}

public override bool Equals(object obj)
{
return Equals(obj as NTIAErrorType);
}

public bool Equals(NTIAErrorType other)
{
if (other == null)
{
return false;
}

return string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase);
}

public static NTIAErrorType InvalidNTIAElement => new NTIAErrorType("InvalidNTIAElement");

public static NTIAErrorType MissingValidSpdxDocument => new NTIAErrorType("MissingValidSpdxDocument");

public static NTIAErrorType AdditionalSpdxDocument => new NTIAErrorType("AdditionalSpdxDocument");

public static NTIAErrorType MissingValidCreationInfo => new NTIAErrorType("MissingValidCreationInfo");

public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode(StringComparison.OrdinalIgnoreCase);
}
}
Loading