securenative 0.1.22 → 0.1.23

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +62 -64
  3. data/lib/config/configuration_builder.rb +4 -3
  4. data/lib/config/configuration_manager.rb +2 -1
  5. data/lib/config/securenative_options.rb +4 -3
  6. data/lib/utils/request_utils.rb +20 -5
  7. data/out/production/securenative-ruby/api_manager.rb +13 -5
  8. data/out/production/securenative-ruby/config/configuration_builder.rb +6 -9
  9. data/out/production/securenative-ruby/config/configuration_manager.rb +24 -23
  10. data/out/production/securenative-ruby/config/securenative_options.rb +8 -5
  11. data/out/production/securenative-ruby/context/hanami_context.rb +42 -0
  12. data/out/production/securenative-ruby/context/rails_context.rb +44 -0
  13. data/out/production/securenative-ruby/context/securenative_context.rb +35 -8
  14. data/out/production/securenative-ruby/context/sinatra_context.rb +42 -0
  15. data/out/production/securenative-ruby/event_manager.rb +15 -14
  16. data/out/production/securenative-ruby/http/{http_response.rb → secure_native_http_response.rb} +1 -1
  17. data/out/production/securenative-ruby/http/securenative_http_client.rb +23 -5
  18. data/out/production/securenative-ruby/models/event_options.rb +23 -1
  19. data/out/production/securenative-ruby/models/request_context.rb +2 -2
  20. data/out/production/securenative-ruby/models/sdk_event.rb +22 -6
  21. data/out/production/securenative-ruby/models/user_traits.rb +1 -1
  22. data/out/production/securenative-ruby/models/verify_result.rb +5 -1
  23. data/out/production/securenative-ruby/securenative.rb +2 -10
  24. data/out/production/securenative-ruby/utils/date_utils.rb +1 -1
  25. data/out/production/securenative-ruby/utils/encryption_utils.rb +38 -24
  26. data/out/production/securenative-ruby/utils/request_utils.rb +53 -7
  27. data/out/production/securenative-ruby/utils/secure_native_logger.rb +6 -6
  28. data/out/production/securenative-ruby/utils/version_utils.rb +5 -6
  29. data/out/test/securenative-ruby/spec_api_manager.rb +37 -31
  30. data/out/test/securenative-ruby/spec_context_builder.rb +52 -34
  31. data/out/test/securenative-ruby/spec_encryption_utils.rb +13 -13
  32. data/out/test/securenative-ruby/spec_event_manager.rb +49 -15
  33. data/out/test/securenative-ruby/spec_helper.rb +8 -0
  34. data/out/test/securenative-ruby/spec_request_utils.rb +25 -0
  35. data/out/test/securenative-ruby/spec_sdk_event.rb +24 -0
  36. data/out/test/securenative-ruby/spec_securenative.rb +35 -39
  37. data/out/test/securenative-ruby/spec_securenative_http_client.rb +13 -5
  38. data/out/test/securenative-ruby/spec_signature_utils.rb +1 -1
  39. data/out/test/securenative-ruby/spec_version_util.rb +10 -0
  40. data/securenative.gemspec +1 -1
  41. metadata +9 -4
  42. data/out/production/securenative-ruby/event_options.rb +0 -32
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'enums/failover_strategy'
4
+
3
5
  class SecureNativeOptions
4
- attr_reader :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
5
- attr_writer :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
6
+ attr_reader :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy, :proxy_headers
7
+ attr_writer :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy, :proxy_headers
6
8
 
7
- def initialize(api_key = nil, api_url = "https://api.securenative.com/collector/api/v1", interval = 1000,
8
- max_events = 1000, timeout = 1500, auto_send = true, disable = false, log_level = "FATAL",
9
- fail_over_strategy = FailOverStrategy::FAIL_OPEN)
9
+ def initialize(api_key: nil, api_url: "https://api.securenative.com/collector/api/v1", interval: 1000,
10
+ max_events: 1000, timeout: 1500, auto_send: true, disable: false, log_level: "FATAL",
11
+ fail_over_strategy: FailOverStrategy::FAIL_OPEN, proxy_headers: [])
10
12
  @api_key = api_key
11
13
  @api_url = api_url
12
14
  @interval = interval
@@ -16,5 +18,6 @@ class SecureNativeOptions
16
18
  @disable = disable
17
19
  @log_level = log_level
18
20
  @fail_over_strategy = fail_over_strategy
21
+ @proxy_headers = proxy_headers
19
22
  end
20
23
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HanamiContext
4
+ SECURENATIVE_COOKIE = '_sn'
5
+
6
+ def self.get_client_token(request)
7
+ begin
8
+ request.env[SECURENATIVE_COOKIE]
9
+ rescue StandardError
10
+ begin
11
+ request.cookies[SECURENATIVE_COOKIE]
12
+ rescue StandardError
13
+ nil
14
+ end
15
+ end
16
+ end
17
+
18
+ def self.get_url(request)
19
+ begin
20
+ request.env['REQUEST_PATH']
21
+ rescue StandardError
22
+ nil
23
+ end
24
+ end
25
+
26
+ def self.get_method(request)
27
+ begin
28
+ request.request_method
29
+ rescue StandardError
30
+ nil
31
+ end
32
+ end
33
+
34
+ def self.get_headers(request)
35
+ begin
36
+ # Note: At the moment we're filtering out everything but user-agent since ruby's payload is way too big
37
+ { 'user-agent' => request.env['HTTP_USER_AGENT'] }
38
+ rescue StandardError
39
+ nil
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RailsContext
4
+ SECURENATIVE_COOKIE = '_sn'
5
+
6
+ def self.get_client_token(request)
7
+ begin
8
+ request.cookies[SECURENATIVE_COOKIE]
9
+ rescue StandardError
10
+ nil
11
+ end
12
+ end
13
+
14
+ def self.get_url(request)
15
+ begin
16
+ # Rails >= 3.x
17
+ request.fullpath
18
+ rescue StandardError
19
+ begin
20
+ # Rails < 3.x & Sinatra
21
+ request.url if url.nil?
22
+ rescue StandardError
23
+ nil
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.get_method(request)
29
+ begin
30
+ request.method
31
+ rescue StandardError
32
+ nil
33
+ end
34
+ end
35
+
36
+ def self.get_headers(request)
37
+ begin
38
+ # Note: At the moment we're filtering out everything but user-agent since ruby's payload is way too big
39
+ { 'user-agent' => request.env['HTTP_USER_AGENT'] }
40
+ rescue StandardError
41
+ nil
42
+ end
43
+ end
44
+ end
@@ -1,10 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'utils/request_utils'
4
+ require 'utils/utils'
5
+ require 'context/rails_context'
6
+ require 'context/hanami_context'
7
+ require 'context/sinatra_context'
8
+
3
9
  class SecureNativeContext
4
10
  attr_reader :client_token, :ip, :remote_ip, :headers, :url, :http_method, :body
5
11
  attr_writer :client_token, :ip, :remote_ip, :headers, :url, :http_method, :body
6
12
 
7
- def initialize(client_token = nil, ip = nil, remote_ip = nil, headers = nil, url = nil, http_method = nil, body = nil)
13
+ SECURENATIVE_COOKIE = '_sn'
14
+
15
+ def initialize(client_token: '', ip: '', remote_ip: '', headers: nil, url: '', http_method: '', body: '')
8
16
  @client_token = client_token
9
17
  @ip = ip
10
18
  @remote_ip = remote_ip
@@ -19,22 +27,41 @@ class SecureNativeContext
19
27
  end
20
28
 
21
29
  def self.from_http_request(request)
30
+ client_token = RailsContext.get_client_token(request)
31
+ client_token = SinatraContext.get_client_token(request) if client_token.nil?
32
+ client_token = HanamiContext.get_client_token(request) if client_token.nil?
33
+
22
34
  begin
23
- client_token = request.cookies[RequestUtils.SECURENATIVE_COOKIE]
35
+ headers = RailsContext.get_headers(request)
36
+ headers = SinatraContext.get_headers(request) if headers.nil?
37
+ headers = HanamiContext.get_headers(request) if headers.nil?
38
+
39
+ # Standard Ruby request
40
+ headers = request.header.to_hash if headers.nil?
24
41
  rescue StandardError
25
- client_token = nil
42
+ headers = []
26
43
  end
27
44
 
45
+ url = RailsContext.get_url(request)
46
+ url = SinatraContext.get_url(request) if url.nil?
47
+ url = HanamiContext.get_url(request) if url.nil?
48
+ url = '' if url.nil?
49
+
50
+ method = RailsContext.get_method(request)
51
+ method = SinatraContext.get_method(request) if method.nil?
52
+ method = HanamiContext.get_method(request) if method.nil?
53
+ method = '' if method.nil?
54
+
28
55
  begin
29
- headers = request.headers
56
+ body = request.body.to_s
30
57
  rescue StandardError
31
- headers = nil
58
+ body = ''
32
59
  end
33
60
 
34
61
  client_token = RequestUtils.get_secure_header_from_request(headers) if Utils.null_or_empty?(client_token)
35
62
 
36
- SecureNativeContext.new(url: request.url, method: request.http_method, header: headers, client_token: client_token,
37
- client_ip: RequestUtils.get_client_ip_from_request(request),
38
- remote_ip: RequestUtils.get_remote_ip_from_request(request), body: nil)
63
+ SecureNativeContext.new(client_token: client_token, ip: RequestUtils.get_client_ip_from_request(request),
64
+ remote_ip: RequestUtils.get_remote_ip_from_request(request),
65
+ headers: headers, url: url, http_method: method || '', body: body)
39
66
  end
40
67
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SinatraContext
4
+ SECURENATIVE_COOKIE = '_sn'
5
+
6
+ def self.get_client_token(request)
7
+ begin
8
+ request.env[SECURENATIVE_COOKIE]
9
+ rescue StandardError
10
+ begin
11
+ request.cookies[SECURENATIVE_COOKIE]
12
+ rescue StandardError
13
+ nil
14
+ end
15
+ end
16
+ end
17
+
18
+ def self.get_url(request)
19
+ begin
20
+ request.env['REQUEST_URI']
21
+ rescue StandardError
22
+ nil
23
+ end
24
+ end
25
+
26
+ def self.get_method(request)
27
+ begin
28
+ request.env['REQUEST_METHOD']
29
+ rescue StandardError
30
+ nil
31
+ end
32
+ end
33
+
34
+ def self.get_headers(request)
35
+ begin
36
+ # Note: At the moment we're filtering out everything but user-agent since ruby's payload is way too big
37
+ { 'user-agent' => request.env['HTTP_USER_AGENT'] }
38
+ rescue StandardError
39
+ nil
40
+ end
41
+ end
42
+ end
@@ -4,6 +4,7 @@ require 'utils/secure_native_logger'
4
4
  require 'config/securenative_options'
5
5
  require 'http/securenative_http_client'
6
6
  require 'errors/securenative_sdk_error'
7
+ require 'errors/securenative_http_error'
7
8
 
8
9
  class QueueItem
9
10
  attr_reader :url, :body, :retry_sending
@@ -36,7 +37,7 @@ class EventManager
36
37
  @attempt = 0
37
38
  @coefficients = [1, 1, 2, 3, 5, 8, 13]
38
39
 
39
- @thread = Thread.new {run}
40
+ @thread = Thread.new { run }
40
41
  end
41
42
 
42
43
  def send_async(event, resource_path)
@@ -45,7 +46,7 @@ class EventManager
45
46
  return
46
47
  end
47
48
 
48
- item = QueueItem(resource_path, JSON.parse(EventManager.serialize(event)), false)
49
+ item = QueueItem.new(resource_path, EventManager.serialize(event).to_json, false)
49
50
  @queue.append(item)
50
51
  end
51
52
 
@@ -62,11 +63,11 @@ class EventManager
62
63
  end
63
64
 
64
65
  SecureNativeLogger.debug("Attempting to send event #{event}")
65
- res = @http_client.post(resource_path, JSON.parse(EventManager.serialize(event)))
66
+ res = @http_client.post(resource_path, EventManager.serialize(event).to_json)
66
67
 
67
- if res.status_code != 200
68
- SecureNativeLogger.info('SecureNative failed to call endpoint {} with event {}. adding back to queue'.format(resource_path, event))
69
- item = QueueItem(resource_path, JSON.parse(EventManager.serialize(event)), retry_sending)
68
+ if res.nil? || res.code != '200'
69
+ SecureNativeLogger.info("SecureNative failed to call endpoint #{resource_path} with event #{event}. adding back to queue")
70
+ item = QueueItem.new(resource_path, EventManager.serialize(event).to_json, retry_sending)
70
71
  @queue.append(item)
71
72
  end
72
73
 
@@ -81,20 +82,20 @@ class EventManager
81
82
  @queue.each do |item|
82
83
  begin
83
84
  res = @http_client.post(item.url, item.body)
84
- if res.status_code == 401
85
+ if res.code == '401'
85
86
  item.retry_sending = false
86
- elsif res.status_code != 200
87
+ elsif res.code != '200'
87
88
  raise SecureNativeHttpError, res.status_code
88
89
  end
89
- SecureNativeLogger.debug('Event successfully sent; {}'.format(item.body))
90
+ SecureNativeLogger.debug("Event successfully sent; #{item.body}")
90
91
  return res
91
92
  rescue StandardError => e
92
- SecureNativeLogger.error('Failed to send event; {}'.format(e))
93
+ SecureNativeLogger.error("Failed to send event; #{e}")
93
94
  if item.retry_sending
94
95
  @attempt = 0 if @coefficients.length == @attempt + 1
95
96
 
96
97
  back_off = @coefficients[@attempt] * @options.interval
97
- SecureNativeLogger.debug('Automatic back-off of {}'.format(back_off))
98
+ SecureNativeLogger.debug("Automatic back-off of #{back_off}")
98
99
  @send_enabled = false
99
100
  sleep back_off
100
101
  @send_enabled = true
@@ -120,10 +121,10 @@ class EventManager
120
121
  SecureNativeLogger.debug('Attempting to stop automatic event persistence')
121
122
  begin
122
123
  flush
123
- @thread&.stop
124
+ @thread&.stop?
124
125
  SecureNativeLogger.debug('Stopped event persistence')
125
126
  rescue StandardError => e
126
- SecureNativeLogger.error('Could not stop event scheduler; {}'.format(e))
127
+ SecureNativeLogger.error("Could not stop event scheduler; #{e}")
127
128
  end
128
129
  end
129
130
  end
@@ -145,7 +146,7 @@ class EventManager
145
146
  fp: obj.request.fp,
146
147
  ip: obj.request.ip,
147
148
  remoteIp: obj.request.remote_ip,
148
- http_method: obj.request.http_method,
149
+ method: obj.request.http_method || '',
149
150
  url: obj.request.url,
150
151
  headers: obj.request.headers
151
152
  },
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class HttpResponse
3
+ class SecureNativeHttpResponse
4
4
  attr_reader :ok, :status_code, :body
5
5
  attr_writer :ok, :status_code, :body
6
6
 
@@ -1,18 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'httpclient'
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+ require 'utils/version_utils'
7
+ require 'utils/secure_native_logger'
4
8
 
5
9
  class SecureNativeHttpClient
6
10
  AUTHORIZATION_HEADER = 'Authorization'
7
11
  VERSION_HEADER = 'SN-Version'
8
12
  USER_AGENT_HEADER = 'User-Agent'
9
- USER_AGENT_HEADER_VALUE = 'SecureNative-python'
13
+ USER_AGENT_HEADER_VALUE = 'SecureNative-ruby'
10
14
  CONTENT_TYPE_HEADER = 'Content-Type'
11
15
  CONTENT_TYPE_HEADER_VALUE = 'application/json'
12
16
 
13
17
  def initialize(securenative_options)
14
18
  @options = securenative_options
15
- @client = HTTPClient.new
16
19
  end
17
20
 
18
21
  def _headers
@@ -25,8 +28,23 @@ class SecureNativeHttpClient
25
28
  end
26
29
 
27
30
  def post(path, body)
28
- url = "#{@options.api_url}/#{path}"
31
+ uri = URI.parse("#{@options.api_url}/#{path}")
29
32
  headers = _headers
30
- @client.post(url, body, headers)
33
+
34
+ client = Net::HTTP.new(uri.host, uri.port)
35
+ client.use_ssl = true
36
+ client.verify_mode = OpenSSL::SSL::VERIFY_NONE
37
+
38
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
39
+ request.body = body
40
+
41
+ res = nil
42
+ begin
43
+ res = client.request(request)
44
+ rescue StandardError => e
45
+ SecureNativeLogger.error("Failed to send request; #{e}")
46
+ return res
47
+ end
48
+ res
31
49
  end
32
50
  end
@@ -1,10 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'models/event_options'
4
+ require 'models/user_traits'
5
+ require 'errors/securenative_invalid_options_error'
6
+
3
7
  class EventOptions
4
8
  attr_reader :event, :user_id, :user_traits, :context, :properties, :timestamp
5
9
  attr_writer :event, :user_id, :user_traits, :context, :properties, :timestamp
6
10
 
7
- def initialize(event, user_id = nil, user_traits = nil, context = nil, properties = nil, timestamp = nil)
11
+ MAX_PROPERTIES_SIZE = 10
12
+
13
+ def initialize(event: nil, user_id: nil, user_traits: nil, user_name: nil, email: nil, phone: nil, created_at: nil, context: nil, properties: nil, timestamp: nil)
14
+ if !properties.nil? && properties.length > MAX_PROPERTIES_SIZE
15
+ raise SecureNativeInvalidOptionsError, "You can have only up to #{MAX_PROPERTIES_SIZE} custom properties"
16
+ end
17
+
18
+ if user_traits.nil?
19
+ if user_name && email && phone && created_at
20
+ user_traits = UserTraits(user_name, email, phone, created_at)
21
+ elsif user_name && email && phone
22
+ user_traits = UserTraits(user_name, email, phone)
23
+ elsif user_name && email
24
+ user_traits = UserTraits(user_name, email)
25
+ else
26
+ user_traits = UserTraits.new
27
+ end
28
+ end
29
+
8
30
  @event = event
9
31
  @user_id = user_id
10
32
  @user_traits = user_traits
@@ -4,7 +4,7 @@ class RequestContext
4
4
  attr_reader :cid, :vid, :fp, :ip, :remote_ip, :headers, :url, :http_method
5
5
  attr_writer :cid, :vid, :fp, :ip, :remote_ip, :headers, :url, :http_method
6
6
 
7
- def initialize(cid = nil, vid = nil, fp = nil, ip = nil, remote_ip = nil, headers = nil, url = nil, method = nil)
7
+ def initialize(cid: nil, vid: nil, fp: nil, ip: nil, remote_ip: nil, headers: nil, url: nil, http_method: nil)
8
8
  @cid = cid
9
9
  @vid = vid
10
10
  @fp = fp
@@ -12,7 +12,7 @@ class RequestContext
12
12
  @remote_ip = remote_ip
13
13
  @headers = headers
14
14
  @url = url
15
- @method = method
15
+ @method = http_method
16
16
  end
17
17
  end
18
18
 
@@ -1,14 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'context/securenative_context'
4
+ require 'errors/securenative_invalid_options_error'
5
+ require 'utils/encryption_utils'
6
+ require 'utils/date_utils'
7
+ require 'models/request_context'
8
+ require 'securerandom'
9
+
3
10
  class SDKEvent
4
11
  attr_reader :context, :rid, :event_type, :user_id, :user_traits, :request, :timestamp, :properties
5
12
  attr_writer :context, :rid, :event_type, :user_id, :user_traits, :request, :timestamp, :properties
6
13
 
7
14
  def initialize(event_options, securenative_options)
15
+ if event_options.user_id.nil? || event_options.user_id.length <= 0 || event_options.user_id == ''
16
+ raise SecureNativeInvalidOptionsError.new, 'Invalid event structure; User Id is missing'
17
+ end
18
+
19
+ if event_options.event.nil? || event_options.event.length <= 0 || event_options.event == ''
20
+ raise SecureNativeInvalidOptionsError.new, 'Invalid event structure; Event Type is missing'
21
+ end
22
+
8
23
  @context = if !event_options.context.nil?
9
24
  event_options.context
10
25
  else
11
- ContextBuilder.default_context_builder
26
+ SecureNativeContext.default_context_builder
12
27
  end
13
28
 
14
29
  client_token = EncryptionUtils.decrypt(@context.client_token, securenative_options.api_key)
@@ -17,10 +32,11 @@ class SDKEvent
17
32
  @event_type = event_options.event
18
33
  @user_id = event_options.user_id
19
34
  @user_traits = event_options.user_traits
20
- @request = RequestContext(cid = client_token ? client_token.cid : '', vid = client_token ? client_token.vid : '',
21
- fp = client_token ? client_token.fp : '', ip = @context.ip,
22
- remote_ip = @context.remote_ip, method = @context.http_method, url = @context.url,
23
- headers = @context.headers)
35
+ @request = RequestContext.new(cid: client_token ? client_token.cid : '', vid: client_token ? client_token.vid : '',
36
+ fp: client_token ? client_token.fp : '', ip: @context.ip,
37
+ remote_ip: @context.remote_ip, headers: @context.headers,
38
+ url: @context.url, http_method: @context.http_method)
39
+
24
40
 
25
41
  @timestamp = DateUtils.to_timestamp(event_options.timestamp)
26
42
  @properties = event_options.properties
@@ -28,6 +44,6 @@ class SDKEvent
28
44
 
29
45
  def to_s
30
46
  "context: #{@context}, rid: #{@rid}, event_type: #{@event_type}, user_id: #{@user_id},
31
- user_traits: #{@user_traits}, request: #{@request}, timestamp: #{@timestamp}, properties: #{@properties}"
47
+ user_traits: #{@user_traits}, request: #{@request}, timestamp: #{@timestamp}, properties: #{@properties}"
32
48
  end
33
49
  end