action_policy 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
```
|