securenative 0.1.5 → 0.1.16

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +57 -0
  3. data/.github/workflows/publish.yml +60 -0
  4. data/.github/workflows/test.yml +45 -0
  5. data/.rakeTasks +7 -0
  6. data/.rspec +3 -0
  7. data/.travis.yml +6 -0
  8. data/Gemfile +4 -1
  9. data/Gemfile.lock +28 -2
  10. data/README.md +134 -66
  11. data/Rakefile +5 -1
  12. data/VERSION +1 -0
  13. data/lib/securenative/api_manager.rb +30 -0
  14. data/lib/securenative/config/configuration_builder.rb +26 -0
  15. data/lib/securenative/config/configuration_manager.rb +53 -0
  16. data/lib/securenative/config/securenative_options.rb +18 -0
  17. data/lib/securenative/context/context_builder.rb +59 -0
  18. data/lib/securenative/context/securenative_context.rb +14 -0
  19. data/lib/securenative/enums/api_route.rb +4 -0
  20. data/lib/securenative/enums/event_types.rb +21 -0
  21. data/lib/securenative/enums/failover_strategy.rb +4 -0
  22. data/lib/securenative/enums/risk_level.rb +5 -0
  23. data/lib/securenative/event_manager.rb +122 -61
  24. data/lib/securenative/event_options_builder.rb +21 -0
  25. data/lib/securenative/exceptions/securenative_config_exception.rb +2 -0
  26. data/lib/securenative/exceptions/securenative_http_exception.rb +2 -0
  27. data/lib/securenative/exceptions/securenative_invalid_options_exception.rb +2 -0
  28. data/lib/securenative/exceptions/securenative_invalid_uri_exception.rb +2 -0
  29. data/lib/securenative/exceptions/securenative_parse_exception.rb +2 -0
  30. data/lib/securenative/exceptions/securenative_sdk_Illegal_state_exception.rb +2 -0
  31. data/lib/securenative/exceptions/securenative_sdk_exception.rb +2 -0
  32. data/lib/securenative/http/http_response.rb +10 -0
  33. data/lib/securenative/http/securenative_http_client.rb +30 -0
  34. data/lib/securenative/logger.rb +42 -0
  35. data/lib/securenative/models/client_token.rb +10 -0
  36. data/lib/securenative/models/device.rb +8 -0
  37. data/lib/securenative/models/event_options.rb +13 -0
  38. data/lib/securenative/models/request_context.rb +15 -0
  39. data/lib/securenative/models/request_options.rb +10 -0
  40. data/lib/securenative/models/sdk_event.rb +25 -0
  41. data/lib/securenative/models/user_traits.rb +10 -0
  42. data/lib/securenative/models/verify_result.rb +10 -0
  43. data/lib/securenative/securenative.iml +9 -0
  44. data/lib/securenative/securenative.rb +82 -0
  45. data/lib/securenative/utils/date_utils.rb +7 -0
  46. data/lib/securenative/utils/encryption_utils.rb +38 -0
  47. data/lib/securenative/utils/ip_utils.rb +22 -0
  48. data/lib/securenative/utils/request_utils.rb +21 -0
  49. data/lib/securenative/utils/signature_utils.rb +14 -0
  50. data/lib/securenative/utils/utils.rb +9 -0
  51. data/lib/securenative/utils/version_utils.rb +10 -0
  52. data/securenative.gemspec +4 -4
  53. metadata +51 -15
  54. data/lib/securenative.rb +0 -39
  55. data/lib/securenative/config.rb +0 -9
  56. data/lib/securenative/event_options.rb +0 -86
  57. data/lib/securenative/event_type.rb +0 -21
  58. data/lib/securenative/http_client.rb +0 -20
  59. data/lib/securenative/secure_native_sdk.rb +0 -62
  60. data/lib/securenative/securenative_options.rb +0 -17
  61. data/lib/securenative/sn_exception.rb +0 -5
  62. data/lib/securenative/utils.rb +0 -41
data/Rakefile CHANGED
@@ -1,2 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
- task :default => :spec
2
+ task :default => :spec
3
+
4
+ require 'rspec/core/rake_task'
5
+ task :default => :spec
6
+ RSpec::Core::RakeTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.16
@@ -0,0 +1,30 @@
1
+ require 'json'
2
+ require_relative 'logger'
3
+
4
+ class ApiManager
5
+ def initialize(event_manager, securenative_options)
6
+ @event_manager = event_manager
7
+ @options = securenative_options
8
+ end
9
+
10
+ def track(event_options)
11
+ Logger.debug('Track event call')
12
+ event = SDKEvent.new(event_options, @options)
13
+ @event_manager.send_async(event, ApiRoute::TRACK)
14
+ end
15
+
16
+ def verify(event_options)
17
+ Logger.debug('Verify event call')
18
+ event = SDKEvent.new(event_options, @options)
19
+
20
+ begin
21
+ res = JSON.parse(@event_manager.send_sync(event, ApiRoute::VERIFY, false))
22
+ return VerifyResult.new(res['riskLevel'], res['score'], res['triggers'])
23
+ rescue StandardError => e
24
+ Logger.debug('Failed to call verify; {}'.format(e))
25
+ end
26
+ return VerifyResult.new(RiskLevel::LOW, 0, nil) if @options.fail_over_strategy == FailOverStrategy::FAIL_OPEN
27
+
28
+ VerifyResult.new(RiskLevel::HIGH, 1, nil)
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ class ConfigurationBuilder
2
+ attr_reader :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
3
+ attr_writer :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
4
+
5
+ def initialize(api_key = nil, api_url = 'https://api.securenative.com/collector/api/v1', interval = 1000,
6
+ max_events = 1000, timeout = 1500, auto_send = true, disable = false, log_level = 'FATAL',
7
+ fail_over_strategy = FailOverStrategy::FAIL_OPEN)
8
+ @api_key = api_key
9
+ @api_url = api_url
10
+ @interval = interval
11
+ @max_events = max_events
12
+ @timeout = timeout
13
+ @auto_send = auto_send
14
+ @disable = disable
15
+ @log_level = log_level
16
+ @fail_over_strategy = fail_over_strategy
17
+ end
18
+
19
+ def self.default_config_builder
20
+ ConfigurationBuilder()
21
+ end
22
+
23
+ def self.default_securenative_options
24
+ SecureNativeOptions()
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ require 'parseconfig'
2
+
3
+ class ConfigurationManager
4
+ DEFAULT_CONFIG_FILE = 'securenative.cfg'.freeze
5
+ CUSTOM_CONFIG_FILE_ENV_NAME = 'SECURENATIVE_COMFIG_FILE'.freeze
6
+ @config = nil
7
+
8
+ def self.read_resource_file(resource_path)
9
+ @config = ParseConfig.new(resource_path)
10
+
11
+ properties = {}
12
+ @config.get_groups.each { |group|
13
+ group.each do |key, value|
14
+ properties[key.upcase] = value
15
+ end
16
+ }
17
+ properties
18
+ end
19
+
20
+ def self._get_resource_path(env_name)
21
+ Env.fetch(env_name, ENV[DEFAULT_CONFIG_FILE])
22
+ end
23
+
24
+ def self.config_builder
25
+ ConfigurationBuilder.default_config_builder
26
+ end
27
+
28
+ def self._get_env_or_default(properties, key, default)
29
+ return Env[key] if Env[key]
30
+ return properties[key] if properties[key]
31
+
32
+ default
33
+ end
34
+
35
+ def self.load_config
36
+ options = ConfigurationBuilder().default_securenative_options
37
+
38
+ resource_path = DEFAULT_CONFIG_FILE
39
+ resource_path = Env[CUSTOM_CONFIG_FILE_ENV_NAME] if Env[CUSTOM_CONFIG_FILE_ENV_NAME]
40
+
41
+ properties = read_resource_file(resource_path)
42
+
43
+ ConfigurationBuilder(_get_env_or_default(properties, 'SECURENATIVE_API_KEY', options.api_key),
44
+ _get_env_or_default(properties, 'SECURENATIVE_API_URL', options.api_url),
45
+ _get_env_or_default(properties, 'SECURENATIVE_INTERVAL', options.interval),
46
+ _get_env_or_default(properties, 'SECURENATIVE_MAX_EVENTS', options.max_events),
47
+ _get_env_or_default(properties, 'SECURENATIVE_TIMEOUT', options.timeout),
48
+ _get_env_or_default(properties, 'SECURENATIVE_AUTO_SEND', options.auto_send),
49
+ _get_env_or_default(properties, 'SECURENATIVE_DISABLE', options.disable),
50
+ _get_env_or_default(properties, 'SECURENATIVE_LOG_LEVEL', options.log_level),
51
+ _get_env_or_default(properties, 'SECURENATIVE_FAILOVER_STRATEGY', options.fail_over_strategy))
52
+ end
53
+ end
@@ -0,0 +1,18 @@
1
+ class SecureNativeOptions
2
+ attr_reader :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
3
+ attr_writer :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy
4
+
5
+ def initialize(api_key = nil, api_url = "https://api.securenative.com/collector/api/v1", interval = 1000,
6
+ max_events = 1000, timeout = 1500, auto_send = true, disable = false, log_level = "FATAL",
7
+ fail_over_strategy = FailOverStrategy::FAIL_OPEN)
8
+ @api_key = api_key
9
+ @api_url = api_url
10
+ @interval = interval
11
+ @max_events = max_events
12
+ @timeout = timeout
13
+ @auto_send = auto_send
14
+ @disable = disable
15
+ @log_level = log_level
16
+ @fail_over_strategy = fail_over_strategy
17
+ end
18
+ end
@@ -0,0 +1,59 @@
1
+ class ContextBuilder
2
+ attr_reader :context
3
+
4
+ def initialize(client_token = nil, ip = nil, remote_ip = nil, headers = nil, url = nil, method = nil, body = nil)
5
+ @context = SecureNativeContext(client_token, ip, remote_ip, headers, url, method, body)
6
+ end
7
+
8
+ def client_token(client_token)
9
+ @context.client_token = client_token
10
+ end
11
+
12
+ def ip(ip)
13
+ @context.ip = ip
14
+ end
15
+
16
+ def remote_ip(remote_ip)
17
+ @context.remote_ip = remote_ip
18
+ end
19
+
20
+ def headers(headers)
21
+ @context.headers = headers
22
+ end
23
+
24
+ def url(url)
25
+ @context.url = url
26
+ end
27
+
28
+ def method(method)
29
+ @context.method = method
30
+ end
31
+
32
+ def body(body)
33
+ @context.body = body
34
+ end
35
+
36
+ def self.default_context_builder
37
+ ContextBuilder()
38
+ end
39
+
40
+ def self.from_http_request(request)
41
+ begin
42
+ client_token = request.cookies[RequestUtils.SECURENATIVE_COOKIE]
43
+ rescue StandardError
44
+ client_token = nil
45
+ end
46
+
47
+ begin
48
+ headers = request.headers
49
+ rescue StandardError
50
+ headers = nil
51
+ end
52
+
53
+ client_token = RequestUtils.get_secure_header_from_request(headers) if Utils.null_or_empty?(client_token)
54
+
55
+ ContextBuilder(url = request.url, method = request.method, header = headers, client_token = client_token,
56
+ client_ip = RequestUtils.get_client_ip_from_request(request),
57
+ remote_ip = RequestUtils.get_remote_ip_from_request(request), nil)
58
+ end
59
+ end
@@ -0,0 +1,14 @@
1
+ class SecureNativeContext
2
+ attr_reader :client_token, :ip, :remote_ip, :headers, :url, :method, :body
3
+ attr_writer :client_token, :ip, :remote_ip, :headers, :url, :method, :body
4
+
5
+ def initialize(client_token = nil, ip = nil, remote_ip = nil, headers = nil, url = nil, method = nil, body = nil)
6
+ @client_token = client_token
7
+ @ip = ip
8
+ @remote_ip = remote_ip
9
+ @headers = headers
10
+ @url = url
11
+ @method = method
12
+ @body = body
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ module ApiRoute
2
+ TRACK = 'track'.freeze
3
+ VERIFY = 'verify'.freeze
4
+ end
@@ -0,0 +1,21 @@
1
+ module EventTypes
2
+ LOG_IN = 'sn.user.login'.freeze
3
+ LOG_IN_CHALLENGE = 'sn.user.login.challenge'.freeze
4
+ LOG_IN_FAILURE = 'sn.user.login.failure'.freeze
5
+ LOG_OUT = 'sn.user.logout'.freeze
6
+ SIGN_UP = 'sn.user.signup'.freeze
7
+ AUTH_CHALLENGE = 'sn.user.auth.challenge'.freeze
8
+ AUTH_CHALLENGE_SUCCESS = 'sn.user.auth.challenge.success'.freeze
9
+ AUTH_CHALLENGE_FAILURE = 'sn.user.auth.challenge.failure'.freeze
10
+ TWO_FACTOR_DISABLE = 'sn.user.2fa.disable'.freeze
11
+ EMAIL_UPDATE = 'sn.user.email.update'.freeze
12
+ PASSWORD_REST = 'sn.user.password.reset'.freeze
13
+ PASSWORD_REST_SUCCESS = 'sn.user.password.reset.success'.freeze
14
+ PASSWORD_UPDATE = 'sn.user.password.update'.freeze
15
+ PASSWORD_REST_FAILURE = 'sn.user.password.reset.failure'.freeze
16
+ USER_INVITE = 'sn.user.invite'.freeze
17
+ ROLE_UPDATE = 'sn.user.role.update'.freeze
18
+ PROFILE_UPDATE = 'sn.user.profile.update'.freeze
19
+ PAGE_VIEW = 'sn.user.page.view'.freeze
20
+ VERIFY = 'sn.verify'.freeze
21
+ end
@@ -0,0 +1,4 @@
1
+ module FailOverStrategy
2
+ FAIL_OPEN = 'fail-open'.freeze
3
+ FAIL_CLOSED = 'fail-closed'.freeze
4
+ end
@@ -0,0 +1,5 @@
1
+ module RiskLevel
2
+ LOW = 'low'.freeze
3
+ MEDIUM = 'medium'.freeze
4
+ HIGH = 'high'.freeze
5
+ end
@@ -1,88 +1,149 @@
1
- require_relative 'securenative_options'
2
- require_relative 'http_client'
3
- require_relative 'sn_exception'
4
- require 'json'
5
- require 'thread'
1
+ require_relative 'logger'
6
2
 
7
3
  class QueueItem
8
- def initialize(url, body)
4
+ attr_reader :url, :body, :retry
5
+ attr_writer :url, :body, :retry
6
+
7
+ def initialize(url, body, _retry)
9
8
  @url = url
10
9
  @body = body
10
+ @retry = _retry
11
11
  end
12
-
13
- attr_reader :url
14
- attr_reader :body
15
12
  end
16
13
 
17
14
  class EventManager
18
- def initialize(api_key, options: SecureNativeOptions.new, http_client: HttpClient.new)
19
- if api_key == nil
20
- raise SecureNativeSDKException.new
15
+ def initialize(options = SecureNativeOptions(), http_client = nil)
16
+ if options.api_key.nil?
17
+ raise SecureNativeSDKException('API key cannot be None, please get your API key from SecureNative console.')
21
18
  end
22
19
 
23
- @api_key = api_key
20
+ @http_client = if http_client.nil?
21
+ SecureNativeHttpClient(options)
22
+ else
23
+ http_client
24
+ end
25
+
26
+ @queue = []
27
+ @thread = Thread.new(run)
28
+ @thread.start
29
+
24
30
  @options = options
25
- @http_client = http_client
26
- @queue = Queue.new
27
-
28
- if @options.auto_send
29
- interval_seconds = [(@options.interval / 1000).floor, 1].max
30
- Thread.new do
31
- loop do
32
- flush
33
- sleep(interval_seconds)
34
- end
35
- end
36
- end
31
+ @send_enabled = false
32
+ @attempt = 0
33
+ @coefficients = [1, 1, 2, 3, 5, 8, 13]
34
+ @thread = nil
35
+ @interval = options.interval
37
36
  end
38
37
 
39
- def send_async(event, path)
40
- q_item = QueueItem.new(build_url(path), event)
41
- @queue.push(q_item)
42
- end
38
+ def send_async(event, resource_path)
39
+ if @options.disable
40
+ Logger.warning('SDK is disabled. no operation will be performed')
41
+ return
42
+ end
43
43
 
44
- def send_sync(event, path)
45
- @http_client.post(
46
- build_url(path),
47
- @api_key,
48
- event.to_hash.to_json
49
- )
44
+ item = QueueItem(resource_path, JSON.parse(EventManager.serialize(event)), false)
45
+ @queue.append(item)
50
46
  end
51
47
 
52
48
  def flush
53
- if is_queue_full
54
- i = @options.max_events - 1
55
- while i >= 0
56
- item = @queue.pop
57
- @http_client.post(
58
- build_url(item.url),
59
- @api_key,
60
- item.body.to_hash.to_json
61
- )
62
- end
63
- else
64
- q = Array.new(@queue.size) {@queue.pop}
65
- q.each do |item|
66
- @http_client.post(
67
- build_url(item.url),
68
- @api_key,
69
- item.body
70
- )
71
- @queue = Queue.new
72
- end
49
+ @queue.each do |item|
50
+ @http_client.post(item.url, item.body)
73
51
  end
74
52
  end
75
53
 
76
- private
54
+ def send_sync(event, resource_path, _retry)
55
+ if @options.disable
56
+ Logger.warning('SDK is disabled. no operation will be performed')
57
+ return
58
+ end
59
+
60
+ Logger.debug('Attempting to send event {}'.format(event))
61
+ res = @http_client.post(resource_path, JSON.parse(EventManager.serialize(event)))
77
62
 
78
- def build_url(path)
79
- @options.api_url + path
63
+ if res.status_code != 200
64
+ Logger.info('SecureNative failed to call endpoint {} with event {}. adding back to queue'.format(resource_path, event))
65
+ item = QueueItem(resource_path, JSON.parse(EventManager.serialize(event)), _retry)
66
+ @queue.append(item)
67
+ end
68
+
69
+ res
70
+ end
71
+
72
+ def run
73
+ loop do
74
+ next unless !@queue.empty? && @send_enabled
75
+
76
+ @queue.each do |item|
77
+ begin
78
+ res = @http_client.post(item.url, item.body)
79
+ if res.status_code == 401
80
+ item.retry = false
81
+ elsif res.status_code != 200
82
+ raise SecureNativeHttpException(res.status_code)
83
+ end
84
+ Logger.debug('Event successfully sent; {}'.format(item.body))
85
+ return res
86
+ rescue StandardError => e
87
+ Logger.error('Failed to send event; {}'.format(e))
88
+ if item.retry
89
+ @attempt = 0 if @coefficients.length == @attempt + 1
90
+
91
+ back_off = @coefficients[@attempt] * @options.interval
92
+ Logger.debug('Automatic back-off of {}'.format(back_off))
93
+ @send_enabled = false
94
+ sleep back_off
95
+ @send_enabled = true
96
+ end
97
+ end
98
+ end
99
+ sleep @interval / 1000
100
+ end
80
101
  end
81
102
 
82
- private
103
+ def start_event_persist
104
+ Logger.debug('Starting automatic event persistence')
105
+ if @options.auto_send || @send_enabled
106
+ @send_enabled = true
107
+ else
108
+ Logger.debug('Automatic event persistence is disabled, you should persist events manually')
109
+ end
110
+ end
83
111
 
84
- def is_queue_full
85
- @queue.length > @options.max_events
112
+ def stop_event_persist
113
+ if @send_enabled
114
+ Logger.debug('Attempting to stop automatic event persistence')
115
+ begin
116
+ flush
117
+ @thread&.stop
118
+ Logger.debug('Stopped event persistence')
119
+ rescue StandardError => e
120
+ Logger.error('Could not stop event scheduler; {}'.format(e))
121
+ end
122
+ end
86
123
  end
87
124
 
125
+ def self.serialize(obj)
126
+ {
127
+ rid: obj.rid,
128
+ eventType: obj.event_type,
129
+ userId: obj.user_id,
130
+ userTraits: {
131
+ name: obj.user_traits.name,
132
+ email: obj.user_traits.email,
133
+ createdAt: obj.user_traits.created_at
134
+ },
135
+ request: {
136
+ cid: obj.request.cid,
137
+ vid: obj.request.vid,
138
+ fp: obj.request.fp,
139
+ ip: obj.request.ip,
140
+ remoteIp: obj.request.remote_ip,
141
+ method: obj.request.method,
142
+ url: obj.request.url,
143
+ headers: obj.request.headers
144
+ },
145
+ timestamp: obj.timestamp,
146
+ properties: obj.properties
147
+ }
148
+ end
88
149
  end