From ac519c9a21bc8e4a75927868f32f29febc648509 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 27 Aug 2021 11:15:26 +0100 Subject: [PATCH 01/43] Initially added skeleton packaging structure and official CycloneDX schemas. --- .gitignore | 17 + MAINFEST.in | 3 + VERSION | 1 + cyclonedx/__init__.py | 0 cyclonedx/schema/bom-1.0.xsd | 247 ++ cyclonedx/schema/bom-1.1.xsd | 731 +++++ cyclonedx/schema/bom-1.2-strict.schema.json | 1020 +++++++ cyclonedx/schema/bom-1.2.schema.json | 997 +++++++ cyclonedx/schema/bom-1.2.xsd | 1418 ++++++++++ cyclonedx/schema/bom-1.3-strict.schema.json | 1079 ++++++++ cyclonedx/schema/bom-1.3.proto | 452 +++ cyclonedx/schema/bom-1.3.schema.json | 1054 +++++++ cyclonedx/schema/bom-1.3.xsd | 1631 +++++++++++ cyclonedx/schema/ext/bom-descriptor-0.9.xsd | 175 ++ cyclonedx/schema/ext/bom-descriptor-1.0.xsd | 183 ++ cyclonedx/schema/ext/dependency-graph-1.0.xsd | 70 + .../vulnerability-1.0-SNAPSHOT.schema.json | 182 ++ cyclonedx/schema/ext/vulnerability-1.0.xsd | 291 ++ cyclonedx/schema/spdx.schema.json | 491 ++++ cyclonedx/schema/spdx.xsd | 2429 +++++++++++++++++ requirements.txt | 1 + setup.cfg | 2 + setup.py | 38 + 23 files changed, 12512 insertions(+) create mode 100644 .gitignore create mode 100644 MAINFEST.in create mode 100644 VERSION create mode 100644 cyclonedx/__init__.py create mode 100644 cyclonedx/schema/bom-1.0.xsd create mode 100644 cyclonedx/schema/bom-1.1.xsd create mode 100644 cyclonedx/schema/bom-1.2-strict.schema.json create mode 100644 cyclonedx/schema/bom-1.2.schema.json create mode 100644 cyclonedx/schema/bom-1.2.xsd create mode 100644 cyclonedx/schema/bom-1.3-strict.schema.json create mode 100644 cyclonedx/schema/bom-1.3.proto create mode 100644 cyclonedx/schema/bom-1.3.schema.json create mode 100644 cyclonedx/schema/bom-1.3.xsd create mode 100644 cyclonedx/schema/ext/bom-descriptor-0.9.xsd create mode 100644 cyclonedx/schema/ext/bom-descriptor-1.0.xsd create mode 100644 cyclonedx/schema/ext/dependency-graph-1.0.xsd create mode 100644 cyclonedx/schema/ext/vulnerability-1.0-SNAPSHOT.schema.json create mode 100644 cyclonedx/schema/ext/vulnerability-1.0.xsd create mode 100644 cyclonedx/schema/spdx.schema.json create mode 100644 cyclonedx/schema/spdx.xsd create mode 100644 requirements.txt create mode 100644 setup.cfg diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..cc79b2ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Exlude python build & distribution directories +build/ +dist/ +*.egg-info* + +# Exlude *.pyc +*.pyc + +# Exclude test-related items +.tox/* + +# Exclude Python Virtual Environment +venv/* + +# Exlude IDE related files +.idea/* +.vscode/* \ No newline at end of file diff --git a/MAINFEST.in b/MAINFEST.in new file mode 100644 index 00000000..d922f707 --- /dev/null +++ b/MAINFEST.in @@ -0,0 +1,3 @@ +include README.md +include VERSION +include cyclonedx/schema/* \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..8a9ecc2e --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/cyclonedx/__init__.py b/cyclonedx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cyclonedx/schema/bom-1.0.xsd b/cyclonedx/schema/bom-1.0.xsd new file mode 100644 index 00000000..9cf88149 --- /dev/null +++ b/cyclonedx/schema/bom-1.0.xsd @@ -0,0 +1,247 @@ + + + + + + + + + + The person(s) or organization(s) that published the component + + + + + The grouping name or identifier. This will often be a shortened, single + name of the company or project that produced the component, or the source package or + domain name. Whitespace and special characters should be avoided. Examples include: + apache, org.apache.commons, and apache.org. + + + + + The name of the component. This will often be a shortened, single name + of the component. Examples: commons-lang3 and jquery + + + + + The component version. The version should ideally comply with semantic versioning + but is not enforced. + + + + + Specifies a description for the component + + + + + Specifies the scope of the component. If scope is not specified, 'runtime' + scope will be assumed. + + + + + + + + + + + + + + + + + + + A valid SPDX license ID + + + + + If SPDX does not define the license used, this field may be used to provide the license name + + + + + + + + + + + + An optional copyright notice informing users of the underlying claims to copyright ownership in a published work. + + + + + Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe + + + + + + Specifies the package-url (PURL). The purl, if specified, must be valid and conform + to the specification defined at: https://github.com/package-url/purl-spec + + + + + + + A boolean value indicating is the component has been modified from the original. + A value of true indicates the component is a derivative of the original. + A value of false indicates the component has not been modified from the original. + + + + + + + Specifies optional sub-components. This is not a dependency tree. It simply provides + an optional way to group large sets of components together. + + + + + + + + + + + + + Specifies the type of component. Software applications, libraries, frameworks, and + other dependencies should be classified as 'application'. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Specifies the file hash of the component + + + + + + Specifies the algorithm used to create hash + + + + + + + + + + + The component is required for runtime + + + + + The component is optional at runtime. Optional components are components that + are not capable of being called due to them not be installed or otherwise accessible by any means. + Components that are installed but due to configuration or other restrictions are prohibited from + being called must be scoped as 'required'. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Define the format for acceptable CPE URIs. Supports CPE 2.2 and CPE 2.3 formats. Refer to https://nvd.nist.gov/products/cpe for official specification. + + + + + + + + + + + + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + The version allows component publishers/authors to make changes to existing + BOMs to update various aspects of the document such as description or licenses. When a system + is presented with multiiple BOMs for the same component, the system should use the most recent + version of the BOM. The default version is '1' and should be incremented for each version of the + BOM that is published. Each version of a component should have a unique BOM and if no changes are + made to the BOMs, then each BOM will have a version of '1'. + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + \ No newline at end of file diff --git a/cyclonedx/schema/bom-1.1.xsd b/cyclonedx/schema/bom-1.1.xsd new file mode 100644 index 00000000..833e1691 --- /dev/null +++ b/cyclonedx/schema/bom-1.1.xsd @@ -0,0 +1,731 @@ + + + + + + + + + CycloneDX Software Bill-of-Material Specification + https://cyclonedx.org/ + Apache License, Version 2.0 + + Steve Springett + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The person(s) or organization(s) that published the component + + + + + The grouping name or identifier. This will often be a shortened, single + name of the company or project that produced the component, or the source package or + domain name. Whitespace and special characters should be avoided. Examples include: + apache, org.apache.commons, and apache.org. + + + + + The name of the component. This will often be a shortened, single name + of the component. Examples: commons-lang3 and jquery + + + + + The component version. The version should ideally comply with semantic versioning + but is not enforced. + + + + + Specifies a description for the component + + + + + Specifies the scope of the component. If scope is not specified, 'runtime' + scope should be assumed by the consumer of the BOM + + + + + + + + + + + + + + + + A valid SPDX license expression. + Refer to https://spdx.org/specifications for syntax requirements + + + + + + + + An optional copyright notice informing users of the underlying claims to + copyright ownership in a published work. + + + + + + DEPRECATED - DO NOT USE. This will be removed in a future version. + Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe + + + + + + + Specifies the package-url (PURL). The purl, if specified, must be valid and conform + to the specification defined at: https://github.com/package-url/purl-spec + + + + + + + DEPRECATED - DO NOT USE. This will be removed in a future version. Use the pedigree + element instead to supply information on exactly how the component was modified. + A boolean value indicating is the component has been modified from the original. + A value of true indicates the component is a derivative of the original. + A value of false indicates the component has not been modified from the original. + + + + + + + Component pedigree is a way to document complex supply chain scenarios where components are + created, distributed, modified, redistributed, combined with other components, etc. + + + + + + Provides the ability to document external references related to the + component or to the project the component describes. + + + + + + Specifies optional sub-components. This is not a dependency tree. It provides a way + to specify a hierarchical representation of component assemblies, similar to + system -> subsystem -> parts assembly in physical supply chains. + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + Specifies the type of component. For software components, classify as application if no more + specific appropriate classification is available or cannot be determined for the component. + Valid choices are: application, framework, library, operating-system, device, or file + Refer to the bom:classification documentation for information describing each one + + + + + + + An optional identifier which can be used to reference the component elsewhere in the BOM. + Uniqueness is enforced within all elements and children of the root-level bom element. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + + A valid SPDX license ID + + + + + If SPDX does not define the license used, this field may be used to provide the license name + + + + + + Specifies the optional full text of the license + + + + + The URL to the license file. If specified, a 'license' + externalReference should also be specified for completeness. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + Specifies attributes of the license text + + + + Specifies the content type of the license text. Defaults to text/plain + if not specified. + + + + + + Specifies the optional encoding the license text is represented in + + + + + + + + + + Specifies the file hash of the component + + + + + + Specifies the algorithm used to create the hash + + + + + + + + + + + The component is required for runtime + + + + + The component is optional at runtime. Optional components are components that + are not capable of being called due to them not be installed or otherwise accessible by any means. + Components that are installed but due to configuration or other restrictions are prohibited from + being called must be scoped as 'required'. + + + + + Components that are excluded provide the ability to document component usage + for test and other non-runtime purposes. Excluded components are not reachable within a call + graph at runtime. + + + + + + + + + + A software application. Refer to https://en.wikipedia.org/wiki/Application_software + for information about applications. + + + + + A software framework. Refer to https://en.wikipedia.org/wiki/Software_framework + for information on how frameworks vary slightly from libraries. + + + + + A software library. Refer to https://en.wikipedia.org/wiki/Library_(computing) + for information about libraries. All third-party and open source reusable components will likely + be a library. If the library also has key features of a framework, then it should be classified + as a framework. If not, or is unknown, then specifying library is recommended. + + + + + A software operating system without regard to deployment model + (i.e. installed on physical hardware, virtual machine, container image, etc) Refer to + https://en.wikipedia.org/wiki/Operating_system + + + + + A hardware device such as a processor, or chip-set. A hardware device + containing firmware should include a component for the physical hardware itself, and another + component of type 'application' or 'operating-system' (whichever is relevant), describing + information about the firmware. + + + + + A computer file. Refer to https://en.wikipedia.org/wiki/Computer_file + for information about files. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Define the format for acceptable CPE URIs. Supports CPE 2.2 and CPE 2.3 formats. + Refer to https://nvd.nist.gov/products/cpe for official specification. + + + + + + + + + + + Defines a string representation of a UUID conforming to RFC 4122. + + + + + + + + + + + + Version Control System + + + + + Issue or defect tracking system, or an Application Lifecycle Management (ALM) system + + + + + Website + + + + + Security advisories + + + + + Bill-of-material document (CycloneDX, SPDX, SWID, etc) + + + + + Mailing list or discussion group + + + + + Social media account + + + + + Real-time chat platform + + + + + Documentation, guides, or how-to instructions + + + + + Community or commercial support + + + + + Direct or repository download location + + + + + The URL to the license file. If a license URL has been defined in the license + node, it should also be defined as an external reference for completeness + + + + + Build-system specific meta file (i.e. pom.xml, package.json, .nuspec, etc) + + + + + URL to an automated build system + + + + + Use this if no other types accurately describe the purpose of the external reference + + + + + + + + + External references provide a way to document systems, sites, and information that may be relevant + but which are not included with the BOM. + + + + + + Zero or more external references can be defined + + + + + + + + + + The URL to the external reference + + + + + An optional comment describing the external reference + + + + + + Specifies the type of external reference. There are built-in types to describe common + references. If a type does not exist for the reference being referred to, use the "other" type. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Zero or more commits can be specified. + + + + + Specifies an individual commit. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + A unique identifier of the commit. This may be version control + specific. For example, Subversion uses revision numbers whereas git uses commit hashes. + + + + + + The URL to the commit. This URL will typically point to a commit + in a version control system. + + + + + + The author who created the changes in the commit + + + + + The person who committed or pushed the commit + + + + + The text description of the contents of the commit + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + The timestamp in which the action occurred + + + + + The name of the individual who performed the action + + + + + The email address of the individual who performed the action + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + Component pedigree is a way to document complex supply chain scenarios where components are created, + distributed, modified, redistributed, combined with other components, etc. Pedigree supports viewing + this complex chain from the beginning, the end, or anywhere in the middle. It also provides a way to + document variants where the exact relation may not be known. + + + + + + Describes zero or more components in which a component is derived + from. This is commonly used to describe forks from existing projects where the forked version + contains a ancestor node containing the original component it was forked from. For example, + Component A is the original component. Component B is the component being used and documented + in the BOM. However, Component B contains a pedigree node with a single ancestor documenting + Component A - the original component from which Component B is derived from. + + + + + + Descendants are the exact opposite of ancestors. This provides a + way to document all forks (and their forks) of an original or root component. + + + + + + Variants describe relations where the relationship between the + components are not known. For example, if Component A contains nearly identical code to + Component B. They are both related, but it is unclear if one is derived from the other, + or if they share a common ancestor. + + + + + + A list of zero or more commits which provide a trail describing + how the component deviates from an ancestor, descendant, or variant. + + + + + Notes, observations, and other non-structured commentary + describing the components pedigree. + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + + + Provides the ability to document external references related to the BOM or + to the project the BOM describes. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + The version allows component publishers/authors to make changes to existing + BOMs to update various aspects of the document such as description or licenses. When a system + is presented with multiple BOMs for the same component, the system should use the most recent + version of the BOM. The default version is '1' and should be incremented for each version of the + BOM that is published. Each version of a component should have a unique BOM and if no changes are + made to the BOMs, then each BOM will have a version of '1'. + + + + + Every BOM generated should have a unique serial number, even if the contents + of the BOM being generated have not changed over time. The process or tool responsible for + creating the BOM should create random UUID's for every BOM generated. + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + \ No newline at end of file diff --git a/cyclonedx/schema/bom-1.2-strict.schema.json b/cyclonedx/schema/bom-1.2-strict.schema.json new file mode 100644 index 00000000..30dad527 --- /dev/null +++ b/cyclonedx/schema/bom-1.2-strict.schema.json @@ -0,0 +1,1020 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://cyclonedx.org/schema/bom-1.2a.schema.json", + "type": "object", + "title": "CycloneDX Software Bill-of-Material Specification", + "$comment" : "CycloneDX JSON schema is published under the terms of the Apache License 2.0.", + "required": [ + "bomFormat", + "specVersion", + "version" + ], + "additionalProperties": false, + "properties": { + "bomFormat": { + "$id": "#/properties/bomFormat", + "type": "string", + "title": "BOM Format", + "description": "Specifies the format of the BOM. This helps to identify the file as CycloneDX since BOMs do not have a filename convention nor does JSON schema support namespaces.", + "enum": [ + "CycloneDX" + ] + }, + "specVersion": { + "$id": "#/properties/specVersion", + "type": "string", + "title": "CycloneDX Specification Version", + "description": "The version of the CycloneDX specification a BOM is written to (starting at version 1.2)", + "examples": ["1.2"] + }, + "serialNumber": { + "$id": "#/properties/serialNumber", + "type": "string", + "title": "BOM Serial Number", + "description": "Every BOM generated should have a unique serial number, even if the contents of the BOM being generated have not changed over time. The process or tool responsible for creating the BOM should create random UUID's for every BOM generated.", + "default": "", + "examples": ["urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"], + "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + "version": { + "$id": "#/properties/version", + "type": "integer", + "title": "BOM Version", + "description": "The version allows component publishers/authors to make changes to existing BOMs to update various aspects of the document such as description or licenses. When a system is presented with multiple BOMs for the same component, the system should use the most recent version of the BOM. The default version is '1' and should be incremented for each version of the BOM that is published. Each version of a component should have a unique BOM and if no changes are made to the BOMs, then each BOM will have a version of '1'.", + "default": 1, + "examples": [1] + }, + "metadata": { + "$id": "#/properties/metadata", + "$ref": "#/definitions/metadata", + "title": "BOM Metadata", + "description": "Provides additional information about a BOM." + }, + "components": { + "$id": "#/properties/components", + "type": "array", + "items": {"$ref": "#/definitions/component"}, + "uniqueItems": true, + "title": "Components" + }, + "services": { + "$id": "#/properties/services", + "type": "array", + "items": {"$ref": "#/definitions/service"}, + "uniqueItems": true, + "title": "Services" + }, + "externalReferences": { + "$id": "#/properties/externalReferences", + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References", + "description": "External references provide a way to document systems, sites, and information that may be relevant but which are not included with the BOM." + }, + "dependencies": { + "$id": "#/properties/dependencies", + "type": "array", + "items": {"$ref": "#/definitions/dependency"}, + "uniqueItems": true, + "title": "Dependencies", + "description": "Provides the ability to document dependency relationships." + } + }, + "definitions": { + "metadata": { + "type": "object", + "title": "BOM Metadata Object", + "additionalProperties": false, + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "The date and time (timestamp) when the document was created." + }, + "tools": { + "type": "array", + "title": "Creation Tools", + "description": "The tool(s) used in the creation of the BOM.", + "items": {"$ref": "#/definitions/tool"} + }, + "authors" :{ + "type": "array", + "title": "Authors", + "description": "The person(s) who created the BOM. Authors are common in BOMs created through manual processes. BOMs created through automated means may not have authors.", + "items": {"$ref": "#/definitions/organizationalContact"} + }, + "component": { + "title": "Component", + "description": "The component that the BOM describes.", + "$ref": "#/definitions/component" + }, + "manufacture": { + "title": "Manufacture", + "description": "The organization that manufactured the component that the BOM describes.", + "$ref": "#/definitions/organizationalEntity" + }, + "supplier": { + "title": "Supplier", + "description": " The organization that supplied the component that the BOM describes. The supplier may often be the manufacture, but may also be a distributor or repackager.", + "$ref": "#/definitions/organizationalEntity" + } + } + }, + "tool": { + "type": "object", + "title": "Tool", + "description": "The tool used to create the BOM.", + "additionalProperties": false, + "properties": { + "vendor": { + "type": "string", + "format": "string", + "title": "Tool Vendor", + "description": "The date and time (timestamp) when the document was created." + }, + "name": { + "type": "string", + "format": "string", + "title": "Tool Name", + "description": "The date and time (timestamp) when the document was created." + }, + "version": { + "type": "string", + "format": "string", + "title": "Tool Version", + "description": "The date and time (timestamp) when the document was created." + }, + "hashes": { + "$id": "#/properties/hashes", + "type": "array", + "items": {"$ref": "#/definitions/hash"}, + "title": "Hashes", + "description": "The hashes of the tool (if applicable)." + } + } + }, + "organizationalEntity": { + "type": "object", + "title": "Organizational Entity Object", + "description": "", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the organization", + "default": "", + "examples": [ + "Example Inc." + ], + "pattern": "^(.*)$" + }, + "url": { + "type": "array", + "title": "URL", + "description": "The URL of the organization. Multiple URLs are allowed.", + "default": "", + "examples": ["https://example.com"], + "pattern": "^(.*)$" + }, + "contact": { + "type": "array", + "title": "Contact", + "description": "A contact at the organization. Multiple contacts are allowed.", + "items": {"$ref": "#/definitions/organizationalContact"} + } + } + }, + "organizationalContact": { + "type": "object", + "title": "Organizational Contact Object", + "description": "", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of a contact", + "default": "", + "examples": ["Contact name"], + "pattern": "^(.*)$" + }, + "email": { + "type": "string", + "title": "Email Address", + "description": "The email address of the contact. Multiple email addresses are allowed.", + "default": "", + "examples": ["firstname.lastname@example.com"], + "pattern": "^(.*)$" + }, + "phone": { + "type": "string", + "title": "Phone", + "description": "The phone number of the contact. Multiple phone numbers are allowed.", + "default": "", + "examples": ["800-555-1212"], + "pattern": "^(.*)$" + } + } + }, + "component": { + "type": "object", + "title": "Component Object", + "required": [ + "type", + "name", + "version" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "application", + "framework", + "library", + "container", + "operating-system", + "device", + "firmware", + "file" + ], + "title": "Component Type", + "description": "Specifies the type of component. For software components, classify as application if no more specific appropriate classification is available or cannot be determined for the component.", + "default": "", + "examples": ["library"], + "pattern": "^(.*)$" + }, + "mime-type": { + "type": "string", + "title": "Mime-Type", + "description": "The optional mime-type of the component. When used on file components, the mime-type can provide additional context about the kind of file being represented such as an image, font, or executable. Some library or framework components may also have an associated mime-type.", + "default": "", + "examples": ["image/jpeg"], + "pattern": "^[-+a-z0-9.]+/[-+a-z0-9.]+$" + }, + "bom-ref": { + "type": "string", + "title": "BOM Reference", + "description": "An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref should be unique.", + "default": "", + "pattern": "^(.*)$" + }, + "supplier": { + "title": "Component Supplier", + "description": " The organization that supplied the component. The supplier may often be the manufacture, but may also be a distributor or repackager.", + "$ref": "#/definitions/organizationalEntity" + }, + "author": { + "type": "string", + "title": "Component Author", + "description": "The person(s) or organization(s) that authored the component", + "default": "", + "examples": ["Acme Inc"], + "pattern": "^(.*)$" + }, + "publisher": { + "type": "string", + "title": "Component Publisher", + "description": "The person(s) or organization(s) that published the component", + "default": "", + "examples": ["Acme Inc"], + "pattern": "^(.*)$" + }, + "group": { + "type": "string", + "title": "Component Group", + "description": "The grouping name or identifier. This will often be a shortened, single name of the company or project that produced the component, or the source package or domain name. Whitespace and special characters should be avoided. Examples include: apache, org.apache.commons, and apache.org.", + "default": "", + "examples": ["com.acme"], + "pattern": "^(.*)$" + }, + "name": { + "type": "string", + "title": "Component Name", + "description": "The name of the component. This will often be a shortened, single name of the component. Examples: commons-lang3 and jquery", + "default": "", + "examples": ["tomcat-catalina"], + "pattern": "^(.*)$" + }, + "version": { + "type": "string", + "title": "Component Version", + "description": "The component version. The version should ideally comply with semantic versioning but is not enforced.", + "default": "", + "examples": ["9.0.14"], + "pattern": "^(.*)$" + }, + "description": { + "type": "string", + "title": "Component Description", + "description": "Specifies a description for the component", + "default": "", + "pattern": "^(.*)$" + }, + "scope": { + "type": "string", + "enum": [ + "required", + "optional", + "excluded" + ], + "title": "Component Scope", + "description": "Specifies the scope of the component. If scope is not specified, 'required' scope should be assumed by the consumer of the BOM", + "default": "required", + "pattern": "^(.*)$" + }, + "hashes": { + "type": "array", + "title": "Component Hashes", + "items": {"$ref": "#/definitions/hash"} + }, + "licenses": { + "type": "array", + "title": "Component License(s)", + "items": { + "additionalProperties": false, + "properties": { + "license": { + "$ref": "#/definitions/license" + }, + "expression": { + "type": "string", + "title": "SPDX License Expression", + "examples": [ + "Apache-2.0 AND (MIT OR GPL-2.0-only)", + "GPL-3.0-only WITH Classpath-exception-2.0" + ], + "pattern": "^(.*)$" + } + }, + "oneOf":[ + { + "required": ["license"] + }, + { + "required": ["expression"] + } + ] + } + }, + "copyright": { + "type": "string", + "title": "Component Copyright", + "description": "An optional copyright notice informing users of the underlying claims to copyright ownership in a published work.", + "examples": ["Acme Inc"], + "pattern": "^(.*)$" + }, + "cpe": { + "type": "string", + "title": "Component Common Platform Enumeration (CPE)", + "description": "DEPRECATED - DO NOT USE. This will be removed in a future version. Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe", + "examples": ["cpe:2.3:a:acme:component_framework:-:*:*:*:*:*:*:*"], + "pattern": "^(.*)$" + }, + "purl": { + "type": "string", + "title": "Component Package URL (purl)", + "default": "", + "examples": ["pkg:maven/com.acme/tomcat-catalina@9.0.14?packaging=jar"], + "pattern": "^(.*)$" + }, + "swid": { + "$ref": "#/definitions/swid", + "title": "SWID Tag", + "description": "Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags." + }, + "modified": { + "type": "boolean", + "title": "Component Modified From Original", + "description": "DEPRECATED - DO NOT USE. This will be removed in a future version. Use the pedigree element instead to supply information on exactly how the component was modified. A boolean value indicating is the component has been modified from the original. A value of true indicates the component is a derivative of the original. A value of false indicates the component has not been modified from the original." + }, + "pedigree": { + "type": "object", + "title": "Component Pedigree", + "description": "Component pedigree is a way to document complex supply chain scenarios where components are created, distributed, modified, redistributed, combined with other components, etc. Pedigree supports viewing this complex chain from the beginning, the end, or anywhere in the middle. It also provides a way to document variants where the exact relation may not be known.", + "additionalProperties": false, + "properties": { + "ancestors": { + "type": "array", + "title": "Ancestors", + "description": "Describes zero or more components in which a component is derived from. This is commonly used to describe forks from existing projects where the forked version contains a ancestor node containing the original component it was forked from. For example, Component A is the original component. Component B is the component being used and documented in the BOM. However, Component B contains a pedigree node with a single ancestor documenting Component A - the original component from which Component B is derived from.", + "items": {"$ref": "#/definitions/component"} + }, + "descendants": { + "type": "array", + "title": "Descendants", + "description": "Descendants are the exact opposite of ancestors. This provides a way to document all forks (and their forks) of an original or root component.", + "items": {"$ref": "#/definitions/component"} + }, + "variants": { + "type": "array", + "title": "Variants", + "description": "Variants describe relations where the relationship between the components are not known. For example, if Component A contains nearly identical code to Component B. They are both related, but it is unclear if one is derived from the other, or if they share a common ancestor.", + "items": {"$ref": "#/definitions/component"} + }, + "commits": { + "type": "array", + "title": "Commits", + "description": "A list of zero or more commits which provide a trail describing how the component deviates from an ancestor, descendant, or variant.", + "items": {"$ref": "#/definitions/commit"} + }, + "patches": { + "type": "array", + "title": "Patches", + "description": ">A list of zero or more patches describing how the component deviates from an ancestor, descendant, or variant. Patches may be complimentary to commits or may be used in place of commits.", + "items": {"$ref": "#/definitions/patch"} + }, + "notes": { + "type": "string", + "title": "Notes", + "description": "Notes, observations, and other non-structured commentary describing the components pedigree.", + "pattern": "^(.*)$" + } + } + }, + "externalReferences": { + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References" + }, + "components": { + "$id": "#/properties/components", + "type": "array", + "items": {"$ref": "#/definitions/component"}, + "uniqueItems": true, + "title": "Components" + } + } + }, + "swid": { + "type": "object", + "title": "SWID Tag", + "description": "Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags.", + "required": [ + "tagId", + "name" + ], + "additionalProperties": false, + "properties": { + "tagId": { + "type": "string", + "title": "Tag ID", + "description": "Maps to the tagId of a SoftwareIdentity." + }, + "name": { + "type": "string", + "title": "Name", + "description": "Maps to the name of a SoftwareIdentity." + }, + "version": { + "type": "string", + "title": "Version", + "default": "0.0", + "description": "Maps to the version of a SoftwareIdentity." + }, + "tagVersion": { + "type": "integer", + "title": "Tag Version", + "default": 0, + "description": "Maps to the tagVersion of a SoftwareIdentity." + }, + "patch": { + "type": "boolean", + "title": "Patch", + "default": false, + "description": "Maps to the patch of a SoftwareIdentity." + }, + "text": { + "title": "Attachment text", + "description": "Specifies the metadata and content of the SWID tag.", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "URL", + "default": "The URL to the SWID file.", + "pattern": "^(.*)$" + } + } + }, + "attachment": { + "type": "object", + "title": "Attachment", + "description": "Specifies the metadata and content for an attachment.", + "required": [ + "content" + ], + "additionalProperties": false, + "properties": { + "contentType": { + "type": "string", + "title": "Content-Type", + "description": "Specifies the content type of the text. Defaults to text/plain if not specified.", + "default": "text/plain" + }, + "encoding": { + "type": "string", + "title": "Encoding", + "description": "Specifies the optional encoding the text is represented in.", + "enum": [ + "base64" + ], + "default": "", + "pattern": "^(.*)$" + }, + "content": { + "type": "string", + "title": "Attachment Text", + "description": "The attachment data" + } + } + }, + "hash": { + "type": "object", + "title": "Hash Objects", + "required": [ + "alg", + "content" + ], + "additionalProperties": false, + "properties": { + "alg": { + "$ref": "#/definitions/hash-alg" + }, + "content": { + "$ref": "#/definitions/hash-content" + } + } + }, + "hash-alg": { + "type": "string", + "enum": [ + "MD5", + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512", + "SHA3-256", + "SHA3-384", + "SHA3-512", + "BLAKE2b-256", + "BLAKE2b-384", + "BLAKE2b-512", + "BLAKE3" + ], + "title": "Hash Algorithm", + "default": "", + "pattern": "^(.*)$" + }, + "hash-content": { + "type": "string", + "title": "Hash Content (value)", + "default": "", + "examples": ["3942447fac867ae5cdb3229b658f4d48"], + "pattern": "^([a-fA-F0-9]{32}|[a-fA-F0-9]{40}|[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128})$" + }, + "license": { + "type": "object", + "title": "License Object", + "oneOf": [ + { + "required": ["id"] + }, + { + "required": ["name"] + } + ], + "additionalProperties": false, + "properties": { + "id": { + "$ref": "spdx.schema.json", + "title": "License ID (SPDX)", + "description": "A valid SPDX license ID", + "examples": ["Apache-2.0"] + }, + "name": { + "type": "string", + "title": "License Name", + "description": "If SPDX does not define the license used, this field may be used to provide the license name", + "default": "", + "examples": ["Acme Software License"], + "pattern": "^(.*)$" + }, + "text": { + "title": "License text", + "description": "An optional way to include the textual content of a license.", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "License URL", + "description": "The URL to the license file. If specified, a 'license' externalReference should also be specified for completeness", + "examples": ["https://www.apache.org/licenses/LICENSE-2.0.txt"], + "pattern": "^(.*)$" + } + } + }, + "commit": { + "type": "object", + "title": "Commit", + "description": "Specifies an individual commit", + "additionalProperties": false, + "properties": { + "uid": { + "type": "string", + "title": "UID", + "description": "A unique identifier of the commit. This may be version control specific. For example, Subversion uses revision numbers whereas git uses commit hashes.", + "pattern": "^(.*)$" + }, + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the commit. This URL will typically point to a commit in a version control system.", + "format": "iri-reference" + }, + "author": { + "title": "Author", + "description": "The author who created the changes in the commit", + "$ref": "#/definitions/identifiableAction" + }, + "committer": { + "title": "Committer", + "description": "The person who committed or pushed the commit", + "$ref": "#/definitions/identifiableAction" + }, + "message": { + "type": "string", + "title": "Message", + "description": "The text description of the contents of the commit", + "pattern": "^(.*)$" + } + } + }, + "patch": { + "type": "object", + "title": "Patch", + "description": "Specifies an individual patch", + "required": [ + "type" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "unofficial", + "monkey", + "backport", + "cherry-pick" + ], + "title": "Type", + "description": "Specifies the purpose for the patch including the resolution of defects, security issues, or new behavior or functionality" + }, + "diff": { + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "$ref": "#/definitions/diff" + }, + "resolves": { + "type": "array", + "items": {"$ref": "#/definitions/issue"}, + "title": "Resolves", + "description": "A collection of issues the patch resolves" + } + } + }, + "diff": { + "type": "object", + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "additionalProperties": false, + "properties": { + "text": { + "title": "Diff text", + "description": "Specifies the optional text of the diff", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "URL", + "description": "Specifies the URL to the diff", + "pattern": "^(.*)$" + } + } + }, + "issue": { + "type": "object", + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "required": [ + "type" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "defect", + "enhancement", + "security" + ], + "title": "Type", + "description": "Specifies the type of issue" + }, + "id": { + "type": "string", + "title": "ID", + "description": "The identifier of the issue assigned by the source of the issue", + "pattern": "^(.*)$" + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the issue", + "pattern": "^(.*)$" + }, + "description": { + "type": "string", + "title": "Description", + "description": "A description of the issue", + "pattern": "^(.*)$" + }, + "source": { + "type": "object", + "title": "Source", + "description": "The source of the issue where it is documented", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the source. For example 'National Vulnerability Database', 'NVD', and 'Apache'", + "pattern": "^(.*)$" + }, + "url": { + "type": "string", + "title": "URL", + "description": "The url of the issue documentation as provided by the source", + "pattern": "^(.*)$" + } + } + }, + "references": { + "type": "array", + "title": "References", + "description": "A collection of URL's for reference. Multiple URLs are allowed.", + "default": "", + "examples": ["https://example.com"], + "pattern": "^(.*)$" + } + } + }, + "identifiableAction": { + "type": "object", + "title": "Identifiable Action", + "description": "Specifies an individual commit", + "additionalProperties": false, + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "The timestamp in which the action occurred" + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the individual who performed the action", + "pattern": "^(.*)$" + }, + "email": { + "type": "string", + "format": "idn-email", + "title": "E-mail", + "description": "The email address of the individual who performed the action" + } + } + }, + "externalReference": { + "type": "object", + "title": "External Reference", + "description": "Specifies an individual external reference", + "required": [ + "url", + "type" + ], + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the external reference", + "pattern": "^(.*)$" + }, + "comment": { + "type": "string", + "title": "Comment", + "description": "An optional comment describing the external reference", + "pattern": "^(.*)$" + }, + "type": { + "type": "string", + "title": "Type", + "description": "Specifies the type of external reference. There are built-in types to describe common references. If a type does not exist for the reference being referred to, use the \"other\" type.", + "enum": [ + "vcs", + "issue-tracker", + "website", + "advisories", + "bom", + "mailing-list", + "social", + "chat", + "documentation", + "support", + "distribution", + "license", + "build-meta", + "build-system", + "other" + ] + } + } + }, + "dependency": { + "type": "object", + "title": "Dependency", + "description": "Defines the direct dependencies of a component. Components that do not have their own dependencies MUST be declared as empty elements within the graph. Components that are not represented in the dependency graph MAY have unknown dependencies. It is RECOMMENDED that implementations assume this to be opaque and not an indicator of a component being dependency-free.", + "required": [ + "ref" + ], + "additionalProperties": false, + "properties": { + "ref": { + "type": "string", + "format": "string", + "title": "Reference", + "description": "References a component by the components bom-ref attribute" + }, + "dependsOn": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "title": "Depends On", + "description": "The bom-ref identifiers of the components that are dependencies of this dependency object." + } + } + }, + "service": { + "type": "object", + "title": "Service Object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "bom-ref": { + "type": "string", + "title": "BOM Reference", + "description": "An optional identifier which can be used to reference the service elsewhere in the BOM. Every bom-ref should be unique.", + "default": "", + "pattern": "^(.*)$" + }, + "provider": { + "title": "Provider", + "description": "The organization that provides the service.", + "$ref": "#/definitions/organizationalEntity" + }, + "group": { + "type": "string", + "title": "Service Group", + "description": "The grouping name, namespace, or identifier. This will often be a shortened, single name of the company or project that produced the service or domain name. Whitespace and special characters should be avoided.", + "default": "", + "examples": ["com.acme"], + "pattern": "^(.*)$" + }, + "name": { + "type": "string", + "title": "Service Name", + "description": "The name of the service. This will often be a shortened, single name of the service.", + "default": "", + "examples": ["ticker-service"], + "pattern": "^(.*)$" + }, + "version": { + "type": "string", + "title": "Service Version", + "description": "The service version.", + "default": "", + "examples": ["1.0.0"], + "pattern": "^(.*)$" + }, + "description": { + "type": "string", + "title": "Service Description", + "description": "Specifies a description for the service", + "default": "", + "pattern": "^(.*)$" + }, + "endpoints": { + "type": "array", + "title": "Endpoints", + "description": "The endpoint URIs of the service. Multiple endpoints are allowed.", + "default": "", + "examples": ["https://example.com/api/v1/ticker"], + "pattern": "^(.*)$" + }, + "authenticated": { + "type": "boolean", + "title": "Authentication Required", + "description": "A boolean value indicating if the service requires authentication. A value of true indicates the service requires authentication prior to use. A value of false indicates the service does not require authentication." + }, + "x-trust-boundary": { + "type": "boolean", + "title": "Crosses Trust Boundary", + "description": "A boolean value indicating if use of the service crosses a trust zone or boundary. A value of true indicates that by using the service, a trust boundary is crossed. A value of false indicates that by using the service, a trust boundary is not crossed." + }, + "data": { + "type": "array", + "items": {"$ref": "#/definitions/dataClassification"}, + "title": "Data Classification", + "description": "Specifies the data classification." + }, + "licenses": { + "type": "array", + "title": "Component License(s)", + "items": { + "additionalProperties": false, + "properties": { + "license": { + "$ref": "#/definitions/license" + }, + "expression": { + "type": "string", + "title": "SPDX License Expression", + "examples": [ + "Apache-2.0 AND (MIT OR GPL-2.0-only)", + "GPL-3.0-only WITH Classpath-exception-2.0" + ], + "pattern": "^(.*)$" + } + }, + "oneOf":[ + { + "required": ["license"] + }, + { + "required": ["expression"] + } + ] + } + }, + "externalReferences": { + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References" + }, + "services": { + "$id": "#/properties/services", + "type": "array", + "items": {"$ref": "#/definitions/service"}, + "uniqueItems": true, + "title": "Services" + } + } + }, + "dataClassification": { + "type": "object", + "title": "Hash Objects", + "required": [ + "flow", + "classification" + ], + "additionalProperties": false, + "properties": { + "flow": { + "$ref": "#/definitions/dataFlow" + }, + "classification": { + "type": "string" + } + } + }, + "dataFlow": { + "type": "string", + "enum": [ + "inbound", + "outbound", + "bi-directional", + "unknown" + ], + "title": "Data flow direction", + "default": "", + "pattern": "^(.*)$" + } + } +} diff --git a/cyclonedx/schema/bom-1.2.schema.json b/cyclonedx/schema/bom-1.2.schema.json new file mode 100644 index 00000000..2e44d942 --- /dev/null +++ b/cyclonedx/schema/bom-1.2.schema.json @@ -0,0 +1,997 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://cyclonedx.org/schema/bom-1.2a.schema.json", + "type": "object", + "title": "CycloneDX Software Bill-of-Material Specification", + "$comment" : "CycloneDX JSON schema is published under the terms of the Apache License 2.0.", + "required": [ + "bomFormat", + "specVersion", + "version" + ], + "properties": { + "bomFormat": { + "$id": "#/properties/bomFormat", + "type": "string", + "title": "BOM Format", + "description": "Specifies the format of the BOM. This helps to identify the file as CycloneDX since BOMs do not have a filename convention nor does JSON schema support namespaces.", + "enum": [ + "CycloneDX" + ] + }, + "specVersion": { + "$id": "#/properties/specVersion", + "type": "string", + "title": "CycloneDX Specification Version", + "description": "The version of the CycloneDX specification a BOM is written to (starting at version 1.2)", + "examples": ["1.2"] + }, + "serialNumber": { + "$id": "#/properties/serialNumber", + "type": "string", + "title": "BOM Serial Number", + "description": "Every BOM generated should have a unique serial number, even if the contents of the BOM being generated have not changed over time. The process or tool responsible for creating the BOM should create random UUID's for every BOM generated.", + "default": "", + "examples": ["urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"], + "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + "version": { + "$id": "#/properties/version", + "type": "integer", + "title": "BOM Version", + "description": "The version allows component publishers/authors to make changes to existing BOMs to update various aspects of the document such as description or licenses. When a system is presented with multiple BOMs for the same component, the system should use the most recent version of the BOM. The default version is '1' and should be incremented for each version of the BOM that is published. Each version of a component should have a unique BOM and if no changes are made to the BOMs, then each BOM will have a version of '1'.", + "default": 1, + "examples": [1] + }, + "metadata": { + "$id": "#/properties/metadata", + "$ref": "#/definitions/metadata", + "title": "BOM Metadata", + "description": "Provides additional information about a BOM." + }, + "components": { + "$id": "#/properties/components", + "type": "array", + "items": {"$ref": "#/definitions/component"}, + "uniqueItems": true, + "title": "Components" + }, + "services": { + "$id": "#/properties/services", + "type": "array", + "items": {"$ref": "#/definitions/service"}, + "uniqueItems": true, + "title": "Services" + }, + "externalReferences": { + "$id": "#/properties/externalReferences", + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References", + "description": "External references provide a way to document systems, sites, and information that may be relevant but which are not included with the BOM." + }, + "dependencies": { + "$id": "#/properties/dependencies", + "type": "array", + "items": {"$ref": "#/definitions/dependency"}, + "uniqueItems": true, + "title": "Dependencies", + "description": "Provides the ability to document dependency relationships." + } + }, + "definitions": { + "metadata": { + "type": "object", + "title": "BOM Metadata Object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "The date and time (timestamp) when the document was created." + }, + "tools": { + "type": "array", + "title": "Creation Tools", + "description": "The tool(s) used in the creation of the BOM.", + "items": {"$ref": "#/definitions/tool"} + }, + "authors" :{ + "type": "array", + "title": "Authors", + "description": "The person(s) who created the BOM. Authors are common in BOMs created through manual processes. BOMs created through automated means may not have authors.", + "items": {"$ref": "#/definitions/organizationalContact"} + }, + "component": { + "title": "Component", + "description": "The component that the BOM describes.", + "$ref": "#/definitions/component" + }, + "manufacture": { + "title": "Manufacture", + "description": "The organization that manufactured the component that the BOM describes.", + "$ref": "#/definitions/organizationalEntity" + }, + "supplier": { + "title": "Supplier", + "description": " The organization that supplied the component that the BOM describes. The supplier may often be the manufacture, but may also be a distributor or repackager.", + "$ref": "#/definitions/organizationalEntity" + } + } + }, + "tool": { + "type": "object", + "title": "Tool", + "description": "The tool used to create the BOM.", + "properties": { + "vendor": { + "type": "string", + "format": "string", + "title": "Tool Vendor", + "description": "The date and time (timestamp) when the document was created." + }, + "name": { + "type": "string", + "format": "string", + "title": "Tool Name", + "description": "The date and time (timestamp) when the document was created." + }, + "version": { + "type": "string", + "format": "string", + "title": "Tool Version", + "description": "The date and time (timestamp) when the document was created." + }, + "hashes": { + "$id": "#/properties/hashes", + "type": "array", + "items": {"$ref": "#/definitions/hash"}, + "title": "Hashes", + "description": "The hashes of the tool (if applicable)." + } + } + }, + "organizationalEntity": { + "type": "object", + "title": "Organizational Entity Object", + "description": "", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the organization", + "default": "", + "examples": [ + "Example Inc." + ], + "pattern": "^(.*)$" + }, + "url": { + "type": "array", + "title": "URL", + "description": "The URL of the organization. Multiple URLs are allowed.", + "default": "", + "examples": ["https://example.com"], + "pattern": "^(.*)$" + }, + "contact": { + "type": "array", + "title": "Contact", + "description": "A contact at the organization. Multiple contacts are allowed.", + "items": {"$ref": "#/definitions/organizationalContact"} + } + } + }, + "organizationalContact": { + "type": "object", + "title": "Organizational Contact Object", + "description": "", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of a contact", + "default": "", + "examples": ["Contact name"], + "pattern": "^(.*)$" + }, + "email": { + "type": "string", + "title": "Email Address", + "description": "The email address of the contact. Multiple email addresses are allowed.", + "default": "", + "examples": ["firstname.lastname@example.com"], + "pattern": "^(.*)$" + }, + "phone": { + "type": "string", + "title": "Phone", + "description": "The phone number of the contact. Multiple phone numbers are allowed.", + "default": "", + "examples": ["800-555-1212"], + "pattern": "^(.*)$" + } + } + }, + "component": { + "type": "object", + "title": "Component Object", + "required": [ + "type", + "name", + "version" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "application", + "framework", + "library", + "container", + "operating-system", + "device", + "firmware", + "file" + ], + "title": "Component Type", + "description": "Specifies the type of component. For software components, classify as application if no more specific appropriate classification is available or cannot be determined for the component.", + "default": "", + "examples": ["library"], + "pattern": "^(.*)$" + }, + "mime-type": { + "type": "string", + "title": "Mime-Type", + "description": "The optional mime-type of the component. When used on file components, the mime-type can provide additional context about the kind of file being represented such as an image, font, or executable. Some library or framework components may also have an associated mime-type.", + "default": "", + "examples": ["image/jpeg"], + "pattern": "^[-+a-z0-9.]+/[-+a-z0-9.]+$" + }, + "bom-ref": { + "type": "string", + "title": "BOM Reference", + "description": "An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref should be unique.", + "default": "", + "pattern": "^(.*)$" + }, + "supplier": { + "title": "Component Supplier", + "description": " The organization that supplied the component. The supplier may often be the manufacture, but may also be a distributor or repackager.", + "$ref": "#/definitions/organizationalEntity" + }, + "author": { + "type": "string", + "title": "Component Author", + "description": "The person(s) or organization(s) that authored the component", + "default": "", + "examples": ["Acme Inc"], + "pattern": "^(.*)$" + }, + "publisher": { + "type": "string", + "title": "Component Publisher", + "description": "The person(s) or organization(s) that published the component", + "default": "", + "examples": ["Acme Inc"], + "pattern": "^(.*)$" + }, + "group": { + "type": "string", + "title": "Component Group", + "description": "The grouping name or identifier. This will often be a shortened, single name of the company or project that produced the component, or the source package or domain name. Whitespace and special characters should be avoided. Examples include: apache, org.apache.commons, and apache.org.", + "default": "", + "examples": ["com.acme"], + "pattern": "^(.*)$" + }, + "name": { + "type": "string", + "title": "Component Name", + "description": "The name of the component. This will often be a shortened, single name of the component. Examples: commons-lang3 and jquery", + "default": "", + "examples": ["tomcat-catalina"], + "pattern": "^(.*)$" + }, + "version": { + "type": "string", + "title": "Component Version", + "description": "The component version. The version should ideally comply with semantic versioning but is not enforced.", + "default": "", + "examples": ["9.0.14"], + "pattern": "^(.*)$" + }, + "description": { + "type": "string", + "title": "Component Description", + "description": "Specifies a description for the component", + "default": "", + "pattern": "^(.*)$" + }, + "scope": { + "type": "string", + "enum": [ + "required", + "optional", + "excluded" + ], + "title": "Component Scope", + "description": "Specifies the scope of the component. If scope is not specified, 'required' scope should be assumed by the consumer of the BOM", + "default": "required", + "pattern": "^(.*)$" + }, + "hashes": { + "type": "array", + "title": "Component Hashes", + "items": {"$ref": "#/definitions/hash"} + }, + "licenses": { + "type": "array", + "title": "Component License(s)", + "items": { + "properties": { + "license": { + "$ref": "#/definitions/license" + }, + "expression": { + "type": "string", + "title": "SPDX License Expression", + "examples": [ + "Apache-2.0 AND (MIT OR GPL-2.0-only)", + "GPL-3.0-only WITH Classpath-exception-2.0" + ], + "pattern": "^(.*)$" + } + }, + "oneOf":[ + { + "required": ["license"] + }, + { + "required": ["expression"] + } + ] + } + }, + "copyright": { + "type": "string", + "title": "Component Copyright", + "description": "An optional copyright notice informing users of the underlying claims to copyright ownership in a published work.", + "examples": ["Acme Inc"], + "pattern": "^(.*)$" + }, + "cpe": { + "type": "string", + "title": "Component Common Platform Enumeration (CPE)", + "description": "DEPRECATED - DO NOT USE. This will be removed in a future version. Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe", + "examples": ["cpe:2.3:a:acme:component_framework:-:*:*:*:*:*:*:*"], + "pattern": "^(.*)$" + }, + "purl": { + "type": "string", + "title": "Component Package URL (purl)", + "default": "", + "examples": ["pkg:maven/com.acme/tomcat-catalina@9.0.14?packaging=jar"], + "pattern": "^(.*)$" + }, + "swid": { + "$ref": "#/definitions/swid", + "title": "SWID Tag", + "description": "Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags." + }, + "modified": { + "type": "boolean", + "title": "Component Modified From Original", + "description": "DEPRECATED - DO NOT USE. This will be removed in a future version. Use the pedigree element instead to supply information on exactly how the component was modified. A boolean value indicating is the component has been modified from the original. A value of true indicates the component is a derivative of the original. A value of false indicates the component has not been modified from the original." + }, + "pedigree": { + "type": "object", + "title": "Component Pedigree", + "description": "Component pedigree is a way to document complex supply chain scenarios where components are created, distributed, modified, redistributed, combined with other components, etc. Pedigree supports viewing this complex chain from the beginning, the end, or anywhere in the middle. It also provides a way to document variants where the exact relation may not be known.", + "properties": { + "ancestors": { + "type": "array", + "title": "Ancestors", + "description": "Describes zero or more components in which a component is derived from. This is commonly used to describe forks from existing projects where the forked version contains a ancestor node containing the original component it was forked from. For example, Component A is the original component. Component B is the component being used and documented in the BOM. However, Component B contains a pedigree node with a single ancestor documenting Component A - the original component from which Component B is derived from.", + "items": {"$ref": "#/definitions/component"} + }, + "descendants": { + "type": "array", + "title": "Descendants", + "description": "Descendants are the exact opposite of ancestors. This provides a way to document all forks (and their forks) of an original or root component.", + "items": {"$ref": "#/definitions/component"} + }, + "variants": { + "type": "array", + "title": "Variants", + "description": "Variants describe relations where the relationship between the components are not known. For example, if Component A contains nearly identical code to Component B. They are both related, but it is unclear if one is derived from the other, or if they share a common ancestor.", + "items": {"$ref": "#/definitions/component"} + }, + "commits": { + "type": "array", + "title": "Commits", + "description": "A list of zero or more commits which provide a trail describing how the component deviates from an ancestor, descendant, or variant.", + "items": {"$ref": "#/definitions/commit"} + }, + "patches": { + "type": "array", + "title": "Patches", + "description": ">A list of zero or more patches describing how the component deviates from an ancestor, descendant, or variant. Patches may be complimentary to commits or may be used in place of commits.", + "items": {"$ref": "#/definitions/patch"} + }, + "notes": { + "type": "string", + "title": "Notes", + "description": "Notes, observations, and other non-structured commentary describing the components pedigree.", + "pattern": "^(.*)$" + } + } + }, + "externalReferences": { + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References" + }, + "components": { + "$id": "#/properties/components", + "type": "array", + "items": {"$ref": "#/definitions/component"}, + "uniqueItems": true, + "title": "Components" + } + } + }, + "swid": { + "type": "object", + "title": "SWID Tag", + "description": "Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags.", + "required": [ + "tagId", + "name" + ], + "properties": { + "tagId": { + "type": "string", + "title": "Tag ID", + "description": "Maps to the tagId of a SoftwareIdentity." + }, + "name": { + "type": "string", + "title": "Name", + "description": "Maps to the name of a SoftwareIdentity." + }, + "version": { + "type": "string", + "title": "Version", + "default": "0.0", + "description": "Maps to the version of a SoftwareIdentity." + }, + "tagVersion": { + "type": "integer", + "title": "Tag Version", + "default": 0, + "description": "Maps to the tagVersion of a SoftwareIdentity." + }, + "patch": { + "type": "boolean", + "title": "Patch", + "default": false, + "description": "Maps to the patch of a SoftwareIdentity." + }, + "text": { + "title": "Attachment text", + "description": "Specifies the metadata and content of the SWID tag.", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "URL", + "default": "The URL to the SWID file.", + "pattern": "^(.*)$" + } + } + }, + "attachment": { + "type": "object", + "title": "Attachment", + "description": "Specifies the metadata and content for an attachment.", + "required": [ + "content" + ], + "properties": { + "contentType": { + "type": "string", + "title": "Content-Type", + "description": "Specifies the content type of the text. Defaults to text/plain if not specified.", + "default": "text/plain" + }, + "encoding": { + "type": "string", + "title": "Encoding", + "description": "Specifies the optional encoding the text is represented in.", + "enum": [ + "base64" + ], + "default": "", + "pattern": "^(.*)$" + }, + "content": { + "type": "string", + "title": "Attachment Text", + "description": "The attachment data" + } + } + }, + "hash": { + "type": "object", + "title": "Hash Objects", + "required": [ + "alg", + "content" + ], + "properties": { + "alg": { + "$ref": "#/definitions/hash-alg" + }, + "content": { + "$ref": "#/definitions/hash-content" + } + } + }, + "hash-alg": { + "type": "string", + "enum": [ + "MD5", + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512", + "SHA3-256", + "SHA3-384", + "SHA3-512", + "BLAKE2b-256", + "BLAKE2b-384", + "BLAKE2b-512", + "BLAKE3" + ], + "title": "Hash Algorithm", + "default": "", + "pattern": "^(.*)$" + }, + "hash-content": { + "type": "string", + "title": "Hash Content (value)", + "default": "", + "examples": ["3942447fac867ae5cdb3229b658f4d48"], + "pattern": "^([a-fA-F0-9]{32}|[a-fA-F0-9]{40}|[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128})$" + }, + "license": { + "type": "object", + "title": "License Object", + "oneOf": [ + { + "required": ["id"] + }, + { + "required": ["name"] + } + ], + "properties": { + "id": { + "$ref": "spdx.schema.json", + "title": "License ID (SPDX)", + "description": "A valid SPDX license ID", + "examples": ["Apache-2.0"] + }, + "name": { + "type": "string", + "title": "License Name", + "description": "If SPDX does not define the license used, this field may be used to provide the license name", + "default": "", + "examples": ["Acme Software License"], + "pattern": "^(.*)$" + }, + "text": { + "title": "License text", + "description": "An optional way to include the textual content of a license.", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "License URL", + "description": "The URL to the license file. If specified, a 'license' externalReference should also be specified for completeness", + "examples": ["https://www.apache.org/licenses/LICENSE-2.0.txt"], + "pattern": "^(.*)$" + } + } + }, + "commit": { + "type": "object", + "title": "Commit", + "description": "Specifies an individual commit", + "properties": { + "uid": { + "type": "string", + "title": "UID", + "description": "A unique identifier of the commit. This may be version control specific. For example, Subversion uses revision numbers whereas git uses commit hashes.", + "pattern": "^(.*)$" + }, + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the commit. This URL will typically point to a commit in a version control system.", + "format": "iri-reference" + }, + "author": { + "title": "Author", + "description": "The author who created the changes in the commit", + "$ref": "#/definitions/identifiableAction" + }, + "committer": { + "title": "Committer", + "description": "The person who committed or pushed the commit", + "$ref": "#/definitions/identifiableAction" + }, + "message": { + "type": "string", + "title": "Message", + "description": "The text description of the contents of the commit", + "pattern": "^(.*)$" + } + } + }, + "patch": { + "type": "object", + "title": "Patch", + "description": "Specifies an individual patch", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "unofficial", + "monkey", + "backport", + "cherry-pick" + ], + "title": "Type", + "description": "Specifies the purpose for the patch including the resolution of defects, security issues, or new behavior or functionality" + }, + "diff": { + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "$ref": "#/definitions/diff" + }, + "resolves": { + "type": "array", + "items": {"$ref": "#/definitions/issue"}, + "title": "Resolves", + "description": "A collection of issues the patch resolves" + } + } + }, + "diff": { + "type": "object", + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "properties": { + "text": { + "title": "Diff text", + "description": "Specifies the optional text of the diff", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "URL", + "description": "Specifies the URL to the diff", + "pattern": "^(.*)$" + } + } + }, + "issue": { + "type": "object", + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "defect", + "enhancement", + "security" + ], + "title": "Type", + "description": "Specifies the type of issue" + }, + "id": { + "type": "string", + "title": "ID", + "description": "The identifier of the issue assigned by the source of the issue", + "pattern": "^(.*)$" + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the issue", + "pattern": "^(.*)$" + }, + "description": { + "type": "string", + "title": "Description", + "description": "A description of the issue", + "pattern": "^(.*)$" + }, + "source": { + "type": "object", + "title": "Source", + "description": "The source of the issue where it is documented", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the source. For example 'National Vulnerability Database', 'NVD', and 'Apache'", + "pattern": "^(.*)$" + }, + "url": { + "type": "string", + "title": "URL", + "description": "The url of the issue documentation as provided by the source", + "pattern": "^(.*)$" + } + } + }, + "references": { + "type": "array", + "title": "References", + "description": "A collection of URL's for reference. Multiple URLs are allowed.", + "default": "", + "examples": ["https://example.com"], + "pattern": "^(.*)$" + } + } + }, + "identifiableAction": { + "type": "object", + "title": "Identifiable Action", + "description": "Specifies an individual commit", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "The timestamp in which the action occurred" + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the individual who performed the action", + "pattern": "^(.*)$" + }, + "email": { + "type": "string", + "format": "idn-email", + "title": "E-mail", + "description": "The email address of the individual who performed the action" + } + } + }, + "externalReference": { + "type": "object", + "title": "External Reference", + "description": "Specifies an individual external reference", + "required": [ + "url", + "type" + ], + "properties": { + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the external reference", + "pattern": "^(.*)$" + }, + "comment": { + "type": "string", + "title": "Comment", + "description": "An optional comment describing the external reference", + "pattern": "^(.*)$" + }, + "type": { + "type": "string", + "title": "Type", + "description": "Specifies the type of external reference. There are built-in types to describe common references. If a type does not exist for the reference being referred to, use the \"other\" type.", + "enum": [ + "vcs", + "issue-tracker", + "website", + "advisories", + "bom", + "mailing-list", + "social", + "chat", + "documentation", + "support", + "distribution", + "license", + "build-meta", + "build-system", + "other" + ] + } + } + }, + "dependency": { + "type": "object", + "title": "Dependency", + "description": "Defines the direct dependencies of a component. Components that do not have their own dependencies MUST be declared as empty elements within the graph. Components that are not represented in the dependency graph MAY have unknown dependencies. It is RECOMMENDED that implementations assume this to be opaque and not an indicator of a component being dependency-free.", + "required": [ + "ref" + ], + "properties": { + "ref": { + "type": "string", + "format": "string", + "title": "Reference", + "description": "References a component by the components bom-ref attribute" + }, + "dependsOn": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "title": "Depends On", + "description": "The bom-ref identifiers of the components that are dependencies of this dependency object." + } + } + }, + "service": { + "type": "object", + "title": "Service Object", + "required": [ + "name" + ], + "properties": { + "bom-ref": { + "type": "string", + "title": "BOM Reference", + "description": "An optional identifier which can be used to reference the service elsewhere in the BOM. Every bom-ref should be unique.", + "default": "", + "pattern": "^(.*)$" + }, + "provider": { + "title": "Provider", + "description": "The organization that provides the service.", + "$ref": "#/definitions/organizationalEntity" + }, + "group": { + "type": "string", + "title": "Service Group", + "description": "The grouping name, namespace, or identifier. This will often be a shortened, single name of the company or project that produced the service or domain name. Whitespace and special characters should be avoided.", + "default": "", + "examples": ["com.acme"], + "pattern": "^(.*)$" + }, + "name": { + "type": "string", + "title": "Service Name", + "description": "The name of the service. This will often be a shortened, single name of the service.", + "default": "", + "examples": ["ticker-service"], + "pattern": "^(.*)$" + }, + "version": { + "type": "string", + "title": "Service Version", + "description": "The service version.", + "default": "", + "examples": ["1.0.0"], + "pattern": "^(.*)$" + }, + "description": { + "type": "string", + "title": "Service Description", + "description": "Specifies a description for the service", + "default": "", + "pattern": "^(.*)$" + }, + "endpoints": { + "type": "array", + "title": "Endpoints", + "description": "The endpoint URIs of the service. Multiple endpoints are allowed.", + "default": "", + "examples": ["https://example.com/api/v1/ticker"], + "pattern": "^(.*)$" + }, + "authenticated": { + "type": "boolean", + "title": "Authentication Required", + "description": "A boolean value indicating if the service requires authentication. A value of true indicates the service requires authentication prior to use. A value of false indicates the service does not require authentication." + }, + "x-trust-boundary": { + "type": "boolean", + "title": "Crosses Trust Boundary", + "description": "A boolean value indicating if use of the service crosses a trust zone or boundary. A value of true indicates that by using the service, a trust boundary is crossed. A value of false indicates that by using the service, a trust boundary is not crossed." + }, + "data": { + "type": "array", + "items": {"$ref": "#/definitions/dataClassification"}, + "title": "Data Classification", + "description": "Specifies the data classification." + }, + "licenses": { + "type": "array", + "title": "Component License(s)", + "items": { + "properties": { + "license": { + "$ref": "#/definitions/license" + }, + "expression": { + "type": "string", + "title": "SPDX License Expression", + "examples": [ + "Apache-2.0 AND (MIT OR GPL-2.0-only)", + "GPL-3.0-only WITH Classpath-exception-2.0" + ], + "pattern": "^(.*)$" + } + }, + "oneOf":[ + { + "required": ["license"] + }, + { + "required": ["expression"] + } + ] + } + }, + "externalReferences": { + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References" + }, + "services": { + "$id": "#/properties/services", + "type": "array", + "items": {"$ref": "#/definitions/service"}, + "uniqueItems": true, + "title": "Services" + } + } + }, + "dataClassification": { + "type": "object", + "title": "Hash Objects", + "required": [ + "flow", + "classification" + ], + "properties": { + "flow": { + "$ref": "#/definitions/dataFlow" + }, + "classification": { + "type": "string" + } + } + }, + "dataFlow": { + "type": "string", + "enum": [ + "inbound", + "outbound", + "bi-directional", + "unknown" + ], + "title": "Data flow direction", + "default": "", + "pattern": "^(.*)$" + } + } +} diff --git a/cyclonedx/schema/bom-1.2.xsd b/cyclonedx/schema/bom-1.2.xsd new file mode 100644 index 00000000..d30ff62e --- /dev/null +++ b/cyclonedx/schema/bom-1.2.xsd @@ -0,0 +1,1418 @@ + + + + + + + + + CycloneDX Software Bill-of-Material Specification + https://cyclonedx.org/ + Apache License, Version 2.0 + + Steve Springett + + + + + + + + + The date and time (timestamp) when the document was created. + + + + + The tool(s) used in the creation of the BOM. + + + + + + + + + + The person(s) who created the BOM. Authors are common in BOMs created through + manual processes. BOMs created through automated means may not have authors. + + + + + + + + + + The component that the BOM describes. + + + + + The organization that manufactured the component that the BOM describes. + + + + + The organization that supplied the component that the BOM describes. The + supplier may often be the manufacture, but may also be a distributor or repackager. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The name of the organization + + + + + The URL of the organization. Multiple URLs are allowed. + + + + + A contact person at the organization. Multiple contacts are allowed. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Specifies a tool (manual or automated). + + + + + The vendor of the tool used to create the BOM. + + + + + The name of the tool used to create the BOM. + + + + + The version of the tool used to create the BOM. + + + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The name of the contact + + + + + The email address of the contact. Multiple email addresses are allowed. + + + + + The phone number of the contact. Multiple phone numbers are allowed. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The organization that supplied the component. The supplier may often + be the manufacture, but may also be a distributor or repackager. + + + + + The person(s) or organization(s) that authored the component + + + + + The person(s) or organization(s) that published the component + + + + + The grouping name or identifier. This will often be a shortened, single + name of the company or project that produced the component, or the source package or + domain name. Whitespace and special characters should be avoided. Examples include: + apache, org.apache.commons, and apache.org. + + + + + The name of the component. This will often be a shortened, single name + of the component. Examples: commons-lang3 and jquery + + + + + The component version. The version should ideally comply with semantic versioning + but is not enforced. + + + + + Specifies a description for the component + + + + + Specifies the scope of the component. If scope is not specified, 'runtime' + scope should be assumed by the consumer of the BOM + + + + + + + + + + + + + + + + A valid SPDX license expression. + Refer to https://spdx.org/specifications for syntax requirements + + + + + + + + An optional copyright notice informing users of the underlying claims to + copyright ownership in a published work. + + + + + + DEPRECATED - DO NOT USE. This will be removed in a future version. + Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe + + + + + + + Specifies the package-url (PURL). The purl, if specified, must be valid and conform + to the specification defined at: https://github.com/package-url/purl-spec + + + + + + + Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags. + + + + + + + DEPRECATED - DO NOT USE. This will be removed in a future version. Use the pedigree + element instead to supply information on exactly how the component was modified. + A boolean value indicating is the component has been modified from the original. + A value of true indicates the component is a derivative of the original. + A value of false indicates the component has not been modified from the original. + + + + + + + Component pedigree is a way to document complex supply chain scenarios where components are + created, distributed, modified, redistributed, combined with other components, etc. + + + + + + Provides the ability to document external references related to the + component or to the project the component describes. + + + + + + Specifies optional sub-components. This is not a dependency tree. It provides a way + to specify a hierarchical representation of component assemblies, similar to + system -> subsystem -> parts assembly in physical supply chains. + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + Specifies the type of component. For software components, classify as application if no more + specific appropriate classification is available or cannot be determined for the component. + + + + + + + The optional mime-type of the component. When used on file components, the mime-type + can provide additional context about the kind of file being represented such as an image, + font, or executable. Some library or framework components may also have an associated mime-type. + + + + + + + An optional identifier which can be used to reference the component elsewhere in the BOM. + Uniqueness is enforced within all elements and children of the root-level bom element. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + + A valid SPDX license ID + + + + + If SPDX does not define the license used, this field may be used to provide the license name + + + + + + Specifies the optional full text of the attachment + + + + + The URL to the attachment file. If the attachment is a license or BOM, + an externalReference should also be specified for completeness. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + Specifies attributes of the text + + + + Specifies the content type of the text. Defaults to text/plain + if not specified. + + + + + + Specifies the optional encoding the text is represented in + + + + + + + + + + Specifies the file hash of the component + + + + + + Specifies the algorithm used to create the hash + + + + + + + + + + + The component is required for runtime + + + + + The component is optional at runtime. Optional components are components that + are not capable of being called due to them not be installed or otherwise accessible by any means. + Components that are installed but due to configuration or other restrictions are prohibited from + being called must be scoped as 'required'. + + + + + Components that are excluded provide the ability to document component usage + for test and other non-runtime purposes. Excluded components are not reachable within a call + graph at runtime. + + + + + + + + + + A software application. Refer to https://en.wikipedia.org/wiki/Application_software + for information about applications. + + + + + A software framework. Refer to https://en.wikipedia.org/wiki/Software_framework + for information on how frameworks vary slightly from libraries. + + + + + A software library. Refer to https://en.wikipedia.org/wiki/Library_(computing) + for information about libraries. All third-party and open source reusable components will likely + be a library. If the library also has key features of a framework, then it should be classified + as a framework. If not, or is unknown, then specifying library is recommended. + + + + + A packaging and/or runtime format, not specific to any particular technology, + which isolates software inside the container from software outside of a container through + virtualization technology. Refer to https://en.wikipedia.org/wiki/OS-level_virtualization + + + + + A software operating system without regard to deployment model + (i.e. installed on physical hardware, virtual machine, image, etc) Refer to + https://en.wikipedia.org/wiki/Operating_system + + + + + A hardware device such as a processor, or chip-set. A hardware device + containing firmware should include a component for the physical hardware itself, and another + component of type 'firmware' or 'operating-system' (whichever is relevant), describing + information about the software running on the device. + + + + + A special type of software that provides low-level control over a devices + hardware. Refer to https://en.wikipedia.org/wiki/Firmware + + + + + A computer file. Refer to https://en.wikipedia.org/wiki/Computer_file + for information about files. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Define the format for acceptable CPE URIs. Supports CPE 2.2 and CPE 2.3 formats. + Refer to https://nvd.nist.gov/products/cpe for official specification. + + + + + + + + + + + + Specifies the full content of the SWID tag. + + + + + The URL to the SWID file. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + Maps to the tagId of a SoftwareIdentity. + + + + + Maps to the name of a SoftwareIdentity. + + + + + Maps to the version of a SoftwareIdentity. + + + + + Maps to the tagVersion of a SoftwareIdentity. + + + + + Maps to the patch of a SoftwareIdentity. + + + + + + + + Defines a string representation of a UUID conforming to RFC 4122. + + + + + + + + + + + + Version Control System + + + + + Issue or defect tracking system, or an Application Lifecycle Management (ALM) system + + + + + Website + + + + + Security advisories + + + + + Bill-of-material document (CycloneDX, SPDX, SWID, etc) + + + + + Mailing list or discussion group + + + + + Social media account + + + + + Real-time chat platform + + + + + Documentation, guides, or how-to instructions + + + + + Community or commercial support + + + + + Direct or repository download location + + + + + The URL to the license file. If a license URL has been defined in the license + node, it should also be defined as an external reference for completeness + + + + + Build-system specific meta file (i.e. pom.xml, package.json, .nuspec, etc) + + + + + URL to an automated build system + + + + + Use this if no other types accurately describe the purpose of the external reference + + + + + + + + + External references provide a way to document systems, sites, and information that may be relevant + but which are not included with the BOM. + + + + + + Zero or more external references can be defined + + + + + + + + + + The URL to the external reference + + + + + An optional comment describing the external reference + + + + + + Specifies the type of external reference. There are built-in types to describe common + references. If a type does not exist for the reference being referred to, use the "other" type. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Zero or more commits can be specified. + + + + + Specifies an individual commit. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + A unique identifier of the commit. This may be version control + specific. For example, Subversion uses revision numbers whereas git uses commit hashes. + + + + + + The URL to the commit. This URL will typically point to a commit + in a version control system. + + + + + + The author who created the changes in the commit + + + + + The person who committed or pushed the commit + + + + + The text description of the contents of the commit + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + Zero or more patches can be specified. + + + + + Specifies an individual patch. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + The patch file (or diff) that show changes. + Refer to https://en.wikipedia.org/wiki/Diff + + + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + Specifies the purpose for the patch including the resolution of defects, + security issues, or new behavior or functionality + + + + + + + + + A patch which is not developed by the creators or maintainers of the software + being patched. Refer to https://en.wikipedia.org/wiki/Unofficial_patch + + + + + A patch which dynamically modifies runtime behavior. + Refer to https://en.wikipedia.org/wiki/Monkey_patch + + + + + A patch which takes code from a newer version of software and applies + it to older versions of the same software. Refer to https://en.wikipedia.org/wiki/Backporting + + + + + A patch created by selectively applying commits from other versions or + branches of the same software. + + + + + + + + + + A fault, flaw, or bug in software + + + + + A new feature or behavior in software + + + + + A special type of defect which impacts security + + + + + + + + + + Specifies the optional text of the diff + + + + + Specifies the URL to the diff + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + The identifier of the issue assigned by the source of the issue + + + + + The name of the issue + + + + + A description of the issue + + + + + + + The source of the issue where it is documented. + + + + + + + The name of the source. For example "National Vulnerability Database", + "NVD", and "Apache" + + + + + + + The url of the issue documentation as provided by the source + + + + + + + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + Specifies the type of issue + + + + + + + + + The timestamp in which the action occurred + + + + + The name of the individual who performed the action + + + + + The email address of the individual who performed the action + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + Component pedigree is a way to document complex supply chain scenarios where components are created, + distributed, modified, redistributed, combined with other components, etc. Pedigree supports viewing + this complex chain from the beginning, the end, or anywhere in the middle. It also provides a way to + document variants where the exact relation may not be known. + + + + + + Describes zero or more components in which a component is derived + from. This is commonly used to describe forks from existing projects where the forked version + contains a ancestor node containing the original component it was forked from. For example, + Component A is the original component. Component B is the component being used and documented + in the BOM. However, Component B contains a pedigree node with a single ancestor documenting + Component A - the original component from which Component B is derived from. + + + + + + Descendants are the exact opposite of ancestors. This provides a + way to document all forks (and their forks) of an original or root component. + + + + + + Variants describe relations where the relationship between the + components are not known. For example, if Component A contains nearly identical code to + Component B. They are both related, but it is unclear if one is derived from the other, + or if they share a common ancestor. + + + + + + A list of zero or more commits which provide a trail describing + how the component deviates from an ancestor, descendant, or variant. + + + + + A list of zero or more patches describing how the component + deviates from an ancestor, descendant, or variant. Patches may be complimentary to commits + or may be used in place of commits. + + + + + Notes, observations, and other non-structured commentary + describing the components pedigree. + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + + + References a component or service by the its bom-ref attribute + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + Components that do not have their own dependencies MUST be declared as empty + elements within the graph. Components that are not represented in the dependency graph MAY + have unknown dependencies. It is RECOMMENDED that implementations assume this to be opaque + and not an indicator of a component being dependency-free. + + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The organization that provides the service. + + + + + The grouping name, namespace, or identifier. This will often be a shortened, + single name of the company or project that produced the service or domain name. + Whitespace and special characters should be avoided. + + + + + The name of the service. This will often be a shortened, single name + of the service. + + + + + The service version. + + + + + Specifies a description for the service. + + + + + + + + A service endpoint URI. + + + + + + + + A boolean value indicating if the service requires authentication. + A value of true indicates the service requires authentication prior to use. + A value of false indicates the service does not require authentication. + + + + + A boolean value indicating if use of the service crosses a trust zone or boundary. + A value of true indicates that by using the service, a trust boundary is crossed. + A value of false indicates that by using the service, a trust boundary is not crossed. + + + + + + + + Specifies the data classification. + + + + + + + + + + + + A valid SPDX license expression. + Refer to https://spdx.org/specifications for syntax requirements + + + + + + + + Provides the ability to document external references related to the service. + + + + + + Specifies optional sub-service. This is not a dependency tree. It provides a way + to specify a hierarchical representation of service assemblies, similar to + system -> subsystem -> parts assembly in physical supply chains. + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + An optional identifier which can be used to reference the service elsewhere in the BOM. + Uniqueness is enforced within all elements and children of the root-level bom element. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Specifies the data classification. + + + + + + Specifies the flow direction of the data. + + + + + + + + + Specifies the flow direction of the data. Valid values are: + inbound, outbound, bi-directional, and unknown. Direction is relative to the service. + Inbound flow states that data enters the service. Outbound flow states that data + leaves the service. Bi-directional states that data flows both ways, and unknown + states that the direction is not known. + + + + + + + + + + + + + + + Provides additional information about a BOM. + + + + + Provides the ability to document a list of components. + + + + + Provides the ability to document a list of external services. + + + + + Provides the ability to document external references related to the BOM or + to the project the BOM describes. + + + + + Provides the ability to document dependency relationships. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + The version allows component publishers/authors to make changes to existing + BOMs to update various aspects of the document such as description or licenses. When a system + is presented with multiple BOMs for the same component, the system should use the most recent + version of the BOM. The default version is '1' and should be incremented for each version of the + BOM that is published. Each version of a component should have a unique BOM and if no changes are + made to the BOMs, then each BOM will have a version of '1'. + + + + + Every BOM generated should have a unique serial number, even if the contents + of the BOM being generated have not changed over time. The process or tool responsible for + creating the BOM should create random UUID's for every BOM generated. + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + diff --git a/cyclonedx/schema/bom-1.3-strict.schema.json b/cyclonedx/schema/bom-1.3-strict.schema.json new file mode 100644 index 00000000..cecf325e --- /dev/null +++ b/cyclonedx/schema/bom-1.3-strict.schema.json @@ -0,0 +1,1079 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://cyclonedx.org/schema/bom-1.3.schema.json", + "type": "object", + "title": "CycloneDX Software Bill-of-Material Specification", + "$comment" : "CycloneDX JSON schema is published under the terms of the Apache License 2.0.", + "required": [ + "bomFormat", + "specVersion", + "version" + ], + "additionalProperties": false, + "properties": { + "bomFormat": { + "$id": "#/properties/bomFormat", + "type": "string", + "title": "BOM Format", + "description": "Specifies the format of the BOM. This helps to identify the file as CycloneDX since BOMs do not have a filename convention nor does JSON schema support namespaces.", + "enum": [ + "CycloneDX" + ] + }, + "specVersion": { + "$id": "#/properties/specVersion", + "type": "string", + "title": "CycloneDX Specification Version", + "description": "The version of the CycloneDX specification a BOM is written to (starting at version 1.2)", + "examples": ["1.3"] + }, + "serialNumber": { + "$id": "#/properties/serialNumber", + "type": "string", + "title": "BOM Serial Number", + "description": "Every BOM generated should have a unique serial number, even if the contents of the BOM being generated have not changed over time. The process or tool responsible for creating the BOM should create random UUID's for every BOM generated.", + "examples": ["urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"], + "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + "version": { + "$id": "#/properties/version", + "type": "integer", + "title": "BOM Version", + "description": "The version allows component publishers/authors to make changes to existing BOMs to update various aspects of the document such as description or licenses. When a system is presented with multiple BOMs for the same component, the system should use the most recent version of the BOM. The default version is '1' and should be incremented for each version of the BOM that is published. Each version of a component should have a unique BOM and if no changes are made to the BOMs, then each BOM will have a version of '1'.", + "default": 1, + "examples": [1] + }, + "metadata": { + "$id": "#/properties/metadata", + "$ref": "#/definitions/metadata", + "title": "BOM Metadata", + "description": "Provides additional information about a BOM." + }, + "components": { + "$id": "#/properties/components", + "type": "array", + "items": {"$ref": "#/definitions/component"}, + "uniqueItems": true, + "title": "Components" + }, + "services": { + "$id": "#/properties/services", + "type": "array", + "items": {"$ref": "#/definitions/service"}, + "uniqueItems": true, + "title": "Services" + }, + "externalReferences": { + "$id": "#/properties/externalReferences", + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References", + "description": "External references provide a way to document systems, sites, and information that may be relevant but which are not included with the BOM." + }, + "dependencies": { + "$id": "#/properties/dependencies", + "type": "array", + "items": {"$ref": "#/definitions/dependency"}, + "uniqueItems": true, + "title": "Dependencies", + "description": "Provides the ability to document dependency relationships." + }, + "compositions": { + "$id": "#/properties/compositions", + "type": "array", + "items": {"$ref": "#/definitions/compositions"}, + "uniqueItems": true, + "title": "Compositions", + "description": "Compositions describe constituent parts (including components, services, and dependency relationships) and their completeness." + } + }, + "definitions": { + "metadata": { + "type": "object", + "title": "BOM Metadata Object", + "additionalProperties": false, + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "The date and time (timestamp) when the document was created." + }, + "tools": { + "type": "array", + "title": "Creation Tools", + "description": "The tool(s) used in the creation of the BOM.", + "items": {"$ref": "#/definitions/tool"} + }, + "authors" :{ + "type": "array", + "title": "Authors", + "description": "The person(s) who created the BOM. Authors are common in BOMs created through manual processes. BOMs created through automated means may not have authors.", + "items": {"$ref": "#/definitions/organizationalContact"} + }, + "component": { + "title": "Component", + "description": "The component that the BOM describes.", + "$ref": "#/definitions/component" + }, + "manufacture": { + "title": "Manufacture", + "description": "The organization that manufactured the component that the BOM describes.", + "$ref": "#/definitions/organizationalEntity" + }, + "supplier": { + "title": "Supplier", + "description": " The organization that supplied the component that the BOM describes. The supplier may often be the manufacturer, but may also be a distributor or repackager.", + "$ref": "#/definitions/organizationalEntity" + }, + "licenses": { + "type": "array", + "title": "BOM License(s)", + "items": {"$ref": "#/definitions/licenseChoice"} + }, + "properties": { + "type": "array", + "title": "Properties", + "description": "Provides the ability to document properties in a name-value store. This provides flexibility to include data not officially supported in the standard without having to use additional namespaces or create extensions. Unlike key-value stores, properties support duplicate names, each potentially having different values.", + "items": {"$ref": "#/definitions/property"} + } + } + }, + "tool": { + "type": "object", + "title": "Tool", + "description": "The tool used to create the BOM.", + "additionalProperties": false, + "properties": { + "vendor": { + "type": "string", + "title": "Tool Vendor", + "description": "The date and time (timestamp) when the document was created." + }, + "name": { + "type": "string", + "title": "Tool Name", + "description": "The date and time (timestamp) when the document was created." + }, + "version": { + "type": "string", + "title": "Tool Version", + "description": "The date and time (timestamp) when the document was created." + }, + "hashes": { + "$id": "#/properties/hashes", + "type": "array", + "items": {"$ref": "#/definitions/hash"}, + "title": "Hashes", + "description": "The hashes of the tool (if applicable)." + } + } + }, + "organizationalEntity": { + "type": "object", + "title": "Organizational Entity Object", + "description": "", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the organization", + "examples": [ + "Example Inc." + ] + }, + "url": { + "type": "array", + "items": { + "type": "string", + "format": "iri-reference" + }, + "title": "URL", + "description": "The URL of the organization. Multiple URLs are allowed.", + "examples": ["https://example.com"] + }, + "contact": { + "type": "array", + "title": "Contact", + "description": "A contact at the organization. Multiple contacts are allowed.", + "items": {"$ref": "#/definitions/organizationalContact"} + } + } + }, + "organizationalContact": { + "type": "object", + "title": "Organizational Contact Object", + "description": "", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of a contact", + "examples": ["Contact name"] + }, + "email": { + "type": "string", + "title": "Email Address", + "description": "The email address of the contact.", + "examples": ["firstname.lastname@example.com"] + }, + "phone": { + "type": "string", + "title": "Phone", + "description": "The phone number of the contact.", + "examples": ["800-555-1212"] + } + } + }, + "component": { + "type": "object", + "title": "Component Object", + "required": [ + "type", + "name", + "version" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "application", + "framework", + "library", + "container", + "operating-system", + "device", + "firmware", + "file" + ], + "title": "Component Type", + "description": "Specifies the type of component. For software components, classify as application if no more specific appropriate classification is available or cannot be determined for the component.", + "examples": ["library"] + }, + "mime-type": { + "type": "string", + "title": "Mime-Type", + "description": "The optional mime-type of the component. When used on file components, the mime-type can provide additional context about the kind of file being represented such as an image, font, or executable. Some library or framework components may also have an associated mime-type.", + "examples": ["image/jpeg"], + "pattern": "^[-+a-z0-9.]+/[-+a-z0-9.]+$" + }, + "bom-ref": { + "type": "string", + "title": "BOM Reference", + "description": "An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref should be unique." + }, + "supplier": { + "title": "Component Supplier", + "description": " The organization that supplied the component. The supplier may often be the manufacturer, but may also be a distributor or repackager.", + "$ref": "#/definitions/organizationalEntity" + }, + "author": { + "type": "string", + "title": "Component Author", + "description": "The person(s) or organization(s) that authored the component", + "examples": ["Acme Inc"] + }, + "publisher": { + "type": "string", + "title": "Component Publisher", + "description": "The person(s) or organization(s) that published the component", + "examples": ["Acme Inc"] + }, + "group": { + "type": "string", + "title": "Component Group", + "description": "The grouping name or identifier. This will often be a shortened, single name of the company or project that produced the component, or the source package or domain name. Whitespace and special characters should be avoided. Examples include: apache, org.apache.commons, and apache.org.", + "examples": ["com.acme"] + }, + "name": { + "type": "string", + "title": "Component Name", + "description": "The name of the component. This will often be a shortened, single name of the component. Examples: commons-lang3 and jquery", + "examples": ["tomcat-catalina"] + }, + "version": { + "type": "string", + "title": "Component Version", + "description": "The component version. The version should ideally comply with semantic versioning but is not enforced.", + "examples": ["9.0.14"] + }, + "description": { + "type": "string", + "title": "Component Description", + "description": "Specifies a description for the component" + }, + "scope": { + "type": "string", + "enum": [ + "required", + "optional", + "excluded" + ], + "title": "Component Scope", + "description": "Specifies the scope of the component. If scope is not specified, 'required' scope should be assumed by the consumer of the BOM", + "default": "required" + }, + "hashes": { + "type": "array", + "title": "Component Hashes", + "items": {"$ref": "#/definitions/hash"} + }, + "licenses": { + "type": "array", + "items": {"$ref": "#/definitions/licenseChoice"}, + "title": "Component License(s)" + }, + "copyright": { + "type": "string", + "title": "Component Copyright", + "description": "An optional copyright notice informing users of the underlying claims to copyright ownership in a published work.", + "examples": ["Acme Inc"] + }, + "cpe": { + "type": "string", + "title": "Component Common Platform Enumeration (CPE)", + "description": "DEPRECATED - DO NOT USE. This will be removed in a future version. Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe", + "examples": ["cpe:2.3:a:acme:component_framework:-:*:*:*:*:*:*:*"] + }, + "purl": { + "type": "string", + "title": "Component Package URL (purl)", + "examples": ["pkg:maven/com.acme/tomcat-catalina@9.0.14?packaging=jar"] + }, + "swid": { + "$ref": "#/definitions/swid", + "title": "SWID Tag", + "description": "Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags." + }, + "modified": { + "type": "boolean", + "title": "Component Modified From Original", + "description": "DEPRECATED - DO NOT USE. This will be removed in a future version. Use the pedigree element instead to supply information on exactly how the component was modified. A boolean value indicating is the component has been modified from the original. A value of true indicates the component is a derivative of the original. A value of false indicates the component has not been modified from the original." + }, + "pedigree": { + "type": "object", + "title": "Component Pedigree", + "description": "Component pedigree is a way to document complex supply chain scenarios where components are created, distributed, modified, redistributed, combined with other components, etc. Pedigree supports viewing this complex chain from the beginning, the end, or anywhere in the middle. It also provides a way to document variants where the exact relation may not be known.", + "additionalProperties": false, + "properties": { + "ancestors": { + "type": "array", + "title": "Ancestors", + "description": "Describes zero or more components in which a component is derived from. This is commonly used to describe forks from existing projects where the forked version contains a ancestor node containing the original component it was forked from. For example, Component A is the original component. Component B is the component being used and documented in the BOM. However, Component B contains a pedigree node with a single ancestor documenting Component A - the original component from which Component B is derived from.", + "items": {"$ref": "#/definitions/component"} + }, + "descendants": { + "type": "array", + "title": "Descendants", + "description": "Descendants are the exact opposite of ancestors. This provides a way to document all forks (and their forks) of an original or root component.", + "items": {"$ref": "#/definitions/component"} + }, + "variants": { + "type": "array", + "title": "Variants", + "description": "Variants describe relations where the relationship between the components are not known. For example, if Component A contains nearly identical code to Component B. They are both related, but it is unclear if one is derived from the other, or if they share a common ancestor.", + "items": {"$ref": "#/definitions/component"} + }, + "commits": { + "type": "array", + "title": "Commits", + "description": "A list of zero or more commits which provide a trail describing how the component deviates from an ancestor, descendant, or variant.", + "items": {"$ref": "#/definitions/commit"} + }, + "patches": { + "type": "array", + "title": "Patches", + "description": ">A list of zero or more patches describing how the component deviates from an ancestor, descendant, or variant. Patches may be complimentary to commits or may be used in place of commits.", + "items": {"$ref": "#/definitions/patch"} + }, + "notes": { + "type": "string", + "title": "Notes", + "description": "Notes, observations, and other non-structured commentary describing the components pedigree." + } + } + }, + "externalReferences": { + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References" + }, + "components": { + "$id": "#/properties/components", + "type": "array", + "items": {"$ref": "#/definitions/component"}, + "uniqueItems": true, + "title": "Components" + }, + "evidence": { + "$ref": "#/definitions/componentEvidence", + "title": "Evidence", + "description": "Provides the ability to document evidence collected through various forms of extraction or analysis." + }, + "properties": { + "type": "array", + "title": "Properties", + "description": "Provides the ability to document properties in a name-value store. This provides flexibility to include data not officially supported in the standard without having to use additional namespaces or create extensions. Unlike key-value stores, properties support duplicate names, each potentially having different values.", + "items": {"$ref": "#/definitions/property"} + } + } + }, + "swid": { + "type": "object", + "title": "SWID Tag", + "description": "Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags.", + "required": [ + "tagId", + "name" + ], + "additionalProperties": false, + "properties": { + "tagId": { + "type": "string", + "title": "Tag ID", + "description": "Maps to the tagId of a SoftwareIdentity." + }, + "name": { + "type": "string", + "title": "Name", + "description": "Maps to the name of a SoftwareIdentity." + }, + "version": { + "type": "string", + "title": "Version", + "default": "0.0", + "description": "Maps to the version of a SoftwareIdentity." + }, + "tagVersion": { + "type": "integer", + "title": "Tag Version", + "default": 0, + "description": "Maps to the tagVersion of a SoftwareIdentity." + }, + "patch": { + "type": "boolean", + "title": "Patch", + "default": false, + "description": "Maps to the patch of a SoftwareIdentity." + }, + "text": { + "title": "Attachment text", + "description": "Specifies the metadata and content of the SWID tag.", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the SWID file.", + "format": "iri-reference" + } + } + }, + "attachment": { + "type": "object", + "title": "Attachment", + "description": "Specifies the metadata and content for an attachment.", + "required": [ + "content" + ], + "additionalProperties": false, + "properties": { + "contentType": { + "type": "string", + "title": "Content-Type", + "description": "Specifies the content type of the text. Defaults to text/plain if not specified.", + "default": "text/plain" + }, + "encoding": { + "type": "string", + "title": "Encoding", + "description": "Specifies the optional encoding the text is represented in.", + "enum": [ + "base64" + ] + }, + "content": { + "type": "string", + "title": "Attachment Text", + "description": "The attachment data" + } + } + }, + "hash": { + "type": "object", + "title": "Hash Objects", + "required": [ + "alg", + "content" + ], + "additionalProperties": false, + "properties": { + "alg": { + "$ref": "#/definitions/hash-alg" + }, + "content": { + "$ref": "#/definitions/hash-content" + } + } + }, + "hash-alg": { + "type": "string", + "enum": [ + "MD5", + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512", + "SHA3-256", + "SHA3-384", + "SHA3-512", + "BLAKE2b-256", + "BLAKE2b-384", + "BLAKE2b-512", + "BLAKE3" + ], + "title": "Hash Algorithm" + }, + "hash-content": { + "type": "string", + "title": "Hash Content (value)", + "examples": ["3942447fac867ae5cdb3229b658f4d48"], + "pattern": "^([a-fA-F0-9]{32}|[a-fA-F0-9]{40}|[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128})$" + }, + "license": { + "type": "object", + "title": "License Object", + "oneOf": [ + { + "required": ["id"] + }, + { + "required": ["name"] + } + ], + "additionalProperties": false, + "properties": { + "id": { + "$ref": "spdx.schema.json", + "title": "License ID (SPDX)", + "description": "A valid SPDX license ID", + "examples": ["Apache-2.0"] + }, + "name": { + "type": "string", + "title": "License Name", + "description": "If SPDX does not define the license used, this field may be used to provide the license name", + "examples": ["Acme Software License"] + }, + "text": { + "title": "License text", + "description": "An optional way to include the textual content of a license.", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "License URL", + "description": "The URL to the license file. If specified, a 'license' externalReference should also be specified for completeness", + "examples": ["https://www.apache.org/licenses/LICENSE-2.0.txt"], + "format": "iri-reference" + } + } + }, + "licenseChoice": { + "type": "object", + "title": "License(s)", + "additionalProperties": false, + "properties": { + "license": { + "$ref": "#/definitions/license" + }, + "expression": { + "type": "string", + "title": "SPDX License Expression", + "examples": [ + "Apache-2.0 AND (MIT OR GPL-2.0-only)", + "GPL-3.0-only WITH Classpath-exception-2.0" + ] + } + }, + "oneOf":[ + { + "required": ["license"] + }, + { + "required": ["expression"] + } + ] + }, + "commit": { + "type": "object", + "title": "Commit", + "description": "Specifies an individual commit", + "additionalProperties": false, + "properties": { + "uid": { + "type": "string", + "title": "UID", + "description": "A unique identifier of the commit. This may be version control specific. For example, Subversion uses revision numbers whereas git uses commit hashes." + }, + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the commit. This URL will typically point to a commit in a version control system.", + "format": "iri-reference" + }, + "author": { + "title": "Author", + "description": "The author who created the changes in the commit", + "$ref": "#/definitions/identifiableAction" + }, + "committer": { + "title": "Committer", + "description": "The person who committed or pushed the commit", + "$ref": "#/definitions/identifiableAction" + }, + "message": { + "type": "string", + "title": "Message", + "description": "The text description of the contents of the commit" + } + } + }, + "patch": { + "type": "object", + "title": "Patch", + "description": "Specifies an individual patch", + "required": [ + "type" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "unofficial", + "monkey", + "backport", + "cherry-pick" + ], + "title": "Type", + "description": "Specifies the purpose for the patch including the resolution of defects, security issues, or new behavior or functionality" + }, + "diff": { + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "$ref": "#/definitions/diff" + }, + "resolves": { + "type": "array", + "items": {"$ref": "#/definitions/issue"}, + "title": "Resolves", + "description": "A collection of issues the patch resolves" + } + } + }, + "diff": { + "type": "object", + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "additionalProperties": false, + "properties": { + "text": { + "title": "Diff text", + "description": "Specifies the optional text of the diff", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "URL", + "description": "Specifies the URL to the diff", + "format": "iri-reference" + } + } + }, + "issue": { + "type": "object", + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "required": [ + "type" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "defect", + "enhancement", + "security" + ], + "title": "Type", + "description": "Specifies the type of issue" + }, + "id": { + "type": "string", + "title": "ID", + "description": "The identifier of the issue assigned by the source of the issue" + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the issue" + }, + "description": { + "type": "string", + "title": "Description", + "description": "A description of the issue" + }, + "source": { + "type": "object", + "title": "Source", + "description": "The source of the issue where it is documented", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the source. For example 'National Vulnerability Database', 'NVD', and 'Apache'" + }, + "url": { + "type": "string", + "title": "URL", + "description": "The url of the issue documentation as provided by the source", + "format": "iri-reference" + } + } + }, + "references": { + "type": "array", + "items": { + "type": "string", + "format": "iri-reference" + }, + "title": "References", + "description": "A collection of URL's for reference. Multiple URLs are allowed.", + "examples": ["https://example.com"] + } + } + }, + "identifiableAction": { + "type": "object", + "title": "Identifiable Action", + "description": "Specifies an individual commit", + "additionalProperties": false, + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "The timestamp in which the action occurred" + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the individual who performed the action" + }, + "email": { + "type": "string", + "format": "idn-email", + "title": "E-mail", + "description": "The email address of the individual who performed the action" + } + } + }, + "externalReference": { + "type": "object", + "title": "External Reference", + "description": "Specifies an individual external reference", + "required": [ + "url", + "type" + ], + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the external reference", + "format": "iri-reference" + }, + "comment": { + "type": "string", + "title": "Comment", + "description": "An optional comment describing the external reference" + }, + "type": { + "type": "string", + "title": "Type", + "description": "Specifies the type of external reference. There are built-in types to describe common references. If a type does not exist for the reference being referred to, use the \"other\" type.", + "enum": [ + "vcs", + "issue-tracker", + "website", + "advisories", + "bom", + "mailing-list", + "social", + "chat", + "documentation", + "support", + "distribution", + "license", + "build-meta", + "build-system", + "other" + ] + }, + "hashes": { + "$id": "#/properties/hashes", + "type": "array", + "items": {"$ref": "#/definitions/hash"}, + "title": "Hashes", + "description": "The hashes of the external reference (if applicable)." + } + } + }, + "dependency": { + "type": "object", + "title": "Dependency", + "description": "Defines the direct dependencies of a component. Components that do not have their own dependencies MUST be declared as empty elements within the graph. Components that are not represented in the dependency graph MAY have unknown dependencies. It is RECOMMENDED that implementations assume this to be opaque and not an indicator of a component being dependency-free.", + "required": [ + "ref" + ], + "additionalProperties": false, + "properties": { + "ref": { + "type": "string", + "title": "Reference", + "description": "References a component by the components bom-ref attribute" + }, + "dependsOn": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "title": "Depends On", + "description": "The bom-ref identifiers of the components that are dependencies of this dependency object." + } + } + }, + "service": { + "type": "object", + "title": "Service Object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "bom-ref": { + "type": "string", + "title": "BOM Reference", + "description": "An optional identifier which can be used to reference the service elsewhere in the BOM. Every bom-ref should be unique." + }, + "provider": { + "title": "Provider", + "description": "The organization that provides the service.", + "$ref": "#/definitions/organizationalEntity" + }, + "group": { + "type": "string", + "title": "Service Group", + "description": "The grouping name, namespace, or identifier. This will often be a shortened, single name of the company or project that produced the service or domain name. Whitespace and special characters should be avoided.", + "examples": ["com.acme"] + }, + "name": { + "type": "string", + "title": "Service Name", + "description": "The name of the service. This will often be a shortened, single name of the service.", + "examples": ["ticker-service"] + }, + "version": { + "type": "string", + "title": "Service Version", + "description": "The service version.", + "examples": ["1.0.0"] + }, + "description": { + "type": "string", + "title": "Service Description", + "description": "Specifies a description for the service" + }, + "endpoints": { + "type": "array", + "items": { + "type": "string", + "format": "iri-reference" + }, + "title": "Endpoints", + "description": "The endpoint URIs of the service. Multiple endpoints are allowed.", + "examples": ["https://example.com/api/v1/ticker"] + }, + "authenticated": { + "type": "boolean", + "title": "Authentication Required", + "description": "A boolean value indicating if the service requires authentication. A value of true indicates the service requires authentication prior to use. A value of false indicates the service does not require authentication." + }, + "x-trust-boundary": { + "type": "boolean", + "title": "Crosses Trust Boundary", + "description": "A boolean value indicating if use of the service crosses a trust zone or boundary. A value of true indicates that by using the service, a trust boundary is crossed. A value of false indicates that by using the service, a trust boundary is not crossed." + }, + "data": { + "type": "array", + "items": {"$ref": "#/definitions/dataClassification"}, + "title": "Data Classification", + "description": "Specifies the data classification." + }, + "licenses": { + "type": "array", + "items": {"$ref": "#/definitions/licenseChoice"}, + "title": "Component License(s)" + }, + "externalReferences": { + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References" + }, + "services": { + "$id": "#/properties/services", + "type": "array", + "items": {"$ref": "#/definitions/service"}, + "uniqueItems": true, + "title": "Services" + }, + "properties": { + "type": "array", + "title": "Properties", + "description": "Provides the ability to document properties in a name-value store. This provides flexibility to include data not officially supported in the standard without having to use additional namespaces or create extensions. Unlike key-value stores, properties support duplicate names, each potentially having different values.", + "items": {"$ref": "#/definitions/property"} + } + } + }, + "dataClassification": { + "type": "object", + "title": "Hash Objects", + "required": [ + "flow", + "classification" + ], + "additionalProperties": false, + "properties": { + "flow": { + "$ref": "#/definitions/dataFlow" + }, + "classification": { + "type": "string" + } + } + }, + "dataFlow": { + "type": "string", + "enum": [ + "inbound", + "outbound", + "bi-directional", + "unknown" + ], + "title": "Data flow direction" + }, + + "copyright": { + "type": "object", + "title": "Copyright", + "required": [ + "text" + ], + "additionalProperties": false, + "properties": { + "text": { + "type": "string", + "title": "Copyright Text" + } + } + }, + + "componentEvidence": { + "type": "object", + "title": "Evidence", + "description": "Provides the ability to document evidence collected through various forms of extraction or analysis.", + "additionalProperties": false, + "properties": { + "licenses": { + "type": "array", + "items": {"$ref": "#/definitions/licenseChoice"}, + "title": "Component License(s)" + }, + "copyright": { + "type": "array", + "items": {"$ref": "#/definitions/copyright"}, + "title": "Copyright" + } + } + }, + "compositions": { + "type": "object", + "title": "Compositions", + "required": [ + "aggregate" + ], + "additionalProperties": false, + "properties": { + "aggregate": { + "$ref": "#/definitions/aggregateType", + "title": "Aggregate", + "description": "Specifies an aggregate type that describe how complete a relationship is." + }, + "assemblies": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "title": "BOM references", + "description": "The bom-ref identifiers of the components or services being described. Assemblies refer to nested relationships whereby a constituent part may include other constituent parts. References do not cascade to child parts. References are explicit for the specified constituent part only." + }, + "dependencies": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "title": "BOM references", + "description": "The bom-ref identifiers of the components or services being described. Dependencies refer to a relationship whereby an independent constituent part requires another independent constituent part. References do not cascade to transitive dependencies. References are explicit for the specified dependency only." + } + } + }, + "aggregateType": { + "type": "string", + "default": "not_specified", + "enum": [ + "complete", + "incomplete", + "incomplete_first_party_only", + "incomplete_third_party_only", + "unknown", + "not_specified" + ] + }, + "property": { + "type": "object", + "title": "Lightweight name-value pair", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the property. Duplicate names are allowed, each potentially having a different value." + }, + "value": { + "type": "string", + "title": "Value", + "description": "The value of the property." + } + } + } + } +} diff --git a/cyclonedx/schema/bom-1.3.proto b/cyclonedx/schema/bom-1.3.proto new file mode 100644 index 00000000..5c9926b2 --- /dev/null +++ b/cyclonedx/schema/bom-1.3.proto @@ -0,0 +1,452 @@ +syntax = "proto3"; +package cyclonedx.v1_3; +import "google/protobuf/timestamp.proto"; + +// Specifies attributes of the text +message AttachedText { + // Specifies the content type of the text. Defaults to text/plain if not specified. + optional string content_type = 1; + // Specifies the optional encoding the text is represented in + optional string encoding = 2; + // SimpleContent value of element + string value = 3; +} + +message Bom { + // The version of the CycloneDX specification a BOM is written to (starting at version 1.3) + string spec_version = 1; + // The version allows component publishers/authors to make changes to existing BOMs to update various aspects of the document such as description or licenses. When a system is presented with multiple BOMs for the same component, the system should use the most recent version of the BOM. The default version is '1' and should be incremented for each version of the BOM that is published. Each version of a component should have a unique BOM and if no changes are made to the BOMs, then each BOM will have a version of '1'. + optional int32 version = 2; + // Every BOM generated should have a unique serial number, even if the contents of the BOM being generated have not changed over time. The process or tool responsible for creating the BOM should create random UUID's for every BOM generated. + optional string serial_number = 3; + // Provides additional information about a BOM. + optional Metadata metadata = 4; + // Provides the ability to document a list of components. + repeated Component components = 5; + // Provides the ability to document a list of external services. + repeated Service services = 6; + // Provides the ability to document external references related to the BOM or to the project the BOM describes. + repeated ExternalReference external_references = 7; + // Provides the ability to document dependency relationships. + repeated Dependency dependencies = 8; + // Provides the ability to document aggregate completeness + repeated Composition compositions = 9; +} + +enum Classification { + CLASSIFICATION_NULL = 0; + // A software application. Refer to https://en.wikipedia.org/wiki/Application_software for information about applications. + CLASSIFICATION_APPLICATION = 1; + // A software framework. Refer to https://en.wikipedia.org/wiki/Software_framework for information on how frameworks vary slightly from libraries. + CLASSIFICATION_FRAMEWORK = 2; + // A software library. Refer to https://en.wikipedia.org/wiki/Library_(computing) for information about libraries. All third-party and open source reusable components will likely be a library. If the library also has key features of a framework, then it should be classified as a framework. If not, or is unknown, then specifying library is recommended. + CLASSIFICATION_LIBRARY = 3; + // A software operating system without regard to deployment model (i.e. installed on physical hardware, virtual machine, image, etc) Refer to https://en.wikipedia.org/wiki/Operating_system + CLASSIFICATION_OPERATING_SYSTEM = 4; + // A hardware device such as a processor, or chip-set. A hardware device containing firmware should include a component for the physical hardware itself, and another component of type 'firmware' or 'operating-system' (whichever is relevant), describing information about the software running on the device. + CLASSIFICATION_DEVICE = 5; + // A computer file. Refer to https://en.wikipedia.org/wiki/Computer_file for information about files. + CLASSIFICATION_FILE = 6; + // A packaging and/or runtime format, not specific to any particular technology, which isolates software inside the container from software outside of a container through virtualization technology. Refer to https://en.wikipedia.org/wiki/OS-level_virtualization + CLASSIFICATION_CONTAINER = 7; + // A special type of software that provides low-level control over a devices hardware. Refer to https://en.wikipedia.org/wiki/Firmware + CLASSIFICATION_FIRMWARE = 8; +} + +message Commit { + // A unique identifier of the commit. This may be version control specific. For example, Subversion uses revision numbers whereas git uses commit hashes. + optional string uid = 1; + // The URL to the commit. This URL will typically point to a commit in a version control system. + optional string url = 2; + // The author who created the changes in the commit + optional IdentifiableAction author = 3; + // The person who committed or pushed the commit + optional IdentifiableAction committer = 4; + // The text description of the contents of the commit + optional string message = 5; +} + +message Component { + // Specifies the type of component. For software components, classify as application if no more specific appropriate classification is available or cannot be determined for the component. + Classification type = 1; + // The optional mime-type of the component. When used on file components, the mime-type can provide additional context about the kind of file being represented such as an image, font, or executable. Some library or framework components may also have an associated mime-type. + optional string mime_type = 2; + // An optional identifier which can be used to reference the component elsewhere in the BOM. Uniqueness is enforced within all elements and children of the root-level bom element. + optional string bom_ref = 3; + // The organization that supplied the component. The supplier may often be the manufacture, but may also be a distributor or repackager. + optional OrganizationalEntity supplier = 4; + // The person(s) or organization(s) that authored the component + optional string author = 5; + // The person(s) or organization(s) that published the component + optional string publisher = 6; + // The grouping name or identifier. This will often be a shortened, single name of the company or project that produced the component, or the source package or domain name. Whitespace and special characters should be avoided. Examples include: apache, org.apache.commons, and apache.org. + optional string group = 7; + // The name of the component. This will often be a shortened, single name of the component. Examples: commons-lang3 and jquery + string name = 8; + // The component version. The version should ideally comply with semantic versioning but is not enforced. + string version = 9; + // Specifies a description for the component + optional string description = 10; + // Specifies the scope of the component. If scope is not specified, 'runtime' scope should be assumed by the consumer of the BOM + optional Scope scope = 11; + repeated Hash hashes = 12; + repeated LicenseChoice licenses = 13; + // An optional copyright notice informing users of the underlying claims to copyright ownership in a published work. + optional string copyright = 14; + // DEPRECATED - DO NOT USE. This will be removed in a future version. Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe + optional string cpe = 15; + // Specifies the package-url (PURL). The purl, if specified, must be valid and conform to the specification defined at: https://github.com/package-url/purl-spec + optional string purl = 16; + // Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags. + optional Swid swid = 17; + // DEPRECATED - DO NOT USE. This will be removed in a future version. Use the pedigree element instead to supply information on exactly how the component was modified. A boolean value indicating is the component has been modified from the original. A value of true indicates the component is a derivative of the original. A value of false indicates the component has not been modified from the original. + optional bool modified = 18; + // Component pedigree is a way to document complex supply chain scenarios where components are created, distributed, modified, redistributed, combined with other components, etc. + optional Pedigree pedigree = 19; + // Provides the ability to document external references related to the component or to the project the component describes. + repeated ExternalReference external_references = 20; + // Specifies optional sub-components. This is not a dependency tree. It provides a way to specify a hierarchical representation of component assemblies, similar to system -> subsystem -> parts assembly in physical supply chains. + repeated Component components = 21; + // Specifies optional, custom, properties + repeated Property properties = 22; + // Specifies optional license and copyright evidence + repeated Evidence evidence = 23; +} + +// Specifies the data classification. +message DataClassification { + // Specifies the flow direction of the data. + DataFlow flow = 1; + // SimpleContent value of element + string value = 2; +} + +// Specifies the flow direction of the data. Valid values are: inbound, outbound, bi-directional, and unknown. Direction is relative to the service. Inbound flow states that data enters the service. Outbound flow states that data leaves the service. Bi-directional states that data flows both ways, and unknown states that the direction is not known. +enum DataFlow { + DATA_FLOW_NULL = 0; + DATA_FLOW_INBOUND = 1; + DATA_FLOW_OUTBOUND = 2; + DATA_FLOW_BI_DIRECTIONAL = 3; + DATA_FLOW_UNKNOWN = 4; +} + +message Dependency { + // References a component or service by the its bom-ref attribute + string ref = 1; + repeated Dependency dependencies = 2; +} + +message Diff { + // Specifies the optional text of the diff + optional AttachedText text = 1; + // Specifies the URL to the diff + optional string url = 2; +} + +message ExternalReference { + // Specifies the type of external reference. There are built-in types to describe common references. If a type does not exist for the reference being referred to, use the "other" type. + ExternalReferenceType type = 1; + // The URL to the external reference + string url = 2; + // An optional comment describing the external reference + optional string comment = 3; + // Optional integrity hashes for the external resource content + repeated Hash hashes = 4; +} + +enum ExternalReferenceType { + // Use this if no other types accurately describe the purpose of the external reference + EXTERNAL_REFERENCE_TYPE_OTHER = 0; + // Version Control System + EXTERNAL_REFERENCE_TYPE_VCS = 1; + // Issue or defect tracking system, or an Application Lifecycle Management (ALM) system + EXTERNAL_REFERENCE_TYPE_ISSUE_TRACKER = 2; + // Website + EXTERNAL_REFERENCE_TYPE_WEBSITE = 3; + // Security advisories + EXTERNAL_REFERENCE_TYPE_ADVISORIES = 4; + // Bill-of-material document (CycloneDX, SPDX, SWID, etc) + EXTERNAL_REFERENCE_TYPE_BOM = 5; + // Mailing list or discussion group + EXTERNAL_REFERENCE_TYPE_MAILING_LIST = 6; + // Social media account + EXTERNAL_REFERENCE_TYPE_SOCIAL = 7; + // Real-time chat platform + EXTERNAL_REFERENCE_TYPE_CHAT = 8; + // Documentation, guides, or how-to instructions + EXTERNAL_REFERENCE_TYPE_DOCUMENTATION = 9; + // Community or commercial support + EXTERNAL_REFERENCE_TYPE_SUPPORT = 10; + // Direct or repository download location + EXTERNAL_REFERENCE_TYPE_DISTRIBUTION = 11; + // The URL to the license file. If a license URL has been defined in the license node, it should also be defined as an external reference for completeness + EXTERNAL_REFERENCE_TYPE_LICENSE = 12; + // Build-system specific meta file (i.e. pom.xml, package.json, .nuspec, etc) + EXTERNAL_REFERENCE_TYPE_BUILD_META = 13; + // URL to an automated build system + EXTERNAL_REFERENCE_TYPE_BUILD_SYSTEM = 14; +} + +enum HashAlg { + HASH_ALG_NULL = 0; + HASH_ALG_MD_5 = 1; + HASH_ALG_SHA_1 = 2; + HASH_ALG_SHA_256 = 3; + HASH_ALG_SHA_384 = 4; + HASH_ALG_SHA_512 = 5; + HASH_ALG_SHA_3_256 = 6; + HASH_ALG_SHA_3_384 = 7; + HASH_ALG_SHA_3_512 = 8; + HASH_ALG_BLAKE_2_B_256 = 9; + HASH_ALG_BLAKE_2_B_384 = 10; + HASH_ALG_BLAKE_2_B_512 = 11; + HASH_ALG_BLAKE_3 = 12; +} + +// Specifies the file hash of the component +message Hash { + // Specifies the algorithm used to create the hash + HashAlg alg = 1; + // SimpleContent value of element + string value = 2; +} + +message IdentifiableAction { + // The timestamp in which the action occurred + optional google.protobuf.Timestamp timestamp = 1; + // The name of the individual who performed the action + optional string name = 2; + // The email address of the individual who performed the action + optional string email = 3; +} + +enum IssueClassification { + ISSUE_CLASSIFICATION_NULL = 0; + // A fault, flaw, or bug in software + ISSUE_CLASSIFICATION_DEFECT = 1; + // A new feature or behavior in software + ISSUE_CLASSIFICATION_ENHANCEMENT = 2; + // A special type of defect which impacts security + ISSUE_CLASSIFICATION_SECURITY = 3; +} + +message Issue { + // Specifies the type of issue + IssueClassification type = 1; + // The identifier of the issue assigned by the source of the issue + optional string id = 2; + // The name of the issue + optional string name = 3; + // A description of the issue + optional string description = 4; + optional Source source = 5; + repeated string references = 6; +} + +// The source of the issue where it is documented. +message Source { + // The name of the source. For example "National Vulnerability Database", "NVD", and "Apache" + optional string name = 1; + // The url of the issue documentation as provided by the source + optional string url = 2; +} + +message LicenseChoice { + oneof choice { + License license = 1; + string expression = 2; + } +} + +message License { + oneof license { + // A valid SPDX license ID + string id = 1; + // If SPDX does not define the license used, this field may be used to provide the license name + string name = 2; + } + // Specifies the optional full text of the attachment + optional AttachedText text = 3; + // The URL to the attachment file. If the attachment is a license or BOM, an externalReference should also be specified for completeness. + optional string url = 4; +} + +message Metadata { + // The date and time (timestamp) when the document was created. + optional google.protobuf.Timestamp timestamp = 1; + // The tool(s) used in the creation of the BOM. + repeated Tool tools = 2; + // The person(s) who created the BOM. Authors are common in BOMs created through manual processes. BOMs created through automated means may not have authors. + repeated OrganizationalContact authors = 3; + // The component that the BOM describes. + optional Component component = 4; + // The organization that manufactured the component that the BOM describes. + optional OrganizationalEntity manufacture = 5; + // The organization that supplied the component that the BOM describes. The supplier may often be the manufacture, but may also be a distributor or repackager. + optional OrganizationalEntity supplier = 6; + // The license information for the BOM document + optional LicenseChoice licenses = 7; + // Specifies optional, custom, properties + repeated Property properties = 8; +} + +message OrganizationalContact { + // The name of the contact + optional string name = 1; + // The email address of the contact. + optional string email = 2; + // The phone number of the contact. + optional string phone = 3; +} + +message OrganizationalEntity { + // The name of the organization + optional string name = 1; + // The URL of the organization. Multiple URLs are allowed. + repeated string url = 2; + // A contact person at the organization. Multiple contacts are allowed. + repeated OrganizationalContact contact = 3; +} + +enum PatchClassification { + PATCH_CLASSIFICATION_NULL = 0; + // A patch which is not developed by the creators or maintainers of the software being patched. Refer to https://en.wikipedia.org/wiki/Unofficial_patch + PATCH_CLASSIFICATION_UNOFFICIAL = 1; + // A patch which dynamically modifies runtime behavior. Refer to https://en.wikipedia.org/wiki/Monkey_patch + PATCH_CLASSIFICATION_MONKEY = 2; + // A patch which takes code from a newer version of software and applies it to older versions of the same software. Refer to https://en.wikipedia.org/wiki/Backporting + PATCH_CLASSIFICATION_BACKPORT = 3; + // A patch created by selectively applying commits from other versions or branches of the same software. + PATCH_CLASSIFICATION_CHERRY_PICK = 4; +} + +message Patch { + // Specifies the purpose for the patch including the resolution of defects, security issues, or new behavior or functionality + PatchClassification type = 1; + // The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff + optional Diff diff = 2; + repeated Issue resolves = 3; +} + +// Component pedigree is a way to document complex supply chain scenarios where components are created, distributed, modified, redistributed, combined with other components, etc. Pedigree supports viewing this complex chain from the beginning, the end, or anywhere in the middle. It also provides a way to document variants where the exact relation may not be known. +message Pedigree { + // Describes zero or more components in which a component is derived from. This is commonly used to describe forks from existing projects where the forked version contains a ancestor node containing the original component it was forked from. For example, Component A is the original component. Component B is the component being used and documented in the BOM. However, Component B contains a pedigree node with a single ancestor documenting Component A - the original component from which Component B is derived from. + repeated Component ancestors = 1; + // Descendants are the exact opposite of ancestors. This provides a way to document all forks (and their forks) of an original or root component. + repeated Component descendants = 2; + // Variants describe relations where the relationship between the components are not known. For example, if Component A contains nearly identical code to Component B. They are both related, but it is unclear if one is derived from the other, or if they share a common ancestor. + repeated Component variants = 3; + // A list of zero or more commits which provide a trail describing how the component deviates from an ancestor, descendant, or variant. + repeated Commit commits = 4; + // A list of zero or more patches describing how the component deviates from an ancestor, descendant, or variant. Patches may be complimentary to commits or may be used in place of commits. + repeated Patch patches = 5; + // Notes, observations, and other non-structured commentary describing the components pedigree. + optional string notes = 6; +} + +enum Scope { + // Default + SCOPE_UNSPECIFIED = 0; + // The component is required for runtime + SCOPE_REQUIRED = 1; + // The component is optional at runtime. Optional components are components that are not capable of being called due to them not be installed or otherwise accessible by any means. Components that are installed but due to configuration or other restrictions are prohibited from being called must be scoped as 'required'. + SCOPE_OPTIONAL = 2; + // Components that are excluded provide the ability to document component usage for test and other non-runtime purposes. Excluded components are not reachable within a call graph at runtime. + SCOPE_EXCLUDED = 3; +} + +message Service { + // An optional identifier which can be used to reference the service elsewhere in the BOM. Uniqueness is enforced within all elements and children of the root-level bom element. + optional string bom_ref = 1; + // The organization that provides the service. + optional OrganizationalEntity provider = 2; + // The grouping name, namespace, or identifier. This will often be a shortened, single name of the company or project that produced the service or domain name. Whitespace and special characters should be avoided. + optional string group = 3; + // The name of the service. This will often be a shortened, single name of the service. + string name = 4; + // The service version. + optional string version = 5; + // Specifies a description for the service. + optional string description = 6; + repeated string endpoints = 7; + // A boolean value indicating if the service requires authentication. A value of true indicates the service requires authentication prior to use. A value of false indicates the service does not require authentication. + optional bool authenticated = 8; + // A boolean value indicating if use of the service crosses a trust zone or boundary. A value of true indicates that by using the service, a trust boundary is crossed. A value of false indicates that by using the service, a trust boundary is not crossed. + optional bool x_trust_boundary = 9; + repeated DataClassification data = 10; + repeated LicenseChoice licenses = 11; + // Provides the ability to document external references related to the service. + repeated ExternalReference external_references = 12; + // Specifies optional sub-service. This is not a dependency tree. It provides a way to specify a hierarchical representation of service assemblies, similar to system -> subsystem -> parts assembly in physical supply chains. + repeated Service services = 13; + // Specifies optional, custom, properties + repeated Property properties = 14; +} + +message Swid { + // Maps to the tagId of a SoftwareIdentity. + string tag_id = 1; + // Maps to the name of a SoftwareIdentity. + string name = 2; + // Maps to the version of a SoftwareIdentity. + optional string version = 3; + // Maps to the tagVersion of a SoftwareIdentity. + optional int32 tag_version = 4; + // Maps to the patch of a SoftwareIdentity. + optional bool patch = 5; + // Specifies the full content of the SWID tag. + optional AttachedText text = 6; + // The URL to the SWID file. + optional string url = 7; +} + +// Specifies a tool (manual or automated). +message Tool { + // The vendor of the tool used to create the BOM. + optional string vendor = 1; + // The name of the tool used to create the BOM. + optional string name = 2; + // The version of the tool used to create the BOM. + optional string version = 3; + repeated Hash hashes = 4; +} + +// Specifies a property +message Property { + string name = 1; + optional string value = 2; +} + +enum Aggregate { + // Default, no statement about the aggregate completeness is being made + AGGREGATE_NOT_SPECIFIED = 0; + // The aggregate composition is complete + AGGREGATE_COMPLETE = 1; + // The aggregate composition is incomplete + AGGREGATE_INCOMPLETE = 2; + // The aggregate composition is incomplete for first party components, complete for third party components + AGGREGATE_INCOMPLETE_FIRST_PARTY_ONLY = 3; + // The aggregate composition is incomplete for third party components, complete for first party components + AGGREGATE_INCOMPLETE_THIRD_PARTY_ONLY = 4; + // The aggregate composition completeness is unknown + AGGREGATE_UNKNOWN = 5; +} + +message Composition { + // Indicates the aggregate completeness + Aggregate aggregate = 1; + // The assemblies the aggregate completeness applies to + repeated string assemblies = 2; + // The dependencies the aggregate completeness applies to + repeated string dependencies = 3; +} + +message EvidenceCopyright { + // Copyright text + string text = 1; +} + +message Evidence { + repeated LicenseChoice licenses = 1; + repeated EvidenceCopyright copyright = 2; +} diff --git a/cyclonedx/schema/bom-1.3.schema.json b/cyclonedx/schema/bom-1.3.schema.json new file mode 100644 index 00000000..fdec9736 --- /dev/null +++ b/cyclonedx/schema/bom-1.3.schema.json @@ -0,0 +1,1054 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://cyclonedx.org/schema/bom-1.3.schema.json", + "type": "object", + "title": "CycloneDX Software Bill-of-Material Specification", + "$comment" : "CycloneDX JSON schema is published under the terms of the Apache License 2.0.", + "required": [ + "bomFormat", + "specVersion", + "version" + ], + "properties": { + "bomFormat": { + "$id": "#/properties/bomFormat", + "type": "string", + "title": "BOM Format", + "description": "Specifies the format of the BOM. This helps to identify the file as CycloneDX since BOMs do not have a filename convention nor does JSON schema support namespaces.", + "enum": [ + "CycloneDX" + ] + }, + "specVersion": { + "$id": "#/properties/specVersion", + "type": "string", + "title": "CycloneDX Specification Version", + "description": "The version of the CycloneDX specification a BOM is written to (starting at version 1.2)", + "examples": ["1.3"] + }, + "serialNumber": { + "$id": "#/properties/serialNumber", + "type": "string", + "title": "BOM Serial Number", + "description": "Every BOM generated should have a unique serial number, even if the contents of the BOM being generated have not changed over time. The process or tool responsible for creating the BOM should create random UUID's for every BOM generated.", + "examples": ["urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"], + "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + "version": { + "$id": "#/properties/version", + "type": "integer", + "title": "BOM Version", + "description": "The version allows component publishers/authors to make changes to existing BOMs to update various aspects of the document such as description or licenses. When a system is presented with multiple BOMs for the same component, the system should use the most recent version of the BOM. The default version is '1' and should be incremented for each version of the BOM that is published. Each version of a component should have a unique BOM and if no changes are made to the BOMs, then each BOM will have a version of '1'.", + "default": 1, + "examples": [1] + }, + "metadata": { + "$id": "#/properties/metadata", + "$ref": "#/definitions/metadata", + "title": "BOM Metadata", + "description": "Provides additional information about a BOM." + }, + "components": { + "$id": "#/properties/components", + "type": "array", + "items": {"$ref": "#/definitions/component"}, + "uniqueItems": true, + "title": "Components" + }, + "services": { + "$id": "#/properties/services", + "type": "array", + "items": {"$ref": "#/definitions/service"}, + "uniqueItems": true, + "title": "Services" + }, + "externalReferences": { + "$id": "#/properties/externalReferences", + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References", + "description": "External references provide a way to document systems, sites, and information that may be relevant but which are not included with the BOM." + }, + "dependencies": { + "$id": "#/properties/dependencies", + "type": "array", + "items": {"$ref": "#/definitions/dependency"}, + "uniqueItems": true, + "title": "Dependencies", + "description": "Provides the ability to document dependency relationships." + }, + "compositions": { + "$id": "#/properties/compositions", + "type": "array", + "items": {"$ref": "#/definitions/compositions"}, + "uniqueItems": true, + "title": "Compositions", + "description": "Compositions describe constituent parts (including components, services, and dependency relationships) and their completeness." + } + }, + "definitions": { + "metadata": { + "type": "object", + "title": "BOM Metadata Object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "The date and time (timestamp) when the document was created." + }, + "tools": { + "type": "array", + "title": "Creation Tools", + "description": "The tool(s) used in the creation of the BOM.", + "items": {"$ref": "#/definitions/tool"} + }, + "authors" :{ + "type": "array", + "title": "Authors", + "description": "The person(s) who created the BOM. Authors are common in BOMs created through manual processes. BOMs created through automated means may not have authors.", + "items": {"$ref": "#/definitions/organizationalContact"} + }, + "component": { + "title": "Component", + "description": "The component that the BOM describes.", + "$ref": "#/definitions/component" + }, + "manufacture": { + "title": "Manufacture", + "description": "The organization that manufactured the component that the BOM describes.", + "$ref": "#/definitions/organizationalEntity" + }, + "supplier": { + "title": "Supplier", + "description": " The organization that supplied the component that the BOM describes. The supplier may often be the manufacturer, but may also be a distributor or repackager.", + "$ref": "#/definitions/organizationalEntity" + }, + "licenses": { + "type": "array", + "title": "BOM License(s)", + "items": {"$ref": "#/definitions/licenseChoice"} + }, + "properties": { + "type": "array", + "title": "Properties", + "description": "Provides the ability to document properties in a name-value store. This provides flexibility to include data not officially supported in the standard without having to use additional namespaces or create extensions. Unlike key-value stores, properties support duplicate names, each potentially having different values.", + "items": {"$ref": "#/definitions/property"} + } + } + }, + "tool": { + "type": "object", + "title": "Tool", + "description": "The tool used to create the BOM.", + "properties": { + "vendor": { + "type": "string", + "title": "Tool Vendor", + "description": "The date and time (timestamp) when the document was created." + }, + "name": { + "type": "string", + "title": "Tool Name", + "description": "The date and time (timestamp) when the document was created." + }, + "version": { + "type": "string", + "title": "Tool Version", + "description": "The date and time (timestamp) when the document was created." + }, + "hashes": { + "$id": "#/properties/hashes", + "type": "array", + "items": {"$ref": "#/definitions/hash"}, + "title": "Hashes", + "description": "The hashes of the tool (if applicable)." + } + } + }, + "organizationalEntity": { + "type": "object", + "title": "Organizational Entity Object", + "description": "", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the organization", + "examples": [ + "Example Inc." + ] + }, + "url": { + "type": "array", + "items": { + "type": "string", + "format": "iri-reference" + }, + "title": "URL", + "description": "The URL of the organization. Multiple URLs are allowed.", + "examples": ["https://example.com"] + }, + "contact": { + "type": "array", + "title": "Contact", + "description": "A contact at the organization. Multiple contacts are allowed.", + "items": {"$ref": "#/definitions/organizationalContact"} + } + } + }, + "organizationalContact": { + "type": "object", + "title": "Organizational Contact Object", + "description": "", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of a contact", + "examples": ["Contact name"] + }, + "email": { + "type": "string", + "title": "Email Address", + "description": "The email address of the contact.", + "examples": ["firstname.lastname@example.com"] + }, + "phone": { + "type": "string", + "title": "Phone", + "description": "The phone number of the contact.", + "examples": ["800-555-1212"] + } + } + }, + "component": { + "type": "object", + "title": "Component Object", + "required": [ + "type", + "name", + "version" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "application", + "framework", + "library", + "container", + "operating-system", + "device", + "firmware", + "file" + ], + "title": "Component Type", + "description": "Specifies the type of component. For software components, classify as application if no more specific appropriate classification is available or cannot be determined for the component.", + "examples": ["library"] + }, + "mime-type": { + "type": "string", + "title": "Mime-Type", + "description": "The optional mime-type of the component. When used on file components, the mime-type can provide additional context about the kind of file being represented such as an image, font, or executable. Some library or framework components may also have an associated mime-type.", + "examples": ["image/jpeg"], + "pattern": "^[-+a-z0-9.]+/[-+a-z0-9.]+$" + }, + "bom-ref": { + "type": "string", + "title": "BOM Reference", + "description": "An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref should be unique." + }, + "supplier": { + "title": "Component Supplier", + "description": " The organization that supplied the component. The supplier may often be the manufacturer, but may also be a distributor or repackager.", + "$ref": "#/definitions/organizationalEntity" + }, + "author": { + "type": "string", + "title": "Component Author", + "description": "The person(s) or organization(s) that authored the component", + "examples": ["Acme Inc"] + }, + "publisher": { + "type": "string", + "title": "Component Publisher", + "description": "The person(s) or organization(s) that published the component", + "examples": ["Acme Inc"] + }, + "group": { + "type": "string", + "title": "Component Group", + "description": "The grouping name or identifier. This will often be a shortened, single name of the company or project that produced the component, or the source package or domain name. Whitespace and special characters should be avoided. Examples include: apache, org.apache.commons, and apache.org.", + "examples": ["com.acme"] + }, + "name": { + "type": "string", + "title": "Component Name", + "description": "The name of the component. This will often be a shortened, single name of the component. Examples: commons-lang3 and jquery", + "examples": ["tomcat-catalina"] + }, + "version": { + "type": "string", + "title": "Component Version", + "description": "The component version. The version should ideally comply with semantic versioning but is not enforced.", + "examples": ["9.0.14"] + }, + "description": { + "type": "string", + "title": "Component Description", + "description": "Specifies a description for the component" + }, + "scope": { + "type": "string", + "enum": [ + "required", + "optional", + "excluded" + ], + "title": "Component Scope", + "description": "Specifies the scope of the component. If scope is not specified, 'required' scope should be assumed by the consumer of the BOM", + "default": "required" + }, + "hashes": { + "type": "array", + "title": "Component Hashes", + "items": {"$ref": "#/definitions/hash"} + }, + "licenses": { + "type": "array", + "items": {"$ref": "#/definitions/licenseChoice"}, + "title": "Component License(s)" + }, + "copyright": { + "type": "string", + "title": "Component Copyright", + "description": "An optional copyright notice informing users of the underlying claims to copyright ownership in a published work.", + "examples": ["Acme Inc"] + }, + "cpe": { + "type": "string", + "title": "Component Common Platform Enumeration (CPE)", + "description": "DEPRECATED - DO NOT USE. This will be removed in a future version. Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe", + "examples": ["cpe:2.3:a:acme:component_framework:-:*:*:*:*:*:*:*"] + }, + "purl": { + "type": "string", + "title": "Component Package URL (purl)", + "examples": ["pkg:maven/com.acme/tomcat-catalina@9.0.14?packaging=jar"] + }, + "swid": { + "$ref": "#/definitions/swid", + "title": "SWID Tag", + "description": "Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags." + }, + "modified": { + "type": "boolean", + "title": "Component Modified From Original", + "description": "DEPRECATED - DO NOT USE. This will be removed in a future version. Use the pedigree element instead to supply information on exactly how the component was modified. A boolean value indicating is the component has been modified from the original. A value of true indicates the component is a derivative of the original. A value of false indicates the component has not been modified from the original." + }, + "pedigree": { + "type": "object", + "title": "Component Pedigree", + "description": "Component pedigree is a way to document complex supply chain scenarios where components are created, distributed, modified, redistributed, combined with other components, etc. Pedigree supports viewing this complex chain from the beginning, the end, or anywhere in the middle. It also provides a way to document variants where the exact relation may not be known.", + "properties": { + "ancestors": { + "type": "array", + "title": "Ancestors", + "description": "Describes zero or more components in which a component is derived from. This is commonly used to describe forks from existing projects where the forked version contains a ancestor node containing the original component it was forked from. For example, Component A is the original component. Component B is the component being used and documented in the BOM. However, Component B contains a pedigree node with a single ancestor documenting Component A - the original component from which Component B is derived from.", + "items": {"$ref": "#/definitions/component"} + }, + "descendants": { + "type": "array", + "title": "Descendants", + "description": "Descendants are the exact opposite of ancestors. This provides a way to document all forks (and their forks) of an original or root component.", + "items": {"$ref": "#/definitions/component"} + }, + "variants": { + "type": "array", + "title": "Variants", + "description": "Variants describe relations where the relationship between the components are not known. For example, if Component A contains nearly identical code to Component B. They are both related, but it is unclear if one is derived from the other, or if they share a common ancestor.", + "items": {"$ref": "#/definitions/component"} + }, + "commits": { + "type": "array", + "title": "Commits", + "description": "A list of zero or more commits which provide a trail describing how the component deviates from an ancestor, descendant, or variant.", + "items": {"$ref": "#/definitions/commit"} + }, + "patches": { + "type": "array", + "title": "Patches", + "description": ">A list of zero or more patches describing how the component deviates from an ancestor, descendant, or variant. Patches may be complimentary to commits or may be used in place of commits.", + "items": {"$ref": "#/definitions/patch"} + }, + "notes": { + "type": "string", + "title": "Notes", + "description": "Notes, observations, and other non-structured commentary describing the components pedigree." + } + } + }, + "externalReferences": { + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References" + }, + "components": { + "$id": "#/properties/components", + "type": "array", + "items": {"$ref": "#/definitions/component"}, + "uniqueItems": true, + "title": "Components" + }, + "evidence": { + "$ref": "#/definitions/componentEvidence", + "title": "Evidence", + "description": "Provides the ability to document evidence collected through various forms of extraction or analysis." + }, + "properties": { + "type": "array", + "title": "Properties", + "description": "Provides the ability to document properties in a name-value store. This provides flexibility to include data not officially supported in the standard without having to use additional namespaces or create extensions. Unlike key-value stores, properties support duplicate names, each potentially having different values.", + "items": {"$ref": "#/definitions/property"} + } + } + }, + "swid": { + "type": "object", + "title": "SWID Tag", + "description": "Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags.", + "required": [ + "tagId", + "name" + ], + "properties": { + "tagId": { + "type": "string", + "title": "Tag ID", + "description": "Maps to the tagId of a SoftwareIdentity." + }, + "name": { + "type": "string", + "title": "Name", + "description": "Maps to the name of a SoftwareIdentity." + }, + "version": { + "type": "string", + "title": "Version", + "default": "0.0", + "description": "Maps to the version of a SoftwareIdentity." + }, + "tagVersion": { + "type": "integer", + "title": "Tag Version", + "default": 0, + "description": "Maps to the tagVersion of a SoftwareIdentity." + }, + "patch": { + "type": "boolean", + "title": "Patch", + "default": false, + "description": "Maps to the patch of a SoftwareIdentity." + }, + "text": { + "title": "Attachment text", + "description": "Specifies the metadata and content of the SWID tag.", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the SWID file.", + "format": "iri-reference" + } + } + }, + "attachment": { + "type": "object", + "title": "Attachment", + "description": "Specifies the metadata and content for an attachment.", + "required": [ + "content" + ], + "properties": { + "contentType": { + "type": "string", + "title": "Content-Type", + "description": "Specifies the content type of the text. Defaults to text/plain if not specified.", + "default": "text/plain" + }, + "encoding": { + "type": "string", + "title": "Encoding", + "description": "Specifies the optional encoding the text is represented in.", + "enum": [ + "base64" + ] + }, + "content": { + "type": "string", + "title": "Attachment Text", + "description": "The attachment data" + } + } + }, + "hash": { + "type": "object", + "title": "Hash Objects", + "required": [ + "alg", + "content" + ], + "properties": { + "alg": { + "$ref": "#/definitions/hash-alg" + }, + "content": { + "$ref": "#/definitions/hash-content" + } + } + }, + "hash-alg": { + "type": "string", + "enum": [ + "MD5", + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512", + "SHA3-256", + "SHA3-384", + "SHA3-512", + "BLAKE2b-256", + "BLAKE2b-384", + "BLAKE2b-512", + "BLAKE3" + ], + "title": "Hash Algorithm" + }, + "hash-content": { + "type": "string", + "title": "Hash Content (value)", + "examples": ["3942447fac867ae5cdb3229b658f4d48"], + "pattern": "^([a-fA-F0-9]{32}|[a-fA-F0-9]{40}|[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128})$" + }, + "license": { + "type": "object", + "title": "License Object", + "oneOf": [ + { + "required": ["id"] + }, + { + "required": ["name"] + } + ], + "properties": { + "id": { + "$ref": "spdx.schema.json", + "title": "License ID (SPDX)", + "description": "A valid SPDX license ID", + "examples": ["Apache-2.0"] + }, + "name": { + "type": "string", + "title": "License Name", + "description": "If SPDX does not define the license used, this field may be used to provide the license name", + "examples": ["Acme Software License"] + }, + "text": { + "title": "License text", + "description": "An optional way to include the textual content of a license.", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "License URL", + "description": "The URL to the license file. If specified, a 'license' externalReference should also be specified for completeness", + "examples": ["https://www.apache.org/licenses/LICENSE-2.0.txt"], + "format": "iri-reference" + } + } + }, + "licenseChoice": { + "type": "object", + "title": "License(s)", + "properties": { + "license": { + "$ref": "#/definitions/license" + }, + "expression": { + "type": "string", + "title": "SPDX License Expression", + "examples": [ + "Apache-2.0 AND (MIT OR GPL-2.0-only)", + "GPL-3.0-only WITH Classpath-exception-2.0" + ] + } + }, + "oneOf":[ + { + "required": ["license"] + }, + { + "required": ["expression"] + } + ] + }, + "commit": { + "type": "object", + "title": "Commit", + "description": "Specifies an individual commit", + "properties": { + "uid": { + "type": "string", + "title": "UID", + "description": "A unique identifier of the commit. This may be version control specific. For example, Subversion uses revision numbers whereas git uses commit hashes." + }, + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the commit. This URL will typically point to a commit in a version control system.", + "format": "iri-reference" + }, + "author": { + "title": "Author", + "description": "The author who created the changes in the commit", + "$ref": "#/definitions/identifiableAction" + }, + "committer": { + "title": "Committer", + "description": "The person who committed or pushed the commit", + "$ref": "#/definitions/identifiableAction" + }, + "message": { + "type": "string", + "title": "Message", + "description": "The text description of the contents of the commit" + } + } + }, + "patch": { + "type": "object", + "title": "Patch", + "description": "Specifies an individual patch", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "unofficial", + "monkey", + "backport", + "cherry-pick" + ], + "title": "Type", + "description": "Specifies the purpose for the patch including the resolution of defects, security issues, or new behavior or functionality" + }, + "diff": { + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "$ref": "#/definitions/diff" + }, + "resolves": { + "type": "array", + "items": {"$ref": "#/definitions/issue"}, + "title": "Resolves", + "description": "A collection of issues the patch resolves" + } + } + }, + "diff": { + "type": "object", + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "properties": { + "text": { + "title": "Diff text", + "description": "Specifies the optional text of the diff", + "$ref": "#/definitions/attachment" + }, + "url": { + "type": "string", + "title": "URL", + "description": "Specifies the URL to the diff", + "format": "iri-reference" + } + } + }, + "issue": { + "type": "object", + "title": "Diff", + "description": "The patch file (or diff) that show changes. Refer to https://en.wikipedia.org/wiki/Diff", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "defect", + "enhancement", + "security" + ], + "title": "Type", + "description": "Specifies the type of issue" + }, + "id": { + "type": "string", + "title": "ID", + "description": "The identifier of the issue assigned by the source of the issue" + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the issue" + }, + "description": { + "type": "string", + "title": "Description", + "description": "A description of the issue" + }, + "source": { + "type": "object", + "title": "Source", + "description": "The source of the issue where it is documented", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the source. For example 'National Vulnerability Database', 'NVD', and 'Apache'" + }, + "url": { + "type": "string", + "title": "URL", + "description": "The url of the issue documentation as provided by the source", + "format": "iri-reference" + } + } + }, + "references": { + "type": "array", + "items": { + "type": "string", + "format": "iri-reference" + }, + "title": "References", + "description": "A collection of URL's for reference. Multiple URLs are allowed.", + "examples": ["https://example.com"] + } + } + }, + "identifiableAction": { + "type": "object", + "title": "Identifiable Action", + "description": "Specifies an individual commit", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "The timestamp in which the action occurred" + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the individual who performed the action" + }, + "email": { + "type": "string", + "format": "idn-email", + "title": "E-mail", + "description": "The email address of the individual who performed the action" + } + } + }, + "externalReference": { + "type": "object", + "title": "External Reference", + "description": "Specifies an individual external reference", + "required": [ + "url", + "type" + ], + "properties": { + "url": { + "type": "string", + "title": "URL", + "description": "The URL to the external reference", + "format": "iri-reference" + }, + "comment": { + "type": "string", + "title": "Comment", + "description": "An optional comment describing the external reference" + }, + "type": { + "type": "string", + "title": "Type", + "description": "Specifies the type of external reference. There are built-in types to describe common references. If a type does not exist for the reference being referred to, use the \"other\" type.", + "enum": [ + "vcs", + "issue-tracker", + "website", + "advisories", + "bom", + "mailing-list", + "social", + "chat", + "documentation", + "support", + "distribution", + "license", + "build-meta", + "build-system", + "other" + ] + }, + "hashes": { + "$id": "#/properties/hashes", + "type": "array", + "items": {"$ref": "#/definitions/hash"}, + "title": "Hashes", + "description": "The hashes of the external reference (if applicable)." + } + } + }, + "dependency": { + "type": "object", + "title": "Dependency", + "description": "Defines the direct dependencies of a component. Components that do not have their own dependencies MUST be declared as empty elements within the graph. Components that are not represented in the dependency graph MAY have unknown dependencies. It is RECOMMENDED that implementations assume this to be opaque and not an indicator of a component being dependency-free.", + "required": [ + "ref" + ], + "properties": { + "ref": { + "type": "string", + "title": "Reference", + "description": "References a component by the components bom-ref attribute" + }, + "dependsOn": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "title": "Depends On", + "description": "The bom-ref identifiers of the components that are dependencies of this dependency object." + } + } + }, + "service": { + "type": "object", + "title": "Service Object", + "required": [ + "name" + ], + "properties": { + "bom-ref": { + "type": "string", + "title": "BOM Reference", + "description": "An optional identifier which can be used to reference the service elsewhere in the BOM. Every bom-ref should be unique." + }, + "provider": { + "title": "Provider", + "description": "The organization that provides the service.", + "$ref": "#/definitions/organizationalEntity" + }, + "group": { + "type": "string", + "title": "Service Group", + "description": "The grouping name, namespace, or identifier. This will often be a shortened, single name of the company or project that produced the service or domain name. Whitespace and special characters should be avoided.", + "examples": ["com.acme"] + }, + "name": { + "type": "string", + "title": "Service Name", + "description": "The name of the service. This will often be a shortened, single name of the service.", + "examples": ["ticker-service"] + }, + "version": { + "type": "string", + "title": "Service Version", + "description": "The service version.", + "examples": ["1.0.0"] + }, + "description": { + "type": "string", + "title": "Service Description", + "description": "Specifies a description for the service" + }, + "endpoints": { + "type": "array", + "items": { + "type": "string", + "format": "iri-reference" + }, + "title": "Endpoints", + "description": "The endpoint URIs of the service. Multiple endpoints are allowed.", + "examples": ["https://example.com/api/v1/ticker"] + }, + "authenticated": { + "type": "boolean", + "title": "Authentication Required", + "description": "A boolean value indicating if the service requires authentication. A value of true indicates the service requires authentication prior to use. A value of false indicates the service does not require authentication." + }, + "x-trust-boundary": { + "type": "boolean", + "title": "Crosses Trust Boundary", + "description": "A boolean value indicating if use of the service crosses a trust zone or boundary. A value of true indicates that by using the service, a trust boundary is crossed. A value of false indicates that by using the service, a trust boundary is not crossed." + }, + "data": { + "type": "array", + "items": {"$ref": "#/definitions/dataClassification"}, + "title": "Data Classification", + "description": "Specifies the data classification." + }, + "licenses": { + "type": "array", + "items": {"$ref": "#/definitions/licenseChoice"}, + "title": "Component License(s)" + }, + "externalReferences": { + "type": "array", + "items": {"$ref": "#/definitions/externalReference"}, + "title": "External References" + }, + "services": { + "$id": "#/properties/services", + "type": "array", + "items": {"$ref": "#/definitions/service"}, + "uniqueItems": true, + "title": "Services" + }, + "properties": { + "type": "array", + "title": "Properties", + "description": "Provides the ability to document properties in a name-value store. This provides flexibility to include data not officially supported in the standard without having to use additional namespaces or create extensions. Unlike key-value stores, properties support duplicate names, each potentially having different values.", + "items": {"$ref": "#/definitions/property"} + } + } + }, + "dataClassification": { + "type": "object", + "title": "Hash Objects", + "required": [ + "flow", + "classification" + ], + "properties": { + "flow": { + "$ref": "#/definitions/dataFlow" + }, + "classification": { + "type": "string" + } + } + }, + "dataFlow": { + "type": "string", + "enum": [ + "inbound", + "outbound", + "bi-directional", + "unknown" + ], + "title": "Data flow direction" + }, + + "copyright": { + "type": "object", + "title": "Copyright", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "title": "Copyright Text" + } + } + }, + + "componentEvidence": { + "type": "object", + "title": "Evidence", + "description": "Provides the ability to document evidence collected through various forms of extraction or analysis.", + "properties": { + "licenses": { + "type": "array", + "items": {"$ref": "#/definitions/licenseChoice"}, + "title": "Component License(s)" + }, + "copyright": { + "type": "array", + "items": {"$ref": "#/definitions/copyright"}, + "title": "Copyright" + } + } + }, + "compositions": { + "type": "object", + "title": "Compositions", + "required": [ + "aggregate" + ], + "properties": { + "aggregate": { + "$ref": "#/definitions/aggregateType", + "title": "Aggregate", + "description": "Specifies an aggregate type that describe how complete a relationship is." + }, + "assemblies": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "title": "BOM references", + "description": "The bom-ref identifiers of the components or services being described. Assemblies refer to nested relationships whereby a constituent part may include other constituent parts. References do not cascade to child parts. References are explicit for the specified constituent part only." + }, + "dependencies": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "title": "BOM references", + "description": "The bom-ref identifiers of the components or services being described. Dependencies refer to a relationship whereby an independent constituent part requires another independent constituent part. References do not cascade to transitive dependencies. References are explicit for the specified dependency only." + } + } + }, + "aggregateType": { + "type": "string", + "default": "not_specified", + "enum": [ + "complete", + "incomplete", + "incomplete_first_party_only", + "incomplete_third_party_only", + "unknown", + "not_specified" + ] + }, + "property": { + "type": "object", + "title": "Lightweight name-value pair", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the property. Duplicate names are allowed, each potentially having a different value." + }, + "value": { + "type": "string", + "title": "Value", + "description": "The value of the property." + } + } + } + } +} diff --git a/cyclonedx/schema/bom-1.3.xsd b/cyclonedx/schema/bom-1.3.xsd new file mode 100644 index 00000000..53b37ffa --- /dev/null +++ b/cyclonedx/schema/bom-1.3.xsd @@ -0,0 +1,1631 @@ + + + + + + + + + CycloneDX Software Bill-of-Material Specification + https://cyclonedx.org/ + Apache License, Version 2.0 + + + + + + + + The date and time (timestamp) when the document was created. + + + + + The tool(s) used in the creation of the BOM. + + + + + + + + + + The person(s) who created the BOM. Authors are common in BOMs created through + manual processes. BOMs created through automated means may not have authors. + + + + + + + + + + The component that the BOM describes. + + + + + The organization that manufactured the component that the BOM describes. + + + + + The organization that supplied the component that the BOM describes. The + supplier may often be the manufacturer, but may also be a distributor or repackager. + + + + + + Provides the ability to document properties in a key/value store. + This provides flexibility to include data not officially supported in the standard + without having to use additional namespaces or create extensions. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The name of the organization + + + + + The URL of the organization. Multiple URLs are allowed. + + + + + A contact person at the organization. Multiple contacts are allowed. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Specifies a tool (manual or automated). + + + + + The vendor of the tool used to create the BOM. + + + + + The name of the tool used to create the BOM. + + + + + The version of the tool used to create the BOM. + + + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The name of the contact + + + + + The email address of the contact. + + + + + The phone number of the contact. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The organization that supplied the component. The supplier may often + be the manufacturer, but may also be a distributor or repackager. + + + + + The person(s) or organization(s) that authored the component + + + + + The person(s) or organization(s) that published the component + + + + + The grouping name or identifier. This will often be a shortened, single + name of the company or project that produced the component, or the source package or + domain name. Whitespace and special characters should be avoided. Examples include: + apache, org.apache.commons, and apache.org. + + + + + The name of the component. This will often be a shortened, single name + of the component. Examples: commons-lang3 and jquery + + + + + The component version. The version should ideally comply with semantic versioning + but is not enforced. + + + + + Specifies a description for the component + + + + + Specifies the scope of the component. If scope is not specified, 'runtime' + scope should be assumed by the consumer of the BOM + + + + + + + + + + + + + An optional copyright notice informing users of the underlying claims to + copyright ownership in a published work. + + + + + + DEPRECATED - DO NOT USE. This will be removed in a future version. + Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe + + + + + + + Specifies the package-url (PURL). The purl, if specified, must be valid and conform + to the specification defined at: https://github.com/package-url/purl-spec + + + + + + + Specifies metadata and content for ISO-IEC 19770-2 Software Identification (SWID) Tags. + + + + + + + DEPRECATED - DO NOT USE. This will be removed in a future version. Use the pedigree + element instead to supply information on exactly how the component was modified. + A boolean value indicating is the component has been modified from the original. + A value of true indicates the component is a derivative of the original. + A value of false indicates the component has not been modified from the original. + + + + + + + Component pedigree is a way to document complex supply chain scenarios where components are + created, distributed, modified, redistributed, combined with other components, etc. + + + + + + Provides the ability to document external references related to the + component or to the project the component describes. + + + + + Provides the ability to document properties in a key/value store. + This provides flexibility to include data not officially supported in the standard + without having to use additional namespaces or create extensions. + + + + + + Specifies optional sub-components. This is not a dependency tree. It provides a way + to specify a hierarchical representation of component assemblies, similar to + system -> subsystem -> parts assembly in physical supply chains. + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + Provides the ability to document evidence collected through various forms of extraction or analysis. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + Specifies the type of component. For software components, classify as application if no more + specific appropriate classification is available or cannot be determined for the component. + + + + + + + The optional mime-type of the component. When used on file components, the mime-type + can provide additional context about the kind of file being represented such as an image, + font, or executable. Some library or framework components may also have an associated mime-type. + + + + + + + An optional identifier which can be used to reference the component elsewhere in the BOM. + Uniqueness is enforced within all elements and children of the root-level bom element. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + + A valid SPDX license ID + + + + + If SPDX does not define the license used, this field may be used to provide the license name + + + + + + Specifies the optional full text of the attachment + + + + + The URL to the attachment file. If the attachment is a license or BOM, + an externalReference should also be specified for completeness. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + Specifies attributes of the text + + + + Specifies the content type of the text. Defaults to text/plain + if not specified. + + + + + + Specifies the optional encoding the text is represented in + + + + + + + + + + Specifies the file hash of the component + + + + + + Specifies the algorithm used to create the hash + + + + + + + + + + + The component is required for runtime + + + + + The component is optional at runtime. Optional components are components that + are not capable of being called due to them not be installed or otherwise accessible by any means. + Components that are installed but due to configuration or other restrictions are prohibited from + being called must be scoped as 'required'. + + + + + Components that are excluded provide the ability to document component usage + for test and other non-runtime purposes. Excluded components are not reachable within a call + graph at runtime. + + + + + + + + + + A software application. Refer to https://en.wikipedia.org/wiki/Application_software + for information about applications. + + + + + A software framework. Refer to https://en.wikipedia.org/wiki/Software_framework + for information on how frameworks vary slightly from libraries. + + + + + A software library. Refer to https://en.wikipedia.org/wiki/Library_(computing) + for information about libraries. All third-party and open source reusable components will likely + be a library. If the library also has key features of a framework, then it should be classified + as a framework. If not, or is unknown, then specifying library is recommended. + + + + + A packaging and/or runtime format, not specific to any particular technology, + which isolates software inside the container from software outside of a container through + virtualization technology. Refer to https://en.wikipedia.org/wiki/OS-level_virtualization + + + + + A software operating system without regard to deployment model + (i.e. installed on physical hardware, virtual machine, image, etc) Refer to + https://en.wikipedia.org/wiki/Operating_system + + + + + A hardware device such as a processor, or chip-set. A hardware device + containing firmware should include a component for the physical hardware itself, and another + component of type 'firmware' or 'operating-system' (whichever is relevant), describing + information about the software running on the device. + + + + + A special type of software that provides low-level control over a devices + hardware. Refer to https://en.wikipedia.org/wiki/Firmware + + + + + A computer file. Refer to https://en.wikipedia.org/wiki/Computer_file + for information about files. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Define the format for acceptable CPE URIs. Supports CPE 2.2 and CPE 2.3 formats. + Refer to https://nvd.nist.gov/products/cpe for official specification. + + + + + + + + + + + + Specifies the full content of the SWID tag. + + + + + The URL to the SWID file. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + Maps to the tagId of a SoftwareIdentity. + + + + + Maps to the name of a SoftwareIdentity. + + + + + Maps to the version of a SoftwareIdentity. + + + + + Maps to the tagVersion of a SoftwareIdentity. + + + + + Maps to the patch of a SoftwareIdentity. + + + + + + + + Defines a string representation of a UUID conforming to RFC 4122. + + + + + + + + + + + + Version Control System + + + + + Issue or defect tracking system, or an Application Lifecycle Management (ALM) system + + + + + Website + + + + + Security advisories + + + + + Bill-of-material document (CycloneDX, SPDX, SWID, etc) + + + + + Mailing list or discussion group + + + + + Social media account + + + + + Real-time chat platform + + + + + Documentation, guides, or how-to instructions + + + + + Community or commercial support + + + + + Direct or repository download location + + + + + The URL to the license file. If a license URL has been defined in the license + node, it should also be defined as an external reference for completeness + + + + + Build-system specific meta file (i.e. pom.xml, package.json, .nuspec, etc) + + + + + URL to an automated build system + + + + + Use this if no other types accurately describe the purpose of the external reference + + + + + + + + + External references provide a way to document systems, sites, and information that may be relevant + but which are not included with the BOM. + + + + + + Zero or more external references can be defined + + + + + + + + + + The URL to the external reference + + + + + An optional comment describing the external reference + + + + + + + + + + + + + Specifies the type of external reference. There are built-in types to describe common + references. If a type does not exist for the reference being referred to, use the "other" type. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Zero or more commits can be specified. + + + + + Specifies an individual commit. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + A unique identifier of the commit. This may be version control + specific. For example, Subversion uses revision numbers whereas git uses commit hashes. + + + + + + The URL to the commit. This URL will typically point to a commit + in a version control system. + + + + + + The author who created the changes in the commit + + + + + The person who committed or pushed the commit + + + + + The text description of the contents of the commit + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + Zero or more patches can be specified. + + + + + Specifies an individual patch. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + The patch file (or diff) that show changes. + Refer to https://en.wikipedia.org/wiki/Diff + + + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + Specifies the purpose for the patch including the resolution of defects, + security issues, or new behavior or functionality + + + + + + + + + A patch which is not developed by the creators or maintainers of the software + being patched. Refer to https://en.wikipedia.org/wiki/Unofficial_patch + + + + + A patch which dynamically modifies runtime behavior. + Refer to https://en.wikipedia.org/wiki/Monkey_patch + + + + + A patch which takes code from a newer version of software and applies + it to older versions of the same software. Refer to https://en.wikipedia.org/wiki/Backporting + + + + + A patch created by selectively applying commits from other versions or + branches of the same software. + + + + + + + + + + A fault, flaw, or bug in software + + + + + A new feature or behavior in software + + + + + A special type of defect which impacts security + + + + + + + + + + Specifies the optional text of the diff + + + + + Specifies the URL to the diff + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + The identifier of the issue assigned by the source of the issue + + + + + The name of the issue + + + + + A description of the issue + + + + + + + The source of the issue where it is documented. + + + + + + + The name of the source. For example "National Vulnerability Database", + "NVD", and "Apache" + + + + + + + The url of the issue documentation as provided by the source + + + + + + + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + Specifies the type of issue + + + + + + + + + The timestamp in which the action occurred + + + + + The name of the individual who performed the action + + + + + The email address of the individual who performed the action + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + Component pedigree is a way to document complex supply chain scenarios where components are created, + distributed, modified, redistributed, combined with other components, etc. Pedigree supports viewing + this complex chain from the beginning, the end, or anywhere in the middle. It also provides a way to + document variants where the exact relation may not be known. + + + + + + Describes zero or more components in which a component is derived + from. This is commonly used to describe forks from existing projects where the forked version + contains a ancestor node containing the original component it was forked from. For example, + Component A is the original component. Component B is the component being used and documented + in the BOM. However, Component B contains a pedigree node with a single ancestor documenting + Component A - the original component from which Component B is derived from. + + + + + + Descendants are the exact opposite of ancestors. This provides a + way to document all forks (and their forks) of an original or root component. + + + + + + Variants describe relations where the relationship between the + components are not known. For example, if Component A contains nearly identical code to + Component B. They are both related, but it is unclear if one is derived from the other, + or if they share a common ancestor. + + + + + + A list of zero or more commits which provide a trail describing + how the component deviates from an ancestor, descendant, or variant. + + + + + A list of zero or more patches describing how the component + deviates from an ancestor, descendant, or variant. Patches may be complimentary to commits + or may be used in place of commits. + + + + + Notes, observations, and other non-structured commentary + describing the components pedigree. + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + + + References a component or service by the its bom-ref attribute + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + Components that do not have their own dependencies MUST be declared as empty + elements within the graph. Components that are not represented in the dependency graph MAY + have unknown dependencies. It is RECOMMENDED that implementations assume this to be opaque + and not an indicator of a component being dependency-free. + + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The organization that provides the service. + + + + + The grouping name, namespace, or identifier. This will often be a shortened, + single name of the company or project that produced the service or domain name. + Whitespace and special characters should be avoided. + + + + + The name of the service. This will often be a shortened, single name + of the service. + + + + + The service version. + + + + + Specifies a description for the service. + + + + + + + + A service endpoint URI. + + + + + + + + A boolean value indicating if the service requires authentication. + A value of true indicates the service requires authentication prior to use. + A value of false indicates the service does not require authentication. + + + + + A boolean value indicating if use of the service crosses a trust zone or boundary. + A value of true indicates that by using the service, a trust boundary is crossed. + A value of false indicates that by using the service, a trust boundary is not crossed. + + + + + + + + Specifies the data classification. + + + + + + + + + Provides the ability to document external references related to the service. + + + + + Provides the ability to document properties in a key/value store. + This provides flexibility to include data not officially supported in the standard + without having to use additional namespaces or create extensions. + + + + + + Specifies optional sub-service. This is not a dependency tree. It provides a way + to specify a hierarchical representation of service assemblies, similar to + system -> subsystem -> parts assembly in physical supply chains. + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + An optional identifier which can be used to reference the service elsewhere in the BOM. + Uniqueness is enforced within all elements and children of the root-level bom element. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Specifies the data classification. + + + + + + Specifies the flow direction of the data. + + + + + + + + + Specifies the flow direction of the data. Valid values are: + inbound, outbound, bi-directional, and unknown. Direction is relative to the service. + Inbound flow states that data enters the service. Outbound flow states that data + leaves the service. Bi-directional states that data flows both ways, and unknown + states that the direction is not known. + + + + + + + + + + + + + + + A valid SPDX license expression. + Refer to https://spdx.org/specifications for syntax requirements + + + + + + + + + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + Specifies an aggregate type that describe how complete a relationship is. + + + + + + The bom-ref identifiers of the components or services being described. Assemblies refer to + nested relationships whereby a constituent part may include other constituent parts. References + do not cascade to child parts. References are explicit for the specified constituent part only. + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + The bom-ref identifiers of the components or services being described. Dependencies refer to a + relationship whereby an independent constituent part requires another independent constituent + part. References do not cascade to transitive dependencies. References are explicit for the + specified dependency only. + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + + + + + + + + The relationship is complete. No further relationships including constituent components, services, or dependencies exist. + + + + + The relationship is incomplete. Additional relationships exist and may include constituent components, services, or dependencies. + + + + + The relationship is incomplete. Only relationships for first-party components, services, or their dependencies are represented. + + + + + The relationship is incomplete. Only relationships for third-party components, services, or their dependencies are represented. + + + + + The relationship may be complete or incomplete. This usually signifies a 'best-effort' to obtain constituent components, services, or dependencies but the completeness is inconclusive. + + + + + The relationship completeness is not specified. + + + + + + + + + References a component or service by the its bom-ref attribute + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Specifies an individual property with a name and value. + + + + + + The name of the property. Duplicate names are allowed, each potentially having a different value. + + + + + + + + + + + + Provides additional information about a BOM. + + + + + Provides the ability to document a list of components. + + + + + Provides the ability to document a list of external services. + + + + + Provides the ability to document external references related to the BOM or + to the project the BOM describes. + + + + + Provides the ability to document dependency relationships. + + + + + Compositions describe constituent parts (including components, services, and dependency relationships) and their completeness. + + + + + Provides the ability to document properties in a name-value store. + This provides flexibility to include data not officially supported in the standard + without having to use additional namespaces or create extensions. Unlike key-value + stores, properties support duplicate names, each potentially having different values. + + + + + + Allows any undeclared elements as long as the elements are placed in a different namespace. + + + + + + + The version allows component publishers/authors to make changes to existing + BOMs to update various aspects of the document such as description or licenses. When a system + is presented with multiple BOMs for the same component, the system should use the most recent + version of the BOM. The default version is '1' and should be incremented for each version of the + BOM that is published. Each version of a component should have a unique BOM and if no changes are + made to the BOMs, then each BOM will have a version of '1'. + + + + + Every BOM generated should have a unique serial number, even if the contents + of the BOM being generated have not changed over time. The process or tool responsible for + creating the BOM should create random UUID's for every BOM generated. + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + diff --git a/cyclonedx/schema/ext/bom-descriptor-0.9.xsd b/cyclonedx/schema/ext/bom-descriptor-0.9.xsd new file mode 100644 index 00000000..605df12f --- /dev/null +++ b/cyclonedx/schema/ext/bom-descriptor-0.9.xsd @@ -0,0 +1,175 @@ + + + + + + + CycloneDX BOM Descriptor Extension + https://cyclonedx.org/ext/bom-descriptor + Apache License, Version 2.0 + + Steve Springett + + + + + + + + + Specifies the name of the software the BOM describes. + + + + + Specifies the version of the software the BOM describes. + + + + + Specifies the edition of the software the BOM describes. + + + + + + + + + + + + + + + + A valid SPDX license expression. + Refer to https://spdx.org/specifications for syntax requirements + + + + + + + + An optional copyright notice informing users of the underlying claims to + copyright ownership in a published work. + + + + + + Specifies a well-formed CPE name. See https://nvd.nist.gov/products/cpe + + + + + + + Specifies the package-url (PURL). The purl, if specified, must be valid and conform + to the specification defined at: https://github.com/package-url/purl-spec + + + + + + The organization that manufactured the software for which the BOM describes. + + + + + The organization that supplied the software for which the BOM describes. The + supplier may often be the manufacture, but may also be a distributor or repackager. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The name of the organization + + + + + The URL of the organization. Multiple URLs are allowed. + + + + + A contact person at the organization. Multiple contacts are allowed. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The name of the person + + + + + The email address of the person. Multiple email addresses are allowed. + + + + + The phone number of the person. Multiple phone numbers are allowed. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Provides additional information about a BOM. + + + + diff --git a/cyclonedx/schema/ext/bom-descriptor-1.0.xsd b/cyclonedx/schema/ext/bom-descriptor-1.0.xsd new file mode 100644 index 00000000..013f550e --- /dev/null +++ b/cyclonedx/schema/ext/bom-descriptor-1.0.xsd @@ -0,0 +1,183 @@ + + + + + + + CycloneDX BOM Descriptor Extension + https://cyclonedx.org/ext/bom-descriptor + Apache License, Version 2.0 + + Steve Springett + + + + + + + + + + + The date and time (timestamp) when the document was created. + + + + + The tool used to create the BOM. + + + + + The person(s) who created the BOM. Authors are common in BOMs created through + manual processes. BOMs created through automated means may not have authors. + + + + + + + + + + The component that the BOM describes. + + + + + The organization that manufactured the component that the BOM describes. + + + + + The organization that supplied the component that the BOM describes. The + supplier may often be the manufacture, but may also be a distributor or repackager. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The name of the organization + + + + + The URL of the organization. Multiple URLs are allowed. + + + + + A contact person at the organization. Multiple contacts are allowed. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Specifies a tool (manual or automated). + + + + + The vendor of the tool used to create the BOM. + + + + + The name of the tool used to create the BOM. + + + + + The version of the tool used to create the BOM. + + + + + + + + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + The name of the person + + + + + The email address of the person. Multiple email addresses are allowed. + + + + + The phone number of the person. Multiple phone numbers are allowed. + + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + Provides additional information about a BOM. + + + + diff --git a/cyclonedx/schema/ext/dependency-graph-1.0.xsd b/cyclonedx/schema/ext/dependency-graph-1.0.xsd new file mode 100644 index 00000000..ddcb5365 --- /dev/null +++ b/cyclonedx/schema/ext/dependency-graph-1.0.xsd @@ -0,0 +1,70 @@ + + + + + + + CycloneDX Dependency Graph Extension + https://cyclonedx.org/ext/dependency-graph + Apache License, Version 2.0 + + Steve Springett + + + + + + + + + + + References a component by the components bom-ref attribute + + + + + User-defined attributes may be used on this element as long as they + do not have the same name as an existing attribute used by the schema. + + + + + + + + + + Components that do not have their own dependencies MUST be declared as empty + elements within the graph. Components that are not represented in the dependency graph MAY + have unknown dependencies. It is RECOMMENDED that implementations assume this to be opaque + and not an indicator of a component being dependency-free. + + + + + + + diff --git a/cyclonedx/schema/ext/vulnerability-1.0-SNAPSHOT.schema.json b/cyclonedx/schema/ext/vulnerability-1.0-SNAPSHOT.schema.json new file mode 100644 index 00000000..378bd498 --- /dev/null +++ b/cyclonedx/schema/ext/vulnerability-1.0-SNAPSHOT.schema.json @@ -0,0 +1,182 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://cyclonedx.org/schema/ext/vulnerability-1.0-SNAPSHOT.schema.json", + "type": "object", + "title": "CycloneDX Vulnerability Extension", + "$comment" : "CycloneDX Vulnerability Extension for JSON Schema is published under the terms of the Apache License 2.0.", + "properties": { + "vulnerabilities": { + "$id": "#/properties/vulnerabilities", + "type": "array", + "items": {"$ref": "#/definitions/vulnerability"}, + "title": "Vulnerabilities", + "description": "Defines a list of vulnerabilities." + } + }, + "definitions": { + "cwe": { + "type": "integer", + "minimum": 1, + "title": "CWE", + "description": "Integer representation of a Common Weaknesses Enumerations (CWE). For example 399 (of https://cwe.mitre.org/data/definitions/399.html)" + }, + "severity": { + "type": "string", + "title": "Severity", + "description": "Textual representation of the severity of the vulnerability adopted by the risk analysis method. If an other risk analysis method is used other than whats defined in scoreSourceType, the user is expected to translate appropriately to match with an element value below.", + "enum": [ + "None", + "Low", + "Medium", + "High", + "Critical", + "Unknown" + ] + }, + "scoreValue": { + "type": "number", + "title": "Score", + "description": "Numerical representation of the vulnerability score. Must be a number between 0 - 10 (maps to lowest severity - highest severity)", + "multipleOf": 0.1, + "examples": [7.9, 10.0] + }, + "scoreSource": { + "type": "string", + "title": "Source", + "description": "Specifies the risk scoring methodology/standard used.", + "enum": [ + "CVSSv2", + "CVSSv3", + "OWASP Risk", + "Open FAIR", + "Other" + ] + }, + "score": { + "type": "object", + "title": "Score", + "description": "Defines the numerical risk score of a vulnerability", + "properties": { + "base": { + "type": "number", + "title": "Base Score", + "description": "The base score of the security vulnerability (Refer CVSS standard for example)", + "multipleOf": 0.1, + "examples": [2.9, 7.2] + }, + "impact": { + "type": "number", + "title": "Impact Score", + "description": "The impact subscore of the security vulnerability (Refer CVSS standard for example)", + "multipleOf": 0.1, + "examples": [2.9, 7.2] + }, + "exploitability": { + "type": "number", + "title": "Exploitability Score", + "description": "The exploitability subscore of the security vulnerability (Refer CVSS standard for example)", + "multipleOf": 0.1, + "examples": [2.9, 7.2] + } + } + }, + "rating": { + "type": "object", + "title": "Rating", + "description": "Defines the risk rating of a vulnerability.", + "properties": { + "score": { + "$ref": "#/definitions/score" + }, + "severity": { + "$ref": "#/definitions/severity" + }, + "method": { + "$ref": "#/definitions/scoreSource" + }, + "vector": { + "type": "string", + "title": "Vector", + "description": "Textual representation of the metric values used to score the vulnerability see attack vector in https://www.first.org/cvss/v3.1/specification-document" + } + } + }, + "source": { + "type": "object", + "title": "Source", + "description": "The source of the vulnerability where it is documented. Usually the name of the organization publishing vulnerability information", + "properties": { + "url": { + "type": "string", + "title": "URL", + "description": "The url of the vulnerability documentation as provided by the source.", + "examples": [ + "https://nvd.nist.gov/vuln/detail/CVE-2019-15842" + ] + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the source.", + "examples": [ + "NVD", "National Vulnerability Database", "OSS Index", "VulnDB", "NPM Advisories" + ] + } + } + }, + "vulnerability": { + "type": "object", + "title": "Vulnerability", + "description": "Defines the structure of a vulnerability.", + "properties": { + "ref": { + "type": "string", + "format": "string", + "title": "Reference", + "description": "References a component by the components bom-ref attribute" + }, + "id": { + "type": "string", + "title": "ID", + "description": "The id of the vulnerability as defined by the risk scoring methodology. For example CVE-2019-15842 (of https://nvd.nist.gov/vuln/detail/CVE-2019-15842)" + }, + "source": { + "$ref": "#/definitions/source" + }, + "ratings": { + "type": "array", + "title": "Ratings", + "description": "List of the vulnerability ratings as defined by various risk rating methodologies.", + "items": {"$ref": "#/definitions/rating"} + }, + "cwes": { + "type": "array", + "title": "CWEs", + "description": "List of Common Weaknesses Enumerations (CWEs) codes that describes this vulnerability. For example 399 (of https://cwe.mitre.org/data/definitions/399.html)", + "items": {"$ref": "#/definitions/cwe"} + }, + "description": { + "type": "string", + "title": "Description", + "description": "Description of the vulnerability as provided by the source organization" + }, + "recommendations": { + "type": "array", + "title": "Recommendations", + "description": "List of recommendations of how the particular vulnerability can be avoided/mitigated.", + "items": { + "type": "string" + } + }, + "advisories": { + "type": "array", + "title": "Advisories", + "description": "Published advisories of the vulnerability if provided.", + "items": { + "type": "string" + } + } + } + } + } +} diff --git a/cyclonedx/schema/ext/vulnerability-1.0.xsd b/cyclonedx/schema/ext/vulnerability-1.0.xsd new file mode 100644 index 00000000..2d684745 --- /dev/null +++ b/cyclonedx/schema/ext/vulnerability-1.0.xsd @@ -0,0 +1,291 @@ + + + + + + + CycloneDX Vulnerability Extension + https://cyclonedx.org/ext/vulnerability + Apache License, Version 2.0 + + + + + + + Textual representation of the severity of the vulnerability adopted by the risk analysis method. + If an other risk analysis method is used other than whats defined in scoreSourceType, + the user is expected to translate appropriately to match with an element value below. + + + + + + + + + + + + + + + + Numerical representation of the vulnerability score. + Must be a number between 0 - 10 (maps to lowest severity - highest severity) + + + + + + + + + + + + + Specifies the risk scoring methodology/standard used. + + + + + + + The rating is based on CVSS v2 standard + https://www.first.org/cvss/v2/guide + + + + + + + The rating is based on CVSS v3 standard + https://www.first.org/cvss/v3.1/specification-document + + + + + + + The rating is based on OWASP Risk Rating + https://www.owasp.org/index.php/OWASP_Risk_Rating_Methodology + + + + + + + The rating is based on Open FAIR specification + http://www.opengroup.org/subjectareas/security/risk + + + + + + + Use this if the risk scoring methodology is not based on any of the options above + + + + + + + + + + Defines the numerical risk score of a vulnerability + + + + + + + + + + The base score of the security vulnerability (Refer CVSS standard for example) + + + + + + + The impact subscore of the security vulnerability (Refer CVSS standard for example) + + + + + + + The exploitability subscore of the security vulnerability (Refer CVSS standard for + example) + + + + + + + + + + + + Textual representation of the metric values used to score the vulnerability + see attack vector in https://www.first.org/cvss/v3.1/specification-document + + + + + + + + + + Defines the structure of a vulnerability. + + + + + + + The id of the vulnerability as defined by the risk scoring methodology + For example CVE-2019-15842 (of https://nvd.nist.gov/vuln/detail/CVE-2019-15842) + + + + + + + + The source of the vulnerability where it is documented. + Usually the name of the organization publishing vulnerability information + + + + + + + The url of the vulnerability documentation as provided by the source + For example https://nvd.nist.gov/vuln/detail/CVE-2019-15842 + + + + + + + + The name of the source. For example "National Vulnerability Database" + + + + + + + + + List of the vulnerability ratings as defined by various risk rating methodologies. + + + + + + + + + + + + + List of Common Weaknesses Enumerations (CWEs) codes that describes this vulnerability. + For example 399 (of https://cwe.mitre.org/data/definitions/399.html) + + + + + + + + + + + Description of the vulnerability as provided by the source organization + + + + + + + + The remediation options for the vulnerability if available + + + + + + + A recommendation of how the particular vulnerability can be avoided/mitigated. + + + + + + + + + + + Published advisories of the vulnerability if provided + + + + + + + + + + + References a component by the components bom-ref attribute + + + + + + + + Defines a list of vulnerabilities. + Vulnerabilities are intended to be used inside the BOM component element. + Extending a component ability to declare associated vulnerability information. + Each component element optionally can add a vulnerabilities element. + + + + + + + + + diff --git a/cyclonedx/schema/spdx.schema.json b/cyclonedx/schema/spdx.schema.json new file mode 100644 index 00000000..049708a4 --- /dev/null +++ b/cyclonedx/schema/spdx.schema.json @@ -0,0 +1,491 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://cyclonedx.org/schema/spdx.schema.json", + "$comment": "v1.0-3.10", + "type": "string", + "enum": [ + "0BSD", + "AAL", + "ADSL", + "AFL-1.1", + "AFL-1.2", + "AFL-2.0", + "AFL-2.1", + "AFL-3.0", + "AGPL-1.0", + "AGPL-1.0-only", + "AGPL-1.0-or-later", + "AGPL-3.0", + "AGPL-3.0-only", + "AGPL-3.0-or-later", + "AMDPLPA", + "AML", + "AMPAS", + "ANTLR-PD", + "APAFML", + "APL-1.0", + "APSL-1.0", + "APSL-1.1", + "APSL-1.2", + "APSL-2.0", + "Abstyles", + "Adobe-2006", + "Adobe-Glyph", + "Afmparse", + "Aladdin", + "Apache-1.0", + "Apache-1.1", + "Apache-2.0", + "Artistic-1.0", + "Artistic-1.0-Perl", + "Artistic-1.0-cl8", + "Artistic-2.0", + "BSD-1-Clause", + "BSD-2-Clause", + "BSD-2-Clause-FreeBSD", + "BSD-2-Clause-NetBSD", + "BSD-2-Clause-Patent", + "BSD-2-Clause-Views", + "BSD-3-Clause", + "BSD-3-Clause-Attribution", + "BSD-3-Clause-Clear", + "BSD-3-Clause-LBNL", + "BSD-3-Clause-No-Nuclear-License", + "BSD-3-Clause-No-Nuclear-License-2014", + "BSD-3-Clause-No-Nuclear-Warranty", + "BSD-3-Clause-Open-MPI", + "BSD-4-Clause", + "BSD-4-Clause-UC", + "BSD-Protection", + "BSD-Source-Code", + "BSL-1.0", + "Bahyph", + "Barr", + "Beerware", + "BitTorrent-1.0", + "BitTorrent-1.1", + "BlueOak-1.0.0", + "Borceux", + "CAL-1.0", + "CAL-1.0-Combined-Work-Exception", + "CATOSL-1.1", + "CC-BY-1.0", + "CC-BY-2.0", + "CC-BY-2.5", + "CC-BY-3.0", + "CC-BY-3.0-AT", + "CC-BY-4.0", + "CC-BY-NC-1.0", + "CC-BY-NC-2.0", + "CC-BY-NC-2.5", + "CC-BY-NC-3.0", + "CC-BY-NC-4.0", + "CC-BY-NC-ND-1.0", + "CC-BY-NC-ND-2.0", + "CC-BY-NC-ND-2.5", + "CC-BY-NC-ND-3.0", + "CC-BY-NC-ND-3.0-IGO", + "CC-BY-NC-ND-4.0", + "CC-BY-NC-SA-1.0", + "CC-BY-NC-SA-2.0", + "CC-BY-NC-SA-2.5", + "CC-BY-NC-SA-3.0", + "CC-BY-NC-SA-4.0", + "CC-BY-ND-1.0", + "CC-BY-ND-2.0", + "CC-BY-ND-2.5", + "CC-BY-ND-3.0", + "CC-BY-ND-4.0", + "CC-BY-SA-1.0", + "CC-BY-SA-2.0", + "CC-BY-SA-2.5", + "CC-BY-SA-3.0", + "CC-BY-SA-3.0-AT", + "CC-BY-SA-4.0", + "CC-PDDC", + "CC0-1.0", + "CDDL-1.0", + "CDDL-1.1", + "CDLA-Permissive-1.0", + "CDLA-Sharing-1.0", + "CECILL-1.0", + "CECILL-1.1", + "CECILL-2.0", + "CECILL-2.1", + "CECILL-B", + "CECILL-C", + "CERN-OHL-1.1", + "CERN-OHL-1.2", + "CERN-OHL-P-2.0", + "CERN-OHL-S-2.0", + "CERN-OHL-W-2.0", + "CNRI-Jython", + "CNRI-Python", + "CNRI-Python-GPL-Compatible", + "CPAL-1.0", + "CPL-1.0", + "CPOL-1.02", + "CUA-OPL-1.0", + "Caldera", + "ClArtistic", + "Condor-1.1", + "Crossword", + "CrystalStacker", + "Cube", + "D-FSL-1.0", + "DOC", + "DSDP", + "Dotseqn", + "ECL-1.0", + "ECL-2.0", + "EFL-1.0", + "EFL-2.0", + "EPICS", + "EPL-1.0", + "EPL-2.0", + "EUDatagrid", + "EUPL-1.0", + "EUPL-1.1", + "EUPL-1.2", + "Entessa", + "ErlPL-1.1", + "Eurosym", + "FSFAP", + "FSFUL", + "FSFULLR", + "FTL", + "Fair", + "Frameworx-1.0", + "FreeImage", + "GFDL-1.1", + "GFDL-1.1-invariants-only", + "GFDL-1.1-invariants-or-later", + "GFDL-1.1-no-invariants-only", + "GFDL-1.1-no-invariants-or-later", + "GFDL-1.1-only", + "GFDL-1.1-or-later", + "GFDL-1.2", + "GFDL-1.2-invariants-only", + "GFDL-1.2-invariants-or-later", + "GFDL-1.2-no-invariants-only", + "GFDL-1.2-no-invariants-or-later", + "GFDL-1.2-only", + "GFDL-1.2-or-later", + "GFDL-1.3", + "GFDL-1.3-invariants-only", + "GFDL-1.3-invariants-or-later", + "GFDL-1.3-no-invariants-only", + "GFDL-1.3-no-invariants-or-later", + "GFDL-1.3-only", + "GFDL-1.3-or-later", + "GL2PS", + "GLWTPL", + "GPL-1.0", + "GPL-1.0+", + "GPL-1.0-only", + "GPL-1.0-or-later", + "GPL-2.0", + "GPL-2.0+", + "GPL-2.0-only", + "GPL-2.0-or-later", + "GPL-2.0-with-GCC-exception", + "GPL-2.0-with-autoconf-exception", + "GPL-2.0-with-bison-exception", + "GPL-2.0-with-classpath-exception", + "GPL-2.0-with-font-exception", + "GPL-3.0", + "GPL-3.0+", + "GPL-3.0-only", + "GPL-3.0-or-later", + "GPL-3.0-with-GCC-exception", + "GPL-3.0-with-autoconf-exception", + "Giftware", + "Glide", + "Glulxe", + "HPND", + "HPND-sell-variant", + "HaskellReport", + "Hippocratic-2.1", + "IBM-pibs", + "ICU", + "IJG", + "IPA", + "IPL-1.0", + "ISC", + "ImageMagick", + "Imlib2", + "Info-ZIP", + "Intel", + "Intel-ACPI", + "Interbase-1.0", + "JPNIC", + "JSON", + "JasPer-2.0", + "LAL-1.2", + "LAL-1.3", + "LGPL-2.0", + "LGPL-2.0+", + "LGPL-2.0-only", + "LGPL-2.0-or-later", + "LGPL-2.1", + "LGPL-2.1+", + "LGPL-2.1-only", + "LGPL-2.1-or-later", + "LGPL-3.0", + "LGPL-3.0+", + "LGPL-3.0-only", + "LGPL-3.0-or-later", + "LGPLLR", + "LPL-1.0", + "LPL-1.02", + "LPPL-1.0", + "LPPL-1.1", + "LPPL-1.2", + "LPPL-1.3a", + "LPPL-1.3c", + "Latex2e", + "Leptonica", + "LiLiQ-P-1.1", + "LiLiQ-R-1.1", + "LiLiQ-Rplus-1.1", + "Libpng", + "Linux-OpenIB", + "MIT", + "MIT-0", + "MIT-CMU", + "MIT-advertising", + "MIT-enna", + "MIT-feh", + "MITNFA", + "MPL-1.0", + "MPL-1.1", + "MPL-2.0", + "MPL-2.0-no-copyleft-exception", + "MS-PL", + "MS-RL", + "MTLL", + "MakeIndex", + "MirOS", + "Motosoto", + "MulanPSL-1.0", + "MulanPSL-2.0", + "Multics", + "Mup", + "NASA-1.3", + "NBPL-1.0", + "NCGL-UK-2.0", + "NCSA", + "NGPL", + "NIST-PD", + "NIST-PD-fallback", + "NLOD-1.0", + "NLPL", + "NOSL", + "NPL-1.0", + "NPL-1.1", + "NPOSL-3.0", + "NRL", + "NTP", + "NTP-0", + "Naumen", + "Net-SNMP", + "NetCDF", + "Newsletr", + "Nokia", + "Noweb", + "Nunit", + "O-UDA-1.0", + "OCCT-PL", + "OCLC-2.0", + "ODC-By-1.0", + "ODbL-1.0", + "OFL-1.0", + "OFL-1.0-RFN", + "OFL-1.0-no-RFN", + "OFL-1.1", + "OFL-1.1-RFN", + "OFL-1.1-no-RFN", + "OGC-1.0", + "OGL-Canada-2.0", + "OGL-UK-1.0", + "OGL-UK-2.0", + "OGL-UK-3.0", + "OGTSL", + "OLDAP-1.1", + "OLDAP-1.2", + "OLDAP-1.3", + "OLDAP-1.4", + "OLDAP-2.0", + "OLDAP-2.0.1", + "OLDAP-2.1", + "OLDAP-2.2", + "OLDAP-2.2.1", + "OLDAP-2.2.2", + "OLDAP-2.3", + "OLDAP-2.4", + "OLDAP-2.5", + "OLDAP-2.6", + "OLDAP-2.7", + "OLDAP-2.8", + "OML", + "OPL-1.0", + "OSET-PL-2.1", + "OSL-1.0", + "OSL-1.1", + "OSL-2.0", + "OSL-2.1", + "OSL-3.0", + "OpenSSL", + "PDDL-1.0", + "PHP-3.0", + "PHP-3.01", + "PSF-2.0", + "Parity-6.0.0", + "Parity-7.0.0", + "Plexus", + "PolyForm-Noncommercial-1.0.0", + "PolyForm-Small-Business-1.0.0", + "PostgreSQL", + "Python-2.0", + "QPL-1.0", + "Qhull", + "RHeCos-1.1", + "RPL-1.1", + "RPL-1.5", + "RPSL-1.0", + "RSA-MD", + "RSCPL", + "Rdisc", + "Ruby", + "SAX-PD", + "SCEA", + "SGI-B-1.0", + "SGI-B-1.1", + "SGI-B-2.0", + "SHL-0.5", + "SHL-0.51", + "SISSL", + "SISSL-1.2", + "SMLNJ", + "SMPPL", + "SNIA", + "SPL-1.0", + "SSH-OpenSSH", + "SSH-short", + "SSPL-1.0", + "SWL", + "Saxpath", + "Sendmail", + "Sendmail-8.23", + "SimPL-2.0", + "Sleepycat", + "Spencer-86", + "Spencer-94", + "Spencer-99", + "StandardML-NJ", + "SugarCRM-1.1.3", + "TAPR-OHL-1.0", + "TCL", + "TCP-wrappers", + "TMate", + "TORQUE-1.1", + "TOSL", + "TU-Berlin-1.0", + "TU-Berlin-2.0", + "UCL-1.0", + "UPL-1.0", + "Unicode-DFS-2015", + "Unicode-DFS-2016", + "Unicode-TOU", + "Unlicense", + "VOSTROM", + "VSL-1.0", + "Vim", + "W3C", + "W3C-19980720", + "W3C-20150513", + "WTFPL", + "Watcom-1.0", + "Wsuipa", + "X11", + "XFree86-1.1", + "XSkat", + "Xerox", + "Xnet", + "YPL-1.0", + "YPL-1.1", + "ZPL-1.1", + "ZPL-2.0", + "ZPL-2.1", + "Zed", + "Zend-2.0", + "Zimbra-1.3", + "Zimbra-1.4", + "Zlib", + "blessing", + "bzip2-1.0.5", + "bzip2-1.0.6", + "copyleft-next-0.3.0", + "copyleft-next-0.3.1", + "curl", + "diffmark", + "dvipdfm", + "eCos-2.0", + "eGenix", + "etalab-2.0", + "gSOAP-1.3b", + "gnuplot", + "iMatix", + "libpng-2.0", + "libselinux-1.0", + "libtiff", + "mpich2", + "psfrag", + "psutils", + "wxWindows", + "xinetd", + "xpp", + "zlib-acknowledgement", + "GCC-exception-2.0", + "openvpn-openssl-exception", + "Nokia-Qt-exception-1.1", + "GPL-3.0-linking-exception", + "Fawkes-Runtime-exception", + "u-boot-exception-2.0", + "PS-or-PDF-font-exception-20170817", + "gnu-javamail-exception", + "LGPL-3.0-linking-exception", + "DigiRule-FOSS-exception", + "LLVM-exception", + "Linux-syscall-note", + "GPL-3.0-linking-source-exception", + "Qwt-exception-1.0", + "389-exception", + "mif-exception", + "eCos-exception-2.0", + "CLISP-exception-2.0", + "Bison-exception-2.2", + "Libtool-exception", + "LZMA-exception", + "OpenJDK-assembly-exception-1.0", + "Font-exception-2.0", + "OCaml-LGPL-linking-exception", + "GCC-exception-3.1", + "Bootloader-exception", + "SHL-2.0", + "Classpath-exception-2.0", + "Swift-exception", + "Autoconf-exception-2.0", + "FLTK-exception", + "freertos-exception-2.0", + "Universal-FOSS-exception-1.0", + "WxWindows-exception-3.1", + "OCCT-exception-1.0", + "Autoconf-exception-3.0", + "i2p-gpl-java-exception", + "GPL-CC-1.0", + "Qt-LGPL-exception-1.1", + "SHL-2.1", + "Qt-GPL-exception-1.0" + ] +} diff --git a/cyclonedx/schema/spdx.xsd b/cyclonedx/schema/spdx.xsd new file mode 100644 index 00000000..dbd61b16 --- /dev/null +++ b/cyclonedx/schema/spdx.xsd @@ -0,0 +1,2429 @@ + + + + + + + + + BSD Zero Clause License + + + + + Attribution Assurance License + + + + + Amazon Digital Services License + + + + + Academic Free License v1.1 + + + + + Academic Free License v1.2 + + + + + Academic Free License v2.0 + + + + + Academic Free License v2.1 + + + + + Academic Free License v3.0 + + + + + Affero General Public License v1.0 + + + + + Affero General Public License v1.0 only + + + + + Affero General Public License v1.0 or later + + + + + GNU Affero General Public License v3.0 + + + + + GNU Affero General Public License v3.0 only + + + + + GNU Affero General Public License v3.0 or later + + + + + AMD's plpa_map.c License + + + + + Apple MIT License + + + + + Academy of Motion Picture Arts and Sciences BSD + + + + + ANTLR Software Rights Notice + + + + + Adobe Postscript AFM License + + + + + Adaptive Public License 1.0 + + + + + Apple Public Source License 1.0 + + + + + Apple Public Source License 1.1 + + + + + Apple Public Source License 1.2 + + + + + Apple Public Source License 2.0 + + + + + Abstyles License + + + + + Adobe Systems Incorporated Source Code License Agreement + + + + + Adobe Glyph List License + + + + + Afmparse License + + + + + Aladdin Free Public License + + + + + Apache License 1.0 + + + + + Apache License 1.1 + + + + + Apache License 2.0 + + + + + Artistic License 1.0 + + + + + Artistic License 1.0 (Perl) + + + + + Artistic License 1.0 w/clause 8 + + + + + Artistic License 2.0 + + + + + BSD 1-Clause License + + + + + BSD 2-Clause "Simplified" License + + + + + BSD 2-Clause FreeBSD License + + + + + BSD 2-Clause NetBSD License + + + + + BSD-2-Clause Plus Patent License + + + + + BSD 2-Clause with views sentence + + + + + BSD 3-Clause "New" or "Revised" License + + + + + BSD with attribution + + + + + BSD 3-Clause Clear License + + + + + Lawrence Berkeley National Labs BSD variant license + + + + + BSD 3-Clause No Nuclear License + + + + + BSD 3-Clause No Nuclear License 2014 + + + + + BSD 3-Clause No Nuclear Warranty + + + + + BSD 3-Clause Open MPI variant + + + + + BSD 4-Clause "Original" or "Old" License + + + + + BSD-4-Clause (University of California-Specific) + + + + + BSD Protection License + + + + + BSD Source Code Attribution + + + + + Boost Software License 1.0 + + + + + Bahyph License + + + + + Barr License + + + + + Beerware License + + + + + BitTorrent Open Source License v1.0 + + + + + BitTorrent Open Source License v1.1 + + + + + Blue Oak Model License 1.0.0 + + + + + Borceux license + + + + + Cryptographic Autonomy License 1.0 + + + + + Cryptographic Autonomy License 1.0 (Combined Work Exception) + + + + + Computer Associates Trusted Open Source License 1.1 + + + + + Creative Commons Attribution 1.0 Generic + + + + + Creative Commons Attribution 2.0 Generic + + + + + Creative Commons Attribution 2.5 Generic + + + + + Creative Commons Attribution 3.0 Unported + + + + + Creative Commons Attribution 3.0 Austria + + + + + Creative Commons Attribution 4.0 International + + + + + Creative Commons Attribution Non Commercial 1.0 Generic + + + + + Creative Commons Attribution Non Commercial 2.0 Generic + + + + + Creative Commons Attribution Non Commercial 2.5 Generic + + + + + Creative Commons Attribution Non Commercial 3.0 Unported + + + + + Creative Commons Attribution Non Commercial 4.0 International + + + + + Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic + + + + + Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic + + + + + Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic + + + + + Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported + + + + + Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO + + + + + Creative Commons Attribution Non Commercial No Derivatives 4.0 International + + + + + Creative Commons Attribution Non Commercial Share Alike 1.0 Generic + + + + + Creative Commons Attribution Non Commercial Share Alike 2.0 Generic + + + + + Creative Commons Attribution Non Commercial Share Alike 2.5 Generic + + + + + Creative Commons Attribution Non Commercial Share Alike 3.0 Unported + + + + + Creative Commons Attribution Non Commercial Share Alike 4.0 International + + + + + Creative Commons Attribution No Derivatives 1.0 Generic + + + + + Creative Commons Attribution No Derivatives 2.0 Generic + + + + + Creative Commons Attribution No Derivatives 2.5 Generic + + + + + Creative Commons Attribution No Derivatives 3.0 Unported + + + + + Creative Commons Attribution No Derivatives 4.0 International + + + + + Creative Commons Attribution Share Alike 1.0 Generic + + + + + Creative Commons Attribution Share Alike 2.0 Generic + + + + + Creative Commons Attribution Share Alike 2.5 Generic + + + + + Creative Commons Attribution Share Alike 3.0 Unported + + + + + Creative Commons Attribution-Share Alike 3.0 Austria + + + + + Creative Commons Attribution Share Alike 4.0 International + + + + + Creative Commons Public Domain Dedication and Certification + + + + + Creative Commons Zero v1.0 Universal + + + + + Common Development and Distribution License 1.0 + + + + + Common Development and Distribution License 1.1 + + + + + Community Data License Agreement Permissive 1.0 + + + + + Community Data License Agreement Sharing 1.0 + + + + + CeCILL Free Software License Agreement v1.0 + + + + + CeCILL Free Software License Agreement v1.1 + + + + + CeCILL Free Software License Agreement v2.0 + + + + + CeCILL Free Software License Agreement v2.1 + + + + + CeCILL-B Free Software License Agreement + + + + + CeCILL-C Free Software License Agreement + + + + + CERN Open Hardware Licence v1.1 + + + + + CERN Open Hardware Licence v1.2 + + + + + CERN Open Hardware Licence Version 2 - Permissive + + + + + CERN Open Hardware Licence Version 2 - Strongly Reciprocal + + + + + CERN Open Hardware Licence Version 2 - Weakly Reciprocal + + + + + CNRI Jython License + + + + + CNRI Python License + + + + + CNRI Python Open Source GPL Compatible License Agreement + + + + + Common Public Attribution License 1.0 + + + + + Common Public License 1.0 + + + + + Code Project Open License 1.02 + + + + + CUA Office Public License v1.0 + + + + + Caldera License + + + + + Clarified Artistic License + + + + + Condor Public License v1.1 + + + + + Crossword License + + + + + CrystalStacker License + + + + + Cube License + + + + + Deutsche Freie Software Lizenz + + + + + DOC License + + + + + DSDP License + + + + + Dotseqn License + + + + + Educational Community License v1.0 + + + + + Educational Community License v2.0 + + + + + Eiffel Forum License v1.0 + + + + + Eiffel Forum License v2.0 + + + + + EPICS Open License + + + + + Eclipse Public License 1.0 + + + + + Eclipse Public License 2.0 + + + + + EU DataGrid Software License + + + + + European Union Public License 1.0 + + + + + European Union Public License 1.1 + + + + + European Union Public License 1.2 + + + + + Entessa Public License v1.0 + + + + + Erlang Public License v1.1 + + + + + Eurosym License + + + + + FSF All Permissive License + + + + + FSF Unlimited License + + + + + FSF Unlimited License (with License Retention) + + + + + Freetype Project License + + + + + Fair License + + + + + Frameworx Open License 1.0 + + + + + FreeImage Public License v1.0 + + + + + GNU Free Documentation License v1.1 + + + + + GNU Free Documentation License v1.1 only - invariants + + + + + GNU Free Documentation License v1.1 or later - invariants + + + + + GNU Free Documentation License v1.1 only - no invariants + + + + + GNU Free Documentation License v1.1 or later - no invariants + + + + + GNU Free Documentation License v1.1 only + + + + + GNU Free Documentation License v1.1 or later + + + + + GNU Free Documentation License v1.2 + + + + + GNU Free Documentation License v1.2 only - invariants + + + + + GNU Free Documentation License v1.2 or later - invariants + + + + + GNU Free Documentation License v1.2 only - no invariants + + + + + GNU Free Documentation License v1.2 or later - no invariants + + + + + GNU Free Documentation License v1.2 only + + + + + GNU Free Documentation License v1.2 or later + + + + + GNU Free Documentation License v1.3 + + + + + GNU Free Documentation License v1.3 only - invariants + + + + + GNU Free Documentation License v1.3 or later - invariants + + + + + GNU Free Documentation License v1.3 only - no invariants + + + + + GNU Free Documentation License v1.3 or later - no invariants + + + + + GNU Free Documentation License v1.3 only + + + + + GNU Free Documentation License v1.3 or later + + + + + GL2PS License + + + + + Good Luck With That Public License + + + + + GNU General Public License v1.0 only + + + + + GNU General Public License v1.0 or later + + + + + GNU General Public License v1.0 only + + + + + GNU General Public License v1.0 or later + + + + + GNU General Public License v2.0 only + + + + + GNU General Public License v2.0 or later + + + + + GNU General Public License v2.0 only + + + + + GNU General Public License v2.0 or later + + + + + GNU General Public License v2.0 w/GCC Runtime Library exception + + + + + GNU General Public License v2.0 w/Autoconf exception + + + + + GNU General Public License v2.0 w/Bison exception + + + + + GNU General Public License v2.0 w/Classpath exception + + + + + GNU General Public License v2.0 w/Font exception + + + + + GNU General Public License v3.0 only + + + + + GNU General Public License v3.0 or later + + + + + GNU General Public License v3.0 only + + + + + GNU General Public License v3.0 or later + + + + + GNU General Public License v3.0 w/GCC Runtime Library exception + + + + + GNU General Public License v3.0 w/Autoconf exception + + + + + Giftware License + + + + + 3dfx Glide License + + + + + Glulxe License + + + + + Historical Permission Notice and Disclaimer + + + + + Historical Permission Notice and Disclaimer - sell variant + + + + + Haskell Language Report License + + + + + Hippocratic License 2.1 + + + + + IBM PowerPC Initialization and Boot Software + + + + + ICU License + + + + + Independent JPEG Group License + + + + + IPA Font License + + + + + IBM Public License v1.0 + + + + + ISC License + + + + + ImageMagick License + + + + + Imlib2 License + + + + + Info-ZIP License + + + + + Intel Open Source License + + + + + Intel ACPI Software License Agreement + + + + + Interbase Public License v1.0 + + + + + Japan Network Information Center License + + + + + JSON License + + + + + JasPer License + + + + + Licence Art Libre 1.2 + + + + + Licence Art Libre 1.3 + + + + + GNU Library General Public License v2 only + + + + + GNU Library General Public License v2 or later + + + + + GNU Library General Public License v2 only + + + + + GNU Library General Public License v2 or later + + + + + GNU Lesser General Public License v2.1 only + + + + + GNU Library General Public License v2.1 or later + + + + + GNU Lesser General Public License v2.1 only + + + + + GNU Lesser General Public License v2.1 or later + + + + + GNU Lesser General Public License v3.0 only + + + + + GNU Lesser General Public License v3.0 or later + + + + + GNU Lesser General Public License v3.0 only + + + + + GNU Lesser General Public License v3.0 or later + + + + + Lesser General Public License For Linguistic Resources + + + + + Lucent Public License Version 1.0 + + + + + Lucent Public License v1.02 + + + + + LaTeX Project Public License v1.0 + + + + + LaTeX Project Public License v1.1 + + + + + LaTeX Project Public License v1.2 + + + + + LaTeX Project Public License v1.3a + + + + + LaTeX Project Public License v1.3c + + + + + Latex2e License + + + + + Leptonica License + + + + + Licence Libre du Québec – Permissive version 1.1 + + + + + Licence Libre du Québec – Réciprocité version 1.1 + + + + + Licence Libre du Québec – Réciprocité forte version 1.1 + + + + + libpng License + + + + + Linux Kernel Variant of OpenIB.org license + + + + + MIT License + + + + + MIT No Attribution + + + + + CMU License + + + + + Enlightenment License (e16) + + + + + enna License + + + + + feh License + + + + + MIT +no-false-attribs license + + + + + Mozilla Public License 1.0 + + + + + Mozilla Public License 1.1 + + + + + Mozilla Public License 2.0 + + + + + Mozilla Public License 2.0 (no copyleft exception) + + + + + Microsoft Public License + + + + + Microsoft Reciprocal License + + + + + Matrix Template Library License + + + + + MakeIndex License + + + + + The MirOS Licence + + + + + Motosoto License + + + + + Mulan Permissive Software License, Version 1 + + + + + Mulan Permissive Software License, Version 2 + + + + + Multics License + + + + + Mup License + + + + + NASA Open Source Agreement 1.3 + + + + + Net Boolean Public License v1 + + + + + Non-Commercial Government Licence + + + + + University of Illinois/NCSA Open Source License + + + + + Nethack General Public License + + + + + NIST Public Domain Notice + + + + + NIST Public Domain Notice with license fallback + + + + + Norwegian Licence for Open Government Data + + + + + No Limit Public License + + + + + Netizen Open Source License + + + + + Netscape Public License v1.0 + + + + + Netscape Public License v1.1 + + + + + Non-Profit Open Software License 3.0 + + + + + NRL License + + + + + NTP License + + + + + NTP No Attribution + + + + + Naumen Public License + + + + + Net-SNMP License + + + + + NetCDF license + + + + + Newsletr License + + + + + Nokia Open Source License + + + + + Noweb License + + + + + Nunit License + + + + + Open Use of Data Agreement v1.0 + + + + + Open CASCADE Technology Public License + + + + + OCLC Research Public License 2.0 + + + + + Open Data Commons Attribution License v1.0 + + + + + ODC Open Database License v1.0 + + + + + SIL Open Font License 1.0 + + + + + SIL Open Font License 1.0 with Reserved Font Name + + + + + SIL Open Font License 1.0 with no Reserved Font Name + + + + + SIL Open Font License 1.1 + + + + + SIL Open Font License 1.1 with Reserved Font Name + + + + + SIL Open Font License 1.1 with no Reserved Font Name + + + + + OGC Software License, Version 1.0 + + + + + Open Government Licence - Canada + + + + + Open Government Licence v1.0 + + + + + Open Government Licence v2.0 + + + + + Open Government Licence v3.0 + + + + + Open Group Test Suite License + + + + + Open LDAP Public License v1.1 + + + + + Open LDAP Public License v1.2 + + + + + Open LDAP Public License v1.3 + + + + + Open LDAP Public License v1.4 + + + + + Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B) + + + + + Open LDAP Public License v2.0.1 + + + + + Open LDAP Public License v2.1 + + + + + Open LDAP Public License v2.2 + + + + + Open LDAP Public License v2.2.1 + + + + + Open LDAP Public License 2.2.2 + + + + + Open LDAP Public License v2.3 + + + + + Open LDAP Public License v2.4 + + + + + Open LDAP Public License v2.5 + + + + + Open LDAP Public License v2.6 + + + + + Open LDAP Public License v2.7 + + + + + Open LDAP Public License v2.8 + + + + + Open Market License + + + + + Open Public License v1.0 + + + + + OSET Public License version 2.1 + + + + + Open Software License 1.0 + + + + + Open Software License 1.1 + + + + + Open Software License 2.0 + + + + + Open Software License 2.1 + + + + + Open Software License 3.0 + + + + + OpenSSL License + + + + + ODC Public Domain Dedication & License 1.0 + + + + + PHP License v3.0 + + + + + PHP License v3.01 + + + + + Python Software Foundation License 2.0 + + + + + The Parity Public License 6.0.0 + + + + + The Parity Public License 7.0.0 + + + + + Plexus Classworlds License + + + + + PolyForm Noncommercial License 1.0.0 + + + + + PolyForm Small Business License 1.0.0 + + + + + PostgreSQL License + + + + + Python License 2.0 + + + + + Q Public License 1.0 + + + + + Qhull License + + + + + Red Hat eCos Public License v1.1 + + + + + Reciprocal Public License 1.1 + + + + + Reciprocal Public License 1.5 + + + + + RealNetworks Public Source License v1.0 + + + + + RSA Message-Digest License + + + + + Ricoh Source Code Public License + + + + + Rdisc License + + + + + Ruby License + + + + + Sax Public Domain Notice + + + + + SCEA Shared Source License + + + + + SGI Free Software License B v1.0 + + + + + SGI Free Software License B v1.1 + + + + + SGI Free Software License B v2.0 + + + + + Solderpad Hardware License v0.5 + + + + + Solderpad Hardware License, Version 0.51 + + + + + Sun Industry Standards Source License v1.1 + + + + + Sun Industry Standards Source License v1.2 + + + + + Standard ML of New Jersey License + + + + + Secure Messaging Protocol Public License + + + + + SNIA Public License 1.1 + + + + + Sun Public License v1.0 + + + + + SSH OpenSSH license + + + + + SSH short notice + + + + + Server Side Public License, v 1 + + + + + Scheme Widget Library (SWL) Software License Agreement + + + + + Saxpath License + + + + + Sendmail License + + + + + Sendmail License 8.23 + + + + + Simple Public License 2.0 + + + + + Sleepycat License + + + + + Spencer License 86 + + + + + Spencer License 94 + + + + + Spencer License 99 + + + + + Standard ML of New Jersey License + + + + + SugarCRM Public License v1.1.3 + + + + + TAPR Open Hardware License v1.0 + + + + + TCL/TK License + + + + + TCP Wrappers License + + + + + TMate Open Source License + + + + + TORQUE v2.5+ Software License v1.1 + + + + + Trusster Open Source License + + + + + Technische Universitaet Berlin License 1.0 + + + + + Technische Universitaet Berlin License 2.0 + + + + + Upstream Compatibility License v1.0 + + + + + Universal Permissive License v1.0 + + + + + Unicode License Agreement - Data Files and Software (2015) + + + + + Unicode License Agreement - Data Files and Software (2016) + + + + + Unicode Terms of Use + + + + + The Unlicense + + + + + VOSTROM Public License for Open Source + + + + + Vovida Software License v1.0 + + + + + Vim License + + + + + W3C Software Notice and License (2002-12-31) + + + + + W3C Software Notice and License (1998-07-20) + + + + + W3C Software Notice and Document License (2015-05-13) + + + + + Do What The F*ck You Want To Public License + + + + + Sybase Open Watcom Public License 1.0 + + + + + Wsuipa License + + + + + X11 License + + + + + XFree86 License 1.1 + + + + + XSkat License + + + + + Xerox License + + + + + X.Net License + + + + + Yahoo! Public License v1.0 + + + + + Yahoo! Public License v1.1 + + + + + Zope Public License 1.1 + + + + + Zope Public License 2.0 + + + + + Zope Public License 2.1 + + + + + Zed License + + + + + Zend License v2.0 + + + + + Zimbra Public License v1.3 + + + + + Zimbra Public License v1.4 + + + + + zlib License + + + + + SQLite Blessing + + + + + bzip2 and libbzip2 License v1.0.5 + + + + + bzip2 and libbzip2 License v1.0.6 + + + + + copyleft-next 0.3.0 + + + + + copyleft-next 0.3.1 + + + + + curl License + + + + + diffmark license + + + + + dvipdfm License + + + + + eCos license version 2.0 + + + + + eGenix.com Public License 1.1.0 + + + + + Etalab Open License 2.0 + + + + + gSOAP Public License v1.3b + + + + + gnuplot License + + + + + iMatix Standard Function Library Agreement + + + + + PNG Reference Library version 2 + + + + + libselinux public domain notice + + + + + libtiff License + + + + + mpich2 License + + + + + psfrag License + + + + + psutils License + + + + + wxWindows Library License + + + + + xinetd License + + + + + XPP License + + + + + zlib/libpng License with Acknowledgement + + + + + + GCC Runtime Library exception 2.0 + + + + + OpenVPN OpenSSL Exception + + + + + Nokia Qt LGPL exception 1.1 + + + + + GPL-3.0 Linking Exception + + + + + Fawkes Runtime Exception + + + + + U-Boot exception 2.0 + + + + + PS/PDF font exception (2017-08-17) + + + + + GNU JavaMail exception + + + + + LGPL-3.0 Linking Exception + + + + + DigiRule FOSS License Exception + + + + + LLVM Exception + + + + + Linux Syscall Note + + + + + GPL-3.0 Linking Exception (with Corresponding Source) + + + + + Qwt exception 1.0 + + + + + 389 Directory Server Exception + + + + + Macros and Inline Functions Exception + + + + + eCos exception 2.0 + + + + + CLISP exception 2.0 + + + + + Bison exception 2.2 + + + + + Libtool Exception + + + + + LZMA exception + + + + + OpenJDK Assembly exception 1.0 + + + + + Font exception 2.0 + + + + + OCaml LGPL Linking Exception + + + + + GCC Runtime Library exception 3.1 + + + + + Bootloader Distribution Exception + + + + + Solderpad Hardware License v2.0 + + + + + Classpath exception 2.0 + + + + + Swift Exception + + + + + Autoconf exception 2.0 + + + + + FLTK exception + + + + + FreeRTOS Exception 2.0 + + + + + Universal FOSS Exception, Version 1.0 + + + + + WxWindows Library Exception 3.1 + + + + + Open CASCADE Exception 1.0 + + + + + Autoconf exception 3.0 + + + + + i2p GPL+Java Exception + + + + + GPL Cooperation Commitment 1.0 + + + + + Qt LGPL exception 1.1 + + + + + Solderpad Hardware License v2.1 + + + + + Qt GPL exception 1.0 + + + + + + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..916c2db7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +setuptools>=50.3.2 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..3480374b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 \ No newline at end of file diff --git a/setup.py b/setup.py index e69de29b..121616c1 100644 --- a/setup.py +++ b/setup.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import os.path +from setuptools import setup, find_packages + +script_path = os.path.dirname(__file__) + +setup( + name='cyclonedx-python-lib', + version=open(os.path.join(script_path, 'VERSION')).read(), + url="https://github.com/sonatype-nexus-community/cyclonedx-python-lib", + author="Sonatype Community", + author_email="community-group@sonatype.com", + description="A library for producing CycloneDX SBOM (Software Bill of Materials) files.", + long_description=open(os.path.join(script_path, 'README.md')).read(), + long_description_content_type="text/markdown", + keywords=["BOM", "SBOM", "SCA", "OWASP"], + license="Apache-2.0", + classifiers=[ + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: Legal Industry', + 'Intended Audience :: System Administrators', + 'Topic :: Security', + 'Topic :: Software Development', + 'Topic :: System :: Software Distribution', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3' + ], + packages=find_packages(), + python_requires='>=3.6', + package_data={ + 'cyclonedx': ['schema/*.json', 'schema/*.xsd', 'schema/ext/*.json', 'schema/ext/*.xsd'] + }, + include_package_data=True, + install_requires=open(os.path.join(script_path, 'requirements.txt')).read() +) From 1def2015d3aad4b58980d9b86cca840f19ac4ee6 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 27 Aug 2021 12:53:15 +0100 Subject: [PATCH 02/43] Added tox config with flake8 and py3.9 support. --- cyclonedx/generator.py | 0 cyclonedx/model/__init__.py | 0 cyclonedx/model/bom.py | 0 cyclonedx/model/cyclonedx.py | 0 requirements-test.txt | 2 ++ setup.py | 1 + tox.ini | 20 ++++++++++++++++++++ 7 files changed, 23 insertions(+) create mode 100644 cyclonedx/generator.py create mode 100644 cyclonedx/model/__init__.py create mode 100644 cyclonedx/model/bom.py create mode 100644 cyclonedx/model/cyclonedx.py create mode 100644 requirements-test.txt create mode 100644 tox.ini diff --git a/cyclonedx/generator.py b/cyclonedx/generator.py new file mode 100644 index 00000000..e69de29b diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py new file mode 100644 index 00000000..e69de29b diff --git a/cyclonedx/model/cyclonedx.py b/cyclonedx/model/cyclonedx.py new file mode 100644 index 00000000..e69de29b diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 00000000..6bf72e58 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,2 @@ +setuptools>=50.3.2 +tox==3.24.3 \ No newline at end of file diff --git a/setup.py b/setup.py index 121616c1..3c4ff0bd 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ package_data={ 'cyclonedx': ['schema/*.json', 'schema/*.xsd', 'schema/ext/*.json', 'schema/ext/*.xsd'] }, + data_files=['README.md', 'requirements.txt', 'VERSION'], include_package_data=True, install_requires=open(os.path.join(script_path, 'requirements.txt')).read() ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..aed663b2 --- /dev/null +++ b/tox.ini @@ -0,0 +1,20 @@ +[tox] +minversion=3.9.0 +envlist = flake8,py39 + +[testenv] +;changedir = tests +deps = -r{toxinidir}/requirements-test.txt +commands = python -m unittest discover -s tests + +[testenv:flake8] +basepython = python3 +skip_install = true +deps = + flake8 +commands = flake8 cyclonedx/ tests/ setup.py + +[flake8] +ignore = E305 +exclude = .git,__pycache__ +max-line-length = 120 \ No newline at end of file From 6ac5dc29fb4bc52f66698966e0b570588621be72 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 27 Aug 2021 17:12:51 +0100 Subject: [PATCH 03/43] Basic structure without any output generation available (very basic Component definition). --- cyclonedx/model/bom.py | 36 ++++++++++++++ cyclonedx/model/cyclonedx.py | 46 +++++++++++++++++ cyclonedx/output/__init__.py | 0 cyclonedx/output/xml.py | 9 ++++ cyclonedx/parser/__init__.py | 14 ++++++ cyclonedx/parser/environment.py | 16 ++++++ cyclonedx/parser/requirements.py | 32 ++++++++++++ requirements-test.txt | 2 + requirements.txt | 2 + tests/__init__.py | 0 tests/fixtures/requirements-example-1.txt | 3 ++ tests/fixtures/requirements-simple.txt | 1 + tests/test_bom.py | 21 ++++++++ tests/test_component.py | 60 +++++++++++++++++++++++ tests/test_parser_environment.py | 16 ++++++ tests/test_parser_requirements.py | 23 +++++++++ 16 files changed, 281 insertions(+) create mode 100644 cyclonedx/output/__init__.py create mode 100644 cyclonedx/output/xml.py create mode 100644 cyclonedx/parser/__init__.py create mode 100644 cyclonedx/parser/environment.py create mode 100644 cyclonedx/parser/requirements.py create mode 100644 tests/__init__.py create mode 100644 tests/fixtures/requirements-example-1.txt create mode 100644 tests/fixtures/requirements-simple.txt create mode 100644 tests/test_bom.py create mode 100644 tests/test_component.py create mode 100644 tests/test_parser_environment.py create mode 100644 tests/test_parser_requirements.py diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index e69de29b..764a5b00 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -0,0 +1,36 @@ +from typing import List +from .cyclonedx import Component +from ..parser import BaseParser + + +class Bom: + """ + This is our internal representation of the BOM. + + We can pass a BOM instance to a Generator to produce CycloneDX in the required format and according + to the requested schema version. + """ + + _components: List[Component] = [] + + @staticmethod + def from_parser(parser: BaseParser): + bom = Bom() + bom.add_components(parser.get_components()) + return bom + + def __init__(self): + self._components.clear() + + def add_component(self, component: Component): + self._components.add(component) + + def add_components(self, components: List[Component]): + self._components = self._components + components + + def component_count(self) -> int: + return len(self._components) + + def has_component(self, component: Component) -> bool: + print("Checking if {} is contained within {}".format(component, self._components)) + return component in self._components diff --git a/cyclonedx/model/cyclonedx.py b/cyclonedx/model/cyclonedx.py index e69de29b..d0188f7d 100644 --- a/cyclonedx/model/cyclonedx.py +++ b/cyclonedx/model/cyclonedx.py @@ -0,0 +1,46 @@ +from enum import Enum + +PURL_TYPE_PREFIX = 'pypi' + + +class ComponentType(Enum): + """ + Enum object that defines the permissable 'types' for a Component according to the CycloneDX + schemas. + """ + APPLICATION = 'application' + CONTAINER = 'container' + DEVICE = 'device' + FILE = 'file' + FIRMWARE = 'firmware' + FRAMEWORK = 'framework' + LIBRARY = 'library' + OPERATING_SYSTEM = 'operating-system' + + +class Component: + """ + An object that mirrors the Component type in the CycloneDX schema. + """ + _type: ComponentType + _name: str + _version: str + _qualifiers: str + + def __init__(self, name: str, version: str, qualifiers: str = None, type: ComponentType = ComponentType.LIBRARY): + self._name = name + self._version = version + self._type = type + self._qualifiers = qualifiers + + def get_purl(self) -> str: + base_purl = 'pkg:{}/{}@{}'.format(PURL_TYPE_PREFIX, self._name, self._version) + if self._qualifiers: + base_purl = '{}?{}'.format(base_purl, self._qualifiers) + return base_purl + + def __eq__(self, other): + return other.get_purl() == self.get_purl() + + def __repr__(self): + return ''.format(self._name, self._version) diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py new file mode 100644 index 00000000..112b91a6 --- /dev/null +++ b/cyclonedx/output/xml.py @@ -0,0 +1,9 @@ +from cyclonedx.model.bom import Bom + + +class Xml: + + _bom: Bom + + def __init__(self, bom: Bom): + self._bom = bom diff --git a/cyclonedx/parser/__init__.py b/cyclonedx/parser/__init__.py new file mode 100644 index 00000000..cb4f12d9 --- /dev/null +++ b/cyclonedx/parser/__init__.py @@ -0,0 +1,14 @@ +from abc import ABC +from typing import List + +from ..model.cyclonedx import Component + + +class BaseParser(ABC): + _components: List[Component] = [] + + def component_count(self) -> int: + return len(self._components) + + def get_components(self) -> List[Component]: + return self._components diff --git a/cyclonedx/parser/environment.py b/cyclonedx/parser/environment.py new file mode 100644 index 00000000..8bb703e1 --- /dev/null +++ b/cyclonedx/parser/environment.py @@ -0,0 +1,16 @@ +from . import BaseParser + +from ..model.cyclonedx import Component + + +class EnvironmentParser(BaseParser): + """ + This will look at the current Python environment and list out all installed packages. + + Best used when you have virtual Python environments per project. + """ + + def __init__(self): + import pkg_resources + for i in iter(pkg_resources.working_set): + self._components.append(Component(name=i.project_name, version=i.version, type='pypi')) diff --git a/cyclonedx/parser/requirements.py b/cyclonedx/parser/requirements.py new file mode 100644 index 00000000..ef7827ff --- /dev/null +++ b/cyclonedx/parser/requirements.py @@ -0,0 +1,32 @@ +import pkg_resources + +from . import BaseParser + +from ..model.cyclonedx import Component + + +class RequirementsParser(BaseParser): + + def __init__(self, requirements_content: str): + requirements = pkg_resources.parse_requirements(requirements_content) + for requirement in requirements: + """ + @todo + Note that the below line will get the first (lowest) version specified in the Requirement and + ignore the operator (it might not be ==). This is passed to the Component. + + For example if a requirement was listed as: "PickyThing>1.6,<=1.9,!=1.8.6", we'll be interpretting this + as if it were written "PickyThing==1.6" + """ + (op, version) = requirement.specs[0] + self._components.append(Component( + name=requirement.project_name, version=version + )) + + +class RequirementsFileParser(RequirementsParser): + + def __init__(self, requirements_file: str): + with open(requirements_file) as r: + super(RequirementsFileParser, self).__init__(requirements_content=r.read()) + r.close() diff --git a/requirements-test.txt b/requirements-test.txt index 6bf72e58..fd99ba6e 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,2 +1,4 @@ +packageurl-python==0.9.4 +requirements_parser==0.2.0 setuptools>=50.3.2 tox==3.24.3 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 916c2db7..ea71d69b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ +packageurl-python>=0.9.4 +requirements_parser>=0.2.0 setuptools>=50.3.2 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/requirements-example-1.txt b/tests/fixtures/requirements-example-1.txt new file mode 100644 index 00000000..ea71d69b --- /dev/null +++ b/tests/fixtures/requirements-example-1.txt @@ -0,0 +1,3 @@ +packageurl-python>=0.9.4 +requirements_parser>=0.2.0 +setuptools>=50.3.2 \ No newline at end of file diff --git a/tests/fixtures/requirements-simple.txt b/tests/fixtures/requirements-simple.txt new file mode 100644 index 00000000..890d7da2 --- /dev/null +++ b/tests/fixtures/requirements-simple.txt @@ -0,0 +1 @@ +setuptools==50.3.2 \ No newline at end of file diff --git a/tests/test_bom.py b/tests/test_bom.py new file mode 100644 index 00000000..272d4b51 --- /dev/null +++ b/tests/test_bom.py @@ -0,0 +1,21 @@ +from unittest import TestCase + +import os + +from cyclonedx.model.bom import Bom +from cyclonedx.model.cyclonedx import Component +from cyclonedx.parser.requirements import RequirementsFileParser + + +class TestBom(TestCase): + + def test_bom_simple(self): + parser = RequirementsFileParser( + requirements_file=os.path.join(os.path.dirname(__file__), 'fixtures/requirements-simple.txt') + ) + bom = Bom.from_parser(parser=parser) + + self.assertEqual(bom.component_count(), 1) + self.assertTrue(bom.has_component( + Component(name='setuptools', version='50.3.2') + )) diff --git a/tests/test_component.py b/tests/test_component.py new file mode 100644 index 00000000..94209959 --- /dev/null +++ b/tests/test_component.py @@ -0,0 +1,60 @@ +from unittest import TestCase + +from cyclonedx.model.cyclonedx import Component +from packageurl import PackageURL + + +class TestComponent(TestCase): + _component: Component + + @classmethod + def setUpClass(cls) -> None: + cls._component = Component(name='setuptools', version='50.3.2').get_purl() + cls._component_with_qualifiers = Component(name='setuptools', version='50.3.2', + qualifiers='extension=tar.gz').get_purl() + + def test_purl_correct(self): + self.assertEqual( + str(PackageURL( + type='pypi', name='setuptools', version='50.3.2' + )), + TestComponent._component + ) + + def test_purl_incorrect_version(self): + purl = PackageURL( + type='pypi', name='setuptools', version='50.3.1' + ) + self.assertNotEqual( + str(purl), + TestComponent._component + ) + self.assertEqual(purl.type, 'pypi') + self.assertEqual(purl.name, 'setuptools') + self.assertEqual(purl.version, '50.3.1') + + def test_purl_incorrect_name(self): + purl = PackageURL( + type='pypi', name='setuptoolz', version='50.3.2' + ) + self.assertNotEqual( + str(purl), + TestComponent._component + ) + self.assertEqual(purl.type, 'pypi') + self.assertEqual(purl.name, 'setuptoolz') + self.assertEqual(purl.version, '50.3.2') + + def test_purl_with_qualifiers(self): + purl = PackageURL( + type='pypi', name='setuptools', version='50.3.2', qualifiers='extension=tar.gz' + ) + self.assertEqual( + str(purl), + TestComponent._component_with_qualifiers + ) + self.assertNotEqual( + str(purl), + TestComponent._component + ) + self.assertEqual(purl.qualifiers, {'extension': 'tar.gz'}) diff --git a/tests/test_parser_environment.py b/tests/test_parser_environment.py new file mode 100644 index 00000000..b8887d52 --- /dev/null +++ b/tests/test_parser_environment.py @@ -0,0 +1,16 @@ +from unittest import TestCase + +from cyclonedx.parser.environment import EnvironmentParser + + +class TestRequirementsParser(TestCase): + + def test_simple(self): + """ + @todo This test is a vague as it will detect the unique environment where tests are being executed - + so is this valid? + + :return: + """ + parser = EnvironmentParser() + self.assertGreater(parser.component_count(), 1) diff --git a/tests/test_parser_requirements.py b/tests/test_parser_requirements.py new file mode 100644 index 00000000..7ee2515d --- /dev/null +++ b/tests/test_parser_requirements.py @@ -0,0 +1,23 @@ +import os +from unittest import TestCase + +from cyclonedx.parser.requirements import RequirementsParser + + +class TestRequirementsParser(TestCase): + + def test_simple(self): + with open(os.path.join(os.path.dirname(__file__), 'fixtures/requirements-simple.txt')) as r: + parser = RequirementsParser( + requirements_content=r.read() + ) + r.close() + self.assertTrue(1, parser.component_count()) + + def test_example_1(self): + with open(os.path.join(os.path.dirname(__file__), 'fixtures/requirements-example-1.txt')) as r: + parser = RequirementsParser( + requirements_content=r.read() + ) + r.close() + self.assertTrue(3, parser.component_count()) From cce130f53a7c73554015ce672cbe8799e863e64b Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 31 Aug 2021 13:44:39 +0100 Subject: [PATCH 04/43] Fixed issue reported by Flake8. Ensuring tests run on PY 3.9. --- cyclonedx/parser/requirements.py | 2 +- setup.py | 2 +- tox.ini | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cyclonedx/parser/requirements.py b/cyclonedx/parser/requirements.py index ef7827ff..84516325 100644 --- a/cyclonedx/parser/requirements.py +++ b/cyclonedx/parser/requirements.py @@ -14,7 +14,7 @@ def __init__(self, requirements_content: str): @todo Note that the below line will get the first (lowest) version specified in the Requirement and ignore the operator (it might not be ==). This is passed to the Component. - + For example if a requirement was listed as: "PickyThing>1.6,<=1.9,!=1.8.6", we'll be interpretting this as if it were written "PickyThing==1.6" """ diff --git a/setup.py b/setup.py index 3c4ff0bd..14e9e80a 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ package_data={ 'cyclonedx': ['schema/*.json', 'schema/*.xsd', 'schema/ext/*.json', 'schema/ext/*.xsd'] }, - data_files=['README.md', 'requirements.txt', 'VERSION'], + data_files=[('', ['README.md', 'requirements.txt', 'requirements-test.txt', 'VERSION'])], include_package_data=True, install_requires=open(os.path.join(script_path, 'requirements.txt')).read() ) diff --git a/tox.ini b/tox.ini index aed663b2..e91eef25 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,8 @@ minversion=3.9.0 envlist = flake8,py39 [testenv] -;changedir = tests -deps = -r{toxinidir}/requirements-test.txt +deps = + -r{toxinidir}/requirements-test.txt commands = python -m unittest discover -s tests [testenv:flake8] @@ -12,6 +12,7 @@ basepython = python3 skip_install = true deps = flake8 + -r{toxinidir}/requirements-test.txt commands = flake8 cyclonedx/ tests/ setup.py [flake8] From 460c62487e66df750a99e10a62bf19bf0baf2e76 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 31 Aug 2021 13:55:38 +0100 Subject: [PATCH 05/43] Added a little more information to the README. --- README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e1388af6..3217df89 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,32 @@ # Python Library for generating CycloneDX -_Coming soon!_ +This CycloneDX module for Python can generate valid CycloneDX bill-of-material document containing an aggregate of all +project dependencies. + +This module is not designed for standalone use. If you're looking for a tool to run to generate CycloneDX +software bill-of-materials documents, why not checkout: +- [Jake](https://github.com/sonatype-nexus-community/jake) + +Or you can use this module yourself in your application to generate SBOMs. + +CycloneDX is a lightweight BOM specification that is easily created, human-readable, and simple to parse. + +## Installation + +Install from pypi.org as you would any other Python module: + +``` +pip install cyclonedx-python-lib +``` + +## Architecture + +This module break out into three key areas: +1. **Parser**: Use a parser that suits your needs to automatically gather information + about your environment or application +2. **Model**: Internal models used to unify data from different parsers +3. **Output**: Choose and configure an output which allows you to define output format as well + as the CycloneDX schema version ## The Fine Print From a614f3e9cc6210a25daff79e4ec428f15221cc1e Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 31 Aug 2021 16:39:11 +0100 Subject: [PATCH 06/43] Initial skeleton tests for output genereation. --- cyclonedx/model/bom.py | 1 - cyclonedx/output/__init__.py | 58 ++++++++++++++++++++++++++++++++++++ cyclonedx/output/xml.py | 33 ++++++++++++++++---- tests/test_output_generic.py | 19 ++++++++++++ 4 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 tests/test_output_generic.py diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 764a5b00..da845402 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -32,5 +32,4 @@ def component_count(self) -> int: return len(self._components) def has_component(self, component: Component) -> bool: - print("Checking if {} is contained within {}".format(component, self._components)) return component in self._components diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index e69de29b..2a5e1bf7 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -0,0 +1,58 @@ +import importlib + +from abc import ABC, abstractmethod +from enum import Enum + +from ..model.bom import Bom + + +class OutputFormat(Enum): + JSON: str = 'Json' + XML: str = 'Xml' + + +class SchemaVersion(Enum): + V1_2: str = 'V1Dot2' + V1_3: str = 'V1Dot3' + + +DEFAULT_SCHEMA_VERSION = SchemaVersion.V1_3 + + +class BaseOutput(ABC): + _bom: Bom + + def __init__(self, bom: Bom = None): + self._bom = bom + + def set_bom(self, bom: Bom): + self._bom = bom + + @abstractmethod + def output_as_string(self) -> str: + pass + + @abstractmethod + def output_to_file(self, filename: str): + pass + + +def get_instance(bom: Bom = None, output_format: OutputFormat = OutputFormat.XML, + schema_version: SchemaVersion = DEFAULT_SCHEMA_VERSION) -> BaseOutput: + """ + Helper method to quickly get the correct output class/formatter. + + Pass in your BOM and optionally an output format and schema version (defaults to XML and latest schema version). + + :param bom: Bom + :param output_format: OutputFormat + :param schema_version: SchemaVersion + :return: + """ + try: + module = importlib.import_module(f"cyclonedx.output.{output_format.value.lower()}") + output_klass = getattr(module, f"{output_format.value}{schema_version.value}") + except (ImportError, AttributeError): + raise ValueError(f"Unknown format {output_format.value.lower()!r}") from None + + return output_klass(bom=bom) diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 112b91a6..39d9ab2e 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -1,9 +1,32 @@ -from cyclonedx.model.bom import Bom +from abc import abstractmethod +from . import BaseOutput -class Xml: - _bom: Bom +class Xml(BaseOutput): - def __init__(self, bom: Bom): - self._bom = bom + @abstractmethod + def output_as_string(self) -> str: + pass + + @abstractmethod + def output_to_file(self, filename: str): + pass + + +class XmlV1Dot2(Xml): + + def output_as_string(self) -> str: + return '' + + def output_to_file(self, filename: str): + return '' + + +class XmlV1Dot3(Xml): + + def output_as_string(self) -> str: + return '' + + def output_to_file(self, filename: str): + return '' diff --git a/tests/test_output_generic.py b/tests/test_output_generic.py new file mode 100644 index 00000000..74c7892c --- /dev/null +++ b/tests/test_output_generic.py @@ -0,0 +1,19 @@ +from unittest import TestCase + +from cyclonedx.output import get_instance, OutputFormat, SchemaVersion +from cyclonedx.output.xml import XmlV1Dot3 + + +class TestOutputGeneric(TestCase): + + def test_get_instance_default(self): + i = get_instance() + self.assertIsInstance(i, XmlV1Dot3) + + def test_get_instance_xml(self): + i = get_instance(output_format=OutputFormat.XML) + self.assertIsInstance(i, XmlV1Dot3) + + def test_get_instance_xml_v1_3(self): + i = get_instance(output_format=OutputFormat.XML, schema_version=SchemaVersion.V1_3) + self.assertIsInstance(i, XmlV1Dot3) \ No newline at end of file From 9a5623098ff712df0cefbd2327e8058f9ac74e17 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 31 Aug 2021 18:25:33 +0100 Subject: [PATCH 07/43] Updated CircleCI config to run tox. Fixed fomratting in tests. --- .circleci/config.yml | 22 ++++++++++------------ tests/test_output_generic.py | 2 +- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 990c6caf..29a7788f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,13 +15,13 @@ version: 2.1 executors: - python: + python39: docker: - - image: circleci/python:3.7 + - image: circleci/python:3.9 jobs: publish: - executor: python + executor: python39 environment: PIPENV_VENV_IN_PROJECT: true steps: @@ -39,28 +39,26 @@ jobs: # TODO: perform publish steps, maybe using python-semantic-release build: - executor: python + executor: python39 environment: PIPENV_VENV_IN_PROJECT: true steps: - checkout - run: - name: Setup Python environment + name: Install Tox command: | - # TODO: do stuff, like setup .venv, poetry, pip etc. + pip install tox - run: - name: Run tests + name: Run tox command: | - # TODO: maybe run pylint and run those tests + tox --result-json=.tox/results.json - run: name: Run self scan command: | # TODO: audit with jake maybe? - - store_test_results: # Upload test results for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ - path: test-results - store_artifacts: # Upload test summary for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ - path: test-results - destination: tr1 + path: .tox/results.json + destination: tox-logs workflows: version: 2 diff --git a/tests/test_output_generic.py b/tests/test_output_generic.py index 74c7892c..60e6eab0 100644 --- a/tests/test_output_generic.py +++ b/tests/test_output_generic.py @@ -16,4 +16,4 @@ def test_get_instance_xml(self): def test_get_instance_xml_v1_3(self): i = get_instance(output_format=OutputFormat.XML, schema_version=SchemaVersion.V1_3) - self.assertIsInstance(i, XmlV1Dot3) \ No newline at end of file + self.assertIsInstance(i, XmlV1Dot3) From 35bdfca4fc01cdb3fa7ab6fb37b1c05eaa7189ec Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 31 Aug 2021 19:01:43 +0100 Subject: [PATCH 08/43] WIP: Starting to generate XML output for BOMs --- cyclonedx/model/bom.py | 5 +- cyclonedx/model/cyclonedx.py | 9 ++++ cyclonedx/output/__init__.py | 3 ++ cyclonedx/output/xml.py | 93 +++++++++++++++++++++++++++++++----- tests/test_output_xml.py | 19 ++++++++ 5 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 tests/test_output_xml.py diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index da845402..925a105d 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -23,7 +23,7 @@ def __init__(self): self._components.clear() def add_component(self, component: Component): - self._components.add(component) + self._components.append(component) def add_components(self, components: List[Component]): self._components = self._components + components @@ -31,5 +31,8 @@ def add_components(self, components: List[Component]): def component_count(self) -> int: return len(self._components) + def get_components(self) -> List[Component]: + return self._components + def has_component(self, component: Component) -> bool: return component in self._components diff --git a/cyclonedx/model/cyclonedx.py b/cyclonedx/model/cyclonedx.py index d0188f7d..e07e4701 100644 --- a/cyclonedx/model/cyclonedx.py +++ b/cyclonedx/model/cyclonedx.py @@ -33,12 +33,21 @@ def __init__(self, name: str, version: str, qualifiers: str = None, type: Compon self._type = type self._qualifiers = qualifiers + def get_name(self) -> str: + return self._name + def get_purl(self) -> str: base_purl = 'pkg:{}/{}@{}'.format(PURL_TYPE_PREFIX, self._name, self._version) if self._qualifiers: base_purl = '{}?{}'.format(base_purl, self._qualifiers) return base_purl + def get_type(self) -> ComponentType: + return self._type + + def get_version(self) -> str: + return self._version + def __eq__(self, other): return other.get_purl() == self.get_purl() diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index 2a5e1bf7..261d5f20 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -25,6 +25,9 @@ class BaseOutput(ABC): def __init__(self, bom: Bom = None): self._bom = bom + def get_bom(self) -> Bom: + return self._bom + def set_bom(self, bom: Bom): self._bom = bom diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 39d9ab2e..3e54a130 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -1,32 +1,101 @@ from abc import abstractmethod +from xml.etree import ElementTree from . import BaseOutput +from ..model.cyclonedx import Component + + +def _xml_pretty_print(elem: ElementTree.Element, level: int = 0) -> ElementTree.Element: + """ + Helper method lifed from cyclonedx-python original project for formatting + XML without using any XML-libraries. + + NOTE: This method is recursive. + + :param elem: + :param level: + :return: + """ + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + _xml_pretty_print(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + return elem class Xml(BaseOutput): + XML_VERSION_DECLARATION: str = '\n' - @abstractmethod def output_as_string(self) -> str: + bom = ElementTree.Element('bom', {'xmlns': self._get_target_namespace(), 'version': self._get_schema_version()}) + components = ElementTree.SubElement(bom, 'components') + for component in self.get_bom().get_components(): + components.append(Xml._get_component_as_xml_element(component=component)) + + return Xml.XML_VERSION_DECLARATION + ElementTree.tostring(bom, 'unicode') + + def output_to_file(self, filename: str): pass + @staticmethod + def _get_component_as_xml_element(component: Component) -> ElementTree.Element: + element = ElementTree.Element('component', {'type': component.get_type().value, 'bom-ref': component.get_purl()}) + + # if publisher and publisher != "UNKNOWN": + # ElementTree.SubElement(component, "publisher").text = re.sub(RE_XML_ILLEGAL, "?", publisher) + + # if name and name != "UNKNOWN": + ElementTree.SubElement(element, 'name').text = component.get_name() + + # if version and version != "UNKNOWN": + ElementTree.SubElement(element, 'version').text = component.get_version() + + # if description and description != "UNKNOWN": + # ElementTree.SubElement(component, "description").text = re.sub(RE_XML_ILLEGAL, "?", description) + # + # if hashes: + # hashes_elm = ElementTree.SubElement(component, "hashes") + # for h in hashes: + # ElementTree.SubElement(hashes_elm, "hash", alg=h.alg).text = h.content + # + # if len(licenses): + # licenses_elm = ElementTree.SubElement(component, "licenses") + # for component_license in licenses: + # if component_license.license is not None: + # license_elm = ElementTree.SubElement(licenses_elm, "license") + # ElementTree.SubElement(license_elm, "name").text = re.sub(RE_XML_ILLEGAL, "?", component_license.license.name) + + # if purl: + ElementTree.SubElement(element, 'purl').text = component.get_purl() + + # ElementTree.SubElement(component, "modified").text = modified if modified else "false" + + return element + @abstractmethod - def output_to_file(self, filename: str): + def _get_schema_version(self) -> str: pass + def _get_target_namespace(self) -> str: + return 'http://cyclonedx.org/schema/bom/{}'.format(self._get_schema_version()) -class XmlV1Dot2(Xml): - def output_as_string(self) -> str: - return '' +class XmlV1Dot2(Xml): - def output_to_file(self, filename: str): - return '' + def _get_schema_version(self) -> str: + return '1.2' class XmlV1Dot3(Xml): - def output_as_string(self) -> str: - return '' - - def output_to_file(self, filename: str): - return '' + def _get_schema_version(self) -> str: + return '1.3' diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py new file mode 100644 index 00000000..60e6eab0 --- /dev/null +++ b/tests/test_output_xml.py @@ -0,0 +1,19 @@ +from unittest import TestCase + +from cyclonedx.output import get_instance, OutputFormat, SchemaVersion +from cyclonedx.output.xml import XmlV1Dot3 + + +class TestOutputGeneric(TestCase): + + def test_get_instance_default(self): + i = get_instance() + self.assertIsInstance(i, XmlV1Dot3) + + def test_get_instance_xml(self): + i = get_instance(output_format=OutputFormat.XML) + self.assertIsInstance(i, XmlV1Dot3) + + def test_get_instance_xml_v1_3(self): + i = get_instance(output_format=OutputFormat.XML, schema_version=SchemaVersion.V1_3) + self.assertIsInstance(i, XmlV1Dot3) From cb4337a1cb14ee62471140add8954dd7c5b6b314 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 09:20:17 +0100 Subject: [PATCH 09/43] Added first tests for XML SBOM generation (v1.3 and v1.2). --- cyclonedx/output/xml.py | 8 ++++-- requirements-test.txt | 16 +++++++++-- tests/base.py | 39 ++++++++++++++++++++++++++ tests/fixtures/bom_v1.2_setuptools.xml | 10 +++++++ tests/fixtures/bom_v1.3_setuptools.xml | 10 +++++++ tests/test_output_xml.py | 36 +++++++++++++++--------- 6 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 tests/base.py create mode 100644 tests/fixtures/bom_v1.2_setuptools.xml create mode 100644 tests/fixtures/bom_v1.3_setuptools.xml diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 3e54a130..a90a705d 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -36,7 +36,7 @@ class Xml(BaseOutput): XML_VERSION_DECLARATION: str = '\n' def output_as_string(self) -> str: - bom = ElementTree.Element('bom', {'xmlns': self._get_target_namespace(), 'version': self._get_schema_version()}) + bom = ElementTree.Element('bom', {'xmlns': self._get_target_namespace(), 'version': '1'}) components = ElementTree.SubElement(bom, 'components') for component in self.get_bom().get_components(): components.append(Xml._get_component_as_xml_element(component=component)) @@ -48,7 +48,8 @@ def output_to_file(self, filename: str): @staticmethod def _get_component_as_xml_element(component: Component) -> ElementTree.Element: - element = ElementTree.Element('component', {'type': component.get_type().value, 'bom-ref': component.get_purl()}) + element = ElementTree.Element('component', + {'type': component.get_type().value, 'bom-ref': component.get_purl()}) # if publisher and publisher != "UNKNOWN": # ElementTree.SubElement(component, "publisher").text = re.sub(RE_XML_ILLEGAL, "?", publisher) @@ -72,7 +73,8 @@ def _get_component_as_xml_element(component: Component) -> ElementTree.Element: # for component_license in licenses: # if component_license.license is not None: # license_elm = ElementTree.SubElement(licenses_elm, "license") - # ElementTree.SubElement(license_elm, "name").text = re.sub(RE_XML_ILLEGAL, "?", component_license.license.name) + # ElementTree.SubElement(license_elm, "name").text = re.sub(RE_XML_ILLEGAL, "?", + # component_license.license.name) # if purl: ElementTree.SubElement(element, 'purl').text = component.get_purl() diff --git a/requirements-test.txt b/requirements-test.txt index fd99ba6e..5633e730 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,14 @@ +backports.entry-points-selectable==1.1.0 +distlib==0.3.2 +filelock==3.0.12 packageurl-python==0.9.4 -requirements_parser==0.2.0 -setuptools>=50.3.2 -tox==3.24.3 \ No newline at end of file +packaging==21.0 +platformdirs==2.2.0 +pluggy==1.0.0 +py==1.10.0 +pyparsing==2.4.7 +requirements-parser==0.2.0 +six==1.16.0 +toml==0.10.2 +tox==3.24.3 +virtualenv==20.7.2 diff --git a/tests/base.py b/tests/base.py new file mode 100644 index 00000000..047a733b --- /dev/null +++ b/tests/base.py @@ -0,0 +1,39 @@ +from unittest import TestCase + +from xml.dom import minidom + + +class BaseXmlTestCase(TestCase): + + def assertEqualXml(self, a: str, b: str): + da, db = minidom.parseString(a), minidom.parseString(b) + self.assertTrue(self._is_equal_xml_element(da.documentElement, db.documentElement)) + + def _is_equal_xml_element(self, a, b): + if a.tagName != b.tagName: + return False + if sorted(a.attributes.items()) != sorted(b.attributes.items()): + return False + + """ + Remove any pure whitespace Dom Text Nodes before we compare + + See: https://xml-sig.python.narkive.com/8o0UIicu + """ + for n in a.childNodes: + if n.nodeType == n.TEXT_NODE and n.data.strip() == '': + a.removeChild(n) + for n in b.childNodes: + if n.nodeType == n.TEXT_NODE and n.data.strip() == '': + b.removeChild(n) + + if len(a.childNodes) != len(b.childNodes): + return False + for ac, bc in zip(a.childNodes, b.childNodes): + if ac.nodeType != bc.nodeType: + return False + if ac.nodeType == ac.TEXT_NODE and ac.data != bc.data: + return False + if ac.nodeType == ac.ELEMENT_NODE and not self._is_equal_xml_element(ac, bc): + return False + return True diff --git a/tests/fixtures/bom_v1.2_setuptools.xml b/tests/fixtures/bom_v1.2_setuptools.xml new file mode 100644 index 00000000..694739cb --- /dev/null +++ b/tests/fixtures/bom_v1.2_setuptools.xml @@ -0,0 +1,10 @@ + + + + + setuptools + 50.3.2 + pkg:pypi/setuptools@50.3.2?extension=tar.gz + + + \ No newline at end of file diff --git a/tests/fixtures/bom_v1.3_setuptools.xml b/tests/fixtures/bom_v1.3_setuptools.xml new file mode 100644 index 00000000..7e116b29 --- /dev/null +++ b/tests/fixtures/bom_v1.3_setuptools.xml @@ -0,0 +1,10 @@ + + + + + setuptools + 50.3.2 + pkg:pypi/setuptools@50.3.2?extension=tar.gz + + + \ No newline at end of file diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py index 60e6eab0..032f40e9 100644 --- a/tests/test_output_xml.py +++ b/tests/test_output_xml.py @@ -1,19 +1,29 @@ -from unittest import TestCase +from os.path import dirname, join -from cyclonedx.output import get_instance, OutputFormat, SchemaVersion -from cyclonedx.output.xml import XmlV1Dot3 +from cyclonedx.model.bom import Bom +from cyclonedx.model.cyclonedx import Component +from cyclonedx.output import get_instance, SchemaVersion +from cyclonedx.output.xml import XmlV1Dot3, XmlV1Dot2 +from tests.base import BaseXmlTestCase -class TestOutputGeneric(TestCase): - def test_get_instance_default(self): - i = get_instance() - self.assertIsInstance(i, XmlV1Dot3) +class TestOutputXml(BaseXmlTestCase): - def test_get_instance_xml(self): - i = get_instance(output_format=OutputFormat.XML) - self.assertIsInstance(i, XmlV1Dot3) + def test_simple_bom_v1_3(self): + bom = Bom() + bom.add_component(Component(name='setuptools', version='50.3.2', qualifiers='extension=tar.gz')) + outputter = get_instance(bom=bom) + self.assertIsInstance(outputter, XmlV1Dot3) + with open(join(dirname(__file__), 'fixtures/bom_v1.3_setuptools.xml')) as expected_xml: + self.assertEqualXml(outputter.output_as_string(), expected_xml.read()) + expected_xml.close() - def test_get_instance_xml_v1_3(self): - i = get_instance(output_format=OutputFormat.XML, schema_version=SchemaVersion.V1_3) - self.assertIsInstance(i, XmlV1Dot3) + def test_simple_bom_v1_2(self): + bom = Bom() + bom.add_component(Component(name='setuptools', version='50.3.2', qualifiers='extension=tar.gz')) + outputter = get_instance(bom=bom, schema_version=SchemaVersion.V1_2) + self.assertIsInstance(outputter, XmlV1Dot2) + with open(join(dirname(__file__), 'fixtures/bom_v1.2_setuptools.xml')) as expected_xml: + self.assertEqualXml(outputter.output_as_string(), expected_xml.read()) + expected_xml.close() From c34b1a63fd7958d2b1060ba51054a55b57228549 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 09:35:42 +0100 Subject: [PATCH 10/43] Added coverage reporting for tests --- .circleci/config.yml | 10 ++++++++-- .gitignore | 3 +++ tox.ini | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 29a7788f..aaf1bf37 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,13 +45,17 @@ jobs: steps: - checkout - run: - name: Install Tox + name: Install Tox & Coverage command: | - pip install tox + pip install tox coverage - run: name: Run tox command: | tox --result-json=.tox/results.json + - run: + name: Generate Coverage Reports + command: | + coverage report && coverage xml -o test-reports/coverage.xml - run: name: Run self scan command: | @@ -59,6 +63,8 @@ jobs: - store_artifacts: # Upload test summary for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ path: .tox/results.json destination: tox-logs + - store_test_results: + path: test-reports workflows: version: 2 diff --git a/.gitignore b/.gitignore index 790d8950..1aef9ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ dist/ # Exclude test-related items .tox/* +# Exclude coverage +.coverage + # Exclude Python Virtual Environment venv/* diff --git a/tox.ini b/tox.ini index e91eef25..33a566a0 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = flake8,py39 [testenv] deps = -r{toxinidir}/requirements-test.txt -commands = python -m unittest discover -s tests +commands = coverage run --source=cyclonedx -m unittest discover -s tests [testenv:flake8] basepython = python3 From 01643d67f73ec8ee35884d0bcc15c892649f6b72 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 09:40:45 +0100 Subject: [PATCH 11/43] Missed coverage as a dependency for testing. --- requirements-test.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-test.txt b/requirements-test.txt index 5633e730..48c07777 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,5 @@ backports.entry-points-selectable==1.1.0 +coverage==5.5 distlib==0.3.2 filelock==3.0.12 packageurl-python==0.9.4 From ce700e5bdff7ce4a8bd5614239b129e59afe2908 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 09:44:29 +0100 Subject: [PATCH 12/43] Added HTML coverage report. --- .circleci/config.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index aaf1bf37..43c057e7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,14 +55,17 @@ jobs: - run: name: Generate Coverage Reports command: | - coverage report && coverage xml -o test-reports/coverage.xml + coverage report && coverage xml -o test-reports/coverage.xml && coverage html -o test-reports/coverage.html - run: name: Run self scan command: | # TODO: audit with jake maybe? - - store_artifacts: # Upload test summary for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + - store_artifacts: path: .tox/results.json destination: tox-logs + - store_artifacts: + path: test-reports + destination: test-reports - store_test_results: path: test-reports From dd886032b92d491f462d62f269f3df7ed823d436 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 09:47:01 +0100 Subject: [PATCH 13/43] Fix to generate HTML coverage reports and stash in CircleCI builds. --- .circleci/config.yml | 2 +- .gitignore | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 43c057e7..b9ec1cb6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,7 +55,7 @@ jobs: - run: name: Generate Coverage Reports command: | - coverage report && coverage xml -o test-reports/coverage.xml && coverage html -o test-reports/coverage.html + coverage report && coverage xml -o test-reports/coverage.xml && coverage html -d test-reports - run: name: Run self scan command: | diff --git a/.gitignore b/.gitignore index 1aef9ce6..a414d201 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ dist/ # Exclude coverage .coverage +test-reports # Exclude Python Virtual Environment venv/* From 3e1f5ec9354a779adf44129656a1ccdcffadee6d Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 10:37:35 +0100 Subject: [PATCH 14/43] Added initial JSON outputter and associated tests. --- cyclonedx/output/json.py | 50 +++++++++++++++++++++++++ tests/base.py | 21 +++++++++++ tests/fixtures/bom_v1.2_setuptools.json | 14 +++++++ tests/fixtures/bom_v1.3_setuptools.json | 14 +++++++ tests/test_output_json.py | 28 ++++++++++++++ 5 files changed, 127 insertions(+) create mode 100644 cyclonedx/output/json.py create mode 100644 tests/fixtures/bom_v1.2_setuptools.json create mode 100644 tests/fixtures/bom_v1.3_setuptools.json create mode 100644 tests/test_output_json.py diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py new file mode 100644 index 00000000..4551ca85 --- /dev/null +++ b/cyclonedx/output/json.py @@ -0,0 +1,50 @@ +import json +from abc import abstractmethod + +from . import BaseOutput +from ..model.cyclonedx import Component + + +class Json(BaseOutput): + + def output_as_string(self) -> str: + return json.dumps(self._get_json()) + + def output_to_file(self, filename: str): + pass + + def _get_json(self) -> dict: + components = list(map(Json._get_component_as_dict, self.get_bom().get_components())) + + return { + "bomFormat": "CycloneDX", + "specVersion": str(self._get_schema_version()), + "serialNumber": "", + "version": 1, + "components": components + } + + @staticmethod + def _get_component_as_dict(component: Component) -> dict: + return { + "type": component.get_type().value, + "name": component.get_name(), + "version": component.get_version(), + "purl": component.get_purl() + } + + @abstractmethod + def _get_schema_version(self) -> str: + pass + + +class JsonV1Dot2(Json): + + def _get_schema_version(self) -> str: + return '1.2' + + +class JsonV1Dot3(Json): + + def _get_schema_version(self) -> str: + return '1.3' diff --git a/tests/base.py b/tests/base.py index 047a733b..ce37dc5a 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,8 +1,29 @@ from unittest import TestCase +import json from xml.dom import minidom +class BaseJsonTestCase(TestCase): + + def assertEqualJson(self, a: str, b: str): + self.assertEqual( + json.dumps(json.loads(a), sort_keys=True), + json.dumps(json.loads(b), sort_keys=True) + ) + + def assertEqualJsonBom(self, a: str, b: str): + """ + Remove UUID before comparison as this will be unique to each generation + """ + ab, bb = json.loads(a), json.loads(b) + + ab['serialNumber'] = '' + bb['serialNumber'] = '' + + self.assertEqualJson(json.dumps(ab), json.dumps(bb)) + + class BaseXmlTestCase(TestCase): def assertEqualXml(self, a: str, b: str): diff --git a/tests/fixtures/bom_v1.2_setuptools.json b/tests/fixtures/bom_v1.2_setuptools.json new file mode 100644 index 00000000..ba2cd470 --- /dev/null +++ b/tests/fixtures/bom_v1.2_setuptools.json @@ -0,0 +1,14 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.2", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", + "version": 1, + "components": [ + { + "type": "library", + "name": "setuptools", + "version": "50.3.2", + "purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/bom_v1.3_setuptools.json b/tests/fixtures/bom_v1.3_setuptools.json new file mode 100644 index 00000000..079a3804 --- /dev/null +++ b/tests/fixtures/bom_v1.3_setuptools.json @@ -0,0 +1,14 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.3", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", + "version": 1, + "components": [ + { + "type": "library", + "name": "setuptools", + "version": "50.3.2", + "purl": "pkg:pypi/setuptools@50.3.2?extension=tar.gz" + } + ] +} \ No newline at end of file diff --git a/tests/test_output_json.py b/tests/test_output_json.py new file mode 100644 index 00000000..2b666e8f --- /dev/null +++ b/tests/test_output_json.py @@ -0,0 +1,28 @@ +from os.path import dirname, join +from tests.base import BaseJsonTestCase + +from cyclonedx.model.bom import Bom +from cyclonedx.model.cyclonedx import Component +from cyclonedx.output import get_instance, OutputFormat, SchemaVersion +from cyclonedx.output.json import JsonV1Dot3, JsonV1Dot2 + + +class TestOutputJson(BaseJsonTestCase): + + def test_simple_bom_v1_3(self): + bom = Bom() + bom.add_component(Component(name='setuptools', version='50.3.2', qualifiers='extension=tar.gz')) + outputter = get_instance(bom=bom, output_format=OutputFormat.JSON) + self.assertIsInstance(outputter, JsonV1Dot3) + with open(join(dirname(__file__), 'fixtures/bom_v1.3_setuptools.json')) as expected_json: + self.assertEqualJsonBom(outputter.output_as_string(), expected_json.read()) + expected_json.close() + + def test_simple_bom_v1_2(self): + bom = Bom() + bom.add_component(Component(name='setuptools', version='50.3.2', qualifiers='extension=tar.gz')) + outputter = get_instance(bom=bom, output_format=OutputFormat.JSON, schema_version=SchemaVersion.V1_2) + self.assertIsInstance(outputter, JsonV1Dot2) + with open(join(dirname(__file__), 'fixtures/bom_v1.2_setuptools.json')) as expected_json: + self.assertEqualJsonBom(outputter.output_as_string(), expected_json.read()) + expected_json.close() From f9e97733b0cc57bbb71341b4ced4ccc8f09b7f28 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 12:01:36 +0100 Subject: [PATCH 15/43] Addition of simple 'metadata' element for XML SBOM's. --- cyclonedx/model/bom.py | 22 ++++++++++++++++++++++ cyclonedx/output/xml.py | 16 ++++++++++++---- tests/base.py | 25 ++++++++++++++++++++++++- tests/fixtures/bom_v1.2_setuptools.xml | 3 +++ tests/fixtures/bom_v1.3_setuptools.xml | 3 +++ tests/test_output_xml.py | 10 ++++++---- 6 files changed, 70 insertions(+), 9 deletions(-) diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 925a105d..5e9e5402 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -1,8 +1,25 @@ +import datetime from typing import List from .cyclonedx import Component from ..parser import BaseParser +class BomMetaData: + """ + Our internal representation of the metadata complex type within the CycloneDX standard. + + See https://cyclonedx.org/docs/1.3/#type_metadata + """ + + _timestamp: datetime.datetime + + def __init__(self): + self._timestamp = datetime.datetime.now(tz=datetime.timezone.utc) + + def get_timestamp(self) -> datetime.datetime: + return self._timestamp + + class Bom: """ This is our internal representation of the BOM. @@ -11,6 +28,7 @@ class Bom: to the requested schema version. """ + _metadata: BomMetaData = None _components: List[Component] = [] @staticmethod @@ -20,6 +38,7 @@ def from_parser(parser: BaseParser): return bom def __init__(self): + self._metadata = BomMetaData() self._components.clear() def add_component(self, component: Component): @@ -34,5 +53,8 @@ def component_count(self) -> int: def get_components(self) -> List[Component]: return self._components + def get_metadata(self) -> BomMetaData: + return self._metadata + def has_component(self, component: Component) -> bool: return component in self._components diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index a90a705d..f150d0bb 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -33,10 +33,14 @@ def _xml_pretty_print(elem: ElementTree.Element, level: int = 0) -> ElementTree. class Xml(BaseOutput): - XML_VERSION_DECLARATION: str = '\n' + XML_VERSION_DECLARATION: str = '' + + def get_target_namespace(self) -> str: + return 'http://cyclonedx.org/schema/bom/{}'.format(self._get_schema_version()) def output_as_string(self) -> str: - bom = ElementTree.Element('bom', {'xmlns': self._get_target_namespace(), 'version': '1'}) + bom = ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1'}) + bom = self._add_metadata(bom=bom) components = ElementTree.SubElement(bom, 'components') for component in self.get_bom().get_components(): components.append(Xml._get_component_as_xml_element(component=component)) @@ -83,12 +87,16 @@ def _get_component_as_xml_element(component: Component) -> ElementTree.Element: return element + def _add_metadata(self, bom: ElementTree.Element) -> ElementTree.Element: + metadata_e = ElementTree.SubElement(bom, 'metadata') + ElementTree.SubElement(metadata_e, 'timestamp').text = self.get_bom().get_metadata().get_timestamp().isoformat() + return bom + @abstractmethod def _get_schema_version(self) -> str: pass - def _get_target_namespace(self) -> str: - return 'http://cyclonedx.org/schema/bom/{}'.format(self._get_schema_version()) + class XmlV1Dot2(Xml): diff --git a/tests/base.py b/tests/base.py index ce37dc5a..a4cddbc6 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,6 +1,8 @@ +import xml.etree.ElementTree from unittest import TestCase import json +from datetime import datetime, timezone from xml.dom import minidom @@ -28,7 +30,28 @@ class BaseXmlTestCase(TestCase): def assertEqualXml(self, a: str, b: str): da, db = minidom.parseString(a), minidom.parseString(b) - self.assertTrue(self._is_equal_xml_element(da.documentElement, db.documentElement)) + self.assertTrue(self._is_equal_xml_element(da.documentElement, db.documentElement), + 'XML Documents are not equal: \n{}\n{}'.format(da.toxml(), db.toxml())) + + def assertEqualXmlBom(self, a: str, b: str, namespace: str): + """ + Sanitise some fields such as timestamps which cannot have their values directly compared for equality. + """ + ba, bb = xml.etree.ElementTree.fromstring(a), xml.etree.ElementTree.fromstring(b) + + now = datetime.now(tz=timezone.utc) + metadata_ts_a = ba.find('./{{{}}}metadata/{{{}}}timestamp'.format(namespace, namespace)) + if metadata_ts_a is not None: + metadata_ts_a.text = now.isoformat() + + metadata_ts_b = bb.find('./{{{}}}metadata/{{{}}}timestamp'.format(namespace, namespace)) + if metadata_ts_b is not None: + metadata_ts_b.text = now.isoformat() + + self.assertEqualXml( + xml.etree.ElementTree.tostring(ba, 'unicode'), + xml.etree.ElementTree.tostring(bb, 'unicode') + ) def _is_equal_xml_element(self, a, b): if a.tagName != b.tagName: diff --git a/tests/fixtures/bom_v1.2_setuptools.xml b/tests/fixtures/bom_v1.2_setuptools.xml index 694739cb..9030abd7 100644 --- a/tests/fixtures/bom_v1.2_setuptools.xml +++ b/tests/fixtures/bom_v1.2_setuptools.xml @@ -1,5 +1,8 @@ + + 2021-09-01T10:50:42.051979+00:00 + setuptools diff --git a/tests/fixtures/bom_v1.3_setuptools.xml b/tests/fixtures/bom_v1.3_setuptools.xml index 7e116b29..420b8dbe 100644 --- a/tests/fixtures/bom_v1.3_setuptools.xml +++ b/tests/fixtures/bom_v1.3_setuptools.xml @@ -1,5 +1,8 @@ + + 2021-09-01T10:50:42.051979+00:00 + setuptools diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py index 032f40e9..5989f6ae 100644 --- a/tests/test_output_xml.py +++ b/tests/test_output_xml.py @@ -3,7 +3,7 @@ from cyclonedx.model.bom import Bom from cyclonedx.model.cyclonedx import Component from cyclonedx.output import get_instance, SchemaVersion -from cyclonedx.output.xml import XmlV1Dot3, XmlV1Dot2 +from cyclonedx.output.xml import XmlV1Dot3, XmlV1Dot2, Xml from tests.base import BaseXmlTestCase @@ -13,10 +13,11 @@ class TestOutputXml(BaseXmlTestCase): def test_simple_bom_v1_3(self): bom = Bom() bom.add_component(Component(name='setuptools', version='50.3.2', qualifiers='extension=tar.gz')) - outputter = get_instance(bom=bom) + outputter: Xml = get_instance(bom=bom) self.assertIsInstance(outputter, XmlV1Dot3) with open(join(dirname(__file__), 'fixtures/bom_v1.3_setuptools.xml')) as expected_xml: - self.assertEqualXml(outputter.output_as_string(), expected_xml.read()) + self.assertEqualXmlBom(a=outputter.output_as_string(), b=expected_xml.read(), + namespace=outputter.get_target_namespace()) expected_xml.close() def test_simple_bom_v1_2(self): @@ -25,5 +26,6 @@ def test_simple_bom_v1_2(self): outputter = get_instance(bom=bom, schema_version=SchemaVersion.V1_2) self.assertIsInstance(outputter, XmlV1Dot2) with open(join(dirname(__file__), 'fixtures/bom_v1.2_setuptools.xml')) as expected_xml: - self.assertEqualXml(outputter.output_as_string(), expected_xml.read()) + self.assertEqualXmlBom(outputter.output_as_string(), expected_xml.read(), + namespace=outputter.get_target_namespace()) expected_xml.close() From 8c5590fd3c5c59de9a5b6cf49005f4c6e444265d Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 12:24:19 +0100 Subject: [PATCH 16/43] Added metadata initial support to JSON output format. --- cyclonedx/output/json.py | 7 +++++++ tests/base.py | 6 ++++++ tests/fixtures/bom_v1.2_setuptools.json | 3 +++ tests/fixtures/bom_v1.3_setuptools.json | 3 +++ 4 files changed, 19 insertions(+) diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index 4551ca85..ccfede9f 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -21,6 +21,7 @@ def _get_json(self) -> dict: "specVersion": str(self._get_schema_version()), "serialNumber": "", "version": 1, + "metadata": self._get_metadata_as_dict(), "components": components } @@ -33,6 +34,12 @@ def _get_component_as_dict(component: Component) -> dict: "purl": component.get_purl() } + def _get_metadata_as_dict(self) -> dict: + metadata = self.get_bom().get_metadata() + return { + "timestamp": metadata.get_timestamp().isoformat() + } + @abstractmethod def _get_schema_version(self) -> str: pass diff --git a/tests/base.py b/tests/base.py index a4cddbc6..b6cc772b 100644 --- a/tests/base.py +++ b/tests/base.py @@ -20,9 +20,15 @@ def assertEqualJsonBom(self, a: str, b: str): """ ab, bb = json.loads(a), json.loads(b) + # Null serialNumbers ab['serialNumber'] = '' bb['serialNumber'] = '' + # Unify timestamps to ensure they will compare + now = datetime.now(tz=timezone.utc) + ab['metadata']['timestamp'] = now.isoformat() + bb['metadata']['timestamp'] = now.isoformat() + self.assertEqualJson(json.dumps(ab), json.dumps(bb)) diff --git a/tests/fixtures/bom_v1.2_setuptools.json b/tests/fixtures/bom_v1.2_setuptools.json index ba2cd470..b01e52c0 100644 --- a/tests/fixtures/bom_v1.2_setuptools.json +++ b/tests/fixtures/bom_v1.2_setuptools.json @@ -3,6 +3,9 @@ "specVersion": "1.2", "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, + "metadata": { + "timestamp": "2021-09-01T10:50:42.051979+00:00" + }, "components": [ { "type": "library", diff --git a/tests/fixtures/bom_v1.3_setuptools.json b/tests/fixtures/bom_v1.3_setuptools.json index 079a3804..2c51c26e 100644 --- a/tests/fixtures/bom_v1.3_setuptools.json +++ b/tests/fixtures/bom_v1.3_setuptools.json @@ -3,6 +3,9 @@ "specVersion": "1.3", "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", "version": 1, + "metadata": { + "timestamp": "2021-09-01T10:50:42.051979+00:00" + }, "components": [ { "type": "library", From bb41dc6d333f59025aae97c602cbe41343645b20 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 12:24:37 +0100 Subject: [PATCH 17/43] Added a bunch more content to the README to explain how the library can be used. --- README.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 71c1d310..815992f3 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@ [![CircleCI](https://circleci.com/gh/sonatype-nexus-community/cyclonedx-python-lib.svg?style=shield)](https://circleci.com/gh/sonatype-nexus-community/cyclonedx-python-lib) -This CycloneDX module for Python can generate valid CycloneDX bill-of-material document containing an aggregate of all +This CycloneDX module for Python can generate valid CycloneDX bill-of-material document containing an aggregate of all project dependencies. -This module is not designed for standalone use. If you're looking for a tool to run to generate CycloneDX -software bill-of-materials documents, why not checkout: +This module is not designed for standalone use. If you're looking for a tool to run to generate CycloneDX software +bill-of-materials documents, why not checkout: + - [Jake](https://github.com/sonatype-nexus-community/jake) Or you can use this module yourself in your application to generate SBOMs. @@ -24,18 +25,83 @@ pip install cyclonedx-python-lib ## Architecture This module break out into three key areas: -1. **Parser**: Use a parser that suits your needs to automatically gather information - about your environment or application + +1. **Parser**: Use a parser that suits your needs to automatically gather information about your environment or + application 2. **Model**: Internal models used to unify data from different parsers -3. **Output**: Choose and configure an output which allows you to define output format as well - as the CycloneDX schema version +3. **Output**: Choose and configure an output which allows you to define output format as well as the CycloneDX schema + version + +### Parsing + +You can use one of the parsers to obtain information about your project or environment. Available parsers: + +| Parser | Class / Import | Description | +| ------- | ------ | ------ | +| Environment | `from cyclonedx.parser.environment import EnvironmentParser` | Looks at the packaged installed in your current Python environment. | +| RequirementsParser | `from cyclonedx.parser.requirements import RequirementsParser` | Parses a multiline string that you provide that conforms to the `requirements.txt` [PEP-508](https://www.python.org/dev/peps/pep-0508/) standard. | +| RequirementsFileParser | `from cyclonedx.parser.requirements import RequirementsFileParser` | Parses a file that you provide the path to that conforms to the `requirements.txt` [PEP-508](https://www.python.org/dev/peps/pep-0508/) standard. | + +#### Example + +``` +from cyclonedx.parser.environment import EnvironmentParser + +parser = EnvironmentParser() +``` + +### Modelling + +You can create a BOM Model from either an Parser instance or manually using the methods avaialbel directly on the `Bom` class. + +#### Example from a Parser + +``` +from cyclonedx.model.bom import Bom +from cyclonedx.parser.environment import EnvironmentParser + +parser = EnvironmentParser() +bom = Bom.from_parser(parser=parser) +``` + +### Generating Output + +Once you have an instance of a `Bom` you can produce output in either `JSON` or `XML` against any of the supporting CycloneDX schema versions as you require. + +We provide two helper methods: +1. Output to string (for you to do with as you require) +2. Output directly to a filename you provide + +#### Supported Schema Versions + +| Schema Version | JSON | XML | Notes | +| ---- | ---- | ---- | ---- | +| 1.3 _(current)_ | Y | Y | | +| 1.2 | Y | Y | | + +##### Example as JSON + +``` +from cyclonedx.output import get_instance, OutputFormat + +outputter = get_instance(bom=bom, output_format=OutputFormat.JSON) +outputter.output_as_string() +``` + +##### Example as XML +``` +from cyclonedx.output import get_instance, SchemaVersion + +outputter = get_instance(bom=bom, schema_version=SchemaVersion.V1_) +outputter.output_to_file(filename='/tmp/sbom-v1.2.xml') +``` ## The Fine Print Remember: -It is worth noting that this is **NOT SUPPORTED** by Sonatype, and is a contribution of ours -to the open source community (read: you!) +It is worth noting that this is **NOT SUPPORTED** by Sonatype, and is a contribution of ours to the open source +community (read: you!) * Use this contribution at the risk tolerance that you have * Do NOT file Sonatype support tickets related to `cyclonedx-python-lib` support in regard to this project From 50e3c7546b92e3241feefa6dea0fbfa9c1145843 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 12:36:55 +0100 Subject: [PATCH 18/43] Added 'serialNumber' to SBOMs (JSON and XML). --- cyclonedx/model/bom.py | 7 +++++++ cyclonedx/output/json.py | 2 +- cyclonedx/output/xml.py | 5 ++--- tests/base.py | 12 ++++++++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 5e9e5402..db743a7c 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -1,5 +1,7 @@ import datetime from typing import List +from uuid import uuid4 + from .cyclonedx import Component from ..parser import BaseParser @@ -28,6 +30,7 @@ class Bom: to the requested schema version. """ + _uuid: str _metadata: BomMetaData = None _components: List[Component] = [] @@ -38,6 +41,7 @@ def from_parser(parser: BaseParser): return bom def __init__(self): + self._uuid = uuid4() self._metadata = BomMetaData() self._components.clear() @@ -56,5 +60,8 @@ def get_components(self) -> List[Component]: def get_metadata(self) -> BomMetaData: return self._metadata + def get_urn_uuid(self) -> str: + return 'urn:uuid:{}'.format(self._uuid) + def has_component(self, component: Component) -> bool: return component in self._components diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index ccfede9f..5281f5d3 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -19,7 +19,7 @@ def _get_json(self) -> dict: return { "bomFormat": "CycloneDX", "specVersion": str(self._get_schema_version()), - "serialNumber": "", + "serialNumber": self.get_bom().get_urn_uuid(), "version": 1, "metadata": self._get_metadata_as_dict(), "components": components diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index f150d0bb..5356c4e6 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -39,7 +39,8 @@ def get_target_namespace(self) -> str: return 'http://cyclonedx.org/schema/bom/{}'.format(self._get_schema_version()) def output_as_string(self) -> str: - bom = ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1'}) + bom = ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1', + 'serialNumber': self.get_bom().get_urn_uuid()}) bom = self._add_metadata(bom=bom) components = ElementTree.SubElement(bom, 'components') for component in self.get_bom().get_components(): @@ -97,8 +98,6 @@ def _get_schema_version(self) -> str: pass - - class XmlV1Dot2(Xml): def _get_schema_version(self) -> str: diff --git a/tests/base.py b/tests/base.py index b6cc772b..0f4ae1e0 100644 --- a/tests/base.py +++ b/tests/base.py @@ -3,8 +3,11 @@ import json from datetime import datetime, timezone +from uuid import uuid4 from xml.dom import minidom +single_uuid: str = 'urn:uuid:{}'.format(uuid4()) + class BaseJsonTestCase(TestCase): @@ -21,8 +24,8 @@ def assertEqualJsonBom(self, a: str, b: str): ab, bb = json.loads(a), json.loads(b) # Null serialNumbers - ab['serialNumber'] = '' - bb['serialNumber'] = '' + ab['serialNumber'] = single_uuid + bb['serialNumber'] = single_uuid # Unify timestamps to ensure they will compare now = datetime.now(tz=timezone.utc) @@ -45,6 +48,11 @@ def assertEqualXmlBom(self, a: str, b: str, namespace: str): """ ba, bb = xml.etree.ElementTree.fromstring(a), xml.etree.ElementTree.fromstring(b) + # Align serialNumbers + ba.set('serialNumber', single_uuid) + bb.set('serialNumber', single_uuid) + + # Align timestamps in metadata now = datetime.now(tz=timezone.utc) metadata_ts_a = ba.find('./{{{}}}metadata/{{{}}}timestamp'.format(namespace, namespace)) if metadata_ts_a is not None: From 37f6b00b7e354b76a9f8f72ed2c1004a0e728319 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 12:57:08 +0100 Subject: [PATCH 19/43] Initial support for V1.0 and V1.1 in XML output format. --- cyclonedx/output/__init__.py | 2 + cyclonedx/output/xml.py | 54 ++++++++++++++++++++++---- tests/fixtures/bom_v1.0_setuptools.xml | 10 +++++ tests/fixtures/bom_v1.1_setuptools.xml | 11 ++++++ tests/test_output_xml.py | 22 ++++++++++- 5 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/bom_v1.0_setuptools.xml create mode 100644 tests/fixtures/bom_v1.1_setuptools.xml diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index 261d5f20..a2c6ea23 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -12,6 +12,8 @@ class OutputFormat(Enum): class SchemaVersion(Enum): + V1_0: str = 'V1Dot0' + V1_1: str = 'V1Dot1' V1_2: str = 'V1Dot2' V1_3: str = 'V1Dot3' diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 5356c4e6..4699f630 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -39,22 +39,30 @@ def get_target_namespace(self) -> str: return 'http://cyclonedx.org/schema/bom/{}'.format(self._get_schema_version()) def output_as_string(self) -> str: - bom = ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1', - 'serialNumber': self.get_bom().get_urn_uuid()}) - bom = self._add_metadata(bom=bom) + bom = self._get_bom_root_element() + + if self._bom_supports_metadata(): + bom = self._add_metadata(bom=bom) + components = ElementTree.SubElement(bom, 'components') for component in self.get_bom().get_components(): - components.append(Xml._get_component_as_xml_element(component=component)) + components.append(self._get_component_as_xml_element(component=component)) return Xml.XML_VERSION_DECLARATION + ElementTree.tostring(bom, 'unicode') def output_to_file(self, filename: str): pass - @staticmethod - def _get_component_as_xml_element(component: Component) -> ElementTree.Element: - element = ElementTree.Element('component', - {'type': component.get_type().value, 'bom-ref': component.get_purl()}) + def _get_bom_root_element(self) -> ElementTree.Element: + return ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1', + 'serialNumber': self.get_bom().get_urn_uuid()}) + + def _get_component_as_xml_element(self, component: Component) -> ElementTree.Element: + element_attributes = {'type': component.get_type().value} + if self._component_supports_bom_ref_attribute(): + element_attributes['bom-ref'] = component.get_purl() + + element = ElementTree.Element('component', element_attributes) # if publisher and publisher != "UNKNOWN": # ElementTree.SubElement(component, "publisher").text = re.sub(RE_XML_ILLEGAL, "?", publisher) @@ -93,11 +101,41 @@ def _add_metadata(self, bom: ElementTree.Element) -> ElementTree.Element: ElementTree.SubElement(metadata_e, 'timestamp').text = self.get_bom().get_metadata().get_timestamp().isoformat() return bom + def _bom_supports_metadata(self) -> bool: + return True + + def _component_supports_bom_ref_attribute(self) -> bool: + return True + @abstractmethod def _get_schema_version(self) -> str: pass +class XmlV1Dot0(Xml): + + def _get_bom_root_element(self) -> ElementTree.Element: + return ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1'}) + + def _get_schema_version(self) -> str: + return '1.0' + + def _bom_supports_metadata(self) -> bool: + return False + + def _component_supports_bom_ref_attribute(self) -> bool: + return False + + +class XmlV1Dot1(Xml): + + def _get_schema_version(self) -> str: + return '1.1' + + def _bom_supports_metadata(self) -> bool: + return False + + class XmlV1Dot2(Xml): def _get_schema_version(self) -> str: diff --git a/tests/fixtures/bom_v1.0_setuptools.xml b/tests/fixtures/bom_v1.0_setuptools.xml new file mode 100644 index 00000000..0a837f10 --- /dev/null +++ b/tests/fixtures/bom_v1.0_setuptools.xml @@ -0,0 +1,10 @@ + + + + + setuptools + 50.3.2 + pkg:pypi/setuptools@50.3.2?extension=tar.gz + + + \ No newline at end of file diff --git a/tests/fixtures/bom_v1.1_setuptools.xml b/tests/fixtures/bom_v1.1_setuptools.xml new file mode 100644 index 00000000..d051ce6b --- /dev/null +++ b/tests/fixtures/bom_v1.1_setuptools.xml @@ -0,0 +1,11 @@ + + + + + setuptools + 50.3.2 + pkg:pypi/setuptools@50.3.2?extension=tar.gz + + + \ No newline at end of file diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py index 5989f6ae..ce9a9442 100644 --- a/tests/test_output_xml.py +++ b/tests/test_output_xml.py @@ -3,7 +3,7 @@ from cyclonedx.model.bom import Bom from cyclonedx.model.cyclonedx import Component from cyclonedx.output import get_instance, SchemaVersion -from cyclonedx.output.xml import XmlV1Dot3, XmlV1Dot2, Xml +from cyclonedx.output.xml import XmlV1Dot3, XmlV1Dot2, XmlV1Dot1, XmlV1Dot0, Xml from tests.base import BaseXmlTestCase @@ -29,3 +29,23 @@ def test_simple_bom_v1_2(self): self.assertEqualXmlBom(outputter.output_as_string(), expected_xml.read(), namespace=outputter.get_target_namespace()) expected_xml.close() + + def test_simple_bom_v1_1(self): + bom = Bom() + bom.add_component(Component(name='setuptools', version='50.3.2', qualifiers='extension=tar.gz')) + outputter = get_instance(bom=bom, schema_version=SchemaVersion.V1_1) + self.assertIsInstance(outputter, XmlV1Dot1) + with open(join(dirname(__file__), 'fixtures/bom_v1.1_setuptools.xml')) as expected_xml: + self.assertEqualXmlBom(outputter.output_as_string(), expected_xml.read(), + namespace=outputter.get_target_namespace()) + expected_xml.close() + + def test_simple_bom_v1_0(self): + bom = Bom() + bom.add_component(Component(name='setuptools', version='50.3.2', qualifiers='extension=tar.gz')) + outputter = get_instance(bom=bom, schema_version=SchemaVersion.V1_0) + self.assertIsInstance(outputter, XmlV1Dot0) + with open(join(dirname(__file__), 'fixtures/bom_v1.0_setuptools.xml')) as expected_xml: + self.assertEqualXmlBom(outputter.output_as_string(), expected_xml.read(), + namespace=outputter.get_target_namespace()) + expected_xml.close() From 34f421f4076d16c30ddf291f5c1866c1b623258a Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 13:24:01 +0100 Subject: [PATCH 20/43] Updated README to include a summary of the support this library provides across the different schema versions. --- README.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/README.md b/README.md index 815992f3..ad2c1f9a 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,71 @@ outputter = get_instance(bom=bom, schema_version=SchemaVersion.V1_) outputter.output_to_file(filename='/tmp/sbom-v1.2.xml') ``` +## Schema Support + +This library is a work in progress and complete support for all parts of the CycloneDX schema will come in future releases. + +Here is a summary of the parts of the schema supported by this library: + +_Note: We refer throughout using XPath, but the same is true for both XML and JSON output formats._ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
XPathSupport v1.3Support v1.2Support v1.1Support v1.0Notes
/bomYYYY + This is the root element and is supported with all it's defined attributes. +
/bom/metadataYYN/AN/A + Only timestamp is currently supported +
/bom/componentsYYYY 
/bom/components/component
./nameYYYY 
./versionYYYY 
./purlYYYY 
+ +### Notes on Schema Support + +1. N/A is where the CycloneDX standard does not include this +2. If the table above does not refer to an element, it is not currently supported + ## The Fine Print Remember: From 0d2c35519374b4efddf399dd519e5a1443a56692 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 17:03:43 +0100 Subject: [PATCH 21/43] Corrected typo in README --- README.md | 2 +- tests/test_e2e_environment.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/test_e2e_environment.py diff --git a/README.md b/README.md index ad2c1f9a..3d940b43 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ outputter.output_as_string() ``` from cyclonedx.output import get_instance, SchemaVersion -outputter = get_instance(bom=bom, schema_version=SchemaVersion.V1_) +outputter = get_instance(bom=bom, schema_version=SchemaVersion.V1_2) outputter.output_to_file(filename='/tmp/sbom-v1.2.xml') ``` diff --git a/tests/test_e2e_environment.py b/tests/test_e2e_environment.py new file mode 100644 index 00000000..b8887d52 --- /dev/null +++ b/tests/test_e2e_environment.py @@ -0,0 +1,16 @@ +from unittest import TestCase + +from cyclonedx.parser.environment import EnvironmentParser + + +class TestRequirementsParser(TestCase): + + def test_simple(self): + """ + @todo This test is a vague as it will detect the unique environment where tests are being executed - + so is this valid? + + :return: + """ + parser = EnvironmentParser() + self.assertGreater(parser.component_count(), 1) From e987f357314199442ed2c5823575833915dfccb1 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 17:04:14 +0100 Subject: [PATCH 22/43] Skeleton support for 'author' + v1.1 and v1.0 for JSON added (along with tests). --- cyclonedx/model/cyclonedx.py | 15 ++++++++-- cyclonedx/output/json.py | 50 +++++++++++++++++++++++++++++---- cyclonedx/output/xml.py | 22 +++++++++++---- cyclonedx/parser/environment.py | 11 +++++++- tests/test_e2e_environment.py | 47 +++++++++++++++++++++++++------ 5 files changed, 121 insertions(+), 24 deletions(-) diff --git a/cyclonedx/model/cyclonedx.py b/cyclonedx/model/cyclonedx.py index e07e4701..ec93d526 100644 --- a/cyclonedx/model/cyclonedx.py +++ b/cyclonedx/model/cyclonedx.py @@ -5,7 +5,7 @@ class ComponentType(Enum): """ - Enum object that defines the permissable 'types' for a Component according to the CycloneDX + Enum object that defines the permissible 'types' for a Component according to the CycloneDX schemas. """ APPLICATION = 'application' @@ -27,12 +27,18 @@ class Component: _version: str _qualifiers: str - def __init__(self, name: str, version: str, qualifiers: str = None, type: ComponentType = ComponentType.LIBRARY): + _author: str = None + + def __init__(self, name: str, version: str, qualifiers: str = None, + component_type: ComponentType = ComponentType.LIBRARY): self._name = name self._version = version - self._type = type + self._type = component_type self._qualifiers = qualifiers + def get_author(self) -> str: + return self._author + def get_name(self) -> str: return self._name @@ -48,6 +54,9 @@ def get_type(self) -> ComponentType: def get_version(self) -> str: return self._version + def set_author(self, author: str): + self._author = author + def __eq__(self, other): return other.get_purl() == self.get_purl() diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index 5281f5d3..354dd5bd 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -14,26 +14,40 @@ def output_to_file(self, filename: str): pass def _get_json(self) -> dict: - components = list(map(Json._get_component_as_dict, self.get_bom().get_components())) + components = list(map(self._get_component_as_dict, self.get_bom().get_components())) - return { + response = { "bomFormat": "CycloneDX", "specVersion": str(self._get_schema_version()), "serialNumber": self.get_bom().get_urn_uuid(), "version": 1, - "metadata": self._get_metadata_as_dict(), "components": components } - @staticmethod - def _get_component_as_dict(component: Component) -> dict: - return { + if self._bom_supports_metadata(): + response['metadata'] = self._get_metadata_as_dict() + + return response + + def _get_component_as_dict(self, component: Component) -> dict: + c = { "type": component.get_type().value, "name": component.get_name(), "version": component.get_version(), "purl": component.get_purl() } + if self._component_supports_author() and component.get_author() is not None: + c['author'] = component.get_author() + + return c + + def _bom_supports_metadata(self) -> bool: + return True + + def _component_supports_author(self) -> bool: + return True + def _get_metadata_as_dict(self) -> dict: metadata = self.get_bom().get_metadata() return { @@ -45,6 +59,30 @@ def _get_schema_version(self) -> str: pass +class JsonV1Dot0(Json): + + def _get_schema_version(self) -> str: + return '1.0' + + def _bom_supports_metadata(self) -> bool: + return False + + def _component_supports_author(self) -> bool: + return False + + +class JsonV1Dot1(Json): + + def _get_schema_version(self) -> str: + return '1.1' + + def _bom_supports_metadata(self) -> bool: + return False + + def _component_supports_author(self) -> bool: + return False + + class JsonV1Dot2(Json): def _get_schema_version(self) -> str: diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 4699f630..f43550bb 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -62,16 +62,19 @@ def _get_component_as_xml_element(self, component: Component) -> ElementTree.Ele if self._component_supports_bom_ref_attribute(): element_attributes['bom-ref'] = component.get_purl() - element = ElementTree.Element('component', element_attributes) + component_element = ElementTree.Element('component', element_attributes) + + if self._component_supports_author() and component.get_author() is not None: + ElementTree.SubElement(component_element, 'author').text = component.get_author() # if publisher and publisher != "UNKNOWN": # ElementTree.SubElement(component, "publisher").text = re.sub(RE_XML_ILLEGAL, "?", publisher) # if name and name != "UNKNOWN": - ElementTree.SubElement(element, 'name').text = component.get_name() + ElementTree.SubElement(component_element, 'name').text = component.get_name() # if version and version != "UNKNOWN": - ElementTree.SubElement(element, 'version').text = component.get_version() + ElementTree.SubElement(component_element, 'version').text = component.get_version() # if description and description != "UNKNOWN": # ElementTree.SubElement(component, "description").text = re.sub(RE_XML_ILLEGAL, "?", description) @@ -90,11 +93,11 @@ def _get_component_as_xml_element(self, component: Component) -> ElementTree.Ele # component_license.license.name) # if purl: - ElementTree.SubElement(element, 'purl').text = component.get_purl() + ElementTree.SubElement(component_element, 'purl').text = component.get_purl() # ElementTree.SubElement(component, "modified").text = modified if modified else "false" - return element + return component_element def _add_metadata(self, bom: ElementTree.Element) -> ElementTree.Element: metadata_e = ElementTree.SubElement(bom, 'metadata') @@ -104,6 +107,9 @@ def _add_metadata(self, bom: ElementTree.Element) -> ElementTree.Element: def _bom_supports_metadata(self) -> bool: return True + def _component_supports_author(self) -> bool: + return True + def _component_supports_bom_ref_attribute(self) -> bool: return True @@ -126,6 +132,9 @@ def _bom_supports_metadata(self) -> bool: def _component_supports_bom_ref_attribute(self) -> bool: return False + def _component_supports_author(self) -> bool: + return False + class XmlV1Dot1(Xml): @@ -135,6 +144,9 @@ def _get_schema_version(self) -> str: def _bom_supports_metadata(self) -> bool: return False + def _component_supports_author(self) -> bool: + return False + class XmlV1Dot2(Xml): diff --git a/cyclonedx/parser/environment.py b/cyclonedx/parser/environment.py index 8bb703e1..bd60cecc 100644 --- a/cyclonedx/parser/environment.py +++ b/cyclonedx/parser/environment.py @@ -12,5 +12,14 @@ class EnvironmentParser(BaseParser): def __init__(self): import pkg_resources + from importlib.metadata import metadata + + i: pkg_resources.DistInfoDistribution for i in iter(pkg_resources.working_set): - self._components.append(Component(name=i.project_name, version=i.version, type='pypi')) + c = Component(name=i.project_name, version=i.version) + i_metadata = metadata(i.project_name) + + if 'Author' in i_metadata.keys(): + c.set_author(i_metadata.get('Author')) + + self._components.append(c) diff --git a/tests/test_e2e_environment.py b/tests/test_e2e_environment.py index b8887d52..9a619da2 100644 --- a/tests/test_e2e_environment.py +++ b/tests/test_e2e_environment.py @@ -1,16 +1,45 @@ +import json from unittest import TestCase +from xml.etree import ElementTree +from cyclonedx.model.bom import Bom +from cyclonedx.output import get_instance, OutputFormat +from cyclonedx.output.json import Json +from cyclonedx.output.xml import Xml from cyclonedx.parser.environment import EnvironmentParser -class TestRequirementsParser(TestCase): +class TestE2EEnvironment(TestCase): - def test_simple(self): - """ - @todo This test is a vague as it will detect the unique environment where tests are being executed - - so is this valid? + def test_json_defaults(self): + outputter: Json = get_instance(bom=Bom.from_parser(EnvironmentParser()), output_format=OutputFormat.JSON) + bom_json = json.loads(outputter.output_as_string()) + component_this_library = next( + (x for x in bom_json['components'] if x['purl'] == 'pkg:pypi/cyclonedx-python-lib@0.0.1'), None + ) - :return: - """ - parser = EnvironmentParser() - self.assertGreater(parser.component_count(), 1) + self.assertTrue('author' in component_this_library.keys(), 'author is missing from JSON BOM') + self.assertEqual(component_this_library['author'], 'Sonatype Community') + self.assertEqual(component_this_library['name'], 'cyclonedx-python-lib') + self.assertEqual(component_this_library['version'], '0.0.1') + + def test_xml_defaults(self): + outputter: Xml = get_instance(bom=Bom.from_parser(EnvironmentParser())) + + # Check we have cyclonedx-python-lib version 0.0.1 with Author, Name and Version + bom_xml_e = ElementTree.fromstring(outputter.output_as_string()) + component_this_library = bom_xml_e.find('./{{{}}}components/{{{}}}component[@bom-ref=\'pkg:pypi/{}\']'.format( + outputter.get_target_namespace(), outputter.get_target_namespace(), 'cyclonedx-python-lib@0.0.1' + )) + + author = component_this_library.find('./{{{}}}author'.format(outputter.get_target_namespace())) + self.assertIsNotNone(author, 'No author element but one was expected.') + self.assertEqual(author.text, 'Sonatype Community') + + name = component_this_library.find('./{{{}}}name'.format(outputter.get_target_namespace())) + self.assertIsNotNone(name, 'No name element but one was expected.') + self.assertEqual(name.text, 'cyclonedx-python-lib') + + version = component_this_library.find('./{{{}}}version'.format(outputter.get_target_namespace())) + self.assertIsNotNone(version, 'No version element but one was expected.') + self.assertEqual(version.text, '0.0.1') From bff5954f70967f3605fa6226a223590b89e07313 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 17:05:13 +0100 Subject: [PATCH 23/43] Updated README to reflect support for author. --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3d940b43..25e0e03a 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,6 @@ We provide two helper methods: 1. Output to string (for you to do with as you require) 2. Output directly to a filename you provide -#### Supported Schema Versions - -| Schema Version | JSON | XML | Notes | -| ---- | ---- | ---- | ---- | -| 1.3 _(current)_ | Y | Y | | -| 1.2 | Y | Y | | - ##### Example as JSON ``` @@ -138,6 +131,11 @@ _Note: We refer throughout using XPath, but the same is true for both XML and JS /bom/components/component + + ./author + YYN/AN/A +   + ./name YYYY From 95c5b389bb5c8c358420aaf5c62694dcabe663ce Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 17:38:42 +0100 Subject: [PATCH 24/43] Refactored output classes to use multiple inheritance allowing a single place to define which schema version support various attributes and elements. --- cyclonedx/output/json.py | 59 ++++++------------------ cyclonedx/output/schema.py | 55 ++++++++++++++++++++++ cyclonedx/output/xml.py | 93 +++++++------------------------------- 3 files changed, 86 insertions(+), 121 deletions(-) create mode 100644 cyclonedx/output/schema.py diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index 354dd5bd..f368a749 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -1,30 +1,31 @@ import json -from abc import abstractmethod +from abc import ABC, abstractmethod from . import BaseOutput +from .schema import BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, SchemaVersion1Dot2, SchemaVersion1Dot3 from ..model.cyclonedx import Component -class Json(BaseOutput): +class Json(BaseOutput, BaseSchemaVersion): def output_as_string(self) -> str: return json.dumps(self._get_json()) def output_to_file(self, filename: str): - pass + raise NotImplemented def _get_json(self) -> dict: components = list(map(self._get_component_as_dict, self.get_bom().get_components())) response = { "bomFormat": "CycloneDX", - "specVersion": str(self._get_schema_version()), + "specVersion": str(self.get_schema_version()), "serialNumber": self.get_bom().get_urn_uuid(), "version": 1, "components": components } - if self._bom_supports_metadata(): + if self.bom_supports_metadata(): response['metadata'] = self._get_metadata_as_dict() return response @@ -37,59 +38,29 @@ def _get_component_as_dict(self, component: Component) -> dict: "purl": component.get_purl() } - if self._component_supports_author() and component.get_author() is not None: + if self.component_supports_author() and component.get_author() is not None: c['author'] = component.get_author() return c - def _bom_supports_metadata(self) -> bool: - return True - - def _component_supports_author(self) -> bool: - return True - def _get_metadata_as_dict(self) -> dict: metadata = self.get_bom().get_metadata() return { "timestamp": metadata.get_timestamp().isoformat() } - @abstractmethod - def _get_schema_version(self) -> str: - pass - - -class JsonV1Dot0(Json): - - def _get_schema_version(self) -> str: - return '1.0' - - def _bom_supports_metadata(self) -> bool: - return False - - def _component_supports_author(self) -> bool: - return False - - -class JsonV1Dot1(Json): - - def _get_schema_version(self) -> str: - return '1.1' - - def _bom_supports_metadata(self) -> bool: - return False - def _component_supports_author(self) -> bool: - return False +class JsonV1Dot0(Json, SchemaVersion1Dot0): + pass -class JsonV1Dot2(Json): +class JsonV1Dot1(Json, SchemaVersion1Dot1): + pass - def _get_schema_version(self) -> str: - return '1.2' +class JsonV1Dot2(Json, SchemaVersion1Dot2): + pass -class JsonV1Dot3(Json): - def _get_schema_version(self) -> str: - return '1.3' +class JsonV1Dot3(Json, SchemaVersion1Dot3): + pass diff --git a/cyclonedx/output/schema.py b/cyclonedx/output/schema.py new file mode 100644 index 00000000..7a2b3c6b --- /dev/null +++ b/cyclonedx/output/schema.py @@ -0,0 +1,55 @@ +from abc import ABC, abstractmethod + + +class BaseSchemaVersion(ABC): + + def bom_supports_metadata(self) -> bool: + return True + + def component_supports_author(self) -> bool: + return True + + def component_supports_bom_ref(self) -> bool: + return True + + def get_schema_version(self) -> str: + pass + + +class SchemaVersion1Dot3(BaseSchemaVersion): + + def get_schema_version(self) -> str: + return '1.3' + + +class SchemaVersion1Dot2(BaseSchemaVersion): + + def get_schema_version(self) -> str: + return '1.2' + + +class SchemaVersion1Dot1(BaseSchemaVersion): + + def bom_supports_metadata(self) -> bool: + return False + + def component_supports_author(self) -> bool: + return False + + def get_schema_version(self) -> str: + return '1.1' + + +class SchemaVersion1Dot0(BaseSchemaVersion): + + def bom_supports_metadata(self) -> bool: + return False + + def component_supports_author(self) -> bool: + return False + + def component_supports_bom_ref(self) -> bool: + return False + + def get_schema_version(self) -> str: + return '1.0' diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index f43550bb..bf099b89 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -1,47 +1,20 @@ -from abc import abstractmethod from xml.etree import ElementTree from . import BaseOutput +from .schema import BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, SchemaVersion1Dot2, SchemaVersion1Dot3 from ..model.cyclonedx import Component -def _xml_pretty_print(elem: ElementTree.Element, level: int = 0) -> ElementTree.Element: - """ - Helper method lifed from cyclonedx-python original project for formatting - XML without using any XML-libraries. - - NOTE: This method is recursive. - - :param elem: - :param level: - :return: - """ - i = "\n" + level * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - _xml_pretty_print(elem, level + 1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - return elem - - -class Xml(BaseOutput): +class Xml(BaseOutput, BaseSchemaVersion): XML_VERSION_DECLARATION: str = '' def get_target_namespace(self) -> str: - return 'http://cyclonedx.org/schema/bom/{}'.format(self._get_schema_version()) + return 'http://cyclonedx.org/schema/bom/{}'.format(self.get_schema_version()) def output_as_string(self) -> str: bom = self._get_bom_root_element() - if self._bom_supports_metadata(): + if self.bom_supports_metadata(): bom = self._add_metadata(bom=bom) components = ElementTree.SubElement(bom, 'components') @@ -53,18 +26,21 @@ def output_as_string(self) -> str: def output_to_file(self, filename: str): pass + def _component_supports_bom_ref_attribute(self) -> bool: + return True + def _get_bom_root_element(self) -> ElementTree.Element: return ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1', 'serialNumber': self.get_bom().get_urn_uuid()}) def _get_component_as_xml_element(self, component: Component) -> ElementTree.Element: element_attributes = {'type': component.get_type().value} - if self._component_supports_bom_ref_attribute(): + if self.component_supports_bom_ref(): element_attributes['bom-ref'] = component.get_purl() component_element = ElementTree.Element('component', element_attributes) - if self._component_supports_author() and component.get_author() is not None: + if self.component_supports_author() and component.get_author() is not None: ElementTree.SubElement(component_element, 'author').text = component.get_author() # if publisher and publisher != "UNKNOWN": @@ -104,57 +80,20 @@ def _add_metadata(self, bom: ElementTree.Element) -> ElementTree.Element: ElementTree.SubElement(metadata_e, 'timestamp').text = self.get_bom().get_metadata().get_timestamp().isoformat() return bom - def _bom_supports_metadata(self) -> bool: - return True - - def _component_supports_author(self) -> bool: - return True - - def _component_supports_bom_ref_attribute(self) -> bool: - return True - - @abstractmethod - def _get_schema_version(self) -> str: - pass - -class XmlV1Dot0(Xml): +class XmlV1Dot0(Xml, SchemaVersion1Dot0): def _get_bom_root_element(self) -> ElementTree.Element: return ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1'}) - def _get_schema_version(self) -> str: - return '1.0' - - def _bom_supports_metadata(self) -> bool: - return False - - def _component_supports_bom_ref_attribute(self) -> bool: - return False - - def _component_supports_author(self) -> bool: - return False - - -class XmlV1Dot1(Xml): - - def _get_schema_version(self) -> str: - return '1.1' - - def _bom_supports_metadata(self) -> bool: - return False - - def _component_supports_author(self) -> bool: - return False - -class XmlV1Dot2(Xml): +class XmlV1Dot1(Xml, SchemaVersion1Dot1): + pass - def _get_schema_version(self) -> str: - return '1.2' +class XmlV1Dot2(Xml, SchemaVersion1Dot2): + pass -class XmlV1Dot3(Xml): - def _get_schema_version(self) -> str: - return '1.3' +class XmlV1Dot3(Xml, SchemaVersion1Dot3): + pass From 3ad394c14d9cbf3e706f4fe47b6f83938576a2ac Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Wed, 1 Sep 2021 17:40:56 +0100 Subject: [PATCH 25/43] Addressing issues reported by flake8. --- cyclonedx/output/json.py | 3 +-- cyclonedx/output/schema.py | 2 +- cyclonedx/output/xml.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index f368a749..ffafa06c 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -1,5 +1,4 @@ import json -from abc import ABC, abstractmethod from . import BaseOutput from .schema import BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, SchemaVersion1Dot2, SchemaVersion1Dot3 @@ -12,7 +11,7 @@ def output_as_string(self) -> str: return json.dumps(self._get_json()) def output_to_file(self, filename: str): - raise NotImplemented + raise NotImplementedError def _get_json(self) -> dict: components = list(map(self._get_component_as_dict, self.get_bom().get_components())) diff --git a/cyclonedx/output/schema.py b/cyclonedx/output/schema.py index 7a2b3c6b..26338cfd 100644 --- a/cyclonedx/output/schema.py +++ b/cyclonedx/output/schema.py @@ -1,4 +1,4 @@ -from abc import ABC, abstractmethod +from abc import ABC class BaseSchemaVersion(ABC): diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index bf099b89..5fd2d20c 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -24,7 +24,7 @@ def output_as_string(self) -> str: return Xml.XML_VERSION_DECLARATION + ElementTree.tostring(bom, 'unicode') def output_to_file(self, filename: str): - pass + raise NotImplementedError def _component_supports_bom_ref_attribute(self) -> bool: return True From e9a67f8a405b6c664d2b91bd4966a8ade9902d40 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Thu, 2 Sep 2021 13:57:19 +0100 Subject: [PATCH 26/43] Added Poetry supprot. --- poetry.lock | 226 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 19 +++++ 2 files changed, 245 insertions(+) create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..3dd10c8e --- /dev/null +++ b/poetry.lock @@ -0,0 +1,226 @@ +[[package]] +name = "backports.entry-points-selectable" +version = "1.1.0" +description = "Compatibility shim providing selectable entry points for older implementations" +category = "dev" +optional = false +python-versions = ">=2.7" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "distlib" +version = "0.3.2" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.0.12" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packageurl-python" +version = "0.9.4" +description = "A \"purl\" aka. Package URL parser and builder" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "platformdirs" +version = "2.3.0" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "requirements-parser" +version = "0.2.0" +description = "Parses Pip requirement files" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tox" +version = "3.24.3" +description = "tox is a generic virtualenv management and test command line tool" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} +filelock = ">=3.0.0" +packaging = ">=14" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" +toml = ">=0.9.4" +virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" + +[package.extras] +docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] + +[[package]] +name = "virtualenv" +version = "20.7.2" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +"backports.entry-points-selectable" = ">=1.0.4" +distlib = ">=0.3.1,<1" +filelock = ">=3.0.0,<4" +platformdirs = ">=2,<3" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "de0e227891f076abfd822ac9244f9a02a079af74c02021d87cc208a7a6d6ae5c" + +[metadata.files] +"backports.entry-points-selectable" = [ + {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, + {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +distlib = [ + {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, + {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +packageurl-python = [ + {file = "packageurl-python-0.9.4.tar.gz", hash = "sha256:bd0e829260baff12055c47e1898e0f4014469d09bdb380ddcb102b5d2392fb56"}, + {file = "packageurl_python-0.9.4-py2.py3-none-any.whl", hash = "sha256:65f1eade0f3f412bdc77401e76725e9fc21d0c742ba0f2d066113cb19ccd8b61"}, +] +packaging = [ + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, +] +platformdirs = [ + {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"}, + {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +requirements-parser = [ + {file = "requirements-parser-0.2.0.tar.gz", hash = "sha256:5963ee895c2d05ae9f58d3fc641082fb38021618979d6a152b6b1398bd7d4ed4"}, + {file = "requirements_parser-0.2.0-py2-none-any.whl", hash = "sha256:76650b4a9d98fc65edf008a7920c076bb2a76c08eaae230ce4cfc6f51ea6a773"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tox = [ + {file = "tox-3.24.3-py2.py3-none-any.whl", hash = "sha256:9fbf8e2ab758b2a5e7cb2c72945e4728089934853076f67ef18d7575c8ab6b88"}, + {file = "tox-3.24.3.tar.gz", hash = "sha256:c6c4e77705ada004283610fd6d9ba4f77bc85d235447f875df9f0ba1bc23b634"}, +] +virtualenv = [ + {file = "virtualenv-20.7.2-py2.py3-none-any.whl", hash = "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06"}, + {file = "virtualenv-20.7.2.tar.gz", hash = "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..c3cf9aa4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "cyclonedx" +version = "0.0.1" +description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files." +authors = ["Paul Horton "] +license = "Apache-2.0" + +[tool.poetry.dependencies] +python = "^3.9" +packageurl-python = "^0.9.4" +requirements_parser = "^0.2.0" +setuptools = "^50.3.2" + +[tool.poetry.dev-dependencies] +tox = "^3.24.3" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file From e2403e8c4194be6bee70a58ef86d9acec6de5dbb Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Thu, 2 Sep 2021 14:17:51 +0100 Subject: [PATCH 27/43] Initial draft GitHub actions being added. --- .github/dependabot.yml | 29 +++++++++++++++++ .github/workflows/poetry.yml | 62 ++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/poetry.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..8b2226c1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,29 @@ +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: 'pip' + directory: '/' + schedule: + interval: 'weekly' + day: 'saturday' + allow: + - dependency-type: 'all' + versioning-strategy: 'auto' + labels: [ 'dependencies' ] + commit-message: + ## prefix maximum string length of 15 + prefix: 'poetry' + include: 'scope' + open-pull-requests-limit: 999 + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' + day: 'saturday' + labels: [ 'dependencies' ] + commit-message: + ## prefix maximum string length of 15 + prefix: 'gh-actions' + include: 'scope' + open-pull-requests-limit: 999 \ No newline at end of file diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml new file mode 100644 index 00000000..aa349255 --- /dev/null +++ b/.github/workflows/poetry.yml @@ -0,0 +1,62 @@ +# For details of what checks are run for PRs please refer below +# docs: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions +name: Python CI + +on: + push: + branches: ["master"] + pull_request: + workflow_dispatch: + schedule: + # schedule weekly tests, since dependencies are not intended to be pinned + # this means: at 23:42 on Fridays + - cron: '42 23 * * 5' + +env: + REPORTS_DIR: CI_reports + +jobs: + build-and-test: + name: Build & Test (Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.9" # highest and lowest supported + timeout-minutes: 30 + steps: + - name: Checkout + # see https://github.com/actions/checkout + uses: actions/checkout@v2 + - name: Create reports directory + run: mkdir ${{ env.REPORTS_DIR }} + - name: Setup Python Environment + # see https://github.com/actions/setup-python + uses: actions/setup-python@v2 + with: + python-version: {{ matrix.python-version }} + architecture: 'x64' + - name: Install poetry + # see https://github.com/marketplace/actions/setup-poetry + uses: Gr1N/setup-poetry@v7 + with: + poetry-version: 1.1.8 + - name: Install dependencies + run: poetry install + - name: Ensure build successful + run: poetry build + - name: Run tox + run: tox + - name: Generate coverage reports + run: > + coverage report && coverage xml -o ${{ env.REPORTS_DIR }}/coverage.xml && + coverage html -d ${{ env.REPORTS_DIR }} + - name: Artifact reports + if: ${{ ! cancelled() }} + # see https://github.com/actions/upload-artifact + uses: actions/upload-artifact@v2 + with: + name: ${{ env.REPORTS_ARTIFACT }} + path: ${{ env.REPORTS_DIR }} + if-no-files-found: error \ No newline at end of file From 75041e51ff684853d7c2b94e5a722a4ec14043fc Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Thu, 2 Sep 2021 14:18:21 +0100 Subject: [PATCH 28/43] Updated poetry dependencies and configuration. --- poetry.lock | 67 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 3dd10c8e..9fb39b85 100644 --- a/poetry.lock +++ b/poetry.lock @@ -18,6 +18,17 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "coverage" +version = "5.5" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +toml = ["toml"] + [[package]] name = "distlib" version = "0.3.2" @@ -161,7 +172,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "de0e227891f076abfd822ac9244f9a02a079af74c02021d87cc208a7a6d6ae5c" +content-hash = "c999095096157597d33c58905fbfe6906cd0d3a0ce21fd3bd4b618c36d15f2bd" [metadata.files] "backports.entry-points-selectable" = [ @@ -172,6 +183,60 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +coverage = [ + {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, + {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, + {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, + {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, + {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, + {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, + {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, + {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, + {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, + {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, + {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, + {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, + {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, + {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, + {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, + {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, + {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, + {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, + {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, + {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, + {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, +] distlib = [ {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"}, diff --git a/pyproject.toml b/pyproject.toml index c3cf9aa4..a0b39a36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ setuptools = "^50.3.2" [tool.poetry.dev-dependencies] tox = "^3.24.3" +coverage = "^5.5" [build-system] requires = ["poetry-core>=1.0.0"] From 2f4917ba81f8ddba994a2c5012303bccb307a419 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Thu, 2 Sep 2021 14:18:39 +0100 Subject: [PATCH 29/43] Correction: Supported Python version in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 14e9e80a..fb291184 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ 'Programming Language :: Python :: 3' ], packages=find_packages(), - python_requires='>=3.6', + python_requires='>=3.9', package_data={ 'cyclonedx': ['schema/*.json', 'schema/*.xsd', 'schema/ext/*.json', 'schema/ext/*.xsd'] }, From 395367531e7a00c086e723a78d059e6016fb242e Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Thu, 2 Sep 2021 14:19:41 +0100 Subject: [PATCH 30/43] Fixed typo in Github action. --- .github/workflows/poetry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index aa349255..22afd21e 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -35,7 +35,7 @@ jobs: # see https://github.com/actions/setup-python uses: actions/setup-python@v2 with: - python-version: {{ matrix.python-version }} + python-version: ${{ matrix.python-version }} architecture: 'x64' - name: Install poetry # see https://github.com/marketplace/actions/setup-poetry From 780e3dfa043957174e1f79cf450d1ee69d6530d3 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Thu, 2 Sep 2021 14:27:06 +0100 Subject: [PATCH 31/43] Added poetry virtualenv caching + wrapped tox and coverage with poetry to ensure they run in the poetry venv. --- .github/workflows/poetry.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index 22afd21e..80d725f8 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -42,16 +42,20 @@ jobs: uses: Gr1N/setup-poetry@v7 with: poetry-version: 1.1.8 + - uses: actions/cache@v2 + with: + path: ~/.cache/pypoetry/virtualenvs + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} - name: Install dependencies run: poetry install - name: Ensure build successful run: poetry build - name: Run tox - run: tox + run: poetry run tox - name: Generate coverage reports run: > - coverage report && coverage xml -o ${{ env.REPORTS_DIR }}/coverage.xml && - coverage html -d ${{ env.REPORTS_DIR }} + poetry run coverage report && coverage xml -o ${{ env.REPORTS_DIR }}/coverage.xml && + poetry run coverage html -d ${{ env.REPORTS_DIR }} - name: Artifact reports if: ${{ ! cancelled() }} # see https://github.com/actions/upload-artifact From 3c74c822445e5aeaaa387c8e5522ca8cd841cfd8 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Thu, 2 Sep 2021 14:29:53 +0100 Subject: [PATCH 32/43] Missed wrapping a coverage command with poetry. --- .github/workflows/poetry.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index 80d725f8..bade11d0 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -54,7 +54,8 @@ jobs: run: poetry run tox - name: Generate coverage reports run: > - poetry run coverage report && coverage xml -o ${{ env.REPORTS_DIR }}/coverage.xml && + poetry run coverage report && + poetry run coverage xml -o ${{ env.REPORTS_DIR }}/coverage.xml && poetry run coverage html -d ${{ env.REPORTS_DIR }} - name: Artifact reports if: ${{ ! cancelled() }} From c750ec62411c6d4473d3cc0a33dc96f90a443cef Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Thu, 2 Sep 2021 14:32:13 +0100 Subject: [PATCH 33/43] Added missing ENV var for GH actions. --- .github/workflows/poetry.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index bade11d0..07a4dc28 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -19,6 +19,8 @@ jobs: build-and-test: name: Build & Test (Python ${{ matrix.python-version }} runs-on: ubuntu-latest + env: + REPORTS_ARTIFACT: tests-reports strategy: fail-fast: false matrix: From ae24ba9c26ddf4ef91937e8489b1894a986724de Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 3 Sep 2021 08:55:39 +0100 Subject: [PATCH 34/43] Added support for Python versions 3.7+ --- .github/workflows/poetry.yml | 5 ++- README.md | 13 +++++++ cyclonedx/parser/environment.py | 17 +++++++-- poetry.lock | 62 +++++++++++++++++++++++++++++++-- pyproject.toml | 3 +- setup.py | 2 +- tox.ini | 2 +- 7 files changed, 96 insertions(+), 8 deletions(-) diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index 07a4dc28..67d4261f 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -25,7 +25,10 @@ jobs: fail-fast: false matrix: python-version: - - "3.9" # highest and lowest supported + - "3.9" # highest supported + - "3.8" + - "3.7" + - "3.6" # lowest supported timeout-minutes: 30 steps: - name: Checkout diff --git a/README.md b/README.md index 25e0e03a..c7f366df 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # Python Library for generating CycloneDX [![CircleCI](https://circleci.com/gh/sonatype-nexus-community/cyclonedx-python-lib.svg?style=shield)](https://circleci.com/gh/sonatype-nexus-community/cyclonedx-python-lib) +![GitHub Workflow Status](https://img.shields.io/github/workflow/status/sonatype-nexus-community/cyclonedx-python-lib/Python%20CI) +![Python Version Support](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9-blue) +[![GitHub license](https://img.shields.io/github/license/sonatype-nexus-community/cyclonedx-python-lib)](https://github.com/sonatype-nexus-community/cyclonedx-python-lib/blob/main/LICENSE) +[![GitHub issues](https://img.shields.io/github/issues/sonatype-nexus-community/cyclonedx-python-lib)](https://github.com/sonatype-nexus-community/cyclonedx-python-lib/issues) +[![GitHub forks](https://img.shields.io/github/forks/sonatype-nexus-community/cyclonedx-python-lib)](https://github.com/sonatype-nexus-community/cyclonedx-python-lib/network) +[![GitHub stars](https://img.shields.io/github/stars/sonatype-nexus-community/cyclonedx-python-lib)](https://github.com/sonatype-nexus-community/cyclonedx-python-lib/stargazers) + +---- This CycloneDX module for Python can generate valid CycloneDX bill-of-material document containing an aggregate of all project dependencies. @@ -159,6 +167,11 @@ _Note: We refer throughout using XPath, but the same is true for both XML and JS 1. N/A is where the CycloneDX standard does not include this 2. If the table above does not refer to an element, it is not currently supported +## Python Support + +We endeavour to support all functionality for all [current actively supported Python versions](https://www.python.org/downloads/). +However, some features may not be possible/present in older Python versions due to their lack of support. + ## The Fine Print Remember: diff --git a/cyclonedx/parser/environment.py b/cyclonedx/parser/environment.py index bd60cecc..13b1d4d4 100644 --- a/cyclonedx/parser/environment.py +++ b/cyclonedx/parser/environment.py @@ -1,3 +1,10 @@ +import sys + +if sys.version_info >= (3, 8, 0): + from importlib.metadata import metadata +else: + from importlib_metadata import metadata + from . import BaseParser from ..model.cyclonedx import Component @@ -12,14 +19,20 @@ class EnvironmentParser(BaseParser): def __init__(self): import pkg_resources - from importlib.metadata import metadata i: pkg_resources.DistInfoDistribution for i in iter(pkg_resources.working_set): c = Component(name=i.project_name, version=i.version) - i_metadata = metadata(i.project_name) + i_metadata = self._get_metadata_for_package(i.project_name) if 'Author' in i_metadata.keys(): c.set_author(i_metadata.get('Author')) self._components.append(c) + + @staticmethod + def _get_metadata_for_package(package_name: str): + if sys.version_info >= (3, 8, 0): + return metadata(package_name) + else: + return metadata(package_name) diff --git a/poetry.lock b/poetry.lock index 9fb39b85..a8fd3756 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,6 +6,9 @@ category = "dev" optional = false python-versions = ">=2.7" +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] @@ -45,6 +48,23 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "importlib-metadata" +version = "4.8.1" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + [[package]] name = "packageurl-python" version = "0.9.4" @@ -84,6 +104,9 @@ category = "dev" optional = false python-versions = ">=3.6" +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] @@ -139,6 +162,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} filelock = ">=3.0.0" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" @@ -150,6 +174,14 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] +[[package]] +name = "typing-extensions" +version = "3.10.0.2" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "virtualenv" version = "20.7.2" @@ -162,6 +194,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" "backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.0.0,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} platformdirs = ">=2,<3" six = ">=1.9.0,<2" @@ -169,10 +202,22 @@ six = ">=1.9.0,<2" docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] +[[package]] +name = "zipp" +version = "3.5.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + [metadata] lock-version = "1.1" -python-versions = "^3.9" -content-hash = "c999095096157597d33c58905fbfe6906cd0d3a0ce21fd3bd4b618c36d15f2bd" +python-versions = "^3.7" +content-hash = "472cd0e242e0e5092197ef5f52350af4dfc97f79cc0e616915335de93e68871b" [metadata.files] "backports.entry-points-selectable" = [ @@ -245,6 +290,10 @@ filelock = [ {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, ] +importlib-metadata = [ + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, +] packageurl-python = [ {file = "packageurl-python-0.9.4.tar.gz", hash = "sha256:bd0e829260baff12055c47e1898e0f4014469d09bdb380ddcb102b5d2392fb56"}, {file = "packageurl_python-0.9.4-py2.py3-none-any.whl", hash = "sha256:65f1eade0f3f412bdc77401e76725e9fc21d0c742ba0f2d066113cb19ccd8b61"}, @@ -285,7 +334,16 @@ tox = [ {file = "tox-3.24.3-py2.py3-none-any.whl", hash = "sha256:9fbf8e2ab758b2a5e7cb2c72945e4728089934853076f67ef18d7575c8ab6b88"}, {file = "tox-3.24.3.tar.gz", hash = "sha256:c6c4e77705ada004283610fd6d9ba4f77bc85d235447f875df9f0ba1bc23b634"}, ] +typing-extensions = [ + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, +] virtualenv = [ {file = "virtualenv-20.7.2-py2.py3-none-any.whl", hash = "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06"}, {file = "virtualenv-20.7.2.tar.gz", hash = "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0"}, ] +zipp = [ + {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, + {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, +] diff --git a/pyproject.toml b/pyproject.toml index a0b39a36..029f0e41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,10 +6,11 @@ authors = ["Paul Horton "] license = "Apache-2.0" [tool.poetry.dependencies] -python = "^3.9" +python = "^3.7" packageurl-python = "^0.9.4" requirements_parser = "^0.2.0" setuptools = "^50.3.2" +importlib-metadata = "^4.8.1" [tool.poetry.dev-dependencies] tox = "^3.24.3" diff --git a/setup.py b/setup.py index fb291184..03e99ffb 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ 'Programming Language :: Python :: 3' ], packages=find_packages(), - python_requires='>=3.9', + python_requires='>=3.7', package_data={ 'cyclonedx': ['schema/*.json', 'schema/*.xsd', 'schema/ext/*.json', 'schema/ext/*.xsd'] }, diff --git a/tox.ini b/tox.ini index 33a566a0..52ffd253 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion=3.9.0 -envlist = flake8,py39 +envlist = flake8,py39,py38,py37 [testenv] deps = From affb6b2dc7afeaff5b5cd0a1d4f65678394a2ff7 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 3 Sep 2021 10:26:59 +0100 Subject: [PATCH 35/43] Attempt to fix CI's for multiple Python environments. --- .circleci/config.yml | 34 +++++++++++++++++++++++----------- .github/workflows/poetry.yml | 2 +- tox.ini | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b9ec1cb6..8ccf05a9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,13 +15,23 @@ version: 2.1 executors: + python37: + docker: + - image: circleci/python:3.7 + python38: + docker: + - image: circleci/python:3.8 python39: docker: - image: circleci/python:3.9 jobs: publish: - executor: python39 + parameters: + executor: + type: executor + default: python39 + executor: << parameters.executor >> environment: PIPENV_VENV_IN_PROJECT: true steps: @@ -73,15 +83,12 @@ workflows: version: 2 build_and_test_and_publish: jobs: - - build -# TODO: enable to publish after successful build -# - publish: -# filters: -# branches: -# only: main -# context: pypi -# requires: -# - build + - build: + executor: python37 + - build: + executor: python38 + - build: + executor: python39 build_nightly: triggers: @@ -91,4 +98,9 @@ workflows: branches: only: main jobs: - - build + - build: + executor: python37 + - build: + executor: python38 + - build: + executor: python39 diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index 67d4261f..3fdf3358 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -56,7 +56,7 @@ jobs: - name: Ensure build successful run: poetry build - name: Run tox - run: poetry run tox + run: poetry run tox -e py${{ matrix.python-version }} - name: Generate coverage reports run: > poetry run coverage report && diff --git a/tox.ini b/tox.ini index 52ffd253..c6321385 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion=3.9.0 -envlist = flake8,py39,py38,py37 +envlist = flake8,py3.9,py3.8,py3.7 [testenv] deps = From 8c01da3d8f6038fb24df07ab3fb0945c79893e9f Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 3 Sep 2021 12:57:19 +0100 Subject: [PATCH 36/43] Disabled Py3.6 checks and added flake8. --- .github/workflows/poetry.yml | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index 3fdf3358..ed68c4ba 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -16,6 +16,33 @@ env: REPORTS_DIR: CI_reports jobs: + coding-standards: + name: Linting & Coding Standards + runs-on: ubuntu-latest + steps: + - name: Checkout + # see https://github.com/actions/checkout + uses: actions/checkout@v2 + - name: Setup Python Environment + # see https://github.com/actions/setup-python + uses: actions/setup-python@v2 + with: + python-version: python3.9 + architecture: 'x64' + - name: Install poetry + # see https://github.com/marketplace/actions/setup-poetry + uses: Gr1N/setup-poetry@v7 + with: + poetry-version: 1.1.8 + - uses: actions/cache@v2 + with: + path: ~/.cache/pypoetry/virtualenvs + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} + - name: Install dependencies + run: poetry install + - name: Run tox + run: poetry run tox -e flake8 + build-and-test: name: Build & Test (Python ${{ matrix.python-version }} runs-on: ubuntu-latest @@ -28,7 +55,7 @@ jobs: - "3.9" # highest supported - "3.8" - "3.7" - - "3.6" # lowest supported +# - "3.6" # lowest supported timeout-minutes: 30 steps: - name: Checkout From d2aa277bce954100adad42e33c095bc1f9ce23cd Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 3 Sep 2021 13:47:12 +0100 Subject: [PATCH 37/43] Fixes to GitHub actions. --- .github/workflows/poetry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index ed68c4ba..8fa2ee21 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -27,7 +27,7 @@ jobs: # see https://github.com/actions/setup-python uses: actions/setup-python@v2 with: - python-version: python3.9 + python-version: 3.9 architecture: 'x64' - name: Install poetry # see https://github.com/marketplace/actions/setup-poetry From a446f4cb197fd40a3065a372108c1719cde91136 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 3 Sep 2021 13:54:14 +0100 Subject: [PATCH 38/43] Fixing CircleCI config. --- .circleci/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8ccf05a9..cc522f31 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,7 +49,11 @@ jobs: # TODO: perform publish steps, maybe using python-semantic-release build: - executor: python39 + parameters: + executor: + type: executor + default: python39 + executor: << parameters.executor >> environment: PIPENV_VENV_IN_PROJECT: true steps: From daa12ba8925128da040cf836bc3f16a2126e9091 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 3 Sep 2021 16:23:07 +0100 Subject: [PATCH 39/43] Adding Python 3.6 support for test & CI. --- .circleci/config.yml | 7 +++++++ .github/workflows/poetry.yml | 2 +- tox.ini | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cc522f31..fd42402a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,6 +15,9 @@ version: 2.1 executors: + python36: + docker: + - image: circleci/python:3.6 python37: docker: - image: circleci/python:3.7 @@ -87,6 +90,8 @@ workflows: version: 2 build_and_test_and_publish: jobs: + - build: + executor: python36 - build: executor: python37 - build: @@ -102,6 +107,8 @@ workflows: branches: only: main jobs: + - build: + executor: python36 - build: executor: python37 - build: diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index 8fa2ee21..8990a985 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -55,7 +55,7 @@ jobs: - "3.9" # highest supported - "3.8" - "3.7" -# - "3.6" # lowest supported + - "3.6" # lowest supported timeout-minutes: 30 steps: - name: Checkout diff --git a/tox.ini b/tox.ini index c6321385..36c5ba44 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion=3.9.0 -envlist = flake8,py3.9,py3.8,py3.7 +envlist = flake8,py3.9,py3.8,py3.7,py3.6 [testenv] deps = From 619ee1dfc23f7220a1941c3fa5068761346c84cb Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 3 Sep 2021 16:32:35 +0100 Subject: [PATCH 40/43] Updated project to state support from Python v3.6+ --- pyproject.toml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 029f0e41..ce570c06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Paul Horton "] license = "Apache-2.0" [tool.poetry.dependencies] -python = "^3.7" +python = "^3.6" packageurl-python = "^0.9.4" requirements_parser = "^0.2.0" setuptools = "^50.3.2" diff --git a/setup.py b/setup.py index 03e99ffb..14e9e80a 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ 'Programming Language :: Python :: 3' ], packages=find_packages(), - python_requires='>=3.7', + python_requires='>=3.6', package_data={ 'cyclonedx': ['schema/*.json', 'schema/*.xsd', 'schema/ext/*.json', 'schema/ext/*.xsd'] }, From 5d3d49184039a2f41411cd96d5dfcf1544fab05f Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 3 Sep 2021 16:39:07 +0100 Subject: [PATCH 41/43] Forgot to add updated poetry.lock file relfecting Python 3.6+ support --- poetry.lock | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index a8fd3756..c63968a1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -65,6 +65,21 @@ docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] perf = ["ipython"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +[[package]] +name = "importlib-resources" +version = "5.2.2" +description = "Read resources from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] + [[package]] name = "packageurl-python" version = "0.9.4" @@ -195,6 +210,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" distlib = ">=0.3.1,<1" filelock = ">=3.0.0,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} platformdirs = ">=2,<3" six = ">=1.9.0,<2" @@ -216,8 +232,8 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" -python-versions = "^3.7" -content-hash = "472cd0e242e0e5092197ef5f52350af4dfc97f79cc0e616915335de93e68871b" +python-versions = "^3.6" +content-hash = "bc05fb928d057f1640afadcd1413625114bbe97b215619acefa9f715295d1a1a" [metadata.files] "backports.entry-points-selectable" = [ @@ -294,6 +310,10 @@ importlib-metadata = [ {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] +importlib-resources = [ + {file = "importlib_resources-5.2.2-py3-none-any.whl", hash = "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977"}, + {file = "importlib_resources-5.2.2.tar.gz", hash = "sha256:a65882a4d0fe5fbf702273456ba2ce74fe44892c25e42e057aca526b702a6d4b"}, +] packageurl-python = [ {file = "packageurl-python-0.9.4.tar.gz", hash = "sha256:bd0e829260baff12055c47e1898e0f4014469d09bdb380ddcb102b5d2392fb56"}, {file = "packageurl_python-0.9.4-py2.py3-none-any.whl", hash = "sha256:65f1eade0f3f412bdc77401e76725e9fc21d0c742ba0f2d066113cb19ccd8b61"}, From 03d03edfca7bed56d21733120cb5b002a32bb466 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 3 Sep 2021 17:00:55 +0100 Subject: [PATCH 42/43] Renamed model file to not reference CycloneDX as the models are agnostic on purpose. --- cyclonedx/model/bom.py | 2 +- cyclonedx/model/{cyclonedx.py => component.py} | 0 cyclonedx/output/json.py | 2 +- cyclonedx/output/xml.py | 2 +- cyclonedx/parser/__init__.py | 2 +- cyclonedx/parser/environment.py | 2 +- cyclonedx/parser/requirements.py | 2 +- tests/test_bom.py | 2 +- tests/test_component.py | 2 +- tests/test_output_json.py | 2 +- tests/test_output_xml.py | 2 +- 11 files changed, 10 insertions(+), 10 deletions(-) rename cyclonedx/model/{cyclonedx.py => component.py} (100%) diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index db743a7c..6510d885 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -2,7 +2,7 @@ from typing import List from uuid import uuid4 -from .cyclonedx import Component +from .component import Component from ..parser import BaseParser diff --git a/cyclonedx/model/cyclonedx.py b/cyclonedx/model/component.py similarity index 100% rename from cyclonedx/model/cyclonedx.py rename to cyclonedx/model/component.py diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index ffafa06c..1eb12977 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -2,7 +2,7 @@ from . import BaseOutput from .schema import BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, SchemaVersion1Dot2, SchemaVersion1Dot3 -from ..model.cyclonedx import Component +from ..model.component import Component class Json(BaseOutput, BaseSchemaVersion): diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 5fd2d20c..04874a26 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -2,7 +2,7 @@ from . import BaseOutput from .schema import BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, SchemaVersion1Dot2, SchemaVersion1Dot3 -from ..model.cyclonedx import Component +from ..model.component import Component class Xml(BaseOutput, BaseSchemaVersion): diff --git a/cyclonedx/parser/__init__.py b/cyclonedx/parser/__init__.py index cb4f12d9..8e8eb05d 100644 --- a/cyclonedx/parser/__init__.py +++ b/cyclonedx/parser/__init__.py @@ -1,7 +1,7 @@ from abc import ABC from typing import List -from ..model.cyclonedx import Component +from ..model.component import Component class BaseParser(ABC): diff --git a/cyclonedx/parser/environment.py b/cyclonedx/parser/environment.py index 13b1d4d4..7321f37d 100644 --- a/cyclonedx/parser/environment.py +++ b/cyclonedx/parser/environment.py @@ -7,7 +7,7 @@ from . import BaseParser -from ..model.cyclonedx import Component +from ..model.component import Component class EnvironmentParser(BaseParser): diff --git a/cyclonedx/parser/requirements.py b/cyclonedx/parser/requirements.py index 84516325..0d65649c 100644 --- a/cyclonedx/parser/requirements.py +++ b/cyclonedx/parser/requirements.py @@ -2,7 +2,7 @@ from . import BaseParser -from ..model.cyclonedx import Component +from ..model.component import Component class RequirementsParser(BaseParser): diff --git a/tests/test_bom.py b/tests/test_bom.py index 272d4b51..d746d058 100644 --- a/tests/test_bom.py +++ b/tests/test_bom.py @@ -3,7 +3,7 @@ import os from cyclonedx.model.bom import Bom -from cyclonedx.model.cyclonedx import Component +from cyclonedx.model.component import Component from cyclonedx.parser.requirements import RequirementsFileParser diff --git a/tests/test_component.py b/tests/test_component.py index 94209959..ba7b7645 100644 --- a/tests/test_component.py +++ b/tests/test_component.py @@ -1,6 +1,6 @@ from unittest import TestCase -from cyclonedx.model.cyclonedx import Component +from cyclonedx.model.component import Component from packageurl import PackageURL diff --git a/tests/test_output_json.py b/tests/test_output_json.py index 2b666e8f..5b2f43aa 100644 --- a/tests/test_output_json.py +++ b/tests/test_output_json.py @@ -2,7 +2,7 @@ from tests.base import BaseJsonTestCase from cyclonedx.model.bom import Bom -from cyclonedx.model.cyclonedx import Component +from cyclonedx.model.component import Component from cyclonedx.output import get_instance, OutputFormat, SchemaVersion from cyclonedx.output.json import JsonV1Dot3, JsonV1Dot2 diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py index ce9a9442..7d7b4aba 100644 --- a/tests/test_output_xml.py +++ b/tests/test_output_xml.py @@ -1,7 +1,7 @@ from os.path import dirname, join from cyclonedx.model.bom import Bom -from cyclonedx.model.cyclonedx import Component +from cyclonedx.model.component import Component from cyclonedx.output import get_instance, SchemaVersion from cyclonedx.output.xml import XmlV1Dot3, XmlV1Dot2, XmlV1Dot1, XmlV1Dot0, Xml From bb6bb24440996257ce609b0f399f930153b65e8e Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Mon, 6 Sep 2021 10:55:39 +0100 Subject: [PATCH 43/43] Added license headers to all source files. Added classifiers for Python version to setup.py. --- cyclonedx/generator.py | 0 cyclonedx/model/bom.py | 16 ++++++++++++++++ cyclonedx/model/component.py | 30 ++++++++++++++++++++++++++++++ cyclonedx/output/__init__.py | 17 ++++++++++++++++- cyclonedx/output/json.py | 16 ++++++++++++++++ cyclonedx/output/schema.py | 16 ++++++++++++++++ cyclonedx/output/xml.py | 16 ++++++++++++++++ cyclonedx/parser/__init__.py | 16 ++++++++++++++++ cyclonedx/parser/environment.py | 22 +++++++++++++++++++++- cyclonedx/parser/requirements.py | 17 ++++++++++++++++- setup.py | 6 +++++- tests/base.py | 19 +++++++++++++++++-- tests/test_bom.py | 17 ++++++++++++++++- tests/test_component.py | 19 ++++++++++++++++++- tests/test_e2e_environment.py | 16 ++++++++++++++++ tests/test_output_generic.py | 16 ++++++++++++++++ tests/test_output_json.py | 18 +++++++++++++++++- tests/test_output_xml.py | 17 ++++++++++++++++- tests/test_parser_environment.py | 16 ++++++++++++++++ tests/test_parser_requirements.py | 16 ++++++++++++++++ 20 files changed, 316 insertions(+), 10 deletions(-) delete mode 100644 cyclonedx/generator.py diff --git a/cyclonedx/generator.py b/cyclonedx/generator.py deleted file mode 100644 index e69de29b..00000000 diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 6510d885..d7a4ac07 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + import datetime from typing import List from uuid import uuid4 diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index ec93d526..5cfed9b3 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + from enum import Enum PURL_TYPE_PREFIX = 'pypi' @@ -28,6 +44,8 @@ class Component: _qualifiers: str _author: str = None + _description: str = None + _license: str = None def __init__(self, name: str, version: str, qualifiers: str = None, component_type: ComponentType = ComponentType.LIBRARY): @@ -39,6 +57,12 @@ def __init__(self, name: str, version: str, qualifiers: str = None, def get_author(self) -> str: return self._author + def get_description(self) -> str: + return self._description + + def get_license(self) -> str: + return self._license + def get_name(self) -> str: return self._name @@ -57,6 +81,12 @@ def get_version(self) -> str: def set_author(self, author: str): self._author = author + def set_description(self, description: str): + self._description = description + + def set_license(self, license_str: str): + self._license = license_str + def __eq__(self, other): return other.get_purl() == self.get_purl() diff --git a/cyclonedx/output/__init__.py b/cyclonedx/output/__init__.py index a2c6ea23..a83e1618 100644 --- a/cyclonedx/output/__init__.py +++ b/cyclonedx/output/__init__.py @@ -1,5 +1,20 @@ -import importlib +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +import importlib from abc import ABC, abstractmethod from enum import Enum diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index 1eb12977..99bda479 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + import json from . import BaseOutput diff --git a/cyclonedx/output/schema.py b/cyclonedx/output/schema.py index 26338cfd..03f637ab 100644 --- a/cyclonedx/output/schema.py +++ b/cyclonedx/output/schema.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + from abc import ABC diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index 04874a26..734e0e11 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + from xml.etree import ElementTree from . import BaseOutput diff --git a/cyclonedx/parser/__init__.py b/cyclonedx/parser/__init__.py index 8e8eb05d..27b65a09 100644 --- a/cyclonedx/parser/__init__.py +++ b/cyclonedx/parser/__init__.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + from abc import ABC from typing import List diff --git a/cyclonedx/parser/environment.py b/cyclonedx/parser/environment.py index 7321f37d..8cea45d7 100644 --- a/cyclonedx/parser/environment.py +++ b/cyclonedx/parser/environment.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + import sys if sys.version_info >= (3, 8, 0): @@ -25,8 +41,12 @@ def __init__(self): c = Component(name=i.project_name, version=i.version) i_metadata = self._get_metadata_for_package(i.project_name) + print(i_metadata.keys()) if 'Author' in i_metadata.keys(): - c.set_author(i_metadata.get('Author')) + c.set_author(author=i_metadata.get('Author')) + + if 'License' in i_metadata.keys(): + c.set_license(license_str=i_metadata.get('License')) self._components.append(c) diff --git a/cyclonedx/parser/requirements.py b/cyclonedx/parser/requirements.py index 0d65649c..8950942e 100644 --- a/cyclonedx/parser/requirements.py +++ b/cyclonedx/parser/requirements.py @@ -1,7 +1,22 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + import pkg_resources from . import BaseParser - from ..model.component import Component diff --git a/setup.py b/setup.py index 14e9e80a..6f0d79a3 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ keywords=["BOM", "SBOM", "SCA", "OWASP"], license="Apache-2.0", classifiers=[ + 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'Intended Audience :: Legal Industry', @@ -26,7 +27,10 @@ 'Topic :: Software Development', 'Topic :: System :: Software Distribution', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3' + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9' ], packages=find_packages(), python_requires='>=3.6', diff --git a/tests/base.py b/tests/base.py index 0f4ae1e0..880fc6d7 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,8 +1,23 @@ -import xml.etree.ElementTree -from unittest import TestCase +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 import json +import xml.etree.ElementTree from datetime import datetime, timezone +from unittest import TestCase from uuid import uuid4 from xml.dom import minidom diff --git a/tests/test_bom.py b/tests/test_bom.py index d746d058..7c7695da 100644 --- a/tests/test_bom.py +++ b/tests/test_bom.py @@ -1,6 +1,21 @@ -from unittest import TestCase +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 import os +from unittest import TestCase from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component diff --git a/tests/test_component.py b/tests/test_component.py index ba7b7645..99d1f4c5 100644 --- a/tests/test_component.py +++ b/tests/test_component.py @@ -1,8 +1,25 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + from unittest import TestCase -from cyclonedx.model.component import Component from packageurl import PackageURL +from cyclonedx.model.component import Component + class TestComponent(TestCase): _component: Component diff --git a/tests/test_e2e_environment.py b/tests/test_e2e_environment.py index 9a619da2..1b7904b8 100644 --- a/tests/test_e2e_environment.py +++ b/tests/test_e2e_environment.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + import json from unittest import TestCase from xml.etree import ElementTree diff --git a/tests/test_output_generic.py b/tests/test_output_generic.py index 60e6eab0..74eb671b 100644 --- a/tests/test_output_generic.py +++ b/tests/test_output_generic.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + from unittest import TestCase from cyclonedx.output import get_instance, OutputFormat, SchemaVersion diff --git a/tests/test_output_json.py b/tests/test_output_json.py index 5b2f43aa..7feb0bdb 100644 --- a/tests/test_output_json.py +++ b/tests/test_output_json.py @@ -1,10 +1,26 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + from os.path import dirname, join -from tests.base import BaseJsonTestCase from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from cyclonedx.output import get_instance, OutputFormat, SchemaVersion from cyclonedx.output.json import JsonV1Dot3, JsonV1Dot2 +from tests.base import BaseJsonTestCase class TestOutputJson(BaseJsonTestCase): diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py index 7d7b4aba..663639df 100644 --- a/tests/test_output_xml.py +++ b/tests/test_output_xml.py @@ -1,10 +1,25 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + from os.path import dirname, join from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from cyclonedx.output import get_instance, SchemaVersion from cyclonedx.output.xml import XmlV1Dot3, XmlV1Dot2, XmlV1Dot1, XmlV1Dot0, Xml - from tests.base import BaseXmlTestCase diff --git a/tests/test_parser_environment.py b/tests/test_parser_environment.py index b8887d52..02c210e0 100644 --- a/tests/test_parser_environment.py +++ b/tests/test_parser_environment.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + from unittest import TestCase from cyclonedx.parser.environment import EnvironmentParser diff --git a/tests/test_parser_requirements.py b/tests/test_parser_requirements.py index 7ee2515d..dfb67fad 100644 --- a/tests/test_parser_requirements.py +++ b/tests/test_parser_requirements.py @@ -1,3 +1,19 @@ +# encoding: utf-8 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + import os from unittest import TestCase