license_finder 6.5.0 → 6.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/CONTRIBUTING.md +5 -4
  4. data/Dockerfile +26 -9
  5. data/README.md +53 -15
  6. data/Rakefile +1 -10
  7. data/VERSION +1 -1
  8. data/ci/pipelines/pull-request.yml.erb +2 -0
  9. data/ci/pipelines/release.yml.erb +16 -4
  10. data/ci/tasks/rubocop.yml +2 -0
  11. data/ci/tasks/update-changelog.yml +2 -0
  12. data/examples/Gemfile +4 -0
  13. data/examples/custom_erb_template.rb +24 -0
  14. data/examples/extract_license_data.rb +63 -0
  15. data/examples/sample_template.erb +7 -0
  16. data/lib/license_finder/cli/base.rb +8 -1
  17. data/lib/license_finder/cli/inherited_decisions.rb +18 -0
  18. data/lib/license_finder/cli/main.rb +5 -1
  19. data/lib/license_finder/configuration.rb +13 -1
  20. data/lib/license_finder/core.rb +5 -2
  21. data/lib/license_finder/decisions.rb +58 -10
  22. data/lib/license_finder/license.rb +45 -1
  23. data/lib/license_finder/license/definitions.rb +49 -2
  24. data/lib/license_finder/license/header_matcher.rb +7 -2
  25. data/lib/license_finder/license/templates/0BSD.txt +10 -0
  26. data/lib/license_finder/license/templates/MPL1_1.txt +469 -0
  27. data/lib/license_finder/license/text.rb +2 -2
  28. data/lib/license_finder/logger.rb +2 -0
  29. data/lib/license_finder/package.rb +2 -0
  30. data/lib/license_finder/package_manager.rb +15 -5
  31. data/lib/license_finder/package_managers/composer.rb +8 -4
  32. data/lib/license_finder/package_managers/conda.rb +131 -0
  33. data/lib/license_finder/package_managers/dep.rb +6 -1
  34. data/lib/license_finder/package_managers/dotnet.rb +2 -1
  35. data/lib/license_finder/package_managers/erlangmk.rb +50 -0
  36. data/lib/license_finder/package_managers/go_15vendorexperiment.rb +6 -1
  37. data/lib/license_finder/package_managers/go_dep.rb +15 -8
  38. data/lib/license_finder/package_managers/go_modules.rb +43 -15
  39. data/lib/license_finder/package_managers/mix.rb +1 -1
  40. data/lib/license_finder/package_managers/npm.rb +1 -1
  41. data/lib/license_finder/package_managers/nuget.rb +36 -1
  42. data/lib/license_finder/package_managers/pipenv.rb +1 -1
  43. data/lib/license_finder/package_managers/rebar.rb +29 -8
  44. data/lib/license_finder/package_managers/trash.rb +6 -1
  45. data/lib/license_finder/package_managers/yarn.rb +1 -1
  46. data/lib/license_finder/packages/conda_package.rb +74 -0
  47. data/lib/license_finder/packages/erlangmk_package.rb +114 -0
  48. data/lib/license_finder/packages/pip_package.rb +9 -2
  49. data/lib/license_finder/report.rb +1 -0
  50. data/lib/license_finder/reports/junit_report.rb +19 -0
  51. data/lib/license_finder/reports/templates/junit_report.erb +41 -0
  52. data/lib/license_finder/scanner.rb +25 -2
  53. data/license_finder.gemspec +3 -2
  54. metadata +41 -9
data/ci/tasks/rubocop.yml CHANGED
@@ -5,6 +5,8 @@ image_resource:
5
5
  source:
6
6
  repository: ruby
7
7
  tag: 2.7.1
8
+ username: ((LicenseFinderDocker.username))
9
+ password: ((LicenseFinderDocker.password))
8
10
 
9
11
  inputs:
10
12
  - name: LicenseFinder
@@ -4,6 +4,8 @@ image_resource:
4
4
  source:
5
5
  repository: brenix/alpine-bash-git-ssh
6
6
  tag: latest
7
+ username: ((LicenseFinderDocker.username))
8
+ password: ((LicenseFinderDocker.password))
7
9
  platform: linux
8
10
  inputs:
9
11
  - name: lf-git
data/examples/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gem 'license_finder', path: '..'
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'rubygems'
6
+ require 'bundler/setup'
7
+
8
+ # This is an example of how to programatically generate a report using a custom
9
+ # ERB template. Run with
10
+ # > bundle install
11
+ # > ./custom_erb_template.rb
12
+
13
+ require 'license_finder'
14
+
15
+ # See lib/license_finder/core.rb for more configuration options.
16
+ # A quiet logger is required when running reports...
17
+ lf = LicenseFinder::Core.new(LicenseFinder::Configuration.with_optional_saved_config(logger: :quiet))
18
+
19
+ # Find many more examples of complex ERB templates in
20
+ # lib/license_finder/reports/templates/
21
+ template = Pathname.new('./sample_template.erb')
22
+ print LicenseFinder::ErbReport
23
+ .new(lf.acknowledged, project_name: lf.project_name)
24
+ .to_s(template)
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'rubygems'
6
+ require 'bundler/setup'
7
+
8
+ # This is an example of how to programatically extract the information that
9
+ # LicenseFinder has about packages and their licenses.
10
+ # > bundle install
11
+ # > ./extract_license_data.rb
12
+
13
+ require 'license_finder'
14
+
15
+ # See lib/license_finder/core.rb for more configuration options.
16
+ # A quiet logger is required when running reports...
17
+ lf = LicenseFinder::Core.new(LicenseFinder::Configuration.with_optional_saved_config(logger: :quiet))
18
+
19
+ # Groups of packages
20
+ lf.acknowledged # All (non-ignored) packages license_finder is tracking
21
+ lf.unapproved # The packages which have not been approved or permitted
22
+ lf.restricted # The packages which have been restricted
23
+
24
+ # Package details
25
+ lf.acknowledged.each do |package|
26
+ # Approvals
27
+ package.approved? # Whether the package has been approved manually or permitted
28
+ package.approved_manually?
29
+ package.permitted?
30
+ package.restricted?
31
+
32
+ # Licensing
33
+ # The set of licenses, each of which has a name and url, which
34
+ # license_finder will report for this package.
35
+ package.licenses
36
+ # Additional information about how these licenses were chosen
37
+ # (from decision, from spec, from files, or none-found). See
38
+ # LicenseFinder::Licensing and LicenseFinder::Activation
39
+ package.activations
40
+ # The files that look like licenses, found in the package's
41
+ # directory. Caveat: if a package's licenses were specified by a decision or
42
+ # by the package's spec, the license_files will be ignored. That means,
43
+ # package.licenses may report different licenses than those found in
44
+ # license_files.
45
+ package.license_files
46
+ package.license_files.each do |file|
47
+ # The license found in this file.
48
+ file.license
49
+ # The text of the file. Sometimes this will be an entire README file,
50
+ # because license_finder has found the phrase "is released under the
51
+ # MIT license" in it.
52
+ file.text
53
+ end
54
+ package.licensing.activations_from_decisions # If license_finder only knew about decisions, what licenses would it report?
55
+ package.licensing.activations_from_spec # If license_finder only knew about package specs, what licenses would it report?
56
+ package.licensing.activations_from_files # If license_finder only knew about package files, what licenses would it report?
57
+ package.licensing.activations_from_files.each do |activation|
58
+ # Each activation groups together all files that point to the same license.
59
+ # Each file contains its #license and #text.
60
+ activation.license
61
+ activation.files
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ Licenses
2
+
3
+ <%= dependencies.size %> total
4
+
5
+ <% grouped_dependencies.each do |license_name, group| -%>
6
+ * <%= group.size %> <%= license_name %>
7
+ <% end %>
@@ -11,6 +11,10 @@ module LicenseFinder
11
11
  desc: 'Where decisions are saved. Defaults to doc/dependency_decisions.yml.'
12
12
  class_option :log_directory,
13
13
  desc: 'Where logs are saved. Defaults to ./lf_logs/$PROJECT/prepare_$PACKAGE_MANAGER.log'
14
+ class_option :enabled_package_managers,
15
+ desc: 'List of package managers to be enabled. Defaults to all supported package managers.',
16
+ type: :array,
17
+ enum: LicenseFinder::Scanner.supported_package_manager_ids
14
18
 
15
19
  no_commands do
16
20
  def decisions
@@ -32,6 +36,7 @@ module LicenseFinder
32
36
  extract_options(
33
37
  :project_path,
34
38
  :decisions_file,
39
+ :enabled_package_managers,
35
40
  :go_full_version,
36
41
  :gradle_command,
37
42
  :gradle_include_groups,
@@ -53,7 +58,9 @@ module LicenseFinder
53
58
  :columns,
54
59
  :aggregate_paths,
55
60
  :recursive,
56
- :sbt_include_groups
61
+ :sbt_include_groups,
62
+ :conda_bash_setup_script,
63
+ :composer_check_require_only
57
64
  ).merge(
58
65
  logger: logger_mode
59
66
  )
@@ -20,6 +20,15 @@ module LicenseFinder
20
20
  say "Added #{decision_files.join(', ')} to the inherited decisions"
21
21
  end
22
22
 
23
+ auditable
24
+ desc 'add_with_auth URL AUTH_TYPE TOKEN_OR_ENV', 'Add a remote decision file that needs authentication'
25
+ def add_with_auth(*params)
26
+ url, auth_type, token_or_env = params
27
+ auth_info = { 'url' => url, 'authorization' => "#{auth_type} #{token_or_env}" }
28
+ modifying { decisions.add_decision [:inherit_from, auth_info] }
29
+ say "Added #{url} to the inherited decisions"
30
+ end
31
+
23
32
  auditable
24
33
  desc 'remove DECISION_FILE...', 'Remove one or more decision files from the inherited decisions'
25
34
  def remove(*decision_files)
@@ -27,6 +36,15 @@ module LicenseFinder
27
36
  modifying { decision_files.each { |filepath| decisions.remove_inheritance(filepath) } }
28
37
  say "Removed #{decision_files.join(', ')} from the inherited decisions"
29
38
  end
39
+
40
+ auditable
41
+ desc 'remove_with_auth URL AUTH_TYPE TOKEN_OR_ENV', 'Add a remote decision file that needs authentication'
42
+ def remove_with_auth(*params)
43
+ url, auth_type, token_or_env = params
44
+ auth_info = { 'url' => url, 'authorization' => "#{auth_type} #{token_or_env}" }
45
+ modifying { decisions.remove_inheritance(auth_info) }
46
+ say "Removed #{url} from the inherited decisions"
47
+ end
30
48
  end
31
49
  end
32
50
  end
@@ -19,7 +19,8 @@ module LicenseFinder
19
19
  'markdown' => MarkdownReport,
20
20
  'csv' => CsvReport,
21
21
  'xml' => XmlReport,
22
- 'json' => JsonReport
22
+ 'json' => JsonReport,
23
+ 'junit' => JunitReport
23
24
  }.freeze
24
25
 
25
26
  class_option :go_full_version, desc: 'Whether dependency version should include full version. Only meaningful if used with a Go project. Defaults to false.'
@@ -37,6 +38,9 @@ module LicenseFinder
37
38
  class_option :mix_command, desc: "Command to use when fetching packages through Mix. Only meaningful if used with a Mix project (i.e., Elixir or Erlang). Defaults to 'mix'."
38
39
  class_option :mix_deps_dir, desc: "Path to Mix dependencies directory. Only meaningful if used with a Mix project (i.e., Elixir or Erlang). Defaults to 'deps'."
39
40
  class_option :sbt_include_groups, desc: 'Whether dependency name should include group id. Only meaningful if used with a Scala/sbt project. Defaults to false.'
41
+ class_option :conda_bash_setup_script, desc: "Path to conda.sh script. Only meaningful if used with a Conda project. Defaults to '~/miniconda3/etc/profile.d/conda.sh'."
42
+ class_option :composer_check_require_only,
43
+ desc: "Whether to only check for licenses from dependencies on the 'require' section. Only meaningful if used with a Composer project. Defaults to false."
40
44
 
41
45
  # Method options which are shared between report and action_item
42
46
  def self.format_option
@@ -35,7 +35,7 @@ module LicenseFinder
35
35
  end
36
36
 
37
37
  def rebar_deps_dir
38
- path = get(:rebar_deps_dir) || 'deps'
38
+ path = get(:rebar_deps_dir) || '_build/default/lib'
39
39
  project_path.join(path).expand_path
40
40
  end
41
41
 
@@ -65,6 +65,10 @@ module LicenseFinder
65
65
  Pathname(path_prefix).expand_path
66
66
  end
67
67
 
68
+ def enabled_package_manager_ids
69
+ get(:enabled_package_managers)
70
+ end
71
+
68
72
  def logger_mode
69
73
  get(:logger)
70
74
  end
@@ -93,6 +97,10 @@ module LicenseFinder
93
97
  get(:pip_requirements_path)
94
98
  end
95
99
 
100
+ def conda_bash_setup_script
101
+ get(:conda_bash_setup_script)
102
+ end
103
+
96
104
  def python_version
97
105
  get(:python_version)
98
106
  end
@@ -137,6 +145,10 @@ module LicenseFinder
137
145
  get(:sbt_include_groups)
138
146
  end
139
147
 
148
+ def composer_check_require_only
149
+ get(:composer_check_require_only)
150
+ end
151
+
140
152
  attr_writer :strict_matching
141
153
 
142
154
  attr_reader :strict_matching
@@ -24,7 +24,7 @@ module LicenseFinder
24
24
  # Default +options+:
25
25
  # {
26
26
  # project_path: Pathname.pwd
27
- # logger: {}, # can include quiet: true or debug: true
27
+ # logger: nil, # can be :quiet or :debug
28
28
  # decisions_file: "doc/dependency_decisions.yml",
29
29
  # gradle_command: "gradle",
30
30
  # rebar_command: "rebar",
@@ -93,6 +93,7 @@ module LicenseFinder
93
93
  project_path: config.project_path,
94
94
  log_directory: File.join(config.log_directory, project_name),
95
95
  ignored_groups: decisions.ignored_groups,
96
+ enabled_package_manager_ids: config.enabled_package_manager_ids,
96
97
  go_full_version: config.go_full_version,
97
98
  gradle_command: config.gradle_command,
98
99
  gradle_include_groups: config.gradle_include_groups,
@@ -107,7 +108,9 @@ module LicenseFinder
107
108
  mix_deps_dir: config.mix_deps_dir,
108
109
  prepare: config.prepare,
109
110
  prepare_no_fail: config.prepare_no_fail,
110
- sbt_include_groups: config.sbt_include_groups
111
+ sbt_include_groups: config.sbt_include_groups,
112
+ conda_bash_setup_script: config.conda_bash_setup_script,
113
+ composer_check_require_only: config.composer_check_require_only
111
114
  }
112
115
  end
113
116
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'open-uri'
4
+ require 'license_finder/license'
4
5
 
5
6
  module LicenseFinder
6
7
  class Decisions
@@ -39,7 +40,15 @@ module LicenseFinder
39
40
  end
40
41
 
41
42
  def permitted?(lic)
42
- @permitted.include?(lic)
43
+ if @permitted.include?(lic)
44
+ true
45
+ elsif lic.is_a?(OrLicense)
46
+ lic.sub_licenses.any? { |sub_lic| @permitted.include?(sub_lic) }
47
+ elsif lic.is_a?(AndLicense)
48
+ lic.sub_licenses.all? { |sub_lic| @permitted.include?(sub_lic) }
49
+ else
50
+ false
51
+ end
43
52
  end
44
53
 
45
54
  def restricted?(lic)
@@ -183,19 +192,37 @@ module LicenseFinder
183
192
  self
184
193
  end
185
194
 
186
- def inherit_from(filepath)
195
+ def inherit_from(filepath_info)
187
196
  decisions =
188
- if filepath =~ %r{^https?://}
189
- open_uri(filepath).read
197
+ if filepath_info.is_a?(Hash)
198
+ resolve_inheritance(filepath_info)
199
+ elsif filepath_info =~ %r{^https?://}
200
+ open_uri(filepath_info).read
190
201
  else
191
- Pathname(filepath).read
202
+ Pathname(filepath_info).read
192
203
  end
193
204
 
194
- add_decision [:inherit_from, filepath]
195
- @inherited_decisions << filepath
205
+ add_decision [:inherit_from, filepath_info]
206
+ @inherited_decisions << filepath_info
196
207
  restore_inheritance(decisions)
197
208
  end
198
209
 
210
+ def resolve_inheritance(filepath_info)
211
+ if (gem_name = filepath_info['gem'])
212
+ Pathname(gem_config_path(gem_name, filepath_info['path'])).read
213
+ else
214
+ open_uri(filepath_info['url'], filepath_info['authorization']).read
215
+ end
216
+ end
217
+
218
+ def gem_config_path(gem_name, relative_config_path)
219
+ spec = Gem::Specification.find_by_name(gem_name)
220
+ File.join(spec.gem_dir, relative_config_path)
221
+ rescue Gem::LoadError => e
222
+ raise Gem::LoadError,
223
+ "Unable to find gem #{gem_name}; is the gem installed? #{e}"
224
+ end
225
+
199
226
  def remove_inheritance(filepath)
200
227
  @decisions -= [[:inherit_from, filepath]]
201
228
  @inherited_decisions.delete(filepath)
@@ -213,17 +240,31 @@ module LicenseFinder
213
240
  self
214
241
  end
215
242
 
216
- def open_uri(uri)
243
+ def open_uri(uri, auth = nil)
244
+ header = {}
245
+ auth_header = resolve_authorization(auth)
246
+ header['Authorization'] = auth_header if auth_header
247
+
217
248
  # ruby < 2.5.0 URI.open is private
218
249
  if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5.0')
219
250
  # rubocop:disable Security/Open
220
- open(uri)
251
+ open(uri, header)
221
252
  # rubocop:enable Security/Open
222
253
  else
223
- URI.open(uri)
254
+ URI.open(uri, header)
224
255
  end
225
256
  end
226
257
 
258
+ def resolve_authorization(auth)
259
+ return unless auth
260
+
261
+ token_env = auth.match(/\$(\S.*)/)
262
+ return auth unless token_env
263
+
264
+ token = ENV[token_env[1]]
265
+ auth.sub(token_env[0], token)
266
+ end
267
+
227
268
  #########
228
269
  # PERSIST
229
270
  #########
@@ -240,6 +281,13 @@ module LicenseFinder
240
281
  return result unless persisted
241
282
 
242
283
  actions = YAML.load(persisted)
284
+
285
+ list_of_actions = (actions || []).map(&:first)
286
+
287
+ if (list_of_actions & %i[whitelist blacklist]).any?
288
+ raise 'The decisions file seems to have whitelist/blacklist keys which are deprecated. Please replace them with permit/restrict respectively and try again! More info - https://github.com/pivotal/LicenseFinder/commit/a40b22fda11b3a0efbb3c0a021381534bc998dd9'
289
+ end
290
+
243
291
  (actions || []).each do |action, *args|
244
292
  result.send(action, *args)
245
293
  end
@@ -19,7 +19,17 @@ module LicenseFinder
19
19
 
20
20
  def find_by_name(name)
21
21
  name ||= 'unknown'
22
- all.detect { |l| l.matches_name? l.stripped_name(name) } || Definitions.build_unrecognized(name)
22
+ license = all.detect { |l| l.matches_name? l.stripped_name(name) }
23
+
24
+ if license
25
+ license
26
+ elsif name.include?(OrLicense.operator)
27
+ OrLicense.new(name)
28
+ elsif name.include?(AndLicense.operator)
29
+ AndLicense.new(name)
30
+ else
31
+ Definitions.build_unrecognized(name)
32
+ end
23
33
  end
24
34
 
25
35
  def find_by_text(text)
@@ -61,6 +71,10 @@ module LicenseFinder
61
71
  name.hash
62
72
  end
63
73
 
74
+ def unrecognized_matcher?
75
+ matcher.is_a?(NoneMatcher)
76
+ end
77
+
64
78
  private
65
79
 
66
80
  attr_reader :short_name, :pretty_name, :other_names
@@ -70,4 +84,34 @@ module LicenseFinder
70
84
  ([short_name, pretty_name] + other_names).uniq
71
85
  end
72
86
  end
87
+ class AndLicense < License
88
+ def self.operator
89
+ ' AND '
90
+ end
91
+
92
+ def initialize(name, operator = AndLicense.operator)
93
+ @short_name = name
94
+ @pretty_name = name
95
+ @url = nil
96
+ @matcher = NoneMatcher.new
97
+ # removes heading and trailing parentesis and splits
98
+ name = name[1..-2] if name.start_with?('(')
99
+ names = name.split(operator)
100
+ @sub_licenses = names.map do |sub_name|
101
+ License.find_by_name(sub_name)
102
+ end
103
+ end
104
+
105
+ attr_reader :sub_licenses
106
+ end
107
+
108
+ class OrLicense < AndLicense
109
+ def self.operator
110
+ ' OR '
111
+ end
112
+
113
+ def initialize(name)
114
+ super(name, OrLicense.operator)
115
+ end
116
+ end
73
117
  end