cancancan_nested_auth 0.0.0
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
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b6ec611ac65c753256beb9533524d9706345eda4d9f8e2cb88a7e904d57bcdb0
|
4
|
+
data.tar.gz: d91c1ecf3bdc31617a1cfbef45f4bb2d790da9cfa0346ddc45728a1fdbc9ebe6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 95eae08b0d76102c889c23e413e6b5258ae9a867dd96c7f0e90741af287e0b29da4a6357e6256b969059b7be5b59dc09f1ffe377bc96ee2a4716dd606fa5e825
|
7
|
+
data.tar.gz: baf460771849ca266b896b952cd1cc63313898abf3a3f031372438d7396d3c5f9393a2ca70f8fd98e8a8303f3e51e688f606c18f7f2054a04bc5566f5f15a902
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CanCanCan
|
2
|
+
module NestedAssignmentAndAuthorization
|
3
|
+
class Configuration
|
4
|
+
attr_accessor :silence_raised_errors, :use_smart_nested_authorizations
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
# Allows for stopping unauthorized actions without raising errors
|
8
|
+
@silence_raised_errors = false
|
9
|
+
# Auto-determine what action to auth on nested associations (:create, :update, :destroy)
|
10
|
+
# - will use the action of the root object otherwise.
|
11
|
+
@use_smart_nested_authorizations = true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'action_controller/railtie'
|
2
|
+
|
3
|
+
module CanCanCan
|
4
|
+
class AssignmentAndAuthorization
|
5
|
+
attr_reader :ability, :action_name, :parent_object, :params
|
6
|
+
|
7
|
+
# to handle adding/removing associations by "_ids" suffix
|
8
|
+
# IDS_ATTRIB_PERMISSION_KEY_GEN = Proc.new { |assoc_key| "#{assoc_key.to_s.singularize}_ids".to_sym }
|
9
|
+
# IDS_ACTION_PERMISSION_KEY_GEN = Proc.new { |assoc_key| "_can_add_or_remove_association_#{assoc_key.to_s}".to_sym }
|
10
|
+
|
11
|
+
# to handle updating nested attributes
|
12
|
+
# NESTED_ATTRIB_PERMISSION_KEY_GEN = Proc.new { |assoc_key| "#{assoc_key}_attributes".to_sym }
|
13
|
+
# NESTED_ACTION_PERMISSION_KEY_GEN = Proc.new { |assoc_key| "_can_update_association_#{assoc_key.to_s}".to_sym }
|
14
|
+
|
15
|
+
def initialize(current_ability, action_name, parent_object, params)
|
16
|
+
@ability = current_ability
|
17
|
+
@parent_object = parent_object
|
18
|
+
@params = params
|
19
|
+
if @params.kind_of?(ActionController::Parameters)
|
20
|
+
@params = @params.permit!.to_h
|
21
|
+
end
|
22
|
+
@action_name = action_name.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
def call
|
26
|
+
# Pre-assignment auth check
|
27
|
+
first_authorize = @ability.can?(@action_name, @parent_object)
|
28
|
+
unless first_authorize || CanCanCan::NestedAssignmentAndAuthorization.configuration.silence_raised_errors
|
29
|
+
raise CanCan::AccessDenied.new("Not authorized!", @action_name, @parent_object)
|
30
|
+
end
|
31
|
+
|
32
|
+
return false unless first_authorize
|
33
|
+
|
34
|
+
second_authorize = false
|
35
|
+
|
36
|
+
# sanitized_attribs = ActionController::Parameters.new(
|
37
|
+
# @params
|
38
|
+
# ).permit(@ability.permitted_attributes(@action_name, @parent_object))
|
39
|
+
|
40
|
+
# sanitized_attribs will only contain attributes, not permitted associations
|
41
|
+
sanitized_attribs = sanitize_parameters(@params, @ability.permitted_attributes(@action_name, @parent_object))
|
42
|
+
|
43
|
+
ActiveRecord::Base.transaction do
|
44
|
+
# Attributes
|
45
|
+
@parent_object.assign_attributes(
|
46
|
+
sanitized_attribs.except(
|
47
|
+
*@parent_object.class.nested_attributes_options.keys.collect { |v| "#{v}_attributes".to_sym }
|
48
|
+
)
|
49
|
+
)
|
50
|
+
# Associations
|
51
|
+
instantiate_and_assign_nested_associations(
|
52
|
+
@parent_object,
|
53
|
+
sanitize_parameters(sanitized_attribs, @parent_object.class.nested_attributes_options.keys.collect { |v| "#{v}_attributes".to_sym })
|
54
|
+
)
|
55
|
+
# Post-assignment auth check
|
56
|
+
second_authorize = @ability.can?(@action_name, @parent_object)
|
57
|
+
unless second_authorize
|
58
|
+
# NOTE: Does not halt the controller process, just rolls back the DB
|
59
|
+
raise ActiveRecord::Rollback
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
unless second_authorize || CanCanCan::NestedAssignmentAndAuthorization.configuration.silence_raised_errors
|
64
|
+
raise CanCan::AccessDenied.new("Not authorized!", @action_name, @parent_object)
|
65
|
+
end
|
66
|
+
|
67
|
+
return false unless second_authorize
|
68
|
+
|
69
|
+
save_result = @parent_object.save
|
70
|
+
return save_result
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# recursive
|
76
|
+
# - param_attribs are not sanitized, as we need to check 2 types of assoc permissions
|
77
|
+
# - action and attrib
|
78
|
+
def instantiate_and_assign_nested_associations(parent, param_attribs)
|
79
|
+
return if param_attribs.keys.none?
|
80
|
+
|
81
|
+
parent.nested_attributes_options.each_key do |nested_attrib_key|
|
82
|
+
param_key = "#{nested_attrib_key}_attributes".to_sym
|
83
|
+
|
84
|
+
next unless param_attribs.key?(param_key)
|
85
|
+
|
86
|
+
reflection = parent.class.reflect_on_association(nested_attrib_key)
|
87
|
+
assoc_type = association_type(reflection)
|
88
|
+
assoc_klass = reflection.klass
|
89
|
+
|
90
|
+
if assoc_type == :collection
|
91
|
+
param_attribs[param_key].each do |attribs|
|
92
|
+
child = save_child_and_child_associations(parent, reflection, nested_attrib_key, attribs)
|
93
|
+
next unless child
|
94
|
+
parent.send(nested_attrib_key).send(:<<, child)
|
95
|
+
end
|
96
|
+
elsif assoc_type == :singular
|
97
|
+
attribs = param_attribs[param_key]
|
98
|
+
child = save_child_and_child_associations(parent, reflection, nested_attrib_key, attribs)
|
99
|
+
parent.send("#{nested_attrib_key}=", child) if child
|
100
|
+
else
|
101
|
+
# unknown, do nothing
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
# NOT RECURSIVE
|
108
|
+
def save_child_and_child_associations parent, reflection, nested_attrib_key, attribs
|
109
|
+
assoc_klass = reflection.klass
|
110
|
+
child, child_action = save_child(
|
111
|
+
parent,
|
112
|
+
reflection,
|
113
|
+
nested_attrib_key,
|
114
|
+
attribs.except(
|
115
|
+
*assoc_klass.nested_attributes_options.keys.collect { |v| "#{v}_attributes".to_sym }
|
116
|
+
)
|
117
|
+
)
|
118
|
+
return nil unless child
|
119
|
+
|
120
|
+
sanitized_attribs = sanitize_parameters(attribs, @ability.permitted_attributes(child_action, child))
|
121
|
+
sanitized_attribs = sanitize_parameters(sanitized_attribs, assoc_klass.nested_attributes_options.keys.collect { |v| "#{v}_attributes".to_sym })
|
122
|
+
|
123
|
+
# recursive call
|
124
|
+
instantiate_and_assign_nested_associations(
|
125
|
+
child,
|
126
|
+
sanitized_attribs
|
127
|
+
)
|
128
|
+
return child
|
129
|
+
end
|
130
|
+
|
131
|
+
# NOT RECURSIVE!
|
132
|
+
def save_child parent, reflection, nested_attrib_key, attribs
|
133
|
+
# Check permission on parent
|
134
|
+
assoc_klass = reflection.klass
|
135
|
+
assoc_primary_key = reflection.options[:primary_key]&.to_sym
|
136
|
+
assoc_primary_key ||= :id if assoc_klass.column_names.include?('id')
|
137
|
+
assignment_exceptions = [
|
138
|
+
:id,
|
139
|
+
:_destroy,
|
140
|
+
assoc_primary_key
|
141
|
+
] + assoc_klass.nested_attributes_options.keys.collect{ |v| "#{v}_attributes".to_sym }
|
142
|
+
|
143
|
+
# Had issues with nested records on other root objects not being able to be updated to be nested under this root object
|
144
|
+
if attribs[assoc_primary_key].present?
|
145
|
+
child = assoc_klass.where(assoc_primary_key => attribs[assoc_primary_key]).first
|
146
|
+
end
|
147
|
+
child ||= parent.send(nested_attrib_key).find_or_initialize_by(assoc_primary_key => attribs[assoc_primary_key])
|
148
|
+
|
149
|
+
child_action = @action_name if !CanCanCan::NestedAssignmentAndAuthorization.configuration.use_smart_nested_authorizations
|
150
|
+
child_action ||= :destroy if reflection.options[:allow_destroy] && ['1', 1, true].include?(attribs[:_destroy])
|
151
|
+
child_action ||= :create if child.new_record?
|
152
|
+
child_action ||= :update
|
153
|
+
|
154
|
+
# Pre-assignment auth check
|
155
|
+
first_authorize = @ability.can?(child_action, child)
|
156
|
+
unless first_authorize || CanCanCan::NestedAssignmentAndAuthorization.configuration.silence_raised_errors
|
157
|
+
# TODO if debug
|
158
|
+
# puts "CanCan::AccessDenied.new('Not authorized!', #{child_action}, #{child.class.name})"
|
159
|
+
raise CanCan::AccessDenied.new("Not authorized!", child_action, child)
|
160
|
+
end
|
161
|
+
|
162
|
+
unless first_authorize
|
163
|
+
parent.send(nested_attrib_key).delete(child)
|
164
|
+
return nil
|
165
|
+
end
|
166
|
+
|
167
|
+
sanitized_attribs = sanitize_parameters(attribs, @ability.permitted_attributes(child_action, child))
|
168
|
+
|
169
|
+
second_authorize = false
|
170
|
+
ActiveRecord::Base.transaction do
|
171
|
+
child.assign_attributes(sanitized_attribs.except(*assignment_exceptions))
|
172
|
+
# Post-assignment auth check
|
173
|
+
second_authorize = @ability.can?(child_action, child)
|
174
|
+
unless second_authorize
|
175
|
+
# NOTE: Does not halt the controller process, just rolls back the DB
|
176
|
+
raise ActiveRecord::Rollback
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
unless second_authorize || CanCanCan::NestedAssignmentAndAuthorization.configuration.silence_raised_errors
|
181
|
+
raise CanCan::AccessDenied.new("Not authorized!", child_action, child)
|
182
|
+
end
|
183
|
+
|
184
|
+
unless second_authorize
|
185
|
+
parent.send(nested_attrib_key).delete(child)
|
186
|
+
return nil
|
187
|
+
end
|
188
|
+
|
189
|
+
return child, child_action
|
190
|
+
end
|
191
|
+
|
192
|
+
# Can be overridden if needs be
|
193
|
+
def sanitize_parameters parameters, permit_list
|
194
|
+
# ActionController::Parameters.new(
|
195
|
+
# parameters
|
196
|
+
# ).permit(permit_list).to_h
|
197
|
+
parameters.slice(*permit_list)
|
198
|
+
end
|
199
|
+
|
200
|
+
def association_type(association_reflection)
|
201
|
+
case association_reflection.macro
|
202
|
+
when :belongs_to, :has_one
|
203
|
+
:singular
|
204
|
+
when :has_many, :has_and_belongs_to_many
|
205
|
+
:collection
|
206
|
+
else
|
207
|
+
:unknown
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'cancancan/configuration'
|
2
|
+
require_relative 'cancancan/services/assignment_and_authorization'
|
3
|
+
require_relative 'cancancan/version'
|
4
|
+
|
5
|
+
# include the extension
|
6
|
+
# ActiveRecord::Base.send(:include, Serializer::Concern)
|
7
|
+
|
8
|
+
module CanCanCan
|
9
|
+
module NestedAssignmentAndAuthorization
|
10
|
+
# config src: http://lizabinante.com/blog/creating-a-configurable-ruby-gem/
|
11
|
+
class << self
|
12
|
+
attr_accessor :configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configuration
|
16
|
+
@configuration ||= Configuration.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.reset
|
20
|
+
@configuration = Configuration.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configure
|
24
|
+
yield(configuration)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cancancan_nested_auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- benjamin.dana.software.dev@gmail.com
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-08-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cancancan
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.5.0
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.5.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.5.0
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.5.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rails
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 6.1.7.3
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 6.1.7.3
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.9'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.9'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: listen
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.2'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.2'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rspec-rails
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '4.0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '4.0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: database_cleaner
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.8'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '1.8'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: sqlite3
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.4'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '1.4'
|
117
|
+
description:
|
118
|
+
email:
|
119
|
+
executables: []
|
120
|
+
extensions: []
|
121
|
+
extra_rdoc_files: []
|
122
|
+
files:
|
123
|
+
- lib/cancancan/configuration.rb
|
124
|
+
- lib/cancancan/services/assignment_and_authorization.rb
|
125
|
+
- lib/cancancan/version.rb
|
126
|
+
- lib/cancancan_nested_assignment_and_authorization.rb
|
127
|
+
homepage: https://github.com/danabr75/cancancan_nested_auth
|
128
|
+
licenses:
|
129
|
+
- LGPL-3.0-only
|
130
|
+
metadata: {}
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">"
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '2'
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
requirements: []
|
146
|
+
rubygems_version: 3.4.15
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: A Rails Service class that uses CanCan's authorization at nested associations'
|
150
|
+
individual level
|
151
|
+
test_files: []
|