package_protections 3.2.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +0 -20
- data/lib/package_protections/private/configuration.rb +0 -1
- data/lib/package_protections/private.rb +0 -1
- data/lib/package_protections/protected_package.rb +0 -5
- data/lib/package_protections/rspec/application_fixture_helper.rb +1 -7
- metadata +2 -3
- data/lib/package_protections/private/visibility_protection.rb +0 -186
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7dcf5c2038b67566fabbec1bebb7f7e793552f912613449f88d5fa244959200
|
4
|
+
data.tar.gz: d067b4f72f24f52a8b37bb36da54ad0d3fb52549529b012a819ecd796231fedd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 406251f9227b31309c2d991ea9cee1119ccc581724877b3e8ff2b9b76a0dfa71147ddc900bedcbc0cf7dfc52857a180500f6bb15f6c418dd9801ebc29f9e14b7
|
7
|
+
data.tar.gz: 2cb074c305a57e514ca3f923b08bdeacb7a156e404bed441c4b169ad7b92cf86dc7f5d5ac76b0e47d97cb4ae26eba87699e06694660fabc824092ddeee08ec61
|
data/README.md
CHANGED
@@ -10,7 +10,6 @@ This gem ships with the following checks
|
|
10
10
|
2) Other packages are not using the private API of your package (via `packwerk` `enforce_privacy`)
|
11
11
|
3) Your package has a typed public API (via the `rubocop` `PackageProtections/TypedPublicApi` cop)
|
12
12
|
4) Your package only creates a single namespace (via the `rubocop` `PackageProtections/NamespacedUnderPackageName` cop)
|
13
|
-
4) Your package is only visible to a select number of packages (via the `packwerk` `enforce_privacy` cop)
|
14
13
|
|
15
14
|
## Initial Configuration
|
16
15
|
Package protections first requires that your application is using [`packwerk`](https://github.com/Shopify/packwerk), [`rubocop`](https://github.com/rubocop/rubocop), and [`rubocop-sorbet`](https://github.com/Shopify/rubocop-sorbet). Follow the regular setup instructions for those tools before proceeding.
|
@@ -63,25 +62,6 @@ end
|
|
63
62
|
|
64
63
|
If you've worked through all of the TODOs for this cop and are able to set the value to `fail_on_any`, you can also set `automatic_pack_namespace` which will support your pack having one global namespace without extra subdirectories. That is, instead of `packs/foo/app/services/foo/bar.rb`, you can use `packs/foo/app/services/bar.rb` and still have it define `Foo::Bar`. [See the `stimpack` README.md](https://github.com/rubyatscale/stimpack#readme) for more information.
|
65
64
|
|
66
|
-
### `prevent_other_packages_from_using_this_package_without_explicit_visibility`
|
67
|
-
*This is only available if your package has `enforce_privacy` set to `true`!*
|
68
|
-
This protection exists to help packages have control over who their clients are. When turning on this protection, only clients who are listed in your `visible_to` metadata will be allowed to consume your package. Here is an example in `packs/apples/package.yml`:
|
69
|
-
```yml
|
70
|
-
enforce_privacy: true
|
71
|
-
enforce_dependencies: true
|
72
|
-
metadata:
|
73
|
-
protections:
|
74
|
-
prevent_other_packages_from_using_this_package_without_explicit_visibility: fail_on_new
|
75
|
-
# ... other protections are the same
|
76
|
-
visible_to:
|
77
|
-
- packs/other_pack
|
78
|
-
- packs/another_pack
|
79
|
-
```
|
80
|
-
In this package, only `packs/other_pack` and `packs/another_pack` can use `packs/apples`. With both the `fail_on_new` and `fail_on_any` setting, only those packs can state a dependency on `packs/apples` in their `package.yml`. If any other packs state a dependency on `packs/apples`, the build will fail, even with violations. With the `fail_on_new` setting, a pack can create a dependency or privacy violation on `packs/apples` even if it's not listed. With `fail_on_any`, no violations are allowed.
|
81
|
-
If `visible_to` is not set and the protection is turned on, then the package cannot be consumed by any package (a top-level package might be a good candidate for this).
|
82
|
-
|
83
|
-
Note that this protection's default behavior is `fail_never`, so it can remain unset in the `package.yml`.
|
84
|
-
|
85
65
|
## Violation Behaviors
|
86
66
|
#### `fail_on_any`
|
87
67
|
If this behavior is selected, the build will fail if there is *any* issue, new or old.
|
@@ -34,7 +34,6 @@ module PackageProtections
|
|
34
34
|
Private::IncomingPrivacyProtection.new,
|
35
35
|
RuboCop::Cop::PackageProtections::TypedPublicApi.new,
|
36
36
|
RuboCop::Cop::PackageProtections::NamespacedUnderPackageName.new,
|
37
|
-
Private::VisibilityProtection.new,
|
38
37
|
RuboCop::Cop::PackageProtections::OnlyClassMethods.new,
|
39
38
|
RuboCop::Cop::PackageProtections::RequireDocumentedPublicApis.new
|
40
39
|
]
|
@@ -6,7 +6,6 @@ require 'package_protections/private/output'
|
|
6
6
|
require 'package_protections/private/incoming_privacy_protection'
|
7
7
|
require 'package_protections/private/outgoing_dependency_protection'
|
8
8
|
require 'package_protections/private/metadata_modifiers'
|
9
|
-
require 'package_protections/private/visibility_protection'
|
10
9
|
require 'package_protections/private/configuration'
|
11
10
|
|
12
11
|
module PackageProtections
|
@@ -66,11 +66,6 @@ module PackageProtections
|
|
66
66
|
original_package.dependencies
|
67
67
|
end
|
68
68
|
|
69
|
-
sig { returns(T::Set[String]) }
|
70
|
-
def visible_to
|
71
|
-
Set.new(metadata['visible_to'] || [])
|
72
|
-
end
|
73
|
-
|
74
69
|
sig { returns(T::Array[ParsePackwerk::Violation]) }
|
75
70
|
def violations
|
76
71
|
deprecated_references.violations
|
@@ -16,23 +16,17 @@ module ApplicationFixtureHelper
|
|
16
16
|
dependencies: [],
|
17
17
|
enforce_dependencies: true,
|
18
18
|
enforce_privacy: true,
|
19
|
-
protections: {}
|
20
|
-
visible_to: []
|
19
|
+
protections: {}
|
21
20
|
)
|
22
21
|
defaults = {
|
23
22
|
'prevent_this_package_from_violating_its_stated_dependencies' => 'fail_on_new',
|
24
23
|
'prevent_other_packages_from_using_this_packages_internals' => 'fail_on_new',
|
25
24
|
'prevent_this_package_from_exposing_an_untyped_api' => 'fail_on_new',
|
26
25
|
'prevent_this_package_from_creating_other_namespaces' => 'fail_on_new',
|
27
|
-
'prevent_other_packages_from_using_this_package_without_explicit_visibility' => 'fail_never',
|
28
26
|
'prevent_this_package_from_exposing_instance_method_public_apis' => 'fail_never'
|
29
27
|
}
|
30
28
|
protections_with_defaults = defaults.merge(protections)
|
31
29
|
metadata = { 'protections' => protections_with_defaults }
|
32
|
-
if visible_to.any?
|
33
|
-
metadata.merge!('visible_to' => visible_to)
|
34
|
-
end
|
35
|
-
|
36
30
|
package = ParsePackwerk::Package.new(
|
37
31
|
name: pack_name,
|
38
32
|
dependencies: dependencies,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: package_protections
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -197,7 +197,6 @@ files:
|
|
197
197
|
- lib/package_protections/private/metadata_modifiers.rb
|
198
198
|
- lib/package_protections/private/outgoing_dependency_protection.rb
|
199
199
|
- lib/package_protections/private/output.rb
|
200
|
-
- lib/package_protections/private/visibility_protection.rb
|
201
200
|
- lib/package_protections/protected_package.rb
|
202
201
|
- lib/package_protections/protection_interface.rb
|
203
202
|
- lib/package_protections/rspec/application_fixture_helper.rb
|
@@ -1,186 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module PackageProtections
|
5
|
-
module Private
|
6
|
-
class VisibilityProtection
|
7
|
-
extend T::Sig
|
8
|
-
|
9
|
-
include ProtectionInterface
|
10
|
-
|
11
|
-
IDENTIFIER = 'prevent_other_packages_from_using_this_package_without_explicit_visibility'
|
12
|
-
|
13
|
-
sig { override.returns(String) }
|
14
|
-
def identifier
|
15
|
-
IDENTIFIER
|
16
|
-
end
|
17
|
-
|
18
|
-
sig { override.params(behavior: ViolationBehavior, package: ParsePackwerk::Package).returns(T.nilable(String)) }
|
19
|
-
def unmet_preconditions_for_behavior(behavior, package)
|
20
|
-
# This protection relies on seeing privacy violations in other packages.
|
21
|
-
# We also require that the other package enforces dependencies, as otherwise, if the client is using public API, it won't show up
|
22
|
-
# as a privacy OR dependency violation. For now, we don't have the system structure to support that requirement.
|
23
|
-
if behavior.enabled? && !package.enforces_privacy?
|
24
|
-
"Package #{package.name} must have `enforce_privacy: true` to use this protection"
|
25
|
-
elsif !behavior.enabled? && !package.metadata['visible_to'].nil?
|
26
|
-
"Invalid configuration for package `#{package.name}`. `#{identifier}` must be turned on to use `visible_to` configuration."
|
27
|
-
else
|
28
|
-
nil
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
#
|
33
|
-
# By default, this protection does not show up when creating a new package, and its default behavior is FailNever
|
34
|
-
# A package that uses this protection is not considered strictly better -- in general, we want to design packages that
|
35
|
-
# are consumable by all packages. Therefore, a package that is consumable by all packages is the happy path.
|
36
|
-
#
|
37
|
-
# If a user wants to turn on package visibility, they must do it explicitly.
|
38
|
-
#
|
39
|
-
sig { returns(ViolationBehavior) }
|
40
|
-
def default_behavior
|
41
|
-
ViolationBehavior::FailNever
|
42
|
-
end
|
43
|
-
|
44
|
-
sig { override.returns(String) }
|
45
|
-
def humanized_protection_name
|
46
|
-
'Visibility Violations'
|
47
|
-
end
|
48
|
-
|
49
|
-
sig { override.returns(String) }
|
50
|
-
def humanized_protection_description
|
51
|
-
<<~MESSAGE
|
52
|
-
These files are using a constant from a package that restricts its usage through the `visible_to` flag in its `package.yml`
|
53
|
-
To resolve these violations, work with the team who owns the package you are trying to use and to figure out the
|
54
|
-
preferred public API for the behavior you want.
|
55
|
-
|
56
|
-
See https://go/packwerk_cheatsheet_visibility for more info.
|
57
|
-
MESSAGE
|
58
|
-
end
|
59
|
-
|
60
|
-
sig do
|
61
|
-
override.params(
|
62
|
-
new_violations: T::Array[PerFileViolation]
|
63
|
-
).returns(T::Array[Offense])
|
64
|
-
end
|
65
|
-
def get_offenses_for_new_violations(new_violations)
|
66
|
-
new_violations.flat_map do |per_file_violation|
|
67
|
-
depended_on_package = Private.get_package_with_name(per_file_violation.constant_source_package)
|
68
|
-
violation_behavior = depended_on_package.violation_behavior_for(identifier)
|
69
|
-
visible_to = depended_on_package.visible_to
|
70
|
-
next [] if visible_to.include?(per_file_violation.reference_source_package.name)
|
71
|
-
|
72
|
-
case violation_behavior
|
73
|
-
when ViolationBehavior::FailNever
|
74
|
-
next []
|
75
|
-
when ViolationBehavior::FailOnNew
|
76
|
-
message = message_for_fail_on_new(per_file_violation)
|
77
|
-
when ViolationBehavior::FailOnAny
|
78
|
-
message = message_for_fail_on_any(per_file_violation)
|
79
|
-
else
|
80
|
-
T.absurd(violation_behavior)
|
81
|
-
end
|
82
|
-
|
83
|
-
Offense.new(
|
84
|
-
file: per_file_violation.filepath,
|
85
|
-
message: message,
|
86
|
-
violation_type: identifier,
|
87
|
-
package: depended_on_package.original_package
|
88
|
-
)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
sig do
|
93
|
-
override.params(
|
94
|
-
protected_packages: T::Array[ProtectedPackage]
|
95
|
-
).returns(T::Array[Offense])
|
96
|
-
end
|
97
|
-
def get_offenses_for_existing_violations(protected_packages)
|
98
|
-
all_offenses = T.let([], T::Array[Offense])
|
99
|
-
|
100
|
-
all_listed_violations = protected_packages.flat_map do |protected_package|
|
101
|
-
protected_package.violations.flat_map do |violation|
|
102
|
-
PerFileViolation.from(violation, protected_package.original_package)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
#
|
107
|
-
# First we get offenses related to violations between packages, looking at all dependency and privacy
|
108
|
-
# violations between packages.
|
109
|
-
#
|
110
|
-
|
111
|
-
# We only care about looking at each edge once. Since an edge can show up twice if its both a privacy and dependency violation,
|
112
|
-
# we only look at each combination of class from package (A) that's referenced in package (B)
|
113
|
-
unique_per_file_violations = all_listed_violations.uniq do |per_file_violation|
|
114
|
-
[per_file_violation.reference_source_package.name, per_file_violation.constant_source_package, per_file_violation.class_name]
|
115
|
-
end
|
116
|
-
|
117
|
-
all_offenses += unique_per_file_violations.flat_map do |per_file_violation|
|
118
|
-
depended_on_package = Private.get_package_with_name(per_file_violation.constant_source_package)
|
119
|
-
violation_behavior = depended_on_package.violation_behavior_for(identifier)
|
120
|
-
visible_to = depended_on_package.visible_to
|
121
|
-
next [] if visible_to.include?(per_file_violation.reference_source_package.name)
|
122
|
-
|
123
|
-
case violation_behavior
|
124
|
-
when ViolationBehavior::FailNever, ViolationBehavior::FailOnNew
|
125
|
-
next []
|
126
|
-
when ViolationBehavior::FailOnAny
|
127
|
-
message = message_for_fail_on_any(per_file_violation)
|
128
|
-
else
|
129
|
-
T.absurd(violation_behavior)
|
130
|
-
end
|
131
|
-
|
132
|
-
Offense.new(
|
133
|
-
file: per_file_violation.filepath,
|
134
|
-
message: message,
|
135
|
-
violation_type: identifier,
|
136
|
-
package: depended_on_package.original_package
|
137
|
-
)
|
138
|
-
end
|
139
|
-
|
140
|
-
#
|
141
|
-
# Then we get offenses from stated dependencies
|
142
|
-
#
|
143
|
-
all_offenses += protected_packages.flat_map do |protected_package|
|
144
|
-
protected_package.dependencies.flat_map do |package_dependency_name|
|
145
|
-
depended_on_package = Private.get_package_with_name(package_dependency_name)
|
146
|
-
visible_to = depended_on_package.visible_to
|
147
|
-
next [] if visible_to.include?(protected_package.name)
|
148
|
-
|
149
|
-
violation_behavior = depended_on_package.violation_behavior_for(identifier)
|
150
|
-
|
151
|
-
case violation_behavior
|
152
|
-
when ViolationBehavior::FailNever
|
153
|
-
next []
|
154
|
-
when ViolationBehavior::FailOnAny, ViolationBehavior::FailOnNew
|
155
|
-
# continue
|
156
|
-
else
|
157
|
-
T.absurd(violation_behavior)
|
158
|
-
end
|
159
|
-
|
160
|
-
message = "`#{protected_package.name}` cannot state a dependency on `#{depended_on_package.name}`, as it violates package visibility in `#{depended_on_package.yml}`"
|
161
|
-
Offense.new(
|
162
|
-
file: protected_package.yml.to_s,
|
163
|
-
message: message,
|
164
|
-
violation_type: identifier,
|
165
|
-
package: depended_on_package.original_package
|
166
|
-
)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
all_offenses
|
171
|
-
end
|
172
|
-
|
173
|
-
private
|
174
|
-
|
175
|
-
sig { params(per_file_violation: PerFileViolation).returns(String) }
|
176
|
-
def message_for_fail_on_any(per_file_violation)
|
177
|
-
"#{message_for_fail_on_new(per_file_violation)} (`#{per_file_violation.constant_source_package}` set to `fail_on_any`)"
|
178
|
-
end
|
179
|
-
|
180
|
-
sig { params(per_file_violation: PerFileViolation).returns(String) }
|
181
|
-
def message_for_fail_on_new(per_file_violation)
|
182
|
-
"`#{per_file_violation.filepath}` references non-visible `#{per_file_violation.class_name}` from `#{per_file_violation.constant_source_package}`"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|