pundit-matchers 2.3.0 → 3.0.0.beta1
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.
- checksums.yaml +4 -4
- data/lib/pundit/matchers/actions_matcher.rb +41 -0
- data/lib/pundit/matchers/attributes_matcher.rb +71 -0
- data/lib/pundit/matchers/base_matcher.rb +27 -0
- data/lib/pundit/matchers/forbid_all_actions_matcher.rb +43 -0
- data/lib/pundit/matchers/forbid_only_actions_matcher.rb +60 -0
- data/lib/pundit/matchers/permit_actions_matcher.rb +69 -0
- data/lib/pundit/matchers/permit_all_actions_matcher.rb +43 -0
- data/lib/pundit/matchers/permit_attributes_matcher.rb +65 -0
- data/lib/pundit/matchers/permit_only_actions_matcher.rb +60 -0
- data/lib/pundit/matchers/utils/policy_info.rb +67 -6
- data/lib/pundit/matchers.rb +120 -364
- metadata +16 -39
- data/lib/pundit/matchers/utils/all_actions/actions_matcher.rb +0 -39
- data/lib/pundit/matchers/utils/all_actions/error_message_formatter.rb +0 -47
- data/lib/pundit/matchers/utils/all_actions/forbidden_actions_error_formatter.rb +0 -26
- data/lib/pundit/matchers/utils/all_actions/forbidden_actions_matcher.rb +0 -20
- data/lib/pundit/matchers/utils/all_actions/permitted_actions_error_formatter.rb +0 -26
- data/lib/pundit/matchers/utils/all_actions/permitted_actions_matcher.rb +0 -20
- data/lib/pundit/matchers/utils/only_actions/actions_matcher.rb +0 -39
- data/lib/pundit/matchers/utils/only_actions/error_message_formatter.rb +0 -54
- data/lib/pundit/matchers/utils/only_actions/forbidden_actions_error_formatter.rb +0 -26
- data/lib/pundit/matchers/utils/only_actions/forbidden_actions_matcher.rb +0 -20
- data/lib/pundit/matchers/utils/only_actions/permitted_actions_error_formatter.rb +0 -26
- data/lib/pundit/matchers/utils/only_actions/permitted_actions_matcher.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96b6768ee2dad9c91d75c12506f76d530b9b6052fc8d8b09c6fdaa56f88d1748
|
4
|
+
data.tar.gz: a2e24ef15671dec4fd9820ef5f61029499d16fd56a726be2dcdd5892dc02088f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0aa08b14749957cdeb61631b97712288eb9207201197e35f303892ab90730c5dd1fdd736f01f9d84e1c6665131d79d5a5bdc1028e2f6d4b34e875e5850e71871
|
7
|
+
data.tar.gz: 4fab87c430012868c5274767a5bc4d75da673e3c7637ea544d83eb244604548c17be7a21cea94617b940b42bfabc9b2f4c8fc6ab6682e18d55d2a527f5afba61
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This is the base action matcher class. Matchers related to actions should inherit from this class.
|
8
|
+
class ActionsMatcher < BaseMatcher
|
9
|
+
# Error message when actions are not implemented in a policy.
|
10
|
+
ACTIONS_NOT_IMPLEMENTED_ERROR = "'%<policy>s' does not implement %<actions>s"
|
11
|
+
# Error message when at least one action must be specified.
|
12
|
+
ARGUMENTS_REQUIRED_ERROR = 'At least one action must be specified'
|
13
|
+
|
14
|
+
# Initializes a new instance of the ActionsMatcher class.
|
15
|
+
#
|
16
|
+
# @param expected_actions [Array<String, Symbol>] The expected actions to be checked.
|
17
|
+
#
|
18
|
+
# @raise [ArgumentError] If no actions are specified.
|
19
|
+
def initialize(*expected_actions)
|
20
|
+
raise ArgumentError, ARGUMENTS_REQUIRED_ERROR if expected_actions.empty?
|
21
|
+
|
22
|
+
super()
|
23
|
+
@expected_actions = expected_actions.flatten.map(&:to_sym).sort
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :expected_actions
|
29
|
+
|
30
|
+
def check_actions!
|
31
|
+
missing_actions = expected_actions - policy_info.actions
|
32
|
+
return if missing_actions.empty?
|
33
|
+
|
34
|
+
raise ArgumentError, format(
|
35
|
+
ACTIONS_NOT_IMPLEMENTED_ERROR,
|
36
|
+
policy: policy_info, actions: missing_actions
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# The AttributesMatcher class is used to test whether a Pundit policy allows or denies access to certain attributes.
|
8
|
+
class AttributesMatcher < BaseMatcher
|
9
|
+
# Error message to be raised when no attributes are specified.
|
10
|
+
ARGUMENTS_REQUIRED_ERROR = 'At least one attribute must be specified'
|
11
|
+
|
12
|
+
# Initializes a new instance of the AttributesMatcher class.
|
13
|
+
#
|
14
|
+
# @param expected_attributes [Array<String, Symbol, Hash>] The list of attributes to be tested.
|
15
|
+
def initialize(*expected_attributes)
|
16
|
+
raise ArgumentError, ARGUMENTS_REQUIRED_ERROR if expected_attributes.empty?
|
17
|
+
|
18
|
+
super()
|
19
|
+
@expected_attributes = flatten_attributes(expected_attributes)
|
20
|
+
@options = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Specifies the action to be tested.
|
24
|
+
#
|
25
|
+
# @param action [Symbol, String] The action to be tested.
|
26
|
+
# @return [AttributesMatcher] The current instance of the AttributesMatcher class.
|
27
|
+
def for_action(action)
|
28
|
+
@options[:action] = action
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :expected_attributes, :options
|
35
|
+
|
36
|
+
def permitted_attributes(policy)
|
37
|
+
@permitted_attributes ||=
|
38
|
+
if options.key?(:action)
|
39
|
+
flatten_attributes(policy.public_send(:"permitted_attributes_for_#{options[:action]}"))
|
40
|
+
else
|
41
|
+
flatten_attributes(policy.permitted_attributes)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def action_message
|
46
|
+
" when authorising the '#{options[:action]}' action"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Flattens and sorts a hash or array of attributes into an array of symbols.
|
50
|
+
#
|
51
|
+
# This is a private method used internally by the `Matcher` class to convert
|
52
|
+
# attribute lists into a flattened, sorted array of symbols. The resulting
|
53
|
+
# array can be used to compare attribute lists.
|
54
|
+
#
|
55
|
+
# @param attributes [String, Symbol, Array, Hash] the attributes to be flattened.
|
56
|
+
# @return [Array<Symbol>] the flattened, sorted array of symbols.
|
57
|
+
def flatten_attributes(attributes)
|
58
|
+
case attributes
|
59
|
+
when String, Symbol
|
60
|
+
[attributes.to_sym]
|
61
|
+
when Array
|
62
|
+
attributes.flat_map { |item| flatten_attributes(item) }.sort
|
63
|
+
when Hash
|
64
|
+
attributes.flat_map do |key, value|
|
65
|
+
flatten_attributes(value).map { |item| :"#{key}[#{item}]" }
|
66
|
+
end.sort
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'utils/policy_info'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This is the base class for all matchers in the Pundit Matchers library.
|
8
|
+
class BaseMatcher
|
9
|
+
# Error message when an ambiguous negated matcher is used.
|
10
|
+
AMBIGUOUS_NEGATED_MATCHER_ERROR = <<~MSG
|
11
|
+
`expect().not_to %<name>s` is not supported since it creates ambiguity.
|
12
|
+
MSG
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :policy_info
|
17
|
+
|
18
|
+
def setup_policy_info!(policy)
|
19
|
+
@policy_info = Pundit::Matchers::Utils::PolicyInfo.new(policy)
|
20
|
+
end
|
21
|
+
|
22
|
+
def user_message
|
23
|
+
" for '#{policy_info.user}'"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy forbids all actions.
|
8
|
+
class ForbidAllActionsMatcher < BaseMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] Description of the matcher.
|
12
|
+
def description
|
13
|
+
'forbid all actions'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy forbids all actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy forbids all actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
|
23
|
+
policy_info.permitted_actions.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Raises a NotImplementedError
|
27
|
+
# @raise NotImplementedError
|
28
|
+
# @return [void]
|
29
|
+
def does_not_match?(_policy)
|
30
|
+
raise NotImplementedError, format(AMBIGUOUS_NEGATED_MATCHER_ERROR, name: 'forbid_all_actions')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a failure message if the matcher fails.
|
34
|
+
#
|
35
|
+
# @return [String] Failure message.
|
36
|
+
def failure_message
|
37
|
+
message = +"expected '#{policy_info}' to forbid all actions,"
|
38
|
+
message << " but permitted #{policy_info.permitted_actions}"
|
39
|
+
message << user_message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'actions_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy forbids only the expected actions.
|
8
|
+
class ForbidOnlyActionsMatcher < ActionsMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] Description of the matcher.
|
12
|
+
def description
|
13
|
+
"forbid only #{expected_actions}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy forbids only the expected actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy forbids only the expected actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
check_actions!
|
23
|
+
|
24
|
+
@actual_actions = policy_info.forbidden_actions - expected_actions
|
25
|
+
@extra_actions = policy_info.permitted_actions & expected_actions
|
26
|
+
|
27
|
+
actual_actions.empty? && extra_actions.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Raises a NotImplementedError
|
31
|
+
# @raise NotImplementedError
|
32
|
+
# @return [void]
|
33
|
+
def does_not_match?(_policy)
|
34
|
+
raise NotImplementedError, format(AMBIGUOUS_NEGATED_MATCHER_ERROR, name: 'forbid_only_actions')
|
35
|
+
end
|
36
|
+
|
37
|
+
# The failure message when the expected actions and the forbidden actions do not match.
|
38
|
+
#
|
39
|
+
# @return [String] A failure message when the expected actions and the forbidden actions do not match.
|
40
|
+
def failure_message
|
41
|
+
message = +"expected '#{policy_info}' to forbid only #{expected_actions},"
|
42
|
+
message << " but forbade #{actual_actions}" unless actual_actions.empty?
|
43
|
+
message << extra_message unless extra_actions.empty?
|
44
|
+
message << user_message
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :actual_actions, :extra_actions
|
50
|
+
|
51
|
+
def extra_message
|
52
|
+
if actual_actions.empty?
|
53
|
+
" but permitted #{extra_actions}"
|
54
|
+
else
|
55
|
+
" and permitted #{extra_actions}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'actions_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy permits or forbids the expected actions.
|
8
|
+
class PermitActionsMatcher < ActionsMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] Description of the matcher.
|
12
|
+
def description
|
13
|
+
"permit #{expected_actions}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy permits the expected actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy permits the expected actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
check_actions!
|
23
|
+
|
24
|
+
@actual_actions = expected_actions.reject do |action|
|
25
|
+
policy.public_send(:"#{action}?")
|
26
|
+
end
|
27
|
+
|
28
|
+
actual_actions.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks if the given policy forbids the expected actions.
|
32
|
+
#
|
33
|
+
# @param policy [Object] The policy to test.
|
34
|
+
# @return [Boolean] True if the policy forbids the expected actions, false otherwise.
|
35
|
+
def does_not_match?(policy)
|
36
|
+
setup_policy_info! policy
|
37
|
+
check_actions!
|
38
|
+
|
39
|
+
@actual_actions = expected_actions.select do |action|
|
40
|
+
policy.public_send(:"#{action}?")
|
41
|
+
end
|
42
|
+
|
43
|
+
actual_actions.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns a failure message when the expected actions are forbidden.
|
47
|
+
#
|
48
|
+
# @return [String] A failure message when the expected actions are not forbidden.
|
49
|
+
def failure_message
|
50
|
+
message = +"expected '#{policy_info}' to permit #{expected_actions},"
|
51
|
+
message << " but forbade #{actual_actions}"
|
52
|
+
message << user_message
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a failure message when the expected actions are permitted.
|
56
|
+
#
|
57
|
+
# @return [String] A failure message when the expected actions are permitted.
|
58
|
+
def failure_message_when_negated
|
59
|
+
message = +"expected '#{policy_info}' to forbid #{expected_actions},"
|
60
|
+
message << " but permitted #{actual_actions}"
|
61
|
+
message << user_message
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :actual_actions
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy permits all actions.
|
8
|
+
class PermitAllActionsMatcher < BaseMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] A description of the matcher.
|
12
|
+
def description
|
13
|
+
'permit all actions'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy permits all actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy permits all actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
|
23
|
+
policy_info.forbidden_actions.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Raises a NotImplementedError
|
27
|
+
# @raise NotImplementedError
|
28
|
+
# @return [void]
|
29
|
+
def does_not_match?(_policy)
|
30
|
+
raise NotImplementedError, format(AMBIGUOUS_NEGATED_MATCHER_ERROR, name: 'permit_all_actions')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a failure message when the policy does not permit all actions.
|
34
|
+
#
|
35
|
+
# @return [String] A failure message when the policy does not permit all actions.
|
36
|
+
def failure_message
|
37
|
+
message = +"expected '#{policy_info}' to permit all actions,"
|
38
|
+
message << " but forbade #{policy_info.forbidden_actions}"
|
39
|
+
message << user_message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'attributes_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy permits or forbids the mass assignment of the expected attributes.
|
8
|
+
class PermitAttributesMatcher < AttributesMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] A description of the matcher.
|
12
|
+
def description
|
13
|
+
"permit the mass assignment of #{expected_attributes}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy permits the mass assignment of the expected attributes.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy permits the mass assignment of the expected attributes, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
|
23
|
+
@actual_attributes = expected_attributes - permitted_attributes(policy)
|
24
|
+
|
25
|
+
actual_attributes.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Checks if the given policy forbids the mass assignment of the expected attributes.
|
29
|
+
#
|
30
|
+
# @param policy [Object] The policy to test.
|
31
|
+
# @return [Boolean] True if the policy forbids the mass assignment of the expected attributes, false otherwise.
|
32
|
+
def does_not_match?(policy)
|
33
|
+
setup_policy_info! policy
|
34
|
+
|
35
|
+
@actual_attributes = expected_attributes & permitted_attributes(policy)
|
36
|
+
|
37
|
+
actual_attributes.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
# The failure message when the expected attributes are forbidden.
|
41
|
+
#
|
42
|
+
# @return [String] A failure message when the expected attributes are not permitted.
|
43
|
+
def failure_message
|
44
|
+
message = +"expected '#{policy_info}' to permit the mass assignment of #{expected_attributes}"
|
45
|
+
message << action_message if options.key?(:action)
|
46
|
+
message << ", but forbade the mass assignment of #{actual_attributes}"
|
47
|
+
message << user_message
|
48
|
+
end
|
49
|
+
|
50
|
+
# The failure message when the expected attributes are permitted.
|
51
|
+
#
|
52
|
+
# @return [String] A failure message when the expected attributes are forbidden.
|
53
|
+
def failure_message_when_negated
|
54
|
+
message = +"expected '#{policy_info}' to forbid the mass assignment of #{expected_attributes}"
|
55
|
+
message << action_message if options.key?(:action)
|
56
|
+
message << ", but permitted the mass assignment of #{actual_attributes}"
|
57
|
+
message << user_message
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_reader :actual_attributes
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'actions_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy permits only the expected actions.
|
8
|
+
class PermitOnlyActionsMatcher < ActionsMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] A description of the matcher.
|
12
|
+
def description
|
13
|
+
"permit only #{expected_actions}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy permits only the expected actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy permits only the expected actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
check_actions!
|
23
|
+
|
24
|
+
@actual_actions = policy_info.permitted_actions - expected_actions
|
25
|
+
@extra_actions = policy_info.forbidden_actions & expected_actions
|
26
|
+
|
27
|
+
actual_actions.empty? && extra_actions.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Raises a NotImplementedError
|
31
|
+
# @raise NotImplementedError
|
32
|
+
# @return [void]
|
33
|
+
def does_not_match?(_policy)
|
34
|
+
raise NotImplementedError, format(AMBIGUOUS_NEGATED_MATCHER_ERROR, name: 'permit_only_actions')
|
35
|
+
end
|
36
|
+
|
37
|
+
# The failure message when the expected actions and the permitted actions do not match.
|
38
|
+
#
|
39
|
+
# @return [String] A failure message when the expected actions and the permitted actions do not match.
|
40
|
+
def failure_message
|
41
|
+
message = +"expected '#{policy_info}' to permit only #{expected_actions},"
|
42
|
+
message << " but permitted #{actual_actions}" unless actual_actions.empty?
|
43
|
+
message << extra_message unless extra_actions.empty?
|
44
|
+
message << user_message
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :actual_actions, :extra_actions
|
50
|
+
|
51
|
+
def extra_message
|
52
|
+
if actual_actions.empty?
|
53
|
+
" but forbade #{extra_actions}"
|
54
|
+
else
|
55
|
+
" and forbade #{extra_actions}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -3,27 +3,88 @@
|
|
3
3
|
module Pundit
|
4
4
|
module Matchers
|
5
5
|
module Utils
|
6
|
-
#
|
6
|
+
# This class provides methods to retrieve information about a policy class,
|
7
|
+
# such as the actions it defines and which of those actions are permitted
|
8
|
+
# or forbidden. It also provides a string representation of the policy class name
|
9
|
+
# and the user object associated with the policy.
|
7
10
|
class PolicyInfo
|
11
|
+
# Error message when policy does not respond to `user_alias`.
|
12
|
+
USER_NOT_IMPLEMENTED_ERROR = <<~MSG
|
13
|
+
'%<policy>s' does not implement '%<user_alias>s'. You may want to
|
14
|
+
configure an alias, which you can do as follows:
|
15
|
+
|
16
|
+
Pundit::Matchers.configure do |config|
|
17
|
+
# Alias for all policies
|
18
|
+
config.default_user_alias = :%<user_alias>s
|
19
|
+
|
20
|
+
# Per-policy alias
|
21
|
+
config.user_aliases = { '%<policy>s' => :%<user_alias>s }
|
22
|
+
end
|
23
|
+
MSG
|
24
|
+
|
8
25
|
attr_reader :policy
|
9
26
|
|
27
|
+
# Initializes a new instance of PolicyInfo.
|
28
|
+
#
|
29
|
+
# @param policy [Class] The policy class to collect details about.
|
10
30
|
def initialize(policy)
|
11
31
|
@policy = policy
|
32
|
+
check_user_alias!
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a string representation of the policy class name.
|
36
|
+
#
|
37
|
+
# @return [String] A string representation of the policy class name.
|
38
|
+
def to_s
|
39
|
+
policy.class.name
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the user object associated with the policy.
|
43
|
+
#
|
44
|
+
# @return [Object] The user object associated with the policy.
|
45
|
+
def user
|
46
|
+
@user ||= policy.public_send(user_alias)
|
12
47
|
end
|
13
48
|
|
49
|
+
# Returns an array of all actions defined in the policy class.
|
50
|
+
#
|
51
|
+
# It assumes that actions are defined as public instance methods that end with a question mark.
|
52
|
+
#
|
53
|
+
# @return [Array<Symbol>] An array of all actions defined in the policy class.
|
14
54
|
def actions
|
15
|
-
@actions ||=
|
16
|
-
|
17
|
-
policy_methods.grep(/\?$/).sort.map { |policy_method| policy_method.to_s.delete_suffix('?').to_sym }
|
55
|
+
@actions ||= policy_public_methods.grep(/\?$/).sort.map do |policy_method|
|
56
|
+
policy_method.to_s.delete_suffix('?').to_sym
|
18
57
|
end
|
19
58
|
end
|
20
59
|
|
60
|
+
# Returns an array of all permitted actions defined in the policy class.
|
61
|
+
#
|
62
|
+
# @return [Array<Symbol>] An array of all permitted actions defined in the policy class.
|
21
63
|
def permitted_actions
|
22
|
-
@permitted_actions ||= actions.select { |action| policy.public_send("#{action}?") }
|
64
|
+
@permitted_actions ||= actions.select { |action| policy.public_send(:"#{action}?") }
|
23
65
|
end
|
24
66
|
|
67
|
+
# Returns an array of all forbidden actions defined in the policy class.
|
68
|
+
#
|
69
|
+
# @return [Array<Symbol>] An array of all forbidden actions defined in the policy class.
|
25
70
|
def forbidden_actions
|
26
|
-
actions - permitted_actions
|
71
|
+
@forbidden_actions ||= actions - permitted_actions
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def policy_public_methods
|
77
|
+
@policy_public_methods ||= policy.public_methods - Object.instance_methods
|
78
|
+
end
|
79
|
+
|
80
|
+
def user_alias
|
81
|
+
@user_alias ||= Pundit::Matchers.configuration.user_alias(policy)
|
82
|
+
end
|
83
|
+
|
84
|
+
def check_user_alias!
|
85
|
+
return if policy.respond_to?(user_alias)
|
86
|
+
|
87
|
+
raise ArgumentError, format(USER_NOT_IMPLEMENTED_ERROR, policy: self, user_alias: user_alias)
|
27
88
|
end
|
28
89
|
end
|
29
90
|
end
|