├── .github ├── dependabot.yml └── workflows │ └── ruby.yml ├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── .simplecov ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── cucumber.yml ├── cyclonedx-ruby.gemspec ├── exe └── cyclonedx-ruby ├── features ├── defaults.feature ├── fixtures │ └── simple │ │ ├── Gemfile │ │ ├── Gemfile.lock │ │ ├── bom.json.expected │ │ └── bom.xml.expected ├── help.feature ├── json_format.feature ├── step_definitions │ ├── .gitkeep │ ├── json_bom_matching.rb │ └── xml_bom_matching.rb ├── support │ ├── env.rb │ └── simplecov_support.rb └── xml_format.feature ├── lib ├── bom_builder.rb ├── bom_component.rb ├── bom_helpers.rb └── licenses.json └── spec ├── bom_component_spec.rb ├── bom_helpers_spec.rb └── spec_helper.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'bundler' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | day: 'saturday' 8 | allow: 9 | - dependency-type: 'all' 10 | versioning-strategy: 'auto' 11 | labels: [ 'dependencies' ] 12 | commit-message: 13 | prefix: 'chore' ## prefix maximum string length of 15 14 | include: 'scope' 15 | open-pull-requests-limit: 999 16 | - package-ecosystem: 'github-actions' 17 | directory: '/' 18 | schedule: 19 | interval: 'weekly' 20 | day: 'saturday' 21 | labels: [ 'dependencies' ] 22 | commit-message: 23 | prefix: 'chore' ## prefix maximum string length of 15 24 | include: 'scope' 25 | open-pull-requests-limit: 999 26 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | name: Ruby ${{ matrix.ruby }} 14 | strategy: 15 | matrix: 16 | ruby: 17 | - '3.2.2' 18 | - '3.1.4' 19 | - '3.0.6' 20 | - '2.7.7' 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Set up Ruby ${{ matrix.ruby }} 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: ${{ matrix.ruby }} 28 | bundler-cache: true 29 | - name: Run the default task 30 | run: bundle exec rake 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /Gemfile.lock 3 | /tmp/aruba 4 | /coverage -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: enable 3 | TargetRubyVersion: 2.7 4 | 5 | inherit_from: .rubocop_todo.yml 6 | 7 | # The behavior of RuboCop can be controlled via the .rubocop.yml 8 | # configuration file. It makes it possible to enable/disable 9 | # certain cops (checks) and to alter their behavior if they accept 10 | # any parameters. The file can be placed either in your home 11 | # directory or in some project directory. 12 | # 13 | # RuboCop will start looking for the configuration file in the directory 14 | # where the inspected file is and continue its way up to the root directory. 15 | # 16 | # See https://docs.rubocop.org/rubocop/configuration 17 | 18 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2023-04-05 19:57:43 UTC using RuboCop version 1.49.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # This cop supports safe autocorrection (--autocorrect). 11 | # Configuration parameters: Severity, Include. 12 | # Include: **/*.gemspec 13 | Gemspec/DeprecatedAttributeAssignment: 14 | Exclude: 15 | - 'cyclonedx-ruby.gemspec' 16 | 17 | # Offense count: 4 18 | # This cop supports safe autocorrection (--autocorrect). 19 | # Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. 20 | # Include: **/*.gemspec 21 | Gemspec/OrderedDependencies: 22 | Exclude: 23 | - 'cyclonedx-ruby.gemspec' 24 | 25 | # Offense count: 1 26 | # This cop supports safe autocorrection (--autocorrect). 27 | # Configuration parameters: Severity, Include. 28 | # Include: **/*.gemspec 29 | Gemspec/RequireMFA: 30 | Exclude: 31 | - 'cyclonedx-ruby.gemspec' 32 | 33 | # Offense count: 1 34 | # This cop supports safe autocorrection (--autocorrect). 35 | Layout/EmptyLineAfterMagicComment: 36 | Exclude: 37 | - 'lib/bom_builder.rb' 38 | 39 | # Offense count: 1 40 | # This cop supports safe autocorrection (--autocorrect). 41 | # Configuration parameters: EnforcedStyle. 42 | # SupportedStyles: around, only_before 43 | Layout/EmptyLinesAroundAccessModifier: 44 | Exclude: 45 | - 'lib/bom_builder.rb' 46 | 47 | # Offense count: 1 48 | # This cop supports safe autocorrection (--autocorrect). 49 | Layout/EmptyLinesAroundMethodBody: 50 | Exclude: 51 | - 'lib/bom_component.rb' 52 | 53 | # Offense count: 1 54 | # This cop supports safe autocorrection (--autocorrect). 55 | # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. 56 | Layout/ExtraSpacing: 57 | Exclude: 58 | - 'cyclonedx-ruby.gemspec' 59 | 60 | # Offense count: 1 61 | # This cop supports safe autocorrection (--autocorrect). 62 | # Configuration parameters: EnforcedStyle, IndentationWidth. 63 | # SupportedStyles: special_inside_parentheses, consistent, align_brackets 64 | Layout/FirstArrayElementIndentation: 65 | Exclude: 66 | - 'lib/bom_component.rb' 67 | 68 | # Offense count: 1 69 | # This cop supports safe autocorrection (--autocorrect). 70 | Layout/LeadingEmptyLines: 71 | Exclude: 72 | - 'lib/bom_component.rb' 73 | 74 | # Offense count: 2 75 | # This cop supports safe autocorrection (--autocorrect). 76 | # Configuration parameters: EnforcedStyle. 77 | # SupportedStyles: final_newline, final_blank_line 78 | Layout/TrailingEmptyLines: 79 | Exclude: 80 | - 'Rakefile' 81 | - 'lib/bom_component.rb' 82 | 83 | # Offense count: 3 84 | # This cop supports safe autocorrection (--autocorrect). 85 | # Configuration parameters: AllowInHeredoc. 86 | Layout/TrailingWhitespace: 87 | Exclude: 88 | - 'Rakefile' 89 | - 'spec/bom_component_spec.rb' 90 | 91 | # Offense count: 2 92 | Lint/IneffectiveAccessModifier: 93 | Exclude: 94 | - 'lib/bom_builder.rb' 95 | 96 | # Offense count: 1 97 | # This cop supports safe autocorrection (--autocorrect). 98 | Lint/ScriptPermission: 99 | Exclude: 100 | - 'Rakefile' 101 | 102 | # Offense count: 1 103 | Lint/ShadowingOuterLocalVariable: 104 | Exclude: 105 | - 'lib/bom_builder.rb' 106 | 107 | # Offense count: 19 108 | # This cop supports safe autocorrection (--autocorrect). 109 | # Configuration parameters: EnforcedStyle. 110 | # SupportedStyles: strict, consistent 111 | Lint/SymbolConversion: 112 | Exclude: 113 | - 'lib/bom_component.rb' 114 | - 'lib/bom_helpers.rb' 115 | 116 | # Offense count: 1 117 | # This cop supports safe autocorrection (--autocorrect). 118 | # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. 119 | Lint/UnusedMethodArgument: 120 | Exclude: 121 | - 'lib/bom_builder.rb' 122 | 123 | # Offense count: 1 124 | # This cop supports safe autocorrection (--autocorrect). 125 | # Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. 126 | Lint/UselessAccessModifier: 127 | Exclude: 128 | - 'lib/bom_builder.rb' 129 | 130 | # Offense count: 4 131 | # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. 132 | Metrics/AbcSize: 133 | Max: 67 134 | 135 | # Offense count: 4 136 | # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. 137 | # AllowedMethods: refine 138 | Metrics/BlockLength: 139 | Max: 38 140 | 141 | # Offense count: 1 142 | # Configuration parameters: CountComments, CountAsOne. 143 | Metrics/ClassLength: 144 | Max: 128 145 | 146 | # Offense count: 1 147 | # Configuration parameters: AllowedMethods, AllowedPatterns. 148 | Metrics/CyclomaticComplexity: 149 | Max: 9 150 | 151 | # Offense count: 6 152 | # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. 153 | Metrics/MethodLength: 154 | Max: 68 155 | 156 | # Offense count: 1 157 | # Configuration parameters: AllowedMethods, AllowedPatterns. 158 | Metrics/PerceivedComplexity: 159 | Max: 12 160 | 161 | # Offense count: 1 162 | # This cop supports safe autocorrection (--autocorrect). 163 | # Configuration parameters: PreferredName. 164 | Naming/RescuedExceptionsVariableName: 165 | Exclude: 166 | - 'Rakefile' 167 | 168 | # Offense count: 3 169 | # Configuration parameters: AllowedConstants. 170 | Style/Documentation: 171 | Exclude: 172 | - 'spec/**/*' 173 | - 'test/**/*' 174 | - 'Rakefile' 175 | - 'lib/bom_builder.rb' 176 | - 'lib/bom_component.rb' 177 | 178 | # Offense count: 1 179 | # This cop supports safe autocorrection (--autocorrect). 180 | # Configuration parameters: AllowedVars. 181 | Style/FetchEnvVar: 182 | Exclude: 183 | - 'lib/bom_helpers.rb' 184 | 185 | # Offense count: 1 186 | # This cop supports safe autocorrection (--autocorrect). 187 | Style/FileWrite: 188 | Exclude: 189 | - 'lib/bom_builder.rb' 190 | 191 | # Offense count: 12 192 | # This cop supports unsafe autocorrection (--autocorrect-all). 193 | # Configuration parameters: EnforcedStyle. 194 | # SupportedStyles: always, always_true, never 195 | Style/FrozenStringLiteralComment: 196 | Exclude: 197 | - '.simplecov' 198 | - 'Gemfile' 199 | - 'Rakefile' 200 | - 'features/fixtures/simple/Gemfile' 201 | - 'features/step_definitions/json_bom_matching.rb' 202 | - 'features/step_definitions/xml_bom_matching.rb' 203 | - 'features/support/env.rb' 204 | - 'features/support/simplecov_support.rb' 205 | - 'lib/bom_component.rb' 206 | - 'spec/bom_component_spec.rb' 207 | - 'spec/bom_helpers_spec.rb' 208 | - 'spec/spec_helper.rb' 209 | 210 | # Offense count: 1 211 | # This cop supports safe autocorrection (--autocorrect). 212 | # Configuration parameters: AllowedMethods, AllowedPatterns. 213 | Style/MethodCallWithoutArgsParentheses: 214 | Exclude: 215 | - 'lib/bom_helpers.rb' 216 | 217 | # Offense count: 1 218 | # This cop supports unsafe autocorrection (--autocorrect-all). 219 | # Configuration parameters: EnforcedStyle. 220 | # SupportedStyles: literals, strict 221 | Style/MutableConstant: 222 | Exclude: 223 | - 'lib/bom_builder.rb' 224 | 225 | # Offense count: 2 226 | Style/OpenStructUse: 227 | Exclude: 228 | - 'lib/bom_builder.rb' 229 | - 'spec/bom_component_spec.rb' 230 | 231 | # Offense count: 1 232 | # This cop supports safe autocorrection (--autocorrect). 233 | # Configuration parameters: PreferredDelimiters. 234 | Style/PercentLiteralDelimiters: 235 | Exclude: 236 | - 'Rakefile' 237 | 238 | # Offense count: 19 239 | # This cop supports safe autocorrection (--autocorrect). 240 | # Configuration parameters: . 241 | # SupportedStyles: same_as_string_literals, single_quotes, double_quotes 242 | Style/QuotedSymbols: 243 | EnforcedStyle: double_quotes 244 | 245 | # Offense count: 1 246 | # This cop supports safe autocorrection (--autocorrect). 247 | Style/RedundantBegin: 248 | Exclude: 249 | - 'Rakefile' 250 | 251 | # Offense count: 6 252 | # This cop supports safe autocorrection (--autocorrect). 253 | Style/RedundantRegexpEscape: 254 | Exclude: 255 | - 'features/step_definitions/json_bom_matching.rb' 256 | - 'features/step_definitions/xml_bom_matching.rb' 257 | 258 | # Offense count: 20 259 | # This cop supports safe autocorrection (--autocorrect). 260 | # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. 261 | # SupportedStyles: single_quotes, double_quotes 262 | Style/StringLiterals: 263 | Exclude: 264 | - '.simplecov' 265 | - 'Rakefile' 266 | - 'cyclonedx-ruby.gemspec' 267 | - 'lib/bom_component.rb' 268 | - 'lib/bom_helpers.rb' 269 | - 'spec/bom_component_spec.rb' 270 | - 'spec/bom_helpers_spec.rb' 271 | 272 | # Offense count: 1 273 | # This cop supports safe autocorrection (--autocorrect). 274 | # Configuration parameters: MinSize. 275 | # SupportedStyles: percent, brackets 276 | Style/SymbolArray: 277 | EnforcedStyle: brackets 278 | 279 | # Offense count: 2 280 | # This cop supports safe autocorrection (--autocorrect). 281 | Style/SymbolLiteral: 282 | Exclude: 283 | - 'lib/bom_component.rb' 284 | 285 | # Offense count: 5 286 | # This cop supports safe autocorrection (--autocorrect). 287 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. 288 | # URISchemes: http, https 289 | Layout/LineLength: 290 | Max: 237 291 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/cucumber/aruba/blob/3b1a6cea6e3ba55370c3396eef0a955aeb40f287/.simplecov 2 | # Licensed under MIT - https://github.com/cucumber/aruba/blob/3b1a6cea6e3ba55370c3396eef0a955aeb40f287/LICENSE 3 | 4 | SimpleCov.configure do 5 | enable_for_subprocesses true 6 | 7 | # Activate branch coverage 8 | enable_coverage :branch 9 | 10 | # ignore this file 11 | add_filter ".simplecov" 12 | add_filter "features" 13 | 14 | # Rake tasks aren't tested with rspec 15 | add_filter "Rakefile" 16 | add_filter "lib/tasks" 17 | 18 | # 19 | # Changed Files in Git Group 20 | # @see http://fredwu.me/post/35625566267/simplecov-test-coverage-for-changed-files-only 21 | untracked = `git ls-files --exclude-standard --others` 22 | unstaged = `git diff --name-only` 23 | staged = `git diff --name-only --cached` 24 | all = untracked + unstaged + staged 25 | changed_filenames = all.split("\n") 26 | 27 | add_group "Changed" do |source_file| 28 | changed_filenames.select do |changed_filename| 29 | source_file.filename.end_with?(changed_filename) 30 | end 31 | end 32 | 33 | add_group "Libraries", "lib" 34 | 35 | # Specs are reported on to ensure that all examples are being run and all 36 | # lets, befores, afters, etc are being used. 37 | add_group "Specs", "spec/" 38 | end 39 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in cyclonedx-ruby.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright OWASP Foundation 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/CycloneDX/cyclonedx-ruby-gem/workflows/Ruby%20CI/badge.svg)](https://github.com/CycloneDX/cyclonedx-ruby-gem/actions?workflow=Ruby+CI) 2 | [![Gem Version](https://badge.fury.io/rb/cyclonedx-ruby.svg)](https://badge.fury.io/rb/cyclonedx-ruby) 3 | [![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)][License] 4 | [![Website](https://img.shields.io/badge/https://-cyclonedx.org-blue.svg)](https://cyclonedx.org/) 5 | [![Slack Invite](https://img.shields.io/badge/Slack-Join-blue?logo=slack&labelColor=393939)](https://cyclonedx.org/slack/invite) 6 | [![Group Discussion](https://img.shields.io/badge/discussion-groups.io-blue.svg)](https://groups.io/g/CycloneDX) 7 | [![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=social&label=Follow)](https://twitter.com/CycloneDX_Spec) 8 | 9 | 10 | # CycloneDX Ruby Gem 11 | 12 | The CycloneDX Ruby Gem creates a valid CycloneDX Software Bill of Materials (SBOM) from all project dependencies. CycloneDX is a lightweight SBOM specification that is easily created, human readable, and simple to parse. 13 | 14 | #### Installing from RubyGems 15 | 16 | ```bash 17 | gem install cyclonedx-ruby 18 | ``` 19 | 20 | #### Building and Installing From Source 21 | 22 | ```bash 23 | gem build cyclonedx-ruby.gemspec 24 | gem install cyclonedx-ruby-x.x.x.gem 25 | ``` 26 | 27 | #### Usage 28 | cyclonedx-ruby [options] 29 | 30 | `-v, --[no-]verbose` Run verbosely 31 | `-p, --path path` Path to Ruby project directory 32 | `-f, --format` Bom output format 33 | `-h, --help` Show help message 34 | 35 | **Output:** bom.xml or bom.json file in project directory 36 | 37 | #### Example 38 | ```bash 39 | cyclonedx-ruby -p /path/to/ruby/project 40 | ``` 41 | 42 | 43 | Copyright & License 44 | ------------------- 45 | 46 | CycloneDX Ruby Gem is Copyright (c) OWASP Foundation. All Rights Reserved. 47 | 48 | Permission to modify and redistribute is granted under the terms of the Apache 2.0 license. See the [LICENSE] file for the full license. 49 | 50 | [License]: https://github.com/CycloneDX/cyclonedx-ruby-gem/blob/master/LICENSE 51 | 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | $LOAD_PATH << File.expand_path(__dir__) 3 | 4 | require "aruba/platform" 5 | 6 | require "bundler" 7 | Bundler.setup 8 | 9 | require 'bundler/gem_tasks' 10 | require "cucumber/rake/task" 11 | require "rspec/core/rake_task" 12 | require 'rake/clean' 13 | 14 | # Work around a bug in `rake/clean` from `rake` versions older than 13. It's 15 | # failing when it calls `FileUtils::rm_r` because that method needs to receive 16 | # the `opts` parameter as parameters instead of as a `Hash`. 17 | module Rake 18 | module Cleaner 19 | module_function 20 | 21 | def cleanup(file_name, **opts) 22 | begin 23 | opts = { verbose: Rake.application.options.trace }.merge(opts) 24 | rm_r file_name, **opts 25 | rescue StandardError => ex 26 | puts "Failed to remove #{file_name}: #{ex}" unless file_already_gone?(file_name) 27 | end 28 | end 29 | end 30 | end 31 | 32 | # Remove the `coverage` directory when the `:clobber` task is run. 33 | CLOBBER.include('coverage') 34 | 35 | Cucumber::Rake::Task.new do |t| 36 | t.cucumber_opts = %w(--format progress) 37 | end 38 | 39 | RSpec::Core::RakeTask.new('spec') 40 | 41 | # Run the `clobber` task when running the entire test suite, because the 42 | # coverage information reported by `simplecov` can be skewed when a `coverage` 43 | # directory is already present. 44 | desc "Run the whole test suite." 45 | task test: [:clobber, :spec, :cucumber] 46 | 47 | task default: :test -------------------------------------------------------------------------------- /cucumber.yml: -------------------------------------------------------------------------------- 1 | default: --publish-quiet 2 | -------------------------------------------------------------------------------- /cyclonedx-ruby.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = 'cyclonedx-ruby' 5 | spec.version = '1.2.0' 6 | spec.date = '2023-07-14' 7 | spec.summary = 'CycloneDX software bill-of-material (SBoM) generation utility' 8 | spec.description = 'CycloneDX is a lightweight software bill-of-material (SBOM) specification designed for use in application security contexts and supply chain component analysis. This Gem generates CycloneDX BOMs from Ruby projects.' 9 | spec.authors = ['Joseph Kobti', 'Steve Springett'] 10 | spec.email = 'josephkobti@outlook.com' 11 | spec.homepage = 'https://github.com/CycloneDX/cyclonedx-ruby-gem' 12 | spec.license = 'Apache-2.0' 13 | 14 | spec.required_ruby_version = '>= 2.7.0' 15 | 16 | spec.files = Dir.chdir(__dir__) do 17 | `git ls-files -z`.split("\x0").reject do |f| 18 | (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor]) 19 | end 20 | end 21 | spec.bindir = 'exe' 22 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 23 | spec.require_paths = ['lib'] 24 | 25 | spec.add_dependency('json', '~> 2.6') 26 | spec.add_dependency('nokogiri', '~> 1.15') 27 | spec.add_dependency('ostruct', '~> 0.5.5') 28 | spec.add_dependency('rest-client', '~> 2.0') 29 | spec.add_dependency('activesupport', '~> 7.0') 30 | spec.add_development_dependency 'rake', '~> 13' 31 | spec.add_development_dependency 'rspec', '~> 3.12' 32 | spec.add_development_dependency 'cucumber', '~> 8.0' 33 | spec.add_development_dependency 'aruba', '~> 2.1' 34 | spec.add_development_dependency 'simplecov', '~> 0.22.0' 35 | spec.add_development_dependency 'rubocop', '~> 1.54' 36 | end 37 | -------------------------------------------------------------------------------- /exe/cyclonedx-ruby: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bom_builder' 5 | Bombuilder.build(ARGV[0]) 6 | -------------------------------------------------------------------------------- /features/defaults.feature: -------------------------------------------------------------------------------- 1 | Feature: Default parameter values 2 | 3 | Many of the options for the `cyclonedx-ruby` command are optional. 4 | 5 | Scenario: Running against simple fixture 6 | Given I use a fixture named "simple" 7 | And I run `cyclonedx-ruby --path .` 8 | Then the output should contain: 9 | """ 10 | 5 gems were written to BOM located at ./bom.xml 11 | """ 12 | And a file named "bom.xml" should exist 13 | And the generated XML BOM file "bom.xml" matches "bom.xml.expected" 14 | -------------------------------------------------------------------------------- /features/fixtures/simple/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'activesupport' 4 | -------------------------------------------------------------------------------- /features/fixtures/simple/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (7.0.4.3) 5 | concurrent-ruby (~> 1.0, >= 1.0.2) 6 | i18n (>= 1.6, < 2) 7 | minitest (>= 5.1) 8 | tzinfo (~> 2.0) 9 | concurrent-ruby (1.2.2) 10 | i18n (1.12.0) 11 | concurrent-ruby (~> 1.0) 12 | minitest (5.18.0) 13 | tzinfo (2.0.6) 14 | concurrent-ruby (~> 1.0) 15 | 16 | PLATFORMS 17 | arm64-darwin-22 18 | 19 | DEPENDENCIES 20 | activesupport 21 | 22 | BUNDLED WITH 23 | 2.4.10 24 | -------------------------------------------------------------------------------- /features/fixtures/simple/bom.json.expected: -------------------------------------------------------------------------------- 1 | { 2 | "bomFormat": "CycloneDX", 3 | "specVersion": "1.1", 4 | "serialNumber": "urn:uuid:d498cdc2-5494-4031-b37d-ff3d10d336bf", 5 | "version": 1, 6 | "components": [ 7 | { 8 | "type": "library", 9 | "name": "activesupport", 10 | "version": "7.0.4.3", 11 | "description": "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.", 12 | "purl": "pkg:gem/activesupport@7.0.4.3", 13 | "hashes": [ 14 | { 15 | "alg": "SHA-256", 16 | "content": "571ed0fac8510f1fc8a1d66aa070d07ea269913bf9ef50960a8044536358a096" 17 | } 18 | ], 19 | "licenses": [ 20 | { 21 | "license": { 22 | "id": "MIT" 23 | } 24 | } 25 | ] 26 | }, 27 | { 28 | "type": "library", 29 | "name": "concurrent-ruby", 30 | "version": "1.2.2", 31 | "description": "Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.", 32 | "purl": "pkg:gem/concurrent-ruby@1.2.2", 33 | "hashes": [ 34 | { 35 | "alg": "SHA-256", 36 | "content": "3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f" 37 | } 38 | ], 39 | "licenses": [ 40 | { 41 | "license": { 42 | "id": "MIT" 43 | } 44 | } 45 | ] 46 | }, 47 | { 48 | "type": "library", 49 | "name": "i18n", 50 | "version": "1.12.0", 51 | "description": "New wave Internationalization support for Ruby", 52 | "purl": "pkg:gem/i18n@1.12.0", 53 | "hashes": [ 54 | { 55 | "alg": "SHA-256", 56 | "content": "91e3cc1b97616d308707eedee413d82ee021d751c918661fb82152793e64aced" 57 | } 58 | ], 59 | "licenses": [ 60 | { 61 | "license": { 62 | "id": "MIT" 63 | } 64 | } 65 | ] 66 | }, 67 | { 68 | "type": "library", 69 | "name": "minitest", 70 | "version": "5.18.0", 71 | "description": "minitest provides a complete suite of testing facilities supporting TDD, BDD, mocking, and benchmarking", 72 | "purl": "pkg:gem/minitest@5.18.0", 73 | "hashes": [ 74 | { 75 | "alg": "SHA-256", 76 | "content": "06f43aa0692ce3acf19cb5bc539ad2c6095ca3d2c7e5fbafc58a7d847e898745" 77 | } 78 | ], 79 | "licenses": [ 80 | { 81 | "license": { 82 | "id": "MIT" 83 | } 84 | } 85 | ] 86 | }, 87 | { 88 | "type": "library", 89 | "name": "tzinfo", 90 | "version": "2.0.6", 91 | "description": "Time Zone Library", 92 | "purl": "pkg:gem/tzinfo@2.0.6", 93 | "hashes": [ 94 | { 95 | "alg": "SHA-256", 96 | "content": "8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b" 97 | } 98 | ], 99 | "licenses": [ 100 | { 101 | "license": { 102 | "id": "MIT" 103 | } 104 | } 105 | ] 106 | } 107 | ] 108 | } -------------------------------------------------------------------------------- /features/fixtures/simple/bom.xml.expected: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | activesupport 6 | 7.0.4.3 7 | A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. 8 | 9 | 571ed0fac8510f1fc8a1d66aa070d07ea269913bf9ef50960a8044536358a096 10 | 11 | 12 | 13 | MIT 14 | 15 | 16 | pkg:gem/activesupport@7.0.4.3 17 | 18 | 19 | concurrent-ruby 20 | 1.2.2 21 | Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns. 22 | 23 | 3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f 24 | 25 | 26 | 27 | MIT 28 | 29 | 30 | pkg:gem/concurrent-ruby@1.2.2 31 | 32 | 33 | i18n 34 | 1.12.0 35 | New wave Internationalization support for Ruby 36 | 37 | 91e3cc1b97616d308707eedee413d82ee021d751c918661fb82152793e64aced 38 | 39 | 40 | 41 | MIT 42 | 43 | 44 | pkg:gem/i18n@1.12.0 45 | 46 | 47 | minitest 48 | 5.18.0 49 | minitest provides a complete suite of testing facilities supporting TDD, BDD, mocking, and benchmarking 50 | 51 | 06f43aa0692ce3acf19cb5bc539ad2c6095ca3d2c7e5fbafc58a7d847e898745 52 | 53 | 54 | 55 | MIT 56 | 57 | 58 | pkg:gem/minitest@5.18.0 59 | 60 | 61 | tzinfo 62 | 2.0.6 63 | Time Zone Library 64 | 65 | 8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b 66 | 67 | 68 | 69 | MIT 70 | 71 | 72 | pkg:gem/tzinfo@2.0.6 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /features/help.feature: -------------------------------------------------------------------------------- 1 | Feature: Command Help 2 | 3 | The `cyclonedx-ruby` needs to provide help output so that people know the 4 | parameters that they can specify. 5 | 6 | Scenario: Generate help on demand 7 | Given I run `cyclonedx-ruby --help` 8 | Then the output should contain: 9 | """ 10 | Usage: cyclonedx-ruby [options] 11 | -v, --[no-]verbose Run verbosely 12 | -p, --path path (Required) Path to Ruby project directory 13 | -o, --output bom_file_path (Optional) Path to output the bom.xml file to 14 | -f, --format bom_output_format (Optional) Output format for bom. Currently support xml (default) and json. 15 | -h, --help Show help message 16 | """ 17 | -------------------------------------------------------------------------------- /features/json_format.feature: -------------------------------------------------------------------------------- 1 | Feature: Creating BOM using Json format 2 | 3 | Scenario: Using default output path 4 | Given I use a fixture named "simple" 5 | And I run `cyclonedx-ruby --path . --format json` 6 | Then the output should contain: 7 | """ 8 | 5 gems were written to BOM located at ./bom.json 9 | """ 10 | And a file named "bom.json" should exist 11 | And the generated Json BOM file "bom.json" matches "bom.json.expected" 12 | 13 | Scenario: Specifying the output path 14 | Given I use a fixture named "simple" 15 | And I run `cyclonedx-ruby --path . --format json --output bom/simple.bom.json` 16 | Then the output should contain: 17 | """ 18 | 5 gems were written to BOM located at bom/simple.bom.json 19 | """ 20 | And a file named "bom/simple.bom.json" should exist 21 | And the generated Json BOM file "bom/simple.bom.json" matches "bom.json.expected" 22 | 23 | Scenario: Verbose output 24 | Given I use a fixture named "simple" 25 | And I run `cyclonedx-ruby --path . --format json --verbose` 26 | Then the output should match: 27 | """ 28 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Changing directory to Ruby project directory located at \. 29 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : BOM will be written to \./bom\.json 30 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Parsing specs from \./Gemfile\.lock\.\.\. 31 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Specs successfully parsed! 32 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : activesupport:7\.0\.4\.3 gem added 33 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : concurrent-ruby:1\.2\.2 gem added 34 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : i18n:1\.12\.0 gem added 35 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : minitest:5\.18\.0 gem added 36 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : tzinfo:2\.0\.6 gem added 37 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Changing directory to the original working directory located at .*/tmp/aruba/simple 38 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Writing BOM to \./bom\.json\.\.\. 39 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : 5 gems were written to BOM located at \./bom\.json 40 | """ 41 | And a file named "bom.json" should exist 42 | And the generated Json BOM file "bom.json" matches "bom.json.expected" 43 | 44 | -------------------------------------------------------------------------------- /features/step_definitions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CycloneDX/cyclonedx-ruby-gem/513d5d6c55fd90b8b75237c7908576ff5f8e50a7/features/step_definitions/.gitkeep -------------------------------------------------------------------------------- /features/step_definitions/json_bom_matching.rb: -------------------------------------------------------------------------------- 1 | Then('the generated Json BOM file {string} matches {string}') do |generated_file, expected_file| 2 | generated_file_contents = File.read(expand_path(generated_file)) 3 | expected_file_contents = File.read(expand_path(expected_file)) 4 | 5 | serial_number_matcher = /\"serialNumber\": \"urn:uuid:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\"/ 6 | normalized_serial_number = '"serialNumber": "urn:uuid:00000000-0000-0000-0000-000000000000"' 7 | normalized_generated_file_contents = generated_file_contents.gsub(serial_number_matcher, normalized_serial_number) 8 | normalized_expected_file_contents = expected_file_contents.gsub(serial_number_matcher, normalized_serial_number) 9 | 10 | expect(normalized_expected_file_contents).to eq(normalized_generated_file_contents) 11 | end 12 | -------------------------------------------------------------------------------- /features/step_definitions/xml_bom_matching.rb: -------------------------------------------------------------------------------- 1 | Then('the generated XML BOM file {string} matches {string}') do |generated_file, expected_file| 2 | generated_file_contents = File.read(expand_path(generated_file)) 3 | expected_file_contents = File.read(expand_path(expected_file)) 4 | 5 | serial_number_matcher = /serialNumber=\"urn:uuid:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\"/ 6 | normalized_serial_number = 'serialNumber="urn:uuid:00000000-0000-0000-0000-000000000000"' 7 | normalized_generated_file_contents = generated_file_contents.gsub(serial_number_matcher, normalized_serial_number) 8 | normalized_expected_file_contents = expected_file_contents.gsub(serial_number_matcher, normalized_serial_number) 9 | 10 | expect(normalized_expected_file_contents).to eq(normalized_generated_file_contents) 11 | end 12 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/cucumber/aruba/blob/3b1a6cea6e3ba55370c3396eef0a955aeb40f287/features/support/env.rb 2 | # Licensed under MIT - https://github.com/cucumber/aruba/blob/3b1a6cea6e3ba55370c3396eef0a955aeb40f287/LICENSE 3 | 4 | $LOAD_PATH.unshift File.expand_path('../../lib', __dir__) 5 | 6 | # Has to be the first file required so that all other files show coverage information 7 | require_relative 'simplecov_support' unless RUBY_PLATFORM.include?('java') 8 | 9 | require 'fileutils' 10 | require 'pathname' 11 | 12 | require 'aruba/cucumber' 13 | require 'rspec/expectations' 14 | 15 | Around do |test_case, block| 16 | command_name = "#{test_case.location.file}:#{test_case.location.line} # #{test_case.name}" 17 | 18 | # Used in simplecov_setup so that each scenario has a different name and 19 | # their coverage results are merged instead of overwriting each other as 20 | # 'Cucumber Features' 21 | set_environment_variable 'SIMPLECOV_COMMAND_NAME', command_name.to_s 22 | 23 | simplecov_setup_pathname = 24 | Pathname.new(__FILE__).expand_path.parent.to_s 25 | 26 | # set environment variable so child processes will merge their coverage data 27 | # with parent process's coverage data. 28 | prepend_environment_variable 'RUBYOPT', "-I#{simplecov_setup_pathname} -rsimplecov_support " 29 | 30 | with_environment do 31 | block.call 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /features/support/simplecov_support.rb: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/cucumber/aruba/blob/3b1a6cea6e3ba55370c3396eef0a955aeb40f287/features/support/simplecov_setup.rb 2 | # Licensed under MIT - https://github.com/cucumber/aruba/blob/3b1a6cea6e3ba55370c3396eef0a955aeb40f287/LICENSE 3 | 4 | # @note this file is loaded in env.rb to setup simplecov using RUBYOPTs for 5 | # child processes and @in-process 6 | unless RUBY_PLATFORM.include?('java') 7 | require 'simplecov' 8 | root = File.expand_path('../..', __dir__) 9 | command_name = ENV['SIMPLECOV_COMMAND_NAME'] || 'Cucumber Features' 10 | SimpleCov.command_name(command_name) 11 | SimpleCov.root(root) 12 | 13 | # Run simplecov by default 14 | SimpleCov.start unless ENV.key? 'ARUBA_NO_COVERAGE' 15 | end 16 | -------------------------------------------------------------------------------- /features/xml_format.feature: -------------------------------------------------------------------------------- 1 | Feature: Creating BOM using XML format 2 | 3 | Scenario: Using default output path 4 | Given I use a fixture named "simple" 5 | And I run `cyclonedx-ruby --path . --format xml` 6 | Then the output should contain: 7 | """ 8 | 5 gems were written to BOM located at ./bom.xml 9 | """ 10 | And a file named "bom.xml" should exist 11 | And the generated XML BOM file "bom.xml" matches "bom.xml.expected" 12 | 13 | Scenario: Specifying the output path 14 | Given I use a fixture named "simple" 15 | And I run `cyclonedx-ruby --path . --format xml --output bom/simple.bom.xml` 16 | Then the output should contain: 17 | """ 18 | 5 gems were written to BOM located at bom/simple.bom.xml 19 | """ 20 | And a file named "bom/simple.bom.xml" should exist 21 | And the generated XML BOM file "bom/simple.bom.xml" matches "bom.xml.expected" 22 | 23 | Scenario: Verbose output 24 | Given I use a fixture named "simple" 25 | And I run `cyclonedx-ruby --path . --format xml --verbose` 26 | Then the output should match: 27 | """ 28 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Changing directory to Ruby project directory located at \. 29 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : BOM will be written to \./bom\.xml 30 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Parsing specs from \./Gemfile\.lock\.\.\. 31 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Specs successfully parsed! 32 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : activesupport:7\.0\.4\.3 gem added 33 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : concurrent-ruby:1\.2\.2 gem added 34 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : i18n:1\.12\.0 gem added 35 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : minitest:5\.18\.0 gem added 36 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : tzinfo:2\.0\.6 gem added 37 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Changing directory to the original working directory located at .*/tmp/aruba/simple 38 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : Writing BOM to \./bom\.xml\.\.\. 39 | I, \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6} #\d+\] INFO -- : 5 gems were written to BOM located at \./bom\.xml 40 | """ 41 | And a file named "bom.xml" should exist 42 | And the generated XML BOM file "bom.xml" matches "bom.xml.expected" 43 | -------------------------------------------------------------------------------- /lib/bom_builder.rb: -------------------------------------------------------------------------------- 1 | # This file is part of CycloneDX Ruby Gem. 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # 20 | # SPDX-License-Identifier: Apache-2.0 21 | # Copyright (c) OWASP Foundation. All Rights Reserved. 22 | # 23 | # frozen_string_literal: true 24 | require 'bundler' 25 | require 'fileutils' 26 | require 'json' 27 | require 'logger' 28 | require 'nokogiri' 29 | require 'optparse' 30 | require 'ostruct' 31 | require 'rest_client' 32 | require 'securerandom' 33 | require_relative 'bom_helpers' 34 | require 'active_support/core_ext/hash' 35 | 36 | class Bombuilder 37 | SUPPORTED_BOM_FORMATS = %w[xml json] 38 | 39 | def self.build(path) 40 | original_working_directory = Dir.pwd 41 | setup(path) 42 | specs_list 43 | bom = build_bom(@gems, @bom_output_format) 44 | 45 | begin 46 | @logger.info("Changing directory to the original working directory located at #{original_working_directory}") 47 | Dir.chdir original_working_directory 48 | rescue StandardError => e 49 | @logger.error("Unable to change directory the original working directory located at #{original_working_directory}. #{e.message}: #{e.backtrace.join('\n')}") 50 | abort 51 | end 52 | 53 | bom_directory = File.dirname(@bom_file_path) 54 | begin 55 | FileUtils.mkdir_p(bom_directory) unless File.directory?(bom_directory) 56 | rescue StandardError => e 57 | @logger.error("Unable to create the directory to hold the BOM output at #{@bom_directory}. #{e.message}: #{e.backtrace.join('\n')}") 58 | abort 59 | end 60 | 61 | begin 62 | @logger.info("Writing BOM to #{@bom_file_path}...") 63 | File.open(@bom_file_path, 'w') { |file| file.write(bom) } 64 | 65 | if @options[:verbose] 66 | @logger.info("#{@gems.size} gems were written to BOM located at #{@bom_file_path}") 67 | else 68 | puts "#{@gems.size} gems were written to BOM located at #{@bom_file_path}" 69 | end 70 | rescue StandardError => e 71 | @logger.error("Unable to write BOM to #{@bom_file_path}. #{e.message}: #{e.backtrace.join('\n')}") 72 | abort 73 | end 74 | end 75 | private 76 | def self.setup(path) 77 | @options = {} 78 | OptionParser.new do |opts| 79 | opts.banner = 'Usage: cyclonedx-ruby [options]' 80 | 81 | opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v| 82 | @options[:verbose] = v 83 | end 84 | opts.on('-p', '--path path', '(Required) Path to Ruby project directory') do |path| 85 | @options[:path] = path 86 | end 87 | opts.on('-o', '--output bom_file_path', '(Optional) Path to output the bom.xml file to') do |bom_file_path| 88 | @options[:bom_file_path] = bom_file_path 89 | end 90 | opts.on('-f', '--format bom_output_format', '(Optional) Output format for bom. Currently support xml (default) and json.') do |bom_output_format| 91 | @options[:bom_output_format] = bom_output_format 92 | end 93 | opts.on_tail('-h', '--help', 'Show help message') do 94 | puts opts 95 | exit 96 | end 97 | end.parse! 98 | 99 | @logger = Logger.new($stdout) 100 | @logger.level = if @options[:verbose] 101 | Logger::INFO 102 | else 103 | Logger::ERROR 104 | end 105 | 106 | @gems = [] 107 | licenses_file = File.read "#{__dir__}/licenses.json" 108 | @licenses_list = JSON.parse(licenses_file) 109 | 110 | if @options[:path].nil? 111 | @logger.error('missing path to project directory') 112 | abort 113 | end 114 | 115 | unless File.directory?(@options[:path]) 116 | @logger.error("path provided is not a valid directory. path provided was: #{@options[:path]}") 117 | abort 118 | end 119 | 120 | begin 121 | @logger.info("Changing directory to Ruby project directory located at #{@options[:path]}") 122 | Dir.chdir @options[:path] 123 | rescue StandardError => e 124 | @logger.error("Unable to change directory to Ruby project directory located at #{@options[:path]}. #{e.message}: #{e.backtrace.join('\n')}") 125 | abort 126 | end 127 | 128 | if @options[:bom_output_format].nil? 129 | @bom_output_format = 'xml' 130 | elsif SUPPORTED_BOM_FORMATS.include?(@options[:bom_output_format]) 131 | @bom_output_format = @options[:bom_output_format] 132 | else 133 | @logger.error("Unrecognized cyclonedx bom output format provided. Please choose one of #{SUPPORTED_BOM_FORMATS}") 134 | abort 135 | end 136 | 137 | @bom_file_path = if @options[:bom_file_path].nil? 138 | "./bom.#{@bom_output_format}" 139 | else 140 | @options[:bom_file_path] 141 | end 142 | 143 | @logger.info("BOM will be written to #{@bom_file_path}") 144 | 145 | begin 146 | gemfile_path = "#{@options[:path]}/Gemfile.lock" 147 | @logger.info("Parsing specs from #{gemfile_path}...") 148 | gemfile_contents = File.read(gemfile_path) 149 | @specs = Bundler::LockfileParser.new(gemfile_contents).specs 150 | @logger.info('Specs successfully parsed!') 151 | rescue StandardError => e 152 | @logger.error("Unable to parse specs from #{gemfile_path}. #{e.message}: #{e.backtrace.join('\n')}") 153 | abort 154 | end 155 | end 156 | 157 | def self.specs_list 158 | count = 0 159 | @specs.each do |dependency| 160 | object = OpenStruct.new 161 | object.name = dependency.name 162 | object.version = dependency.version 163 | object.purl = purl(object.name, object.version) 164 | gem = get_gem(object.name, object.version) 165 | next if gem.nil? 166 | 167 | if gem['licenses']&.length&.positive? 168 | if @licenses_list.include? gem['licenses'].first 169 | object.license_id = gem['licenses'].first 170 | else 171 | object.license_name = gem['licenses'].first 172 | end 173 | end 174 | 175 | object.author = gem['authors'] 176 | object.description = gem['summary'] 177 | object.hash = gem['sha'] 178 | @gems.push(object) 179 | count += 1 180 | @logger.info("#{object.name}:#{object.version} gem added") 181 | end 182 | end 183 | end 184 | -------------------------------------------------------------------------------- /lib/bom_component.rb: -------------------------------------------------------------------------------- 1 | 2 | class BomComponent 3 | DEFAULT_TYPE = "library".freeze 4 | HASH_ALG = 'SHA-256'.freeze 5 | 6 | def initialize(gem) 7 | @name = gem['name'] 8 | @version = gem['version'] 9 | @description = gem['description'] 10 | @hash = gem['hash'] 11 | @purl = gem['purl'] 12 | @gem = gem 13 | end 14 | 15 | def hash_val 16 | component_hash = { 17 | "type": DEFAULT_TYPE, 18 | "name": @name, 19 | "version": @version, 20 | "description": @description, 21 | "purl": @purl, 22 | "hashes": [ 23 | "alg": HASH_ALG, 24 | "content": @hash 25 | ] 26 | } 27 | 28 | if @gem['license_id'] 29 | component_hash[:"licenses"] = [ 30 | "license": { 31 | "id": @gem['license_id'] 32 | } 33 | ] 34 | elsif @gem['license_name'] 35 | component_hash[:"licenses"] = [ 36 | "license": { 37 | "name": @gem['license_name'] 38 | } 39 | ] 40 | end 41 | 42 | [component_hash] 43 | 44 | end 45 | end -------------------------------------------------------------------------------- /lib/bom_helpers.rb: -------------------------------------------------------------------------------- 1 | # This file is part of CycloneDX Ruby Gem. 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # 20 | # SPDX-License-Identifier: Apache-2.0 21 | # Copyright (c) OWASP Foundation. All Rights Reserved. 22 | # 23 | # frozen_string_literal: true 24 | 25 | require_relative 'bom_component' 26 | 27 | def purl(name, version) 28 | "pkg:gem/#{name}@#{version}" 29 | end 30 | 31 | def random_urn_uuid 32 | "urn:uuid:#{SecureRandom.uuid}" 33 | end 34 | 35 | def build_bom(gems, format) 36 | if format == 'json' 37 | build_json_bom(gems) 38 | else 39 | build_bom_xml(gems) 40 | end 41 | end 42 | 43 | def build_json_bom(gems) 44 | bom_hash = { 45 | "bomFormat": "CycloneDX", 46 | "specVersion": "1.1", 47 | "serialNumber": random_urn_uuid, 48 | "version": 1, 49 | "components": [] 50 | } 51 | 52 | gems.each do |gem| 53 | bom_hash[:components] += BomComponent.new(gem).hash_val() 54 | end 55 | 56 | JSON.pretty_generate(bom_hash) 57 | end 58 | 59 | def build_bom_xml(gems) 60 | builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| 61 | attributes = { 'xmlns' => 'http://cyclonedx.org/schema/bom/1.1', 'version' => '1', 'serialNumber' => random_urn_uuid } 62 | xml.bom(attributes) do 63 | xml.components do 64 | gems.each do |gem| 65 | xml.component('type' => 'library') do 66 | xml.name gem['name'] 67 | xml.version gem['version'] 68 | xml.description gem['description'] 69 | xml.hashes do 70 | xml.hash_ gem['hash'], alg: 'SHA-256' 71 | end 72 | if gem['license_id'] 73 | xml.licenses do 74 | xml.license do 75 | xml.id gem['license_id'] 76 | end 77 | end 78 | elsif gem['license_name'] 79 | xml.licenses do 80 | xml.license do 81 | xml.name gem['license_name'] 82 | end 83 | end 84 | end 85 | xml.purl gem['purl'] 86 | end 87 | end 88 | end 89 | end 90 | end 91 | 92 | builder.to_xml 93 | end 94 | 95 | def get_gem(name, version) 96 | url = "https://rubygems.org/api/v1/versions/#{name}.json" 97 | begin 98 | RestClient.proxy = ENV['http_proxy'] 99 | response = RestClient.get(url) 100 | body = JSON.parse(response.body) 101 | body.select { |item| item['number'] == version.to_s }.first 102 | rescue StandardError 103 | @logger.warn("#{name} couldn't be fetched") 104 | nil 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/licenses.json: -------------------------------------------------------------------------------- 1 | [ 2 | "0BSD", 3 | "AAL", 4 | "ADSL", 5 | "AFL-1.1", 6 | "AFL-1.2", 7 | "AFL-2.0", 8 | "AFL-2.1", 9 | "AFL-3.0", 10 | "AGPL-1.0", 11 | "AGPL-1.0-only", 12 | "AGPL-1.0-or-later", 13 | "AGPL-3.0", 14 | "AGPL-3.0-only", 15 | "AGPL-3.0-or-later", 16 | "AMDPLPA", 17 | "AML", 18 | "AMPAS", 19 | "ANTLR-PD", 20 | "APAFML", 21 | "APL-1.0", 22 | "APSL-1.0", 23 | "APSL-1.1", 24 | "APSL-1.2", 25 | "APSL-2.0", 26 | "Abstyles", 27 | "Adobe-2006", 28 | "Adobe-Glyph", 29 | "Afmparse", 30 | "Aladdin", 31 | "Apache-1.0", 32 | "Apache-1.1", 33 | "Apache-2.0", 34 | "Artistic-1.0", 35 | "Artistic-1.0-Perl", 36 | "Artistic-1.0-cl8", 37 | "Artistic-2.0", 38 | "BSD-1-Clause", 39 | "BSD-2-Clause", 40 | "BSD-2-Clause-FreeBSD", 41 | "BSD-2-Clause-NetBSD", 42 | "BSD-2-Clause-Patent", 43 | "BSD-3-Clause", 44 | "BSD-3-Clause-Attribution", 45 | "BSD-3-Clause-Clear", 46 | "BSD-3-Clause-LBNL", 47 | "BSD-3-Clause-No-Nuclear-License", 48 | "BSD-3-Clause-No-Nuclear-License-2014", 49 | "BSD-3-Clause-No-Nuclear-Warranty", 50 | "BSD-3-Clause-Open-MPI", 51 | "BSD-4-Clause", 52 | "BSD-4-Clause-UC", 53 | "BSD-Protection", 54 | "BSD-Source-Code", 55 | "BSL-1.0", 56 | "Bahyph", 57 | "Barr", 58 | "Beerware", 59 | "BitTorrent-1.0", 60 | "BitTorrent-1.1", 61 | "BlueOak-1.0.0", 62 | "Borceux", 63 | "CATOSL-1.1", 64 | "CC-BY-1.0", 65 | "CC-BY-2.0", 66 | "CC-BY-2.5", 67 | "CC-BY-3.0", 68 | "CC-BY-4.0", 69 | "CC-BY-NC-1.0", 70 | "CC-BY-NC-2.0", 71 | "CC-BY-NC-2.5", 72 | "CC-BY-NC-3.0", 73 | "CC-BY-NC-4.0", 74 | "CC-BY-NC-ND-1.0", 75 | "CC-BY-NC-ND-2.0", 76 | "CC-BY-NC-ND-2.5", 77 | "CC-BY-NC-ND-3.0", 78 | "CC-BY-NC-ND-4.0", 79 | "CC-BY-NC-SA-1.0", 80 | "CC-BY-NC-SA-2.0", 81 | "CC-BY-NC-SA-2.5", 82 | "CC-BY-NC-SA-3.0", 83 | "CC-BY-NC-SA-4.0", 84 | "CC-BY-ND-1.0", 85 | "CC-BY-ND-2.0", 86 | "CC-BY-ND-2.5", 87 | "CC-BY-ND-3.0", 88 | "CC-BY-ND-4.0", 89 | "CC-BY-SA-1.0", 90 | "CC-BY-SA-2.0", 91 | "CC-BY-SA-2.5", 92 | "CC-BY-SA-3.0", 93 | "CC-BY-SA-4.0", 94 | "CC-PDDC", 95 | "CC0-1.0", 96 | "CDDL-1.0", 97 | "CDDL-1.1", 98 | "CDLA-Permissive-1.0", 99 | "CDLA-Sharing-1.0", 100 | "CECILL-1.0", 101 | "CECILL-1.1", 102 | "CECILL-2.0", 103 | "CECILL-2.1", 104 | "CECILL-B", 105 | "CECILL-C", 106 | "CERN-OHL-1.1", 107 | "CERN-OHL-1.2", 108 | "CNRI-Jython", 109 | "CNRI-Python", 110 | "CNRI-Python-GPL-Compatible", 111 | "CPAL-1.0", 112 | "CPL-1.0", 113 | "CPOL-1.02", 114 | "CUA-OPL-1.0", 115 | "Caldera", 116 | "ClArtistic", 117 | "Condor-1.1", 118 | "Crossword", 119 | "CrystalStacker", 120 | "Cube", 121 | "D-FSL-1.0", 122 | "DOC", 123 | "DSDP", 124 | "Dotseqn", 125 | "ECL-1.0", 126 | "ECL-2.0", 127 | "EFL-1.0", 128 | "EFL-2.0", 129 | "EPL-1.0", 130 | "EPL-2.0", 131 | "EUDatagrid", 132 | "EUPL-1.0", 133 | "EUPL-1.1", 134 | "EUPL-1.2", 135 | "Entessa", 136 | "ErlPL-1.1", 137 | "Eurosym", 138 | "FSFAP", 139 | "FSFUL", 140 | "FSFULLR", 141 | "FTL", 142 | "Fair", 143 | "Frameworx-1.0", 144 | "FreeImage", 145 | "GFDL-1.1", 146 | "GFDL-1.1-only", 147 | "GFDL-1.1-or-later", 148 | "GFDL-1.2", 149 | "GFDL-1.2-only", 150 | "GFDL-1.2-or-later", 151 | "GFDL-1.3", 152 | "GFDL-1.3-only", 153 | "GFDL-1.3-or-later", 154 | "GL2PS", 155 | "GPL-1.0", 156 | "GPL-1.0+", 157 | "GPL-1.0-only", 158 | "GPL-1.0-or-later", 159 | "GPL-2.0", 160 | "GPL-2.0+", 161 | "GPL-2.0-only", 162 | "GPL-2.0-or-later", 163 | "GPL-2.0-with-GCC-exception", 164 | "GPL-2.0-with-autoconf-exception", 165 | "GPL-2.0-with-bison-exception", 166 | "GPL-2.0-with-classpath-exception", 167 | "GPL-2.0-with-font-exception", 168 | "GPL-3.0", 169 | "GPL-3.0+", 170 | "GPL-3.0-only", 171 | "GPL-3.0-or-later", 172 | "GPL-3.0-with-GCC-exception", 173 | "GPL-3.0-with-autoconf-exception", 174 | "Giftware", 175 | "Glide", 176 | "Glulxe", 177 | "HPND", 178 | "HPND-sell-variant", 179 | "HaskellReport", 180 | "IBM-pibs", 181 | "ICU", 182 | "IJG", 183 | "IPA", 184 | "IPL-1.0", 185 | "ISC", 186 | "ImageMagick", 187 | "Imlib2", 188 | "Info-ZIP", 189 | "Intel", 190 | "Intel-ACPI", 191 | "Interbase-1.0", 192 | "JPNIC", 193 | "JSON", 194 | "JasPer-2.0", 195 | "LAL-1.2", 196 | "LAL-1.3", 197 | "LGPL-2.0", 198 | "LGPL-2.0+", 199 | "LGPL-2.0-only", 200 | "LGPL-2.0-or-later", 201 | "LGPL-2.1", 202 | "LGPL-2.1+", 203 | "LGPL-2.1-only", 204 | "LGPL-2.1-or-later", 205 | "LGPL-3.0", 206 | "LGPL-3.0+", 207 | "LGPL-3.0-only", 208 | "LGPL-3.0-or-later", 209 | "LGPLLR", 210 | "LPL-1.0", 211 | "LPL-1.02", 212 | "LPPL-1.0", 213 | "LPPL-1.1", 214 | "LPPL-1.2", 215 | "LPPL-1.3a", 216 | "LPPL-1.3c", 217 | "Latex2e", 218 | "Leptonica", 219 | "LiLiQ-P-1.1", 220 | "LiLiQ-R-1.1", 221 | "LiLiQ-Rplus-1.1", 222 | "Libpng", 223 | "Linux-OpenIB", 224 | "MIT", 225 | "MIT-0", 226 | "MIT-CMU", 227 | "MIT-advertising", 228 | "MIT-enna", 229 | "MIT-feh", 230 | "MITNFA", 231 | "MPL-1.0", 232 | "MPL-1.1", 233 | "MPL-2.0", 234 | "MPL-2.0-no-copyleft-exception", 235 | "MS-PL", 236 | "MS-RL", 237 | "MTLL", 238 | "MakeIndex", 239 | "MirOS", 240 | "Motosoto", 241 | "Multics", 242 | "Mup", 243 | "NASA-1.3", 244 | "NBPL-1.0", 245 | "NCSA", 246 | "NGPL", 247 | "NLOD-1.0", 248 | "NLPL", 249 | "NOSL", 250 | "NPL-1.0", 251 | "NPL-1.1", 252 | "NPOSL-3.0", 253 | "NRL", 254 | "NTP", 255 | "Naumen", 256 | "Net-SNMP", 257 | "NetCDF", 258 | "Newsletr", 259 | "Nokia", 260 | "Noweb", 261 | "Nunit", 262 | "OCCT-PL", 263 | "OCLC-2.0", 264 | "ODC-By-1.0", 265 | "ODbL-1.0", 266 | "OFL-1.0", 267 | "OFL-1.1", 268 | "OGL-UK-1.0", 269 | "OGL-UK-2.0", 270 | "OGL-UK-3.0", 271 | "OGTSL", 272 | "OLDAP-1.1", 273 | "OLDAP-1.2", 274 | "OLDAP-1.3", 275 | "OLDAP-1.4", 276 | "OLDAP-2.0", 277 | "OLDAP-2.0.1", 278 | "OLDAP-2.1", 279 | "OLDAP-2.2", 280 | "OLDAP-2.2.1", 281 | "OLDAP-2.2.2", 282 | "OLDAP-2.3", 283 | "OLDAP-2.4", 284 | "OLDAP-2.5", 285 | "OLDAP-2.6", 286 | "OLDAP-2.7", 287 | "OLDAP-2.8", 288 | "OML", 289 | "OPL-1.0", 290 | "OSET-PL-2.1", 291 | "OSL-1.0", 292 | "OSL-1.1", 293 | "OSL-2.0", 294 | "OSL-2.1", 295 | "OSL-3.0", 296 | "OpenSSL", 297 | "PDDL-1.0", 298 | "PHP-3.0", 299 | "PHP-3.01", 300 | "Parity-6.0.0", 301 | "Plexus", 302 | "PostgreSQL", 303 | "Python-2.0", 304 | "QPL-1.0", 305 | "Qhull", 306 | "RHeCos-1.1", 307 | "RPL-1.1", 308 | "RPL-1.5", 309 | "RPSL-1.0", 310 | "RSA-MD", 311 | "RSCPL", 312 | "Rdisc", 313 | "Ruby", 314 | "SAX-PD", 315 | "SCEA", 316 | "SGI-B-1.0", 317 | "SGI-B-1.1", 318 | "SGI-B-2.0", 319 | "SHL-0.5", 320 | "SHL-0.51", 321 | "SISSL", 322 | "SISSL-1.2", 323 | "SMLNJ", 324 | "SMPPL", 325 | "SNIA", 326 | "SPL-1.0", 327 | "SSPL-1.0", 328 | "SWL", 329 | "Saxpath", 330 | "Sendmail", 331 | "Sendmail-8.23", 332 | "SimPL-2.0", 333 | "Sleepycat", 334 | "Spencer-86", 335 | "Spencer-94", 336 | "Spencer-99", 337 | "StandardML-NJ", 338 | "SugarCRM-1.1.3", 339 | "TAPR-OHL-1.0", 340 | "TCL", 341 | "TCP-wrappers", 342 | "TMate", 343 | "TORQUE-1.1", 344 | "TOSL", 345 | "TU-Berlin-1.0", 346 | "TU-Berlin-2.0", 347 | "UPL-1.0", 348 | "Unicode-DFS-2015", 349 | "Unicode-DFS-2016", 350 | "Unicode-TOU", 351 | "Unlicense", 352 | "VOSTROM", 353 | "VSL-1.0", 354 | "Vim", 355 | "W3C", 356 | "W3C-19980720", 357 | "W3C-20150513", 358 | "WTFPL", 359 | "Watcom-1.0", 360 | "Wsuipa", 361 | "X11", 362 | "XFree86-1.1", 363 | "XSkat", 364 | "Xerox", 365 | "Xnet", 366 | "YPL-1.0", 367 | "YPL-1.1", 368 | "ZPL-1.1", 369 | "ZPL-2.0", 370 | "ZPL-2.1", 371 | "Zed", 372 | "Zend-2.0", 373 | "Zimbra-1.3", 374 | "Zimbra-1.4", 375 | "Zlib", 376 | "blessing", 377 | "bzip2-1.0.5", 378 | "bzip2-1.0.6", 379 | "copyleft-next-0.3.0", 380 | "copyleft-next-0.3.1", 381 | "curl", 382 | "diffmark", 383 | "dvipdfm", 384 | "eCos-2.0", 385 | "eGenix", 386 | "gSOAP-1.3b", 387 | "gnuplot", 388 | "iMatix", 389 | "libpng-2.0", 390 | "libtiff", 391 | "mpich2", 392 | "psfrag", 393 | "psutils", 394 | "wxWindows", 395 | "xinetd", 396 | "xpp", 397 | "zlib-acknowledgement", 398 | "Libtool-exception", 399 | "Linux-syscall-note", 400 | "Autoconf-exception-3.0", 401 | "OCCT-exception-1.0", 402 | "openvpn-openssl-exception", 403 | "gnu-javamail-exception", 404 | "OpenJDK-assembly-exception-1.0", 405 | "Bison-exception-2.2", 406 | "i2p-gpl-java-exception", 407 | "Universal-FOSS-exception-1.0", 408 | "Qt-LGPL-exception-1.1", 409 | "389-exception", 410 | "Classpath-exception-2.0", 411 | "Fawkes-Runtime-exception", 412 | "PS-or-PDF-font-exception-20170817", 413 | "Qt-GPL-exception-1.0", 414 | "LZMA-exception", 415 | "freertos-exception-2.0", 416 | "Qwt-exception-1.0", 417 | "CLISP-exception-2.0", 418 | "FLTK-exception", 419 | "Bootloader-exception", 420 | "Nokia-Qt-exception-1.1", 421 | "LLVM-exception", 422 | "WxWindows-exception-3.1", 423 | "DigiRule-FOSS-exception", 424 | "Swift-exception", 425 | "GCC-exception-3.1", 426 | "eCos-exception-2.0", 427 | "Autoconf-exception-2.0", 428 | "GPL-CC-1.0", 429 | "Font-exception-2.0", 430 | "u-boot-exception-2.0", 431 | "GCC-exception-2.0", 432 | "mif-exception", 433 | "OCaml-LGPL-linking-exception" 434 | ] 435 | -------------------------------------------------------------------------------- /spec/bom_component_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'bom_component' 3 | 4 | RSpec.shared_examples "a valid hash_val result for gem" do 5 | it { expect(result.count).to eq(1) } 6 | it { expect(result[0][:type]).to eq('library') } 7 | it { expect(result[0][:name]).to eq(gem.name) } 8 | it { expect(result[0][:version]).to eq(gem.version) } 9 | it { expect(result[0][:description]).to eq(gem.description) } 10 | it { expect(result[0][:purl]).to eq(gem.purl) } 11 | it { expect(result[0][:hashes].count).to eq(1) } 12 | it { expect(result[0][:hashes][0][:alg]).to eq('SHA-256') } 13 | it { expect(result[0][:hashes][0][:content]).to eq(gem.hash) } 14 | end 15 | 16 | RSpec.describe BomComponent do 17 | context '#hash_val' do 18 | let(:base_gem) do 19 | OpenStruct.new( 20 | name: 'Sample', 21 | version: '1.0.0', 22 | description: 'Sample description', 23 | hash: '1f809ab336c437d894df9934a9fc9ffd2ea09b535dfa4e3f75db078531c260c8', 24 | purl: 'pkg:gem/sample@1.0.0' 25 | ) 26 | end 27 | 28 | let(:gem) do 29 | base_gem 30 | end 31 | 32 | subject(:result) { BomComponent.new(gem).hash_val } 33 | 34 | context 'with a gem without a license' do 35 | include_examples 'a valid hash_val result for gem' 36 | end 37 | 38 | context 'with a gem that has a license_id' do 39 | let(:gem) do 40 | base_gem.tap do |value| 41 | value.license_id = 'License ID' 42 | end 43 | end 44 | 45 | include_examples 'a valid hash_val result for gem' 46 | 47 | it { expect(result[0][:licenses].count).to eq(1) } 48 | it { expect(result[0][:licenses][0][:license][:id]).to eq(gem.license_id) } 49 | end 50 | 51 | context 'with a gem that has a license_name' do 52 | let(:gem) do 53 | base_gem.tap do |value| 54 | value.license_name = 'License Name' 55 | end 56 | end 57 | 58 | include_examples 'a valid hash_val result for gem' 59 | 60 | it { expect(result[0][:licenses].count).to eq(1) } 61 | it { expect(result[0][:licenses][0][:license][:name]).to eq(gem.license_name) } 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/bom_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'bom_helpers' 3 | 4 | RSpec.describe 'helper methods' do 5 | context '#purl' do 6 | it 'builds a purl' do 7 | expect(purl('activesupport', '7.0.1')).to eq("pkg:gem/activesupport@7.0.1") 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/cucumber/aruba/blob/3b1a6cea6e3ba55370c3396eef0a955aeb40f287/spec/spec_helper.rb 2 | # Licensed under MIT - https://github.com/cucumber/aruba/blob/3b1a6cea6e3ba55370c3396eef0a955aeb40f287/LICENSE 3 | 4 | $LOAD_PATH << File.expand_path('../lib', __dir__) 5 | 6 | unless RUBY_PLATFORM.include?('java') 7 | require 'simplecov' 8 | SimpleCov.command_name 'RSpec' 9 | 10 | # Run simplecov by default 11 | SimpleCov.start unless ENV.key? 'ARUBA_NO_COVERAGE' 12 | end 13 | 14 | # Loading support files 15 | Dir.glob(File.expand_path('support/*.rb', __dir__)).sort.each { |f| require_relative f } 16 | Dir.glob(File.expand_path('support/**/*.rb', __dir__)).sort.each { |f| require_relative f } 17 | --------------------------------------------------------------------------------