Skip to content

Commit bdd6c7f

Browse files
committed
feat: metadata.tools support compoennts and services
Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 305f4da commit bdd6c7f

File tree

7 files changed

+140
-16
lines changed

7 files changed

+140
-16
lines changed

src/_helpers/iterable.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
export function * chainI<T = any> (...iterables: Array<Iterable<T>>): Generator<T> {
21+
for (const iterable of iterables) {
22+
for (const item of iterable) {
23+
yield item
24+
}
25+
}
26+
}

src/models/metadata.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { LifecycleRepository } from './lifecycle'
2323
import { OrganizationalContactRepository } from './organizationalContact'
2424
import type { OrganizationalEntity } from './organizationalEntity'
2525
import { PropertyRepository } from './property'
26-
import { ToolRepository } from './tool'
26+
import { Tools } from './tool'
2727

2828
export interface OptionalMetadataProperties {
2929
timestamp?: Metadata['timestamp']
@@ -40,7 +40,7 @@ export interface OptionalMetadataProperties {
4040
export class Metadata {
4141
timestamp?: Date
4242
lifecycles: LifecycleRepository
43-
tools: ToolRepository
43+
tools: Tools
4444
authors: OrganizationalContactRepository
4545
component?: Component
4646
manufacture?: OrganizationalEntity
@@ -51,7 +51,7 @@ export class Metadata {
5151
constructor (op: OptionalMetadataProperties = {}) {
5252
this.timestamp = op.timestamp
5353
this.lifecycles = op.lifecycles ?? new LifecycleRepository()
54-
this.tools = op.tools ?? new ToolRepository()
54+
this.tools = op.tools ?? new Tools()
5555
this.authors = op.authors ?? new OrganizationalContactRepository()
5656
this.component = op.component
5757
this.manufacture = op.manufacture

src/models/tool.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type { Comparable } from '../_helpers/sortable'
2121
import { SortableComparables } from '../_helpers/sortable'
2222
import { ExternalReferenceRepository } from './externalReference'
2323
import { HashDictionary } from './hash'
24+
import {type Component, ComponentRepository} from "./component";
2425

2526
export interface OptionalToolProperties {
2627
vendor?: Tool['vendor']
@@ -53,7 +54,41 @@ export class Tool implements Comparable<Tool> {
5354
(this.version ?? '').localeCompare(other.version ?? '')
5455
/* eslint-enable @typescript-eslint/strict-boolean-expressions */
5556
}
57+
58+
static fromComponent(component: Component): Tool {
59+
return new Tool({
60+
vendor: component.group,
61+
name: component.name,
62+
version: component.version,
63+
hashes: component.hashes,
64+
externalReferences: component.externalReferences
65+
})
66+
}
5667
}
5768

5869
export class ToolRepository extends SortableComparables<Tool> {
5970
}
71+
72+
73+
export interface OptionalToolsProperties {
74+
components?: ComponentRepository
75+
tools?: ToolRepository
76+
}
77+
78+
export class Tools {
79+
components: ComponentRepository
80+
// TODO: services
81+
tools: ToolRepository
82+
83+
constructor(op: OptionalToolsProperties = {}) {
84+
this.components = op.components ?? new ComponentRepository()
85+
// TODO: this.services
86+
this.tools = op.tools ?? new ToolRepository()
87+
}
88+
89+
get size(): number {
90+
return this.components.size
91+
// TODO: this.services
92+
+ this.tools.size
93+
}
94+
}

src/models/vulnerability/vulnerability.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { SortableComparables } from '../../_helpers/sortable'
2222
import { CweRepository } from '../../types/cwe'
2323
import { BomRef } from '../bomRef'
2424
import { PropertyRepository } from '../property'
25-
import { ToolRepository } from '../tool'
25+
import { Tools } from '../tool'
2626
import { AdvisoryRepository } from './advisory'
2727
import { AffectRepository } from './affect'
2828
import type { Analysis } from './analysis'
@@ -68,7 +68,7 @@ export class Vulnerability implements Comparable<Vulnerability> {
6868
published?: Date
6969
updated?: Date
7070
credits?: Credits
71-
tools: ToolRepository
71+
tools: Tools
7272
analysis?: Analysis
7373
affects: AffectRepository
7474
properties: PropertyRepository
@@ -88,7 +88,7 @@ export class Vulnerability implements Comparable<Vulnerability> {
8888
this.published = op.published
8989
this.updated = op.updated
9090
this.credits = op.credits
91-
this.tools = op.tools ?? new ToolRepository()
91+
this.tools = op.tools ?? new Tools()
9292
this.analysis = op.analysis
9393
this.affects = op.affects ?? new AffectRepository()
9494
this.properties = op.properties ?? new PropertyRepository()

src/serialize/json/normalize.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type { Stringable } from '../../_helpers/stringable'
2323
import { treeIteratorSymbol } from '../../_helpers/tree'
2424
import { escapeUri } from '../../_helpers/uri'
2525
import type * as Models from '../../models'
26+
import { ToolRepository } from '../../models/tool'
2627
import { LicenseExpression, NamedLicense, SpdxLicense } from '../../models/license'
2728
import { NamedLifecycle } from '../../models/lifecycle'
2829
import { AffectedSingleVersion, AffectedVersionRange } from '../../models/vulnerability/affect'
@@ -32,6 +33,8 @@ import { Version as SpecVersion } from '../../spec/enums'
3233
import type { NormalizerOptions } from '../types'
3334
import type { Normalized } from './types'
3435
import { JsonSchema } from './types'
36+
import { chainI} from "../../_helpers/iterable";
37+
import {Tool} from "../../models";
3538

3639
export class Factory {
3740
readonly #spec: Spec
@@ -68,6 +71,10 @@ export class Factory {
6871
return new ToolNormalizer(this)
6972
}
7073

74+
makeForTools (): ToolsNormalizer {
75+
return new ToolsNormalizer(this)
76+
}
77+
7178
makeForOrganizationalContact (): OrganizationalContactNormalizer {
7279
return new OrganizationalContactNormalizer(this)
7380
}
@@ -214,7 +221,7 @@ export class MetadataNormalizer extends BaseJsonNormalizer<Models.Metadata> {
214221
? this._factory.makeForLifecycle().normalizeIterable(data.lifecycles, options)
215222
: undefined,
216223
tools: data.tools.size > 0
217-
? this._factory.makeForTool().normalizeIterable(data.tools, options)
224+
? this._factory.makeForTools().normalize(data.tools, options)
218225
: undefined,
219226
authors: data.authors.size > 0
220227
? this._factory.makeForOrganizationalContact().normalizeIterable(data.authors, options)
@@ -278,6 +285,23 @@ export class ToolNormalizer extends BaseJsonNormalizer<Models.Tool> {
278285
}
279286
}
280287

288+
export class ToolsNormalizer extends BaseJsonNormalizer<Models.Tools> {
289+
normalize(data: Models.Tools, options: NormalizerOptions): Normalized.ToolsType {
290+
if (data.tools.size > 0) {
291+
return this._factory.makeForTool().normalizeIterable(
292+
new ToolRepository(chainI<Models.Tool>(
293+
Array.from(data.components, Tool.fromComponent),
294+
// TODO services
295+
data.tools,
296+
)), options)
297+
}
298+
return {
299+
components: this._factory.makeForComponent().normalizeIterable(data.components, options)
300+
// TODO services
301+
}
302+
}
303+
}
304+
281305
export class HashNormalizer extends BaseJsonNormalizer<Models.Hash> {
282306
normalize ([algorithm, content]: Models.Hash, options: NormalizerOptions): Normalized.Hash | undefined {
283307
const spec = this._factory.spec
@@ -675,7 +699,7 @@ export class VulnerabilityNormalizer extends BaseJsonNormalizer<Models.Vulnerabi
675699
? undefined
676700
: this._factory.makeForVulnerabilityCredits().normalize(data.credits, options),
677701
tools: data.tools.size > 0
678-
? this._factory.makeForTool().normalizeIterable(data.tools, options)
702+
? this._factory.makeForTools().normalize(data.tools, options)
679703
: undefined,
680704
analysis: data.analysis === undefined
681705
? undefined

src/serialize/json/types.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export namespace Normalized {
8888
export interface Metadata {
8989
timestamp?: JsonSchema.DateTime
9090
lifecycles?: Lifecycle[]
91-
tools?: Tool[]
91+
tools?: ToolsType
9292
authors?: OrganizationalContact[]
9393
component?: Component
9494
manufacture?: OrganizationalEntity
@@ -116,6 +116,14 @@ export namespace Normalized {
116116
externalReferences?: ExternalReference[]
117117
}
118118

119+
/** since CDX 1.5 */
120+
export interface Tools {
121+
components: Component[]
122+
// TODO: services
123+
}
124+
125+
export type ToolsType = Tools | Tool[]
126+
119127
export interface OrganizationalContact {
120128
name?: string
121129
email?: JsonSchema.IdnEmail
@@ -241,7 +249,7 @@ export namespace Normalized {
241249
published?: JsonSchema.DateTime
242250
updated?: JsonSchema.DateTime
243251
credits?: Vulnerability.Credits
244-
tools?: Tool[]
252+
tools?: ToolsType
245253
analysis?: Vulnerability.Analysis
246254
affects?: Vulnerability.Affect[]
247255
properties?: Property[]

src/serialize/xml/normalize.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import type { NormalizerOptions } from '../types'
3333
import { normalizedString, token} from './_xsd'
3434
import type { SimpleXml } from './types'
3535
import { XmlSchema } from './types'
36+
import {Tool, ToolRepository} from "../../models";
37+
import {chainI} from "../../_helpers/iterable";
3638

3739
export class Factory {
3840
readonly #spec: Spec
@@ -69,6 +71,10 @@ export class Factory {
6971
return new ToolNormalizer(this)
7072
}
7173

74+
makeForTools (): ToolsNormalizer {
75+
return new ToolsNormalizer(this)
76+
}
77+
7278
makeForOrganizationalContact (): OrganizationalContactNormalizer {
7379
return new OrganizationalContactNormalizer(this)
7480
}
@@ -238,11 +244,7 @@ export class MetadataNormalizer extends BaseXmlNormalizer<Models.Metadata> {
238244
}
239245
: undefined
240246
const tools: SimpleXml.Element | undefined = data.tools.size > 0
241-
? {
242-
type: 'element',
243-
name: 'tools',
244-
children: this._factory.makeForTool().normalizeIterable(data.tools, options, 'tool')
245-
}
247+
? this._factory.makeForTools().normalize(data.tools, options)
246248
: undefined
247249
const authors: SimpleXml.Element | undefined = data.authors.size > 0
248250
? {
@@ -357,6 +359,35 @@ export class ToolNormalizer extends BaseXmlNormalizer<Models.Tool> {
357359
}
358360
}
359361

362+
export class ToolsNormalizer extends BaseXmlNormalizer<Models.Tools> {
363+
normalize (data: Models.Tools, options: NormalizerOptions, elementName: string): SimpleXml.Element {
364+
let children: SimpleXml.Element[]
365+
if (data.tools.size > 0) {
366+
children = this._factory.makeForTool().normalizeIterable(
367+
new ToolRepository(chainI<Models.Tool>(
368+
Array.from(data.components, Tool.fromComponent),
369+
// TODO services
370+
data.tools,
371+
)), options, 'tool')
372+
} else {
373+
children = []
374+
if (data.components.size > 0) {
375+
children.push({
376+
type: 'element',
377+
name: 'components',
378+
children: this._factory.makeForComponent().normalizeIterable(data.components, options, 'component')
379+
})
380+
}
381+
// TODO data.services
382+
}
383+
return {
384+
type: 'element',
385+
name: elementName,
386+
children
387+
}
388+
}
389+
}
390+
360391
export class HashNormalizer extends BaseXmlNormalizer<Models.Hash> {
361392
normalize ([algorithm, content]: Models.Hash, options: NormalizerOptions, elementName: string): SimpleXml.Element | undefined {
362393
const spec = this._factory.spec
@@ -857,7 +888,7 @@ export class VulnerabilityNormalizer extends BaseXmlNormalizer<Models.Vulnerabil
857888
? {
858889
type: 'element',
859890
name: 'tools',
860-
children: this._factory.makeForTool().normalizeIterable(data.tools, options, 'tool')
891+
children: this._factory.makeForTools().normalize(data.tools, options)
861892
}
862893
: undefined
863894
const affects: SimpleXml.Element | undefined = data.affects.size > 0

0 commit comments

Comments
 (0)