cancancan_nested_auth 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,5 @@
1
+ module CanCanCan
2
+ module NestedAssignmentAndAuthorization
3
+ VERSION = '1.0.0'
4
+ end
5
+ 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: []