Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions docs/detectors/spdx.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ The detector:
- Document name
- SPDX version
- Root element ID from `documentDescribes` (defaults to `SPDXRef-Document` if not specified)
- Creator tool from `creationInfo.creators` (e.g., `Tool: microsoft/sbom-tool-2.2.0`)
- Creator organization from `creationInfo.creators` (e.g., `Organization: Microsoft`)
- Creates an `SpdxComponent` to represent the SPDX document

The detector does not parse or register individual packages listed within the SPDX document; it only registers the SPDX document itself as a component.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,13 @@ public SpdxComponent(string spdxVersion, Uri documentNamespace, string name, str
[JsonPropertyName("path")]
public string Path { get; set; }

[JsonPropertyName("creatorTool")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string CreatorTool { get; set; }

[JsonPropertyName("creatorOrganization")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string CreatorOrganization { get; set; }
Comment thread
alisonlomaka marked this conversation as resolved.
Outdated

protected override string ComputeBaseId() => $"{this.Name}-{this.SpdxVersion}-{this.Checksum}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,37 @@ private SpdxComponent ConvertJsonElementToSbomComponent(ProcessRequest processRe
var path = processRequest.ComponentStream.Location;
var component = new SpdxComponent(spdxVersion, new Uri(sbomNamespace), name, fileHash, rootElementId, path);

if (document.TryGetProperty("creationInfo", out var creationInfoElement)
&& creationInfoElement.TryGetProperty("creators", out var creatorsElement)
&& creatorsElement.ValueKind == JsonValueKind.Array)
{
foreach (var creator in creatorsElement.EnumerateArray())
{
var creatorString = creator.GetString();
if (creatorString == null)
{
continue;
Comment thread
alisonlomaka marked this conversation as resolved.
}

if (component.CreatorTool == null && creatorString.StartsWith("Tool: ", StringComparison.Ordinal))
{
var tool = creatorString["Tool: ".Length..];
if (!string.IsNullOrEmpty(tool))
{
component.CreatorTool = tool;
}
}
else if (component.CreatorOrganization == null && creatorString.StartsWith("Organization: ", StringComparison.Ordinal))
{
var org = creatorString["Organization: ".Length..];
if (!string.IsNullOrEmpty(org))
{
component.CreatorOrganization = org;
}
}
Comment thread
alisonlomaka marked this conversation as resolved.
}
}

return component;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ public async Task TestSbomDetector_SimpleSbomAsync()
sbomComponent.SpdxVersion.Should().Be("SPDX-2.2");
sbomComponent.Checksum.Should().Be(checksum);
sbomComponent.Path.Should().Be(Path.Combine(Path.GetTempPath(), spdxFileName));

sbomComponent.CreatorTool.Should().Be("Microsoft.SBOMTool-1.0.0");
sbomComponent.CreatorOrganization.Should().Be("Microsoft");
}

[TestMethod]
Expand Down Expand Up @@ -160,4 +163,182 @@ public async Task TestSbomDetector_InvalidFileAsync()
var components = detectedComponents.ToList();
components.Should().BeEmpty();
}

[TestMethod]
public async Task TestSbomDetector_ExtractsCreatorToolAndOrganizationAsync()
{
var spdxFile = /*lang=json,strict*/ @"{
""spdxVersion"": ""SPDX-2.2"",
""SPDXID"": ""SPDXRef-DOCUMENT"",
""name"": ""TestDoc"",
""documentNamespace"": ""https://sbom.microsoft/test/1.0.0/abc"",
""creationInfo"": {
""created"": ""2024-01-01T00:00:00Z"",
""creators"": [
""Tool: microsoft/sbom-tool-2.2.0"",
""Organization: Microsoft""
]
},
""documentDescribes"": [""SPDXRef-RootPackage""],
""packages"": [],
""relationships"": []
}";

var (scanResult, componentRecorder) = await this.detectorTestUtility
.WithFile("manifest.spdx.json", spdxFile)
.ExecuteDetectorAsync();

scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);

var sbomComponent = (SpdxComponent)componentRecorder.GetDetectedComponents().Single().Component;
sbomComponent.CreatorTool.Should().Be("microsoft/sbom-tool-2.2.0");
sbomComponent.CreatorOrganization.Should().Be("Microsoft");
}

[TestMethod]
public async Task TestSbomDetector_MissingCreationInfoReturnsNullAsync()
{
var spdxFile = /*lang=json,strict*/ @"{
""spdxVersion"": ""SPDX-2.2"",
""SPDXID"": ""SPDXRef-DOCUMENT"",
""name"": ""TestDoc"",
""documentNamespace"": ""https://sbom.microsoft/test/1.0.0/abc"",
""documentDescribes"": [""SPDXRef-RootPackage""],
""packages"": [],
""relationships"": []
}";

var (scanResult, componentRecorder) = await this.detectorTestUtility
.WithFile("manifest.spdx.json", spdxFile)
.ExecuteDetectorAsync();

scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);

var sbomComponent = (SpdxComponent)componentRecorder.GetDetectedComponents().Single().Component;
sbomComponent.CreatorTool.Should().BeNull();
sbomComponent.CreatorOrganization.Should().BeNull();
}

[TestMethod]
Comment thread
alisonlomaka marked this conversation as resolved.
public async Task TestSbomDetector_MultipleCreatorsPicksFirstToolAndOrgAsync()
{
var spdxFile = /*lang=json,strict*/ @"{
""spdxVersion"": ""SPDX-2.2"",
""SPDXID"": ""SPDXRef-DOCUMENT"",
""name"": ""TestDoc"",
""documentNamespace"": ""https://sbom.microsoft/test/1.0.0/abc"",
""creationInfo"": {
""created"": ""2024-01-01T00:00:00Z"",
""creators"": [
""Tool: first-tool-1.0"",
""Tool: second-tool-2.0"",
""Organization: FirstOrg"",
""Organization: SecondOrg""
]
},
""documentDescribes"": [""SPDXRef-RootPackage""],
""packages"": [],
""relationships"": []
}";

var (scanResult, componentRecorder) = await this.detectorTestUtility
.WithFile("manifest.spdx.json", spdxFile)
.ExecuteDetectorAsync();

scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);

var sbomComponent = (SpdxComponent)componentRecorder.GetDetectedComponents().Single().Component;
sbomComponent.CreatorTool.Should().Be("first-tool-1.0");
sbomComponent.CreatorOrganization.Should().Be("FirstOrg");
}

[TestMethod]
public async Task TestSbomDetector_OnlyToolNoOrganizationAsync()
{
var spdxFile = /*lang=json,strict*/ @"{
""spdxVersion"": ""SPDX-2.2"",
""SPDXID"": ""SPDXRef-DOCUMENT"",
""name"": ""TestDoc"",
""documentNamespace"": ""https://sbom.microsoft/test/1.0.0/abc"",
""creationInfo"": {
""created"": ""2024-01-01T00:00:00Z"",
""creators"": [
""Tool: my-tool-3.0""
]
},
""documentDescribes"": [""SPDXRef-RootPackage""],
""packages"": [],
""relationships"": []
}";

var (scanResult, componentRecorder) = await this.detectorTestUtility
.WithFile("manifest.spdx.json", spdxFile)
.ExecuteDetectorAsync();

scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);

var sbomComponent = (SpdxComponent)componentRecorder.GetDetectedComponents().Single().Component;
sbomComponent.CreatorTool.Should().Be("my-tool-3.0");
sbomComponent.CreatorOrganization.Should().BeNull();
}

[TestMethod]
public async Task TestSbomDetector_OnlyOrganizationNoToolAsync()
{
var spdxFile = /*lang=json,strict*/ @"{
""spdxVersion"": ""SPDX-2.2"",
""SPDXID"": ""SPDXRef-DOCUMENT"",
""name"": ""TestDoc"",
""documentNamespace"": ""https://sbom.microsoft/test/1.0.0/abc"",
""creationInfo"": {
""created"": ""2024-01-01T00:00:00Z"",
""creators"": [
""Organization: Contoso""
]
},
""documentDescribes"": [""SPDXRef-RootPackage""],
""packages"": [],
""relationships"": []
}";

var (scanResult, componentRecorder) = await this.detectorTestUtility
.WithFile("manifest.spdx.json", spdxFile)
.ExecuteDetectorAsync();

scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);

var sbomComponent = (SpdxComponent)componentRecorder.GetDetectedComponents().Single().Component;
sbomComponent.CreatorTool.Should().BeNull();
sbomComponent.CreatorOrganization.Should().Be("Contoso");
}

[TestMethod]
public async Task TestSbomDetector_CreatorsWithNoToolOrOrgPrefixAsync()
{
var spdxFile = /*lang=json,strict*/ @"{
""spdxVersion"": ""SPDX-2.2"",
""SPDXID"": ""SPDXRef-DOCUMENT"",
""name"": ""TestDoc"",
""documentNamespace"": ""https://sbom.microsoft/test/1.0.0/abc"",
""creationInfo"": {
""created"": ""2024-01-01T00:00:00Z"",
""creators"": [
""Person: John Doe (john@example.com)""
]
},
""documentDescribes"": [""SPDXRef-RootPackage""],
""packages"": [],
""relationships"": []
}";

var (scanResult, componentRecorder) = await this.detectorTestUtility
.WithFile("manifest.spdx.json", spdxFile)
.ExecuteDetectorAsync();

scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);

var sbomComponent = (SpdxComponent)componentRecorder.GetDetectedComponents().Single().Component;
sbomComponent.CreatorTool.Should().BeNull();
sbomComponent.CreatorOrganization.Should().BeNull();
}
}
Loading