Skip to content

Commit 37c29b8

Browse files
authored
Serial number (#597)
Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 3158763 commit 37c29b8

File tree

11 files changed

+158
-17
lines changed

11 files changed

+158
-17
lines changed

HISTORY.md

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
44

55
## unreleased
66

7+
* Fixed
8+
* "Bom.serialNumber" data model can have values following the alternative format allowed in CycloneDX XML specification ([#588] via [#])
9+
* `Serialize.{JSON,XML}.Normalize.BomNormalizer.normalize` now omits invalid/unsupported values for serialNumber ([#588] via [#])
10+
* Changed
11+
* Property `Models.Bom.serialNumber` is of type `string`, was type-aliased `Types.UrnUuid = string` ([#588] via [#])
12+
Also the setter no longer throws exceptions, since no string format is illegal.
13+
This is considered a non-breaking behaviour change, because the corresponding normalizers assure valid data results.
14+
* Added
15+
* Bom serialNumber generator: `Utils.BomUtility.randomSerialNumber()` ([#588] via [#])
16+
* Deprecation
17+
* Type alias `Types.UrnUuid = string` became deprecated (via [#])
18+
Use type `string` instead.
19+
* Function `Types.isUrnUuid` became deprecated (via [#])
20+
21+
[#588]: https://github.com/CycloneDX/cyclonedx-javascript-library/issues/588
22+
723
## 1.12.2 - 2023-03-28
824

925
* Fixed

src/index.common.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ export * as Models from './models'
2222
export * as SPDX from './spdx'
2323
export * as Spec from './spec'
2424
export * as Types from './types'
25+
export * as Utils from './utils'
2526
// do not export the _helpers, they are for internal use only

src/models/bom.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ SPDX-License-Identifier: Apache-2.0
1717
Copyright (c) OWASP Foundation. All Rights Reserved.
1818
*/
1919

20-
import type { PositiveInteger, UrnUuid } from '../types'
21-
import { isPositiveInteger, isUrnUuid } from '../types'
20+
import type { PositiveInteger } from '../types'
21+
import { isPositiveInteger } from '../types'
2222
import { ComponentRepository } from './component'
2323
import { Metadata } from './metadata'
2424
import { VulnerabilityRepository } from './vulnerability'
@@ -40,7 +40,7 @@ export class Bom {
4040
#version: PositiveInteger = 1
4141

4242
/** @see {@link serialNumber} */
43-
#serialNumber?: UrnUuid
43+
#serialNumber?: string
4444

4545
// Property `bomFormat` is not part of model, it is runtime information.
4646
// Property `specVersion` is not part of model, it is runtime information.
@@ -74,17 +74,13 @@ export class Bom {
7474
this.#version = value
7575
}
7676

77-
get serialNumber (): UrnUuid | undefined {
77+
get serialNumber (): string | undefined {
7878
return this.#serialNumber
7979
}
8080

81-
/**
82-
* @throws {@link TypeError} if value is neither {@link UrnUuid} nor `undefined`
83-
*/
84-
set serialNumber (value: UrnUuid | undefined) {
85-
if (value !== undefined && !isUrnUuid(value)) {
86-
throw new TypeError('Not UrnUuid nor undefined')
87-
}
88-
this.#serialNumber = value
81+
set serialNumber (value: string | undefined) {
82+
this.#serialNumber = value === ''
83+
? undefined
84+
: value
8985
}
9086
}

src/serialize/json/normalize.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ export class BomNormalizer extends BaseJsonNormalizer<Models.Bom> {
132132
bomFormat: 'CycloneDX',
133133
specVersion: this._factory.spec.version,
134134
version: data.version,
135-
serialNumber: data.serialNumber,
135+
serialNumber: this.#isEligibleSerialNumber(data.serialNumber)
136+
? data.serialNumber
137+
: undefined,
136138
metadata: this._factory.makeForMetadata().normalize(data.metadata, options),
137139
components: data.components.size > 0
138140
? this._factory.makeForComponent().normalizeIterable(data.components, options)
@@ -143,6 +145,12 @@ export class BomNormalizer extends BaseJsonNormalizer<Models.Bom> {
143145
: undefined
144146
}
145147
}
148+
149+
#isEligibleSerialNumber (v: string | undefined): boolean {
150+
return v !== undefined &&
151+
// see https://github.com/CycloneDX/specification/blob/ef71717ae0ecb564c0b4c9536d6e9e57e35f2e69/schema/bom-1.4.schema.json#L39
152+
/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(v)
153+
}
146154
}
147155

148156
export class MetadataNormalizer extends BaseJsonNormalizer<Models.Metadata> {

src/serialize/json/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved.
2020
import type * as Enums from '../../enums'
2121
import type { HashContent } from '../../models'
2222
import type { SpdxId } from '../../spdx'
23-
import type { CPE, Integer, UrnUuid } from '../../types'
23+
import type { CPE, Integer } from '../../types'
2424

2525
// eslint-disable-next-line @typescript-eslint/no-namespace
2626
export namespace JsonSchema {
@@ -69,7 +69,7 @@ export namespace Normalized {
6969
bomFormat: 'CycloneDX'
7070
specVersion: string
7171
version: Integer
72-
serialNumber?: UrnUuid
72+
serialNumber?: string
7373
metadata?: Metadata
7474
components?: Component[]
7575
externalReferences?: ExternalReference[]

src/serialize/xml/normalize.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,9 @@ export class BomNormalizer extends BaseXmlNormalizer<Models.Bom> {
145145
namespace: xmlNamespace.get(this._factory.spec.version),
146146
attributes: {
147147
version: data.version,
148-
serialNumber: data.serialNumber
148+
serialNumber: this.#isEligibleSerialNumber(data.serialNumber)
149+
? data.serialNumber
150+
: undefined
149151
},
150152
children: [
151153
data.metadata
@@ -158,6 +160,12 @@ export class BomNormalizer extends BaseXmlNormalizer<Models.Bom> {
158160
].filter(isNotUndefined)
159161
}
160162
}
163+
164+
#isEligibleSerialNumber (v: string | undefined): boolean {
165+
return v !== undefined &&
166+
// see https://github.com/CycloneDX/specification/blob/ef71717ae0ecb564c0b4c9536d6e9e57e35f2e69/schema/bom-1.4.xsd#L699
167+
/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$|^\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}$/.test(v)
168+
}
161169
}
162170

163171
export class MetadataNormalizer extends BaseXmlNormalizer<Models.Metadata> {

src/types/urn.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,19 @@ Copyright (c) OWASP Foundation. All Rights Reserved.
2222
* {@link https://datatracker.ietf.org/doc/html/rfc4122 | RFC 4122}.
2323
*
2424
* @see {@link isUrnUuid}
25+
*
26+
* @deprecated Use `string` instead.
2527
*/
2628
export type UrnUuid = string
2729

28-
/* regular expression was taken from the CycloneDX schema definitions. */
30+
/* regular expression was taken from the CycloneDX schema definitions.
31+
* see https://github.com/CycloneDX/specification/blob/ef71717ae0ecb564c0b4c9536d6e9e57e35f2e69/schema/bom-1.4.schema.json#L39
32+
*/
2933
const urnUuidPattern = /^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
3034

35+
/**
36+
* @deprecated This function will be removed during next mayor release.
37+
*/
3138
export function isUrnUuid (value: any): value is UrnUuid {
3239
return typeof value === 'string' &&
3340
urnUuidPattern.test(value)

src/utils/bomUtility.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
export function randomSerialNumber (): string {
20+
const b = [
21+
Math.round(Math.random() * 0xFFFF),
22+
Math.round(Math.random() * 0xFFFF),
23+
Math.round(Math.random() * 0xFFFF),
24+
// UUID version 4
25+
Math.round(Math.random() * 0x0FFF) | 0x4000,
26+
// UUID version 4 variant 1
27+
Math.round(Math.random() * 0x3FFF) | 0x8000,
28+
Math.round(Math.random() * 0xFFFF),
29+
Math.round(Math.random() * 0xFFFF),
30+
Math.round(Math.random() * 0xFFFF)
31+
].map(n => n.toString(16).padStart(4, '0'))
32+
return `urn:uuid:${b[0]}${b[1]}-${b[2]}-${b[3]}-${b[4]}-${b[5]}${b[6]}${b[7]}`
33+
}

src/utils/index.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
export * as BomUtility from './bomUtility'

tests/unit/Models.Bom.spec.js

+13
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,17 @@ suite('Models.Bom', () => {
9696
})
9797
)
9898
)
99+
100+
suite('can set serialNumber', () => {
101+
test('empty string', () => {
102+
const bom = new Bom()
103+
bom.serialNumber = ''
104+
assert.strictEqual(bom.serialNumber, undefined)
105+
})
106+
test('something', () => {
107+
const bom = new Bom()
108+
bom.serialNumber = 'something'
109+
assert.strictEqual(bom.serialNumber, 'something')
110+
})
111+
})
99112
})

tests/unit/Utils.BomUtility.spec.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
const assert = require('assert')
21+
const { suite, test } = require('mocha')
22+
23+
const {
24+
Utils: {
25+
BomUtility
26+
}
27+
} = require('../../')
28+
29+
suite('Utils.BomUtility', () => {
30+
suite('randomSerialNumber()', () => {
31+
test('has correct format according to XSD', () => {
32+
const value = BomUtility.randomSerialNumber()
33+
assert.match(value, /^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$|^\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}$/)
34+
})
35+
test('has correct format according to JSON schema', () => {
36+
const value = BomUtility.randomSerialNumber()
37+
assert.match(value, /^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
38+
})
39+
})
40+
})

0 commit comments

Comments
 (0)