license_finder 5.11.1 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +25 -0
- data/CONTRIBUTING.md +1 -1
- data/Dockerfile +9 -14
- data/LICENSE +1 -1
- data/README.md +18 -18
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/bin/license_finder_pip.py +6 -2
- data/ci/pipelines/pull-request.yml.erb +14 -0
- data/ci/pipelines/release.yml.erb +24 -0
- data/lib/license_finder/cli.rb +2 -2
- data/lib/license_finder/cli/approvals.rb +1 -0
- data/lib/license_finder/cli/dependencies.rb +3 -2
- data/lib/license_finder/cli/main.rb +8 -8
- data/lib/license_finder/cli/makes_decisions.rb +3 -0
- data/lib/license_finder/cli/permitted_licenses.rb +32 -0
- data/lib/license_finder/cli/restricted_licenses.rb +32 -0
- data/lib/license_finder/core.rb +1 -1
- data/lib/license_finder/decision_applier.rb +6 -6
- data/lib/license_finder/decisions.rb +19 -19
- data/lib/license_finder/license_aggregator.rb +2 -2
- data/lib/license_finder/package.rb +15 -15
- data/lib/license_finder/package_manager.rb +1 -0
- data/lib/license_finder/package_managers/bundler.rb +3 -1
- data/lib/license_finder/package_managers/composer.rb +1 -1
- data/lib/license_finder/package_managers/pip.rb +11 -19
- data/lib/license_finder/package_managers/pipenv.rb +63 -0
- data/lib/license_finder/package_utils/pypi.rb +41 -0
- data/lib/license_finder/packages/merged_package.rb +2 -2
- data/lib/license_finder/reports/templates/html_report.erb +2 -2
- data/lib/license_finder/reports/templates/markdown_report.erb +2 -2
- data/lib/license_finder/scanner.rb +4 -2
- data/lib/license_finder/shared_helpers/common_path.rb +3 -1
- data/license_finder.gemspec +5 -6
- metadata +15 -14
- data/lib/license_finder/cli/blacklist.rb +0 -32
- data/lib/license_finder/cli/whitelist.rb +0 -32
@@ -8,9 +8,10 @@ module LicenseFinder
|
|
8
8
|
|
9
9
|
method_option :approve, type: :boolean, desc: 'Approve the added dependency'
|
10
10
|
method_option :homepage, type: :string, desc: 'Source of the added dependency'
|
11
|
+
|
11
12
|
auditable
|
12
|
-
desc 'add DEPENDENCY LICENSE
|
13
|
-
def add(name, license, version
|
13
|
+
desc 'add DEPENDENCY LICENSE VERSION [--homepage=HOMEPAGE] [--approve]', 'Add a dependency that is not managed by a package manager, optionally approving it at the same time'
|
14
|
+
def add(name, license, version)
|
14
15
|
modifying do
|
15
16
|
decisions
|
16
17
|
.add_package(name, version, txn)
|
@@ -107,7 +107,7 @@ module LicenseFinder
|
|
107
107
|
finder = LicenseAggregator.new(config, aggregate_paths)
|
108
108
|
any_packages = finder.any_packages?
|
109
109
|
unapproved = finder.unapproved
|
110
|
-
|
110
|
+
restricted = finder.restricted
|
111
111
|
|
112
112
|
# Ensure to start output on a new line even with dot progress indicators.
|
113
113
|
say "\n"
|
@@ -120,12 +120,12 @@ module LicenseFinder
|
|
120
120
|
if unapproved.empty?
|
121
121
|
say 'All dependencies are approved for use', :green
|
122
122
|
else
|
123
|
-
unless
|
124
|
-
say '
|
125
|
-
say report_of(
|
123
|
+
unless restricted.empty?
|
124
|
+
say 'Restricted dependencies:', :red
|
125
|
+
say report_of(restricted)
|
126
126
|
end
|
127
127
|
|
128
|
-
other_unapproved = unapproved -
|
128
|
+
other_unapproved = unapproved - restricted
|
129
129
|
unless other_unapproved.empty?
|
130
130
|
say 'Dependencies that need approval:', :yellow
|
131
131
|
say report_of(other_unapproved)
|
@@ -165,11 +165,11 @@ module LicenseFinder
|
|
165
165
|
|
166
166
|
subcommand 'dependencies', Dependencies, 'Add or remove dependencies that your package managers are not aware of'
|
167
167
|
subcommand 'licenses', Licenses, "Set a dependency's licenses, if the licenses found by license_finder are missing or wrong"
|
168
|
-
subcommand 'approvals', Approvals, 'Manually approve dependencies, even if their licenses are not
|
168
|
+
subcommand 'approvals', Approvals, 'Manually approve dependencies, even if their licenses are not permitted'
|
169
169
|
subcommand 'ignored_groups', IgnoredGroups, 'Exclude test and development dependencies from action items and reports'
|
170
170
|
subcommand 'ignored_dependencies', IgnoredDependencies, 'Exclude individual dependencies from action items and reports'
|
171
|
-
subcommand '
|
172
|
-
subcommand '
|
171
|
+
subcommand 'permitted_licenses', PermittedLicenses, 'Automatically approve any dependency that has a permitted license'
|
172
|
+
subcommand 'restricted_licenses', RestrictedLicenses, 'Forbid approval of any dependency whose licenses are all restricted'
|
173
173
|
subcommand 'project_name', ProjectName, 'Set the project name, for display in reports'
|
174
174
|
|
175
175
|
private
|
@@ -11,6 +11,9 @@ module LicenseFinder
|
|
11
11
|
def auditable
|
12
12
|
method_option :who, desc: 'The person making this decision'
|
13
13
|
method_option :why, desc: 'The reason for making this decision'
|
14
|
+
end
|
15
|
+
|
16
|
+
def approvable
|
14
17
|
method_option :version, desc: 'The version that will be approved'
|
15
18
|
end
|
16
19
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LicenseFinder
|
4
|
+
module CLI
|
5
|
+
class PermittedLicenses < Base
|
6
|
+
extend Subcommand
|
7
|
+
include MakesDecisions
|
8
|
+
|
9
|
+
desc 'list', 'List all the permitted licenses'
|
10
|
+
def list
|
11
|
+
say 'Permitted Licenses:', :blue
|
12
|
+
say_each(decisions.permitted, &:name)
|
13
|
+
end
|
14
|
+
|
15
|
+
auditable
|
16
|
+
desc 'add LICENSE...', 'Add one or more licenses to the permitted licenses'
|
17
|
+
def add(*licenses)
|
18
|
+
assert_some licenses
|
19
|
+
modifying { licenses.each { |l| decisions.permit(l, txn) } }
|
20
|
+
say "Added #{licenses.join(', ')} to the permitted licenses"
|
21
|
+
end
|
22
|
+
|
23
|
+
auditable
|
24
|
+
desc 'remove LICENSE...', 'Remove one or more licenses from the permitted licenses'
|
25
|
+
def remove(*licenses)
|
26
|
+
assert_some licenses
|
27
|
+
modifying { licenses.each { |l| decisions.unpermit(l, txn) } }
|
28
|
+
say "Removed #{licenses.join(', ')} from the license permitted licenses"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LicenseFinder
|
4
|
+
module CLI
|
5
|
+
class RestrictedLicenses < Base
|
6
|
+
extend Subcommand
|
7
|
+
include MakesDecisions
|
8
|
+
|
9
|
+
desc 'list', 'List all the restricted licenses'
|
10
|
+
def list
|
11
|
+
say 'Restricted Licenses:', :blue
|
12
|
+
say_each(decisions.restricted, &:name)
|
13
|
+
end
|
14
|
+
|
15
|
+
auditable
|
16
|
+
desc 'add LICENSE...', 'Add one or more licenses to the restricted licenses'
|
17
|
+
def add(*licenses)
|
18
|
+
assert_some licenses
|
19
|
+
modifying { licenses.each { |l| decisions.restrict(l, txn) } }
|
20
|
+
say "Added #{licenses.join(', ')} to the restricted licenses"
|
21
|
+
end
|
22
|
+
|
23
|
+
auditable
|
24
|
+
desc 'remove LICENSE...', 'Remove one or more licenses from the restricted licenses'
|
25
|
+
def remove(*licenses)
|
26
|
+
assert_some licenses
|
27
|
+
modifying { licenses.each { |l| decisions.unrestrict(l, txn) } }
|
28
|
+
say "Removed #{licenses.join(', ')} from the restricted licenses"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/license_finder/core.rb
CHANGED
@@ -42,7 +42,7 @@ module LicenseFinder
|
|
42
42
|
end
|
43
43
|
|
44
44
|
extend Forwardable
|
45
|
-
def_delegators :decision_applier, :acknowledged, :unapproved, :
|
45
|
+
def_delegators :decision_applier, :acknowledged, :unapproved, :restricted, :any_packages?
|
46
46
|
|
47
47
|
def project_name
|
48
48
|
decisions.project_name || config.project_path.basename.to_s
|
@@ -14,8 +14,8 @@ module LicenseFinder
|
|
14
14
|
acknowledged.reject(&:approved?)
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
acknowledged.select(&:
|
17
|
+
def restricted
|
18
|
+
acknowledged.select(&:restricted?)
|
19
19
|
end
|
20
20
|
|
21
21
|
def any_packages?
|
@@ -53,12 +53,12 @@ module LicenseFinder
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def with_approval(package)
|
56
|
-
if package.licenses.all? { |license| decisions.
|
57
|
-
package.
|
56
|
+
if package.licenses.all? { |license| decisions.restricted?(license) }
|
57
|
+
package.restricted!
|
58
58
|
elsif decisions.approved?(package.name, package.version)
|
59
59
|
package.approved_manually!(decisions.approval_of(package.name, package.version))
|
60
|
-
elsif package.licenses.any? { |license| decisions.
|
61
|
-
package.
|
60
|
+
elsif package.licenses.any? { |license| decisions.permitted?(license) }
|
61
|
+
package.permitted!
|
62
62
|
end
|
63
63
|
package
|
64
64
|
end
|
@@ -6,7 +6,7 @@ module LicenseFinder
|
|
6
6
|
# READ
|
7
7
|
######
|
8
8
|
|
9
|
-
attr_reader :packages, :
|
9
|
+
attr_reader :packages, :permitted, :restricted, :ignored, :ignored_groups, :project_name
|
10
10
|
|
11
11
|
def licenses_of(name)
|
12
12
|
@licenses[name]
|
@@ -36,12 +36,12 @@ module LicenseFinder
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def
|
40
|
-
@
|
39
|
+
def permitted?(lic)
|
40
|
+
@permitted.include?(lic)
|
41
41
|
end
|
42
42
|
|
43
|
-
def
|
44
|
-
@
|
43
|
+
def restricted?(lic)
|
44
|
+
@restricted.include?(lic)
|
45
45
|
end
|
46
46
|
|
47
47
|
def ignored?(name)
|
@@ -68,8 +68,8 @@ module LicenseFinder
|
|
68
68
|
@licenses = Hash.new { |h, k| h[k] = Set.new }
|
69
69
|
@homepages = {}
|
70
70
|
@approvals = {}
|
71
|
-
@
|
72
|
-
@
|
71
|
+
@permitted = Set.new
|
72
|
+
@restricted = Set.new
|
73
73
|
@ignored = Set.new
|
74
74
|
@ignored_groups = Set.new
|
75
75
|
end
|
@@ -120,27 +120,27 @@ module LicenseFinder
|
|
120
120
|
self
|
121
121
|
end
|
122
122
|
|
123
|
-
def
|
124
|
-
@decisions << [:
|
125
|
-
@
|
123
|
+
def permit(lic, txn = {})
|
124
|
+
@decisions << [:permit, lic, txn]
|
125
|
+
@permitted << License.find_by_name(lic)
|
126
126
|
self
|
127
127
|
end
|
128
128
|
|
129
|
-
def
|
130
|
-
@decisions << [:
|
131
|
-
@
|
129
|
+
def unpermit(lic, txn = {})
|
130
|
+
@decisions << [:unpermit, lic, txn]
|
131
|
+
@permitted.delete(License.find_by_name(lic))
|
132
132
|
self
|
133
133
|
end
|
134
134
|
|
135
|
-
def
|
136
|
-
@decisions << [:
|
137
|
-
@
|
135
|
+
def restrict(lic, txn = {})
|
136
|
+
@decisions << [:restrict, lic, txn]
|
137
|
+
@restricted << License.find_by_name(lic)
|
138
138
|
self
|
139
139
|
end
|
140
140
|
|
141
|
-
def
|
142
|
-
@decisions << [:
|
143
|
-
@
|
141
|
+
def unrestrict(lic, txn = {})
|
142
|
+
@decisions << [:unrestrict, lic, txn]
|
143
|
+
@restricted.delete(License.find_by_name(lic))
|
144
144
|
self
|
145
145
|
end
|
146
146
|
|
@@ -48,8 +48,8 @@ module LicenseFinder
|
|
48
48
|
@groups = options[:groups] || []
|
49
49
|
|
50
50
|
## APPROVAL
|
51
|
-
@
|
52
|
-
@
|
51
|
+
@permitted = false
|
52
|
+
@restricted = false
|
53
53
|
@manual_approval = nil
|
54
54
|
|
55
55
|
## LICENSING
|
@@ -78,26 +78,26 @@ module LicenseFinder
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def approved?
|
81
|
-
# Question: is `!
|
82
|
-
# DecisionApplier does not call `
|
83
|
-
# if a Package has been
|
84
|
-
(approved_manually? ||
|
81
|
+
# Question: is `!restricted?` redundant?
|
82
|
+
# DecisionApplier does not call `permitted!` or `approved_manually!`
|
83
|
+
# if a Package has been restricted.
|
84
|
+
(approved_manually? || permitted?) && !restricted?
|
85
85
|
end
|
86
86
|
|
87
|
-
def
|
88
|
-
@
|
87
|
+
def permitted!
|
88
|
+
@permitted = true
|
89
89
|
end
|
90
90
|
|
91
|
-
def
|
92
|
-
@
|
91
|
+
def permitted?
|
92
|
+
@permitted
|
93
93
|
end
|
94
94
|
|
95
|
-
def
|
96
|
-
@
|
95
|
+
def restricted!
|
96
|
+
@restricted = true
|
97
97
|
end
|
98
98
|
|
99
|
-
def
|
100
|
-
@
|
99
|
+
def restricted?
|
100
|
+
@restricted
|
101
101
|
end
|
102
102
|
|
103
103
|
attr_reader :manual_approval
|
@@ -125,7 +125,7 @@ module LicenseFinder
|
|
125
125
|
attr_reader :install_path # checked in tests, otherwise private
|
126
126
|
|
127
127
|
def licenses
|
128
|
-
@licenses ||= activations.map(&:license).to_set
|
128
|
+
@licenses ||= activations.map(&:license).sort_by(&:name).to_set
|
129
129
|
end
|
130
130
|
|
131
131
|
def activations
|
@@ -152,6 +152,7 @@ require 'license_finder/package_managers/bundler'
|
|
152
152
|
require 'license_finder/package_managers/npm'
|
153
153
|
require 'license_finder/package_managers/yarn'
|
154
154
|
require 'license_finder/package_managers/pip'
|
155
|
+
require 'license_finder/package_managers/pipenv'
|
155
156
|
require 'license_finder/package_managers/maven'
|
156
157
|
require 'license_finder/package_managers/mix'
|
157
158
|
require 'license_finder/package_managers/cocoa_pods'
|
@@ -24,7 +24,9 @@ module LicenseFinder
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def prepare_command
|
27
|
-
'
|
27
|
+
ignored_groups_argument = !ignored_groups.empty? ? "--without #{ignored_groups.to_a.join(' ')}" : ''
|
28
|
+
|
29
|
+
"bundle install #{ignored_groups_argument}".strip
|
28
30
|
end
|
29
31
|
|
30
32
|
def possible_package_paths
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
-
require 'net/http'
|
5
4
|
|
6
5
|
module LicenseFinder
|
7
6
|
class Pip < PackageManager
|
@@ -17,7 +16,7 @@ module LicenseFinder
|
|
17
16
|
PipPackage.new(
|
18
17
|
name,
|
19
18
|
version,
|
20
|
-
|
19
|
+
PyPI.definition(name, version),
|
21
20
|
logger: logger,
|
22
21
|
children: children,
|
23
22
|
install_path: Pathname(location).join(name)
|
@@ -57,24 +56,17 @@ module LicenseFinder
|
|
57
56
|
private
|
58
57
|
|
59
58
|
def pip_output
|
60
|
-
|
61
|
-
|
62
|
-
package.values_at('name', 'version', 'dependencies', 'location')
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def pypi_def(name, version)
|
67
|
-
response = pypi_request("https://pypi.org/pypi/#{name}/#{version}/json")
|
68
|
-
response.is_a?(Net::HTTPSuccess) ? JSON.parse(response.body).fetch('info', {}) : {}
|
69
|
-
end
|
59
|
+
command = "python#{@python_version == '2' ? '' : '3'} #{LicenseFinder::BIN_PATH.join('license_finder_pip.py')} #{detected_package_path}"
|
60
|
+
stdout, stderr, status = Cmd.run(command)
|
70
61
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
62
|
+
if status.success?
|
63
|
+
JSON(stdout).map do |package|
|
64
|
+
package.values_at('name', 'version', 'dependencies', 'location')
|
65
|
+
end
|
66
|
+
else
|
67
|
+
log_errors "LicenseFinder command '#{command}' failed:\n\t#{stderr}"
|
68
|
+
[]
|
69
|
+
end
|
78
70
|
end
|
79
71
|
end
|
80
72
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'license_finder/package_utils/pypi'
|
5
|
+
|
6
|
+
module LicenseFinder
|
7
|
+
class Pipenv < PackageManager
|
8
|
+
def initialize(options = {})
|
9
|
+
super
|
10
|
+
@lockfile = Pathname('Pipfile.lock')
|
11
|
+
end
|
12
|
+
|
13
|
+
def current_packages
|
14
|
+
@current_packages ||=
|
15
|
+
begin
|
16
|
+
packages = {}
|
17
|
+
each_dependency(groups: allowed_groups) do |name, data, group|
|
18
|
+
version = canonicalize(data['version'])
|
19
|
+
package = packages.fetch(key_for(name, version)) do |key|
|
20
|
+
packages[key] = build_package_for(name, version)
|
21
|
+
end
|
22
|
+
package.groups << group
|
23
|
+
end
|
24
|
+
packages.values
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def possible_package_paths
|
29
|
+
project_path ? [project_path.join(@lockfile)] : [@lockfile]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def each_dependency(groups: [])
|
35
|
+
dependencies = JSON.parse(IO.read(detected_package_path))
|
36
|
+
groups.each do |group|
|
37
|
+
dependencies[group].each do |name, data|
|
38
|
+
yield name, data, group
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def canonicalize(version)
|
44
|
+
version.sub(/^==/, '')
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_package_for(name, version)
|
48
|
+
PipPackage.new(name, version, PyPI.definition(name, version))
|
49
|
+
end
|
50
|
+
|
51
|
+
def key_for(name, version)
|
52
|
+
"#{name}-#{version}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def allowed_groups
|
56
|
+
%w[default develop] - ignored_groups.to_a
|
57
|
+
end
|
58
|
+
|
59
|
+
def ignored_groups
|
60
|
+
@ignored_groups || []
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|