moesif_rack 1.4.18 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,20 +5,19 @@ require 'base64'
5
5
  require 'zlib'
6
6
  require 'stringio'
7
7
  require 'rack'
8
- require_relative './client_ip.rb'
9
- require_relative './app_config.rb'
10
- require_relative './update_user.rb'
11
- require_relative './update_company.rb'
12
- require_relative './moesif_helpers.rb'
8
+ require_relative './client_ip'
9
+ require_relative './app_config'
10
+ require_relative './update_user'
11
+ require_relative './update_company'
12
+ require_relative './moesif_helpers'
13
+ require_relative './governance_rules'
13
14
 
14
15
  module MoesifRack
15
-
16
16
  class MoesifMiddleware
17
- def initialize app, options = {}
17
+ def initialize(app, options = {})
18
18
  @app = app
19
- if not options['application_id']
20
- raise 'application_id required for Moesif Middleware'
21
- end
19
+ raise 'application_id required for Moesif Middleware' unless options['application_id']
20
+
22
21
  @api_client = MoesifApi::MoesifAPIClient.new(options['application_id'])
23
22
  @api_controller = @api_client.api
24
23
 
@@ -32,35 +31,40 @@ module MoesifRack
32
31
  @debug = options['debug']
33
32
  @app_config = AppConfig.new(@debug)
34
33
  @moesif_helpers = MoesifHelpers.new(@debug)
35
- @config = @app_config.get_config(@api_controller)
34
+
36
35
  @config_etag = nil
37
36
  @last_config_download_time = Time.now.utc
38
- @last_worker_run = Time.now.utc
39
- @config_dict = Hash.new
37
+ @config_dict = {}
40
38
  @disable_transaction_id = options['disable_transaction_id'] || false
41
39
  @log_body = options.fetch('log_body', true)
42
- @batch_size = options['batch_size'] || 25
40
+ @batch_size = options['batch_size'] || 200
41
+ @event_queue_size = options['event_queue_size'] || 1000
43
42
  @batch_max_time = options['batch_max_time'] || 2
44
43
  @events_queue = Queue.new
45
44
  @event_response_config_etag = nil
46
- start_worker()
45
+ @governance_manager = GovernanceRules.new(@debug)
46
+
47
+ # start the worker and Update the last worker run
48
+ @last_worker_run = Time.now.utc
49
+ start_worker
47
50
 
48
51
  begin
49
52
  new_config = @app_config.get_config(@api_controller)
50
- if !new_config.nil?
53
+ unless new_config.nil?
51
54
  @config, @config_etag, @last_config_download_time = @app_config.parse_configuration(new_config)
52
55
  end
53
- rescue => exception
56
+ @governance_manager.load_rules(@api_controller)
57
+ rescue StandardError => e
54
58
  @moesif_helpers.log_debug 'Error while parsing application configuration on initialization'
55
- @moesif_helpers.log_debug exception.to_s
59
+ @moesif_helpers.log_debug e.to_s
56
60
  end
57
61
  @capture_outoing_requests = options['capture_outoing_requests']
58
62
  @capture_outgoing_requests = options['capture_outgoing_requests']
59
- if @capture_outoing_requests || @capture_outgoing_requests
60
- @moesif_helpers.log_debug 'Start Capturing outgoing requests'
61
- require_relative '../../moesif_capture_outgoing/httplog.rb'
62
- MoesifCaptureOutgoing.start_capture_outgoing(options)
63
- end
63
+ return unless @capture_outoing_requests || @capture_outgoing_requests
64
+
65
+ @moesif_helpers.log_debug 'Start Capturing outgoing requests'
66
+ require_relative '../../moesif_capture_outgoing/httplog'
67
+ MoesifCaptureOutgoing.start_capture_outgoing(options)
64
68
  end
65
69
 
66
70
  def update_user(user_profile)
@@ -88,17 +92,17 @@ module MoesifRack
88
92
  end
89
93
 
90
94
  def transform_headers(headers)
91
- Hash[headers.map { |k, v| [k.downcase, v]}]
95
+ Hash[headers.map { |k, v| [k.downcase, v] }]
92
96
  end
93
97
 
94
98
  def base64_encode_body(body)
95
- return Base64.encode64(body), 'base64'
99
+ [Base64.encode64(body), 'base64']
96
100
  end
97
101
 
98
102
  def @moesif_helpers.log_debug(message)
99
- if @debug
100
- puts("#{Time.now.to_s} [Moesif Middleware] PID #{Process.pid} TID #{Thread.current.object_id} #{message}")
101
- end
103
+ return unless @debug
104
+
105
+ puts("#{Time.now} [Moesif Middleware] PID #{Process.pid} TID #{Thread.current.object_id} #{message}")
102
106
  end
103
107
 
104
108
  def parse_multipart(multipart_form_data, content_type)
@@ -110,9 +114,10 @@ module MoesifRack
110
114
  tempfile = Rack::Multipart::Parser::TEMPFILE_FACTORY
111
115
  bufsize = Rack::Multipart::Parser::BUFSIZE
112
116
  query_parser = Rack::Utils.default_query_parser
113
- result = Rack::Multipart::Parser.parse(io, sanitized_multipart_form_data.length, content_type, tempfile, bufsize, query_parser)
117
+ result = Rack::Multipart::Parser.parse(io, sanitized_multipart_form_data.length, content_type, tempfile, bufsize,
118
+ query_parser)
114
119
 
115
- @moesif_helpers.log_debug("multipart parse result")
120
+ @moesif_helpers.log_debug('multipart parse result')
116
121
  @moesif_helpers.log_debug(result.inspect)
117
122
 
118
123
  # this is a hash shold be treated as JSON down the road.
@@ -121,63 +126,78 @@ module MoesifRack
121
126
 
122
127
  def parse_body(body, headers)
123
128
  begin
124
- if (body.instance_of?(Hash) || body.instance_of?(Array))
129
+ if body.instance_of?(Hash) || body.instance_of?(Array)
125
130
  parsed_body = body
126
131
  transfer_encoding = 'json'
127
132
  elsif start_with_json(body)
128
133
  parsed_body = JSON.parse(body)
129
134
  transfer_encoding = 'json'
130
- elsif headers.key?('content-type') && ((headers['content-type'].downcase).include? 'multipart/form-data')
135
+ elsif headers.key?('content-type') && (headers['content-type'].downcase.include? 'multipart/form-data')
131
136
  parsed_body = parse_multipart(body, headers['content-type'])
132
137
  transfer_encoding = 'json'
133
- elsif headers.key?('content-encoding') && ((headers['content-encoding'].downcase).include? "gzip")
138
+ elsif headers.key?('content-encoding') && (headers['content-encoding'].downcase.include? 'gzip')
134
139
  uncompressed_string = decompress_body(body)
135
140
  parsed_body, transfer_encoding = base64_encode_body(uncompressed_string)
136
141
  else
137
142
  parsed_body, transfer_encoding = base64_encode_body(body)
138
143
  end
139
- rescue
144
+ rescue StandardError
140
145
  parsed_body, transfer_encoding = base64_encode_body(body)
141
146
  end
142
- return parsed_body, transfer_encoding
147
+ [parsed_body, transfer_encoding]
143
148
  end
144
149
 
145
150
  def start_worker
146
- Thread::new do
147
- @last_worker_run = Time.now.utc
151
+ Thread.new do
148
152
  loop do
153
+ # Update the last worker run, in case the events_queue is empty
154
+ @last_worker_run = Time.now.utc
149
155
  begin
150
- until @events_queue.empty? do
151
- batch_events = []
152
- until batch_events.size == @batch_size || @events_queue.empty? do
153
- batch_events << @events_queue.pop
154
- end
155
- @moesif_helpers.log_debug("Sending #{batch_events.size.to_s} events to Moesif")
156
- event_api_response = @api_controller.create_events_batch(batch_events)
157
- @event_response_config_etag = event_api_response[:x_moesif_config_etag]
158
- @moesif_helpers.log_debug(event_api_response.to_s)
159
- @moesif_helpers.log_debug("Events successfully sent to Moesif")
156
+ until @events_queue.empty?
157
+ # Update the last worker run in case sending events take more than 60 seconds
158
+ @last_worker_run = Time.now.utc
159
+ # Populate the batch events from queue
160
+ batch_events = []
161
+ batch_events << @events_queue.pop until batch_events.size == @batch_size || @events_queue.empty?
162
+ @moesif_helpers.log_debug("Sending #{batch_events.size} events to Moesif")
163
+ event_api_response = @api_controller.create_events_batch(batch_events)
164
+ @event_response_config_etag = event_api_response[:x_moesif_config_etag]
165
+ @moesif_helpers.log_debug(event_api_response.to_s)
166
+ @moesif_helpers.log_debug('Events successfully sent to Moesif')
160
167
  end
161
168
 
162
- if @events_queue.empty?
163
- @moesif_helpers.log_debug("No events to read from the queue")
169
+ @moesif_helpers.log_debug('No events to read from the queue') if @events_queue.empty?
170
+ if (!@event_response_config_etag.nil? && !@config_etag.nil? && @config_etag != @event_response_config_etag) || (Time.now.utc > (@last_config_download_time + 300))
171
+ begin
172
+ @moesif_helpers.log_debug('try to reload config and rules again')
173
+ new_config = @app_config.get_config(@api_controller)
174
+ unless new_config.nil?
175
+ @config, @config_etag, @last_config_download_time = @app_config.parse_configuration(new_config)
176
+ end
177
+ # since logic to reload config is already here for every 5 minutes,
178
+ # reload rules here also.
179
+ @governance_manager.load_rules(@api_controller)
180
+ rescue StandardError => e
181
+ @moesif_helpers.log_debug 'Error while updating the application configuration'
182
+ @moesif_helpers.log_debug e.to_s
183
+ end
164
184
  end
165
185
 
166
186
  sleep @batch_max_time
167
187
  rescue MoesifApi::APIException => e
168
188
  if e.response_code.between?(401, 403)
169
- puts "Unathorized accesss sending event to Moesif. Please verify your Application Id."
189
+ puts 'Unathorized accesss sending event to Moesif. Please verify your Application Id.'
170
190
  @moesif_helpers.log_debug(e.to_s)
171
191
  end
172
- @moesif_helpers.log_debug("Error sending event to Moesif, with status code #{e.response_code.to_s}")
173
- rescue => e
192
+ @moesif_helpers.log_debug("Error sending event to Moesif, with status code #{e.response_code}")
193
+ rescue StandardError => e
174
194
  @moesif_helpers.log_debug(e.to_s)
175
195
  end
176
196
  end
177
197
  end
178
198
  end
179
199
 
180
- def call env
200
+ def call(env)
181
201
  start_time = Time.now.utc.iso8601(3)
182
202
 
183
203
  @moesif_helpers.log_debug('Calling Moesif middleware')
@@ -185,15 +205,15 @@ module MoesifRack
185
205
  status, headers, body = @app.call env
186
206
  end_time = Time.now.utc.iso8601(3)
187
207
 
188
- process_send = lambda do
208
+ make_event_model = lambda do
189
209
  req = Rack::Request.new(env)
190
210
  complex_copy = env.dup
191
211
 
192
212
  # Filter hash to only have keys of type string
193
- complex_copy = complex_copy.select { |k, v| k.is_a? String }
213
+ complex_copy = complex_copy.select { |k, _v| k.is_a? String }
194
214
 
195
215
  req_headers = {}
196
- complex_copy.select {|k,v| k.start_with?('HTTP_', 'CONTENT_') }.each do |key, val|
216
+ complex_copy.select { |k, _v| k.start_with?('HTTP_', 'CONTENT_') }.each do |key, val|
197
217
  new_key = key.sub(/^HTTP_/, '')
198
218
  new_key = new_key.sub('_', '-')
199
219
  req_headers[new_key] = val
@@ -206,167 +226,187 @@ module MoesifRack
206
226
  req_body_transfer_encoding = nil
207
227
  req_body = nil
208
228
 
209
- if @log_body
210
- if req_body_string && req_body_string.length != 0
211
- req_body, req_body_transfer_encoding = parse_body(req_body_string, transform_headers(req_headers))
212
- end
229
+ if @log_body && (req_body_string && req_body_string.length != 0)
230
+ req_body, req_body_transfer_encoding = parse_body(req_body_string,
231
+ transform_headers(req_headers))
213
232
  end
214
233
 
215
234
  rsp_headers = headers.dup
216
235
 
217
- rsp_body_string = get_response_body(body);
236
+ rsp_body_string = get_response_body(body)
218
237
  rsp_body_transfer_encoding = nil
219
238
  rsp_body = nil
220
239
 
221
- if @log_body
222
- if rsp_body_string && rsp_body_string.length != 0
223
- rsp_body, rsp_body_transfer_encoding = parse_body(rsp_body_string, transform_headers(rsp_headers))
224
- end
240
+ if @log_body && (rsp_body_string && rsp_body_string.length != 0)
241
+ rsp_body, rsp_body_transfer_encoding = parse_body(rsp_body_string,
242
+ transform_headers(rsp_headers))
225
243
  end
226
244
 
227
- event_req = MoesifApi::EventRequestModel.new()
245
+ event_req = MoesifApi::EventRequestModel.new
228
246
  event_req.time = start_time
229
247
  event_req.uri = req.url
230
248
  event_req.verb = req.request_method
231
249
 
232
- if @api_version
233
- event_req.api_version = @api_version
234
- end
250
+ event_req.api_version = @api_version if @api_version
235
251
 
236
252
  # Add Transaction Id to the Request Header
237
- if !@disable_transaction_id
238
- req_trans_id = req_headers["X-MOESIF_TRANSACTION_ID"]
253
+ unless @disable_transaction_id
254
+ req_trans_id = req_headers['X-MOESIF_TRANSACTION_ID']
239
255
  if !req_trans_id.nil?
240
256
  transaction_id = req_trans_id
241
- if transaction_id.strip.empty?
242
- transaction_id = SecureRandom.uuid
243
- end
257
+ transaction_id = SecureRandom.uuid if transaction_id.strip.empty?
244
258
  else
245
259
  transaction_id = SecureRandom.uuid
246
260
  end
247
261
  # Add Transaction Id to Request Header
248
- req_headers["X-Moesif-Transaction-Id"] = transaction_id
262
+ req_headers['X-Moesif-Transaction-Id'] = transaction_id
249
263
  # Filter out the old key as HTTP Headers case are not preserved
250
- if req_headers.key?("X-MOESIF_TRANSACTION_ID")
251
- req_headers = req_headers.except("X-MOESIF_TRANSACTION_ID")
252
- end
264
+ req_headers = req_headers.except('X-MOESIF_TRANSACTION_ID') if req_headers.key?('X-MOESIF_TRANSACTION_ID')
253
265
  end
254
266
 
255
267
  # Add Transaction Id to the Response Header
256
- if !transaction_id.nil?
257
- rsp_headers["X-Moesif-Transaction-Id"] = transaction_id
258
- end
268
+ rsp_headers['X-Moesif-Transaction-Id'] = transaction_id unless transaction_id.nil?
259
269
 
260
270
  # Add Transaction Id to the Repsonse Header sent to the client
261
- if !transaction_id.nil?
262
- headers["X-Moesif-Transaction-Id"] = transaction_id
263
- end
271
+ headers['X-Moesif-Transaction-Id'] = transaction_id unless transaction_id.nil?
264
272
 
265
273
  event_req.ip_address = get_client_address(req.env)
266
274
  event_req.headers = req_headers
267
275
  event_req.body = req_body
268
276
  event_req.transfer_encoding = req_body_transfer_encoding
269
277
 
270
- event_rsp = MoesifApi::EventResponseModel.new()
278
+ event_rsp = MoesifApi::EventResponseModel.new
271
279
  event_rsp.time = end_time
272
280
  event_rsp.status = status
273
281
  event_rsp.headers = rsp_headers
274
282
  event_rsp.body = rsp_body
275
283
  event_rsp.transfer_encoding = rsp_body_transfer_encoding
276
284
 
277
- event_model = MoesifApi::EventModel.new()
278
- event_model.request = event_req
279
- event_model.response = event_rsp
280
- event_model.direction = "Incoming"
285
+ _event_model = MoesifApi::EventModel.new
286
+ _event_model.request = event_req
287
+ _event_model.response = event_rsp
288
+ _event_model.direction = 'Incoming'
281
289
 
282
290
  if @identify_user
283
- @moesif_helpers.log_debug "calling identify user proc"
284
- event_model.user_id = @identify_user.call(env, headers, body)
291
+ @moesif_helpers.log_debug 'calling identify user proc'
292
+ _event_model.user_id = @identify_user.call(env, headers, body)
285
293
  end
286
294
 
287
295
  if @identify_company
288
- @moesif_helpers.log_debug "calling identify company proc"
289
- event_model.company_id = @identify_company.call(env, headers, body)
296
+ @moesif_helpers.log_debug 'calling identify company proc'
297
+ _event_model.company_id = @identify_company.call(env, headers, body)
290
298
  end
291
299
 
292
300
  if @get_metadata
293
- @moesif_helpers.log_debug "calling get_metadata proc"
294
- event_model.metadata = @get_metadata.call(env, headers, body)
301
+ @moesif_helpers.log_debug 'calling get_metadata proc'
302
+ _event_model.metadata = @get_metadata.call(env, headers, body)
295
303
  end
296
304
 
297
305
  if @identify_session
298
- @moesif_helpers.log_debug "calling identify session proc"
299
- event_model.session_token = @identify_session.call(env, headers, body)
306
+ @moesif_helpers.log_debug 'calling identify session proc'
307
+ _event_model.session_token = @identify_session.call(env, headers, body)
300
308
  end
301
309
  if @mask_data
302
- @moesif_helpers.log_debug "calling mask_data proc"
303
- event_model = @mask_data.call(event_model)
310
+ @moesif_helpers.log_debug 'calling mask_data proc'
311
+ _event_model = @mask_data.call(_event_model)
304
312
  end
305
313
 
306
- @moesif_helpers.log_debug "sending data to moesif"
307
- @moesif_helpers.log_debug event_model.to_json
314
+ return _event_model
315
+ rescue StandardError => e
316
+ @moesif_helpers.log_debug 'Error making event model'
317
+ @moesif_helpers.log_debug e.to_s
318
+ end
319
+
320
+ process_send = lambda do |_event_model|
321
+ @moesif_helpers.log_debug 'sending data to moesif'
322
+ @moesif_helpers.log_debug _event_model.to_json
323
+
308
324
  # Perform the API call through the SDK function
309
325
  begin
310
- random_percentage = Random.rand(0.00..100.00)
326
+ random_percentage = Random.rand(0.00..100.00)
311
327
 
312
328
  begin
313
- sampling_percentage = @app_config.get_sampling_percentage(event_model, @config, event_model.user_id, event_model.company_id)
329
+ sampling_percentage = @app_config.get_sampling_percentage(_event_model, @config, _event_model.user_id,
330
+ _event_model.company_id)
314
331
  @moesif_helpers.log_debug "Using sample rate #{sampling_percentage}"
315
- rescue => exception
332
+ rescue StandardError => e
316
333
  @moesif_helpers.log_debug 'Error while getting sampling percentage, assuming default behavior'
317
- @moesif_helpers.log_debug exception.to_s
334
+ @moesif_helpers.log_debug e.to_s
318
335
  sampling_percentage = 100
319
336
  end
320
337
 
321
338
  if sampling_percentage > random_percentage
322
- event_model.weight = @app_config.calculate_weight(sampling_percentage)
339
+ _event_model.weight = @app_config.calculate_weight(sampling_percentage)
323
340
  # Add Event to the queue
324
- @events_queue << event_model
325
- @moesif_helpers.log_debug("Event added to the queue ")
326
- if Time.now.utc > (@last_worker_run + 60)
327
- start_worker()
341
+ if @events_queue.size >= @event_queue_size
342
+ @moesif_helpers.log_debug("Skipped Event due to events_queue size [#{@events_queue.size}] is over max #{@event_queue_size} ")
343
+ else
344
+ @events_queue << _event_model
345
+ @moesif_helpers.log_debug('Event added to the queue ')
328
346
  end
329
347
 
330
- if !@event_response_config_etag.nil? && !@config_etag.nil? && @config_etag != @event_response_config_etag && Time.now.utc > (@last_config_download_time + 300)
331
- begin
332
- new_config = @app_config.get_config(@api_controller)
333
- if !new_config.nil?
334
- @config, @config_etag, @last_config_download_time = @app_config.parse_configuration(new_config)
335
- end
336
-
337
- rescue => exception
338
- @moesif_helpers.log_debug 'Error while updating the application configuration'
339
- @moesif_helpers.log_debug exception.to_s
340
- end
341
- end
348
+ start_worker if Time.now.utc > (@last_worker_run + 60)
342
349
  else
343
- @moesif_helpers.log_debug("Skipped Event due to sampling percentage: " + sampling_percentage.to_s + " and random percentage: " + random_percentage.to_s)
350
+ @moesif_helpers.log_debug('Skipped Event due to sampling percentage: ' + sampling_percentage.to_s + ' and random percentage: ' + random_percentage.to_s)
344
351
  end
345
- rescue => exception
346
- @moesif_helpers.log_debug "Error adding event to the queue "
347
- @moesif_helpers.log_debug exception.to_s
352
+ rescue StandardError => e
353
+ @moesif_helpers.log_debug 'Error adding event to the queue '
354
+ @moesif_helpers.log_debug e.to_s
348
355
  end
349
-
350
356
  end
351
357
 
352
358
  should_skip = false
353
359
 
354
- if @skip
355
- if @skip.call(env, headers, body)
356
- should_skip = true;
360
+ should_skip = true if @skip && @skip.call(env, headers, body)
361
+
362
+ should_govern = @governance_manager.has_rules
363
+
364
+ event_model = make_event_model.call if !should_skip || should_govern
365
+
366
+ if should_govern
367
+ # now we can do govern based on
368
+ # override_response = govern(env, event_model)
369
+ # return override_response if override_response
370
+ new_response = @governance_manager.govern_request(@config, env, event_model, status, headers, body)
371
+
372
+ # update the event model
373
+ if new_response
374
+ @moesif_helpers.log_debug 'new response back from govern' + new_response.to_json
375
+
376
+ # replace headers since it might be non blocking rules that adds headers
377
+ headers = new_response.fetch(:headers, headers)
378
+
379
+ # replace in event_model
380
+ event_model.response.headers = new_response.fetch(:headers, headers).dup
381
+
382
+ blocked_by = new_response.fetch(:block_rule_id, nil)
383
+
384
+ unless blocked_by.nil?
385
+ # we only replace body and status if it is blocked.
386
+ body = @moesif_helpers.format_replacement_body(new_response.fetch(:body, nil), body)
387
+ status = new_response.fetch(:status, status)
388
+
389
+ # replace the event model.
390
+ event_model.blocked_by = blocked_by
391
+ event_model.response.status = new_response.fetch(:status, status)
392
+ replaced_body = new_response.fetch(:body, event_model.response.body)
393
+ event_model.response.body = replaced_body
394
+ # replaced body is always json should not be transfer encoding needed.
395
+ event_model.response.transfer_encoding = 'json'
396
+ end
357
397
  end
358
398
  end
359
399
 
360
400
  if !should_skip
361
401
  begin
362
- process_send.call
363
- rescue => exception
402
+ process_send.call(event_model)
403
+ rescue StandardError => e
364
404
  @moesif_helpers.log_debug 'Error while logging event - '
365
- @moesif_helpers.log_debug exception.to_s
366
- @moesif_helpers.log_debug exception.backtrace
405
+ @moesif_helpers.log_debug e.to_s
406
+ @moesif_helpers.log_debug e.backtrace
367
407
  end
368
408
  else
369
- @moesif_helpers.log_debug "Skipped Event using should_skip configuration option."
409
+ @moesif_helpers.log_debug 'Skipped Event using should_skip configuration option.'
370
410
  end
371
411
 
372
412
  [status, headers, body]
@@ -374,12 +414,10 @@ module MoesifRack
374
414
 
375
415
  def get_response_body(response)
376
416
  body = response.respond_to?(:body) ? response.body : response
377
- if (body.instance_of?(Hash) || body.instance_of?(Array))
378
- return body
379
- end
380
- body = body.inject("") { |i, a| i << a } if (body.respond_to?(:each) && body.respond_to?(:inject))
417
+ return body if body.instance_of?(Hash) || body.instance_of?(Array)
418
+
419
+ body = body.inject('') { |i, a| i << a } if body.respond_to?(:each) && body.respond_to?(:inject)
381
420
  body.to_s
382
421
  end
383
-
384
422
  end
385
423
  end