action_policy 0.2.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/action_policy.gemspec +1 -0
- data/benchmarks/namespaced_lookup_cache.rb +75 -0
- data/docs/_sidebar.md +2 -0
- data/docs/controller_action_aliases.md +109 -0
- data/docs/index.html +2 -1
- data/docs/lookup_chain.md +1 -1
- data/docs/rails.md +2 -2
- data/lib/action_policy/behaviour.rb +1 -1
- data/lib/action_policy/cache_middleware.rb +17 -0
- data/lib/action_policy/lookup_chain.rb +7 -2
- data/lib/action_policy/railtie.rb +7 -2
- data/lib/action_policy/version.rb +1 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5aa63f362aa8606be7d0ba77e1c78be9ed8c811eb57b06c38835ec4df24c9bf1
|
4
|
+
data.tar.gz: 9c704fda90d4735827eddfb78be38a6f2ea2a293f71165df1955cdd78a3e803d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0cd9a62e04d0dbf1263d5c30ba0e860930877e63dd5c3c93f0be327e204b5e66265b8d9ce264278e9b16bef1bff7924331c66f11f88012c0a26dca445ad51bd
|
7
|
+
data.tar.gz: eb7b6b92a10869ab50f71656b446d26199d339891f5336ca28f87918cfd8dad467498e0262011703662944f82c49927e0c26dc24c871c456813ca87397cc4786
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
## master
|
2
2
|
|
3
|
+
## 0.2.2 (2018-07-01)
|
4
|
+
|
5
|
+
- [Fix [#29](https://github.com/palkan/action_policy/issues/29)] Fix loading cache middleware. ([@palkan][])
|
6
|
+
|
7
|
+
- Use `send` instead of `public_send` to get the `authorization_context` so that contexts such as
|
8
|
+
`current_user` can be `private` in the controller. ([@brendon][])
|
9
|
+
|
10
|
+
- Fix railtie initialisation for Rails < 5. ([@brendon][])
|
11
|
+
|
12
|
+
## 0.2.1 (yanked)
|
13
|
+
|
3
14
|
## 0.2.0 (2018-06-17)
|
4
15
|
|
5
16
|
- Make `action_policy` JRuby-compatible. ([@palkan][])
|
@@ -45,3 +56,4 @@
|
|
45
56
|
|
46
57
|
[@palkan]: https://github.com/palkan
|
47
58
|
[@ilyasgaraev]: https://github.com/ilyasgaraev
|
59
|
+
[@brendon]: https://github.com/brendon
|
data/action_policy.gemspec
CHANGED
@@ -29,4 +29,5 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency "rspec", "~> 3.3"
|
30
30
|
spec.add_development_dependency "rubocop", "~> 0.56.0"
|
31
31
|
spec.add_development_dependency "rubocop-md", "~> 0.2"
|
32
|
+
spec.add_development_dependency "benchmark-ips", "~> 2.7.0"
|
32
33
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
This benchmark measures the efficiency of NamespaceCache.
|
6
|
+
|
7
|
+
Run it multiple times with cache on/off to see the results:
|
8
|
+
|
9
|
+
$ bundle exec ruby namespaced_lookup_cache.rb
|
10
|
+
$ bundle exec ruby namespaced_lookup_cache.rb
|
11
|
+
$ NO_CACHE=1 bundle exec ruby namespaced_lookup_cache.rb
|
12
|
+
$ NO_CACHE=1 bundle exec ruby namespaced_lookup_cache.rb
|
13
|
+
|
14
|
+
=end
|
15
|
+
|
16
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
17
|
+
|
18
|
+
require "action_policy"
|
19
|
+
require "benchmark/ips"
|
20
|
+
|
21
|
+
GC.disable
|
22
|
+
|
23
|
+
class A; end
|
24
|
+
|
25
|
+
class B; end
|
26
|
+
|
27
|
+
module X
|
28
|
+
class BPolicy < ActionPolicy::Base; end
|
29
|
+
|
30
|
+
module Y
|
31
|
+
module Z
|
32
|
+
class APolicy < ActionPolicy::Base; end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
a = A.new
|
38
|
+
b = B.new
|
39
|
+
|
40
|
+
if ENV['NO_CACHE']
|
41
|
+
ActionPolicy::LookupChain.namespace_cache_enabled = false
|
42
|
+
end
|
43
|
+
|
44
|
+
Benchmark.ips do |x|
|
45
|
+
x.warmup = 0
|
46
|
+
|
47
|
+
x.report("cache A") do
|
48
|
+
ActionPolicy.lookup(a, namespace: X::Y::Z)
|
49
|
+
end
|
50
|
+
|
51
|
+
x.report("cache B") do
|
52
|
+
ActionPolicy.lookup(b, namespace: X::Y::Z)
|
53
|
+
end
|
54
|
+
|
55
|
+
x.report("no cache A") do
|
56
|
+
ActionPolicy.lookup(a, namespace: X::Y::Z)
|
57
|
+
end
|
58
|
+
|
59
|
+
x.report("no cache B") do
|
60
|
+
ActionPolicy.lookup(b, namespace: X::Y::Z)
|
61
|
+
end
|
62
|
+
|
63
|
+
x.hold! 'temp_results'
|
64
|
+
|
65
|
+
x.compare!
|
66
|
+
end
|
67
|
+
|
68
|
+
=begin
|
69
|
+
|
70
|
+
Comparison:
|
71
|
+
cache B: 178577.4 i/s
|
72
|
+
cache A: 173061.4 i/s - same-ish: difference falls within error
|
73
|
+
no cache A: 97991.7 i/s - same-ish: difference falls within error
|
74
|
+
no cache B: 42505.4 i/s - 4.20x slower
|
75
|
+
=end
|
data/docs/_sidebar.md
CHANGED
@@ -14,6 +14,8 @@
|
|
14
14
|
* [Failure Reasons](reasons.md)
|
15
15
|
* [Instrumentation](instrumentation.md)
|
16
16
|
* [I18n Support](i18n.md)
|
17
|
+
* Tips & Tricks
|
18
|
+
* [Controller Action Aliases](controller_action_aliases.md)
|
17
19
|
* Customize
|
18
20
|
* [Base Policy](custom_policy.md)
|
19
21
|
* [Lookup Chain](custom_lookup_chain.md)
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# Controller Action Aliases
|
2
|
+
|
3
|
+
**This is a feature proposed here: https://github.com/palkan/action_policy/issues/25**
|
4
|
+
|
5
|
+
If you'd like to see this feature implemented, please comment on the issue to show your support.
|
6
|
+
|
7
|
+
## Outline
|
8
|
+
|
9
|
+
Say you have abstracted your `authorize!` call to a controller superclass because your policy can
|
10
|
+
be executed without regard to the record in any of the subclass controllers:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
class AbstractController < ApplicationController
|
14
|
+
authorize :context
|
15
|
+
before_action :authorize_context
|
16
|
+
|
17
|
+
def context
|
18
|
+
# Some code to get your policy context
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def authorize_context
|
24
|
+
authorize! Context
|
25
|
+
end
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
Your policy might look like this:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class ContextPolicy < ApplicationPolicy
|
33
|
+
authorize :context
|
34
|
+
|
35
|
+
alias_rule :index?, :show?, to: :view?
|
36
|
+
alias_rule :new?, :create?, :update?, :destroy?, to: :edit?
|
37
|
+
|
38
|
+
def view?
|
39
|
+
context.has_permission_to(:view, user)
|
40
|
+
end
|
41
|
+
|
42
|
+
def edit?
|
43
|
+
context.has_permission_to(:edit, user)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
We can safely add aliases for the common REST actions in the policy.
|
49
|
+
|
50
|
+
You may then want to include a concern in your subclass controller(s) that add extra actions to the controller.
|
51
|
+
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class ConcreteController < AbstractController
|
55
|
+
include AdditionalFunctionalityConcern
|
56
|
+
|
57
|
+
def index
|
58
|
+
# Index Action
|
59
|
+
end
|
60
|
+
|
61
|
+
def new
|
62
|
+
# New Action
|
63
|
+
end
|
64
|
+
|
65
|
+
# etc...
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
At this point you may be wondering how to tell your abstracted policy that these new methods map to either
|
70
|
+
the `view?` or `edit?` rule. You can currently provide the rule to execute to the `authorize!` method with
|
71
|
+
the `to:` parameter but since our call to `authorize!` is in a superclass it has no idea about our concern.
|
72
|
+
I propose the following controller method:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
alias_action(*actions, to_rule: rule)
|
76
|
+
```
|
77
|
+
|
78
|
+
Here's an example:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
module AdditionalFunctionalityConcern
|
82
|
+
extend ActiveSupport::Concern
|
83
|
+
|
84
|
+
included do
|
85
|
+
alias_action [:first_action, :second_action], to_rule: :view?
|
86
|
+
alias_action [:third_action], to_rule: :edit?
|
87
|
+
end
|
88
|
+
|
89
|
+
def first_action
|
90
|
+
# First Action
|
91
|
+
end
|
92
|
+
|
93
|
+
def second_action
|
94
|
+
# Second Action
|
95
|
+
end
|
96
|
+
|
97
|
+
def third_action
|
98
|
+
# Third Action
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
When `authorize!` is called in a controller, it will first check the action aliases for a corresponding
|
104
|
+
rule. If one is found, it will execute that rule instead of a rule matching the name of the current action.
|
105
|
+
The rule may point at a concrete rule in the policy, or a rule alias in the policy, it doens't matter, the
|
106
|
+
alias in the policy will be resolved like normal.
|
107
|
+
|
108
|
+
If you'd like to see this feature implemented, please show your support on the
|
109
|
+
[Github Issue](https://github.com/palkan/action_policy/issues/25).
|
data/docs/index.html
CHANGED
data/docs/lookup_chain.md
CHANGED
@@ -4,7 +4,7 @@ Action Policy tries to automatically infer policy class from the target using th
|
|
4
4
|
|
5
5
|
1. If the target responds to `policy_class`, then use it;
|
6
6
|
2. If the target's class responds to `policy_class`, then use it;
|
7
|
-
3. If the target's class responds to `policy_name`, then use
|
7
|
+
3. If the target's class responds to `policy_name`, then use it (the `policy_name` should end with `Policy` as it's not appended automatically);
|
8
8
|
4. Otherwise, use `#{target.class.name}Policy`.
|
9
9
|
|
10
10
|
> \* [Namespaces](namespaces.md) could be also be considered when `namespace` option is set.
|
data/docs/rails.md
CHANGED
@@ -12,7 +12,7 @@ You can turn off this behaviour by setting `config.action_policy.controller_auth
|
|
12
12
|
|
13
13
|
```ruby
|
14
14
|
class ApplicationController < ActionController::Base
|
15
|
-
authorize :
|
15
|
+
authorize :user, through: :my_current_user
|
16
16
|
end
|
17
17
|
```
|
18
18
|
|
@@ -71,7 +71,7 @@ class ApiController < ApplicationController::API
|
|
71
71
|
include ActionPolicy::Controller
|
72
72
|
|
73
73
|
# NOTE: you have to provide authorization context manually as well
|
74
|
-
authorize :
|
74
|
+
authorize :user, through: :current_user
|
75
75
|
end
|
76
76
|
```
|
77
77
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy # :nodoc:
|
4
|
+
class CacheMiddleware # :nodoc:
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
ActionPolicy::PerThreadCache.clear_all
|
11
|
+
result = @app.call(env)
|
12
|
+
ActionPolicy::PerThreadCache.clear_all
|
13
|
+
|
14
|
+
result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -15,12 +15,14 @@ module ActionPolicy
|
|
15
15
|
require "action_policy/ext/module_namespace"
|
16
16
|
using ActionPolicy::Ext::ModuleNamespace
|
17
17
|
|
18
|
-
# Cache namespace resolving result for policies
|
18
|
+
# Cache namespace resolving result for policies.
|
19
|
+
# @see benchmarks/namespaced_lookup_cache.rb
|
19
20
|
class NamespaceCache
|
20
21
|
class << self
|
21
22
|
attr_reader :store
|
22
23
|
|
23
24
|
def fetch(namespace, policy)
|
25
|
+
return yield unless LookupChain.namespace_cache_enabled
|
24
26
|
return store[namespace][policy] if store[namespace].key?(policy)
|
25
27
|
store[namespace][policy] ||= yield
|
26
28
|
end
|
@@ -34,7 +36,7 @@ module ActionPolicy
|
|
34
36
|
end
|
35
37
|
|
36
38
|
class << self
|
37
|
-
attr_accessor :chain
|
39
|
+
attr_accessor :chain, :namespace_cache_enabled
|
38
40
|
|
39
41
|
def call(record, **opts)
|
40
42
|
chain.each do |probe|
|
@@ -74,6 +76,9 @@ module ActionPolicy
|
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
79
|
+
# Enable namespace cache by default
|
80
|
+
self.namespace_cache_enabled = true
|
81
|
+
|
77
82
|
# By self `policy_class` method
|
78
83
|
INSTANCE_POLICY_CLASS = ->(record, _) {
|
79
84
|
record.policy_class if record.respond_to?(:policy_class)
|
@@ -45,8 +45,13 @@ module ActionPolicy # :nodoc:
|
|
45
45
|
config.action_policy = Config
|
46
46
|
|
47
47
|
initializer "action_policy.clear_per_thread_cache" do |app|
|
48
|
-
|
49
|
-
|
48
|
+
if Rails::VERSION::MAJOR >= 5
|
49
|
+
app.executor.to_run { ActionPolicy::PerThreadCache.clear_all }
|
50
|
+
app.executor.to_complete { ActionPolicy::PerThreadCache.clear_all }
|
51
|
+
else
|
52
|
+
require "action_policy/cache_middleware"
|
53
|
+
app_middleware.use ActionPolicy::CacheMiddleware
|
54
|
+
end
|
50
55
|
end
|
51
56
|
|
52
57
|
config.to_prepare do |_app|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: action_policy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: benchmark-ips
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.7.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.7.0
|
97
111
|
description: Authorization framework for Ruby/Rails application
|
98
112
|
email:
|
99
113
|
- dementiev.vm@gmail.com
|
@@ -113,6 +127,7 @@ files:
|
|
113
127
|
- README.md
|
114
128
|
- Rakefile
|
115
129
|
- action_policy.gemspec
|
130
|
+
- benchmarks/namespaced_lookup_cache.rb
|
116
131
|
- bin/console
|
117
132
|
- bin/setup
|
118
133
|
- docs/.nojekyll
|
@@ -133,6 +148,7 @@ files:
|
|
133
148
|
- docs/assets/vue.min.css
|
134
149
|
- docs/authorization_context.md
|
135
150
|
- docs/caching.md
|
151
|
+
- docs/controller_action_aliases.md
|
136
152
|
- docs/custom_lookup_chain.md
|
137
153
|
- docs/custom_policy.md
|
138
154
|
- docs/favicon.ico
|
@@ -159,6 +175,7 @@ files:
|
|
159
175
|
- lib/action_policy/behaviours/namespaced.rb
|
160
176
|
- lib/action_policy/behaviours/policy_for.rb
|
161
177
|
- lib/action_policy/behaviours/thread_memoized.rb
|
178
|
+
- lib/action_policy/cache_middleware.rb
|
162
179
|
- lib/action_policy/ext/module_namespace.rb
|
163
180
|
- lib/action_policy/ext/policy_cache_key.rb
|
164
181
|
- lib/action_policy/ext/string_constantize.rb
|