action_policy 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +233 -171
  3. data/LICENSE.txt +1 -1
  4. data/README.md +7 -11
  5. data/lib/action_policy.rb +7 -1
  6. data/lib/action_policy/behaviour.rb +22 -16
  7. data/lib/action_policy/behaviours/policy_for.rb +10 -3
  8. data/lib/action_policy/behaviours/scoping.rb +2 -1
  9. data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
  10. data/lib/action_policy/ext/module_namespace.rb +1 -6
  11. data/lib/action_policy/ext/policy_cache_key.rb +15 -33
  12. data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
  13. data/lib/action_policy/i18n.rb +1 -1
  14. data/lib/action_policy/lookup_chain.rb +41 -21
  15. data/lib/action_policy/policy/aliases.rb +7 -12
  16. data/lib/action_policy/policy/authorization.rb +14 -17
  17. data/lib/action_policy/policy/cache.rb +34 -18
  18. data/lib/action_policy/policy/core.rb +25 -12
  19. data/lib/action_policy/policy/defaults.rb +3 -9
  20. data/lib/action_policy/policy/execution_result.rb +3 -9
  21. data/lib/action_policy/policy/pre_check.rb +19 -58
  22. data/lib/action_policy/policy/reasons.rb +30 -20
  23. data/lib/action_policy/policy/scoping.rb +5 -6
  24. data/lib/action_policy/rails/controller.rb +6 -1
  25. data/lib/action_policy/rails/ext/active_record.rb +7 -0
  26. data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
  27. data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
  28. data/lib/action_policy/rspec/dsl.rb +3 -3
  29. data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
  30. data/lib/action_policy/testing.rb +1 -1
  31. data/lib/action_policy/utils/pretty_print.rb +21 -24
  32. data/lib/action_policy/utils/suggest_message.rb +1 -3
  33. data/lib/action_policy/version.rb +1 -1
  34. data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +1 -1
  35. data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
  36. data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
  37. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
  38. data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
  39. metadata +30 -119
  40. data/.gitattributes +0 -2
  41. data/.github/FUNDING.yml +0 -1
  42. data/.github/ISSUE_TEMPLATE.md +0 -18
  43. data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
  44. data/.gitignore +0 -15
  45. data/.rubocop.yml +0 -54
  46. data/.tidelift.yml +0 -6
  47. data/.travis.yml +0 -31
  48. data/Gemfile +0 -22
  49. data/Rakefile +0 -27
  50. data/action_policy.gemspec +0 -44
  51. data/benchmarks/namespaced_lookup_cache.rb +0 -71
  52. data/bin/console +0 -14
  53. data/bin/setup +0 -8
  54. data/docs/.nojekyll +0 -0
  55. data/docs/CNAME +0 -1
  56. data/docs/README.md +0 -77
  57. data/docs/_sidebar.md +0 -27
  58. data/docs/aliases.md +0 -122
  59. data/docs/assets/docsify-search.js +0 -364
  60. data/docs/assets/docsify.min.js +0 -3
  61. data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
  62. data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
  63. data/docs/assets/images/banner.png +0 -0
  64. data/docs/assets/images/cache.png +0 -0
  65. data/docs/assets/images/cache.svg +0 -70
  66. data/docs/assets/images/layer.png +0 -0
  67. data/docs/assets/images/layer.svg +0 -35
  68. data/docs/assets/prism-ruby.min.js +0 -1
  69. data/docs/assets/styles.css +0 -347
  70. data/docs/assets/vue.min.css +0 -1
  71. data/docs/authorization_context.md +0 -92
  72. data/docs/behaviour.md +0 -113
  73. data/docs/caching.md +0 -273
  74. data/docs/controller_action_aliases.md +0 -109
  75. data/docs/custom_lookup_chain.md +0 -48
  76. data/docs/custom_policy.md +0 -53
  77. data/docs/debugging.md +0 -55
  78. data/docs/decorators.md +0 -27
  79. data/docs/favicon.ico +0 -0
  80. data/docs/graphql.md +0 -302
  81. data/docs/i18n.md +0 -44
  82. data/docs/index.html +0 -43
  83. data/docs/instrumentation.md +0 -84
  84. data/docs/lookup_chain.md +0 -17
  85. data/docs/namespaces.md +0 -77
  86. data/docs/non_rails.md +0 -28
  87. data/docs/pre_checks.md +0 -57
  88. data/docs/pundit_migration.md +0 -80
  89. data/docs/quick_start.md +0 -118
  90. data/docs/rails.md +0 -120
  91. data/docs/reasons.md +0 -120
  92. data/docs/scoping.md +0 -255
  93. data/docs/testing.md +0 -333
  94. data/docs/writing_policies.md +0 -107
  95. data/gemfiles/jruby.gemfile +0 -8
  96. data/gemfiles/rails42.gemfile +0 -8
  97. data/gemfiles/rails6.gemfile +0 -8
  98. data/gemfiles/railsmaster.gemfile +0 -6
  99. data/lib/action_policy/ext/string_match.rb +0 -14
  100. 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: ".."