action_policy 0.5.0 → 0.5.5

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -2
  3. data/config/rubocop-rspec.yml +17 -0
  4. data/lib/.rbnext/1995.next/action_policy/behaviours/policy_for.rb +62 -0
  5. data/lib/.rbnext/1995.next/action_policy/behaviours/scoping.rb +35 -0
  6. data/lib/.rbnext/1995.next/action_policy/policy/authorization.rb +87 -0
  7. data/lib/.rbnext/1995.next/action_policy/utils/pretty_print.rb +159 -0
  8. data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +62 -0
  9. data/lib/.rbnext/2.7/action_policy/i18n.rb +56 -0
  10. data/lib/.rbnext/2.7/action_policy/policy/cache.rb +101 -0
  11. data/lib/.rbnext/2.7/action_policy/policy/pre_check.rb +162 -0
  12. data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +89 -0
  13. data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +124 -0
  14. data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +159 -0
  15. data/lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb +59 -0
  16. data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +72 -0
  17. data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +69 -0
  18. data/lib/.rbnext/3.0/action_policy/policy/cache.rb +101 -0
  19. data/lib/.rbnext/3.0/action_policy/policy/core.rb +161 -0
  20. data/lib/.rbnext/3.0/action_policy/policy/defaults.rb +31 -0
  21. data/lib/.rbnext/3.0/action_policy/policy/execution_result.rb +37 -0
  22. data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +162 -0
  23. data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +212 -0
  24. data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +89 -0
  25. data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +124 -0
  26. data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +159 -0
  27. data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +19 -0
  28. data/lib/action_policy/behaviour.rb +2 -2
  29. data/lib/action_policy/behaviours/memoized.rb +1 -1
  30. data/lib/action_policy/behaviours/namespaced.rb +1 -1
  31. data/lib/action_policy/behaviours/policy_for.rb +1 -1
  32. data/lib/action_policy/behaviours/scoping.rb +2 -2
  33. data/lib/action_policy/behaviours/thread_memoized.rb +1 -1
  34. data/lib/action_policy/lookup_chain.rb +11 -27
  35. data/lib/action_policy/policy/aliases.rb +2 -2
  36. data/lib/action_policy/policy/authorization.rb +2 -2
  37. data/lib/action_policy/policy/cache.rb +2 -2
  38. data/lib/action_policy/policy/pre_check.rb +2 -2
  39. data/lib/action_policy/policy/reasons.rb +4 -2
  40. data/lib/action_policy/policy/scoping.rb +2 -2
  41. data/lib/action_policy/test_helper.rb +1 -0
  42. data/lib/action_policy/version.rb +1 -1
  43. metadata +29 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4fc0130963013d2a27c7abf48817dba07345f15fde792a1d9b55633de820c318
4
- data.tar.gz: 238306ff0b289bbe89e69c7805c57cee46d1f1bfb89479bc35b34c801dad6994
3
+ metadata.gz: 939f6e73eca2c33b4f9d3575fde063ac346e46bcda7ab05c062ea9a35edf7252
4
+ data.tar.gz: 8c200d093dd9efa19d6cabd811c4ce29f7c4420efd3e8052c863adbf43c2a632
5
5
  SHA512:
6
- metadata.gz: 192f5beabda0c3d0ad49deee958107b919e50eb1dc20e79df3fc96f8ee59f274eedb93e96f4d18614a58dc3df57b4f363ec360d40c3dfe42a2d0b2fca0eb6f81
7
- data.tar.gz: ce790734997fbb3f6ac38bf9dea4aee0fd9a5c6dbe8442bb48fe2724f6e77574823d512444f398a5a2ad06b5b302d1a8ac031e50a62ea13830e6c38f2f75cd60
6
+ metadata.gz: 380f476a6716d6fc096d1b6abb6ef68eba62c4ad5fb761427754870582550a25394f24c0b29bab711785ab4806812b9c20ca0ec26f0f4efe7ddca11b762a9585
7
+ data.tar.gz: 0a63c058b49e2063af867ee74a78066e1e39f5a22d226748ee5780543e40287d672bfc177bedaf91f7036168ac72746a9128dc222a4d8a7bfe0690cdda7b5b49
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.5.5 (2020-12-28)
6
+
7
+ - Upgrade to Ruby 3.0. ([@palkan][])
8
+
9
+ ## 0.5.4 (2020-12-09)
10
+
11
+ - Add support for RSpec aliases detection when linting policy specs with `rubocop-rspec` 2.0 ([@pirj][])
12
+
13
+ - Fix `strict_namespace: true` lookup option not finding policies in global namespace ([@Be-ngt-oH][])
14
+
5
15
  ## 0.5.0 (2020-09-29)
6
16
 
7
17
  - Move `deny!` / `allow!` to core. ([@palkan][])
@@ -37,7 +47,7 @@ This method is similar to `allowed_to?` but returns an authorization result obje
37
47
 
38
48
  Fixes [#122](https://github.com/palkan/action_policy/issues/122).
39
49
 
40
- - Separated `#classify`-based and `#camelize`-based symbol lookups. ([Be-ngt-oH][])
50
+ - Separated `#classify`-based and `#camelize`-based symbol lookups. ([@Be-ngt-oH][])
41
51
 
42
52
  Only affects Rails apps. Now lookup for `:users` tries to find `UsersPolicy` first (camelize),
43
53
  and only then search for `UserPolicy` (classify).
@@ -425,7 +435,8 @@ This value is now stored in a cache (if any) instead of just the call result (`t
425
435
  [@ilyasgaraev]: https://github.com/ilyasgaraev
426
436
  [@brendon]: https://github.com/brendon
427
437
  [@DmitryTsepelev]: https://github.com/DmitryTsepelev
428
- [@korolvs]: https://github.com/korolvs
438
+ [@korolvs]: https://github.com/slavadev
429
439
  [@nicolas-brousse]: https://github.com/nicolas-brousse
430
440
  [@somenugget]: https://github.com/somenugget
431
441
  [@Be-ngt-oH]: https://github.com/Be-ngt-oH
442
+ [@pirj]: https://github.com/pirj
@@ -0,0 +1,17 @@
1
+ RSpec:
2
+ Language:
3
+ ExampleGroups:
4
+ Regular:
5
+ - describe_rule
6
+ Focused:
7
+ - fdescribe_rule
8
+ Skipped:
9
+ - xdescribe_rule
10
+ Includes:
11
+ Examples:
12
+ - succeed
13
+ - failed
14
+ - fsucceed
15
+ - ffailed
16
+ - xsucceed
17
+ - xfailed
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ module Behaviours
5
+ # Adds `policy_for` method
6
+ module PolicyFor
7
+ require "action_policy/ext/policy_cache_key"
8
+ using ActionPolicy::Ext::PolicyCacheKey
9
+
10
+ # Returns policy instance for the record.
11
+ def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
12
+ policy_class = with || ::ActionPolicy.lookup(
13
+ record,
14
+ namespace: namespace, context: context, allow_nil: allow_nil, default: default
15
+ )
16
+ policy_class&.new(record, **context)
17
+ end
18
+
19
+ def authorization_context
20
+ raise NotImplementedError, "Please, define `authorization_context` method!"
21
+ end
22
+
23
+ def authorization_namespace
24
+ # override to provide specific authorization namespace
25
+ end
26
+
27
+ def default_authorization_policy_class
28
+ # override to provide a policy class use when no policy found
29
+ end
30
+
31
+ # Override this method to provide implicit authorization target
32
+ # that would be used in case `record` is not specified in
33
+ # `authorize!` and `allowed_to?` call.
34
+ #
35
+ # It is also used to infer a policy for scoping (in `authorized_scope` method).
36
+ def implicit_authorization_target
37
+ # no-op
38
+ end
39
+
40
+ # Return implicit authorization target or raises an exception if it's nil
41
+ def implicit_authorization_target!
42
+ implicit_authorization_target || raise(
43
+ NotFound,
44
+ [
45
+ self,
46
+ "Couldn't find implicit authorization target " \
47
+ "for #{self.class}. " \
48
+ "Please, provide policy class explicitly using `with` option or " \
49
+ "define the `implicit_authorization_target` method."
50
+ ]
51
+ )
52
+ end
53
+
54
+ def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **)
55
+ record_key = record._policy_cache_key(use_object_id: true)
56
+ context_key = context.values.map { _1._policy_cache_key(use_object_id: true) }.join(".")
57
+
58
+ "#{namespace}/#{with}/#{context_key}/#{record_key}"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ module Behaviours
5
+ # Adds `authorized_scop` method to behaviour
6
+ module Scoping
7
+ # Apply scope to the target of the specified type.
8
+ #
9
+ # NOTE: policy lookup consists of the following steps:
10
+ # - first, check whether `with` option is present
11
+ # - secondly, try to infer policy class from `target` (non-raising lookup)
12
+ # - use `implicit_authorization_target` if none of the above works.
13
+ def authorized_scope(target, type: nil, as: :default, scope_options: nil, **options)
14
+ options[:context] && (options[:context] = authorization_context.merge(options[:context]))
15
+
16
+ policy = policy_for(record: target, allow_nil: true, **options)
17
+ policy ||= policy_for(record: implicit_authorization_target!, **options)
18
+
19
+ type ||= authorization_scope_type_for(policy, target)
20
+ name = as
21
+
22
+ Authorizer.scopify(target, policy, type: type, name: name, scope_options: scope_options)
23
+ end
24
+
25
+ # For backward compatibility
26
+ alias_method :authorized, :authorized_scope
27
+
28
+ # Infer scope type for target if none provided.
29
+ # Raises an exception if type couldn't be inferred.
30
+ def authorization_scope_type_for(policy, target)
31
+ policy.resolve_scope_type(target)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ class AuthorizationContextMissing < Error # :nodoc:
5
+ MESSAGE_TEMPLATE = "Missing policy authorization context: %s"
6
+
7
+ attr_reader :message
8
+
9
+ def initialize(id)
10
+ @message = MESSAGE_TEMPLATE % id
11
+ end
12
+ end
13
+
14
+ module Policy
15
+ # Authorization context could include multiple parameters.
16
+ #
17
+ # It is possible to provide more verificatio contexts, by specifying them in the policy and
18
+ # providing them at the authorization step.
19
+ #
20
+ # For example:
21
+ #
22
+ # class ApplicationPolicy < ActionPolicy::Base
23
+ # # Add user and account to the context; it's required to be passed
24
+ # # to a policy constructor and be not nil
25
+ # authorize :user, :account
26
+ #
27
+ # # you can skip non-nil check if you want
28
+ # # authorize :account, allow_nil: true
29
+ #
30
+ # def manage?
31
+ # # available as a simple accessor
32
+ # account.enabled?
33
+ # end
34
+ # end
35
+ #
36
+ # ApplicantPolicy.new(user: user, account: account)
37
+ module Authorization
38
+ class << self
39
+ def included(base)
40
+ base.extend ClassMethods
41
+ end
42
+ end
43
+
44
+ attr_reader :authorization_context
45
+
46
+ def initialize(record = nil, **params)
47
+ super(record)
48
+
49
+ @authorization_context = {}
50
+
51
+ self.class.authorization_targets.each do |id, opts|
52
+ raise AuthorizationContextMissing, id unless params.key?(id) || opts[:optional]
53
+
54
+ val = params.fetch(id, nil)
55
+
56
+ raise AuthorizationContextMissing, id if val.nil? && opts[:allow_nil] != true
57
+
58
+ authorization_context[id] = instance_variable_set("@#{id}", val)
59
+ end
60
+
61
+ authorization_context.freeze
62
+ end
63
+
64
+ module ClassMethods # :nodoc:
65
+ def authorize(*ids, allow_nil: false, optional: false)
66
+ allow_nil ||= optional
67
+
68
+ ids.each do |id|
69
+ authorization_targets[id] = {allow_nil: allow_nil, optional: optional}
70
+ end
71
+
72
+ attr_reader(*ids)
73
+ end
74
+
75
+ def authorization_targets
76
+ return @authorization_targets if instance_variable_defined?(:@authorization_targets)
77
+
78
+ @authorization_targets = if superclass.respond_to?(:authorization_targets)
79
+ superclass.authorization_targets.dup
80
+ else
81
+ {}
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ old_verbose = $VERBOSE
4
+
5
+ begin
6
+ require "method_source"
7
+ # Ignore parse warnings when patch
8
+ # Ruby version mismatches
9
+ $VERBOSE = nil
10
+ require "parser/current"
11
+ require "unparser"
12
+ rescue LoadError
13
+ # do nothing
14
+ ensure
15
+ $VERBOSE = old_verbose
16
+ end
17
+
18
+ module ActionPolicy
19
+ using RubyNext
20
+
21
+ # Takes the object and a method name,
22
+ # and returns the "annotated" source code for the method:
23
+ # code is split into parts by logical operators and each
24
+ # part is evaluated separately.
25
+ #
26
+ # Example:
27
+ #
28
+ # class MyClass
29
+ # def access?
30
+ # admin? && access_feed?
31
+ # end
32
+ # end
33
+ #
34
+ # puts PrettyPrint.format_method(MyClass.new, :access?)
35
+ #
36
+ # #=> MyClass#access?
37
+ # #=> ↳ admin? #=> false
38
+ # #=> AND
39
+ # #=> access_feed? #=> true
40
+ module PrettyPrint
41
+ TRUE = "\e[32mtrue\e[0m"
42
+ FALSE = "\e[31mfalse\e[0m"
43
+
44
+ class Visitor
45
+ attr_reader :lines, :object
46
+ attr_accessor :indent
47
+
48
+ def initialize(object)
49
+ @object = object
50
+ end
51
+
52
+ def collect(ast)
53
+ @lines = []
54
+ @indent = 0
55
+
56
+ visit_node(ast)
57
+
58
+ lines.join("\n")
59
+ end
60
+
61
+ def visit_node(ast)
62
+ if respond_to?("visit_#{ast.type}")
63
+ send("visit_#{ast.type}", ast)
64
+ else
65
+ visit_missing ast
66
+ end
67
+ end
68
+
69
+ def expression_with_result(sexp)
70
+ expression = Unparser.unparse(sexp)
71
+ "#{expression} #=> #{PrettyPrint.colorize(eval_exp(expression))}"
72
+ end
73
+
74
+ def eval_exp(exp)
75
+ return "<skipped>" if ignore_exp?(exp)
76
+ object.instance_eval(exp)
77
+ rescue => e
78
+ "Failed: #{e.message}"
79
+ end
80
+
81
+ def visit_and(ast)
82
+ visit_node(ast.children[0])
83
+ lines << indented("AND")
84
+ visit_node(ast.children[1])
85
+ end
86
+
87
+ def visit_or(ast)
88
+ visit_node(ast.children[0])
89
+ lines << indented("OR")
90
+ visit_node(ast.children[1])
91
+ end
92
+
93
+ def visit_begin(ast)
94
+ # Parens
95
+ if ast.children.size == 1
96
+ lines << indented("(")
97
+ self.indent += 2
98
+ visit_node(ast.children[0])
99
+ self.indent -= 2
100
+ lines << indented(")")
101
+ else
102
+ # Multiple expressions
103
+ ast.children.each do |node|
104
+ visit_node(node)
105
+ # restore indent after each expression
106
+ self.indent -= 2
107
+ end
108
+ end
109
+ end
110
+
111
+ def visit_missing(ast)
112
+ lines << indented(expression_with_result(ast))
113
+ end
114
+
115
+ def indented(str)
116
+ "#{indent.zero? ? "↳ " : ""}#{" " * indent}#{str}".tap do
117
+ # increase indent after the first expression
118
+ self.indent += 2 if indent.zero?
119
+ end
120
+ end
121
+
122
+ # Some lines should not be evaled
123
+ def ignore_exp?(exp)
124
+ PrettyPrint.ignore_expressions.any? { exp.match?(_1) }
125
+ end
126
+ end
127
+
128
+ class << self
129
+ attr_accessor :ignore_expressions
130
+
131
+ if defined?(::Unparser) && defined?(::MethodSource)
132
+ def available?() = true
133
+
134
+ def print_method(object, method_name)
135
+ ast = object.method(method_name).source.then(&Unparser.method(:parse))
136
+ # outer node is a method definition itself
137
+ body = ast.children[2]
138
+
139
+ Visitor.new(object).collect(body)
140
+ end
141
+ else
142
+ def available?() = false
143
+
144
+ def print_method(_, _) = ""
145
+ end
146
+
147
+ def colorize(val)
148
+ return val unless $stdout.isatty
149
+ return TRUE if val.eql?(true)
150
+ return FALSE if val.eql?(false)
151
+ val
152
+ end
153
+ end
154
+
155
+ self.ignore_expressions = [
156
+ /^\s*binding\.(pry|irb)\s*$/s
157
+ ]
158
+ end
159
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPolicy
4
+ module Behaviours
5
+ # Adds `policy_for` method
6
+ module PolicyFor
7
+ require "action_policy/ext/policy_cache_key"
8
+ using ActionPolicy::Ext::PolicyCacheKey
9
+
10
+ # Returns policy instance for the record.
11
+ def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
12
+ policy_class = with || ::ActionPolicy.lookup(
13
+ record,
14
+ namespace: namespace, context: context, allow_nil: allow_nil, default: default
15
+ )
16
+ policy_class&.new(record, **context)
17
+ end
18
+
19
+ def authorization_context
20
+ raise NotImplementedError, "Please, define `authorization_context` method!"
21
+ end
22
+
23
+ def authorization_namespace
24
+ # override to provide specific authorization namespace
25
+ end
26
+
27
+ def default_authorization_policy_class
28
+ # override to provide a policy class use when no policy found
29
+ end
30
+
31
+ # Override this method to provide implicit authorization target
32
+ # that would be used in case `record` is not specified in
33
+ # `authorize!` and `allowed_to?` call.
34
+ #
35
+ # It is also used to infer a policy for scoping (in `authorized_scope` method).
36
+ def implicit_authorization_target
37
+ # no-op
38
+ end
39
+
40
+ # Return implicit authorization target or raises an exception if it's nil
41
+ def implicit_authorization_target!
42
+ implicit_authorization_target || raise(
43
+ NotFound,
44
+ [
45
+ self,
46
+ "Couldn't find implicit authorization target " \
47
+ "for #{self.class}. " \
48
+ "Please, provide policy class explicitly using `with` option or " \
49
+ "define the `implicit_authorization_target` method."
50
+ ]
51
+ )
52
+ end
53
+
54
+ def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **)
55
+ record_key = record._policy_cache_key(use_object_id: true)
56
+ context_key = context.values.map { |_1| _1._policy_cache_key(use_object_id: true) }.join(".")
57
+
58
+ "#{namespace}/#{with}/#{context_key}/#{record_key}"
59
+ end
60
+ end
61
+ end
62
+ end