dry-ability 0.0.1
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 +7 -0
- data/.gitattributes +2 -0
- data/.gitignore +50 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +20 -0
- data/Rakefile +13 -0
- data/dry-ability.gemspec +36 -0
- data/init.rb +1 -0
- data/lib/dry-ability.rb +1 -0
- data/lib/dry/ability.rb +120 -0
- data/lib/dry/ability/container.rb +43 -0
- data/lib/dry/ability/controller.rb +21 -0
- data/lib/dry/ability/controller/dsl.rb +161 -0
- data/lib/dry/ability/controller/mixin.rb +80 -0
- data/lib/dry/ability/controller/resource.rb +218 -0
- data/lib/dry/ability/controller_resource.rb +62 -0
- data/lib/dry/ability/exceptions.rb +36 -0
- data/lib/dry/ability/f.rb +57 -0
- data/lib/dry/ability/inherited_resource.rb +26 -0
- data/lib/dry/ability/key.rb +19 -0
- data/lib/dry/ability/resource_mediator.rb +94 -0
- data/lib/dry/ability/rule.rb +141 -0
- data/lib/dry/ability/rule_interface.rb +27 -0
- data/lib/dry/ability/rules_builder.rb +92 -0
- data/lib/dry/ability/t.rb +45 -0
- data/lib/dry/ability/version.rb +7 -0
- metadata +214 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8d5704a2a2bf2199fda53a0a74556d9f93c1cc82a46ef9d9912d2452f34fce4f
|
4
|
+
data.tar.gz: b9dd8b4243ad05c462fd14a2052c623bac572588941368f3943a91d6228ce548
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 816667eb2d315a2fda11b40df7e1a5bef065f4425cb4a6785e9bcf617c4761670b9b406f3ddfacef5d0ccde498ef9d226c2f6a4a66596ee8dee0b0c1f04a4944
|
7
|
+
data.tar.gz: de539543156d5b2b9878a0057f4e5271100c06d7d74d9fce7e5abe56215d85a6d7a68db281f258838eff31e6bf044d0b125b52de6b766a9d0392f6f6115a3cb6
|
data/.gitattributes
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
# Gemfile.lock
|
46
|
+
# .ruby-version
|
47
|
+
# .ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.6.5
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Anton
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Dry::Ability
|
2
|
+
|
3
|
+
Dry::Ability is an authorization library, which is trying to replace [cancancan](https://github.com/CanCanCommunity/cancancan) API. The goal is creating less secondary objects by performing authorization. Some codebase, like a controller extension, was copied from cancancan and slightly modified, but public API stays the same. By some reasons (especially, due to integration with InheritedResources) I forked it from version `1.17.0`.
|
4
|
+
|
5
|
+
One significant difference with CanCanCan is that an ability's rules a defined once on class definition and stored into `Dry::Container`.
|
6
|
+
|
7
|
+
Docs, specs & benchmarks is coming soon…
|
8
|
+
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this to your Gemfile:
|
13
|
+
|
14
|
+
gem 'dry-ability'
|
15
|
+
|
16
|
+
and run the `bundle install` command.
|
17
|
+
|
18
|
+
## Getting Started
|
19
|
+
|
20
|
+
Soon…
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
# require 'rspec/core/rake_task'
|
3
|
+
# require 'rubocop/rake_task'
|
4
|
+
|
5
|
+
# desc 'Run Rubocop'
|
6
|
+
# RuboCop::RakeTask.new
|
7
|
+
#
|
8
|
+
# desc 'Run RSpec'
|
9
|
+
# RSpec::Core::RakeTask.new do |t|
|
10
|
+
# t.verbose = false
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# task default: [:rubocop, :spec]
|
data/dry-ability.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'dry/ability/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'dry-ability'
|
9
|
+
s.version = Dry::Ability::VERSION
|
10
|
+
s.authors = ['Anton Semenov', 'Alessandro Rodi (Renuo AG)', 'Bryan Rite', 'Ryan Bates', 'Richard Wilson']
|
11
|
+
s.email = 'anton.estum@gmail.com'
|
12
|
+
s.homepage = 'https://github.com/estum/dry-ability'
|
13
|
+
s.summary = 'Dried authorization solution for Rails.'
|
14
|
+
s.description = 'Dried authorization solution for Rails. All permissions are stored in a single location.'
|
15
|
+
s.platform = Gem::Platform::RUBY
|
16
|
+
s.license = 'MIT'
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
19
|
+
s.test_files = `git ls-files -- Appraisals {spec,features,gemfiles}/*`.split($INPUT_RECORD_SEPARATOR)
|
20
|
+
s.executables = `git ls-files -- bin/*`.split($INPUT_RECORD_SEPARATOR).map { |f| File.basename(f) }
|
21
|
+
s.require_paths = ['lib']
|
22
|
+
|
23
|
+
s.required_ruby_version = '>= 2.6.0'
|
24
|
+
|
25
|
+
s.add_dependency 'activesupport', '>= 5.2'
|
26
|
+
s.add_dependency 'dry-types', '>= 1.5.0'
|
27
|
+
s.add_dependency 'dry-initializer', '>= 3.0.4'
|
28
|
+
s.add_dependency 'dry-container', '>= 0.7.2'
|
29
|
+
s.add_dependency 'concurrent-ruby', '>= 1.1.8'
|
30
|
+
|
31
|
+
s.add_development_dependency 'bundler', '~> 2.2.15'
|
32
|
+
s.add_development_dependency 'rubocop', '~> 0.46'
|
33
|
+
s.add_development_dependency 'rake', '~> 13.0.3'
|
34
|
+
s.add_development_dependency 'rspec', '~> 3.2.0'
|
35
|
+
s.add_development_dependency 'appraisal', '>= 2.0.0'
|
36
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'dry-ability'
|
data/lib/dry-ability.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "dry/ability"
|
data/lib/dry/ability.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
require 'active_support/core_ext/object/with_options'
|
5
|
+
require 'active_support/core_ext/module/delegation'
|
6
|
+
require 'active_support/core_ext/class/attribute'
|
7
|
+
|
8
|
+
require 'dry/ability/version'
|
9
|
+
require 'dry/ability/exceptions'
|
10
|
+
require "dry/ability/f"
|
11
|
+
require "dry/ability/t"
|
12
|
+
require "dry/ability/key"
|
13
|
+
require 'dry/ability/rules_builder'
|
14
|
+
|
15
|
+
module Dry
|
16
|
+
# Mixin class with DSL to define abilities
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
#
|
20
|
+
# class Ability
|
21
|
+
# include Dry::Ability.define -> do
|
22
|
+
# map_subject! :public => %w(Post Like Comment)
|
23
|
+
#
|
24
|
+
# map_action! :read => %i(index show),
|
25
|
+
# :create => %i(new),
|
26
|
+
# :update => %i(edit),
|
27
|
+
# :crud => %i(index create read show update destroy),
|
28
|
+
# :change => %i(update destroy)
|
29
|
+
#
|
30
|
+
# can :read, :public
|
31
|
+
# can :
|
32
|
+
#
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
module Ability
|
36
|
+
# @private
|
37
|
+
module DSL
|
38
|
+
def define(proc = nil, **options, &block)
|
39
|
+
rules = RulesBuilder.new(**options)
|
40
|
+
rules.instance_exec(&(proc || block))
|
41
|
+
[self, rules.mixin]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
extend ActiveSupport::Concern
|
46
|
+
extend DSL
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
attr_reader :_container
|
50
|
+
alias_method :rules, :_container
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :account
|
54
|
+
|
55
|
+
def initialize(account)
|
56
|
+
@account = account
|
57
|
+
end
|
58
|
+
|
59
|
+
def authorize!(action, subject, message: nil)
|
60
|
+
if can?(action, subject)
|
61
|
+
subject
|
62
|
+
else
|
63
|
+
raise AccessDenied.new(message, action, subject)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def can?(action, subject)
|
68
|
+
rules = resolve_rules(action, subject) do
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
|
72
|
+
rules.reduce(true) do |result, rule|
|
73
|
+
result && rule[@account, subject]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def cannot?(action, subject, *args)
|
78
|
+
!can?(action, subject, *args)
|
79
|
+
end
|
80
|
+
|
81
|
+
def attributes_for(action, subject)
|
82
|
+
rules = resolve_rules(action, subject) do
|
83
|
+
return {}
|
84
|
+
end
|
85
|
+
rules.reduce({}) do |result, rule|
|
86
|
+
result.merge!(rule.attributes_for(@account, subject)); result
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def scope_for(action, subject)
|
91
|
+
rules = resolve_rules(action, subject) do
|
92
|
+
return yield if block_given?
|
93
|
+
if subject.respond_to?(:none)
|
94
|
+
return subject.none
|
95
|
+
else
|
96
|
+
raise ArgumentError, "expected subject to be an ActiveRecord::Base class or Relation. given: #{subject}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
if rules.none?(&:accessible?)
|
100
|
+
if block_given?
|
101
|
+
return yield
|
102
|
+
else
|
103
|
+
raise Error, "none of matched rules are provides scope for #{action}, #{subject}, pass block instead"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
rules.map { |rule| rule.scope_for(@account, subject) }.reduce(:merge)
|
107
|
+
end
|
108
|
+
|
109
|
+
def resolve_rules(action, subject)
|
110
|
+
rules.resolve_with_mappings(action, subject) do |e|
|
111
|
+
Rails.logger.warn { e.message }
|
112
|
+
block_given? ? yield : nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
delegate :rules, to: "self.class"
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/container"
|
4
|
+
require "dry/ability/f"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Ability
|
8
|
+
class Container < Dry::Container
|
9
|
+
MAPPING_NSFN = {
|
10
|
+
:subject => F[:string_tpl, "mappings.subject.%s"].to_proc,
|
11
|
+
:action => F[:string_tpl, "mappings.action.%s"].to_proc
|
12
|
+
}
|
13
|
+
RULES_NSFN = F[:string_tpl, "rules.%s.%s"].to_proc
|
14
|
+
|
15
|
+
# @yieldparam exception
|
16
|
+
# Yields block with an instance of +RuleNotDefault+ exception class
|
17
|
+
def resolve_with_mappings(action, subject)
|
18
|
+
candidates = key_candidates(action, subject)
|
19
|
+
result = []
|
20
|
+
candidates.each do |key|
|
21
|
+
next unless key?(key)
|
22
|
+
result << resolve(key)
|
23
|
+
end
|
24
|
+
if result.blank?
|
25
|
+
exception = RuleNotDefined.new(action: action, subject: subject, candidates: candidates)
|
26
|
+
raise exception unless block_given?
|
27
|
+
yield(exception)
|
28
|
+
else
|
29
|
+
result
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def key_candidates(action, subject)
|
34
|
+
subject, action = mappings(:subject, subject), mappings(:action, action)
|
35
|
+
subject.product(action).map!(&RULES_NSFN)
|
36
|
+
end
|
37
|
+
|
38
|
+
def mappings(kind, key)
|
39
|
+
F.collect_mappings(key, self, MAPPING_NSFN[kind], &:to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/ability/resource_mediator"
|
4
|
+
require "dry/ability/controller/mixin"
|
5
|
+
require "dry/ability/controller/dsl"
|
6
|
+
require "dry/ability/controller_resource"
|
7
|
+
require "dry/ability/inherited_resource" if defined?(::InheritedResources)
|
8
|
+
|
9
|
+
module Dry
|
10
|
+
module Ability
|
11
|
+
module Controller
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
include Controller::Mixin
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
include Controller::DSL
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent/map"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Ability
|
7
|
+
module Controller
|
8
|
+
module DSL
|
9
|
+
private def inherited(klass)
|
10
|
+
super(klass)
|
11
|
+
klass.instance_variable_set(:@_resource_mediators, @_resource_mediators.dup)
|
12
|
+
klass.instance_variable_set(:@_cancan_skipper, @_cancan_skipper.dup)
|
13
|
+
klass
|
14
|
+
end
|
15
|
+
|
16
|
+
# @api private
|
17
|
+
def set_resource_mediator_callback(name, **opts)
|
18
|
+
callback_options = opts.extract!(:only, :except, :if, :unless, :prepend)
|
19
|
+
@_resource_mediators ||= Concurrent::Map.new
|
20
|
+
@_resource_mediators.fetch_or_store(name) do
|
21
|
+
ResourceMediator.new(name, controller_path, __callee__, **opts).
|
22
|
+
tap { |m| before_action m, callback_options }
|
23
|
+
end.sequence << __callee__
|
24
|
+
end
|
25
|
+
|
26
|
+
# @!method load_and_authorize_resource(*args, **opts)
|
27
|
+
alias_method :load_and_authorize_resource, :set_resource_mediator_callback
|
28
|
+
|
29
|
+
# @!method load_resource(*args, **opts)
|
30
|
+
# @see #load_and_authorize_resource
|
31
|
+
# @option opts [Array<String,Symbol>] :only Only applies before filter to given actions.
|
32
|
+
# @option opts [Array<String,Symbol>] :except Does not apply before filter to given actions.
|
33
|
+
# @option opts [Boolean] :prepend Prepend callback
|
34
|
+
# @option opts [Symbol] :through Load this resource through another one.
|
35
|
+
# @option opts [Symbol] :through_association
|
36
|
+
# @option opts [Boolean] :shallow (false) allow this resource to be loaded directly when parent is +nil+
|
37
|
+
# @option opts [Boolean] :singleton (false) singleton resource through a +has_one+ association.
|
38
|
+
# @option opts [Boolean] :parent defaults to +true+ if a resource name is given which does not match the controller.
|
39
|
+
# @option opts [String,Class] :class The class to use for the model (string or constant).
|
40
|
+
# @option opts [String,Symbol] :instance_name The name of the instance variable to load the resource into.
|
41
|
+
# @option opts [Symbol] :find_by will use find_by(permalink: params[:id])
|
42
|
+
# @option opts [Symbol] :id_param Find using a param key other than :id. For example:
|
43
|
+
# @option opts [Array<Symbol>] :collection External actions as resource collection
|
44
|
+
# @option opts [Array<Symbol>] :new External actions as new resource
|
45
|
+
alias_method :load_resource, :set_resource_mediator_callback
|
46
|
+
|
47
|
+
# @!method authorize_resource(*args, **opts)
|
48
|
+
# @see #load_and_authorize_resource
|
49
|
+
# @option opts [Array<String,Symbol>] :only Only applies before filter to given actions.
|
50
|
+
# @option opts [Array<String,Symbol>] :except Does not apply before filter to given actions.
|
51
|
+
# @option opts [Boolean] :prepend Prepend callback
|
52
|
+
# @option opts [Boolean] :singleton (false) singleton resource through a +has_one+ association.
|
53
|
+
# @option opts [Boolean] :parent (true)
|
54
|
+
# @option opts [String,Class] :class The class to use for the model (string or constant).
|
55
|
+
# @option opts [String,Symbol] :instance_name The name of the instance variable to load the resource into.
|
56
|
+
# @option opts [Symbol] :through Authorize conditions on this parent resource when instance isn't available.
|
57
|
+
alias_method :authorize_resource, :set_resource_mediator_callback
|
58
|
+
|
59
|
+
undef_method :set_resource_mediator_callback
|
60
|
+
|
61
|
+
# Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily
|
62
|
+
# useful to skip the behavior of a superclass. You can pass :only and :except options to specify which actions
|
63
|
+
# to skip the effects on. It will apply to all actions by default.
|
64
|
+
#
|
65
|
+
# class ProjectsController < SomeOtherController
|
66
|
+
# skip_load_and_authorize_resource :only => :index
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# You can also pass the resource name as the first argument to skip that resource.
|
70
|
+
def skip_load_and_authorize_resource(*args)
|
71
|
+
skip_load_resource(*args)
|
72
|
+
skip_authorize_resource(*args)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Skip the loading behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to
|
76
|
+
# only do authorization on certain actions. You can pass :only and :except options to specify which actions to
|
77
|
+
# skip the effects on. It will apply to all actions by default.
|
78
|
+
#
|
79
|
+
# class ProjectsController < ApplicationController
|
80
|
+
# load_and_authorize_resource
|
81
|
+
# skip_load_resource :only => :index
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# You can also pass the resource name as the first argument to skip that resource.
|
85
|
+
def skip_load_resource(name, **options)
|
86
|
+
cancan_skipper[:load][name] = options
|
87
|
+
end
|
88
|
+
|
89
|
+
# Skip the authorization behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to
|
90
|
+
# only do loading on certain actions. You can pass :only and :except options to specify which actions to
|
91
|
+
# skip the effects on. It will apply to all actions by default.
|
92
|
+
#
|
93
|
+
# class ProjectsController < ApplicationController
|
94
|
+
# load_and_authorize_resource
|
95
|
+
# skip_authorize_resource :only => :index
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# You can also pass the resource name as the first argument to skip that resource.
|
99
|
+
def skip_authorize_resource(name, **options)
|
100
|
+
cancan_skipper[:authorize][name] = options
|
101
|
+
end
|
102
|
+
|
103
|
+
# Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
|
104
|
+
# If neither of these authorization methods are called,
|
105
|
+
# a CanCan::AuthorizationNotPerformed exception will be raised.
|
106
|
+
# This is normally added to the ApplicationController to ensure all controller actions do authorization.
|
107
|
+
#
|
108
|
+
# class ApplicationController < ActionController::Base
|
109
|
+
# check_authorization
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# See skip_authorization_check to bypass this check on specific controller actions.
|
113
|
+
#
|
114
|
+
# Options:
|
115
|
+
# [:+only+]
|
116
|
+
# Only applies to given actions.
|
117
|
+
#
|
118
|
+
# [:+except+]
|
119
|
+
# Does not apply to given actions.
|
120
|
+
#
|
121
|
+
# [:+if+]
|
122
|
+
# Supply the name of a controller method to be called.
|
123
|
+
# The authorization check only takes place if this returns true.
|
124
|
+
#
|
125
|
+
# check_authorization :if => :admin_controller?
|
126
|
+
#
|
127
|
+
# [:+unless+]
|
128
|
+
# Supply the name of a controller method to be called.
|
129
|
+
# The authorization check only takes place if this returns false.
|
130
|
+
#
|
131
|
+
# check_authorization :unless => :devise_controller?
|
132
|
+
#
|
133
|
+
def check_authorization(**options)
|
134
|
+
after_action(**options) do |controller|
|
135
|
+
next if controller.instance_variable_defined?(:@_authorized)
|
136
|
+
raise AuthorizationNotPerformed,
|
137
|
+
'This action failed the check_authorization because it does not authorize_resource. '\
|
138
|
+
'Add skip_authorization_check to bypass this check.'
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Call this in the class of a controller to skip the check_authorization behavior on the actions.
|
143
|
+
#
|
144
|
+
# class HomeController < ApplicationController
|
145
|
+
# skip_authorization_check :only => :index
|
146
|
+
# end
|
147
|
+
#
|
148
|
+
# Any arguments are passed to the +before_action+ it triggers.
|
149
|
+
def skip_authorization_check(*args)
|
150
|
+
before_action(*args) do |controller|
|
151
|
+
controller.instance_variable_set(:@_authorized, true)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def cancan_skipper
|
156
|
+
@_cancan_skipper ||= { authorize: {}, load: {} }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|