action_policy 0.5.0 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
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