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,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: []