action_policy 0.5.3 → 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -2
  3. data/LICENSE.txt +1 -1
  4. data/README.md +1 -1
  5. data/config/rubocop-rspec.yml +17 -0
  6. data/lib/.rbnext/{3.0 → 1995.next}/action_policy/behaviours/policy_for.rb +1 -1
  7. data/lib/.rbnext/{3.0 → 1995.next}/action_policy/behaviours/scoping.rb +2 -2
  8. data/lib/.rbnext/{3.0 → 1995.next}/action_policy/policy/authorization.rb +0 -0
  9. data/lib/.rbnext/1995.next/action_policy/utils/pretty_print.rb +159 -0
  10. data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +1 -1
  11. data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +2 -2
  12. data/lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb +1 -1
  13. data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +8 -0
  14. data/lib/.rbnext/3.0/action_policy/policy/core.rb +9 -2
  15. data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +2 -2
  16. data/lib/action_policy.rb +2 -0
  17. data/lib/action_policy/behaviour.rb +2 -2
  18. data/lib/action_policy/behaviours/memoized.rb +1 -1
  19. data/lib/action_policy/behaviours/namespaced.rb +1 -1
  20. data/lib/action_policy/behaviours/policy_for.rb +1 -1
  21. data/lib/action_policy/behaviours/scoping.rb +2 -2
  22. data/lib/action_policy/behaviours/thread_memoized.rb +1 -1
  23. data/lib/action_policy/lookup_chain.rb +11 -27
  24. data/lib/action_policy/policy/aliases.rb +10 -2
  25. data/lib/action_policy/policy/authorization.rb +2 -2
  26. data/lib/action_policy/policy/cache.rb +2 -2
  27. data/lib/action_policy/policy/core.rb +9 -2
  28. data/lib/action_policy/policy/pre_check.rb +2 -2
  29. data/lib/action_policy/policy/reasons.rb +2 -2
  30. data/lib/action_policy/policy/scoping.rb +2 -2
  31. data/lib/action_policy/test_helper.rb +1 -0
  32. data/lib/action_policy/utils/pretty_print.rb +2 -2
  33. data/lib/action_policy/version.rb +1 -1
  34. metadata +9 -9
  35. data/lib/.rbnext/3.0/action_policy/behaviour.rb +0 -115
  36. data/lib/.rbnext/3.0/action_policy/policy/scoping.rb +0 -160
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e0d9085b8204e1846c9820dbc5eb7a9960fa9c512a51d942cc8aab16b065257
4
- data.tar.gz: c85bf2348affc45eb365200070257bc31b873ded038033a0fb3d6f5ca8adb48d
3
+ metadata.gz: 021e6da5ccd46f76732cb52c0c04ae1a45ce3a74a2b64643e179ee16c40417e3
4
+ data.tar.gz: 96a978308fde0160f5b739d47abdd833005ba7109e97837ac0dd5721f1ec52a8
5
5
  SHA512:
6
- metadata.gz: 2a1de70b5460eed8dc481a651ea52318e060fb486e604b0595f0fa09a4840a0eb93be2b90b7f20789cb135e62388f00a18650881e9739564ba311d11a3fbd131
7
- data.tar.gz: 5cbcf29add9f224ce3b77a42a17ab403dec7f2f93a581f6ad25e13d8f1e75bc650d9889cb05dd247355fee6b6844fb400852f51b2d1118ac30076e5277b95e75
6
+ metadata.gz: 69953fedcbaa8d007c3c922e17dd26618eda2ed840d59bac476111f62bb7aae7c20f2920606925eb4b3acab7e90f994a6e1a9a55b7de0a0740f3747bb4e85aa3
7
+ data.tar.gz: 23de3c82949db85f3a41f079187493087add27f88e0cd9315ccb492e925f4490390d5ae17fb493eb3ae290f5469f216ab258e21e7c155be9853de5728c8b3311
data/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.5.7 (2021-03-03)
6
+
7
+ The previous release had incorrect dependencies (due to the missing transpiled files).
8
+
9
+ ## ~~0.5.6 (2021-03-03)~~
10
+
11
+ - Add `ActionPolicy.enforce_predicate_rules_naming` config to catch rule missing question mark ([@skojin][])
12
+
13
+ ## 0.5.5 (2020-12-28)
14
+
15
+ - Upgrade to Ruby 3.0. ([@palkan][])
16
+
17
+ ## 0.5.4 (2020-12-09)
18
+
19
+ - Add support for RSpec aliases detection when linting policy specs with `rubocop-rspec` 2.0 ([@pirj][])
20
+
21
+ - Fix `strict_namespace: true` lookup option not finding policies in global namespace ([@Be-ngt-oH][])
22
+
5
23
  ## 0.5.0 (2020-09-29)
6
24
 
7
25
  - Move `deny!` / `allow!` to core. ([@palkan][])
@@ -37,7 +55,7 @@ This method is similar to `allowed_to?` but returns an authorization result obje
37
55
 
38
56
  Fixes [#122](https://github.com/palkan/action_policy/issues/122).
39
57
 
40
- - Separated `#classify`-based and `#camelize`-based symbol lookups. ([Be-ngt-oH][])
58
+ - Separated `#classify`-based and `#camelize`-based symbol lookups. ([@Be-ngt-oH][])
41
59
 
42
60
  Only affects Rails apps. Now lookup for `:users` tries to find `UsersPolicy` first (camelize),
43
61
  and only then search for `UserPolicy` (classify).
@@ -425,7 +443,9 @@ This value is now stored in a cache (if any) instead of just the call result (`t
425
443
  [@ilyasgaraev]: https://github.com/ilyasgaraev
426
444
  [@brendon]: https://github.com/brendon
427
445
  [@DmitryTsepelev]: https://github.com/DmitryTsepelev
428
- [@korolvs]: https://github.com/korolvs
446
+ [@korolvs]: https://github.com/slavadev
429
447
  [@nicolas-brousse]: https://github.com/nicolas-brousse
430
448
  [@somenugget]: https://github.com/somenugget
431
449
  [@Be-ngt-oH]: https://github.com/Be-ngt-oH
450
+ [@pirj]: https://github.com/pirj
451
+ [@skojin]: https://github.com/skojin
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018-2020 Vladimir Dementyev
3
+ Copyright (c) 2018-2021 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
@@ -31,7 +31,7 @@ Composable. Extensible. Performant.
31
31
  Add this line to your application's `Gemfile`:
32
32
 
33
33
  ```ruby
34
- gem "action_policy", "~> 0.4.0"
34
+ gem "action_policy"
35
35
  ```
36
36
 
37
37
  And then execute:
@@ -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
@@ -11,7 +11,7 @@ module ActionPolicy
11
11
  def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
12
12
  policy_class = with || ::ActionPolicy.lookup(
13
13
  record,
14
- **{namespace: namespace, context: context, allow_nil: allow_nil, default: default}
14
+ namespace: namespace, context: context, allow_nil: allow_nil, default: default
15
15
  )
16
16
  policy_class&.new(record, **context)
17
17
  end
@@ -19,11 +19,11 @@ module ActionPolicy
19
19
  type ||= authorization_scope_type_for(policy, target)
20
20
  name = as
21
21
 
22
- Authorizer.scopify(target, policy, **{type: type, name: name, scope_options: scope_options})
22
+ Authorizer.scopify(target, policy, type: type, name: name, scope_options: scope_options)
23
23
  end
24
24
 
25
25
  # For backward compatibility
26
- alias authorized authorized_scope
26
+ alias_method :authorized, :authorized_scope
27
27
 
28
28
  # Infer scope type for target if none provided.
29
29
  # Raises an exception if type couldn't be inferred.
@@ -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) # rubocop:disable Lint/DeprecatedConstants
150
+ return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
151
+ val
152
+ end
153
+ end
154
+
155
+ self.ignore_expressions = [
156
+ /^\s*binding\.(pry|irb)\s*$/s
157
+ ]
158
+ end
159
+ end
@@ -11,7 +11,7 @@ module ActionPolicy
11
11
  def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
12
12
  policy_class = with || ::ActionPolicy.lookup(
13
13
  record,
14
- **{namespace: namespace, context: context, allow_nil: allow_nil, default: default}
14
+ namespace: namespace, context: context, allow_nil: allow_nil, default: default
15
15
  )
16
16
  policy_class&.new(record, **context)
17
17
  end
@@ -146,8 +146,8 @@ module ActionPolicy
146
146
 
147
147
  def colorize(val)
148
148
  return val unless $stdout.isatty
149
- return TRUE if val.eql?(true)
150
- return FALSE if val.eql?(false)
149
+ return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
150
+ return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
151
151
  val
152
152
  end
153
153
  end
@@ -40,7 +40,7 @@ module ActionPolicy
40
40
  base.prepend InstanceMethods
41
41
  end
42
42
 
43
- alias included prepended
43
+ alias_method :included, :prepended
44
44
  end
45
45
 
46
46
  module InstanceMethods # :nodoc:
@@ -31,10 +31,18 @@ module ActionPolicy
31
31
  def resolve_rule(activity)
32
32
  self.class.lookup_alias(activity) ||
33
33
  (activity if respond_to?(activity)) ||
34
+ (check_rule_naming(activity) if ActionPolicy.enforce_predicate_rules_naming) ||
34
35
  self.class.lookup_default_rule ||
35
36
  super
36
37
  end
37
38
 
39
+ private def check_rule_naming(activity)
40
+ unless activity[-1] == "?"
41
+ raise NonPredicateRule.new(self, activity)
42
+ end
43
+ nil
44
+ end
45
+
38
46
  module ClassMethods # :nodoc:
39
47
  def default_rule(val)
40
48
  rules_aliases[DEFAULT] = val
@@ -23,12 +23,19 @@ module ActionPolicy
23
23
  def initialize(policy, rule)
24
24
  @policy = policy.class
25
25
  @rule = rule
26
- @message =
27
- "Couldn't find rule '#{@rule}' for #{@policy}" \
26
+ @message = "Couldn't find rule '#{@rule}' for #{@policy}" \
28
27
  "#{suggest(@rule, @policy.instance_methods - Object.instance_methods)}"
29
28
  end
30
29
  end
31
30
 
31
+ class NonPredicateRule < UnknownRule
32
+ def initialize(policy, rule)
33
+ @policy = policy.class
34
+ @rule = rule
35
+ @message = "The rule '#{@rule}' of '#{@policy}' must ends with ? (question mark)\nDid you mean? #{@rule}?"
36
+ end
37
+ end
38
+
32
39
  module Policy
33
40
  # Core policy API
34
41
  module Core
@@ -146,8 +146,8 @@ module ActionPolicy
146
146
 
147
147
  def colorize(val)
148
148
  return val unless $stdout.isatty
149
- return TRUE if val.eql?(true)
150
- return FALSE if val.eql?(false)
149
+ return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
150
+ return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
151
151
  val
152
152
  end
153
153
  end
data/lib/action_policy.rb CHANGED
@@ -34,6 +34,8 @@ module ActionPolicy
34
34
  class << self
35
35
  attr_accessor :cache_store
36
36
 
37
+ attr_accessor :enforce_predicate_rules_naming
38
+
37
39
  # Find a policy class for a target
38
40
  def lookup(target, allow_nil: false, default: nil, **options)
39
41
  LookupChain.call(target, **options) ||
@@ -104,11 +104,11 @@ module ActionPolicy
104
104
  def authorization_targets
105
105
  return @authorization_targets if instance_variable_defined?(:@authorization_targets)
106
106
 
107
- if superclass.respond_to?(:authorization_targets)
107
+ @authorization_targets = if superclass.respond_to?(:authorization_targets)
108
108
  superclass.authorization_targets.dup
109
109
  else
110
110
  {}
111
- end => @authorization_targets
111
+ end
112
112
  end
113
113
  end
114
114
  end
@@ -24,7 +24,7 @@ module ActionPolicy
24
24
  base.prepend InstanceMethods
25
25
  end
26
26
 
27
- alias included prepended
27
+ alias_method :included, :prepended
28
28
  end
29
29
 
30
30
  module InstanceMethods # :nodoc:
@@ -66,7 +66,7 @@ module ActionPolicy
66
66
  base.prepend InstanceMethods
67
67
  end
68
68
 
69
- alias included prepended
69
+ alias_method :included, :prepended
70
70
  end
71
71
 
72
72
  module InstanceMethods # :nodoc:
@@ -11,7 +11,7 @@ module ActionPolicy
11
11
  def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
12
12
  policy_class = with || ::ActionPolicy.lookup(
13
13
  record,
14
- **{namespace, context, allow_nil, default}
14
+ namespace:, context:, allow_nil:, default:
15
15
  )
16
16
  policy_class&.new(record, **context)
17
17
  end
@@ -19,11 +19,11 @@ module ActionPolicy
19
19
  type ||= authorization_scope_type_for(policy, target)
20
20
  name = as
21
21
 
22
- Authorizer.scopify(target, policy, **{type, name, scope_options})
22
+ Authorizer.scopify(target, policy, type:, name:, scope_options:)
23
23
  end
24
24
 
25
25
  # For backward compatibility
26
- alias authorized authorized_scope
26
+ alias_method :authorized, :authorized_scope
27
27
 
28
28
  # Infer scope type for target if none provided.
29
29
  # Raises an exception if type couldn't be inferred.
@@ -40,7 +40,7 @@ module ActionPolicy
40
40
  base.prepend InstanceMethods
41
41
  end
42
42
 
43
- alias included prepended
43
+ alias_method :included, :prepended
44
44
  end
45
45
 
46
46
  module InstanceMethods # :nodoc:
@@ -36,8 +36,7 @@ module ActionPolicy
36
36
  if strict
37
37
  put_if_absent(:strict, namespace, policy, &block)
38
38
  else
39
- cached_policy = put_if_absent(:strict, namespace, policy, &block)
40
- put_if_absent(:flexible, namespace, policy) { cached_policy }
39
+ put_if_absent(:flexible, namespace, policy, &block)
41
40
  end
42
41
  end
43
42
 
@@ -53,7 +52,7 @@ module ActionPolicy
53
52
  class << self
54
53
  attr_accessor :chain, :namespace_cache_enabled
55
54
 
56
- alias namespace_cache_enabled? namespace_cache_enabled
55
+ alias_method :namespace_cache_enabled?, :namespace_cache_enabled
57
56
 
58
57
  def call(record, **opts)
59
58
  chain.each do |probe|
@@ -66,22 +65,17 @@ module ActionPolicy
66
65
  private
67
66
 
68
67
  def lookup_within_namespace(policy_name, namespace, strict: false)
69
- return unless namespace
70
- NamespaceCache.fetch(namespace.name, policy_name, strict: strict) do
68
+ NamespaceCache.fetch(namespace&.name || "Kernel", policy_name, strict: strict) do
71
69
  mod = namespace
72
70
  loop do
73
- policy = "#{mod.name}::#{policy_name}".safe_constantize
74
- break policy unless policy.nil?
71
+ policy = [mod&.name, policy_name].compact.join("::").safe_constantize
72
+ break policy if policy || mod.nil? || strict
73
+
75
74
  mod = mod.namespace
76
- break if mod.nil? || strict
77
75
  end
78
76
  end
79
77
  end
80
78
 
81
- def objectify_policy(policy_name, strict: false)
82
- policy_name.safe_constantize unless strict
83
- end
84
-
85
79
  def policy_class_name_for(record)
86
80
  return record.policy_name.to_s if record.respond_to?(:policy_name)
87
81
 
@@ -110,17 +104,10 @@ module ActionPolicy
110
104
  record.class.policy_class if record.class.respond_to?(:policy_class)
111
105
  }
112
106
 
113
- # Lookup within namespace when provided
114
- NAMESPACE_LOOKUP = ->(record, namespace: nil, **) {
115
- next if namespace.nil?
116
-
117
- policy_name = policy_class_name_for(record)
118
- lookup_within_namespace(policy_name, namespace)
119
- }
120
-
121
107
  # Infer from class name
122
- INFER_FROM_CLASS = ->(record, **) {
123
- policy_class_name_for(record).safe_constantize
108
+ INFER_FROM_CLASS = ->(record, namespace: nil, strict_namespace: false, **) {
109
+ policy_name = policy_class_name_for(record)
110
+ lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
124
111
  }
125
112
 
126
113
  # Infer from passed symbol
@@ -128,8 +115,7 @@ module ActionPolicy
128
115
  next unless record.is_a?(Symbol)
129
116
 
130
117
  policy_name = "#{record.camelize}Policy"
131
- lookup_within_namespace(policy_name, namespace, strict: strict_namespace) ||
132
- objectify_policy(policy_name, strict: strict_namespace)
118
+ lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
133
119
  }
134
120
 
135
121
  # (Optional) Infer using String#classify if available
@@ -137,8 +123,7 @@ module ActionPolicy
137
123
  next unless record.is_a?(Symbol)
138
124
 
139
125
  policy_name = "#{record.to_s.classify}Policy"
140
- lookup_within_namespace(policy_name, namespace, strict: strict_namespace) ||
141
- objectify_policy(policy_name, strict: strict_namespace)
126
+ lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
142
127
  }
143
128
 
144
129
  self.chain = [
@@ -146,7 +131,6 @@ module ActionPolicy
146
131
  (CLASSIFY_SYMBOL_LOOKUP if String.method_defined?(:classify)),
147
132
  INSTANCE_POLICY_CLASS,
148
133
  CLASS_POLICY_CLASS,
149
- NAMESPACE_LOOKUP,
150
134
  INFER_FROM_CLASS
151
135
  ].compact
152
136
  end
@@ -31,10 +31,18 @@ module ActionPolicy
31
31
  def resolve_rule(activity)
32
32
  self.class.lookup_alias(activity) ||
33
33
  (activity if respond_to?(activity)) ||
34
+ (check_rule_naming(activity) if ActionPolicy.enforce_predicate_rules_naming) ||
34
35
  self.class.lookup_default_rule ||
35
36
  super
36
37
  end
37
38
 
39
+ private def check_rule_naming(activity)
40
+ unless activity[-1] == "?"
41
+ raise NonPredicateRule.new(self, activity)
42
+ end
43
+ nil
44
+ end
45
+
38
46
  module ClassMethods # :nodoc:
39
47
  def default_rule(val)
40
48
  rules_aliases[DEFAULT] = val
@@ -53,11 +61,11 @@ module ActionPolicy
53
61
  def rules_aliases
54
62
  return @rules_aliases if instance_variable_defined?(:@rules_aliases)
55
63
 
56
- if superclass.respond_to?(:rules_aliases)
64
+ @rules_aliases = if superclass.respond_to?(:rules_aliases)
57
65
  superclass.rules_aliases.dup
58
66
  else
59
67
  {}
60
- end => @rules_aliases
68
+ end
61
69
  end
62
70
 
63
71
  def method_added(name)
@@ -75,11 +75,11 @@ module ActionPolicy
75
75
  def authorization_targets
76
76
  return @authorization_targets if instance_variable_defined?(:@authorization_targets)
77
77
 
78
- if superclass.respond_to?(:authorization_targets)
78
+ @authorization_targets = if superclass.respond_to?(:authorization_targets)
79
79
  superclass.authorization_targets.dup
80
80
  else
81
81
  {}
82
- end => @authorization_targets
82
+ end
83
83
  end
84
84
  end
85
85
  end
@@ -89,11 +89,11 @@ module ActionPolicy # :nodoc:
89
89
  def cached_rules
90
90
  return @cached_rules if instance_variable_defined?(:@cached_rules)
91
91
 
92
- if superclass.respond_to?(:cached_rules)
92
+ @cached_rules = if superclass.respond_to?(:cached_rules)
93
93
  superclass.cached_rules.dup
94
94
  else
95
95
  {}
96
- end => @cached_rules
96
+ end
97
97
  end
98
98
  end
99
99
  end
@@ -23,12 +23,19 @@ module ActionPolicy
23
23
  def initialize(policy, rule)
24
24
  @policy = policy.class
25
25
  @rule = rule
26
- @message =
27
- "Couldn't find rule '#{@rule}' for #{@policy}" \
26
+ @message = "Couldn't find rule '#{@rule}' for #{@policy}" \
28
27
  "#{suggest(@rule, @policy.instance_methods - Object.instance_methods)}"
29
28
  end
30
29
  end
31
30
 
31
+ class NonPredicateRule < UnknownRule
32
+ def initialize(policy, rule)
33
+ @policy = policy.class
34
+ @rule = rule
35
+ @message = "The rule '#{@rule}' of '#{@policy}' must ends with ? (question mark)\nDid you mean? #{@rule}?"
36
+ end
37
+ end
38
+
32
39
  module Policy
33
40
  # Core policy API
34
41
  module Core
@@ -150,11 +150,11 @@ module ActionPolicy
150
150
  def pre_checks
151
151
  return @pre_checks if instance_variable_defined?(:@pre_checks)
152
152
 
153
- if superclass.respond_to?(:pre_checks)
153
+ @pre_checks = if superclass.respond_to?(:pre_checks)
154
154
  superclass.pre_checks.dup
155
155
  else
156
156
  []
157
- end => @pre_checks
157
+ end
158
158
  end
159
159
  end
160
160
  end
@@ -72,7 +72,7 @@ module ActionPolicy
72
72
  def all_details
73
73
  return @all_details if defined?(@all_details)
74
74
 
75
- {}.tap do |all|
75
+ @all_details = {}.tap do |all|
76
76
  next unless defined?(@reasons)
77
77
 
78
78
  reasons.reasons.each_value do |rules|
@@ -84,7 +84,7 @@ module ActionPolicy
84
84
  all.merge!(details)
85
85
  end
86
86
  end
87
- end => @all_details
87
+ end
88
88
  end
89
89
 
90
90
  # Add reasons to inspect
@@ -148,11 +148,11 @@ module ActionPolicy
148
148
  def scope_matchers
149
149
  return @scope_matchers if instance_variable_defined?(:@scope_matchers)
150
150
 
151
- if superclass.respond_to?(:scope_matchers)
151
+ @scope_matchers = if superclass.respond_to?(:scope_matchers)
152
152
  superclass.scope_matchers.dup
153
153
  else
154
154
  []
155
- end => @scope_matchers
155
+ end
156
156
  end
157
157
  end
158
158
  end
@@ -21,6 +21,7 @@ module ActionPolicy
21
21
  yield scopes.first.target
22
22
  end
23
23
  end
24
+
24
25
  # Asserts that the given policy was used to authorize the given target.
25
26
  #
26
27
  # def test_authorize
@@ -146,8 +146,8 @@ module ActionPolicy
146
146
 
147
147
  def colorize(val)
148
148
  return val unless $stdout.isatty
149
- return TRUE if val.eql?(true)
150
- return FALSE if val.eql?(false)
149
+ return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
150
+ return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
151
151
  val
152
152
  end
153
153
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionPolicy
4
- VERSION = "0.5.3"
4
+ VERSION = "0.5.7"
5
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.5.3
4
+ version: 0.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-06 00:00:00.000000000 Z
11
+ date: 2021-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-next-core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.10.3
19
+ version: 0.11.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.10.3
26
+ version: 0.11.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: ammeter
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -132,6 +132,11 @@ files:
132
132
  - CHANGELOG.md
133
133
  - LICENSE.txt
134
134
  - README.md
135
+ - config/rubocop-rspec.yml
136
+ - lib/.rbnext/1995.next/action_policy/behaviours/policy_for.rb
137
+ - lib/.rbnext/1995.next/action_policy/behaviours/scoping.rb
138
+ - lib/.rbnext/1995.next/action_policy/policy/authorization.rb
139
+ - lib/.rbnext/1995.next/action_policy/utils/pretty_print.rb
135
140
  - lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb
136
141
  - lib/.rbnext/2.7/action_policy/i18n.rb
137
142
  - lib/.rbnext/2.7/action_policy/policy/cache.rb
@@ -139,20 +144,15 @@ files:
139
144
  - lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb
140
145
  - lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb
141
146
  - lib/.rbnext/2.7/action_policy/utils/pretty_print.rb
142
- - lib/.rbnext/3.0/action_policy/behaviour.rb
143
- - lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb
144
- - lib/.rbnext/3.0/action_policy/behaviours/scoping.rb
145
147
  - lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb
146
148
  - lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb
147
149
  - lib/.rbnext/3.0/action_policy/policy/aliases.rb
148
- - lib/.rbnext/3.0/action_policy/policy/authorization.rb
149
150
  - lib/.rbnext/3.0/action_policy/policy/cache.rb
150
151
  - lib/.rbnext/3.0/action_policy/policy/core.rb
151
152
  - lib/.rbnext/3.0/action_policy/policy/defaults.rb
152
153
  - lib/.rbnext/3.0/action_policy/policy/execution_result.rb
153
154
  - lib/.rbnext/3.0/action_policy/policy/pre_check.rb
154
155
  - lib/.rbnext/3.0/action_policy/policy/reasons.rb
155
- - lib/.rbnext/3.0/action_policy/policy/scoping.rb
156
156
  - lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb
157
157
  - lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb
158
158
  - lib/.rbnext/3.0/action_policy/utils/pretty_print.rb
@@ -1,115 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "action_policy/behaviours/policy_for"
4
- require "action_policy/behaviours/scoping"
5
- require "action_policy/behaviours/memoized"
6
- require "action_policy/behaviours/thread_memoized"
7
- require "action_policy/behaviours/namespaced"
8
-
9
- require "action_policy/authorizer"
10
-
11
- module ActionPolicy
12
- # Provides `authorize!` and `allowed_to?` methods and
13
- # `authorize` class method to define authorization context.
14
- #
15
- # Could be included anywhere to perform authorization.
16
- module Behaviour
17
- include ActionPolicy::Behaviours::PolicyFor
18
- include ActionPolicy::Behaviours::Scoping
19
-
20
- def self.included(base)
21
- # Handle ActiveSupport::Concern differently
22
- if base.respond_to?(:class_methods)
23
- base.class_methods do
24
- include ClassMethods
25
- end
26
- else
27
- base.extend ClassMethods
28
- end
29
- end
30
-
31
- # Authorize action against a policy.
32
- #
33
- # Policy is inferred from record
34
- # (unless explicitly specified through `with` option).
35
- #
36
- # Raises `ActionPolicy::Unauthorized` if check failed.
37
- def authorize!(record = :__undef__, to:, **options)
38
- policy = lookup_authorization_policy(record, **options)
39
-
40
- Authorizer.call(policy, authorization_rule_for(policy, to))
41
- end
42
-
43
- # Checks that an activity is allowed for the current context (e.g. user).
44
- #
45
- # Returns true of false.
46
- def allowed_to?(rule, record = :__undef__, **options)
47
- policy = lookup_authorization_policy(record, **options)
48
-
49
- policy.apply(authorization_rule_for(policy, rule))
50
- end
51
-
52
- # Returns the authorization result object after applying a specified rule to a record.
53
- def allowance_to(rule, record = :__undef__, **options)
54
- policy = lookup_authorization_policy(record, **options)
55
-
56
- policy.apply(authorization_rule_for(policy, rule))
57
- policy.result
58
- end
59
-
60
- def authorization_context
61
- return @__authorization_context if
62
- instance_variable_defined?(:@__authorization_context)
63
-
64
- @__authorization_context = self.class.authorization_targets
65
- .each_with_object({}) do |(key, meth), obj|
66
- obj[key] = send(meth)
67
- end
68
- end
69
-
70
- # Check that rule is defined for policy,
71
- # otherwise fallback to :manage? rule.
72
- def authorization_rule_for(policy, rule)
73
- policy.resolve_rule(rule)
74
- end
75
-
76
- def lookup_authorization_policy(record, **options) # :nodoc:
77
- record = implicit_authorization_target! if record == :__undef__
78
- raise ArgumentError, "Record must be specified" if record.nil?
79
-
80
- options[:context] && (options[:context] = authorization_context.merge(options[:context]))
81
-
82
- policy_for(record: record, **options)
83
- end
84
-
85
- module ClassMethods # :nodoc:
86
- # Configure authorization context.
87
- #
88
- # For example:
89
- #
90
- # class ApplicationController < ActionController::Base
91
- # # Pass the value of `current_user` to authorization as `user`
92
- # authorize :user, through: :current_user
93
- # end
94
- #
95
- # # Assuming that in your ApplicationPolicy
96
- # class ApplicationPolicy < ActionPolicy::Base
97
- # authorize :user
98
- # end
99
- def authorize(key, through: nil)
100
- meth = through || key
101
- authorization_targets[key] = meth
102
- end
103
-
104
- def authorization_targets
105
- return @authorization_targets if instance_variable_defined?(:@authorization_targets)
106
-
107
- @authorization_targets = if superclass.respond_to?(:authorization_targets)
108
- superclass.authorization_targets.dup
109
- else
110
- {}
111
- end
112
- end
113
- end
114
- end
115
- end
@@ -1,160 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "action_policy/behaviours/scoping"
4
-
5
- require "action_policy/utils/suggest_message"
6
-
7
- module ActionPolicy
8
- class UnknownScopeType < Error # :nodoc:
9
- include ActionPolicy::SuggestMessage
10
-
11
- MESSAGE_TEMPLATE = "Unknown policy scope type :%s for %s%s"
12
-
13
- attr_reader :message
14
-
15
- def initialize(policy_class, type)
16
- @message = format(
17
- MESSAGE_TEMPLATE,
18
- type, policy_class,
19
- suggest(type, policy_class.scoping_handlers.keys)
20
- )
21
- end
22
- end
23
-
24
- class UnknownNamedScope < Error # :nodoc:
25
- include ActionPolicy::SuggestMessage
26
-
27
- MESSAGE_TEMPLATE = "Unknown named scope :%s for type :%s for %s%s"
28
-
29
- attr_reader :message
30
-
31
- def initialize(policy_class, type, name)
32
- @message = format(
33
- MESSAGE_TEMPLATE, name, type, policy_class,
34
- suggest(name, policy_class.scoping_handlers[type].keys)
35
- )
36
- end
37
- end
38
-
39
- class UnrecognizedScopeTarget < Error # :nodoc:
40
- MESSAGE_TEMPLATE = "Couldn't infer scope type for %s instance"
41
-
42
- attr_reader :message
43
-
44
- def initialize(target)
45
- target_class = target.is_a?(Module) ? target : target.class
46
-
47
- @message = format(
48
- MESSAGE_TEMPLATE, target_class
49
- )
50
- end
51
- end
52
-
53
- module Policy
54
- # Scoping is used to modify the _object under authorization_.
55
- #
56
- # The most common situation is when you want to _scope_ the collection depending
57
- # on the current user permissions.
58
- #
59
- # For example:
60
- #
61
- # class ApplicationPolicy < ActionPolicy::Base
62
- # # Scoping only makes sense when you have the authorization context
63
- # authorize :user
64
- #
65
- # # :relation here is a scoping type
66
- # scope_for :relation do |relation|
67
- # # authorization context is available within a scope
68
- # if user.admin?
69
- # relation
70
- # else
71
- # relation.publicly_visible
72
- # end
73
- # end
74
- # end
75
- #
76
- # base_scope = User.all
77
- # authorized_scope = ApplicantPolicy.new(user: user)
78
- # .apply_scope(base_scope, type: :relation)
79
- module Scoping
80
- class << self
81
- def included(base)
82
- base.extend ClassMethods
83
- end
84
- end
85
-
86
- include ActionPolicy::Behaviours::Scoping
87
-
88
- # Pass target to the scope handler of the specified type and name.
89
- # If `name` is not specified then `:default` name is used.
90
- # If `type` is not specified then we try to infer the type from the
91
- # target class.
92
- def apply_scope(target, type:, name: :default, scope_options: nil)
93
- raise ActionPolicy::UnknownScopeType.new(self.class, type) unless
94
- self.class.scoping_handlers.key?(type)
95
-
96
- raise ActionPolicy::UnknownNamedScope.new(self.class, type, name) unless
97
- self.class.scoping_handlers[type].key?(name)
98
-
99
- mid = :"__scoping__#{type}__#{name}"
100
- scope_options ? send(mid, target, **scope_options) : send(mid, target)
101
- end
102
-
103
- def resolve_scope_type(target)
104
- lookup_type_from_target(target) ||
105
- raise(ActionPolicy::UnrecognizedScopeTarget, target)
106
- end
107
-
108
- def lookup_type_from_target(target)
109
- self.class.scope_matchers.detect do |(_type, matcher)|
110
- matcher === target
111
- end&.first
112
- end
113
-
114
- module ClassMethods # :nodoc:
115
- # Register a new scoping method for the `type`
116
- def scope_for(type, name = :default, &block)
117
- mid = :"__scoping__#{type}__#{name}"
118
-
119
- define_method(mid, &block)
120
-
121
- scoping_handlers[type][name] = mid
122
- end
123
-
124
- def scoping_handlers
125
- return @scoping_handlers if instance_variable_defined?(:@scoping_handlers)
126
-
127
- @scoping_handlers =
128
- Hash.new { |h, k| h[k] = {} }.tap do |handlers|
129
- if superclass.respond_to?(:scoping_handlers)
130
- superclass.scoping_handlers.each do |k, v|
131
- handlers[k] = v.dup
132
- end
133
- end
134
- end
135
- end
136
-
137
- # Define scope type matcher.
138
- #
139
- # Scope matcher is an object that implements `#===` (_case equality_) or a Proc.
140
- #
141
- # When no type is provided when applying a scope we try to infer a type
142
- # from the target object by calling matchers one by one until we find a matching
143
- # type (i.e. there is a matcher which returns `true` when applying it to the target).
144
- def scope_matcher(type, class_or_proc)
145
- scope_matchers << [type, class_or_proc]
146
- end
147
-
148
- def scope_matchers
149
- return @scope_matchers if instance_variable_defined?(:@scope_matchers)
150
-
151
- @scope_matchers = if superclass.respond_to?(:scope_matchers)
152
- superclass.scope_matchers.dup
153
- else
154
- []
155
- end
156
- end
157
- end
158
- end
159
- end
160
- end