Skip to content

Commit

Permalink
model and normalizer for bom.serialNumber
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
  • Loading branch information
jkowalleck committed Dec 14, 2022
1 parent 7219007 commit 82ef0b7
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 25 deletions.
4 changes: 3 additions & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ All notable changes to this project will be documented in this file.
and changed parameter & return type to non-nullable, was nullable ([#66] via [#131])
* BREAKING: renamed methods `{get,set}MetaData()` -> `{get,set}Metadata()` ([#133] via [#131])
and changed parameter & return type to non-nullable, was nullable ([#66] via [#131])
* Added `{get,set}SerialNumber()` (via [#186])
* `Component` class
* BREAKING: renamed methods `{get,set}DependenciesBomRefRepository()` -> `{get,set}Dependencies()` ([#133] via [#131])
and changed parameter & return type to non-nullable, was nullable ([#66] via [#131])
Expand All @@ -61,7 +62,7 @@ All notable changes to this project will be documented in this file.
and changed it work with class `LicenseRepository` only, was working with various `Models\License\*` types. ([#66] via [#131])
* BREAKING: Changed class property `version` to optional now, to reflect CycloneDX v1.4. ([#27] via [#118], [#131])
This affects constructor arguments, and affects methods `{get,set}Version()`.
* Added `{get,set}Author()` ([#184] via[#185])
* Added `{get,set}Author()` ([#184] via [#185])
* Added `{get,set}Properties()` (via [#165])
* `ExternalReference` class
* BREAKING: renamed methods `{get,set}HashRepository()` -> `{get,set}Hashes()` ([#133] via [#131])
Expand Down Expand Up @@ -185,6 +186,7 @@ All notable changes to this project will be documented in this file.
[#180]: https://github.com/CycloneDX/cyclonedx-php-library/pull/180
[#181]: https://github.com/CycloneDX/cyclonedx-php-library/pull/181
[#185]: https://github.com/CycloneDX/cyclonedx-php-library/pull/185
[#186]: https://github.com/CycloneDX/cyclonedx-php-library/pull/186

## 1.6.3 - 2022-09-15

Expand Down
54 changes: 54 additions & 0 deletions src/Core/Models/Bom.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@
*/
class Bom
{
// Property `bomFormat` is not part of model, it is runtime information.

// Property `specVersion` is not part of model, it is runtime information.

/**
* Every BOM generated SHOULD have a unique serial number, even if the contents of the BOM have not changed over time.
* If specified, the serial number MUST conform to RFC-4122.
* Use of serial numbers are RECOMMENDED.
*
* pattern: ^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
*
* @psalm-var non-empty-string|null
*/
private ?string $serialNumber = null;

/**
* @psalm-suppress PropertyNotSetInConstructor
*/
Expand All @@ -56,13 +71,52 @@ class Bom
*/
private ExternalReferenceRepository $externalReferences;

// Property `dependencies` is not part of this model, but part of `Component` and other models.
// The dependency graph can be normalized on render-time, no need to store it in the bom model.

public function __construct(?ComponentRepository $components = null)
{
$this->setComponents($components ?? new ComponentRepository());
$this->externalReferences = new ExternalReferenceRepository();
$this->metadata = new Metadata();
}

/**
* @psalm-return non-empty-string|null
*/
public function getSerialNumber(): ?string
{
return $this->serialNumber;
}

/**
* @param string|null $serialNumber an empty value or a valid urn:uuid
*
* @throws DomainException if version is neither empty nor a valid urn:uuid
*
* @return $this
*/
public function setSerialNumber(?string $serialNumber): self
{
if ('' === $serialNumber) {
$serialNumber = null;
}
if (null !== $serialNumber && !self::isValidSerialNumber($serialNumber)) {
throw new DomainException("Invalid value: $serialNumber");
}
$this->serialNumber = $serialNumber;

return $this;
}

private static function isValidSerialNumber(string $serialNumber): bool
{
return 1 === preg_match(
'/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/',
$serialNumber
);
}

public function getComponents(): ComponentRepository
{
return $this->components;
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Serialization/DOM/Normalizers/BomNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function normalize(Bom $bom): DOMElement
$element,
[
'version' => $bom->getVersion(),
// serialNumber
'serialNumber' => $bom->getSerialNumber(),
]
);

Expand Down
1 change: 1 addition & 0 deletions src/Core/Serialization/JSON/Normalizers/BomNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public function normalize(Bom $bom): array
'$schema' => self::SCHEMA[$factory->getSpec()->getVersion()] ?? null,
'bomFormat' => self::BOM_FORMAT,
'specVersion' => $factory->getSpec()->getVersion(),
'serialNumber' => $bom->getSerialNumber(),
'version' => $bom->getVersion(),
'metadata' => $this->normalizeMetadata($bom->getMetadata()),
'components' => $factory->makeForComponentRepository()->normalize($bom->getComponents()),
Expand Down
40 changes: 40 additions & 0 deletions tests/Core/Models/BomTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function testConstruct(): Bom

$bom = new Bom($components);

self::assertNull($bom->getSerialNumber());
self::assertSame(1, $bom->getVersion());
self::assertSame($components, $bom->getComponents());
self::assertCount(0, $bom->getExternalReferences());
Expand All @@ -57,6 +58,45 @@ public function testConstruct(): Bom
return $bom;
}

// region serialNumber setter&getter

/**
* @depends testConstruct
*/
public function testSerialNumber(Bom $bom): Bom
{
$serialNumber = 'urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79';
$setOn = $bom->setSerialNumber($serialNumber);

self::assertSame($bom, $setOn);
self::assertSame($serialNumber, $bom->getSerialNumber());

return $bom;
}

/**
* @depends testSerialNumber
*/
public function testSerialNumberEmptyString(Bom $bom): void
{
$setOn = $bom->setSerialNumber('');

self::assertSame($bom, $setOn);
self::assertNull($bom->getSerialNumber());
}

/**
* @depends testSerialNumber
*/
public function testSerialNumberEmptyStringInvalidValue(Bom $bom): void
{
$serialNumber = uniqid('invalid-value', true);
$this->expectException(DomainException::class);
$bom->setSerialNumber($serialNumber);
}

// endregion serialNumber setter&getter

// region components setter&getter&modifiers

/**
Expand Down
15 changes: 8 additions & 7 deletions tests/Core/Serialization/DOM/Normalizers/BomNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@ public function testNormalize(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getSerialNumber' => 'urn:uuid:12345678-dead-1337-beef-123456789012',
'getVersion' => 23,
]
);

$actual = $normalizer->normalize($bom);

self::assertStringEqualsDomNode(
'<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="23">'.
'<bom xmlns="http://cyclonedx.org/schema/bom/1.2" serialNumber="urn:uuid:12345678-dead-1337-beef-123456789012" version="23">'.
'<components></components>'.
'</bom>',
$actual
Expand All @@ -89,7 +90,7 @@ public function testNormalizeComponents(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getVersion' => 23,
'getVersion' => 42,
'getComponents' => $this->createStub(ComponentRepository::class),
]
);
Expand All @@ -102,7 +103,7 @@ public function testNormalizeComponents(): void
$actual = $normalizer->normalize($bom);

self::assertStringEqualsDomNode(
'<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="23">'.
'<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="42">'.
'<components><FakeComponent>dummy</FakeComponent></components>'.
'</bom>',
$actual
Expand Down Expand Up @@ -133,7 +134,7 @@ public function testNormalizeMetadata(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getVersion' => 23,
'getVersion' => 1337,
'getMetadata' => $this->createStub(Metadata::class),
]
);
Expand All @@ -146,7 +147,7 @@ public function testNormalizeMetadata(): void
$actual = $normalizer->normalize($bom);

self::assertStringEqualsDomNode(
'<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="23">'.
'<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1337">'.
'<metadata>FakeMetadata</metadata>'.
'<components></components>'.
'</bom>',
Expand Down Expand Up @@ -176,7 +177,7 @@ public function testNormalizeMetadataNotSupported(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getVersion' => 23,
'getVersion' => 1,
'getMetadata' => $this->createStub(Metadata::class),
]
);
Expand All @@ -188,7 +189,7 @@ public function testNormalizeMetadataNotSupported(): void
$actual = $normalizer->normalize($bom);

self::assertStringEqualsDomNode(
'<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="23">'.
'<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1">'.
'<components></components>'.
'</bom>',
$actual
Expand Down
26 changes: 14 additions & 12 deletions tests/Core/Serialization/JSON/Normalizers/BomNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public function testNormalize(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getSerialNumber' => 'urn:uuid:12345678-dead-1337-beef-123456789012',
'getVersion' => 23,
]
);
Expand All @@ -67,6 +68,7 @@ public function testNormalize(): void
'$schema' => 'http://cyclonedx.org/schema/bom-1.2b.schema.json',
'bomFormat' => 'CycloneDX',
'specVersion' => '1.2',
'serialNumber' => 'urn:uuid:12345678-dead-1337-beef-123456789012',
'version' => 23,
'components' => [],
],
Expand All @@ -89,7 +91,7 @@ public function testNormalizeComponents(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getVersion' => 23,
'getVersion' => 42,
'getComponents' => $this->createStub(ComponentRepository::class),
]
);
Expand All @@ -106,7 +108,7 @@ public function testNormalizeComponents(): void
'$schema' => 'http://cyclonedx.org/schema/bom-1.2b.schema.json',
'bomFormat' => 'CycloneDX',
'specVersion' => '1.2',
'version' => 23,
'version' => 42,
'components' => ['FakeComponents'],
],
$actual
Expand Down Expand Up @@ -139,7 +141,7 @@ public function testNormalizeMetadata(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getVersion' => 23,
'getVersion' => 1337,
'getMetadata' => $this->createStub(Metadata::class),
]
);
Expand All @@ -156,7 +158,7 @@ public function testNormalizeMetadata(): void
'$schema' => 'http://cyclonedx.org/schema/bom-1.2b.schema.json',
'bomFormat' => 'CycloneDX',
'specVersion' => '1.2',
'version' => 23,
'version' => 1337,
'metadata' => ['FakeMetadata'],
'components' => [],
],
Expand Down Expand Up @@ -185,7 +187,7 @@ public function testNormalizeMetadataEmpty(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getVersion' => 23,
'getVersion' => 1,
'getMetadata' => $this->createStub(Metadata::class),
]
);
Expand All @@ -201,7 +203,7 @@ public function testNormalizeMetadataEmpty(): void
'$schema' => 'http://cyclonedx.org/schema/bom-1.2b.schema.json',
'bomFormat' => 'CycloneDX',
'specVersion' => '1.2',
'version' => 23,
'version' => 1,
'components' => [],
],
$actual
Expand Down Expand Up @@ -229,7 +231,7 @@ public function testNormalizeMetadataSkipped(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getVersion' => 23,
'getVersion' => 1,
'getMetadata' => $this->createStub(Metadata::class),
]
);
Expand All @@ -245,7 +247,7 @@ public function testNormalizeMetadataSkipped(): void
'$schema' => 'http://cyclonedx.org/schema/bom-1.2b.schema.json',
'bomFormat' => 'CycloneDX',
'specVersion' => '1.2',
'version' => 23,
'version' => 1,
'components' => [],
],
$actual
Expand Down Expand Up @@ -277,7 +279,7 @@ public function testNormalizeDependencies(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getVersion' => 23,
'getVersion' => 1,
'getMetadata' => $this->createStub(Metadata::class),
]
);
Expand All @@ -294,7 +296,7 @@ public function testNormalizeDependencies(): void
'$schema' => 'http://cyclonedx.org/schema/bom-1.2b.schema.json',
'bomFormat' => 'CycloneDX',
'specVersion' => '1.2',
'version' => 23,
'version' => 1,
'components' => [],
'dependencies' => ['FakeDependencies' => 'dummy'],
],
Expand Down Expand Up @@ -323,7 +325,7 @@ public function testNormalizeDependenciesOmitWhenEmpty(): void
$bom = $this->createConfiguredMock(
Bom::class,
[
'getVersion' => 23,
'getVersion' => 1,
'getMetadata' => $this->createStub(Metadata::class),
]
);
Expand All @@ -340,7 +342,7 @@ public function testNormalizeDependenciesOmitWhenEmpty(): void
'$schema' => 'http://cyclonedx.org/schema/bom-1.2b.schema.json',
'bomFormat' => 'CycloneDX',
'specVersion' => '1.2',
'version' => 23,
'version' => 1,
'components' => [],
// 'dependencies' is unset,
],
Expand Down
6 changes: 2 additions & 4 deletions tests/_data/BomModelProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,8 @@ abstract class BomModelProvider
public static function allBomTestData(): Generator
{
yield from self::bomPlain();

yield from self::bomWithAllComponents();

yield from self::bomWithAllMetadata();

yield from self::bomWithExternalReferences();
}

Expand All @@ -81,7 +78,8 @@ public static function allBomTestData(): Generator
public static function bomPlain(): Generator
{
yield 'bom plain' => [new Bom()];
yield 'bom plain v23' => [(new Bom())->setVersion(23)];
yield 'bom plain with version' => [(new Bom())->setVersion(23)];
yield 'bom plain with serialNumber' => [(new Bom())->setSerialNumber('urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79')];
}

/**
Expand Down

0 comments on commit 82ef0b7

Please sign in to comment.