action_policy 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +85 -0
- data/.travis.yml +25 -2
- data/CHANGELOG.md +7 -0
- data/Gemfile +12 -3
- data/README.md +71 -12
- data/Rakefile +9 -1
- data/action_policy.gemspec +11 -5
- data/docs/.nojekyll +0 -0
- data/docs/CNAME +1 -0
- data/docs/README.md +46 -0
- data/docs/_sidebar.md +19 -0
- data/docs/aliases.md +54 -0
- data/docs/assets/docsify.min.js +1 -0
- data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
- data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
- data/docs/assets/images/cache.png +0 -0
- data/docs/assets/images/cache.svg +70 -0
- data/docs/assets/images/layer.png +0 -0
- data/docs/assets/images/layer.svg +92 -0
- data/docs/assets/prism-ruby.min.js +1 -0
- data/docs/assets/styles.css +317 -0
- data/docs/assets/vue.min.css +1 -0
- data/docs/authorization_context.md +33 -0
- data/docs/caching.md +262 -0
- data/docs/custom_lookup_chain.md +48 -0
- data/docs/custom_policy.md +51 -0
- data/docs/favicon.ico +0 -0
- data/docs/i18n.md +3 -0
- data/docs/index.html +25 -0
- data/docs/instrumentation.md +3 -0
- data/docs/lookup_chain.md +16 -0
- data/docs/namespaces.md +69 -0
- data/docs/non_rails.md +29 -0
- data/docs/pre_checks.md +57 -0
- data/docs/quick_start.md +102 -0
- data/docs/rails.md +110 -0
- data/docs/reasons.md +67 -0
- data/docs/testing.md +116 -0
- data/docs/writing_policies.md +55 -0
- data/gemfiles/jruby.gemfile +5 -0
- data/gemfiles/rails42.gemfile +5 -0
- data/gemfiles/railsmaster.gemfile +6 -0
- data/lib/action_policy.rb +34 -2
- data/lib/action_policy/authorizer.rb +28 -0
- data/lib/action_policy/base.rb +24 -0
- data/lib/action_policy/behaviour.rb +94 -0
- data/lib/action_policy/behaviours/memoized.rb +56 -0
- data/lib/action_policy/behaviours/namespaced.rb +80 -0
- data/lib/action_policy/behaviours/policy_for.rb +23 -0
- data/lib/action_policy/behaviours/thread_memoized.rb +54 -0
- data/lib/action_policy/ext/module_namespace.rb +21 -0
- data/lib/action_policy/ext/policy_cache_key.rb +67 -0
- data/lib/action_policy/ext/string_constantize.rb +23 -0
- data/lib/action_policy/lookup_chain.rb +84 -0
- data/lib/action_policy/policy/aliases.rb +69 -0
- data/lib/action_policy/policy/authorization.rb +91 -0
- data/lib/action_policy/policy/cache.rb +74 -0
- data/lib/action_policy/policy/cached_apply.rb +28 -0
- data/lib/action_policy/policy/core.rb +64 -0
- data/lib/action_policy/policy/defaults.rb +37 -0
- data/lib/action_policy/policy/pre_check.rb +210 -0
- data/lib/action_policy/policy/reasons.rb +109 -0
- data/lib/action_policy/rails/channel.rb +15 -0
- data/lib/action_policy/rails/controller.rb +90 -0
- data/lib/action_policy/railtie.rb +74 -0
- data/lib/action_policy/rspec.rb +3 -0
- data/lib/action_policy/rspec/be_authorized_to.rb +93 -0
- data/lib/action_policy/rspec/pundit_syntax.rb +48 -0
- data/lib/action_policy/test_helper.rb +46 -0
- data/lib/action_policy/testing.rb +64 -0
- data/lib/action_policy/version.rb +3 -1
- metadata +115 -9
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_policy/behaviours/policy_for"
|
4
|
+
|
5
|
+
module ActionPolicy
|
6
|
+
# Raised when `resolve_rule` failed to find an approriate
|
7
|
+
# policy rule method for the activity
|
8
|
+
class UnknownRule < Error
|
9
|
+
attr_reader :policy, :rule, :message
|
10
|
+
|
11
|
+
def initialize(policy, rule)
|
12
|
+
@policy = policy.class
|
13
|
+
@rule = rule
|
14
|
+
@message = "Couldn't find rule '#{@rule}' for #{@policy}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Policy
|
19
|
+
# Core policy API
|
20
|
+
module Core
|
21
|
+
include ActionPolicy::Behaviours::PolicyFor
|
22
|
+
|
23
|
+
attr_reader :record
|
24
|
+
|
25
|
+
def initialize(record = nil)
|
26
|
+
@record = record
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a result of applying the specified rule.
|
30
|
+
# Unlike simply calling a predicate rule (`policy.manage?`),
|
31
|
+
# `apply` also calls pre-checks.
|
32
|
+
def apply(rule)
|
33
|
+
public_send(rule)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a result of applying the specified rule to the specified record.
|
37
|
+
# Under the hood a policy class for record is resolved
|
38
|
+
# (unless it's explicitly set through `with` option).
|
39
|
+
#
|
40
|
+
# If record is `nil` then we uses the current policy.
|
41
|
+
def allowed_to?(rule, record = :__undef__, **options)
|
42
|
+
policy =
|
43
|
+
if record == :__undef__
|
44
|
+
self
|
45
|
+
else
|
46
|
+
policy_for(record: record, **options)
|
47
|
+
end
|
48
|
+
|
49
|
+
policy.apply(rule)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns a rule name (policy method name) for activity.
|
53
|
+
#
|
54
|
+
# By default, rule name is equal to activity name.
|
55
|
+
#
|
56
|
+
# Raises ActionPolicy::UknownRule when rule is not found in policy.
|
57
|
+
def resolve_rule(activity)
|
58
|
+
raise UnknownRule.new(self, activity) unless
|
59
|
+
respond_to?(activity)
|
60
|
+
activity
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
module Policy
|
5
|
+
# Create default rules and aliases:
|
6
|
+
# - `index?` (=`false`)
|
7
|
+
# - `create?` (=`false`)
|
8
|
+
# - `new?` as an alias for `create?`
|
9
|
+
# - `manage?` as a fallback for all unspecified rules (default rule)
|
10
|
+
module Defaults
|
11
|
+
def self.included(base)
|
12
|
+
raise "Aliases support is required for defaults" unless
|
13
|
+
base.ancestors.include?(Aliases)
|
14
|
+
|
15
|
+
base.default_rule :manage?
|
16
|
+
base.alias_rule :new?, to: :create?
|
17
|
+
|
18
|
+
raise "Verification context support is required for defaults" unless
|
19
|
+
base.ancestors.include?(Aliases)
|
20
|
+
|
21
|
+
base.authorize :user
|
22
|
+
end
|
23
|
+
|
24
|
+
def index?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def create?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def manage?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
module Policy
|
5
|
+
# Adds callback-style checks to policies to
|
6
|
+
# extract common checks from rules.
|
7
|
+
#
|
8
|
+
# class ApplicationPolicy < ActionPolicy::Base
|
9
|
+
# authorize :user
|
10
|
+
# pre_check :allow_admins
|
11
|
+
#
|
12
|
+
# private
|
13
|
+
# # Allow every action for admins
|
14
|
+
# def allow_admins
|
15
|
+
# allow! if user.admin?
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# You can specify conditional pre-checks (through `except` / `only`) options
|
20
|
+
# and skip already defined pre-checks if necessary.
|
21
|
+
#
|
22
|
+
# class UserPolicy < ApplicationPolicy
|
23
|
+
# skip_pre_check :allow_admins, only: :destroy?
|
24
|
+
#
|
25
|
+
# def destroy?
|
26
|
+
# user.admin? && !record.admin?
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
module PreCheck
|
30
|
+
# Single pre-check instance.
|
31
|
+
#
|
32
|
+
# Implements filtering logic.
|
33
|
+
class Check
|
34
|
+
# Wrapper over check result
|
35
|
+
class Result
|
36
|
+
DENY = :__deny__
|
37
|
+
ALLOW = :__allow__
|
38
|
+
|
39
|
+
KINDS = {
|
40
|
+
DENY => false,
|
41
|
+
ALLOW => true
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
attr_reader :fulfilled
|
45
|
+
|
46
|
+
def initialize(val)
|
47
|
+
@fulfilled = KINDS.keys.include?(val)
|
48
|
+
@value = val
|
49
|
+
end
|
50
|
+
|
51
|
+
alias fulfilled? fulfilled
|
52
|
+
|
53
|
+
def denied?
|
54
|
+
@value == DENY
|
55
|
+
end
|
56
|
+
|
57
|
+
def value
|
58
|
+
KINDS.fetch(@value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :name, :policy_class
|
63
|
+
|
64
|
+
def initialize(policy, name, except: nil, only: nil)
|
65
|
+
if !except.nil? && !only.nil?
|
66
|
+
raise ArgumentError,
|
67
|
+
"Only one of `except` and `only` may be specified for pre-check"
|
68
|
+
end
|
69
|
+
|
70
|
+
@policy_class = policy
|
71
|
+
@name = name
|
72
|
+
@blacklist = Array(except) unless except.nil?
|
73
|
+
@whitelist = Array(only) unless only.nil?
|
74
|
+
|
75
|
+
rebuild_filter
|
76
|
+
end
|
77
|
+
|
78
|
+
def applicable?(rule)
|
79
|
+
return true if filter.nil?
|
80
|
+
filter.call(rule)
|
81
|
+
end
|
82
|
+
|
83
|
+
def call(policy)
|
84
|
+
Result.new(policy.send(name)).tap do |res|
|
85
|
+
# add denial reason if Reasons included
|
86
|
+
policy.reasons.add(policy_class, name) if
|
87
|
+
res.denied? && policy.respond_to?(:reasons)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# rubocop: disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
92
|
+
# rubocop: disable Metrics/PerceivedComplexity, Metrics/MethodLength
|
93
|
+
def skip!(except: nil, only: nil)
|
94
|
+
if !except.nil? && !only.nil?
|
95
|
+
raise ArgumentError,
|
96
|
+
"Only one of `except` and `only` may be specified when skipping pre-check"
|
97
|
+
end
|
98
|
+
|
99
|
+
if except.nil? && only.nil?
|
100
|
+
raise ArgumentError,
|
101
|
+
"At least one of `except` and `only` must be specified when skipping pre-check"
|
102
|
+
end
|
103
|
+
|
104
|
+
if except
|
105
|
+
@whitelist = Array(except)
|
106
|
+
@whitelist -= blacklist if blacklist
|
107
|
+
@blacklist = nil
|
108
|
+
else
|
109
|
+
# only
|
110
|
+
@blacklist += Array(only) if blacklist
|
111
|
+
@whitelist -= Array(only) if whitelist
|
112
|
+
@blacklist = Array(only) if filter.nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
rebuild_filter
|
116
|
+
end
|
117
|
+
# rubocop: enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
118
|
+
# rubocop: enable Metrics/PerceivedComplexity, Metrics/MethodLength
|
119
|
+
|
120
|
+
def dup
|
121
|
+
self.class.new(policy_class, name, except: blacklist&.dup, only: whitelist&.dup)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
attr_reader :whitelist, :blacklist, :filter
|
127
|
+
|
128
|
+
def rebuild_filter
|
129
|
+
@filter =
|
130
|
+
if whitelist
|
131
|
+
proc { |rule| whitelist.include?(rule) }
|
132
|
+
elsif blacklist
|
133
|
+
proc { |rule| !blacklist.include?(rule) }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class << self
|
139
|
+
def prepended(base)
|
140
|
+
base.extend ClassMethods
|
141
|
+
base.prepend InstanceMethods
|
142
|
+
end
|
143
|
+
|
144
|
+
alias included prepended
|
145
|
+
end
|
146
|
+
|
147
|
+
def run_pre_checks(rule)
|
148
|
+
self.class.pre_checks.each do |check|
|
149
|
+
next unless check.applicable?(rule)
|
150
|
+
res = check.call(self)
|
151
|
+
return res.value if res.fulfilled?
|
152
|
+
end
|
153
|
+
|
154
|
+
yield if block_given?
|
155
|
+
end
|
156
|
+
|
157
|
+
def deny!
|
158
|
+
Check::Result::DENY
|
159
|
+
end
|
160
|
+
|
161
|
+
def allow!
|
162
|
+
Check::Result::ALLOW
|
163
|
+
end
|
164
|
+
|
165
|
+
module InstanceMethods # :nodoc:
|
166
|
+
def apply(rule)
|
167
|
+
run_pre_checks(rule) { super }
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
module ClassMethods # :nodoc:
|
172
|
+
def pre_check(*names, **options)
|
173
|
+
names.each do |name|
|
174
|
+
# do not allow pre-check override
|
175
|
+
check = pre_checks.find { |c| c.name == name }
|
176
|
+
raise "Pre-check already defined: #{name}" unless check.nil?
|
177
|
+
|
178
|
+
pre_checks << Check.new(self, name, **options)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# rubocop: disable Metrics/AbcSize
|
183
|
+
def skip_pre_check(*names, **options)
|
184
|
+
names.each do |name|
|
185
|
+
check = pre_checks.find { |c| c.name == name }
|
186
|
+
raise "Pre-check not found: #{name}" if check.nil?
|
187
|
+
|
188
|
+
# when no options provided we remove this check completely
|
189
|
+
next pre_checks.delete(check) if options.empty?
|
190
|
+
|
191
|
+
# otherwise duplicate and apply skip options
|
192
|
+
pre_checks[pre_checks.index(check)] = check.dup.tap { |c| c.skip! options }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
# rubocop: enable Metrics/AbcSize
|
196
|
+
|
197
|
+
def pre_checks
|
198
|
+
return @pre_checks if instance_variable_defined?(:@pre_checks)
|
199
|
+
|
200
|
+
@pre_checks =
|
201
|
+
if superclass.respond_to?(:pre_checks)
|
202
|
+
superclass.pre_checks.dup
|
203
|
+
else
|
204
|
+
[]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
module Policy
|
5
|
+
class FailureReason # :nodoc:
|
6
|
+
attr_reader :policy, :rule
|
7
|
+
|
8
|
+
def initialize(policy_or_class, rule)
|
9
|
+
@policy = policy_or_class.is_a?(Class) ? policy_or_class : policy_or_class.class
|
10
|
+
@rule = rule
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Failures reasons store
|
15
|
+
class FailureReasons
|
16
|
+
include Enumerable
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
def_delegators :@reasons, :size, :empty?, :last, :each
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@reasons = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(policy, rule)
|
26
|
+
@reasons << FailureReason.new(policy, rule)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Provides failure reasons tracking functionality.
|
31
|
+
# That allows you to distinguish between the reasons why authorization was rejected.
|
32
|
+
#
|
33
|
+
# It's helpful when you compose policies (i.e. use one policy within another).
|
34
|
+
#
|
35
|
+
# For example:
|
36
|
+
#
|
37
|
+
# class ApplicantPolicy < ApplicationPolicy
|
38
|
+
# def show?
|
39
|
+
# user.has_permission?(:view_applicants) &&
|
40
|
+
# allowed_to?(:show?, object.stage)
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Now when you receive an exception, you have a reasons object, which contains additional
|
45
|
+
# information about the failure:
|
46
|
+
#
|
47
|
+
# rescue_from ActionPolicy::Unauthorized do |ex|
|
48
|
+
# ex.reasons.messages #=> { stage: [:show] }
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# You can also wrap _local_ rules into `allowed_to?` to populate reasons:
|
52
|
+
#
|
53
|
+
# class ApplicantPolicy < ApplicationPolicy
|
54
|
+
# def show?
|
55
|
+
# allowed_to?(:view_applicants?) &&
|
56
|
+
# allowed_to?(:show?, object.stage)
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# def view_applicants?
|
60
|
+
# user.has_permission?(:view_applicants)
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
module Reasons
|
64
|
+
class << self
|
65
|
+
def prepended(base)
|
66
|
+
base.prepend InstanceMethods
|
67
|
+
end
|
68
|
+
|
69
|
+
alias included prepended
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_reader :reasons
|
73
|
+
|
74
|
+
def with_clean_reasons # :nodoc:
|
75
|
+
old_reasons = reasons
|
76
|
+
@reasons = nil
|
77
|
+
res = yield
|
78
|
+
@reasons = old_reasons
|
79
|
+
res
|
80
|
+
end
|
81
|
+
|
82
|
+
module InstanceMethods # :nodoc:
|
83
|
+
def apply(rule)
|
84
|
+
@reasons = FailureReasons.new
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
# rubocop: disable Metrics/MethodLength
|
89
|
+
def allowed_to?(rule, record = :__undef__, **options)
|
90
|
+
policy = nil
|
91
|
+
|
92
|
+
succeed =
|
93
|
+
if record == :__undef__
|
94
|
+
policy = self
|
95
|
+
with_clean_reasons { apply(rule) }
|
96
|
+
else
|
97
|
+
policy = policy_for(record: record, **options)
|
98
|
+
|
99
|
+
policy.apply(rule)
|
100
|
+
end
|
101
|
+
|
102
|
+
reasons.add(policy, rule) if reasons && !succeed
|
103
|
+
succeed
|
104
|
+
end
|
105
|
+
# rubocop: enable Metrics/MethodLength
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "action_policy/behaviour"
|
5
|
+
|
6
|
+
module ActionPolicy
|
7
|
+
# Channel concern.
|
8
|
+
# Add `authorize!` and `allowed_to?` methods.
|
9
|
+
module Channel
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
include ActionPolicy::Behaviour
|
13
|
+
include ActionPolicy::Behaviours::Namespaced
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "action_policy/behaviour"
|
5
|
+
|
6
|
+
module ActionPolicy
|
7
|
+
# Raised when `authorize!` hasn't been called for action
|
8
|
+
class UnauthorizedAction < Error
|
9
|
+
def initialize(controller, action)
|
10
|
+
super("Action '#{controller}##{action}' hasn't been authorized")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Controller concern.
|
15
|
+
# Add `authorize!` and `allowed_to?` methods,
|
16
|
+
# provide `verify_authorized` hook.
|
17
|
+
module Controller
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
include ActionPolicy::Behaviour
|
21
|
+
include ActionPolicy::Behaviours::ThreadMemoized
|
22
|
+
include ActionPolicy::Behaviours::Memoized
|
23
|
+
include ActionPolicy::Behaviours::Namespaced
|
24
|
+
|
25
|
+
included do
|
26
|
+
helper_method :allowed_to?
|
27
|
+
|
28
|
+
attr_writer :authorize_count
|
29
|
+
|
30
|
+
protected :authorize_count=, :authorize_count
|
31
|
+
end
|
32
|
+
|
33
|
+
# Authorize action against a policy.
|
34
|
+
#
|
35
|
+
# Policy is inferred from record
|
36
|
+
# (unless explicitly specified through `with` option).
|
37
|
+
#
|
38
|
+
# If action is not provided, it's inferred from `action_name`.
|
39
|
+
#
|
40
|
+
# If record is not provided, tries to infer the resource class
|
41
|
+
# from controller name (i.e. `controller_name.classify.safe_constantize`).
|
42
|
+
#
|
43
|
+
# Raises `ActionPolicy::Unauthorized` if check failed.
|
44
|
+
def authorize!(record = :__undef__, to: nil, **options)
|
45
|
+
record = controller_name.classify.safe_constantize if
|
46
|
+
record == :__undef__
|
47
|
+
|
48
|
+
to ||= :"#{action_name}?"
|
49
|
+
|
50
|
+
super(record, to: to, **options)
|
51
|
+
|
52
|
+
self.authorize_count += 1
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks that an activity is allowed for the current context (e.g. user).
|
56
|
+
#
|
57
|
+
# If record is not provided, tries to infer the resource class
|
58
|
+
# from controller name (i.e. `controller_name.classify.safe_constantize`).
|
59
|
+
#
|
60
|
+
# Returns true of false.
|
61
|
+
def allowed_to?(rule, record = :__undef__, **options)
|
62
|
+
record = controller_name.classify.safe_constantize if
|
63
|
+
record == :__undef__
|
64
|
+
|
65
|
+
super(rule, record, **options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def verify_authorized
|
69
|
+
raise UnauthorizedAction.new(controller_path, action_name) if
|
70
|
+
authorize_count.zero?
|
71
|
+
end
|
72
|
+
|
73
|
+
def authorize_count
|
74
|
+
@authorize_count ||= 0
|
75
|
+
end
|
76
|
+
|
77
|
+
class_methods do
|
78
|
+
# Adds after_action callback to check that
|
79
|
+
# authorize! method has been called.
|
80
|
+
def verify_authorized(**options)
|
81
|
+
after_action :verify_authorized, **options
|
82
|
+
end
|
83
|
+
|
84
|
+
# Skips verify_authorized after_action callback.
|
85
|
+
def skip_verify_authorized(**options)
|
86
|
+
skip_after_action :verify_authorized, **options\
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|