securenative 0.1.16 → 0.1.22

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +7 -15
  3. data/.github/workflows/publish.yml +1 -1
  4. data/.github/workflows/test.yml +5 -2
  5. data/.gitignore +3 -1
  6. data/Gemfile +6 -4
  7. data/Gemfile.lock +234 -23
  8. data/README.md +38 -33
  9. data/lib/api_manager.rb +39 -0
  10. data/lib/config/configuration_builder.rb +26 -0
  11. data/lib/config/configuration_manager.rb +55 -0
  12. data/lib/config/securenative_options.rb +22 -0
  13. data/lib/context/hanami_context.rb +42 -0
  14. data/lib/context/rails_context.rb +44 -0
  15. data/lib/context/securenative_context.rb +67 -0
  16. data/lib/context/sinatra_context.rb +42 -0
  17. data/lib/enums/api_route.rb +6 -0
  18. data/lib/enums/event_types.rb +23 -0
  19. data/lib/enums/failover_strategy.rb +6 -0
  20. data/lib/enums/risk_level.rb +7 -0
  21. data/lib/errors/securenative_config_error.rb +4 -0
  22. data/lib/errors/securenative_http_error.rb +4 -0
  23. data/lib/errors/securenative_invalid_options_error.rb +4 -0
  24. data/lib/errors/securenative_invalid_uri_error.rb +4 -0
  25. data/lib/errors/securenative_parse_error.rb +4 -0
  26. data/lib/errors/securenative_sdk_Illegal_state_error.rb +4 -0
  27. data/lib/errors/securenative_sdk_error.rb +4 -0
  28. data/lib/event_manager.rb +157 -0
  29. data/lib/http/secure_native_http_response.rb +12 -0
  30. data/lib/http/securenative_http_client.rb +50 -0
  31. data/lib/{securenative/models → models}/client_token.rb +2 -0
  32. data/lib/{securenative/models → models}/device.rb +3 -1
  33. data/lib/models/event_options.rb +37 -0
  34. data/lib/models/request_context.rb +18 -0
  35. data/lib/models/request_options.rb +12 -0
  36. data/lib/models/sdk_event.rb +49 -0
  37. data/lib/models/user_traits.rb +13 -0
  38. data/lib/models/verify_result.rb +16 -0
  39. data/lib/securenative.rb +83 -0
  40. data/lib/utils/date_utils.rb +9 -0
  41. data/lib/utils/encryption_utils.rb +49 -0
  42. data/lib/{securenative/utils → utils}/ip_utils.rb +7 -6
  43. data/lib/utils/request_utils.rb +54 -0
  44. data/lib/{securenative/logger.rb → utils/secure_native_logger.rb} +5 -3
  45. data/lib/{securenative/utils → utils}/signature_utils.rb +4 -2
  46. data/lib/utils/utils.rb +9 -0
  47. data/lib/utils/version_utils.rb +11 -0
  48. data/{lib/securenative → out/production/securenative-ruby}/api_manager.rb +6 -5
  49. data/{lib/securenative → out/production/securenative-ruby}/config/configuration_builder.rb +7 -3
  50. data/{lib/securenative → out/production/securenative-ruby}/config/configuration_manager.rb +5 -3
  51. data/{lib/securenative → out/production/securenative-ruby}/config/securenative_options.rb +3 -1
  52. data/out/production/securenative-ruby/context/securenative_context.rb +40 -0
  53. data/out/production/securenative-ruby/enums/api_route.rb +6 -0
  54. data/out/production/securenative-ruby/enums/event_types.rb +23 -0
  55. data/out/production/securenative-ruby/enums/failover_strategy.rb +6 -0
  56. data/out/production/securenative-ruby/enums/risk_level.rb +7 -0
  57. data/out/production/securenative-ruby/errors/securenative_config_error.rb +4 -0
  58. data/out/production/securenative-ruby/errors/securenative_http_error.rb +4 -0
  59. data/out/production/securenative-ruby/errors/securenative_invalid_options_error.rb +4 -0
  60. data/out/production/securenative-ruby/errors/securenative_invalid_uri_error.rb +4 -0
  61. data/out/production/securenative-ruby/errors/securenative_parse_error.rb +4 -0
  62. data/out/production/securenative-ruby/errors/securenative_sdk_Illegal_state_error.rb +4 -0
  63. data/out/production/securenative-ruby/errors/securenative_sdk_error.rb +4 -0
  64. data/out/production/securenative-ruby/event_manager.rb +156 -0
  65. data/out/production/securenative-ruby/event_options.rb +32 -0
  66. data/{lib/securenative → out/production/securenative-ruby}/http/http_response.rb +3 -1
  67. data/out/production/securenative-ruby/http/securenative_http_client.rb +32 -0
  68. data/out/production/securenative-ruby/models/client_token.rb +12 -0
  69. data/out/production/securenative-ruby/models/device.rb +10 -0
  70. data/{lib/securenative → out/production/securenative-ruby}/models/event_options.rb +3 -1
  71. data/{lib/securenative → out/production/securenative-ruby}/models/request_context.rb +5 -2
  72. data/out/production/securenative-ruby/models/request_options.rb +12 -0
  73. data/{lib/securenative → out/production/securenative-ruby}/models/sdk_event.rb +11 -3
  74. data/out/production/securenative-ruby/models/user_traits.rb +13 -0
  75. data/{lib/securenative → out/production/securenative-ruby}/models/verify_result.rb +3 -1
  76. data/{lib/securenative → out/production/securenative-ruby}/securenative.rb +24 -15
  77. data/out/production/securenative-ruby/utils/date_utils.rb +9 -0
  78. data/{lib/securenative → out/production/securenative-ruby}/utils/encryption_utils.rb +1 -4
  79. data/out/production/securenative-ruby/utils/ip_utils.rb +23 -0
  80. data/{lib/securenative → out/production/securenative-ruby}/utils/request_utils.rb +5 -3
  81. data/out/production/securenative-ruby/utils/secure_native_logger.rb +44 -0
  82. data/out/production/securenative-ruby/utils/signature_utils.rb +16 -0
  83. data/out/production/securenative-ruby/utils/utils.rb +9 -0
  84. data/{lib/securenative → out/production/securenative-ruby}/utils/version_utils.rb +3 -1
  85. data/out/test/securenative-ruby/spec_api_manager.rb +81 -0
  86. data/out/test/securenative-ruby/spec_context_builder.rb +69 -0
  87. data/out/test/securenative-ruby/spec_date_utils.rb +13 -0
  88. data/out/test/securenative-ruby/spec_encryption_utils.rb +26 -0
  89. data/out/test/securenative-ruby/spec_event_manager.rb +59 -0
  90. data/out/test/securenative-ruby/spec_helper.rb +20 -0
  91. data/out/test/securenative-ruby/spec_ip_utils.rb +41 -0
  92. data/out/test/securenative-ruby/spec_securenative.rb +65 -0
  93. data/out/test/securenative-ruby/spec_securenative_http_client.rb +23 -0
  94. data/out/test/securenative-ruby/spec_signature_utils.rb +18 -0
  95. data/securenative.gemspec +2 -2
  96. metadata +88 -43
  97. data/.travis.yml +0 -6
  98. data/VERSION +0 -1
  99. data/lib/securenative/context/context_builder.rb +0 -59
  100. data/lib/securenative/context/securenative_context.rb +0 -14
  101. data/lib/securenative/enums/api_route.rb +0 -4
  102. data/lib/securenative/enums/event_types.rb +0 -21
  103. data/lib/securenative/enums/failover_strategy.rb +0 -4
  104. data/lib/securenative/enums/risk_level.rb +0 -5
  105. data/lib/securenative/event_manager.rb +0 -149
  106. data/lib/securenative/event_options_builder.rb +0 -21
  107. data/lib/securenative/exceptions/securenative_config_exception.rb +0 -2
  108. data/lib/securenative/exceptions/securenative_http_exception.rb +0 -2
  109. data/lib/securenative/exceptions/securenative_invalid_options_exception.rb +0 -2
  110. data/lib/securenative/exceptions/securenative_invalid_uri_exception.rb +0 -2
  111. data/lib/securenative/exceptions/securenative_parse_exception.rb +0 -2
  112. data/lib/securenative/exceptions/securenative_sdk_Illegal_state_exception.rb +0 -2
  113. data/lib/securenative/exceptions/securenative_sdk_exception.rb +0 -2
  114. data/lib/securenative/http/securenative_http_client.rb +0 -30
  115. data/lib/securenative/models/request_options.rb +0 -10
  116. data/lib/securenative/models/user_traits.rb +0 -10
  117. data/lib/securenative/securenative.iml +0 -9
  118. data/lib/securenative/utils/date_utils.rb +0 -7
  119. data/lib/securenative/utils/utils.rb +0 -9
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'models/sdk_event'
4
+ require 'enums/failover_strategy'
5
+ require 'enums/risk_level'
6
+ require 'enums/api_route'
7
+ require 'models/verify_result'
8
+ require 'json'
9
+
10
+ class ApiManager
11
+ def initialize(event_manager, securenative_options)
12
+ @event_manager = event_manager
13
+ @options = securenative_options
14
+ end
15
+
16
+ def track(event_options)
17
+ SecureNativeLogger.debug('Track event call')
18
+ event = SDKEvent.new(event_options, @options)
19
+ @event_manager.send_async(event, ApiRoute::TRACK)
20
+ end
21
+
22
+ def verify(event_options)
23
+ SecureNativeLogger.debug('Verify event call')
24
+ event = SDKEvent.new(event_options, @options)
25
+
26
+ begin
27
+ res = @event_manager.send_sync(event, ApiRoute::VERIFY, false)
28
+ ver_result = JSON.parse(res.body)
29
+ return VerifyResult.new(risk_level: ver_result['riskLevel'], score: ver_result['score'], triggers: ver_result['triggers'])
30
+ rescue StandardError => e
31
+ SecureNativeLogger.debug("Failed to call verify; #{e}")
32
+ end
33
+ if @options.fail_over_strategy == FailOverStrategy::FAIL_OPEN
34
+ return VerifyResult.new(risk_level: RiskLevel::LOW, score: 0, triggers: nil)
35
+ end
36
+
37
+ VerifyResult.new(risk_level: RiskLevel::HIGH, score: 1, triggers: nil)
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'enums/failover_strategy'
4
+
5
+ class ConfigurationBuilder
6
+ attr_reader :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
7
+ attr_writer :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
8
+
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)
12
+ @api_key = api_key
13
+ @api_url = api_url
14
+ @interval = interval
15
+ @max_events = max_events
16
+ @timeout = timeout
17
+ @auto_send = auto_send
18
+ @disable = disable
19
+ @log_level = log_level
20
+ @fail_over_strategy = fail_over_strategy
21
+ end
22
+
23
+ def self.default_securenative_options
24
+ SecureNativeOptions.new
25
+ end
26
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'config/configuration_builder'
5
+
6
+ class ConfigurationManager
7
+ DEFAULT_CONFIG_FILE = 'securenative.yml'
8
+ CUSTOM_CONFIG_FILE_ENV_NAME = 'SECURENATIVE_CONFIG_FILE'
9
+ @config = nil
10
+
11
+ def self.read_resource_file(resource_path)
12
+ properties = {}
13
+ begin
14
+ @config = YAML.load_file(resource_path)
15
+ properties = @config unless @config.nil?
16
+ rescue StandardError => e
17
+ SecureNativeLogger.error("Could not parse config file #{resource_path}; #{e}")
18
+ end
19
+ properties
20
+ end
21
+
22
+ def self._get_resource_path(env_name)
23
+ Env.fetch(env_name, ENV[DEFAULT_CONFIG_FILE])
24
+ end
25
+
26
+ def self.config_builder
27
+ ConfigurationBuilder.new
28
+ end
29
+
30
+ def self._get_env_or_default(properties, key, default)
31
+ return ENV[key] if ENV[key]
32
+ return properties[key] if properties[key]
33
+
34
+ default
35
+ end
36
+
37
+ def self.load_config
38
+ options = ConfigurationBuilder.default_securenative_options
39
+
40
+ resource_path = DEFAULT_CONFIG_FILE
41
+ resource_path = ENV[CUSTOM_CONFIG_FILE_ENV_NAME] unless ENV[CUSTOM_CONFIG_FILE_ENV_NAME].nil?
42
+
43
+ properties = read_resource_file(resource_path)
44
+
45
+ ConfigurationBuilder.new(api_key: _get_env_or_default(properties, 'SECURENATIVE_API_KEY', options.api_key),
46
+ api_url: _get_env_or_default(properties, 'SECURENATIVE_API_URL', options.api_url),
47
+ interval: _get_env_or_default(properties, 'SECURENATIVE_INTERVAL', options.interval),
48
+ max_events: _get_env_or_default(properties, 'SECURENATIVE_MAX_EVENTS', options.max_events),
49
+ timeout: _get_env_or_default(properties, 'SECURENATIVE_TIMEOUT', options.timeout),
50
+ auto_send: _get_env_or_default(properties, 'SECURENATIVE_AUTO_SEND', options.auto_send),
51
+ disable: _get_env_or_default(properties, 'SECURENATIVE_DISABLE', options.disable),
52
+ log_level: _get_env_or_default(properties, 'SECURENATIVE_LOG_LEVEL', options.log_level),
53
+ fail_over_strategy: _get_env_or_default(properties, 'SECURENATIVE_FAILOVER_STRATEGY', options.fail_over_strategy))
54
+ end
55
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'enums/failover_strategy'
4
+
5
+ class SecureNativeOptions
6
+ attr_reader :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
7
+ attr_writer :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
8
+
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)
12
+ @api_key = api_key
13
+ @api_url = api_url
14
+ @interval = interval
15
+ @max_events = max_events
16
+ @timeout = timeout
17
+ @auto_send = auto_send
18
+ @disable = disable
19
+ @log_level = log_level
20
+ @fail_over_strategy = fail_over_strategy
21
+ end
22
+ 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
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
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
+
9
+ class SecureNativeContext
10
+ attr_reader :client_token, :ip, :remote_ip, :headers, :url, :http_method, :body
11
+ attr_writer :client_token, :ip, :remote_ip, :headers, :url, :http_method, :body
12
+
13
+ SECURENATIVE_COOKIE = '_sn'
14
+
15
+ def initialize(client_token: '', ip: '', remote_ip: '', headers: nil, url: '', http_method: '', body: '')
16
+ @client_token = client_token
17
+ @ip = ip
18
+ @remote_ip = remote_ip
19
+ @headers = headers
20
+ @url = url
21
+ @http_method = http_method
22
+ @body = body
23
+ end
24
+
25
+ def self.default_context_builder
26
+ SecureNativeContext.new
27
+ end
28
+
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
+
34
+ begin
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?
41
+ rescue StandardError
42
+ headers = []
43
+ end
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
+
55
+ begin
56
+ body = request.body.to_s
57
+ rescue StandardError
58
+ body = ''
59
+ end
60
+
61
+ client_token = RequestUtils.get_secure_header_from_request(headers) if Utils.null_or_empty?(client_token)
62
+
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)
66
+ end
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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiRoute
4
+ TRACK = 'track'
5
+ VERIFY = 'verify'
6
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventTypes
4
+ LOG_IN = 'sn.user.login'
5
+ LOG_IN_CHALLENGE = 'sn.user.login.challenge'
6
+ LOG_IN_FAILURE = 'sn.user.login.failure'
7
+ LOG_OUT = 'sn.user.logout'
8
+ SIGN_UP = 'sn.user.signup'
9
+ AUTH_CHALLENGE = 'sn.user.auth.challenge'
10
+ AUTH_CHALLENGE_SUCCESS = 'sn.user.auth.challenge.success'
11
+ AUTH_CHALLENGE_FAILURE = 'sn.user.auth.challenge.failure'
12
+ TWO_FACTOR_DISABLE = 'sn.user.2fa.disable'
13
+ EMAIL_UPDATE = 'sn.user.email.update'
14
+ PASSWORD_REST = 'sn.user.password.reset'
15
+ PASSWORD_REST_SUCCESS = 'sn.user.password.reset.success'
16
+ PASSWORD_UPDATE = 'sn.user.password.update'
17
+ PASSWORD_REST_FAILURE = 'sn.user.password.reset.failure'
18
+ USER_INVITE = 'sn.user.invite'
19
+ ROLE_UPDATE = 'sn.user.role.update'
20
+ PROFILE_UPDATE = 'sn.user.profile.update'
21
+ PAGE_VIEW = 'sn.user.page.view'
22
+ VERIFY = 'sn.verify'
23
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FailOverStrategy
4
+ FAIL_OPEN = 'fail-open'
5
+ FAIL_CLOSED = 'fail-closed'
6
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RiskLevel
4
+ LOW = 'low'
5
+ MEDIUM = 'medium'
6
+ HIGH = 'high'
7
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SecureNativeConfigError < StandardError
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SecureNativeHttpError < StandardError
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SecureNativeInvalidOptionsError < StandardError
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SecureNativeInvalidUriError < StandardError
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SecureNativeParseError < StandardError
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SecureNativeSDKIllegalStateError < StandardError
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SecureNativeSDKError < StandardError
4
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'utils/secure_native_logger'
4
+ require 'config/securenative_options'
5
+ require 'http/securenative_http_client'
6
+ require 'errors/securenative_sdk_error'
7
+ require 'errors/securenative_http_error'
8
+
9
+ class QueueItem
10
+ attr_reader :url, :body, :retry_sending
11
+ attr_writer :url, :body, :retry_sending
12
+
13
+ def initialize(url, body, retry_sending)
14
+ @url = url
15
+ @body = body
16
+ @retry = retry_sending
17
+ end
18
+ end
19
+
20
+ class EventManager
21
+ def initialize(options = SecureNativeOptions.new, http_client = nil)
22
+ if options.api_key.nil?
23
+ raise SecureNativeSDKError, 'API key cannot be None, please get your API key from SecureNative console.'
24
+ end
25
+
26
+ @http_client = if http_client.nil?
27
+ SecureNativeHttpClient.new(options)
28
+ else
29
+ http_client
30
+ end
31
+
32
+ @queue = []
33
+ @semaphore = Mutex.new
34
+ @interval = options.interval
35
+ @options = options
36
+ @send_enabled = false
37
+ @attempt = 0
38
+ @coefficients = [1, 1, 2, 3, 5, 8, 13]
39
+
40
+ @thread = Thread.new { run }
41
+ end
42
+
43
+ def send_async(event, resource_path)
44
+ if @options.disable
45
+ SecureNativeLogger.warning('SDK is disabled. no operation will be performed')
46
+ return
47
+ end
48
+
49
+ item = QueueItem.new(resource_path, EventManager.serialize(event).to_json, false)
50
+ @queue.append(item)
51
+ end
52
+
53
+ def flush
54
+ @queue.each do |item|
55
+ @http_client.post(item.url, item.body)
56
+ end
57
+ end
58
+
59
+ def send_sync(event, resource_path, retry_sending)
60
+ if @options.disable
61
+ SecureNativeLogger.warning('SDK is disabled. no operation will be performed')
62
+ return
63
+ end
64
+
65
+ SecureNativeLogger.debug("Attempting to send event #{event}")
66
+ res = @http_client.post(resource_path, EventManager.serialize(event).to_json)
67
+
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)
71
+ @queue.append(item)
72
+ end
73
+
74
+ res
75
+ end
76
+
77
+ def run
78
+ loop do
79
+ @semaphore.synchronize do
80
+ next unless !@queue.empty? && @send_enabled
81
+
82
+ @queue.each do |item|
83
+ begin
84
+ res = @http_client.post(item.url, item.body)
85
+ if res.code == '401'
86
+ item.retry_sending = false
87
+ elsif res.code != '200'
88
+ raise SecureNativeHttpError, res.status_code
89
+ end
90
+ SecureNativeLogger.debug("Event successfully sent; #{item.body}")
91
+ return res
92
+ rescue StandardError => e
93
+ SecureNativeLogger.error("Failed to send event; #{e}")
94
+ if item.retry_sending
95
+ @attempt = 0 if @coefficients.length == @attempt + 1
96
+
97
+ back_off = @coefficients[@attempt] * @options.interval
98
+ SecureNativeLogger.debug("Automatic back-off of #{back_off}")
99
+ @send_enabled = false
100
+ sleep back_off
101
+ @send_enabled = true
102
+ end
103
+ end
104
+ end
105
+ end
106
+ sleep @interval / 1000
107
+ end
108
+ end
109
+
110
+ def start_event_persist
111
+ SecureNativeLogger.debug('Starting automatic event persistence')
112
+ if @options.auto_send || @send_enabled
113
+ @send_enabled = true
114
+ else
115
+ SecureNativeLogger.debug('Automatic event persistence is disabled, you should persist events manually')
116
+ end
117
+ end
118
+
119
+ def stop_event_persist
120
+ if @send_enabled
121
+ SecureNativeLogger.debug('Attempting to stop automatic event persistence')
122
+ begin
123
+ flush
124
+ @thread&.stop?
125
+ SecureNativeLogger.debug('Stopped event persistence')
126
+ rescue StandardError => e
127
+ SecureNativeLogger.error("Could not stop event scheduler; #{e}")
128
+ end
129
+ end
130
+ end
131
+
132
+ def self.serialize(obj)
133
+ {
134
+ rid: obj.rid,
135
+ eventType: obj.event_type,
136
+ userId: obj.user_id,
137
+ userTraits: {
138
+ name: obj.user_traits.name,
139
+ email: obj.user_traits.email,
140
+ phone: obj.user_traits.phone,
141
+ createdAt: obj.user_traits.created_at
142
+ },
143
+ request: {
144
+ cid: obj.request.cid,
145
+ vid: obj.request.vid,
146
+ fp: obj.request.fp,
147
+ ip: obj.request.ip,
148
+ remoteIp: obj.request.remote_ip,
149
+ method: obj.request.http_method || '',
150
+ url: obj.request.url,
151
+ headers: obj.request.headers
152
+ },
153
+ timestamp: obj.timestamp,
154
+ properties: obj.properties
155
+ }
156
+ end
157
+ end