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.
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