securenative 0.1.29 → 0.1.30

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 (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
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class EventOptions
5
+ attr_reader :event, :user_id, :user_traits, :context, :properties, :timestamp
6
+ attr_writer :event, :user_id, :user_traits, :context, :properties, :timestamp
7
+
8
+ MAX_PROPERTIES_SIZE = 10
9
+
10
+ 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)
11
+ if !properties.nil? && properties.length > MAX_PROPERTIES_SIZE
12
+ raise SecureNativeInvalidOptionsError, "You can have only up to #{MAX_PROPERTIES_SIZE} custom properties"
13
+ end
14
+
15
+ if user_traits.nil?
16
+ if user_name && email && phone && created_at
17
+ user_traits = SecureNative::UserTraits(user_name, email, phone, created_at)
18
+ elsif user_name && email && phone
19
+ user_traits = SecureNative::UserTraits(user_name, email, phone)
20
+ elsif user_name && email
21
+ user_traits = SecureNative::UserTraits(user_name, email)
22
+ else
23
+ user_traits = UserTraits.new
24
+ end
25
+ end
26
+
27
+ @event = event
28
+ @user_id = user_id
29
+ @user_traits = user_traits
30
+ @context = context
31
+ @properties = properties
32
+ @timestamp = timestamp
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module EventTypes
5
+ LOG_IN = 'sn.user.login'
6
+ LOG_IN_CHALLENGE = 'sn.user.login.challenge'
7
+ LOG_IN_FAILURE = 'sn.user.login.failure'
8
+ LOG_OUT = 'sn.user.logout'
9
+ SIGN_UP = 'sn.user.signup'
10
+ AUTH_CHALLENGE = 'sn.user.auth.challenge'
11
+ AUTH_CHALLENGE_SUCCESS = 'sn.user.auth.challenge.success'
12
+ AUTH_CHALLENGE_FAILURE = 'sn.user.auth.challenge.failure'
13
+ TWO_FACTOR_DISABLE = 'sn.user.2fa.disable'
14
+ EMAIL_UPDATE = 'sn.user.email.update'
15
+ PASSWORD_REST = 'sn.user.password.reset'
16
+ PASSWORD_REST_SUCCESS = 'sn.user.password.reset.success'
17
+ PASSWORD_UPDATE = 'sn.user.password.update'
18
+ PASSWORD_REST_FAILURE = 'sn.user.password.reset.failure'
19
+ USER_INVITE = 'sn.user.invite'
20
+ ROLE_UPDATE = 'sn.user.role.update'
21
+ PROFILE_UPDATE = 'sn.user.profile.update'
22
+ PAGE_VIEW = 'sn.user.page.view'
23
+ VERIFY = 'sn.verify'
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module FailOverStrategy
5
+ FAIL_OPEN = 'fail-open'
6
+ FAIL_CLOSED = 'fail-closed'
7
+ end
8
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module Frameworks
5
+ class Hanami
6
+ SECURENATIVE_COOKIE = '_sn'
7
+
8
+ def self.get_client_token(request)
9
+ begin
10
+ request.env[SECURENATIVE_COOKIE]
11
+ rescue StandardError
12
+ begin
13
+ request.cookies[SECURENATIVE_COOKIE]
14
+ rescue StandardError
15
+ nil
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.get_url(request)
21
+ begin
22
+ request.env['REQUEST_PATH']
23
+ rescue StandardError
24
+ nil
25
+ end
26
+ end
27
+
28
+ def self.get_method(request)
29
+ begin
30
+ request.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
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module Frameworks
5
+ class Rails
6
+ SECURENATIVE_COOKIE = '_sn'
7
+
8
+ def self.get_client_token(request)
9
+ begin
10
+ request.cookies[SECURENATIVE_COOKIE]
11
+ rescue StandardError
12
+ nil
13
+ end
14
+ end
15
+
16
+ def self.get_url(request)
17
+ begin
18
+ # Rails >= 3.x
19
+ request.fullpath
20
+ rescue StandardError
21
+ begin
22
+ # Rails < 3.x & Sinatra
23
+ request.url if url.nil?
24
+ rescue StandardError
25
+ nil
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.get_method(request)
31
+ begin
32
+ request.method
33
+ rescue StandardError
34
+ nil
35
+ end
36
+ end
37
+
38
+ def self.get_headers(request)
39
+ begin
40
+ # Note: At the moment we're filtering out everything but user-agent since ruby's payload is way too big
41
+ {'user-agent' => request.env['HTTP_USER_AGENT']}
42
+ rescue StandardError
43
+ nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module Frameworks
5
+ class Sinatra
6
+ SECURENATIVE_COOKIE = '_sn'
7
+
8
+ def self.get_client_token(request)
9
+ begin
10
+ request.env[SECURENATIVE_COOKIE]
11
+ rescue StandardError
12
+ begin
13
+ request.cookies[SECURENATIVE_COOKIE]
14
+ rescue StandardError
15
+ nil
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.get_url(request)
21
+ begin
22
+ request.env['REQUEST_URI']
23
+ rescue StandardError
24
+ nil
25
+ end
26
+ end
27
+
28
+ def self.get_method(request)
29
+ begin
30
+ request.env['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
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class HttpClient
5
+ AUTHORIZATION_HEADER = 'Authorization'
6
+ VERSION_HEADER = 'SN-Version'
7
+ USER_AGENT_HEADER = 'User-Agent'
8
+ USER_AGENT_HEADER_VALUE = 'SecureNative-ruby'
9
+ CONTENT_TYPE_HEADER = 'Content-Type'
10
+ CONTENT_TYPE_HEADER_VALUE = 'application/json'
11
+
12
+ def initialize(securenative_options)
13
+ @options = securenative_options
14
+ end
15
+
16
+ def _headers
17
+ {
18
+ CONTENT_TYPE_HEADER => CONTENT_TYPE_HEADER_VALUE,
19
+ USER_AGENT_HEADER => USER_AGENT_HEADER_VALUE,
20
+ VERSION_HEADER => SecureNative::Utils::VersionUtils.version,
21
+ AUTHORIZATION_HEADER => @options.api_key
22
+ }
23
+ end
24
+
25
+ def post(path, body)
26
+ uri = URI.parse("#{@options.api_url}/#{path}")
27
+ headers = _headers
28
+
29
+ client = Net::HTTP.new(uri.host, uri.port)
30
+ client.read_timeout = @options.timeout
31
+ client.use_ssl = true
32
+ client.verify_mode = OpenSSL::SSL::VERIFY_NONE
33
+
34
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
35
+ request.body = body
36
+
37
+ res = nil
38
+ begin
39
+ res = client.request(request)
40
+ rescue StandardError => e
41
+ SecureNative::Log.error("Failed to send request; #{e}")
42
+ return res
43
+ end
44
+ res
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class HttpResponse
5
+ attr_reader :ok, :status_code, :body
6
+ attr_writer :ok, :status_code, :body
7
+
8
+ def initialize(ok, status_code, body)
9
+ @ok = ok
10
+ @status_code = status_code
11
+ @body = body
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class Options
5
+ attr_reader :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy, :proxy_headers
6
+ attr_writer :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy, :proxy_headers
7
+
8
+ def initialize(api_key: nil, api_url: "https://api.securenative.com/collector/api/v1", interval: 1000,
9
+ max_events: 1000, timeout: 1500, auto_send: true, disable: false, log_level: "FATAL",
10
+ fail_over_strategy: FailOverStrategy::FAIL_OPEN, proxy_headers: nil)
11
+ @api_key = api_key
12
+ @api_url = api_url
13
+ @interval = interval
14
+ @max_events = max_events
15
+ @timeout = timeout
16
+ @auto_send = auto_send
17
+ @disable = disable
18
+ @log_level = log_level
19
+ @fail_over_strategy = fail_over_strategy
20
+ @proxy_headers = proxy_headers
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class RequestContext
5
+ attr_reader :cid, :vid, :fp, :ip, :remote_ip, :headers, :url, :http_method
6
+ attr_writer :cid, :vid, :fp, :ip, :remote_ip, :headers, :url, :http_method
7
+
8
+ def initialize(cid: nil, vid: nil, fp: nil, ip: nil, remote_ip: nil, headers: nil, url: nil, http_method: nil)
9
+ @cid = cid
10
+ @vid = vid
11
+ @fp = fp
12
+ @ip = ip
13
+ @remote_ip = remote_ip
14
+ @headers = headers
15
+ @url = url
16
+ @method = http_method
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class RequestOptions
5
+ attr_reader :url, :body, :retry_sending
6
+ attr_writer :url, :body, :retry_sending
7
+
8
+ def initialize(url, body, retry_sending)
9
+ @url = url
10
+ @body = body
11
+ @retry_sending = retry_sending
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class SDKEvent
5
+ attr_reader :context, :rid, :event_type, :user_id, :user_traits, :request, :timestamp, :properties
6
+ attr_writer :context, :rid, :event_type, :user_id, :user_traits, :request, :timestamp, :properties
7
+
8
+ def initialize(event_options, securenative_options)
9
+ if event_options.user_id.nil? || event_options.user_id.length <= 0 || event_options.user_id == ''
10
+ raise SecureNativeInvalidOptionsError.new, 'Invalid event structure; User Id is missing'
11
+ end
12
+
13
+ if event_options.event.nil? || event_options.event.length <= 0 || event_options.event == ''
14
+ raise SecureNativeInvalidOptionsError.new, 'Invalid event structure; Event Type is missing'
15
+ end
16
+
17
+ @context = if !event_options.context.nil?
18
+ event_options.context
19
+ else
20
+ Context.default_context_builder
21
+ end
22
+
23
+ client_token = SecureNative::Utils::EncryptionUtils.decrypt(@context.client_token, securenative_options.api_key)
24
+
25
+ @rid = SecureRandom.uuid.to_str
26
+ @event_type = event_options.event
27
+ @user_id = event_options.user_id
28
+ @user_traits = event_options.user_traits
29
+ @request = RequestContext.new(cid: client_token ? client_token.cid : '', vid: client_token ? client_token.vid : '',
30
+ fp: client_token ? client_token.fp : '', ip: @context.ip,
31
+ remote_ip: @context.remote_ip, headers: @context.headers,
32
+ url: @context.url, http_method: @context.http_method)
33
+
34
+
35
+ @timestamp = SecureNative::Utils::DateUtils.to_timestamp(event_options.timestamp)
36
+ @properties = event_options.properties
37
+ end
38
+
39
+ def to_s
40
+ "context: #{@context}, rid: #{@rid}, event_type: #{@event_type}, user_id: #{@user_id},
41
+ user_traits: #{@user_traits}, request: #{@request}, timestamp: #{@timestamp}, properties: #{@properties}"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ class UserTraits
5
+ attr_reader :name, :email, :phone, :created_at
6
+ attr_writer :name, :email, :phone, :created_at
7
+
8
+ def initialize(name: nil, email: nil, phone: nil, created_at: nil)
9
+ @name = name
10
+ @email = email
11
+ @created_at = created_at
12
+ @phone = phone
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module Utils
5
+ class DateUtils
6
+ def self.to_timestamp(date)
7
+ return Time.now.utc.iso8601 if date.nil?
8
+
9
+ Time.parse(date).iso8601
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecureNative
4
+ module Utils
5
+ class EncryptionUtils
6
+ def self.padding_key(key, length)
7
+ if key.length == length
8
+ key
9
+ else
10
+ if key.length > length
11
+ key.slice(0, length)
12
+ else
13
+ (length - key.length).times { key << '0' }
14
+ key
15
+ end
16
+ end
17
+ end
18
+
19
+ def self.encrypt(plain_text, secret_key)
20
+ begin
21
+ cipher = OpenSSL::Cipher.new('aes-256-cbc')
22
+ cipher.encrypt
23
+ iv = cipher.random_iv
24
+ cipher.key = padding_key(secret_key, 32)
25
+ encrypted = cipher.update(plain_text) + cipher.final
26
+ (iv + encrypted).unpack1('H*')
27
+ rescue StandardError
28
+ ''
29
+ end
30
+ end
31
+
32
+ def self.decrypt(cipher_text, secret_key)
33
+ begin
34
+ cipher = OpenSSL::Cipher.new('aes-256-cbc')
35
+ cipher.decrypt
36
+ raw_data = [cipher_text].pack('H*')
37
+ cipher.iv = raw_data.slice(0, 16)
38
+ cipher.key = padding_key(secret_key, 32)
39
+ decrypted = JSON.parse(cipher.update(raw_data.slice(16, raw_data.length)) + cipher.final)
40
+
41
+ SecureNative::ClientToken.new(decrypted['cid'], decrypted['vid'], decrypted['fp'])
42
+ rescue StandardError
43
+ SecureNative::ClientToken.new('', '', '')
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end