action_policy 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +203 -174
  3. data/README.md +5 -4
  4. data/lib/action_policy.rb +7 -1
  5. data/lib/action_policy/behaviour.rb +22 -16
  6. data/lib/action_policy/behaviours/policy_for.rb +10 -3
  7. data/lib/action_policy/behaviours/scoping.rb +2 -1
  8. data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
  9. data/lib/action_policy/ext/module_namespace.rb +1 -6
  10. data/lib/action_policy/ext/policy_cache_key.rb +10 -30
  11. data/lib/action_policy/i18n.rb +1 -1
  12. data/lib/action_policy/lookup_chain.rb +29 -15
  13. data/lib/action_policy/policy/aliases.rb +7 -12
  14. data/lib/action_policy/policy/authorization.rb +8 -7
  15. data/lib/action_policy/policy/cache.rb +11 -17
  16. data/lib/action_policy/policy/core.rb +25 -12
  17. data/lib/action_policy/policy/defaults.rb +3 -9
  18. data/lib/action_policy/policy/execution_result.rb +3 -9
  19. data/lib/action_policy/policy/pre_check.rb +19 -58
  20. data/lib/action_policy/policy/reasons.rb +29 -19
  21. data/lib/action_policy/policy/scoping.rb +5 -6
  22. data/lib/action_policy/rails/controller.rb +6 -1
  23. data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
  24. data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
  25. data/lib/action_policy/rspec/dsl.rb +1 -1
  26. data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
  27. data/lib/action_policy/utils/pretty_print.rb +21 -24
  28. data/lib/action_policy/utils/suggest_message.rb +1 -3
  29. data/lib/action_policy/version.rb +1 -1
  30. data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +0 -0
  31. data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
  32. data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
  33. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
  34. data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
  35. metadata +29 -119
  36. data/.gitattributes +0 -2
  37. data/.github/ISSUE_TEMPLATE.md +0 -21
  38. data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
  39. data/.github/bug_report_template.rb +0 -175
  40. data/.gitignore +0 -15
  41. data/.rubocop.yml +0 -54
  42. data/.tidelift.yml +0 -6
  43. data/.travis.yml +0 -31
  44. data/Gemfile +0 -22
  45. data/Rakefile +0 -27
  46. data/action_policy.gemspec +0 -44
  47. data/benchmarks/namespaced_lookup_cache.rb +0 -74
  48. data/benchmarks/pre_checks.rb +0 -73
  49. data/bin/console +0 -14
  50. data/bin/setup +0 -8
  51. data/docs/.nojekyll +0 -0
  52. data/docs/CNAME +0 -1
  53. data/docs/README.md +0 -79
  54. data/docs/_sidebar.md +0 -27
  55. data/docs/aliases.md +0 -122
  56. data/docs/assets/docsify-search.js +0 -364
  57. data/docs/assets/docsify.min.js +0 -3
  58. data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
  59. data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
  60. data/docs/assets/images/banner.png +0 -0
  61. data/docs/assets/images/cache.png +0 -0
  62. data/docs/assets/images/cache.svg +0 -70
  63. data/docs/assets/images/layer.png +0 -0
  64. data/docs/assets/images/layer.svg +0 -35
  65. data/docs/assets/prism-ruby.min.js +0 -1
  66. data/docs/assets/styles.css +0 -347
  67. data/docs/assets/vue.min.css +0 -1
  68. data/docs/authorization_context.md +0 -92
  69. data/docs/behaviour.md +0 -113
  70. data/docs/caching.md +0 -291
  71. data/docs/controller_action_aliases.md +0 -109
  72. data/docs/custom_lookup_chain.md +0 -48
  73. data/docs/custom_policy.md +0 -53
  74. data/docs/debugging.md +0 -55
  75. data/docs/decorators.md +0 -27
  76. data/docs/favicon.ico +0 -0
  77. data/docs/graphql.md +0 -302
  78. data/docs/i18n.md +0 -44
  79. data/docs/index.html +0 -43
  80. data/docs/instrumentation.md +0 -84
  81. data/docs/lookup_chain.md +0 -22
  82. data/docs/namespaces.md +0 -77
  83. data/docs/non_rails.md +0 -28
  84. data/docs/pre_checks.md +0 -57
  85. data/docs/pundit_migration.md +0 -80
  86. data/docs/quick_start.md +0 -118
  87. data/docs/rails.md +0 -120
  88. data/docs/reasons.md +0 -120
  89. data/docs/scoping.md +0 -255
  90. data/docs/testing.md +0 -390
  91. data/docs/writing_policies.md +0 -107
  92. data/gemfiles/jruby.gemfile +0 -8
  93. data/gemfiles/rails42.gemfile +0 -9
  94. data/gemfiles/rails6.gemfile +0 -8
  95. data/gemfiles/railsmaster.gemfile +0 -6
  96. data/lib/action_policy/ext/string_match.rb +0 -14
  97. data/lib/action_policy/ext/yield_self_then.rb +0 -25
@@ -1,390 +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
- #### Active Record relation example
116
-
117
- There is no single rule on how to test scopes, 'cause it dependes on the _nature_ of the scope.
118
-
119
- Here's an example of RSpec tests for Active Record scoping rules:
120
-
121
- ```ruby
122
- describe PostPolicy do
123
- describe "relation scope" do
124
- let(:user) { build_stubbed :user }
125
- let(:context) { {user: user} }
126
-
127
- # Feel free to replace with `before_all` from `test-prof`:
128
- # https://test-prof.evilmartians.io/#/before_all
129
- before do
130
- create(:post, name: "A")
131
- create(:post, name: "B", draft: true)
132
- end
133
-
134
- let(:target) do
135
- # We want to make sure that only the records created
136
- # for this test are affected, and they have a deterministic order
137
- Post.where(name: %w[A B]).order(name: :asc)
138
- end
139
-
140
- subject { policy.apply_scope(target, type: :active_record_relation).pluck(:name) }
141
-
142
- context "as user" do
143
- it { is_expected.to eq(%w[A]) }
144
- end
145
-
146
- context "as manager" do
147
- before { user.update!(role: :manager) }
148
-
149
- it { is_expected.to eq(%w[A B]) }
150
- end
151
-
152
- context "as banned user" do
153
- before { user.update!(banned: true) }
154
-
155
- it { is_expected.to be_empty }
156
- end
157
- end
158
- end
159
- ```
160
-
161
- #### Action Controller params example
162
-
163
- Here's an example of RSpec tests for Action Controller parameters scoping rules:
164
-
165
- ```ruby
166
- describe PostPolicy do
167
- describe "params scope" do
168
- let(:user) { build_stubbed :user }
169
- let(:context) { {user: user} }
170
-
171
- let(:params) { {name: "a", password: "b"} }
172
- let(:target) { ActionController::Parameters.new(params) }
173
-
174
- # it's easier to asses the hash representation, not the AC::Params object
175
- subject { policy.apply_scope(target, type: :action_controller_params).to_h }
176
-
177
- context "as user" do
178
- it { is_expected.to eq({name: "a"}) }
179
- end
180
-
181
- context "as manager" do
182
- before { user.update!(role: :manager) }
183
-
184
- it { is_expected.to eq({name: "a", password: "b"}) }
185
- end
186
- end
187
- end
188
- ```
189
-
190
- ## Testing authorization
191
-
192
- To test the act of authorization you have to make sure that the `authorize!` method is called with the appropriate arguments.
193
-
194
- Action Policy provides tools for such kind of testing for Minitest and RSpec.
195
-
196
- ### Minitest
197
-
198
- Include `ActionPolicy::TestHelper` to your test class and you'll be able to use
199
- `assert_authorized_to` assertion:
200
-
201
- ```ruby
202
- # in your controller
203
- class PostsController < ApplicationController
204
- def update
205
- @post = Post.find(params[:id])
206
- authorize! @post
207
- if @post.update(post_params)
208
- redirect_to @post
209
- else
210
- render :edit
211
- end
212
- end
213
- end
214
-
215
- # in your test
216
- require "action_policy/test_helper"
217
-
218
- class PostsControllerTest < ActionDispatch::IntegrationTest
219
- include ActionPolicy::TestHelper
220
-
221
- test "update is authorized" do
222
- sign_in users(:john)
223
-
224
- post = posts(:example)
225
-
226
- assert_authorized_to(:update?, post, with: PostPolicy) do
227
- patch :update, id: post.id, name: "Bob"
228
- end
229
- end
230
- end
231
- ```
232
-
233
- You can omit the policy (then it would be inferred from the target):
234
-
235
- ```ruby
236
- assert_authorized_to(:update?, post) do
237
- patch :update, id: post.id, name: "Bob"
238
- end
239
- ```
240
-
241
- ### RSpec
242
-
243
- Add the following to your `rails_helper.rb` (or `spec_helper.rb`):
244
-
245
- ```ruby
246
- require "action_policy/rspec"
247
- ```
248
-
249
- Now you can use `be_authorized_to` matcher:
250
-
251
- ```ruby
252
- describe PostsController do
253
- subject { patch :update, id: post.id, params: params }
254
-
255
- it "is authorized" do
256
- expect { subject }.to be_authorized_to(:update?, post)
257
- .with(PostPolicy)
258
- end
259
- end
260
- ```
261
-
262
- If you omit `.with(PostPolicy)` then the inferred policy for the target (`post`) would be used.
263
-
264
- RSpec composed matchers are available as target:
265
-
266
- ```ruby
267
- expect { subject }.to be_authorized_to(:show?, an_instance_of(Post))
268
- ```
269
-
270
- ## Testing scoping
271
-
272
- Action Policy provides a way to test that a correct scoping has been applied during the code execution.
273
-
274
- For example, you can test that in your `#index` action the correct scoping is used:
275
-
276
- ```ruby
277
- class UsersController < ApplicationController
278
- def index
279
- @user = authorized(User.all)
280
- end
281
- end
282
- ```
283
-
284
- ### Minitest
285
-
286
- Include `ActionPolicy::TestHelper` to your test class and you'll be able to use
287
- `assert_have_authorized_scope` assertion:
288
-
289
- ```ruby
290
- # in your test
291
- require "action_policy/test_helper"
292
-
293
- class UsersControllerTest < ActionDispatch::IntegrationTest
294
- include ActionPolicy::TestHelper
295
-
296
- test "index has authorized scope" do
297
- sign_in users(:john)
298
-
299
- assert_have_authorized_scope(type: :active_record_relation, with: UserPolicy) do
300
- get :index
301
- end
302
- end
303
- end
304
- ```
305
-
306
- You can also specify `as` and `scope_options` options.
307
-
308
- **NOTE:** both `type` and `with` params are required.
309
-
310
- It's not possible to test that a scoped has been applied to a particular _target_ but we provide
311
- a way to perform additional assertions against the matching target (if the assertion didn't fail):
312
-
313
- ```ruby
314
- test "index has authorized scope" do
315
- sign_in users(:john)
316
-
317
- assert_have_authorized_scope(type: :active_record_relation, with: UserPolicy) do
318
- get :index
319
- end.with_target do |target|
320
- # target is a object passed to `authorized` call
321
- assert_equal User.all, target
322
- end
323
- end
324
- ```
325
-
326
- ### RSpec
327
-
328
- Add the following to your `rails_helper.rb` (or `spec_helper.rb`):
329
-
330
- ```ruby
331
- require "action_policy/rspec"
332
- ```
333
-
334
- Now you can use `have_authorized_scope` matcher:
335
-
336
- ```ruby
337
- describe UsersController do
338
- subject { get :index }
339
-
340
- it "has authorized scope" do
341
- expect { subject }.to have_authorized_scope(:active_record_relation)
342
- .with(PostPolicy)
343
- end
344
- end
345
- ```
346
-
347
- You can also add `.as(:named_scope)` and `with_scope_options(options_hash)` options.
348
-
349
- RSpec composed matchers are available as scope options:
350
-
351
- ```ruby
352
- expect { subject }.to have_authorized_scope(:scope)
353
- .with_scope_options(matching(with_deleted: a_falsey_value))
354
- ```
355
-
356
- You can use the `with_target` modifier to run additional expectations against the matching target (if the matcher didn't fail):
357
-
358
- ```ruby
359
- expect { subject }.to have_authorized_scope(:scope)
360
- .with_scope_options(matching(with_deleted: a_falsey_value))
361
- .with_target { |target|
362
- expect(target).to eq(User.all)
363
- }
364
- ```
365
-
366
-
367
- ## Testing views
368
-
369
- When you test views that call policies methods as `allowed_to?`, your may have `Missing policy authorization context: user` error.
370
- You may need to stub `current_user` to resolve the issue.
371
-
372
- Consider an RSpec example:
373
-
374
- ```ruby
375
- describe "users/index.html.slim" do
376
- let(:user) { build_stubbed :user }
377
- let(:users) { create_list(:user, 2) }
378
-
379
- before do
380
- allow(controller).to receive(:current_user).and_return(user)
381
-
382
- assign :users, users
383
- render
384
- end
385
-
386
- describe "displays user#index correctly" do
387
- it { expect(rendered).to have_link(users.first.email, href: edit_user_path(users.first)) }
388
- end
389
- end
390
- ```
@@ -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).