action_policy 0.0.1 → 0.1.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.
- checksums.yaml +5 -5
- data/.rubocop.yml +85 -0
- data/.travis.yml +25 -2
- data/CHANGELOG.md +7 -0
- data/Gemfile +12 -3
- data/README.md +71 -12
- data/Rakefile +9 -1
- data/action_policy.gemspec +11 -5
- data/docs/.nojekyll +0 -0
- data/docs/CNAME +1 -0
- data/docs/README.md +46 -0
- data/docs/_sidebar.md +19 -0
- data/docs/aliases.md +54 -0
- data/docs/assets/docsify.min.js +1 -0
- data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
- data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
- data/docs/assets/images/cache.png +0 -0
- data/docs/assets/images/cache.svg +70 -0
- data/docs/assets/images/layer.png +0 -0
- data/docs/assets/images/layer.svg +92 -0
- data/docs/assets/prism-ruby.min.js +1 -0
- data/docs/assets/styles.css +317 -0
- data/docs/assets/vue.min.css +1 -0
- data/docs/authorization_context.md +33 -0
- data/docs/caching.md +262 -0
- data/docs/custom_lookup_chain.md +48 -0
- data/docs/custom_policy.md +51 -0
- data/docs/favicon.ico +0 -0
- data/docs/i18n.md +3 -0
- data/docs/index.html +25 -0
- data/docs/instrumentation.md +3 -0
- data/docs/lookup_chain.md +16 -0
- data/docs/namespaces.md +69 -0
- data/docs/non_rails.md +29 -0
- data/docs/pre_checks.md +57 -0
- data/docs/quick_start.md +102 -0
- data/docs/rails.md +110 -0
- data/docs/reasons.md +67 -0
- data/docs/testing.md +116 -0
- data/docs/writing_policies.md +55 -0
- data/gemfiles/jruby.gemfile +5 -0
- data/gemfiles/rails42.gemfile +5 -0
- data/gemfiles/railsmaster.gemfile +6 -0
- data/lib/action_policy.rb +34 -2
- data/lib/action_policy/authorizer.rb +28 -0
- data/lib/action_policy/base.rb +24 -0
- data/lib/action_policy/behaviour.rb +94 -0
- data/lib/action_policy/behaviours/memoized.rb +56 -0
- data/lib/action_policy/behaviours/namespaced.rb +80 -0
- data/lib/action_policy/behaviours/policy_for.rb +23 -0
- data/lib/action_policy/behaviours/thread_memoized.rb +54 -0
- data/lib/action_policy/ext/module_namespace.rb +21 -0
- data/lib/action_policy/ext/policy_cache_key.rb +67 -0
- data/lib/action_policy/ext/string_constantize.rb +23 -0
- data/lib/action_policy/lookup_chain.rb +84 -0
- data/lib/action_policy/policy/aliases.rb +69 -0
- data/lib/action_policy/policy/authorization.rb +91 -0
- data/lib/action_policy/policy/cache.rb +74 -0
- data/lib/action_policy/policy/cached_apply.rb +28 -0
- data/lib/action_policy/policy/core.rb +64 -0
- data/lib/action_policy/policy/defaults.rb +37 -0
- data/lib/action_policy/policy/pre_check.rb +210 -0
- data/lib/action_policy/policy/reasons.rb +109 -0
- data/lib/action_policy/rails/channel.rb +15 -0
- data/lib/action_policy/rails/controller.rb +90 -0
- data/lib/action_policy/railtie.rb +74 -0
- data/lib/action_policy/rspec.rb +3 -0
- data/lib/action_policy/rspec/be_authorized_to.rb +93 -0
- data/lib/action_policy/rspec/pundit_syntax.rb +48 -0
- data/lib/action_policy/test_helper.rb +46 -0
- data/lib/action_policy/testing.rb +64 -0
- data/lib/action_policy/version.rb +3 -1
- metadata +115 -9
data/docs/namespaces.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Namespaces
|
2
|
+
|
3
|
+
Action Policy can lookup policies with respect to the current execution _namespace_ (i.e., authorization class module).
|
4
|
+
|
5
|
+
Consider an example:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
module Admin
|
9
|
+
class UsersController < ApplictionController
|
10
|
+
def index
|
11
|
+
# uses Admin::UserPolicy if any, otherwise fallbacks to UserPolicy
|
12
|
+
authorize!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
Module nesting is also supported:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
module Admin
|
22
|
+
module Client
|
23
|
+
class UsersController < ApplictionController
|
24
|
+
def index
|
25
|
+
# lookup for Admin::Client::UserPolicy -> Admin::UserPolicy -> UserPolicy
|
26
|
+
authorize!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
**NOTE**: to support namespaced lookup for non-inferrable resources,
|
34
|
+
you should specify `policy_name` at a class level (instead of `policy_class`, which doesn't take namespaces into account):
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class Guest < User
|
38
|
+
def self.policy_name
|
39
|
+
"UserPolicy"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
**NOTE**: by default, we use class's name as a policy name; so, for namespaced resources, the namespace part is also included:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class Admin
|
48
|
+
class User
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# search for Admin::UserPolicy, but not for UserPolicy
|
53
|
+
authorize! Admin::User.new
|
54
|
+
```
|
55
|
+
|
56
|
+
You can access the current authorization namespace through `authorization_namespace` method.
|
57
|
+
|
58
|
+
You can also define your own namespacing logic by overriding `authorization_namespace`:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
def authorization_namespace
|
62
|
+
return ::Admin if current_user.admin?
|
63
|
+
return ::Staff if current_user.staff?
|
64
|
+
# fallback to current namespace
|
65
|
+
super
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
**NOTE**: namespace support is an extension for `ActionPolicy::Behaviour` and could be included with `ActionPolicy::Behaviours::Namespaced` (included into Rails controllers and channel integrations by default).
|
data/docs/non_rails.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Using with Ruby applications
|
2
|
+
|
3
|
+
Action Policy is designed to be independent of any framework and does not have specific dependencies on Ruby on Rails.
|
4
|
+
You can [write your policies](writing_policies.md) for non-Rails applications the same way as you would do for Rails applications.
|
5
|
+
|
6
|
+
In order to have `authorize!` / `allowed_to?` methods, you will have to include `ActionPolicy::Behaviour` into your class (where you want to perform authorization):
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
class PostUpdateAction
|
10
|
+
include ActionPolicy::Behaviour
|
11
|
+
|
12
|
+
# provide authorization subject (performer)
|
13
|
+
authorize :user
|
14
|
+
|
15
|
+
attr_reader :user
|
16
|
+
|
17
|
+
def initialize(user)
|
18
|
+
@user = user
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(post, params)
|
22
|
+
authorize! post, to: :update?
|
23
|
+
|
24
|
+
post.update!(params)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
`ActionPolicy::Behaviour` provides `authorize` class-level method to configure [authorization context](authorization_context.rb) and two instance-level methods: `authorize!` and `allowed_to?`.
|
data/docs/pre_checks.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Pre-Checks
|
2
|
+
|
3
|
+
Consider a typical situation when you start most—or even all—of your rules with the same predicates.
|
4
|
+
|
5
|
+
For example, when you have a super-user role in the application:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class PostPolicy < ApplicationPolicy
|
9
|
+
def show?
|
10
|
+
user.super_admin? || record.published
|
11
|
+
end
|
12
|
+
|
13
|
+
def update?
|
14
|
+
user.super_admin? || (user.id == record.user_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
# more rules
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
Action Policy allows you to extract the common parts from rules into _pre-checks_:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class PostPolicy < ApplicationPolicy
|
25
|
+
pre_check :allow_admins
|
26
|
+
|
27
|
+
def show?
|
28
|
+
record.published
|
29
|
+
end
|
30
|
+
|
31
|
+
def update?
|
32
|
+
user.id == record.user_id
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def allow_admins
|
38
|
+
allow! if user.super_admin?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Pre-checks act like _callbacks_: you can add multiple pre-checks, specify `except` and `only` options, and skip already defined pre-checks if necessary:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class UserPolicy < ApplicationPolicy
|
47
|
+
skip_pre_check :allow_admins, only: :destroy?
|
48
|
+
|
49
|
+
def destroy?
|
50
|
+
user.admin? && !record.admin?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
To halt the authorization process within a pre-check, you must return either `allow!` or `deny!` call value. When any other value is returned, the pre-check is ignored, and the rule is called (or next pre-check).
|
56
|
+
|
57
|
+
**NOTE**: pre-checks are available only if you inherit from `ActionPolicy::Base` or include `ActionPolicy::Policy::PreCheck` into your `ApplicationPolicy`.
|
data/docs/quick_start.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Quick Start
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
To install Action Policy with RubyGems:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem install action_policy
|
9
|
+
```
|
10
|
+
|
11
|
+
Or add this line to your application's `Gemfile`:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem "action_policy"
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
## Basic usage
|
22
|
+
|
23
|
+
The core component of Action Policy is a _policy class_. Policy class describes how you control access to resources.
|
24
|
+
|
25
|
+
We suggest that you have a separate policy class for each resource and encourage you to follow the conventions:
|
26
|
+
- put policies into the `app/policies` folder (when using with Rails);
|
27
|
+
- name policies using the corresponding resource name (model name) with a `Policy` suffix, e.g. `Post -> PostPolicy`;
|
28
|
+
- name rules using a predicate form of the corresponding activity (typically, a controller's action), e.g. `PostsController#update -> PostsPolicy#update?`.
|
29
|
+
|
30
|
+
We also recommend to use an application-specific `ApplicationPolicy` with a global configuration to inherit from:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class ApplicationPolicy < ActionPolicy::Base
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
**NOTE:** it is not necessary to inherit from `ActionPolicy::Base`; instead, you can [construct basic policy](custom_policy.md) choosing only the components you need.
|
38
|
+
|
39
|
+
Consider a simple example:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class PostPolicy < ApplicationPolicy
|
43
|
+
# everyone can see any post
|
44
|
+
def show?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def update?
|
49
|
+
# `user` is a performing subject,
|
50
|
+
# `record` is a target object (post we want to update)
|
51
|
+
user.admin? || (user.id == record.user_id)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
Now you can easily add authorization to your Rails\* controller:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class PostsController < ApplicationController
|
60
|
+
def update
|
61
|
+
@post = Post.find(params[:id])
|
62
|
+
authorize! @post
|
63
|
+
|
64
|
+
if @post.update(post_params)
|
65
|
+
redirect_to @post
|
66
|
+
else
|
67
|
+
render :edit
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
\* See [Non-Rails Usage](non_rails.md) on how to add `authorize!` to any Ruby project.
|
74
|
+
|
75
|
+
In the above case, Action Policy automatically infers a policy class and a rule to verify access: `@post -> Post -> PostPolicy`, rule is inferred from the action name (`update -> update?`), and `current_user` is used as `user` within the policy by default (read more about [authorization context](authorization_context.md)).
|
76
|
+
|
77
|
+
When authorization is successful (i.e., the corresponding rule returns `true`), nothing happens, but in case of an authorization failure `ActionPolicy::Unauthorized` error is raised.
|
78
|
+
|
79
|
+
There is also an `allowed_to?` method which returns `true` or `false` and could be used, for example, in views:
|
80
|
+
|
81
|
+
```erb
|
82
|
+
<% @posts.each do |post| %>
|
83
|
+
<li><%= post.title %>
|
84
|
+
<% if allowed_to?(:edit?, post) %>
|
85
|
+
= link_to "Edit", post
|
86
|
+
<% end %>
|
87
|
+
</li>
|
88
|
+
<% end %>
|
89
|
+
```
|
90
|
+
|
91
|
+
Although Action Policy tries to [infer the corresponding policy class](policy_lookup.md) and rule itself, there could be a situation when you want to specify those values explicitly:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
# specify the rule to verify access
|
95
|
+
authorize! @post, to: :update?
|
96
|
+
|
97
|
+
# specify policy class
|
98
|
+
authorize! @post, with: CustomPostPolicy
|
99
|
+
|
100
|
+
# or
|
101
|
+
allowed_to? :edit?, @post, with: CustomPostPolicy
|
102
|
+
```
|
data/docs/rails.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# Using with Rails
|
2
|
+
|
3
|
+
Action Policy seamlessly integrates Ruby on Rails applications seamlessly.
|
4
|
+
|
5
|
+
In most cases, you do not have to do anything except writing policy files and adding `authorize!` calls.
|
6
|
+
|
7
|
+
## Controllers integration
|
8
|
+
|
9
|
+
Action Policy assumes that you have a `current_user` method which specifies the current authenticated subject (`user`).
|
10
|
+
|
11
|
+
You can turn off this behaviour by setting `config.action_policy.controller_authorize_current_user = false` in `application.rb`, or override it:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class ApplicationController < ActionController::Base
|
15
|
+
authorize :my_current_user, as: :user
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
> Read more about [authorization context](authorization_context.md).
|
20
|
+
|
21
|
+
In case you don't want to include Action Policy to controllers at all,
|
22
|
+
you can turn disable the integration by setting `config.action_policy.auto_inject_into_controller = false` in `application.rb`.
|
23
|
+
|
24
|
+
### `verify_authorized` hooks
|
25
|
+
|
26
|
+
Usually, you need all of your actions to be authorized. Action Policy provides a controller hook which ensures that an `authorize!` call has been made during the action:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class ApplicationController < ActionController::Base
|
30
|
+
# adds an after_action callback to verify
|
31
|
+
# that `authorize!` has been called.
|
32
|
+
verify_authorized
|
33
|
+
|
34
|
+
# you can also pass additional options,
|
35
|
+
# like with a usual callback
|
36
|
+
verify_authorized except: :index
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
You can skip this check when necessary:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
class PostsController < ApplicationController
|
44
|
+
skip_verify_authorized only: :show
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
When an unauthorized action is encountered, the `ActionPolicy::UnauthorizedAction` error is raised.
|
49
|
+
|
50
|
+
### Resource-less `authorize!`
|
51
|
+
|
52
|
+
You can also call `authorize!` without a resource specified.
|
53
|
+
In that case, Action Policy tries to infer the resource class from the controller name:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class PostsController < ApplicationPolicy
|
57
|
+
def index
|
58
|
+
# Uses Post class as a resource implicitly.
|
59
|
+
# NOTE: it just calls `controller_name.classify.safe_constantize`
|
60
|
+
authorize!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
### Usage with `API` and `Metal` controllers
|
66
|
+
|
67
|
+
Action Policy is only included into `ActionController::Base`. If you want to use it with other base Rails controllers, you have to include it manually:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class ApiController < ApplicationController::API
|
71
|
+
include ActionPolicy::Controller
|
72
|
+
|
73
|
+
# NOTE: you have to provide authorization context manually as well
|
74
|
+
authorize :current_user, as: :user
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
## Channels integration
|
79
|
+
|
80
|
+
Action Policy also integrates with Action Cable to help you authorize your channels actions:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class ChatChannel < ApplicationCable::Channel
|
84
|
+
def follow(data)
|
85
|
+
chat = Chat.find(data["chat_id"])
|
86
|
+
|
87
|
+
# Verify against ChatPolicy#show? rule
|
88
|
+
authorize! chat, to: :show?
|
89
|
+
stream_from chat
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
Action Policy assumes that you have `current_user` as a connection identifier.
|
95
|
+
|
96
|
+
You can turn off this behaviour by setting `config.action_policy.channel_authorize_current_user = false` in `application.rb`, or override it:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
module ApplicationCable
|
100
|
+
class Channel < ActionCable::Channel::Base
|
101
|
+
# assuming that identifier is called `user`
|
102
|
+
authorize :user
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
> Read more about [authorization context](authorization_context.md).
|
108
|
+
|
109
|
+
In case you do not want to include Action Policy to channels at all,
|
110
|
+
you can disable the integration by setting `config.action_policy.auto_inject_into_channel = false` in `application.rb`.
|
data/docs/reasons.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Failure Reasons
|
2
|
+
|
3
|
+
When you have complex policy rules, it could be helpful to have an ability to define an exact reason for why a specific authorization was rejected.
|
4
|
+
|
5
|
+
It is especially helpful when you compose policies (i.e., use one policy within another).
|
6
|
+
|
7
|
+
Action Policy allows you to track failed `allowed_to?` checks in your rules.
|
8
|
+
|
9
|
+
Consider an example:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class ApplicantPolicy < ApplicationPolicy
|
13
|
+
def show?
|
14
|
+
user.has_permission?(:view_applicants) &&
|
15
|
+
allowed_to?(:show?, object.stage)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
When `ApplicantPolicy#show?` check fails, the exception has the `reasons` object, which contains additional information about the failure:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
class ApplicationController < ActionController::Base
|
24
|
+
rescue_from ActionPolicy::Unauthorized do |ex|
|
25
|
+
p ex.reasons.messages #=> { stage: [:show?] }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
You can also wrap _local_ rules into `allowed_to?` to populate reasons:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class ApplicantPolicy < ApplicationPolicy
|
34
|
+
def show?
|
35
|
+
allowed_to?(:view_applicants?) &&
|
36
|
+
allowed_to?(:show?, object.stage)
|
37
|
+
end
|
38
|
+
|
39
|
+
def view_applicants?
|
40
|
+
user.has_permission?(:view_applicants)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# then the reasons object could be
|
45
|
+
p ex.reasons.messages #=> { applicant: [:view_applicants?] }
|
46
|
+
|
47
|
+
# or
|
48
|
+
p ex.reasons.messages #=> { stage: [:show?] }
|
49
|
+
```
|
50
|
+
|
51
|
+
**What is the point of failure reasons?**
|
52
|
+
|
53
|
+
First, you can provide a user with helpful feedback. For example, in the above scenario, when the reason is `ApplicantPolicy#view_applicants?`, you could show the following message:
|
54
|
+
|
55
|
+
```
|
56
|
+
You don't have enough permissions to view applicants.
|
57
|
+
Please, ask your manager to update your role.
|
58
|
+
```
|
59
|
+
|
60
|
+
And when the reason is `StagePolicy#show?`:
|
61
|
+
|
62
|
+
```
|
63
|
+
You don't have access to the stage XYZ.
|
64
|
+
Please, ask your manager to grant access to this stage.
|
65
|
+
```
|
66
|
+
|
67
|
+
Much more useful than just showing "You are not authorized to perform this action," isn't it?
|
data/docs/testing.md
ADDED
@@ -0,0 +1,116 @@
|
|
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 that **the required authorization is performed** within your authorization layer (controller, channel, etc.);
|
7
|
+
- Test the policy class itself.
|
8
|
+
|
9
|
+
## Testing authorization
|
10
|
+
|
11
|
+
To test the act of authorization you have to make sure that the `authorize!` method is called with the appropriate arguments.
|
12
|
+
|
13
|
+
Action Policy provides tools for such kind of testing for Minitest and RSpec.
|
14
|
+
|
15
|
+
### Minitest
|
16
|
+
|
17
|
+
Include `ActionPolicy::TestHelper` to your test class and you'll be able to use
|
18
|
+
`assert_authorized_to` assertion:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# in your controller
|
22
|
+
class PostsController < ApplicationController
|
23
|
+
def update
|
24
|
+
@post = Post.find(params[:id])
|
25
|
+
authorize! @post
|
26
|
+
if @post.update(post_params)
|
27
|
+
redirect_to @post
|
28
|
+
else
|
29
|
+
render :edit
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# in your test
|
35
|
+
require "action_policy/test_helper"
|
36
|
+
|
37
|
+
class PostsControllerTest < ActionDispatch::IntegrationTest
|
38
|
+
include ActionPolicy::TestHelper
|
39
|
+
|
40
|
+
test "update is authorized" do
|
41
|
+
sign_in users(:john)
|
42
|
+
|
43
|
+
post = posts(:example)
|
44
|
+
|
45
|
+
assert_authorized_to(:update?, post, with: PostPolicy) do
|
46
|
+
patch :update, id: post.id, name: "Bob"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
You can omit the policy (then it would be inferred from the target):
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
assert_authorized_to(:update?, post) do
|
56
|
+
patch :update, id: post.id, name: "Bob"
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
### RSpec
|
61
|
+
|
62
|
+
Add the following to your `rails_helper.rb` (or `spec_helper.rb`):
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
require "action_policy/rspec"
|
66
|
+
```
|
67
|
+
|
68
|
+
Now you can use `be_authorized_to` matcher:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
describe PostsController do
|
72
|
+
subject { patch :update, id: post.id, params: params }
|
73
|
+
|
74
|
+
it "is authorized" do
|
75
|
+
expect { subject }.to be_authorized_to(:update?, post)
|
76
|
+
.with(PostPolicy)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
If you omit `.with(PostPolicy)` then the inferred policy for the target (`post`) would be used.
|
82
|
+
|
83
|
+
## Testing policies
|
84
|
+
|
85
|
+
You can test policies as plain-old Ruby classes, no special tooling is required.
|
86
|
+
|
87
|
+
Consider an RSpec example:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
describe PostPolicy do
|
91
|
+
let(:user) { build_stubbed(:user) }
|
92
|
+
let(:post) { build_stubbed(:post) }
|
93
|
+
|
94
|
+
let(:policy) { described_class.new(post, user: user) }
|
95
|
+
|
96
|
+
describe "#update?" do
|
97
|
+
subject { policy.update? }
|
98
|
+
|
99
|
+
it "returns false when the user is not admin nor author" do
|
100
|
+
is_expected.to eq false
|
101
|
+
end
|
102
|
+
|
103
|
+
context "when the user is admin" do
|
104
|
+
let(:user) { build_stubbed(:user, :admin) }
|
105
|
+
|
106
|
+
it { is_expected.to eq true }
|
107
|
+
end
|
108
|
+
|
109
|
+
context "when the user is an author" do
|
110
|
+
let(:post) { build_stubbed(:post, user: user) }
|
111
|
+
|
112
|
+
it { is_expected.to eq true }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|