action_policy 0.3.4 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/palkan/action_policy.svg?branch=master)](https://travis-ci.org/palkan/action_policy)
|
3
3
|
[![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](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
|