rubypitaya 2.15.0 → 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rubypitaya/app-template/Gemfile +6 -6
  3. data/lib/rubypitaya/app-template/Gemfile.lock +94 -68
  4. data/lib/rubypitaya/app-template/Makefile +1 -1
  5. data/lib/rubypitaya/app-template/app/bll/player_bll.rb +2 -2
  6. data/lib/rubypitaya/app-template/app/handlers/player_handler.rb +1 -1
  7. data/lib/rubypitaya/app-template/docker-compose.yml +4 -0
  8. data/lib/rubypitaya/app-template/docker/dev/Dockerfile +14 -7
  9. data/lib/rubypitaya/app-template/docker/entrypoint.sh +2 -4
  10. data/lib/rubypitaya/app-template/docker/prod/Dockerfile +27 -18
  11. data/lib/rubypitaya/app-template/docker/wait_for.sh +184 -0
  12. data/lib/rubypitaya/app-template/helm/Chart.yaml +4 -0
  13. data/lib/rubypitaya/app-template/helm/templates/config-maps/nginx-config.yaml +9 -0
  14. data/lib/rubypitaya/app-template/helm/templates/deployments/connector.yaml +38 -0
  15. data/lib/rubypitaya/app-template/helm/templates/deployments/gameserver-nginx.yaml +40 -0
  16. data/lib/rubypitaya/app-template/helm/templates/deployments/rubypitaya.yaml +71 -0
  17. data/lib/rubypitaya/app-template/helm/templates/role-bindings/rubypitaya.yaml +12 -0
  18. data/lib/rubypitaya/app-template/helm/templates/roles/rubypitaya.yaml +31 -0
  19. data/lib/rubypitaya/app-template/helm/templates/service-accounts/rubypitaya.yaml +4 -0
  20. data/lib/rubypitaya/app-template/helm/templates/services/connector.yaml +13 -0
  21. data/lib/rubypitaya/app-template/helm/templates/services/etcd.yaml +18 -0
  22. data/lib/rubypitaya/app-template/helm/templates/services/gameserver-nginx.yaml +20 -0
  23. data/lib/rubypitaya/app-template/helm/templates/services/gameserver.yaml +20 -0
  24. data/lib/rubypitaya/app-template/helm/templates/services/nats.yaml +13 -0
  25. data/lib/rubypitaya/app-template/helm/templates/services/redis.yaml +14 -0
  26. data/lib/rubypitaya/app-template/helm/templates/services/rubypitaya.yaml +13 -0
  27. data/lib/rubypitaya/app-template/helm/templates/statefulsets/etcd.yaml +28 -0
  28. data/lib/rubypitaya/app-template/helm/templates/statefulsets/nats.yaml +26 -0
  29. data/lib/rubypitaya/app-template/helm/templates/statefulsets/redis.yaml +26 -0
  30. data/lib/rubypitaya/app-template/helm/values.yaml +32 -0
  31. data/lib/rubypitaya/core/config.rb +11 -4
  32. data/lib/rubypitaya/core/config_core.rb +4 -21
  33. data/lib/rubypitaya/core/db/migration/0000000001_create_user_migration.rb +1 -1
  34. data/lib/rubypitaya/core/handler_base.rb +11 -0
  35. data/lib/rubypitaya/core/handler_router.rb +3 -11
  36. data/lib/rubypitaya/core/http_routes.rb +30 -0
  37. data/lib/rubypitaya/core/main.rb +8 -7
  38. data/lib/rubypitaya/core/parameters.rb +449 -138
  39. data/lib/rubypitaya/core/spec-helpers/config_spec_helper.rb +2 -2
  40. data/lib/rubypitaya/core/templates/template_migration.rb.erb +1 -1
  41. data/lib/rubypitaya/version.rb +1 -1
  42. metadata +43 -37
@@ -4,11 +4,10 @@ module RubyPitaya
4
4
 
5
5
  class Config
6
6
 
7
- attr_writer :config_core_override
8
-
9
7
  def initialize
10
8
  @config_core = ConfigCore.new
11
9
  @config_core_override = nil
10
+ @has_config_core_override = false
12
11
 
13
12
  @result_cache = {}
14
13
  end
@@ -17,8 +16,11 @@ module RubyPitaya
17
16
  result = @result_cache[key]
18
17
  return result unless result.nil?
19
18
 
20
- result = @config_core_override[key] unless @config_core_override.nil?
21
- result = @config_core[key] if result.nil?
19
+ if @has_config_core_override
20
+ result = @config_core_override[key]
21
+ else
22
+ result = @config_core[key]
23
+ end
22
24
 
23
25
  @result_cache[key] = result
24
26
 
@@ -33,5 +35,10 @@ module RubyPitaya
33
35
  def clear_cache
34
36
  @result_cache.clear
35
37
  end
38
+
39
+ def config_core_override=(value)
40
+ @config_core_override = value
41
+ @has_config_core_override = !value.nil?
42
+ end
36
43
  end
37
44
  end
@@ -17,8 +17,7 @@ module RubyPitaya
17
17
  end
18
18
 
19
19
  def [](key)
20
- split_key = key.split('/')
21
- @config.dig(*split_key)
20
+ @config[key]
22
21
  end
23
22
 
24
23
  def auto_reload
@@ -42,12 +41,11 @@ module RubyPitaya
42
41
 
43
42
  def load_config_file(configs_folder_path, file_path)
44
43
  config_text = File.open(file_path, &:read)
45
- config_hash = JSON.parse(config_text)
44
+ config_hash = JSON.parse(config_text, symbolize_names: true)
46
45
 
47
- path_array = file_path.sub(/^#{configs_folder_path}/, '')[0..-6]
48
- .split('/')
46
+ file_name = file_path.sub(/^#{configs_folder_path}/, '')[0..-6]
49
47
 
50
- set_config_value(path_array, config_hash)
48
+ @config[file_name] = config_hash
51
49
 
52
50
  rescue Exception => error
53
51
  puts "ERROR: #{error}"
@@ -69,20 +67,5 @@ module RubyPitaya
69
67
  puts "MODIFIED @config: #{path}"
70
68
  end
71
69
  end
72
-
73
- def set_config_value(keys, value)
74
- config = @config
75
-
76
- keys.each_with_index do |key, index|
77
- is_last_index = index == keys.size - 1
78
-
79
- if is_last_index
80
- config[key] = value
81
- else
82
- config[key] = {} unless config.key?(key)
83
- config = config[key]
84
- end
85
- end
86
- end
87
70
  end
88
71
  end
@@ -1,6 +1,6 @@
1
1
  require 'active_record'
2
2
 
3
- class CreateUserMigration < ActiveRecord::Migration[5.1]
3
+ class CreateUserMigration < ActiveRecord::Migration[6.0]
4
4
 
5
5
  enable_extension 'pgcrypto'
6
6
 
@@ -17,6 +17,17 @@ module RubyPitaya
17
17
  @postman = nil
18
18
  end
19
19
 
20
+ def set_attributes(bll, log, redis, setup, config, params, session, postman)
21
+ @bll = bll
22
+ @log = log
23
+ @redis = redis
24
+ @setup = setup
25
+ @config = config
26
+ @params = params
27
+ @session = session
28
+ @postman = postman
29
+ end
30
+
20
31
  def self.non_authenticated_actions(*action_names)
21
32
  self.non_authenticated_routes = action_names.map(&:to_s)
22
33
  end
@@ -62,8 +62,7 @@ module RubyPitaya
62
62
  msg: "Handler #{handler_name} not founded"
63
63
  }
64
64
  end
65
-
66
- unless @handler_name_map[handler_name].methods.include?(action_name.to_sym)
65
+ unless @handler_name_map[handler_name].public_methods(false).include?(action_name.to_sym)
67
66
  return {
68
67
  code: StatusCodes::CODE_ACTION_NOT_FOUND,
69
68
  msg: "Handler #{handler_name} action #{action_name} not founded"
@@ -72,19 +71,12 @@ module RubyPitaya
72
71
 
73
72
  handler = @handler_name_map[handler_name]
74
73
 
75
- handler.bll = bll
76
- handler.log = log
77
- handler.redis = redis
78
- handler.setup = setup
79
- handler.config = config
80
- handler.params = params
81
- handler.session = session
82
- handler.postman = postman
83
-
84
74
  if !handler.class.authenticated_action_name?(action_name)
75
+ handler.set_attributes(bll, log, redis, setup, config, params, session, postman)
85
76
  handler.send(action_name)
86
77
  else
87
78
  if session.authenticated?
79
+ handler.set_attributes(bll, log, redis, setup, config, params, session, postman)
88
80
  handler.send(action_name)
89
81
  else
90
82
  return {
@@ -19,10 +19,14 @@ module RubyPitaya
19
19
  before do
20
20
  content_type :json
21
21
 
22
+ return error_unauthorized unless authorized?(request)
23
+
22
24
  @bll = settings.bll
23
25
  @setup = settings.setup
24
26
  @config = settings.config
25
27
 
28
+ @config.clear_cache
29
+
26
30
  if request.content_type == 'application/json'
27
31
  request_body = request.body.read
28
32
  @params.merge!(JSON.parse(request_body)) if !request_body.blank?
@@ -30,5 +34,31 @@ module RubyPitaya
30
34
 
31
35
  @params = Parameters.new(@params)
32
36
  end
37
+
38
+ private
39
+
40
+ def error_unauthorized
41
+ return halt(401, 'Unauthorized')
42
+ end
43
+
44
+ def authorized?(request)
45
+ return true unless http_auth_enabled?
46
+
47
+ auth_token = request.env['HTTP_AUTHORIZATION']
48
+ return auth_token == get_http_auth
49
+ end
50
+
51
+ def http_auth_enabled?
52
+ return ENV.fetch("HTTP_AUTH_ENABLED") { 'false' } == 'true'
53
+ end
54
+
55
+ def get_http_auth
56
+ user = ENV.fetch("HTTP_AUTH_USER") { '' }
57
+ pass = ENV.fetch("HTTP_AUTH_PASS") { '' }
58
+
59
+ auth_token = ::Base64.strict_encode64("#{user}:#{pass}")
60
+
61
+ return "Basic #{auth_token}"
62
+ end
33
63
  end
34
64
  end
@@ -141,10 +141,10 @@ module RubyPitaya
141
141
 
142
142
  def run_nats_connection
143
143
  @nats_connector.connect do |request|
144
- start_time_seconds = Time.now.to_i
144
+ start_time_seconds = Time.now.to_f
145
145
 
146
146
  message_route = request[:msg][:route]
147
- message_data = request[:msg][:data]
147
+ message_data = request[:msg][:data] || {}
148
148
  message_data = JSON.parse(message_data) if message_data.class == String
149
149
 
150
150
  params = Parameters.new(message_data)
@@ -152,15 +152,16 @@ module RubyPitaya
152
152
  session_id = request[:session][:id]
153
153
  session_uid = request[:session].fetch(:uid, '')
154
154
  session_data = request[:session][:data]
155
- session_data = JSON.parse(session_data) if session_data.class == String
155
+ session_data = JSON.parse(session_data, symbolize_names: true) if session_data.class == String
156
156
  frontend_id = request[:frontendID]
157
157
  metadata = request[:metadata]
158
- metadata = JSON.parse(metadata) if metadata.class == String
158
+ metadata = JSON.parse(metadata, symbolize_names: true) if metadata.class == String
159
159
 
160
160
  @session.update(session_id, session_uid, session_data, metadata,
161
161
  frontend_id)
162
162
 
163
- handler_name, action_name = message_route.split('.')[1..-1]
163
+ # TODO: Validate if handler and action names are not nil
164
+ handler_name, action_name = message_route.scan(/\A\w+\.(\w+)\.(\w+)\z/)[0]
164
165
 
165
166
  @log.info "request -> route: #{message_route}"
166
167
  @log.info " -> data: #{message_data}"
@@ -171,9 +172,9 @@ module RubyPitaya
171
172
  @postman, @redis_connector.redis,
172
173
  @setup, @config, @bll, @log, params)
173
174
 
174
- delta_time_seconds = Time.now.to_i - start_time_seconds
175
+ delta_time_seconds = ((Time.now.to_f - start_time_seconds) * 1000).round(2)
175
176
 
176
- @log.info "response [#{delta_time_seconds}s] -> #{response.to_json}"
177
+ @log.info "response [#{delta_time_seconds}ms] -> #{response.to_json}"
177
178
 
178
179
  response
179
180
  end
@@ -1,4 +1,5 @@
1
1
  require 'date'
2
+ require 'yaml'
2
3
  require 'bigdecimal'
3
4
  require 'stringio'
4
5
 
@@ -9,12 +10,36 @@ require 'active_support/core_ext/array/wrap'
9
10
  module RubyPitaya
10
11
 
11
12
  class ParameterMissing < IndexError
12
- attr_reader :param
13
+ attr_reader :param, :keys
13
14
 
14
- def initialize(param)
15
+ def initialize(param, keys = nil)
15
16
  @param = param
17
+ @keys = keys
16
18
  super("param is missing or the value is empty: #{param}")
17
19
  end
20
+
21
+ class Correction
22
+ def initialize(error)
23
+ @error = error
24
+ end
25
+
26
+ def corrections
27
+ if @error.param && @error.keys
28
+ maybe_these = @error.keys
29
+
30
+ maybe_these.sort_by { |n|
31
+ DidYouMean::Jaro.distance(@error.param.to_s, n)
32
+ }.reverse.first(4)
33
+ else
34
+ []
35
+ end
36
+ end
37
+ end
38
+
39
+ # We may not have DYM, and DYM might not let us register error handlers
40
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
41
+ DidYouMean.correct_error(self, Correction)
42
+ end
18
43
  end
19
44
 
20
45
  class UnpermittedParameters < IndexError
@@ -26,26 +51,98 @@ module RubyPitaya
26
51
  end
27
52
  end
28
53
 
29
- class Parameters < ActiveSupport::HashWithIndifferentAccess
30
- attr_accessor :permitted
31
- alias :permitted? :permitted
54
+ class UnfilteredParameters < ArgumentError
55
+ def initialize # :nodoc:
56
+ super("unable to convert unpermitted parameters to hash")
57
+ end
58
+ end
59
+
60
+ class Parameters
61
+ cattr_accessor :permit_all_parameters, instance_accessor: false, default: false
62
+ cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
63
+
64
+ delegate :keys, :key?, :has_key?, :member?, :values, :has_value?, :value?, :empty?, :include?,
65
+ :as_json, :to_s, :each_key, to: :@parameters
66
+
67
+ cattr_accessor :always_permitted_parameters, default: %w( controller action )
68
+
69
+ class << self
70
+ def nested_attribute?(key, value) # :nodoc:
71
+ /\A-?\d+\z/.match?(key) && (value.is_a?(Hash) || value.is_a?(Parameters))
72
+ end
73
+ end
32
74
 
33
- cattr_accessor :action_on_unpermitted_parameters, :instance_accessor => false
75
+ def initialize(parameters = {})
76
+ @parameters = parameters.with_indifferent_access
77
+ @permitted = self.class.permit_all_parameters
78
+ end
34
79
 
35
- # Never raise an UnpermittedParameters exception because of these params
36
- # are present. They are added by Rails and it's of no concern.
37
- NEVER_UNPERMITTED_PARAMS = %w( controller action )
80
+ def ==(other)
81
+ if other.respond_to?(:permitted?)
82
+ permitted? == other.permitted? && parameters == other.parameters
83
+ else
84
+ @parameters == other
85
+ end
86
+ end
87
+ alias eql? ==
38
88
 
39
- def initialize(attributes = nil)
40
- super(attributes)
41
- @permitted = false
89
+ def hash
90
+ [@parameters.hash, @permitted].hash
91
+ end
92
+
93
+ def to_h
94
+ if permitted?
95
+ convert_parameters_to_hashes(@parameters, :to_h)
96
+ else
97
+ raise UnfilteredParameters
98
+ end
99
+ end
100
+
101
+ def to_hash
102
+ to_h.to_hash
103
+ end
104
+
105
+ def to_query(*args)
106
+ to_h.to_query(*args)
107
+ end
108
+ alias_method :to_param, :to_query
109
+
110
+ def to_unsafe_h
111
+ convert_parameters_to_hashes(@parameters, :to_unsafe_h)
112
+ end
113
+ alias_method :to_unsafe_hash, :to_unsafe_h
114
+
115
+ def each_pair(&block)
116
+ return to_enum(__callee__) unless block_given?
117
+ @parameters.each_pair do |key, value|
118
+ yield [key, convert_hashes_to_parameters(key, value)]
119
+ end
120
+
121
+ self
122
+ end
123
+ alias_method :each, :each_pair
124
+
125
+ def each_value(&block)
126
+ return to_enum(:each_value) unless block_given?
127
+ @parameters.each_pair do |key, value|
128
+ yield convert_hashes_to_parameters(key, value)
129
+ end
130
+
131
+ self
132
+ end
133
+
134
+ def converted_arrays
135
+ @converted_arrays ||= Set.new
136
+ end
137
+
138
+ def permitted?
139
+ @permitted
42
140
  end
43
141
 
44
142
  def permit!
45
143
  each_pair do |key, value|
46
- value = convert_hashes_to_parameters(key, value)
47
- Array.wrap(value).each do |_|
48
- _.permit! if _.respond_to? :permit!
144
+ Array.wrap(value).flatten.each do |v|
145
+ v.permit! if v.respond_to? :permit!
49
146
  end
50
147
  end
51
148
 
@@ -54,7 +151,13 @@ module RubyPitaya
54
151
  end
55
152
 
56
153
  def require(key)
57
- self[key].presence || raise(ParameterMissing.new(key))
154
+ return key.map { |k| require(k) } if key.is_a?(Array)
155
+ value = self[key]
156
+ if value.present? || value == false
157
+ value
158
+ else
159
+ raise ParameterMissing.new(key, @parameters.keys)
160
+ end
58
161
  end
59
162
 
60
163
  alias :required :require
@@ -66,7 +169,7 @@ module RubyPitaya
66
169
  case filter
67
170
  when Symbol, String
68
171
  permitted_scalar_filter(params, filter)
69
- when Hash then
172
+ when Hash
70
173
  hash_filter(params, filter)
71
174
  end
72
175
  end
@@ -77,169 +180,377 @@ module RubyPitaya
77
180
  end
78
181
 
79
182
  def [](key)
80
- convert_hashes_to_parameters(key, super)
183
+ convert_hashes_to_parameters(key, @parameters[key])
184
+ end
185
+
186
+ def []=(key, value)
187
+ @parameters[key] = value
81
188
  end
82
189
 
83
190
  def fetch(key, *args)
84
- convert_hashes_to_parameters(key, super, false)
85
- rescue KeyError, IndexError
86
- raise ParameterMissing.new(key)
191
+ convert_value_to_parameters(
192
+ @parameters.fetch(key) {
193
+ if block_given?
194
+ yield
195
+ else
196
+ args.fetch(0) { raise ActionController::ParameterMissing.new(key, @parameters.keys) }
197
+ end
198
+ }
199
+ )
200
+ end
201
+
202
+ def dig(*keys)
203
+ convert_hashes_to_parameters(keys.first, @parameters[keys.first])
204
+ @parameters.dig(*keys)
205
+ end
206
+
207
+ def slice!(*keys)
208
+ @parameters.slice!(*keys)
209
+ self
210
+ end
211
+
212
+ def except(*keys)
213
+ new_instance_with_inherited_permitted_status(@parameters.except(*keys))
214
+ end
215
+
216
+ def extract!(*keys)
217
+ new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
218
+ end
219
+
220
+ def transform_values
221
+ return to_enum(:transform_values) unless block_given?
222
+ new_instance_with_inherited_permitted_status(
223
+ @parameters.transform_values { |v| yield convert_value_to_parameters(v) }
224
+ )
225
+ end
226
+
227
+ def transform_values!
228
+ return to_enum(:transform_values!) unless block_given?
229
+ @parameters.transform_values! { |v| yield convert_value_to_parameters(v) }
230
+ self
231
+ end
232
+
233
+ def transform_keys(&block)
234
+ return to_enum(:transform_keys) unless block_given?
235
+ new_instance_with_inherited_permitted_status(
236
+ @parameters.transform_keys(&block)
237
+ )
238
+ end
239
+
240
+ def transform_keys!(&block)
241
+ return to_enum(:transform_keys!) unless block_given?
242
+ @parameters.transform_keys!(&block)
243
+ self
244
+ end
245
+
246
+ def deep_transform_keys(&block)
247
+ new_instance_with_inherited_permitted_status(
248
+ @parameters.deep_transform_keys(&block)
249
+ )
250
+ end
251
+
252
+ def deep_transform_keys!(&block)
253
+ @parameters.deep_transform_keys!(&block)
254
+ self
255
+ end
256
+
257
+ def delete(key, &block)
258
+ convert_value_to_parameters(@parameters.delete(key, &block))
259
+ end
260
+
261
+ def select(&block)
262
+ new_instance_with_inherited_permitted_status(@parameters.select(&block))
87
263
  end
88
264
 
89
- def slice(*keys)
90
- self.class.new(super).tap do |new_instance|
91
- new_instance.instance_variable_set :@permitted, @permitted
265
+ def select!(&block)
266
+ @parameters.select!(&block)
267
+ self
268
+ end
269
+ alias_method :keep_if, :select!
270
+
271
+ def reject(&block)
272
+ new_instance_with_inherited_permitted_status(@parameters.reject(&block))
273
+ end
274
+
275
+ def reject!(&block)
276
+ @parameters.reject!(&block)
277
+ self
278
+ end
279
+ alias_method :delete_if, :reject!
280
+
281
+ def compact
282
+ new_instance_with_inherited_permitted_status(@parameters.compact)
283
+ end
284
+
285
+ def compact!
286
+ self if @parameters.compact!
287
+ end
288
+
289
+ def compact_blank
290
+ reject { |_k, v| v.blank? }
291
+ end
292
+
293
+ def compact_blank!
294
+ reject! { |_k, v| v.blank? }
295
+ end
296
+
297
+ def values_at(*keys)
298
+ convert_value_to_parameters(@parameters.values_at(*keys))
299
+ end
300
+
301
+ def merge(other_hash)
302
+ new_instance_with_inherited_permitted_status(
303
+ @parameters.merge(other_hash.to_h)
304
+ )
305
+ end
306
+
307
+ def merge!(other_hash)
308
+ @parameters.merge!(other_hash.to_h)
309
+ self
310
+ end
311
+
312
+ def reverse_merge(other_hash)
313
+ new_instance_with_inherited_permitted_status(
314
+ other_hash.to_h.merge(@parameters)
315
+ )
316
+ end
317
+ alias_method :with_defaults, :reverse_merge
318
+
319
+ def reverse_merge!(other_hash)
320
+ @parameters.merge!(other_hash.to_h) { |key, left, right| left }
321
+ self
322
+ end
323
+ alias_method :with_defaults!, :reverse_merge!
324
+
325
+ def stringify_keys
326
+ dup
327
+ end
328
+
329
+ def inspect
330
+ "#<#{self.class} #{@parameters} permitted: #{@permitted}>"
331
+ end
332
+
333
+ def self.hook_into_yaml_loading
334
+ YAML.load_tags["!ruby/hash-with-ivars:ActionController::Parameters"] = name
335
+ YAML.load_tags["!ruby/hash:ActionController::Parameters"] = name
336
+ end
337
+ hook_into_yaml_loading
338
+
339
+ def init_with(coder)
340
+ case coder.tag
341
+ when "!ruby/hash:ActionController::Parameters"
342
+ @parameters = coder.map.with_indifferent_access
343
+ @permitted = false
344
+ when "!ruby/hash-with-ivars:ActionController::Parameters"
345
+ @parameters = coder.map["elements"].with_indifferent_access
346
+ @permitted = coder.map["ivars"][:@permitted]
347
+ when "!ruby/object:ActionController::Parameters"
348
+ @parameters, @permitted = coder.map["parameters"], coder.map["permitted"]
92
349
  end
93
350
  end
94
351
 
95
- def dup
96
- self.class.new(self).tap do |duplicate|
97
- duplicate.default = default
98
- duplicate.instance_variable_set :@permitted, @permitted
352
+ def deep_dup
353
+ self.class.new(@parameters.deep_dup).tap do |duplicate|
354
+ duplicate.permitted = @permitted
99
355
  end
100
356
  end
101
357
 
102
358
  protected
103
- def convert_value(value, conversion = nil)
104
- if value.class == Hash
105
- Parameters.new(value)
106
- elsif value.is_a?(Array)
107
- value.dup.replace(value.map { |e| convert_value(e) })
108
- else
109
- value
110
- end
111
- end
359
+
360
+ attr_reader :parameters
361
+
362
+ attr_writer :permitted
363
+
364
+ def nested_attributes?
365
+ @parameters.any? { |k, v| Parameters.nested_attribute?(k, v) }
366
+ end
367
+
368
+ def each_nested_attribute
369
+ hash = self.class.new
370
+ self.each { |k, v| hash[k] = yield v if Parameters.nested_attribute?(k, v) }
371
+ hash
372
+ end
112
373
 
113
374
  private
114
375
 
115
- def convert_hashes_to_parameters(key, value, assign_if_converted=true)
116
- converted = convert_value_to_parameters(value)
117
- self[key] = converted if assign_if_converted && !converted.equal?(value)
376
+ def new_instance_with_inherited_permitted_status(hash)
377
+ self.class.new(hash).tap do |new_instance|
378
+ new_instance.permitted = @permitted
379
+ end
380
+ end
381
+
382
+ def convert_parameters_to_hashes(value, using)
383
+ case value
384
+ when Array
385
+ value.map { |v| convert_parameters_to_hashes(v, using) }
386
+ when Hash
387
+ value.transform_values do |v|
388
+ convert_parameters_to_hashes(v, using)
389
+ end.with_indifferent_access
390
+ when Parameters
391
+ value.send(using)
392
+ else
393
+ value
394
+ end
395
+ end
396
+
397
+ def convert_hashes_to_parameters(key, value)
398
+ converted = convert_value_to_parameters(value)
399
+ @parameters[key] = converted unless converted.equal?(value)
400
+ converted
401
+ end
402
+
403
+ def convert_value_to_parameters(value)
404
+ case value
405
+ when Array
406
+ return value if converted_arrays.member?(value)
407
+ converted = value.map { |_| convert_value_to_parameters(_) }
408
+ converted_arrays << converted
118
409
  converted
410
+ when Hash
411
+ self.class.new(value)
412
+ else
413
+ value
119
414
  end
415
+ end
120
416
 
121
- def convert_value_to_parameters(value)
122
- if value.is_a?(Array)
123
- value.map { |_| convert_value_to_parameters(_) }
124
- elsif value.is_a?(Parameters) || !value.is_a?(Hash)
125
- value
417
+ def each_element(object, &block)
418
+ case object
419
+ when Array
420
+ object.grep(Parameters).map { |el| yield el }.compact
421
+ when Parameters
422
+ if object.nested_attributes?
423
+ object.each_nested_attribute(&block)
126
424
  else
127
- self.class.new(value)
425
+ yield object
128
426
  end
129
427
  end
428
+ end
130
429
 
131
- #
132
- # --- Filtering ----------------------------------------------------------
133
- #
134
-
135
- # This is a white list of permitted scalar types that includes the ones
136
- # supported in XML and JSON requests.
137
- #
138
- # This list is in particular used to filter ordinary requests, String goes
139
- # as first element to quickly short-circuit the common case.
140
- #
141
- # If you modify this collection please update the README.
142
- PERMITTED_SCALAR_TYPES = [
143
- String,
144
- Symbol,
145
- NilClass,
146
- Numeric,
147
- TrueClass,
148
- FalseClass,
149
- Date,
150
- Time,
151
- # DateTimes are Dates, we document the type but avoid the redundant check.
152
- StringIO,
153
- IO,
154
- ]
155
-
156
- def permitted_scalar?(value)
157
- PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
158
- end
159
-
160
- def array_of_permitted_scalars?(value)
161
- if value.is_a?(Array)
162
- value.all? {|element| permitted_scalar?(element)}
430
+ def unpermitted_parameters!(params)
431
+ unpermitted_keys = unpermitted_keys(params)
432
+ if unpermitted_keys.any?
433
+ case self.class.action_on_unpermitted_parameters
434
+ when :log
435
+ name = "unpermitted_parameters.action_controller"
436
+ ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
437
+ when :raise
438
+ raise ActionController::UnpermittedParameters.new(unpermitted_keys)
163
439
  end
164
440
  end
441
+ end
165
442
 
166
- def permitted_scalar_filter(params, key)
167
- if has_key?(key) && permitted_scalar?(self[key])
168
- params[key] = self[key]
169
- end
443
+ def unpermitted_keys(params)
444
+ keys - params.keys - always_permitted_parameters
445
+ end
170
446
 
171
- keys.grep(/\A#{Regexp.escape(key.to_s)}\(\d+[if]?\)\z/).each do |key|
172
- if permitted_scalar?(self[key])
173
- params[key] = self[key]
174
- end
175
- end
447
+ PERMITTED_SCALAR_TYPES = [
448
+ String,
449
+ Symbol,
450
+ NilClass,
451
+ Numeric,
452
+ TrueClass,
453
+ FalseClass,
454
+ Date,
455
+ Time,
456
+ # DateTimes are Dates, we document the type but avoid the redundant check.
457
+ StringIO,
458
+ IO,
459
+ ]
460
+
461
+ def permitted_scalar?(value)
462
+ PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) }
463
+ end
464
+
465
+ def permitted_scalar_filter(params, permitted_key)
466
+ permitted_key = permitted_key.to_s
467
+
468
+ if has_key?(permitted_key) && permitted_scalar?(self[permitted_key])
469
+ params[permitted_key] = self[permitted_key]
176
470
  end
177
471
 
178
- def array_of_permitted_scalars_filter(params, key, hash = self)
179
- if hash.has_key?(key) && array_of_permitted_scalars?(hash[key])
180
- params[key] = hash[key]
181
- end
472
+ each_key do |key|
473
+ next unless key =~ /\(\d+[if]?\)\z/
474
+ next unless $~.pre_match == permitted_key
475
+
476
+ params[key] = self[key] if permitted_scalar?(self[key])
477
+ end
478
+ end
479
+
480
+ def array_of_permitted_scalars?(value)
481
+ if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
482
+ yield value
182
483
  end
484
+ end
183
485
 
184
- def hash_filter(params, filter)
185
- filter = filter.with_indifferent_access
486
+ def non_scalar?(value)
487
+ value.is_a?(Array) || value.is_a?(Parameters)
488
+ end
186
489
 
187
- # Slicing filters out non-declared keys.
188
- slice(*filter.keys).each do |key, value|
189
- next unless value
490
+ EMPTY_ARRAY = []
491
+ EMPTY_HASH = {}
492
+ def hash_filter(params, filter)
493
+ filter = filter.with_indifferent_access
190
494
 
191
- if filter[key] == []
192
- # Declaration {:comment_ids => []}.
193
- array_of_permitted_scalars_filter(params, key)
194
- else
195
- # Declaration {:user => :name} or {:user => [:name, :age, {:adress => ...}]}.
196
- params[key] = each_element(value) do |element, index|
197
- if element.is_a?(Hash)
198
- element = self.class.new(element) unless element.respond_to?(:permit)
199
- element.permit(*Array.wrap(filter[key]))
200
- elsif filter[key].is_a?(Hash) && filter[key][index] == []
201
- array_of_permitted_scalars_filter(params, index, value)
202
- end
203
- end
495
+ # Slicing filters out non-declared keys.
496
+ slice(*filter.keys).each do |key, value|
497
+ next unless value
498
+ next unless has_key? key
499
+
500
+ if filter[key] == EMPTY_ARRAY
501
+ # Declaration { comment_ids: [] }.
502
+ array_of_permitted_scalars?(self[key]) do |val|
503
+ params[key] = val
504
+ end
505
+ elsif filter[key] == EMPTY_HASH
506
+ # Declaration { preferences: {} }.
507
+ if value.is_a?(Parameters)
508
+ params[key] = permit_any_in_parameters(value)
509
+ end
510
+ elsif non_scalar?(value)
511
+ # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
512
+ params[key] = each_element(value) do |element|
513
+ element.permit(*Array.wrap(filter[key]))
204
514
  end
205
515
  end
206
516
  end
517
+ end
207
518
 
208
- def each_element(value)
209
- if value.is_a?(Array)
210
- value.map { |el| yield el }.compact
211
- # fields_for on an array of records uses numeric hash keys.
212
- elsif fields_for_style?(value)
213
- hash = value.class.new
214
- value.each { |k,v| hash[k] = yield(v, k) }
215
- hash
216
- else
217
- yield value
519
+ def permit_any_in_parameters(params)
520
+ self.class.new.tap do |sanitized|
521
+ params.each do |key, value|
522
+ case value
523
+ when ->(v) { permitted_scalar?(v) }
524
+ sanitized[key] = value
525
+ when Array
526
+ sanitized[key] = permit_any_in_array(value)
527
+ when Parameters
528
+ sanitized[key] = permit_any_in_parameters(value)
529
+ else
530
+ # Filter this one out.
531
+ end
218
532
  end
219
533
  end
534
+ end
220
535
 
221
- def fields_for_style?(object)
222
- object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
223
- end
224
-
225
- def unpermitted_parameters!(params)
226
- return unless self.class.action_on_unpermitted_parameters
227
-
228
- unpermitted_keys = unpermitted_keys(params)
229
-
230
- if unpermitted_keys.any?
231
- case self.class.action_on_unpermitted_parameters
232
- when :log
233
- name = "unpermitted_parameters.action_controller"
234
- ActiveSupport::Notifications.instrument(name, :keys => unpermitted_keys)
235
- when :raise
236
- raise UnpermittedParameters.new(unpermitted_keys)
536
+ def permit_any_in_array(array)
537
+ [].tap do |sanitized|
538
+ array.each do |element|
539
+ case element
540
+ when ->(e) { permitted_scalar?(e) }
541
+ sanitized << element
542
+ when Parameters
543
+ sanitized << permit_any_in_parameters(element)
544
+ else
545
+ # Filter this one out.
237
546
  end
238
547
  end
239
548
  end
549
+ end
240
550
 
241
- def unpermitted_keys(params)
242
- self.keys - params.keys - NEVER_UNPERMITTED_PARAMS
243
- end
551
+ def initialize_copy(source)
552
+ super
553
+ @parameters = @parameters.dup
554
+ end
244
555
  end
245
556
  end