moesif_rack 1.4.19 → 2.0.1

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.
@@ -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