diff --git a/.rubocop.yml b/.rubocop.yml index f851cda..d4610b9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -35,7 +35,7 @@ Metrics/BlockLength: # Allow some long methods because breaking them up doesn't help anything. Metrics/MethodLength: - AllowedMethods: ['parse_options', 'add_to_bom', 'append_all_pod_dependencies'] + AllowedMethods: ['parse_options', 'add_to_bom', 'append_all_pod_dependencies', 'xml_add_evidence'] Metrics/AbcSize: AllowedMethods: ['parse_options', 'add_to_bom', 'source_for_pod'] diff --git a/CHANGELOG.md b/CHANGELOG.md index fd01f1e..62a12fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.4.0] + +### Added +- Added `evidence` element to the component output to indicate that we are doing manifest analysis to generate the bom. ([Issue #69](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/69)) [@macblazer](https://github.com/macblazer). ### Fixed +- Added top level dependencies when the metadata/component is specified (by using the `--name`, `--version`, and `--type` parameters). ([PR #70](https://github.com/CycloneDX/cyclonedx-cocoapods/pull/70)) [@fnxpt](https://github.com/fnxpt) - Properly concatenate paths to Podfile and Podfile.lock (with unit tests!). ([Issue #71](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/71)) [@macblazer](https://github.com/macblazer). ## [1.3.0] diff --git a/cyclonedx-cocoapods.gemspec b/cyclonedx-cocoapods.gemspec index 864eee2..832d9d4 100644 --- a/cyclonedx-cocoapods.gemspec +++ b/cyclonedx-cocoapods.gemspec @@ -26,8 +26,8 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_runtime_dependency 'cocoapods', ['>= 1.10.1', '< 2.0'] - spec.add_runtime_dependency 'nokogiri', ['>= 1.11.2', '< 2.0'] + spec.add_dependency 'cocoapods', ['>= 1.10.1', '< 2.0'] + spec.add_dependency 'nokogiri', ['>= 1.11.2', '< 2.0'] spec.add_development_dependency 'equivalent-xml', '~> 0.6.0' spec.add_development_dependency 'rake', '~> 13.0' diff --git a/example_bom.xml b/example_bom.xml index b62e5ef..dd4edc0 100644 --- a/example_bom.xml +++ b/example_bom.xml @@ -1,15 +1,15 @@ - + - 2024-02-08T06:35:59Z + 2024-10-15T03:43:07Z CycloneDX cyclonedx-cocoapods - 1.3.0 + 1.4.0 - + kizitonwose/PodsUpdater 1.0.3 @@ -35,6 +35,19 @@ http://github.com/raspu/Highlightr + + + purl + 0.6 + + + manifest-analysis + 0.6 + PodsUpdater/Podfile.lock + + + + Krunoslav Zaher <krunoslav.zaher@gmail.com> @@ -56,6 +69,19 @@ https://github.com/ReactiveX/RxSwift + + + purl + 0.6 + + + manifest-analysis + 0.6 + PodsUpdater/Podfile.lock + + + + Krunoslav Zaher <krunoslav.zaher@gmail.com> @@ -79,6 +105,19 @@ https://github.com/ReactiveX/RxSwift + + + purl + 0.6 + + + manifest-analysis + 0.6 + PodsUpdater/Podfile.lock + + + + Krunoslav Zaher <krunoslav.zaher@gmail.com> @@ -110,9 +149,27 @@ git diff | grep bug | less # linux pipes - programs communicate by sen https://github.com/ReactiveX/RxSwift + + + purl + 0.6 + + + manifest-analysis + 0.6 + PodsUpdater/Podfile.lock + + + + + + + + + diff --git a/lib/cyclonedx/cocoapods/bom_builder.rb b/lib/cyclonedx/cocoapods/bom_builder.rb index 7e7720f..57152f2 100644 --- a/lib/cyclonedx/cocoapods/bom_builder.rb +++ b/lib/cyclonedx/cocoapods/bom_builder.rb @@ -104,10 +104,28 @@ def xml_add_homepage(xml) end end - def add_to_bom(xml, trim_strings_length = 0) + # Add evidence of the purl identity. + # See https://github.com/CycloneDX/guides/blob/main/SBOM/en/0x60-Evidence.md for more info + def xml_add_evidence(xml, manifest_path) + xml.evidence do + xml.identity do + xml.field 'purl' + xml.confidence '0.6' + xml.methods_ do + xml.method_ do + xml.technique 'manifest-analysis' + xml.confidence '0.6' + xml.value manifest_path + end + end + end + end + end + + def add_to_bom(xml, manifest_path, trim_strings_length = 0) xml.component(type: 'library', 'bom-ref': purl) do xml_add_author(xml, trim_strings_length) - xml.name name + xml.name_ name xml.version version.to_s xml.description { xml.cdata description } unless description.nil? unless checksum.nil? @@ -126,6 +144,8 @@ def add_to_bom(xml, trim_strings_length = 0) xml.purl purl.slice(0, trim_strings_length) end xml_add_homepage(xml) + + xml_add_evidence(xml, manifest_path) end end @@ -133,7 +153,7 @@ class License def add_to_bom(xml) xml.license do xml.id identifier if identifier_type == :id - xml.name identifier if identifier_type == :name + xml.name_ identifier if identifier_type == :name xml.text_ text unless text.nil? xml.url url unless url.nil? end @@ -145,19 +165,21 @@ class Component def add_to_bom(xml) xml.component(type: type, 'bom-ref': bomref) do xml.group group unless group.nil? - xml.name name + xml.name_ name xml.version version end end end + # Turns the internal model data into an XML bom. class BOMBuilder NAMESPACE = 'http://cyclonedx.org/schema/bom/1.5' - attr_reader :component, :pods, :dependencies + attr_reader :component, :pods, :manifest_path, :dependencies - def initialize(pods:, component: nil, dependencies: nil) + def initialize(pods:, manifest_path:, component: nil, dependencies: nil) @pods = pods.sort_by(&:purl) + @manifest_path = manifest_path @component = component @dependencies = dependencies&.sort end @@ -184,17 +206,17 @@ def unchecked_bom(version: 1, trim_strings_length: 0) xml.bom(xmlns: NAMESPACE, version: version.to_i.to_s, serialNumber: "urn:uuid:#{SecureRandom.uuid}") do bom_metadata(xml) - bom_components(xml, pods, trim_strings_length) + bom_components(xml, pods, manifest_path, trim_strings_length) bom_dependencies(xml, dependencies) end end.to_xml end - def bom_components(xml, pods, trim_strings_length) + def bom_components(xml, pods, manifest_path, trim_strings_length) xml.components do pods.each do |pod| - pod.add_to_bom(xml, trim_strings_length) + pod.add_to_bom(xml, manifest_path, trim_strings_length) end end end @@ -223,7 +245,7 @@ def bom_tools(xml) xml.tools do xml.tool do xml.vendor 'CycloneDX' - xml.name 'cyclonedx-cocoapods' + xml.name_ 'cyclonedx-cocoapods' xml.version VERSION end end diff --git a/lib/cyclonedx/cocoapods/cli_runner.rb b/lib/cyclonedx/cocoapods/cli_runner.rb index 7c8cb2f..12973ad 100644 --- a/lib/cyclonedx/cocoapods/cli_runner.rb +++ b/lib/cyclonedx/cocoapods/cli_runner.rb @@ -39,9 +39,9 @@ def run setup_logger(verbose: options[:verbose]) @logger.debug "Running cyclonedx-cocoapods with options: #{options}" - component, pods, dependencies = analyze(options) + component, pods, manifest_path, dependencies = analyze(options) - build_and_write_bom(options, component, pods, dependencies) + build_and_write_bom(options, component, pods, manifest_path, dependencies) rescue StandardError => e @logger.error ([e.message] + e.backtrace).join($INPUT_RECORD_SEPARATOR) exit 1 @@ -144,11 +144,18 @@ def analyze(options) dependencies[component.bomref] = top_deps end - [component, pods, dependencies] + manifest_path = lockfile.defined_in_file + if manifest_path.absolute? + # Use the folder that we are building in, then the path to the manifest file + manifest_path = Pathname.pwd.basename + manifest_path.relative_path_from(Pathname.pwd) + end + + [component, pods, manifest_path, dependencies] end - def build_and_write_bom(options, component, pods, dependencies) - builder = BOMBuilder.new(pods: pods, component: component, dependencies: dependencies) + def build_and_write_bom(options, component, pods, manifest_path, dependencies) + builder = BOMBuilder.new(pods: pods, manifest_path: manifest_path, + component: component, dependencies: dependencies) bom = builder.bom(version: options[:bom_version] || 1, trim_strings_length: options[:trim_strings_length] || 0) write_bom_to_file(bom: bom, options: options) diff --git a/lib/cyclonedx/cocoapods/version.rb b/lib/cyclonedx/cocoapods/version.rb index db2d56e..46dfd7c 100644 --- a/lib/cyclonedx/cocoapods/version.rb +++ b/lib/cyclonedx/cocoapods/version.rb @@ -21,6 +21,6 @@ module CycloneDX module CocoaPods - VERSION = '1.3.0' + VERSION = '1.4.0' end end diff --git a/spec/cyclonedx/cocoapods/bom_builder_spec.rb b/spec/cyclonedx/cocoapods/bom_builder_spec.rb index e7dd824..3340e56 100644 --- a/spec/cyclonedx/cocoapods/bom_builder_spec.rb +++ b/spec/cyclonedx/cocoapods/bom_builder_spec.rb @@ -38,13 +38,13 @@ let(:pod) { described_class.new(name: pod_name, version: pod_version, checksum: checksum) } let(:xml) do Nokogiri::XML(Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| - pod.add_to_bom(xml) + pod.add_to_bom(xml, 'unused.lock') end.to_xml) end let(:shortXML) do Nokogiri::XML(Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| - pod.add_to_bom(xml, 7) + pod.add_to_bom(xml, 'unused.lock', 7) end.to_xml) end @@ -330,6 +330,19 @@ Alamofire 5.6.2 pkg:cocoapods/Alamofire@5.6.2 + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewbacca @@ -337,6 +350,19 @@ FirebaseAnalytics 7.10.0 pkg:cocoapods/FirebaseAnalytics@7.10.0 + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewbacca @@ -344,6 +370,19 @@ MSAL 1.2.1 pkg:cocoapods/MSAL@1.2.1 + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewbacca @@ -351,6 +390,19 @@ MSAL/app-lib 1.2.1 pkg:cocoapods/MSAL@1.2.1#app-lib + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewbacca @@ -358,6 +410,19 @@ Realm 5.5.1 pkg:cocoapods/Realm@5.5.1 + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewbacca @@ -365,6 +430,19 @@ RxSwift 5.1.2 pkg:cocoapods/RxSwift@5.1.2 + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + XML @@ -379,6 +457,19 @@ Alamofire 5.6.2 pkg:co + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewba @@ -386,6 +477,19 @@ FirebaseAnalytics 7.10.0 pkg:co + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewba @@ -393,6 +497,19 @@ MSAL 1.2.1 pkg:co + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewba @@ -400,6 +517,19 @@ MSAL/app-lib 1.2.1 pkg:co + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewba @@ -407,6 +537,19 @@ Realm 5.5.1 pkg:co + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + Chewba @@ -414,6 +557,19 @@ RxSwift 5.1.2 pkg:co + + + purl + 0.6 + + + manifest-analysis + 0.6 + sample_manifest.lock + + + + XML @@ -552,7 +708,7 @@ end context 'without a component' do - let(:bom_builder) { described_class.new(pods: pods) } + let(:bom_builder) { described_class.new(pods: pods, manifest_path: 'sample_manifest.lock') } let(:dependencies_result) { '' } it_behaves_like 'bom_generator' @@ -562,7 +718,12 @@ let(:component) do CycloneDX::CocoaPods::Component.new(name: 'Application', version: '1.3.5', type: 'application') end - let(:bom_builder) { described_class.new(component: component, pods: pods, dependencies: dependencies) } + let(:bom_builder) do + described_class.new(component: component, + manifest_path: 'sample_manifest.lock', + pods: pods, + dependencies: dependencies) + end # Important: these expected dependencies are sorted alphabetically let(:dependencies_result) do <<~XML