moesif_rack 1.4.19 → 2.0.1

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