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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +4 -1
  3. data/.github/bug_report_template.rb +175 -0
  4. data/.travis.yml +4 -4
  5. data/CHANGELOG.md +54 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +7 -10
  8. data/benchmarks/namespaced_lookup_cache.rb +8 -5
  9. data/benchmarks/pre_checks.rb +73 -0
  10. data/docs/README.md +2 -0
  11. data/docs/caching.md +22 -4
  12. data/docs/instrumentation.md +11 -0
  13. data/docs/lookup_chain.md +6 -1
  14. data/docs/namespaces.md +2 -2
  15. data/docs/quick_start.md +5 -3
  16. data/docs/rails.md +7 -0
  17. data/docs/testing.md +63 -0
  18. data/docs/writing_policies.md +1 -1
  19. data/gemfiles/rails42.gemfile +1 -0
  20. data/lib/action_policy.rb +1 -1
  21. data/lib/action_policy/behaviour.rb +4 -0
  22. data/lib/action_policy/behaviours/memoized.rb +3 -7
  23. data/lib/action_policy/behaviours/policy_for.rb +13 -4
  24. data/lib/action_policy/behaviours/scoping.rb +2 -0
  25. data/lib/action_policy/behaviours/thread_memoized.rb +3 -7
  26. data/lib/action_policy/ext/policy_cache_key.rb +8 -6
  27. data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
  28. data/lib/action_policy/lookup_chain.rb +19 -13
  29. data/lib/action_policy/policy/authorization.rb +7 -11
  30. data/lib/action_policy/policy/cache.rb +26 -4
  31. data/lib/action_policy/policy/core.rb +2 -2
  32. data/lib/action_policy/policy/pre_check.rb +2 -2
  33. data/lib/action_policy/policy/reasons.rb +2 -2
  34. data/lib/action_policy/rails/ext/active_record.rb +7 -0
  35. data/lib/action_policy/rails/policy/instrumentation.rb +9 -2
  36. data/lib/action_policy/rspec/dsl.rb +6 -6
  37. data/lib/action_policy/rspec/have_authorized_scope.rb +2 -2
  38. data/lib/action_policy/testing.rb +3 -3
  39. data/lib/action_policy/version.rb +1 -1
  40. data/lib/generators/action_policy/install/templates/application_policy.rb +1 -1
  41. metadata +5 -4
  42. data/.github/FUNDING.yml +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 272123a33fae55ffd9e6e09b1098862fbbf353a6ed5ff71d090bc5fd01396244
4
- data.tar.gz: ec53e2634999660b46c95dddc85bcf71055e336fdae5a8ea47023a671bba55ce
3
+ metadata.gz: aa234de7b74f58df4707ec00f9f4d67bee0d89fb721f2b0e4df90627970530e6
4
+ data.tar.gz: e6e4ba56d3be720b8ddac3a236f42c84eec2472d04b95213914d89e23d291bf7
5
5
  SHA512:
6
- metadata.gz: 62353ff2be45386257efcd127a43c7eac61d1cb7fc3cfcbf1acc6d6563e6d0a0a91873a0593b8ff62d51adc70b730e1799eb5a1565e3a1ed3c1f2593d46a05ff
7
- data.tar.gz: 837e717de9323e7131c535a41fe848aaa9501ad8f10471f082ff1355ae824afa7691b44bb9c8db71cb0b33898ad126963f3518b8cfcc1207e34469a282648d62
6
+ metadata.gz: 450572c0987f8d4174ff6c51fdd188d62bd5dfd288b381a593030ee9ef8575df74852b1600706f6dfb1c115fddb9a45e518f03b2dd0a118e653c0a13c0efc05a
7
+ data.tar.gz: 1fa6dac85f5fe5f014026d23357c08e9dba0101b68a07a96315275a1ce2aa28762ab82abffbf0ae985a9a277d942de23c521223d485b7acfe1fdceeae9c4179d
@@ -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
@@ -16,16 +16,16 @@ matrix:
16
16
  include:
17
17
  - rvm: ruby-head
18
18
  gemfile: gemfiles/railsmaster.gemfile
19
- - rvm: jruby-9.2.5.0
19
+ - rvm: jruby-9.2.8.0
20
20
  gemfile: gemfiles/jruby.gemfile
21
- - rvm: 2.6.0
21
+ - rvm: 2.6.5
22
22
  gemfile: gemfiles/rails6.gemfile
23
23
  - rvm: 2.5.3
24
24
  gemfile: Gemfile
25
- - rvm: 2.4.3
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.5.0
30
+ - rvm: jruby-9.2.8.0
31
31
  gemfile: gemfiles/jruby.gemfile
@@ -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
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018-2019 Vladimir Dementyev
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
- # ActionPolicy
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
- - Seattle.rb, 2019 "A Denial!" talk [[slides](https://speakerdeck.com/palkan/seattle-dot-rb-2019-a-denial)]
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
- - 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)]
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.3.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
- = link_to post, "Edit"
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! "temp_results"
64
+ x.hold! results_path
62
65
 
63
66
  x.compare!
64
67
  end
65
68
 
66
69
  #
67
70
  # Comparison:
68
- # cache B: 178577.4 i/s
69
- # cache A: 173061.4 i/s - same-ish: difference falls within error
70
- # no cache A: 97991.7 i/s - same-ish: difference falls within error
71
- # no cache B: 42505.4 i/s - 4.20x slower
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