cancancan_resource_controller 0.0.1

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: a38bfb98462e336b34961d82c4d130022bf466971f55c11699e707b7e6ce4fff
4
+ data.tar.gz: aa0be9ebb6885fc7b15156d0517f1df7c236c3da081a8cf01f6e5b3bf1b456ba
5
+ SHA512:
6
+ metadata.gz: 565013e091449c0a17c33413b7c2c7be15bb660ec644cda55db34df9310cdb7a3b94a3639b256df1745390b1cebc976d121eae3efe39bfe85954f75ef6a4af55
7
+ data.tar.gz: d7b727d66c11e8cf171e2eb6ba0f5175740b8be186ef0e463782e0a619951ccffca6f83fa4a50576d6ebf406629b77a840f74a5ad57b1344ea20fdce8eb3ddd9
@@ -0,0 +1,440 @@
1
+ # How to utilize CanCan Permissions to work with this controller:
2
+ module CanCanCan
3
+ module AbstractResourceController
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :initialize_resource_class
8
+ end
9
+
10
+ # Used to stop infinite recursive on associations (could just be deeply nested structures. Could also be self-referencing).
11
+ MAX_ASSOCIATIVE_NESTED_DEPTH = 60
12
+ REGEX_FOR_HTML_TAG_DETECTION = /.*\<\/?[^_\W]+\>.*/
13
+
14
+ # to handle adding/removing associations by "_ids" suffix
15
+ IDS_ATTIB_PERMISSION_KEY_GEN = Proc.new { |assoc_key| "#{assoc_key.to_s.singularize}_ids".to_sym }
16
+ IDS_ACTION_PERMISSION_KEY_GEN = Proc.new { |assoc_key| "_can_add_or_remove_association_#{assoc_key.to_s}".to_sym }
17
+
18
+ # to handle updating nested attributes
19
+ NESTED_ATTIB_PERMISSION_KEY_GEN = Proc.new { |assoc_key| "#{assoc_key.to_s}_attributes".to_sym }
20
+ NESTED_ACTION_PERMISSION_KEY_GEN = Proc.new { |assoc_key| "_can_update_association_#{assoc_key.to_s}".to_sym }
21
+
22
+ # For Read-Only fields
23
+ # - define this on your class model
24
+ # optional allowlist for incoming parameters for the implemented resource
25
+ # - nil means allowlist is inactive, acceptable parameters are determined by cancan attrib permissions
26
+ # - [] (empty array) means that no parameters will be accepted for resource
27
+ # - [<param1>, <param2>, ...] is self-explanatory, only those listed will be accepted
28
+ # class Resource
29
+ # RESOURCE_CONTROLLER_ATTRIB_ALLOWLIST = nil
30
+ # end
31
+
32
+ # probably a better way to do this. If there is, it's poorly documented.
33
+ # - src: https://www.w3schools.com/TAGS/default.ASP
34
+ # DEFAULT_PARAMETER_SANITIZER_ALLOWED_TAGS - Add to this env var any values to also allow for HTML tags (i.e.: label,span,text_area)
35
+ # DEFAULT_PARAMETER_SANITIZER_ALLOWED_ATTRIBS - Add to this env var any values to also allow for HTML attribs (i.e.: ng-show,ng-hide,data-id)
36
+ DEFAULT_PARAMETER_SANITIZER_ALLOWED_TAGS = (
37
+ %w[
38
+ p
39
+ div
40
+ span
41
+ body
42
+ b
43
+ strong
44
+ br
45
+ center
46
+ font
47
+ label
48
+ pre
49
+ tr
50
+ td
51
+ table
52
+ text_area
53
+ ul
54
+ li
55
+ footer
56
+ em
57
+ ol
58
+ i
59
+ select
60
+ option
61
+ ] + (ENV['DEFAULT_PARAMETER_SANITIZER_ALLOWED_TAGS']&.split(',')&.collect(&:strip) || [])
62
+ ).freeze
63
+ # Only allow attribs that are allowed in HTML friendly text blocks
64
+ # - i.e. NO HREFs!
65
+ DEFAULT_PARAMETER_SANITIZER_ALLOWED_ATTRIBS = (
66
+ %w[
67
+ style
68
+ id
69
+ class
70
+ type
71
+ value
72
+ ] + (ENV['DEFAULT_PARAMETER_SANITIZER_ALLOWED_ATTRIBS']&.split(',')&.collect(&:strip) || [])
73
+ ).freeze
74
+
75
+ def index
76
+ authorize! :index, @resource_class
77
+ @resources ||= @resource_class
78
+
79
+ begin
80
+ @resources = @resources.accessible_by(current_ability)
81
+ rescue CanCan::Error => e
82
+ # The accessible_by call cannot be used with a block 'can' definition
83
+ # Need to switch over to SQL permissions, not using the blocks
84
+ Rails.logger.error "Error: resource class, #{@resource_class.name}, is using CanCan block definitions, not SQL permissions. Unable to run index permission filter"
85
+ raise e
86
+ end
87
+
88
+ @resources = index_resource_query(@resources)
89
+
90
+ respond_with_resources
91
+ end
92
+
93
+ def show
94
+ authorize! :show, @resource_class
95
+ # Allow @resource to be set from subclass controller
96
+ @resource ||= @resource_class.find(params[:id])
97
+ authorize! :show, @resource
98
+
99
+ respond_with_resource
100
+ end
101
+
102
+ def new
103
+ authorize! :create, @resource_class
104
+ @resource ||= @resource_class.new(resource_params)
105
+ # 2nd auth on the object itself
106
+ # Not authing on the nested resources, that could have come in as nested attributes.
107
+ authorize! :create, @resource
108
+
109
+ respond_with_resource
110
+ end
111
+
112
+ def edit
113
+ authorize! :update, @resource_class
114
+ @resource ||= @resource_class.find(params[:id])
115
+ authorize! :update, @resource
116
+
117
+ respond_to do |format|
118
+ format.html # Renders the default
119
+ format.json { render json: @resources }
120
+ format.xml { render xml: @resources }
121
+ format.csv # Renders the default
122
+ format.xlsx # Renders the default
123
+ end
124
+ end
125
+
126
+ def create
127
+ authorize! :create, @resource_class
128
+ # @resource = @resource_class.new(resource_params)
129
+
130
+ # This 2nd @resource initiation is so we run run whitelisting attribs on the object.
131
+ # Class whitelisting is far more broad than object attrib whitelisting.
132
+ # Necessary if class permissions have a permissions-block.
133
+ @resource ||= @resource_class.new(resource_params(@resource))
134
+
135
+ # 2nd auth on the object itself
136
+ authorize! :create, @resource
137
+
138
+ if @resource.save
139
+ respond_with_resource
140
+ else
141
+ begin
142
+ Rails.logger.warn "Failed object validations: could not create #{@resource_class}, id: #{@resource.id}: #{@resource.errors.full_messages}"
143
+ respond_with_resource_invalid
144
+ rescue Exception => e
145
+ Rails.logger.error "CanCanCanResourceController - Caught Internal Server Error: " + e.class.to_s + ': ' + e.message
146
+ Rails.logger.error Rails.backtrace_cleaner.clean(e.backtrace).join("\n").to_s
147
+ respond_with_resource_error
148
+ end
149
+ end
150
+ end
151
+
152
+ def update
153
+ authorize! :update, @resource_class
154
+ @resource ||= @resource_class.find(params[:id])
155
+ authorize! :update, @resource
156
+
157
+ second_authorize = false
158
+ ActiveRecord::Base.transaction do
159
+ @resource.assign_attributes(resource_params(@resource))
160
+ second_authorize = can?(action_name.to_sym, @resource)
161
+ unless second_authorize
162
+ # NOTE: Does not halt the controller process, just rolls back the DB
163
+ raise ActiveRecord::Rollback
164
+ end
165
+ end
166
+
167
+ unless second_authorize
168
+ raise CanCan::AccessDenied.new("Not authorized!", action_name.to_sym, @resource)
169
+ end
170
+
171
+ # 2nd auth, on the updates of the object, without saving, so we can rollback without auth.
172
+ # authorize! :update, @resource
173
+ if @resource.save
174
+ respond_with_resource
175
+ else
176
+ begin
177
+ Rails.logger.warn "Failed object validations: could not update #{@resource_class}, id: #{@resource.id}: #{@resource.errors.full_messages}"
178
+ respond_with_resource_error
179
+ rescue Exception => e
180
+ Rails.logger.error "CanCanCanResourceController - Caught Internal Server Error: " + e.class.to_s + ': ' + e.message
181
+ Rails.logger.error Rails.backtrace_cleaner.clean(e.backtrace).join("\n").to_s
182
+ respond_with_resource_error
183
+ end
184
+ end
185
+ end
186
+
187
+ def destroy
188
+ authorize! :destroy, @resource_class
189
+ @resource ||= @resource_class.find(params[:id])
190
+ authorize! :destroy, @resource
191
+ # retuning the resource in a pre-destroyed state as a destroy response
192
+ results = @resource
193
+ if @resource.destroy
194
+ respond_after_destroy
195
+ else
196
+ begin
197
+ Rails.logger.warn "Failed object validations: could not destroy #{@resource_class}, id: #{@resource.id}: #{@resource.errors.full_messages}"
198
+ respond_with_resource_invalid
199
+ rescue Exception => e
200
+ Rails.logger.error "CanCanCanResourceController - Caught Internal Server Error: " + e.class.to_s + ': ' + e.message
201
+ Rails.logger.error Rails.backtrace_cleaner.clean(e.backtrace).join("\n").to_s
202
+ respond_with_resource_error
203
+ end
204
+ end
205
+ end
206
+
207
+ protected
208
+
209
+ def respond_with_resources
210
+ respond_to do |format|
211
+ format.html # Renders the default
212
+ format.json { render json: @resources }
213
+ end
214
+ end
215
+
216
+ def respond_with_resource
217
+ respond_to do |format|
218
+ format.html # Renders the default
219
+ format.json { render json: @resource }
220
+ end
221
+ end
222
+
223
+ def respond_with_resource_invalid
224
+ respond_to do |format|
225
+ format.html # Renders the default
226
+ format.json { render json: @resource.errors.full_messages, status: 422 }
227
+ end
228
+ end
229
+
230
+ def respond_with_resource_error
231
+ respond_to do |format|
232
+ format.html # Renders the default
233
+ format.json { render json: ["An error has occured. Our support teams have been notified and are working on a solution."], status: 422 }
234
+ end
235
+ end
236
+
237
+ def respond_after_destroy
238
+ respond_to do |format|
239
+ format.html { redirect_to url_for(controller: controller_name, action: 'index') }
240
+ format.json { render json: results, status: :no_content }
241
+ end
242
+ end
243
+
244
+ # meant to be overridden by inheriting controllers.
245
+ def index_resource_query resource_query
246
+ return resource_query
247
+ end
248
+
249
+ # can pass in custom method to supplant 'param.permit', like if you wanted to whitelist a hash instead of params.
250
+ # ex: CanCanCanResourceController#deactivate_helper, permits on fake params: ActionController::Parameters.new(deactive_params)
251
+ def resource_params resource_object = nil, opts = {}, &block
252
+ local_action_name = opts[:custom_action_name] || action_name
253
+ allowlist_permitted = get_nested_attributes_for_class(@resource_class, local_action_name.to_sym, resource_object)
254
+
255
+ # # Rails kludge, issue with allowing parameters with empty arrays
256
+ # # Needs to be nested, recursive
257
+ # # Updating params in-place
258
+ # params.each do |key, value|
259
+ # if key.to_s =~ /(.*)_ids/ && (value == "remove" || value == ["remove"])
260
+ # params[key] = []
261
+ # end
262
+ # end
263
+
264
+ if block_given?
265
+ params_with_only_allowed_parameters = yield(allowlist_permitted)
266
+ else
267
+ params_with_only_allowed_parameters = param_permit(allowlist_permitted)
268
+ end
269
+
270
+ # sanitize all input.
271
+ sanitized_params_with_only_allowed_parameters = clean_parameter_data(params_with_only_allowed_parameters)
272
+
273
+ # recast type (and have to re-permit)
274
+ sanitized_params_with_only_allowed_parameters = ActionController::Parameters.new(sanitized_params_with_only_allowed_parameters).permit(allowlist_permitted)
275
+
276
+ return sanitized_params_with_only_allowed_parameters
277
+ end
278
+
279
+ # recursive
280
+ # src: https://apidock.com/rails/v5.2.3/ActionView/Helpers/SanitizeHelper/sanitize
281
+ def clean_parameter_data param_value
282
+ # was an array element, and not an object.
283
+ # Check for HTML tags
284
+ if param_value.is_a?(String) && !(param_value =~ REGEX_FOR_HTML_TAG_DETECTION).nil?
285
+ # We need a better way, in the future, to specify the allowed values down to the Class and Column level.
286
+ return ActionController::Base.helpers.sanitize(param_value, {tags: self.class::DEFAULT_PARAMETER_SANITIZER_ALLOWED_TAGS, attributes: self.class::DEFAULT_PARAMETER_SANITIZER_ALLOWED_ATTRIBS})
287
+ elsif param_value.is_a?(String) || param_value.is_a?(Integer) || param_value.is_a?(Float) || param_value.nil? || [true, false].include?(param_value)
288
+ return param_value
289
+ end
290
+
291
+ if param_value.is_a?(Hash) || param_value.is_a?(Array) || param_value.is_a?(ActionController::Parameters)
292
+ # good to continue
293
+ else
294
+ error_msg = "Internal Server Error! Unsupported parameter type: #{param_value} (#{param_value.class})"
295
+ Rails.logger.error(error_msg)
296
+ raise error_msg
297
+ end
298
+
299
+ if param_value.is_a?(Array)
300
+ new_array = []
301
+ param_value.each do |array_element|
302
+ new_array << clean_parameter_data(array_element)
303
+ end
304
+ return new_array
305
+ else
306
+ new_hash = {}
307
+ keys = param_value.keys
308
+ keys.each do |key|
309
+ new_hash[key.to_sym] = clean_parameter_data(param_value[key])
310
+ end
311
+ return new_hash
312
+ end
313
+ end
314
+
315
+ # Not checking instances of classes. What if they are object-state dependent?
316
+ # Need to run them again, after object instantiation, but in a different method.
317
+ def get_nested_attributes_for_class resource_class, action_name, root_level_object, depth = 0
318
+ raise "invalid action class: #{action_name.class}" if !action_name.is_a?(Symbol)
319
+ association_parameters = []
320
+ if depth > MAX_ASSOCIATIVE_NESTED_DEPTH
321
+ return association_parameters
322
+ end
323
+
324
+ # Handle resource_class attribs
325
+ # issue here is the 'action_name' on the root 'resource_class' may not be the action that the user has for the 'assoc_class'
326
+ # i.e:
327
+ # We may want the user to update Account, and create attachments on it, but not 'update' attachments.
328
+ if depth == 0
329
+ association_parameters = current_ability.permitted_attributes(action_name, (root_level_object || resource_class))
330
+ else
331
+ association_parameters = current_ability.permitted_attributes(action_name, resource_class)
332
+ end
333
+
334
+ if resource_class.const_defined?('RESOURCE_CONTROLLER_ATTRIB_ALLOWLIST') && !resource_class::RESOURCE_CONTROLLER_ATTRIB_ALLOWLIST.nil?
335
+ association_parameters &= resource_class::RESOURCE_CONTROLLER_ATTRIB_ALLOWLIST
336
+ end
337
+
338
+ # remove customized, non-params, assoc' attrib data by only allowing class columns
339
+ association_parameters &= resource_class.column_names.collect(&:to_sym)
340
+
341
+ resource_class.reflect_on_all_associations(:has_many).each do |assoc_class|
342
+ resource_key = assoc_class.name
343
+ # attrib_permission_key = (resource_key.to_s.singularize + '_ids').to_sym
344
+ attrib_permission_key = IDS_ATTIB_PERMISSION_KEY_GEN.call(resource_key)
345
+ # action_permission_key = ('_can_add_or_remove_association_' + resource_key.to_s).to_sym
346
+ action_permission_key = IDS_ACTION_PERMISSION_KEY_GEN.call(resource_key)
347
+ # i.e. can?(:can_participation_ids, Account)
348
+ # Check to see if we manually gave the user a custom permission
349
+ # # (i.e.: can [:update, :can_account_sector_ids], Account)
350
+ # OR
351
+ # see if it has the attribute on the class's allowed params
352
+ if can?(action_permission_key, resource_class) || can?(action_name, resource_class, attrib_permission_key)
353
+ association_parameters << {
354
+ attrib_permission_key => []
355
+ }
356
+ end
357
+ end
358
+
359
+ resource_class.nested_attributes_options.each do |resource_key, options|
360
+ reflection_class = resource_class.reflect_on_association(resource_key).class
361
+ reflection_type = reflection_class.name
362
+ assoc_class = resource_class.reflect_on_association(resource_key).klass
363
+
364
+ if [
365
+ "ActiveRecord::Reflection::BelongsToReflection",
366
+ "ActiveRecord::Reflection::HasOneReflection",
367
+ "ActiveRecord::Reflection::HasManyReflection"
368
+ ].include?(reflection_type)
369
+ parameter_key = NESTED_ATTIB_PERMISSION_KEY_GEN.call(resource_key)
370
+ permission_key = NESTED_ACTION_PERMISSION_KEY_GEN.call(resource_key)
371
+
372
+ # Can check if permission to update assoc is defined as an action OR as an attrib on the parent resource_class
373
+ if can?(permission_key, resource_class) || can?(action_name, resource_class, parameter_key)
374
+ # Handle recursion
375
+ assoc_parameters = get_nested_attributes_for_class(assoc_class, action_name, root_level_object, depth + 1)
376
+
377
+ if options[:allow_destroy] && can?(:destroy, resource_class)
378
+ assoc_parameters << :_destroy
379
+ end
380
+
381
+ association_parameters << {
382
+ parameter_key => assoc_parameters
383
+ }
384
+ end
385
+ end
386
+ end
387
+
388
+ return association_parameters
389
+ end
390
+
391
+ def param_permit base_parameters
392
+ params.permit(base_parameters)
393
+ end
394
+
395
+ def initialize_resource_class
396
+ # First priority is the namespaced model, e.g. User::Group
397
+ @resource_class ||= begin
398
+ namespaced_class = self.class.name.sub(/Controller$/, '').singularize
399
+ namespaced_class.constantize
400
+ rescue NameError
401
+ nil
402
+ end
403
+
404
+ # Second priority is the top namespace model, e.g. EngineName::Article for EngineName::Admin::ArticlesController
405
+ @resource_class ||= begin
406
+ namespaced_classes = self.class.name.sub(/Controller$/, '').split('::')
407
+ namespaced_class = [namespaced_classes.first, namespaced_classes.last].join('::').singularize
408
+ namespaced_class.constantize
409
+ rescue NameError
410
+ nil
411
+ end
412
+
413
+ # Third priority the camelcased c, i.e. UserGroup
414
+ @resource_class ||= begin
415
+ camelcased_class = self.class.name.sub(/Controller$/, '').gsub('::', '').singularize
416
+ camelcased_class.constantize
417
+ rescue NameError
418
+ nil
419
+ end
420
+
421
+ # Otherwise use the Group class, or fail
422
+ @resource_class ||= begin
423
+ class_name = self.controller_name.classify
424
+ class_name.constantize
425
+ rescue NameError => e
426
+ raise unless e.message.include?(class_name)
427
+ nil
428
+ end
429
+ # portal/portal_imports case needed this
430
+ @resource_class ||= begin
431
+ class_name = controller_path.classify
432
+ class_name.camelize.singularize.constantize
433
+ rescue NameError => e
434
+ raise unless e.message.include?(class_name)
435
+ nil
436
+ end
437
+ end
438
+
439
+ end
440
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'cancancan/abstract_resource_controller'
2
+
3
+ # include the extension
4
+ # ActiveRecord::Base.send(:include, Serializer::Concern)
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cancancan_resource_controller
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - benjamin.dana.software.dev@gmail.com
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-07-27 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/abstract_resource_controller.rb
124
+ - lib/cancancan_resource_controller.rb
125
+ homepage: https://github.com/danabr75/cancancan_resource_controller
126
+ licenses:
127
+ - LGPL-3.0-only
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '2.4'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubygems_version: 3.3.7
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: A Rails Controller Module that uses CanCan's permitted attribs instead of
148
+ typical parameter allowlisting.
149
+ test_files: []