action_policy 0.0.1 → 0.1.0

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 (73) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +85 -0
  3. data/.travis.yml +25 -2
  4. data/CHANGELOG.md +7 -0
  5. data/Gemfile +12 -3
  6. data/README.md +71 -12
  7. data/Rakefile +9 -1
  8. data/action_policy.gemspec +11 -5
  9. data/docs/.nojekyll +0 -0
  10. data/docs/CNAME +1 -0
  11. data/docs/README.md +46 -0
  12. data/docs/_sidebar.md +19 -0
  13. data/docs/aliases.md +54 -0
  14. data/docs/assets/docsify.min.js +1 -0
  15. data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
  16. data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
  17. data/docs/assets/images/cache.png +0 -0
  18. data/docs/assets/images/cache.svg +70 -0
  19. data/docs/assets/images/layer.png +0 -0
  20. data/docs/assets/images/layer.svg +92 -0
  21. data/docs/assets/prism-ruby.min.js +1 -0
  22. data/docs/assets/styles.css +317 -0
  23. data/docs/assets/vue.min.css +1 -0
  24. data/docs/authorization_context.md +33 -0
  25. data/docs/caching.md +262 -0
  26. data/docs/custom_lookup_chain.md +48 -0
  27. data/docs/custom_policy.md +51 -0
  28. data/docs/favicon.ico +0 -0
  29. data/docs/i18n.md +3 -0
  30. data/docs/index.html +25 -0
  31. data/docs/instrumentation.md +3 -0
  32. data/docs/lookup_chain.md +16 -0
  33. data/docs/namespaces.md +69 -0
  34. data/docs/non_rails.md +29 -0
  35. data/docs/pre_checks.md +57 -0
  36. data/docs/quick_start.md +102 -0
  37. data/docs/rails.md +110 -0
  38. data/docs/reasons.md +67 -0
  39. data/docs/testing.md +116 -0
  40. data/docs/writing_policies.md +55 -0
  41. data/gemfiles/jruby.gemfile +5 -0
  42. data/gemfiles/rails42.gemfile +5 -0
  43. data/gemfiles/railsmaster.gemfile +6 -0
  44. data/lib/action_policy.rb +34 -2
  45. data/lib/action_policy/authorizer.rb +28 -0
  46. data/lib/action_policy/base.rb +24 -0
  47. data/lib/action_policy/behaviour.rb +94 -0
  48. data/lib/action_policy/behaviours/memoized.rb +56 -0
  49. data/lib/action_policy/behaviours/namespaced.rb +80 -0
  50. data/lib/action_policy/behaviours/policy_for.rb +23 -0
  51. data/lib/action_policy/behaviours/thread_memoized.rb +54 -0
  52. data/lib/action_policy/ext/module_namespace.rb +21 -0
  53. data/lib/action_policy/ext/policy_cache_key.rb +67 -0
  54. data/lib/action_policy/ext/string_constantize.rb +23 -0
  55. data/lib/action_policy/lookup_chain.rb +84 -0
  56. data/lib/action_policy/policy/aliases.rb +69 -0
  57. data/lib/action_policy/policy/authorization.rb +91 -0
  58. data/lib/action_policy/policy/cache.rb +74 -0
  59. data/lib/action_policy/policy/cached_apply.rb +28 -0
  60. data/lib/action_policy/policy/core.rb +64 -0
  61. data/lib/action_policy/policy/defaults.rb +37 -0
  62. data/lib/action_policy/policy/pre_check.rb +210 -0
  63. data/lib/action_policy/policy/reasons.rb +109 -0
  64. data/lib/action_policy/rails/channel.rb +15 -0
  65. data/lib/action_policy/rails/controller.rb +90 -0
  66. data/lib/action_policy/railtie.rb +74 -0
  67. data/lib/action_policy/rspec.rb +3 -0
  68. data/lib/action_policy/rspec/be_authorized_to.rb +93 -0
  69. data/lib/action_policy/rspec/pundit_syntax.rb +48 -0
  70. data/lib/action_policy/test_helper.rb +46 -0
  71. data/lib/action_policy/testing.rb +64 -0
  72. data/lib/action_policy/version.rb +3 -1
  73. metadata +115 -9
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy # :nodoc:
4
+ require "action_policy/rails/controller"
5
+ require "action_policy/rails/channel"
6
+
7
+ class Railtie < ::Rails::Railtie # :nodoc:
8
+ # Provides Rails-specific configuration,
9
+ # accessible through `Rails.application.config.action_policy`
10
+ module Config
11
+ class << self
12
+ # Define whether we need to extend ApplicationController::Base
13
+ # with the default authorization logic
14
+ attr_accessor :auto_inject_into_controller
15
+
16
+ # Define whether we want to specify `current_user` as
17
+ # the default authorization context in controller
18
+ attr_accessor :controller_authorize_current_user
19
+
20
+ # Define whether we need to include ActionCable::Channel::Base
21
+ # with the default authorization logic
22
+ attr_accessor :auto_inject_into_channel
23
+
24
+ # Define whether we want to specify `current_user` as
25
+ # the default authorization context in channels
26
+ attr_accessor :channel_authorize_current_user
27
+
28
+ def cache_store=(store, *args)
29
+ if store.is_a?(Symbol)
30
+ store = ActiveSupport::Cache.lookup_store(
31
+ store, *args
32
+ )
33
+ end
34
+
35
+ ActionPolicy.cache_store = store
36
+ end
37
+ end
38
+
39
+ self.auto_inject_into_controller = true
40
+ self.controller_authorize_current_user = true
41
+ self.auto_inject_into_channel = true
42
+ self.channel_authorize_current_user = true
43
+ end
44
+
45
+ config.action_policy = Config
46
+
47
+ initializer "action_policy.clear_per_thread_cache" do |app|
48
+ app.executor.to_run { ActionPolicy::PerThreadCache.clear_all }
49
+ app.executor.to_complete { ActionPolicy::PerThreadCache.clear_all }
50
+ end
51
+
52
+ config.after_initialize do |_app|
53
+ ActiveSupport.on_load(:action_controller) do
54
+ next unless Rails.application.config.action_policy.auto_inject_into_controller
55
+
56
+ ActionController::Base.include ActionPolicy::Controller
57
+
58
+ next unless Rails.application.config.action_policy.controller_authorize_current_user
59
+
60
+ ActionController::Base.authorize :user, through: :current_user
61
+ end
62
+
63
+ ActiveSupport.on_load(:action_cable) do
64
+ next unless Rails.application.config.action_policy.auto_inject_into_channel
65
+
66
+ ActionCable::Channel::Base.include ActionPolicy::Channel
67
+
68
+ next unless Rails.application.config.action_policy.channel_authorize_current_user
69
+
70
+ ActionCable::Channel::Base.authorize :user, through: :current_user
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_policy/rspec/be_authorized_to"
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_policy/testing"
4
+
5
+ module ActionPolicy
6
+ module RSpec
7
+ # Authorization matcher `be_authorized_to`.
8
+ #
9
+ # Verifies that a block of code has been authorized using specific policy.
10
+ #
11
+ # Example:
12
+ #
13
+ # # in controller/request specs
14
+ # subject { patch :update, id: product.id }
15
+ #
16
+ # it "is authorized" do
17
+ # expect { subject }
18
+ # .to be_authorized_to(:manage?, product)
19
+ # .with(ProductPolicy)
20
+ # end
21
+ #
22
+ class BeAuthorizedTo < ::RSpec::Matchers::BuiltIn::BaseMatcher
23
+ attr_reader :rule, :target, :policy, :actual_calls
24
+
25
+ def initialize(rule, target)
26
+ @rule = rule
27
+ @target = target
28
+ end
29
+
30
+ def with(policy)
31
+ @policy = policy
32
+ self
33
+ end
34
+
35
+ def match(_expected, actual)
36
+ raise "This matcher only supports block expectations" unless actual.is_a?(Proc)
37
+
38
+ @policy ||= ::ActionPolicy.lookup(target)
39
+
40
+ begin
41
+ ActionPolicy::Testing::AuthorizeTracker.tracking { actual.call }
42
+ rescue ActionPolicy::Unauthorized # rubocop: disable Lint/HandleExceptions
43
+ # we don't want to care about authorization result
44
+ end
45
+
46
+ @actual_calls = ActionPolicy::Testing::AuthorizeTracker.calls
47
+
48
+ actual_calls.any? { |call| call.matches?(policy, rule, target) }
49
+ end
50
+
51
+ def does_not_match?(*)
52
+ raise "This matcher doesn't support negation"
53
+ end
54
+
55
+ def supports_block_expectations?
56
+ true
57
+ end
58
+
59
+ def failure_message
60
+ "expected #{formatted_record} " \
61
+ "to be authorized with #{policy}##{rule}, " \
62
+ "but #{actual_calls_message}"
63
+ end
64
+
65
+ def actual_calls_message
66
+ if actual_calls.empty?
67
+ "no authorization calls have been made"
68
+ else
69
+ "the following calls were encountered:\n" \
70
+ "#{formatted_calls}"
71
+ end
72
+ end
73
+
74
+ def formatted_calls
75
+ actual_calls.map do |acall|
76
+ " - #{acall.inspect}"
77
+ end.join("\n")
78
+ end
79
+
80
+ def formatted_record(record = target)
81
+ ::RSpec::Support::ObjectFormatter.format(record)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ RSpec.configure do |config|
88
+ config.include(Module.new do
89
+ def be_authorized_to(rule, target)
90
+ ActionPolicy::RSpec::BeAuthorizedTo.new(rule, target)
91
+ end
92
+ end)
93
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ module RSpec
5
+ # Adds Pundit-style syntax for testing policies
6
+ module PunditSyntax # :nodoc: all
7
+ module Matchers
8
+ extend ::RSpec::Matchers::DSL
9
+
10
+ matcher :permit do |user, record|
11
+ match do |policy|
12
+ permissions.all? do |permission|
13
+ policy.new(record, user: user).apply(permission)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ module DSL
20
+ def permissions(*list, &block)
21
+ describe list.to_sentence do
22
+ let(:permissions) { list }
23
+
24
+ instance_eval(&block)
25
+ end
26
+ end
27
+ end
28
+
29
+ module PolicyExampleGroup
30
+ include Matchers
31
+
32
+ def self.included(base)
33
+ base.metadata[:type] = :policy
34
+ base.extend DSL
35
+ super
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ RSpec.configure do |config|
43
+ config.include(
44
+ ActionPolicy::RSpec::PunditSyntax::PolicyExampleGroup,
45
+ type: :policy,
46
+ example_group: { file_path: %r{spec/policies} }
47
+ )
48
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_policy/testing"
4
+
5
+ module ActionPolicy
6
+ # Provides assertions for policies usage
7
+ module TestHelper
8
+ # Asserts that the given policy was used to authorize the given target.
9
+ #
10
+ # def test_authorize
11
+ # assert_authorized_to(:show?, user, with: UserPolicy) do
12
+ # get :show, id: user.id
13
+ # end
14
+ # end
15
+ #
16
+ # You can omit the policy (then it would be inferred from the target):
17
+ #
18
+ # assert_authorized_to(:show?, user) do
19
+ # get :show, id: user.id
20
+ # end
21
+ #
22
+ # rubocop: disable Metrics/MethodLength
23
+ def assert_authorized_to(rule, target, with: nil)
24
+ raise ArgumentError, "Block is required" unless block_given?
25
+
26
+ policy = with || ::ActionPolicy.lookup(target)
27
+
28
+ begin
29
+ ActionPolicy::Testing::AuthorizeTracker.tracking { yield }
30
+ rescue ActionPolicy::Unauthorized # rubocop: disable Lint/HandleExceptions
31
+ # we don't want to care about authorization result
32
+ end
33
+
34
+ actual_calls = ActionPolicy::Testing::AuthorizeTracker.calls
35
+
36
+ assert(
37
+ actual_calls.any? { |call| call.matches?(policy, rule, target) },
38
+ "Expected #{target.inspect} to be authorized with #{policy}##{rule}, " \
39
+ "but no such authorization has been made.\n" \
40
+ "Registered authorizations: " \
41
+ "#{actual_calls.empty? ? 'none' : actual_calls.map(&:inspect).join(',')}"
42
+ )
43
+ end
44
+ # rubocop: enable Metrics/MethodLength
45
+ end
46
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ # Testing utils
5
+ module Testing
6
+ # Collects all Authorizer calls
7
+ module AuthorizeTracker
8
+ class Call # :nodoc:
9
+ attr_reader :policy, :rule
10
+
11
+ def initialize(policy, rule)
12
+ @policy = policy
13
+ @rule = rule
14
+ end
15
+
16
+ def matches?(policy_class, actual_rule, target)
17
+ policy_class == policy.class &&
18
+ target == policy.record &&
19
+ rule == actual_rule
20
+ end
21
+
22
+ def inspect
23
+ "#{policy.record.inspect} was authorized with #{policy.class}##{rule}"
24
+ end
25
+ end
26
+
27
+ class << self
28
+ # Wrap code under inspection into this method
29
+ # to track authorize! calls
30
+ def tracking
31
+ calls.clear
32
+ Thread.current[:__action_policy_tracking] = true
33
+ yield
34
+ ensure
35
+ Thread.current[:__action_policy_tracking] = false
36
+ end
37
+
38
+ # Called from Authorizer
39
+ def track(policy, rule)
40
+ return unless tracking?
41
+ calls << Call.new(policy, rule)
42
+ end
43
+
44
+ def calls
45
+ Thread.current[:__action_policy_calls] ||= []
46
+ end
47
+
48
+ def tracking?
49
+ Thread.current[:__action_policy_tracking] == true
50
+ end
51
+ end
52
+ end
53
+
54
+ # Extend authorizer to add tracking functionality
55
+ module AuthorizerExt
56
+ def call(*args)
57
+ AuthorizeTracker.track(*args)
58
+ super
59
+ end
60
+ end
61
+
62
+ ActionPolicy::Authorizer.singleton_class.prepend(AuthorizerExt)
63
+ end
64
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionPolicy
2
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
3
5
  end
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.0.1
4
+ version: 0.1.0
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-02-12 00:00:00.000000000 Z
11
+ date: 2018-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -39,20 +53,48 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: '10.0'
41
55
  - !ruby/object:Gem::Dependency
42
- name: minitest
56
+ name: rspec
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '5.0'
61
+ version: '3.3'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '5.0'
55
- description: 'WIP: Authorization framework'
68
+ version: '3.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.51'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.51'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-md
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.2'
97
+ description: Authorization framework for Ruby/Rails application
56
98
  email:
57
99
  - dementiev.vm@gmail.com
58
100
  executables: []
@@ -60,7 +102,9 @@ extensions: []
60
102
  extra_rdoc_files: []
61
103
  files:
62
104
  - ".gitignore"
105
+ - ".rubocop.yml"
63
106
  - ".travis.yml"
107
+ - CHANGELOG.md
64
108
  - Gemfile
65
109
  - LICENSE.txt
66
110
  - README.md
@@ -68,7 +112,69 @@ files:
68
112
  - action_policy.gemspec
69
113
  - bin/console
70
114
  - bin/setup
115
+ - docs/.nojekyll
116
+ - docs/CNAME
117
+ - docs/README.md
118
+ - docs/_sidebar.md
119
+ - docs/aliases.md
120
+ - docs/assets/docsify.min.js
121
+ - docs/assets/fonts/FiraCode-Medium.woff
122
+ - docs/assets/fonts/FiraCode-Regular.woff
123
+ - docs/assets/images/cache.png
124
+ - docs/assets/images/cache.svg
125
+ - docs/assets/images/layer.png
126
+ - docs/assets/images/layer.svg
127
+ - docs/assets/prism-ruby.min.js
128
+ - docs/assets/styles.css
129
+ - docs/assets/vue.min.css
130
+ - docs/authorization_context.md
131
+ - docs/caching.md
132
+ - docs/custom_lookup_chain.md
133
+ - docs/custom_policy.md
134
+ - docs/favicon.ico
135
+ - docs/i18n.md
136
+ - docs/index.html
137
+ - docs/instrumentation.md
138
+ - docs/lookup_chain.md
139
+ - docs/namespaces.md
140
+ - docs/non_rails.md
141
+ - docs/pre_checks.md
142
+ - docs/quick_start.md
143
+ - docs/rails.md
144
+ - docs/reasons.md
145
+ - docs/testing.md
146
+ - docs/writing_policies.md
147
+ - gemfiles/jruby.gemfile
148
+ - gemfiles/rails42.gemfile
149
+ - gemfiles/railsmaster.gemfile
71
150
  - lib/action_policy.rb
151
+ - lib/action_policy/authorizer.rb
152
+ - lib/action_policy/base.rb
153
+ - lib/action_policy/behaviour.rb
154
+ - lib/action_policy/behaviours/memoized.rb
155
+ - lib/action_policy/behaviours/namespaced.rb
156
+ - lib/action_policy/behaviours/policy_for.rb
157
+ - lib/action_policy/behaviours/thread_memoized.rb
158
+ - lib/action_policy/ext/module_namespace.rb
159
+ - lib/action_policy/ext/policy_cache_key.rb
160
+ - lib/action_policy/ext/string_constantize.rb
161
+ - lib/action_policy/lookup_chain.rb
162
+ - lib/action_policy/policy/aliases.rb
163
+ - lib/action_policy/policy/authorization.rb
164
+ - lib/action_policy/policy/cache.rb
165
+ - lib/action_policy/policy/cached_apply.rb
166
+ - lib/action_policy/policy/core.rb
167
+ - lib/action_policy/policy/defaults.rb
168
+ - lib/action_policy/policy/pre_check.rb
169
+ - lib/action_policy/policy/reasons.rb
170
+ - lib/action_policy/rails/channel.rb
171
+ - lib/action_policy/rails/controller.rb
172
+ - lib/action_policy/railtie.rb
173
+ - lib/action_policy/rspec.rb
174
+ - lib/action_policy/rspec/be_authorized_to.rb
175
+ - lib/action_policy/rspec/pundit_syntax.rb
176
+ - lib/action_policy/test_helper.rb
177
+ - lib/action_policy/testing.rb
72
178
  - lib/action_policy/version.rb
73
179
  homepage: https://github.com/palkan/action-policy
74
180
  licenses:
@@ -82,7 +188,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
188
  requirements:
83
189
  - - ">="
84
190
  - !ruby/object:Gem::Version
85
- version: '0'
191
+ version: 2.3.0
86
192
  required_rubygems_version: !ruby/object:Gem::Requirement
87
193
  requirements:
88
194
  - - ">="
@@ -90,8 +196,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
196
  version: '0'
91
197
  requirements: []
92
198
  rubyforge_project:
93
- rubygems_version: 2.6.13
199
+ rubygems_version: 2.7.4
94
200
  signing_key:
95
201
  specification_version: 4
96
- summary: 'WIP: Authorization framework'
202
+ summary: Authorization framework for Ruby/Rails application
97
203
  test_files: []