action_policy 0.3.4 → 0.4.4
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/.github/ISSUE_TEMPLATE.md +4 -1
- data/.github/bug_report_template.rb +175 -0
- data/.travis.yml +4 -4
- data/CHANGELOG.md +54 -0
- data/LICENSE.txt +1 -1
- data/README.md +7 -10
- data/benchmarks/namespaced_lookup_cache.rb +8 -5
- data/benchmarks/pre_checks.rb +73 -0
- data/docs/README.md +2 -0
- data/docs/caching.md +22 -4
- data/docs/instrumentation.md +11 -0
- data/docs/lookup_chain.md +6 -1
- data/docs/namespaces.md +2 -2
- data/docs/quick_start.md +5 -3
- data/docs/rails.md +7 -0
- data/docs/testing.md +63 -0
- data/docs/writing_policies.md +1 -1
- data/gemfiles/rails42.gemfile +1 -0
- data/lib/action_policy.rb +1 -1
- data/lib/action_policy/behaviour.rb +4 -0
- data/lib/action_policy/behaviours/memoized.rb +3 -7
- data/lib/action_policy/behaviours/policy_for.rb +13 -4
- data/lib/action_policy/behaviours/scoping.rb +2 -0
- data/lib/action_policy/behaviours/thread_memoized.rb +3 -7
- data/lib/action_policy/ext/policy_cache_key.rb +8 -6
- data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
- data/lib/action_policy/lookup_chain.rb +19 -13
- data/lib/action_policy/policy/authorization.rb +7 -11
- data/lib/action_policy/policy/cache.rb +26 -4
- data/lib/action_policy/policy/core.rb +2 -2
- data/lib/action_policy/policy/pre_check.rb +2 -2
- data/lib/action_policy/policy/reasons.rb +2 -2
- data/lib/action_policy/rails/ext/active_record.rb +7 -0
- data/lib/action_policy/rails/policy/instrumentation.rb +9 -2
- data/lib/action_policy/rspec/dsl.rb +6 -6
- data/lib/action_policy/rspec/have_authorized_scope.rb +2 -2
- data/lib/action_policy/testing.rb +3 -3
- data/lib/action_policy/version.rb +1 -1
- data/lib/generators/action_policy/install/templates/application_policy.rb +1 -1
- metadata +5 -4
- data/.github/FUNDING.yml +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa234de7b74f58df4707ec00f9f4d67bee0d89fb721f2b0e4df90627970530e6
|
4
|
+
data.tar.gz: e6e4ba56d3be720b8ddac3a236f42c84eec2472d04b95213914d89e23d291bf7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 450572c0987f8d4174ff6c51fdd188d62bd5dfd288b381a593030ee9ef8575df74852b1600706f6dfb1c115fddb9a45e518f03b2dd0a118e653c0a13c0efc05a
|
7
|
+
data.tar.gz: 1fa6dac85f5fe5f014026d23357c08e9dba0101b68a07a96315275a1ce2aa28762ab82abffbf0ae985a9a277d942de23c521223d485b7acfe1fdceeae9c4179d
|
data/.github/ISSUE_TEMPLATE.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
<!--
|
2
|
-
This template is for bug reports. If you are reporting a bug, please continue on. If you are here for another reason,
|
2
|
+
This template is for bug reports. If you are reporting a bug, please continue on. If you are here for another reason,
|
3
3
|
feel free to skip the rest of this template.
|
4
4
|
-->
|
5
5
|
|
@@ -11,6 +11,9 @@
|
|
11
11
|
|
12
12
|
**Action Policy Version:**
|
13
13
|
|
14
|
+
**Reproduction Script:** Use [this template](https://github.com/palkan/action_policy/blob/master/.github/bug_report_template.rb) to
|
15
|
+
create a standalone reproduction script. That would help us to fix the problem quicker. Thanks!
|
16
|
+
|
14
17
|
### What did you do?
|
15
18
|
|
16
19
|
### What did you expect to happen?
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/inline"
|
4
|
+
|
5
|
+
# This reproduction script allows you to test Action Policy with Rails.
|
6
|
+
# It contains:
|
7
|
+
# - Headless User model
|
8
|
+
# - UserPolicy
|
9
|
+
# - UsersController
|
10
|
+
# - Example tests for the controller.
|
11
|
+
#
|
12
|
+
# Update the classes to reproduce the failing case.
|
13
|
+
#
|
14
|
+
# Run the script as follows:
|
15
|
+
#
|
16
|
+
# $ ruby bug_report_template.rb
|
17
|
+
gemfile(true) do
|
18
|
+
source "https://rubygems.org"
|
19
|
+
|
20
|
+
gem "rails", "~> 6.0"
|
21
|
+
gem "action_policy", "~> 0.4"
|
22
|
+
|
23
|
+
gem "pry-byebug", platform: :mri
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rails"
|
27
|
+
require "action_controller/railtie"
|
28
|
+
require "action_policy"
|
29
|
+
|
30
|
+
require "minitest/autorun"
|
31
|
+
|
32
|
+
module Buggy
|
33
|
+
class Application < Rails::Application
|
34
|
+
config.logger = Logger.new("/dev/null")
|
35
|
+
config.eager_load = false
|
36
|
+
|
37
|
+
initializer "routes" do
|
38
|
+
Rails.application.routes.draw do
|
39
|
+
get ":controller(/:action)"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Rails.application.initialize!
|
46
|
+
|
47
|
+
class User
|
48
|
+
include Comparable
|
49
|
+
|
50
|
+
attr_reader :name
|
51
|
+
|
52
|
+
def initialize(name)
|
53
|
+
@name = name
|
54
|
+
end
|
55
|
+
|
56
|
+
def admin?
|
57
|
+
name == "admin"
|
58
|
+
end
|
59
|
+
|
60
|
+
def <=>(other)
|
61
|
+
return super unless other.is_a?(User)
|
62
|
+
name <=> other.name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class UserPolicy < ActionPolicy::Base
|
67
|
+
def index?
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
def create?
|
72
|
+
user.admin?
|
73
|
+
end
|
74
|
+
|
75
|
+
def show?
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
def manage?
|
80
|
+
user.admin? && !record.admin?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class UsersController < ActionController::Base
|
85
|
+
authorize :user, through: :current_user
|
86
|
+
|
87
|
+
before_action :set_user, only: [:update, :show]
|
88
|
+
|
89
|
+
def index
|
90
|
+
authorize!
|
91
|
+
render plain: "OK"
|
92
|
+
end
|
93
|
+
|
94
|
+
def create
|
95
|
+
authorize!
|
96
|
+
render plain: "OK"
|
97
|
+
end
|
98
|
+
|
99
|
+
def update
|
100
|
+
render plain: "OK"
|
101
|
+
end
|
102
|
+
|
103
|
+
def show
|
104
|
+
if allowed_to?(:update?, @user)
|
105
|
+
render plain: "OK"
|
106
|
+
else
|
107
|
+
render plain: "Read-only"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def current_user
|
112
|
+
@current_user ||= User.new(params[:user])
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def set_user
|
118
|
+
@user = User.new(params[:target])
|
119
|
+
authorize! @user
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class TestBugReproduction < ActionController::TestCase
|
124
|
+
tests UsersController
|
125
|
+
|
126
|
+
def before_setup
|
127
|
+
@routes = Rails.application.routes
|
128
|
+
super
|
129
|
+
end
|
130
|
+
|
131
|
+
def teardown
|
132
|
+
ActionPolicy::PerThreadCache.clear_all
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_index
|
136
|
+
get :index, params: {user: "guest"}
|
137
|
+
assert_equal "OK", response.body
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_create_failed
|
141
|
+
e = assert_raises(ActionPolicy::Unauthorized) do
|
142
|
+
post :create, params: {user: "guest"}
|
143
|
+
end
|
144
|
+
|
145
|
+
assert_equal UserPolicy, e.policy
|
146
|
+
assert_equal :create?, e.rule
|
147
|
+
assert e.result.reasons.is_a?(::ActionPolicy::Policy::FailureReasons)
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_create_succeed
|
151
|
+
post :create, params: {user: "admin"}
|
152
|
+
assert_equal "OK", response.body
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_update_failed
|
156
|
+
assert_raises(ActionPolicy::Unauthorized) do
|
157
|
+
patch :update, params: {user: "admin", target: "admin"}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_update_succeed
|
162
|
+
patch :update, params: {user: "admin", target: "guest"}
|
163
|
+
assert_equal "OK", response.body
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_show
|
167
|
+
get :show, params: {user: "admin", target: "guest"}
|
168
|
+
assert_equal "OK", response.body
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_show_admin
|
172
|
+
get :show, params: {user: "admin", target: "admin"}
|
173
|
+
assert_equal "Read-only", response.body
|
174
|
+
end
|
175
|
+
end
|
data/.travis.yml
CHANGED
@@ -16,16 +16,16 @@ matrix:
|
|
16
16
|
include:
|
17
17
|
- rvm: ruby-head
|
18
18
|
gemfile: gemfiles/railsmaster.gemfile
|
19
|
-
- rvm: jruby-9.2.
|
19
|
+
- rvm: jruby-9.2.8.0
|
20
20
|
gemfile: gemfiles/jruby.gemfile
|
21
|
-
- rvm: 2.6.
|
21
|
+
- rvm: 2.6.5
|
22
22
|
gemfile: gemfiles/rails6.gemfile
|
23
23
|
- rvm: 2.5.3
|
24
24
|
gemfile: Gemfile
|
25
|
-
- rvm: 2.
|
25
|
+
- rvm: 2.5.3
|
26
26
|
gemfile: gemfiles/rails42.gemfile
|
27
27
|
allow_failures:
|
28
28
|
- rvm: ruby-head
|
29
29
|
gemfile: gemfiles/railsmaster.gemfile
|
30
|
-
- rvm: jruby-9.2.
|
30
|
+
- rvm: jruby-9.2.8.0
|
31
31
|
gemfile: gemfiles/jruby.gemfile
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,58 @@
|
|
1
|
+
# Change log
|
2
|
+
|
1
3
|
## master
|
2
4
|
|
5
|
+
## 0.4.4 (2020-07-07)
|
6
|
+
|
7
|
+
- Fix symbol lookup with namespaces. ([@palkan][])
|
8
|
+
|
9
|
+
Fixes [#122](https://github.com/palkan/action_policy/issues/122).
|
10
|
+
|
11
|
+
- Separated `#classify`-based and `#camelize`-based symbol lookups. ([Be-ngt-oH][])
|
12
|
+
|
13
|
+
Only affects Rails apps. Now lookup for `:users` tries to find `UsersPolicy` first (camelize),
|
14
|
+
and only then search for `UserPolicy` (classify).
|
15
|
+
|
16
|
+
See [PR#118](https://github.com/palkan/action_policy/pull/118).
|
17
|
+
|
18
|
+
- Fix calling rules with `allowed_to?` directly. ([@palkan][])
|
19
|
+
|
20
|
+
Fixes [#113](https://github.com/palkan/action_policy/issues/113)
|
21
|
+
|
22
|
+
## 0.4.3 (2019-12-14)
|
23
|
+
|
24
|
+
- Add `#cache(*parts, **options) { ... }` method. ([@palkan][])
|
25
|
+
|
26
|
+
Allows you to cache anything in policy classes using the Action Policy
|
27
|
+
cache key generation mechanism.
|
28
|
+
|
29
|
+
- Handle versioned Rails cache keys. ([@palkan][])
|
30
|
+
|
31
|
+
Use `#cache_with_version` as a cache key if defined.
|
32
|
+
|
33
|
+
## 0.4.2 (2019-12-13)
|
34
|
+
|
35
|
+
- Fix regression introduced in 0.4.0 which broke testing Class targets. ([@palkan][])
|
36
|
+
|
37
|
+
## 0.4.0 (2019-12-11)
|
38
|
+
|
39
|
+
- Add `action_policy.init` instrumentation event. ([@palkan][])
|
40
|
+
|
41
|
+
Triggered every time a new policy object is initialized.
|
42
|
+
|
43
|
+
- Fix policy memoization with explicit context. ([@palkan][])
|
44
|
+
|
45
|
+
Explicit context (`authorize! context: {}`) wasn't considered during
|
46
|
+
policies memoization. Not this is fixed.
|
47
|
+
|
48
|
+
- Support composed matchers for authorization target testing. ([@palkan][])
|
49
|
+
|
50
|
+
Now you can write tests like this:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
expect { subject }.to be_authorized_to(:show?, an_instance_of(User))
|
54
|
+
```
|
55
|
+
|
3
56
|
## 0.3.4 (2019-11-27)
|
4
57
|
|
5
58
|
- Fix Rails generators. ([@palkan][])
|
@@ -346,3 +399,4 @@
|
|
346
399
|
[@korolvs]: https://github.com/korolvs
|
347
400
|
[@nicolas-brousse]: https://github.com/nicolas-brousse
|
348
401
|
[@somenugget]: https://github.com/somenugget
|
402
|
+
[@Be-ngt-oH]: https://github.com/Be-ngt-oH
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2018-
|
3
|
+
Copyright (c) 2018-2020 Vladimir Dementyev
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
[](https://travis-ci.org/palkan/action_policy)
|
3
3
|
[](https://actionpolicy.evilmartians.io)
|
4
4
|
|
5
|
-
#
|
5
|
+
# Action Policy
|
6
6
|
|
7
7
|
Authorization framework for Ruby and Rails applications.
|
8
8
|
|
@@ -15,9 +15,11 @@ Composable. Extensible. Performant.
|
|
15
15
|
|
16
16
|
## Resources
|
17
17
|
|
18
|
-
-
|
18
|
+
- RubyRussia, 2019 "Welcome, or access denied?" talk ([video](https://www.youtube.com/watch?v=y15a2g7v8i0) [RU], [slides](https://speakerdeck.com/palkan/rubyrussia-2019-welcome-or-access-denied))
|
19
19
|
|
20
|
-
-
|
20
|
+
- Seattle.rb, 2019 "A Denial!" talk ([slides](https://speakerdeck.com/palkan/seattle-dot-rb-2019-a-denial))
|
21
|
+
|
22
|
+
- RailsConf, 2018 "Access Denied" talk ([video](https://www.youtube.com/watch?v=NVwx0DARDis), [slides](https://speakerdeck.com/palkan/railsconf-2018-access-denied-the-missing-guide-to-authorization-in-rails))
|
21
23
|
|
22
24
|
|
23
25
|
## Integrations
|
@@ -29,7 +31,7 @@ Composable. Extensible. Performant.
|
|
29
31
|
Add this line to your application's `Gemfile`:
|
30
32
|
|
31
33
|
```ruby
|
32
|
-
gem "action_policy", "~> 0.
|
34
|
+
gem "action_policy", "~> 0.4.0"
|
33
35
|
```
|
34
36
|
|
35
37
|
And then execute:
|
@@ -96,7 +98,7 @@ There is also an `allowed_to?` method which returns `true` or `false`, and could
|
|
96
98
|
<% @posts.each do |post| %>
|
97
99
|
<li><%= post.title %>
|
98
100
|
<% if allowed_to?(:edit?, post) %>
|
99
|
-
|
101
|
+
<%= link_to post, "Edit">
|
100
102
|
<% end %>
|
101
103
|
</li>
|
102
104
|
<% end %>
|
@@ -119,8 +121,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/palkan
|
|
119
121
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
120
122
|
|
121
123
|
[Documentation]: http://actionpolicy.evilmartians.io
|
122
|
-
|
123
|
-
## Security Contact
|
124
|
-
|
125
|
-
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
|
126
|
-
|
@@ -39,6 +39,9 @@ if ENV["NO_CACHE"]
|
|
39
39
|
ActionPolicy::LookupChain.namespace_cache_enabled = false
|
40
40
|
end
|
41
41
|
|
42
|
+
results_path = File.join(__dir__, "../tmp/#{__FILE__.sub(/\.rb$/, ".txt")}")
|
43
|
+
FileUtils.mkdir_p File.dirname(results_path)
|
44
|
+
|
42
45
|
Benchmark.ips do |x|
|
43
46
|
x.warmup = 0
|
44
47
|
|
@@ -58,14 +61,14 @@ Benchmark.ips do |x|
|
|
58
61
|
ActionPolicy.lookup(b, namespace: X::Y::Z)
|
59
62
|
end
|
60
63
|
|
61
|
-
x.hold!
|
64
|
+
x.hold! results_path
|
62
65
|
|
63
66
|
x.compare!
|
64
67
|
end
|
65
68
|
|
66
69
|
#
|
67
70
|
# Comparison:
|
68
|
-
# cache
|
69
|
-
# cache
|
70
|
-
#
|
71
|
-
#
|
71
|
+
# cache A: 204788.3 i/s
|
72
|
+
# cache B: 202536.9 i/s - same-ish: difference falls within error
|
73
|
+
# no cache A: 127772.8 i/s - same-ish: difference falls within error
|
74
|
+
# no cache B: 63487.2 i/s - 3.23x slower
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This benchmark measures the difference between catch/throw and jump-less implementation.
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
6
|
+
|
7
|
+
require "action_policy"
|
8
|
+
require "benchmark/ips"
|
9
|
+
|
10
|
+
GC.disable
|
11
|
+
|
12
|
+
class A; end
|
13
|
+
|
14
|
+
class B; end
|
15
|
+
|
16
|
+
class APolicy < ActionPolicy::Base
|
17
|
+
authorize :user, optional: true
|
18
|
+
|
19
|
+
pre_check :do_nothing, :deny_all
|
20
|
+
|
21
|
+
def show?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def do_nothing
|
26
|
+
end
|
27
|
+
|
28
|
+
def deny_all
|
29
|
+
deny!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class BPolicy < APolicy
|
34
|
+
def run_pre_checks(rule)
|
35
|
+
catch :policy_fulfilled do
|
36
|
+
self.class.pre_checks.each do |check|
|
37
|
+
next unless check.applicable?(rule)
|
38
|
+
check.call(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
return yield if block_given?
|
42
|
+
end
|
43
|
+
|
44
|
+
result.value
|
45
|
+
end
|
46
|
+
|
47
|
+
def deny!
|
48
|
+
result.load false
|
49
|
+
throw :policy_fulfilled
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
a = A.new
|
54
|
+
|
55
|
+
(APolicy.new(record: a).apply(:show?) == BPolicy.new(record: a).apply(:show?)) || raise("Implementations are not equal")
|
56
|
+
|
57
|
+
Benchmark.ips do |x|
|
58
|
+
x.warmup = 0
|
59
|
+
|
60
|
+
x.report("loop pre-check") do
|
61
|
+
APolicy.new(record: a).apply(:show?)
|
62
|
+
end
|
63
|
+
|
64
|
+
x.report("catch/throw pre-check") do
|
65
|
+
BPolicy.new(record: a).apply(:show?)
|
66
|
+
end
|
67
|
+
|
68
|
+
x.compare!
|
69
|
+
end
|
70
|
+
|
71
|
+
# Comparison:
|
72
|
+
# catch/throw pre-check: 286094.2 i/s
|
73
|
+
# loop pre-check: 184786.1 i/s - 1.55x slower
|