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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +233 -171
- data/LICENSE.txt +1 -1
- data/README.md +7 -11
- data/lib/action_policy.rb +7 -1
- data/lib/action_policy/behaviour.rb +22 -16
- data/lib/action_policy/behaviours/policy_for.rb +10 -3
- data/lib/action_policy/behaviours/scoping.rb +2 -1
- data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
- data/lib/action_policy/ext/module_namespace.rb +1 -6
- data/lib/action_policy/ext/policy_cache_key.rb +15 -33
- data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
- data/lib/action_policy/i18n.rb +1 -1
- data/lib/action_policy/lookup_chain.rb +41 -21
- data/lib/action_policy/policy/aliases.rb +7 -12
- data/lib/action_policy/policy/authorization.rb +14 -17
- data/lib/action_policy/policy/cache.rb +34 -18
- data/lib/action_policy/policy/core.rb +25 -12
- data/lib/action_policy/policy/defaults.rb +3 -9
- data/lib/action_policy/policy/execution_result.rb +3 -9
- data/lib/action_policy/policy/pre_check.rb +19 -58
- data/lib/action_policy/policy/reasons.rb +30 -20
- data/lib/action_policy/policy/scoping.rb +5 -6
- data/lib/action_policy/rails/controller.rb +6 -1
- data/lib/action_policy/rails/ext/active_record.rb +7 -0
- data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
- data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
- data/lib/action_policy/rspec/dsl.rb +3 -3
- data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
- data/lib/action_policy/testing.rb +1 -1
- data/lib/action_policy/utils/pretty_print.rb +21 -24
- data/lib/action_policy/utils/suggest_message.rb +1 -3
- data/lib/action_policy/version.rb +1 -1
- data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +1 -1
- data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
- data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
- data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
- data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
- metadata +30 -119
- data/.gitattributes +0 -2
- data/.github/FUNDING.yml +0 -1
- data/.github/ISSUE_TEMPLATE.md +0 -18
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
- data/.gitignore +0 -15
- data/.rubocop.yml +0 -54
- data/.tidelift.yml +0 -6
- data/.travis.yml +0 -31
- data/Gemfile +0 -22
- data/Rakefile +0 -27
- data/action_policy.gemspec +0 -44
- data/benchmarks/namespaced_lookup_cache.rb +0 -71
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/docs/.nojekyll +0 -0
- data/docs/CNAME +0 -1
- data/docs/README.md +0 -77
- data/docs/_sidebar.md +0 -27
- data/docs/aliases.md +0 -122
- data/docs/assets/docsify-search.js +0 -364
- data/docs/assets/docsify.min.js +0 -3
- data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
- data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
- data/docs/assets/images/banner.png +0 -0
- data/docs/assets/images/cache.png +0 -0
- data/docs/assets/images/cache.svg +0 -70
- data/docs/assets/images/layer.png +0 -0
- data/docs/assets/images/layer.svg +0 -35
- data/docs/assets/prism-ruby.min.js +0 -1
- data/docs/assets/styles.css +0 -347
- data/docs/assets/vue.min.css +0 -1
- data/docs/authorization_context.md +0 -92
- data/docs/behaviour.md +0 -113
- data/docs/caching.md +0 -273
- data/docs/controller_action_aliases.md +0 -109
- data/docs/custom_lookup_chain.md +0 -48
- data/docs/custom_policy.md +0 -53
- data/docs/debugging.md +0 -55
- data/docs/decorators.md +0 -27
- data/docs/favicon.ico +0 -0
- data/docs/graphql.md +0 -302
- data/docs/i18n.md +0 -44
- data/docs/index.html +0 -43
- data/docs/instrumentation.md +0 -84
- data/docs/lookup_chain.md +0 -17
- data/docs/namespaces.md +0 -77
- data/docs/non_rails.md +0 -28
- data/docs/pre_checks.md +0 -57
- data/docs/pundit_migration.md +0 -80
- data/docs/quick_start.md +0 -118
- data/docs/rails.md +0 -120
- data/docs/reasons.md +0 -120
- data/docs/scoping.md +0 -255
- data/docs/testing.md +0 -333
- data/docs/writing_policies.md +0 -107
- data/gemfiles/jruby.gemfile +0 -8
- data/gemfiles/rails42.gemfile +0 -8
- data/gemfiles/rails6.gemfile +0 -8
- data/gemfiles/railsmaster.gemfile +0 -6
- data/lib/action_policy/ext/string_match.rb +0 -14
- data/lib/action_policy/ext/yield_self_then.rb +0 -25
data/docs/i18n.md
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
# I18n Support
|
2
|
-
|
3
|
-
`ActionPolicy` integrates with [`i18n`][] to support localizable `full_messages` for [reasons](./reasons.md) and the execution result's `message`:
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
class ApplicationController < ActionController::Base
|
7
|
-
rescue_from ActionPolicy::Unauthorized do |ex|
|
8
|
-
p ex.result.message #=> "You do not have access to the stage"
|
9
|
-
p ex.result.reasons.full_messages #=> ["You do not have access to the stage"]
|
10
|
-
end
|
11
|
-
end
|
12
|
-
```
|
13
|
-
|
14
|
-
The message contains a string for the _rule_ that was called, while `full_messages` contains the list of reasons, why `ActionPolicy::Unauthorized` has been raised. You can find more information about tracking failure reasons [here](./reasons.md).
|
15
|
-
|
16
|
-
## Configuration
|
17
|
-
|
18
|
-
`ActionPolicy` doesn't provide any localization out-of-the-box and uses "You are not authorized to perform this action" as the default message.
|
19
|
-
|
20
|
-
You can add your app-level default fallback by providing the `action_policy.unauthorized` key value.
|
21
|
-
|
22
|
-
When using **Rails**, all you need is to add translations to any file under the `config/locales` folder (or create a new file, e.g. `config/locales/policies.yml`).
|
23
|
-
|
24
|
-
Non-Rails projets should configure [`i18n`][] gem manually:
|
25
|
-
|
26
|
-
```ruby
|
27
|
-
I18n.load_path << Dir[File.expand_path("config/locales") + "/*.yml"]
|
28
|
-
```
|
29
|
-
|
30
|
-
## Translations lookup
|
31
|
-
|
32
|
-
`ActionPolicy` uses the `action_policy` scope. Specific policies translations must be stored inside the `policy` sub-scope.
|
33
|
-
|
34
|
-
The following algorithm is used to find out the translation for a policy with a class `klass` and rule `rule`:
|
35
|
-
1. Translation for `"#{klass.identifier}.#{rule}"` key, when `self.identifier =` is not specified then underscored class name without the _Policy_ suffix would be used (e.g. `GuestUserPolicy` turns into `guest_user:` scope)
|
36
|
-
2. Repeat step 1 for each ancestor which looks like a policy (`.respond_to?(:identifier)?`) up to `ActionPolicy::Base`
|
37
|
-
3. Use `#{rule}` key
|
38
|
-
4. Use `en.action_policy.unauthorized` key
|
39
|
-
5. Use a default message provided by the gem
|
40
|
-
|
41
|
-
For example, given a `GuestUserPolicy` class which is inherited from `DefaultUserPolicy` and a rule `feed?`, the following list of possible translation keys would be used: `[:"action_policy.policy.guest_user.feed?", :"action_policy.policy.default_user.feed?", :"action_policy.policy.feed?", :"action_policy.unauthorized"]`
|
42
|
-
|
43
|
-
|
44
|
-
[`i18n`]: https://github.com/svenfuchs/i18n
|
data/docs/index.html
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
<!DOCTYPE html>
|
2
|
-
<html lang="en">
|
3
|
-
<head>
|
4
|
-
<meta charset="UTF-8">
|
5
|
-
<title>Action Policy: authorization framework for Ruby/Rails applications</title>
|
6
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
7
|
-
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
|
8
|
-
<meta itemprop="name" content="Action Policy" />
|
9
|
-
<meta property="og:title" content="Action Policy" />
|
10
|
-
<meta name="description" content="Athorization framework for Ruby/Rails application" />
|
11
|
-
<meta itemprop="description" content="Authorization framework for Ruby/Rails application" />
|
12
|
-
<meta property="og:description" content="Authorization framework for Ruby/Rails application" />
|
13
|
-
<meta itemprop="image" content="http://actionpolicy.evilmartians.io/assets/images/banner.png" />
|
14
|
-
<meta property="og:image" content="http://actionpolicy.evilmartians.io/assets/images/banner.png" />
|
15
|
-
<meta name="twitter:card" content="summary_large_image" />
|
16
|
-
<meta name="twitter:site" content="@palkan_tula" />
|
17
|
-
<meta name="twitter:creator" content="@palkan_tula" />
|
18
|
-
<meta property="og:site_name" content="Action Policy" />
|
19
|
-
<meta name="keywords" content="ruby, rails, authorization, open-source" />
|
20
|
-
<meta name="theme-color" content="#CB2028" />
|
21
|
-
<link rel="stylesheet" href="assets/vue.min.css">
|
22
|
-
<link rel="stylesheet" href="assets/styles.css">
|
23
|
-
</head>
|
24
|
-
<body>
|
25
|
-
<div id="app"></div>
|
26
|
-
<script>
|
27
|
-
window.$docsify = {
|
28
|
-
name: 'action_policy',
|
29
|
-
repo: 'https://github.com/palkan/action_policy',
|
30
|
-
loadSidebar: true,
|
31
|
-
subMaxLevel: 3,
|
32
|
-
ga: 'UA-104346673-3',
|
33
|
-
auto2top: true,
|
34
|
-
search: {
|
35
|
-
namespace: 'action-policy'
|
36
|
-
}
|
37
|
-
}
|
38
|
-
</script>
|
39
|
-
<script src="assets/docsify.min.js"></script>
|
40
|
-
<script src="assets/docsify-search.js"></script>
|
41
|
-
<script src="assets/prism-ruby.min.js"></script>
|
42
|
-
</body>
|
43
|
-
</html>
|
data/docs/instrumentation.md
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
# Instrumentation
|
2
|
-
|
3
|
-
Action Policy integrates with [Rails instrumentation system](https://guides.rubyonrails.org/active_support_instrumentation.html), `ActiveSupport::Notifications`.
|
4
|
-
|
5
|
-
## Events
|
6
|
-
|
7
|
-
### `action_policy.apply_rule`
|
8
|
-
|
9
|
-
This event is triggered every time a policy rule is applied:
|
10
|
-
- when `authorize!` is called
|
11
|
-
- when `allowed_to?` is called within the policy or the [behaviour](behaviour)
|
12
|
-
- when `apply_rule` is called explicitly (i.e. `SomePolicy.new(record, context).apply_rule(record)`).
|
13
|
-
|
14
|
-
The event contains the following information:
|
15
|
-
- `:policy` – policy class name
|
16
|
-
- `:rule` – applied rule (String)
|
17
|
-
- `:value` – the result of the rule application (true of false)
|
18
|
-
- `:cached` – whether we hit the [cache](caching)\*.
|
19
|
-
|
20
|
-
\* This parameter tracks only the cache store usage, not memoization.
|
21
|
-
|
22
|
-
You can use this event to track your policy cache usage and also detect _slow_ checks.
|
23
|
-
|
24
|
-
Here is an example code for sending policy stats to [Librato](https://librato.com/)
|
25
|
-
using [`librato-rack`](https://github.com/librato/librato-rack):
|
26
|
-
|
27
|
-
```ruby
|
28
|
-
ActiveSupport::Notifications.subscribe("action_policy.apply_rule") do |event, started, finished, _, data|
|
29
|
-
# Track hit and miss events separately (to display two measurements)
|
30
|
-
measurement = "#{event}.#{(data[:cached] ? "hit" : "miss")}"
|
31
|
-
# show ms times
|
32
|
-
timing = ((finished - started) * 1000).to_i
|
33
|
-
Librato.tracker.check_worker
|
34
|
-
Librato.timing measurement, timing, percentile: [95, 99]
|
35
|
-
end
|
36
|
-
```
|
37
|
-
|
38
|
-
### `action_policy.authorize`
|
39
|
-
|
40
|
-
This event is identical to `action_policy.apply_rule` with the one difference:
|
41
|
-
**it's only triggered when `authorize!` method is called**.
|
42
|
-
|
43
|
-
The motivation behind having a separate event for this method is to monitor the number of failed
|
44
|
-
authorizations: the high number of failed authorizations usually means that we do not take
|
45
|
-
into account authorization rules in the application UI (e.g., we show a "Delete" button to the user not
|
46
|
-
permitted to do that).
|
47
|
-
|
48
|
-
The `action_policy.apply_rule` might have a large number of failures, 'cause it also tracks the usage of non-raising applications (i.e. `allowed_to?`).
|
49
|
-
|
50
|
-
### `action_policy.init`
|
51
|
-
|
52
|
-
This event is triggered every time a new policy object is initialized.
|
53
|
-
|
54
|
-
The event contains the following information:
|
55
|
-
|
56
|
-
- `:policy` – policy class name.
|
57
|
-
|
58
|
-
This event is useful if you want to track the number of initialized policies per _action_ (for example, when you want to ensure that
|
59
|
-
the [memoization](caching.md) works as expected).
|
60
|
-
|
61
|
-
## Turn off instrumentation
|
62
|
-
|
63
|
-
Instrumentation is enabled by default. To turn it off add to your configuration:
|
64
|
-
|
65
|
-
```ruby
|
66
|
-
config.action_policy.instrumentation_enabled = false
|
67
|
-
```
|
68
|
-
|
69
|
-
**NOTE:** changing this setting after the application has been initialized doesn't take any effect.
|
70
|
-
|
71
|
-
## Non-Rails usage
|
72
|
-
|
73
|
-
If you don't use Rails itself but have `ActiveSupport::Notifications` available in your application,
|
74
|
-
you can use the instrumentation feature with some additional configuration:
|
75
|
-
|
76
|
-
```ruby
|
77
|
-
# Enable `apply_rule` event by extending the base policy class
|
78
|
-
require "action_policy/rails/policy/instrumentation"
|
79
|
-
ActionPolicy::Base.include ActionPolicy::Policy::Rails::Instrumentation
|
80
|
-
|
81
|
-
# Enabled `authorize` event by extending the authorizer class
|
82
|
-
require "action_policy/rails/authorizer"
|
83
|
-
ActionPolicy::Authorizer.singleton_class.prepend ActionPolicy::Rails::Authorizer
|
84
|
-
```
|
data/docs/lookup_chain.md
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
# Policy Lookup
|
2
|
-
|
3
|
-
Action Policy tries to automatically infer policy class from the target using the following _probes_:
|
4
|
-
|
5
|
-
1. If the target is a `Symbol`, then use `"#{target.to_s.classify}Policy"` as a `policy_name` (see below);
|
6
|
-
2. If the target responds to `policy_class`, then use it;
|
7
|
-
3. If the target's class responds to `policy_class`, then use it;
|
8
|
-
4. If the target or the target's class responds to `policy_name`, then use it (the `policy_name` should end with `Policy` as it's not appended automatically);
|
9
|
-
5. Otherwise, use `#{target.class.name}Policy`.
|
10
|
-
|
11
|
-
> \* [Namespaces](namespaces.md) could be also be considered when `namespace` option is set.
|
12
|
-
|
13
|
-
You can call `ActionPolicy.lookup(record, options)` to infer policy class for the record.
|
14
|
-
|
15
|
-
When no policy class is found, an `ActionPolicy::NotFound` error is raised.
|
16
|
-
|
17
|
-
You can [customize lookup](custom_lookup_chain.md) logic if necessary.
|
data/docs/namespaces.md
DELETED
@@ -1,77 +0,0 @@
|
|
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).
|
70
|
-
|
71
|
-
## Namespace resolution cache
|
72
|
-
|
73
|
-
We cache namespaced policy resolution for better performance (it could affect performance when we look up a policy from a deeply nested module context, see the [benchmark](https://github.com/palkan/action_policy/blob/master/benchmarks/namespaced_lookup_cache.rb)).
|
74
|
-
|
75
|
-
It could be disabled by setting `ActionPolicy::LookupChain.namespace_cache_enabled = false`. It's enabled by default unless `RACK_ENV` env var is specified and is not equal to `"production"` (e.g. when `RACK_ENV=test` the cache is disabled).
|
76
|
-
|
77
|
-
When using Rails it's enabled only in production mode but could be configured through setting the `config.action_policy.namespace_cache_enabled` parameter.
|
data/docs/non_rails.md
DELETED
@@ -1,28 +0,0 @@
|
|
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
|
-
|
5
|
-
You can [write your policies](writing_policies.md) for non-Rails applications the same way as you would do for Rails applications.
|
6
|
-
|
7
|
-
In order to have `authorize!` / `allowed_to?` / `authorized` methods, you will have to include [`ActionPolicy::Behaviour`](./behaviour.md) into your class (where you want to perform authorization):
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
class PostUpdateAction
|
11
|
-
include ActionPolicy::Behaviour
|
12
|
-
|
13
|
-
# provide authorization subject (performer)
|
14
|
-
authorize :user
|
15
|
-
|
16
|
-
attr_reader :user
|
17
|
-
|
18
|
-
def initialize(user)
|
19
|
-
@user = user
|
20
|
-
end
|
21
|
-
|
22
|
-
def call(post, params)
|
23
|
-
authorize! post, to: :update?
|
24
|
-
|
25
|
-
post.update!(params)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
```
|
data/docs/pre_checks.md
DELETED
@@ -1,57 +0,0 @@
|
|
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/pundit_migration.md
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
# Migrate from Pundit to Action Policy
|
2
|
-
|
3
|
-
Migration from Pundit to Action Policy could be done in a progressive way: first, we make Pundit polices and authorization helpers use Action Policy under the hood, then you can rewrite policies in the Action Policy way.
|
4
|
-
|
5
|
-
### Phase 1. Quacking like a Pundit.
|
6
|
-
|
7
|
-
#### Step 1. Prepare controllers.
|
8
|
-
|
9
|
-
- Remove `include Pundit` from ApplicationController
|
10
|
-
|
11
|
-
- Add `authorize` method:
|
12
|
-
|
13
|
-
```ruby
|
14
|
-
def authorize(record, rule = nil)
|
15
|
-
options = {}
|
16
|
-
options[:to] = rule unless rule.nil?
|
17
|
-
|
18
|
-
authorize! record, **options
|
19
|
-
end
|
20
|
-
```
|
21
|
-
|
22
|
-
- Configure [authorization context](authorization_context) if necessary, e.g. add `authorize :current_user, as: :user` to `ApplicationController` (**NOTE:** added automatically in Rails apps)
|
23
|
-
|
24
|
-
- Add `policy` and `policy_scope` helpers:
|
25
|
-
|
26
|
-
```ruby
|
27
|
-
helper_method :policy
|
28
|
-
helper_method :policy_scope
|
29
|
-
|
30
|
-
def policy(record)
|
31
|
-
policy_for(record)
|
32
|
-
end
|
33
|
-
|
34
|
-
def policy_scope(scope)
|
35
|
-
authorized scope
|
36
|
-
end
|
37
|
-
|
38
|
-
```
|
39
|
-
|
40
|
-
**NOTE**: `policy` defined above is not equal to `allowed_to?` since it doesn't take into account pre-checks.
|
41
|
-
|
42
|
-
#### Step 2. Prepare policies.
|
43
|
-
|
44
|
-
We assume that you have a base class for all your policies, e.g. `ApplicationPolicy`.
|
45
|
-
|
46
|
-
Then do the following:
|
47
|
-
- Add `include ActionPolicy::Policy::Core` to `ApplicationPolicy`
|
48
|
-
|
49
|
-
- Update `ApplicationPolicy#initialize`:
|
50
|
-
|
51
|
-
```ruby
|
52
|
-
def initialize(target, user:)
|
53
|
-
# ...
|
54
|
-
end
|
55
|
-
```
|
56
|
-
|
57
|
-
- [Rewrite scopes](scoping).
|
58
|
-
|
59
|
-
Unfortunately, there is no easy way to migrate Pundit class-based scope to Action Policies scopes.
|
60
|
-
|
61
|
-
#### Step 3. Replace RSpec helper:
|
62
|
-
|
63
|
-
We provide a Pundit-compatibile syntax for RSpec tests:
|
64
|
-
|
65
|
-
```
|
66
|
-
# Remove DSL
|
67
|
-
# require "pundit/rspec"
|
68
|
-
#
|
69
|
-
# Add Action Policy Pundit DSL
|
70
|
-
require "action_policy/rspec/pundit_syntax"
|
71
|
-
```
|
72
|
-
|
73
|
-
### Phase 2. No more Pundit.
|
74
|
-
|
75
|
-
When everything is green, it's time to fully migrate to ActionPolicy:
|
76
|
-
- make ApplicationPolicy inherit from `ActionPolicy::Base`
|
77
|
-
- migrate view helpers (from `policy(..)` to `allowed_to?`, from `policy_scope` to `authorized`)
|
78
|
-
- re-write specs using simple non-DSL syntax (or [Action Policy RSpec syntax](testing#rspec-dsl))
|
79
|
-
- add [authorization tests](testing#testing-authorization) (add `require 'action_policy/rspec'`)
|
80
|
-
- use [Reasons](reasons), [I18n integration](i18n), [cache](caching) and other Action Policy features!
|
data/docs/quick_start.md
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
# Quick Start
|
2
|
-
|
3
|
-
## Installation
|
4
|
-
|
5
|
-
Install Action Policy with RubyGems:
|
6
|
-
|
7
|
-
```ruby
|
8
|
-
gem install action_policy
|
9
|
-
```
|
10
|
-
|
11
|
-
Or add `action_policy` 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 having a separate policy class for each resource and encourage you to follow these conventions:
|
26
|
-
- put policies into the `app/policies` folder (when using with Rails);
|
27
|
-
- name policies using the corresponding singular 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 -> PostPolicy#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
|
-
You could use the following command to generate it when using Rails:
|
38
|
-
|
39
|
-
```sh
|
40
|
-
rails generate action_policy:install
|
41
|
-
```
|
42
|
-
|
43
|
-
**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.
|
44
|
-
|
45
|
-
Rules must be public methods on the class. Using private methods as rules will raise an error.
|
46
|
-
|
47
|
-
Consider a simple example:
|
48
|
-
|
49
|
-
```ruby
|
50
|
-
class PostPolicy < ApplicationPolicy
|
51
|
-
# everyone can see any post
|
52
|
-
def show?
|
53
|
-
true
|
54
|
-
end
|
55
|
-
|
56
|
-
def update?
|
57
|
-
# `user` is a performing subject,
|
58
|
-
# `record` is a target object (post we want to update)
|
59
|
-
user.admin? || (user.id == record.user_id)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
```
|
63
|
-
|
64
|
-
Now you can easily add authorization to your Rails\* controller:
|
65
|
-
|
66
|
-
```ruby
|
67
|
-
class PostsController < ApplicationController
|
68
|
-
def update
|
69
|
-
@post = Post.find(params[:id])
|
70
|
-
authorize! @post
|
71
|
-
|
72
|
-
if @post.update(post_params)
|
73
|
-
redirect_to @post
|
74
|
-
else
|
75
|
-
render :edit
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
```
|
80
|
-
|
81
|
-
\* See [Non-Rails Usage](non_rails.md) on how to add `authorize!` to any Ruby project.
|
82
|
-
|
83
|
-
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)).
|
84
|
-
|
85
|
-
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:
|
86
|
-
|
87
|
-
```ruby
|
88
|
-
rescue_from ActionPolicy::Unauthorized do |ex|
|
89
|
-
# Exception object contains the following information
|
90
|
-
ex.policy #=> policy class, e.g. UserPolicy
|
91
|
-
ex.rule #=> applied rule, e.g. :show?
|
92
|
-
end
|
93
|
-
```
|
94
|
-
|
95
|
-
There is also an `allowed_to?` method which returns `true` or `false` and could be used, for example, in views:
|
96
|
-
|
97
|
-
```erb
|
98
|
-
<% @posts.each do |post| %>
|
99
|
-
<li><%= post.title %>
|
100
|
-
<% if allowed_to?(:edit?, post) %>
|
101
|
-
= link_to "Edit", post
|
102
|
-
<% end %>
|
103
|
-
</li>
|
104
|
-
<% end %>
|
105
|
-
```
|
106
|
-
|
107
|
-
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:
|
108
|
-
|
109
|
-
```ruby
|
110
|
-
# specify the rule to verify access
|
111
|
-
authorize! @post, to: :update?
|
112
|
-
|
113
|
-
# specify policy class
|
114
|
-
authorize! @post, with: CustomPostPolicy
|
115
|
-
|
116
|
-
# or
|
117
|
-
allowed_to? :edit?, @post, with: CustomPostPolicy
|
118
|
-
```
|