action_policy 0.4.1 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +230 -172
- data/LICENSE.txt +1 -1
- data/README.md +7 -11
- data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +62 -0
- data/lib/.rbnext/2.7/action_policy/i18n.rb +56 -0
- data/lib/.rbnext/2.7/action_policy/policy/cache.rb +101 -0
- data/lib/.rbnext/2.7/action_policy/policy/pre_check.rb +162 -0
- data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +89 -0
- data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +124 -0
- data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +159 -0
- data/lib/.rbnext/3.0/action_policy/behaviour.rb +115 -0
- data/lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb +62 -0
- data/lib/.rbnext/3.0/action_policy/behaviours/scoping.rb +35 -0
- data/lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb +59 -0
- data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +72 -0
- data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +69 -0
- data/lib/.rbnext/3.0/action_policy/policy/authorization.rb +87 -0
- data/lib/.rbnext/3.0/action_policy/policy/cache.rb +101 -0
- data/lib/.rbnext/3.0/action_policy/policy/core.rb +161 -0
- data/lib/.rbnext/3.0/action_policy/policy/defaults.rb +31 -0
- data/lib/.rbnext/3.0/action_policy/policy/execution_result.rb +37 -0
- data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +162 -0
- data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +210 -0
- data/lib/.rbnext/3.0/action_policy/policy/scoping.rb +160 -0
- data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +89 -0
- data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +124 -0
- data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +159 -0
- data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +19 -0
- 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 -12
- 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 +55 -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
|
-
```
|