securenative 0.1.29 → 0.1.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +49 -0
  3. data/.github/workflows/publish.yml +60 -0
  4. data/.github/workflows/test.yml +48 -0
  5. data/.gitignore +40 -0
  6. data/.rakeTasks +7 -0
  7. data/.rspec +3 -0
  8. data/Gemfile +11 -0
  9. data/Gemfile.lock +270 -0
  10. data/LICENSE +21 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/securenative/api_manager.rb +34 -0
  15. data/lib/securenative/client.rb +75 -0
  16. data/lib/securenative/client_token.rb +14 -0
  17. data/lib/securenative/config/configuration_builder.rb +29 -0
  18. data/lib/securenative/config/configuration_manager.rb +57 -0
  19. data/lib/securenative/context.rb +65 -0
  20. data/lib/securenative/device.rb +12 -0
  21. data/lib/securenative/enums/api_route.rb +10 -0
  22. data/lib/securenative/enums/risk_level.rb +11 -0
  23. data/lib/securenative/errors/config_error.rb +4 -0
  24. data/lib/securenative/errors/http_error.rb +4 -0
  25. data/lib/securenative/errors/invalid_options_error.rb +4 -0
  26. data/lib/securenative/errors/invalid_uri_error.rb +6 -0
  27. data/lib/securenative/errors/parse_error.rb +4 -0
  28. data/lib/securenative/errors/sdk_Illegal_state_error.rb +4 -0
  29. data/lib/securenative/errors/sdk_error.rb +4 -0
  30. data/lib/securenative/event_manager.rb +156 -0
  31. data/lib/securenative/event_options.rb +35 -0
  32. data/lib/securenative/event_types.rb +25 -0
  33. data/lib/securenative/failover_strategy.rb +8 -0
  34. data/lib/securenative/frameworks/hanami.rb +46 -0
  35. data/lib/securenative/frameworks/rails.rb +48 -0
  36. data/lib/securenative/frameworks/sinatra.rb +46 -0
  37. data/lib/securenative/http_client.rb +47 -0
  38. data/lib/securenative/http_response.rb +14 -0
  39. data/lib/securenative/options.rb +23 -0
  40. data/lib/securenative/request_context.rb +20 -0
  41. data/lib/securenative/request_options.rb +14 -0
  42. data/lib/securenative/sdk_event.rb +44 -0
  43. data/lib/securenative/user_traits.rb +15 -0
  44. data/lib/securenative/utils/date_utils.rb +13 -0
  45. data/lib/securenative/utils/encryption_utils.rb +48 -0
  46. data/lib/securenative/utils/ip_utils.rb +25 -0
  47. data/lib/securenative/utils/log.rb +46 -0
  48. data/lib/securenative/utils/request_utils.rb +84 -0
  49. data/lib/securenative/utils/signature_utils.rb +18 -0
  50. data/lib/securenative/utils/utils.rb +13 -0
  51. data/lib/securenative/utils/version_utils.rb +15 -0
  52. data/lib/securenative/verify_result.rb +18 -0
  53. data/lib/securenative/version.rb +5 -0
  54. data/securenative.gemspec +33 -0
  55. metadata +55 -2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2019 SecureNative
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
3
+
4
+ require 'rspec/core/rake_task'
5
+ task :default => :spec
6
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "securenative"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
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
+ SecureNative::Log.debug('Track event call')
12
+ event = SecureNative::SDKEvent.new(event_options, @options)
13
+ @event_manager.send_async(event, SecureNative::Enums::ApiRoute::TRACK)
14
+ end
15
+
16
+ def verify(event_options)
17
+ SecureNative::Log.debug('Verify event call')
18
+ event = SecureNative::SDKEvent.new(event_options, @options)
19
+
20
+ begin
21
+ res = @event_manager.send_sync(event, SecureNative::Enums::ApiRoute::VERIFY)
22
+ ver_result = JSON.parse(res.body)
23
+ return VerifyResult.new(risk_level: ver_result['riskLevel'], score: ver_result['score'], triggers: ver_result['triggers'])
24
+ rescue StandardError => e
25
+ SecureNative::Log.debug("Failed to call verify; #{e}")
26
+ end
27
+ if @options.fail_over_strategy == SecureNative::FailOverStrategy::FAIL_OPEN
28
+ return SecureNative::VerifyResult.new(risk_level: SecureNative::Enums::RiskLevel::LOW, score: 0, triggers: nil)
29
+ end
30
+
31
+ VerifyResult.new(risk_level: SecureNative::Enums::RiskLevel::HIGH, score: 1, triggers: nil)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class Client
5
+ attr_reader :options
6
+
7
+ def initialize(options)
8
+ @securenative = nil
9
+ if SecureNative::Utils::Utils.null_or_empty?(options.api_key)
10
+ raise SecureNativeSDKError, 'You must pass your SecureNative api key'
11
+ end
12
+
13
+ @options = options
14
+ @event_manager = EventManager.new(@options)
15
+
16
+ @api_manager = SecureNative::ApiManager.new(@event_manager, @options)
17
+ SecureNative::Log.init_logger(@options.log_level)
18
+ end
19
+
20
+ def self.init_with_options(options)
21
+ if @securenative.nil?
22
+ @securenative = SecureNative::Client.new(options)
23
+ @securenative
24
+ else
25
+ SecureNative::Log.debug('This SDK was already initialized.')
26
+ raise SecureNativeSDKError, 'This SDK was already initialized.'
27
+ end
28
+ end
29
+
30
+ def self.init_with_api_key(api_key)
31
+ if SecureNative::Utils::Utils.null_or_empty?(api_key)
32
+ raise SecureNativeConfigError, 'You must pass your SecureNative api key'
33
+ end
34
+
35
+ if @securenative.nil?
36
+ options = SecureNative::Config::ConfigurationBuilder.new(api_key: api_key)
37
+ @securenative = SecureNative::Client.new(options)
38
+ @securenative
39
+ else
40
+ SecureNative::Log.debug('This SDK was already initialized.')
41
+ raise SecureNativeSDKError, 'This SDK was already initialized.'
42
+ end
43
+ end
44
+
45
+ def self.init
46
+ options = SecureNative::Config::ConfigurationManager.load_config
47
+ init_with_options(options)
48
+ end
49
+
50
+ def self.instance
51
+ raise SecureNativeSDKIllegalStateError if @securenative.nil?
52
+
53
+ @securenative
54
+ end
55
+
56
+ def track(event_options)
57
+ @api_manager.track(event_options)
58
+ end
59
+
60
+ def verify(event_options)
61
+ @api_manager.verify(event_options)
62
+ end
63
+
64
+ def self._flush
65
+ @securenative = nil
66
+ end
67
+
68
+ def verify_request_payload(request)
69
+ request_signature = request.header[SignatureUtils.SIGNATURE_HEADER]
70
+ body = request.body
71
+
72
+ SignatureUtils.valid_signature?(@options.api_key, body, request_signature)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class ClientToken
5
+ attr_reader :cid, :vid, :fp
6
+ attr_writer :cid, :vid, :fp
7
+
8
+ def initialize(cid, vid, fp)
9
+ @cid = cid
10
+ @vid = vid
11
+ @fp = fp
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module Config
5
+ class ConfigurationBuilder
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
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: SecureNative::FailOverStrategy::FAIL_OPEN, proxy_headers: nil)
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
+ @proxy_headers = proxy_headers
22
+ end
23
+
24
+ def self.default_securenative_options
25
+ Options.new
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module Config
5
+ class ConfigurationManager
6
+ DEFAULT_CONFIG_FILE = 'securenative.yml'
7
+ CUSTOM_CONFIG_FILE_ENV_NAME = 'SECURENATIVE_CONFIG_FILE'
8
+ @config = nil
9
+
10
+ def self.read_resource_file(resource_path)
11
+ properties = {}
12
+ begin
13
+ @config = YAML.load_file(resource_path)
14
+ properties = @config unless @config.nil?
15
+ rescue StandardError => e
16
+ SecureNative::Log.error("Could not parse securenative.config file #{resource_path}; #{e}")
17
+ end
18
+ properties
19
+ end
20
+
21
+ def self._get_resource_path(env_name)
22
+ Env.fetch(env_name, ENV[DEFAULT_CONFIG_FILE])
23
+ end
24
+
25
+ def self.config_builder
26
+ SecureNative::Config::ConfigurationBuilder.new
27
+ end
28
+
29
+ def self._get_env_or_default(properties, key, default)
30
+ return ENV[key] if ENV[key]
31
+ return properties[key] if properties[key]
32
+
33
+ default
34
+ end
35
+
36
+ def self.load_config
37
+ options = SecureNative::Config::ConfigurationBuilder.default_securenative_options
38
+
39
+ resource_path = DEFAULT_CONFIG_FILE
40
+ resource_path = ENV[CUSTOM_CONFIG_FILE_ENV_NAME] unless ENV[CUSTOM_CONFIG_FILE_ENV_NAME].nil?
41
+
42
+ properties = read_resource_file(resource_path)
43
+
44
+ SecureNative::Config::ConfigurationBuilder.new(api_key: _get_env_or_default(properties, 'SECURENATIVE_API_KEY', options.api_key),
45
+ api_url: _get_env_or_default(properties, 'SECURENATIVE_API_URL', options.api_url),
46
+ interval: _get_env_or_default(properties, 'SECURENATIVE_INTERVAL', options.interval),
47
+ max_events: _get_env_or_default(properties, 'SECURENATIVE_MAX_EVENTS', options.max_events),
48
+ timeout: _get_env_or_default(properties, 'SECURENATIVE_TIMEOUT', options.timeout),
49
+ auto_send: _get_env_or_default(properties, 'SECURENATIVE_AUTO_SEND', options.auto_send),
50
+ disable: _get_env_or_default(properties, 'SECURENATIVE_DISABLE', options.disable),
51
+ log_level: _get_env_or_default(properties, 'SECURENATIVE_LOG_LEVEL', options.log_level),
52
+ fail_over_strategy: _get_env_or_default(properties, 'SECURENATIVE_FAILOVER_STRATEGY', options.fail_over_strategy),
53
+ proxy_headers: _get_env_or_default(properties, 'SECURENATIVE_PROXY_HEADERS', options.proxy_headers))
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class Context
5
+ attr_reader :client_token, :ip, :remote_ip, :headers, :url, :http_method, :body
6
+ attr_writer :client_token, :ip, :remote_ip, :headers, :url, :http_method, :body
7
+
8
+ SECURENATIVE_COOKIE = '_sn'
9
+
10
+ def initialize(client_token: '', ip: '', remote_ip: '', headers: nil, url: '', http_method: '', body: '')
11
+ @client_token = client_token
12
+ @ip = ip
13
+ @remote_ip = remote_ip
14
+ @headers = headers
15
+ @url = url
16
+ @http_method = http_method
17
+ @body = body
18
+ end
19
+
20
+ def self.default_context_builder
21
+ SecureNative::Context.new
22
+ end
23
+
24
+ def self.from_http_request(request)
25
+ client_token = SecureNative::Frameworks::Rails.get_client_token(request)
26
+ client_token = SecureNative::Frameworks::Sinatra.get_client_token(request) if client_token.nil?
27
+ client_token = SecureNative::Frameworks::Hanami.get_client_token(request) if client_token.nil?
28
+
29
+ begin
30
+ headers = SecureNative::Frameworks::Rails.get_headers(request)
31
+ headers = SecureNative::Frameworks::Sinatra.get_headers(request) if headers.nil?
32
+ headers = SecureNative::Frameworks::Hanami.get_headers(request) if headers.nil?
33
+
34
+ # Standard Ruby request
35
+ headers = request.header.to_hash if headers.nil?
36
+ rescue StandardError
37
+ headers = []
38
+ end
39
+
40
+ url = SecureNative::Frameworks::Rails.get_url(request)
41
+ url = SecureNative::Frameworks::Sinatra.get_url(request) if url.nil?
42
+ url = SecureNative::Frameworks::Hanami.get_url(request) if url.nil?
43
+ url = '' if url.nil?
44
+
45
+ method = SecureNative::Frameworks::Rails.get_method(request)
46
+ method = SecureNative::Frameworks::Sinatra.get_method(request) if method.nil?
47
+ method = SecureNative::Frameworks::Hanami.get_method(request) if method.nil?
48
+ method = '' if method.nil?
49
+
50
+ begin
51
+ body = request.body.to_s
52
+ rescue StandardError
53
+ body = ''
54
+ end
55
+
56
+ if SecureNative::Utils::Utils.null_or_empty?(client_token)
57
+ client_token = SecureNative::Utils::RequestUtils.get_secure_header_from_request(headers)
58
+ end
59
+
60
+ SecureNative::Context.new(client_token: client_token, ip: SecureNative::Utils::RequestUtils.get_client_ip_from_request(request),
61
+ remote_ip: SecureNative::Utils::RequestUtils.get_remote_ip_from_request(request),
62
+ headers: headers, url: url, http_method: method || '', body: body)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class Device
5
+ attr_reader :device_id
6
+ attr_writer :device_id
7
+
8
+ def initialize(device_id)
9
+ @device_id = device_id
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module Enums
5
+ module ApiRoute
6
+ TRACK = 'track'
7
+ VERIFY = 'verify'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module Enums
5
+ module RiskLevel
6
+ LOW = 'low'
7
+ MEDIUM = 'medium'
8
+ HIGH = 'high'
9
+ end
10
+ end
11
+ 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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class SecureNativeInvalidUriError < StandardError
5
+ end
6
+ 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,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ class QueueItem
4
+ attr_reader :url, :body, :retry_sending
5
+ attr_writer :url, :body, :retry_sending
6
+
7
+ def initialize(url, body, retry_sending)
8
+ @url = url
9
+ @body = body
10
+ @retry = retry_sending
11
+ end
12
+ end
13
+
14
+ class EventManager
15
+ attr_reader :activated
16
+
17
+ def initialize(options = Options.new, http_client = nil)
18
+ if options.api_key.nil?
19
+ raise SecureNativeSDKError, 'API key cannot be None, please get your API key from SecureNative console.'
20
+ end
21
+
22
+ @http_client = if http_client.nil?
23
+ SecureNative::HttpClient.new(options)
24
+ else
25
+ http_client
26
+ end
27
+
28
+ @queue = []
29
+ @semaphore = Mutex.new
30
+ @options = options
31
+ @send_enabled = false
32
+ @attempt = 0
33
+ @coefficients = [1, 1, 2, 3, 5, 8, 13]
34
+ @thread = nil
35
+ end
36
+
37
+ def send_async(event, resource_path)
38
+ if @options.disable
39
+ SecureNative::Log.warning('SDK is disabled. no operation will be performed')
40
+ return
41
+ end
42
+
43
+ start_event_persist unless @send_enabled
44
+
45
+ item = QueueItem.new(resource_path, EventManager.serialize(event).to_json, false)
46
+ @queue.append(item)
47
+ end
48
+
49
+ def flush
50
+ @queue.each do |item|
51
+ @http_client.post(item.url, item.body)
52
+ end
53
+ end
54
+
55
+ def send_sync(event, resource_path)
56
+ if @options.disable
57
+ SecureNative::Log.warning('SDK is disabled. no operation will be performed')
58
+ return
59
+ end
60
+
61
+ SecureNative::Log.debug("Attempting to send event #{event}")
62
+ res = @http_client.post(resource_path, EventManager.serialize(event).to_json)
63
+
64
+ if res.nil? || res.code != '200'
65
+ SecureNative::Log.info("SecureNative failed to call endpoint #{resource_path} with event #{event}. adding back to queue")
66
+ end
67
+
68
+ res
69
+ end
70
+
71
+ def run
72
+ loop do
73
+ @semaphore.synchronize do
74
+ if (item = !@queue.empty? && @send_enabled)
75
+ begin
76
+ item = @queue.shift
77
+ res = @http_client.post(item.url, item.body)
78
+ if res.code == '401'
79
+ item.retry_sending = false
80
+ elsif res.code != '200'
81
+ @queue.append(item)
82
+ raise SecureNativeHttpError, res.status_code
83
+ end
84
+ SecureNative::Log.debug("Event successfully sent; #{item.body}")
85
+ rescue Exception => e
86
+ SecureNative::Log.error("Failed to send event; #{e}")
87
+ if item.retry_sending
88
+ @attempt = 0 if @coefficients.length == @attempt + 1
89
+
90
+ back_off = @coefficients[@attempt] * @options.interval
91
+ SecureNative::Log.debug("Automatic back-off of #{back_off}")
92
+ @send_enabled = false
93
+ sleep back_off
94
+ @send_enabled = true
95
+ end
96
+ end
97
+ end
98
+ end
99
+ sleep @options.interval / 1000 if @queue.empty?
100
+ end
101
+ end
102
+
103
+ def start_event_persist
104
+ SecureNative::Log.debug('Starting automatic event persistence')
105
+ if @options.auto_send
106
+ begin
107
+ @thread = Thread.new { run }
108
+ @send_enabled = true
109
+ rescue StandardError => e
110
+ SecureNative::Log.error("Could not start event scheduler; #{e}")
111
+ @send_enabled = false
112
+ end
113
+ else
114
+ SecureNative::Log.debug('Automatic event persistence is disabled, you should persist events manually')
115
+ end
116
+ end
117
+
118
+ def stop_event_persist
119
+ if @send_enabled
120
+ SecureNative::Log.debug('Attempting to stop automatic event persistence')
121
+ begin
122
+ flush
123
+ @thread&.stop?
124
+ SecureNative::Log.debug('Stopped event persistence')
125
+ rescue StandardError => e
126
+ SecureNative::Log.error("Could not stop event scheduler; #{e}")
127
+ end
128
+ end
129
+ end
130
+
131
+ def self.serialize(obj)
132
+ {
133
+ rid: obj.rid,
134
+ eventType: obj.event_type,
135
+ userId: obj.user_id,
136
+ userTraits: {
137
+ name: obj.user_traits.name,
138
+ email: obj.user_traits.email,
139
+ phone: obj.user_traits.phone,
140
+ createdAt: obj.user_traits.created_at
141
+ },
142
+ request: {
143
+ cid: obj.request.cid,
144
+ vid: obj.request.vid,
145
+ fp: obj.request.fp,
146
+ ip: obj.request.ip,
147
+ remoteIp: obj.request.remote_ip,
148
+ method: obj.request.http_method || '',
149
+ url: obj.request.url,
150
+ headers: obj.request.headers
151
+ },
152
+ timestamp: obj.timestamp,
153
+ properties: obj.properties
154
+ }
155
+ end
156
+ end