action_policy 0.4.1 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +230 -172
  3. data/LICENSE.txt +1 -1
  4. data/README.md +7 -11
  5. data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +62 -0
  6. data/lib/.rbnext/2.7/action_policy/i18n.rb +56 -0
  7. data/lib/.rbnext/2.7/action_policy/policy/cache.rb +101 -0
  8. data/lib/.rbnext/2.7/action_policy/policy/pre_check.rb +162 -0
  9. data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +89 -0
  10. data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +124 -0
  11. data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +159 -0
  12. data/lib/.rbnext/3.0/action_policy/behaviour.rb +115 -0
  13. data/lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb +62 -0
  14. data/lib/.rbnext/3.0/action_policy/behaviours/scoping.rb +35 -0
  15. data/lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb +59 -0
  16. data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +72 -0
  17. data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +69 -0
  18. data/lib/.rbnext/3.0/action_policy/policy/authorization.rb +87 -0
  19. data/lib/.rbnext/3.0/action_policy/policy/cache.rb +101 -0
  20. data/lib/.rbnext/3.0/action_policy/policy/core.rb +161 -0
  21. data/lib/.rbnext/3.0/action_policy/policy/defaults.rb +31 -0
  22. data/lib/.rbnext/3.0/action_policy/policy/execution_result.rb +37 -0
  23. data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +162 -0
  24. data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +210 -0
  25. data/lib/.rbnext/3.0/action_policy/policy/scoping.rb +160 -0
  26. data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +89 -0
  27. data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +124 -0
  28. data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +159 -0
  29. data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +19 -0
  30. data/lib/action_policy.rb +7 -1
  31. data/lib/action_policy/behaviour.rb +22 -16
  32. data/lib/action_policy/behaviours/policy_for.rb +10 -3
  33. data/lib/action_policy/behaviours/scoping.rb +2 -1
  34. data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
  35. data/lib/action_policy/ext/module_namespace.rb +1 -6
  36. data/lib/action_policy/ext/policy_cache_key.rb +15 -33
  37. data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
  38. data/lib/action_policy/i18n.rb +1 -1
  39. data/lib/action_policy/lookup_chain.rb +41 -21
  40. data/lib/action_policy/policy/aliases.rb +7 -12
  41. data/lib/action_policy/policy/authorization.rb +14 -17
  42. data/lib/action_policy/policy/cache.rb +34 -18
  43. data/lib/action_policy/policy/core.rb +25 -12
  44. data/lib/action_policy/policy/defaults.rb +3 -9
  45. data/lib/action_policy/policy/execution_result.rb +3 -9
  46. data/lib/action_policy/policy/pre_check.rb +19 -58
  47. data/lib/action_policy/policy/reasons.rb +30 -20
  48. data/lib/action_policy/policy/scoping.rb +5 -6
  49. data/lib/action_policy/rails/controller.rb +6 -1
  50. data/lib/action_policy/rails/ext/active_record.rb +7 -0
  51. data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
  52. data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
  53. data/lib/action_policy/rspec/dsl.rb +3 -3
  54. data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
  55. data/lib/action_policy/testing.rb +1 -12
  56. data/lib/action_policy/utils/pretty_print.rb +21 -24
  57. data/lib/action_policy/utils/suggest_message.rb +1 -3
  58. data/lib/action_policy/version.rb +1 -1
  59. data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +1 -1
  60. data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
  61. data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
  62. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
  63. data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
  64. metadata +55 -119
  65. data/.gitattributes +0 -2
  66. data/.github/FUNDING.yml +0 -1
  67. data/.github/ISSUE_TEMPLATE.md +0 -18
  68. data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
  69. data/.gitignore +0 -15
  70. data/.rubocop.yml +0 -54
  71. data/.tidelift.yml +0 -6
  72. data/.travis.yml +0 -31
  73. data/Gemfile +0 -22
  74. data/Rakefile +0 -27
  75. data/action_policy.gemspec +0 -44
  76. data/benchmarks/namespaced_lookup_cache.rb +0 -71
  77. data/bin/console +0 -14
  78. data/bin/setup +0 -8
  79. data/docs/.nojekyll +0 -0
  80. data/docs/CNAME +0 -1
  81. data/docs/README.md +0 -77
  82. data/docs/_sidebar.md +0 -27
  83. data/docs/aliases.md +0 -122
  84. data/docs/assets/docsify-search.js +0 -364
  85. data/docs/assets/docsify.min.js +0 -3
  86. data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
  87. data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
  88. data/docs/assets/images/banner.png +0 -0
  89. data/docs/assets/images/cache.png +0 -0
  90. data/docs/assets/images/cache.svg +0 -70
  91. data/docs/assets/images/layer.png +0 -0
  92. data/docs/assets/images/layer.svg +0 -35
  93. data/docs/assets/prism-ruby.min.js +0 -1
  94. data/docs/assets/styles.css +0 -347
  95. data/docs/assets/vue.min.css +0 -1
  96. data/docs/authorization_context.md +0 -92
  97. data/docs/behaviour.md +0 -113
  98. data/docs/caching.md +0 -273
  99. data/docs/controller_action_aliases.md +0 -109
  100. data/docs/custom_lookup_chain.md +0 -48
  101. data/docs/custom_policy.md +0 -53
  102. data/docs/debugging.md +0 -55
  103. data/docs/decorators.md +0 -27
  104. data/docs/favicon.ico +0 -0
  105. data/docs/graphql.md +0 -302
  106. data/docs/i18n.md +0 -44
  107. data/docs/index.html +0 -43
  108. data/docs/instrumentation.md +0 -84
  109. data/docs/lookup_chain.md +0 -17
  110. data/docs/namespaces.md +0 -77
  111. data/docs/non_rails.md +0 -28
  112. data/docs/pre_checks.md +0 -57
  113. data/docs/pundit_migration.md +0 -80
  114. data/docs/quick_start.md +0 -118
  115. data/docs/rails.md +0 -120
  116. data/docs/reasons.md +0 -120
  117. data/docs/scoping.md +0 -255
  118. data/docs/testing.md +0 -333
  119. data/docs/writing_policies.md +0 -107
  120. data/gemfiles/jruby.gemfile +0 -8
  121. data/gemfiles/rails42.gemfile +0 -8
  122. data/gemfiles/rails6.gemfile +0 -8
  123. data/gemfiles/railsmaster.gemfile +0 -6
  124. data/lib/action_policy/ext/string_match.rb +0 -14
  125. data/lib/action_policy/ext/yield_self_then.rb +0 -25
@@ -1,333 +0,0 @@
1
- # Testing
2
-
3
- Authorization is one of the crucial parts of your application. Hence, it should be thoroughly tested (that is the place where 100% coverage makes sense).
4
-
5
- When you use policies for authorization, it is possible to split testing into two parts:
6
- - Test the policy class itself
7
- - Test that **the required authorization is performed** within your authorization layer (controller, channel, etc.)
8
- - Test that **the required scoping has been applied**.
9
-
10
- ## Testing policies
11
-
12
- You can test policies as plain-old Ruby classes, no special tooling is required.
13
-
14
- Consider an RSpec example:
15
-
16
- ```ruby
17
- describe PostPolicy do
18
- let(:user) { build_stubbed(:user) }
19
- let(:post) { build_stubbed(:post) }
20
-
21
- let(:policy) { described_class.new(post, user: user) }
22
-
23
- describe "#update?" do
24
- subject { policy.apply(:update?) }
25
-
26
- it "returns false when the user is not admin nor author" do
27
- is_expected.to eq false
28
- end
29
-
30
- context "when the user is admin" do
31
- let(:user) { build_stubbed(:user, :admin) }
32
-
33
- it { is_expected.to eq true }
34
- end
35
-
36
- context "when the user is an author" do
37
- let(:post) { build_stubbed(:post, user: user) }
38
-
39
- it { is_expected.to eq true }
40
- end
41
- end
42
- end
43
- ```
44
-
45
- ### RSpec DSL
46
-
47
- We also provide a simple RSpec DSL which aims to reduce the boilerplate when writing
48
- policies specs.
49
-
50
- Example:
51
-
52
- ```ruby
53
- # Add this to your spec_helper.rb / rails_helper.rb
54
- require "action_policy/rspec/dsl"
55
-
56
- describe PostPolicy do
57
- let(:user) { build_stubbed :user }
58
- # `record` must be defined – it is the authorization target
59
- let(:record) { build_stubbed :post, draft: false }
60
-
61
- # `context` is the authorization context
62
- let(:context) { {user: user} }
63
-
64
- # `describe_rule` is a combination of
65
- # `describe` and `subject { ... }` (returns the result of
66
- # applying the rule to the record)
67
- describe_rule :show? do
68
- # `succeed` is `context` + `specify`, which checks
69
- # that the result of application is successful
70
- succeed "when post is published"
71
-
72
- # `succeed` is `context` + `specify`, which checks
73
- # that the result of application wasn't successful
74
- failed "when post is draft" do
75
- before { post.draft = false }
76
-
77
- succeed "when user is a manager" do
78
- before { user.role = "manager" }
79
- end
80
- end
81
- end
82
- end
83
- ```
84
-
85
- If test failed the exception message includes the result and [failure reasons](reasons) (if any):
86
-
87
- ```
88
- 1) PostPolicy#show? when post is draft
89
- Failure/Error: ...
90
-
91
- Expected to fail but succeed:
92
- <PostPolicy#show?: true (reasons: ...)>
93
- ```
94
-
95
- If you have [debugging utils](debugging) installed the message also includes the _annotated_
96
- source code of the policy rule:
97
-
98
- ```
99
- 1) UserPolicy#manage? when post is draft
100
- Failure/Error: ...
101
-
102
- Expected to fail but succeed:
103
- <PostPolicy#show?: true (reasons: ...)>
104
- ↳ user.admin? #=> true
105
- OR
106
- !record.draft? #=> false
107
- ```
108
-
109
- **NOTE:** DSL for focusing or skipping examples and groups is also available (e.g. `xdescribe_rule`, `fsucceed`, etc.).
110
-
111
- **NOTE:** the DSL is included only to example with the tag `type: :policy` or in the `spec/policies` folder. If you want to add this DSL to other examples, add `include ActionPolicy::RSpec::PolicyExampleGroup`.
112
-
113
- ### Testing scopes
114
-
115
- There is no single rule on how to test scopes, 'cause it dependes on the _nature_ of the scope.
116
-
117
- Here's an example of RSpec tests for Active Record scoping rules:
118
-
119
- ```ruby
120
- describe PostPolicy do
121
- describe "relation scope" do
122
- let(:user) { build_stubbed :user }
123
- let(:context) { {user: user} }
124
-
125
- # Feel free to replace with `before_all` from `test-prof`:
126
- # https://test-prof.evilmartians.io/#/before_all
127
- before do
128
- create(:post, name: "A")
129
- create(:post, name: "B", draft: true)
130
- end
131
-
132
- let(:target) do
133
- # We want to make sure that only the records created
134
- # for this test are affected, and they have a deterministic order
135
- Post.where(name: %w[A B]).order(name: :asc)
136
- end
137
-
138
- subject { policy.apply_scope(target, type: :active_record_relation).pluck(:name) }
139
-
140
- context "as user" do
141
- it { is_expected.to eq(%w[A]) }
142
- end
143
-
144
- context "as manager" do
145
- before { user.update!(role: :manager) }
146
-
147
- it { is_expected.to eq(%w[A B]) }
148
- end
149
-
150
- context "as banned user" do
151
- before { user.update!(banned: true) }
152
-
153
- it { is_expected.to be_empty }
154
- end
155
- end
156
- end
157
- ```
158
-
159
- ## Testing authorization
160
-
161
- To test the act of authorization you have to make sure that the `authorize!` method is called with the appropriate arguments.
162
-
163
- Action Policy provides tools for such kind of testing for Minitest and RSpec.
164
-
165
- ### Minitest
166
-
167
- Include `ActionPolicy::TestHelper` to your test class and you'll be able to use
168
- `assert_authorized_to` assertion:
169
-
170
- ```ruby
171
- # in your controller
172
- class PostsController < ApplicationController
173
- def update
174
- @post = Post.find(params[:id])
175
- authorize! @post
176
- if @post.update(post_params)
177
- redirect_to @post
178
- else
179
- render :edit
180
- end
181
- end
182
- end
183
-
184
- # in your test
185
- require "action_policy/test_helper"
186
-
187
- class PostsControllerTest < ActionDispatch::IntegrationTest
188
- include ActionPolicy::TestHelper
189
-
190
- test "update is authorized" do
191
- sign_in users(:john)
192
-
193
- post = posts(:example)
194
-
195
- assert_authorized_to(:update?, post, with: PostPolicy) do
196
- patch :update, id: post.id, name: "Bob"
197
- end
198
- end
199
- end
200
- ```
201
-
202
- You can omit the policy (then it would be inferred from the target):
203
-
204
- ```ruby
205
- assert_authorized_to(:update?, post) do
206
- patch :update, id: post.id, name: "Bob"
207
- end
208
- ```
209
-
210
- ### RSpec
211
-
212
- Add the following to your `rails_helper.rb` (or `spec_helper.rb`):
213
-
214
- ```ruby
215
- require "action_policy/rspec"
216
- ```
217
-
218
- Now you can use `be_authorized_to` matcher:
219
-
220
- ```ruby
221
- describe PostsController do
222
- subject { patch :update, id: post.id, params: params }
223
-
224
- it "is authorized" do
225
- expect { subject }.to be_authorized_to(:update?, post)
226
- .with(PostPolicy)
227
- end
228
- end
229
- ```
230
-
231
- If you omit `.with(PostPolicy)` then the inferred policy for the target (`post`) would be used.
232
-
233
- RSpec composed matchers are available as target:
234
-
235
- ```ruby
236
- expect { subject }.to be_authorized_to(:show?, an_instance_of(Post))
237
- ```
238
-
239
- ## Testing scoping
240
-
241
- Action Policy provides a way to test that a correct scoping has been applied during the code execution.
242
-
243
- For example, you can test that in your `#index` action the correct scoping is used:
244
-
245
- ```ruby
246
- class UsersController < ApplicationController
247
- def index
248
- @user = authorized(User.all)
249
- end
250
- end
251
- ```
252
-
253
- ### Minitest
254
-
255
- Include `ActionPolicy::TestHelper` to your test class and you'll be able to use
256
- `assert_have_authorized_scope` assertion:
257
-
258
- ```ruby
259
- # in your test
260
- require "action_policy/test_helper"
261
-
262
- class UsersControllerTest < ActionDispatch::IntegrationTest
263
- include ActionPolicy::TestHelper
264
-
265
- test "index has authorized scope" do
266
- sign_in users(:john)
267
-
268
- assert_have_authorized_scope(type: :active_record_relation, with: UserPolicy) do
269
- get :index
270
- end
271
- end
272
- end
273
- ```
274
-
275
- You can also specify `as` and `scope_options` options.
276
-
277
- **NOTE:** both `type` and `with` params are required.
278
-
279
- It's not possible to test that a scoped has been applied to a particular _target_ but we provide
280
- a way to perform additional assertions against the matching target (if the assertion didn't fail):
281
-
282
- ```ruby
283
- test "index has authorized scope" do
284
- sign_in users(:john)
285
-
286
- assert_have_authorized_scope(type: :active_record_relation, with: UserPolicy) do
287
- get :index
288
- end.with_target do |target|
289
- # target is a object passed to `authorized` call
290
- assert_equal User.all, target
291
- end
292
- end
293
- ```
294
-
295
- ### RSpec
296
-
297
- Add the following to your `rails_helper.rb` (or `spec_helper.rb`):
298
-
299
- ```ruby
300
- require "action_policy/rspec"
301
- ```
302
-
303
- Now you can use `have_authorized_scope` matcher:
304
-
305
- ```ruby
306
- describe UsersController do
307
- subject { get :index }
308
-
309
- it "has authorized scope" do
310
- expect { subject }.to have_authorized_scope(:active_record_relation)
311
- .with(PostPolicy)
312
- end
313
- end
314
- ```
315
-
316
- You can also add `.as(:named_scope)` and `with_scope_options(options_hash)` options.
317
-
318
- RSpec composed matchers are available as scope options:
319
-
320
- ```ruby
321
- expect { subject }.to have_authorized_scope(:scope)
322
- .with_scope_options(matching(with_deleted: a_falsey_value))
323
- ```
324
-
325
- You can use the `with_target` modifier to run additional expectations against the matching target (if the matcher didn't fail):
326
-
327
- ```ruby
328
- expect { subject }.to have_authorized_scope(:scope)
329
- .with_scope_options(matching(with_deleted: a_falsey_value))
330
- .with_target { |target|
331
- expect(target).to eq(User.all)
332
- }
333
- ```
@@ -1,107 +0,0 @@
1
- # Writing Policies
2
-
3
- Policy class contains predicate methods (_rules_) which are used to authorize activities.
4
-
5
- A Policy is instantiated with the target `record` (authorization object) and the [authorization context](authorization_context.md) (by default equals to `user`):
6
-
7
- ```ruby
8
- class PostPolicy < ActionPolicy::Base
9
- def index?
10
- # allow everyone to perform "index" activity on posts
11
- true
12
- end
13
-
14
- def update?
15
- # here we can access our context and record
16
- user.admin? || (user.id == record.user_id)
17
- end
18
- end
19
- ```
20
-
21
- ## Initializing policies
22
-
23
- **NOTE**: it is not recommended to manually initialize policy objects and use them directly (one exclusion–[tests](testing.md)). Use [`authorize!` / `allowed_to?` methods](./behaviour.md#authorize) instead.
24
-
25
- To initialize policy object, you should specify target record and context:
26
-
27
- ```ruby
28
- policy = PostPolicy.new(post, user: user)
29
-
30
- # simply call rule method
31
- policy.update?
32
- ```
33
-
34
- You can omit the first argument (in that case `record` would be `nil`).
35
-
36
- Instead of calling rules directly, it is better to call the `apply` method (which wraps rule method with some useful functionality, such as [caching](caching.md), [pre-checks](pre_checks.md), and [failure reasons tracking](reasons.md)):
37
-
38
- ```ruby
39
- policy.apply(:update?)
40
- ```
41
-
42
- ## Calling other policies
43
-
44
- Sometimes it is useful to call other resources policies from within a policy. Action Policy provides the `allowed_to?` method as a part of `ActionPolicy::Base`:
45
-
46
- ```ruby
47
- class CommentPolicy < ApplicationPolicy
48
- def update?
49
- user.admin? || (user.id == record.id) ||
50
- allowed_to?(:update?, record.post)
51
- end
52
- end
53
- ```
54
-
55
- You can also specify all the usual options (such as `with`).
56
-
57
- There is also a `check?` method which is just an "alias"\* for `allowed_to?` added for better readability:
58
-
59
- ```ruby
60
- class PostPolicy < ApplicationPolicy
61
- def show?
62
- user.admin? || check?(:publicly_visible?)
63
- end
64
-
65
- def publicly_visible?
66
- # ...
67
- end
68
- end
69
- ```
70
-
71
- \* It's not a Ruby _alias_ but a wrapper; we can't use `alias` or `alias_method`, 'cause `allowed_to?` could be extended by some extensions.
72
-
73
- ## Identifiers
74
-
75
- Each policy class has an `identifier`, which is by default just an underscored class name:
76
-
77
- ```ruby
78
- class CommentPolicy < ApplicationPolicy
79
- end
80
-
81
- CommentPolicy.identifier #=> :comment
82
- ```
83
-
84
- For namespaced policies it has a form of:
85
-
86
- ```ruby
87
- module ActiveAdmin
88
- class UserPolicy < ApplicationPolicy
89
- end
90
- end
91
-
92
- ActiveAdmin::UserPolicy.identifier # => :"active_admin/user"
93
- ```
94
-
95
- You can specify your own identifier:
96
-
97
- ```ruby
98
- module MyVeryLong
99
- class LongLongNamePolicy < ApplicationPolicy
100
- self.identifier = :long_name
101
- end
102
- end
103
-
104
- MyVeryLong::LongLongNamePolicy.identifier #=> :long_name
105
- ```
106
-
107
- Identifiers are required for some modules, such as [failure reasons tracking](reasons.md) and [i18n](i18n.md).
@@ -1,8 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "activerecord-jdbcsqlite3-adapter", "~> 52.0"
4
- gem "jdbc-sqlite3"
5
-
6
- gem "rails", "~> 5.0"
7
-
8
- gemspec path: ".."
@@ -1,8 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "sqlite3", "~> 1.3.0"
4
- gem "rails", "~> 4.2"
5
- gem "method_source"
6
- gem "unparser"
7
-
8
- gemspec path: ".."