securenative 0.1.29 → 0.1.34

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) 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/README.md +2 -2
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/lib/securenative/api_manager.rb +34 -0
  16. data/lib/securenative/client.rb +79 -0
  17. data/lib/securenative/client_token.rb +14 -0
  18. data/lib/securenative/config/configuration_builder.rb +29 -0
  19. data/lib/securenative/config/configuration_manager.rb +57 -0
  20. data/lib/securenative/context.rb +65 -0
  21. data/lib/securenative/device.rb +12 -0
  22. data/lib/securenative/enums/api_route.rb +10 -0
  23. data/lib/securenative/enums/risk_level.rb +11 -0
  24. data/lib/securenative/errors/config_error.rb +4 -0
  25. data/lib/securenative/errors/http_error.rb +4 -0
  26. data/lib/securenative/errors/invalid_options_error.rb +4 -0
  27. data/lib/securenative/errors/invalid_uri_error.rb +6 -0
  28. data/lib/securenative/errors/parse_error.rb +4 -0
  29. data/lib/securenative/errors/sdk_Illegal_state_error.rb +4 -0
  30. data/lib/securenative/errors/sdk_error.rb +4 -0
  31. data/lib/securenative/event_manager.rb +156 -0
  32. data/lib/securenative/event_options.rb +35 -0
  33. data/lib/securenative/event_types.rb +25 -0
  34. data/lib/securenative/failover_strategy.rb +8 -0
  35. data/lib/securenative/frameworks/hanami.rb +49 -0
  36. data/lib/securenative/frameworks/rails.rb +51 -0
  37. data/lib/securenative/frameworks/sinatra.rb +49 -0
  38. data/lib/securenative/http_client.rb +47 -0
  39. data/lib/securenative/http_response.rb +14 -0
  40. data/lib/securenative/options.rb +23 -0
  41. data/lib/securenative/request_context.rb +20 -0
  42. data/lib/securenative/request_options.rb +14 -0
  43. data/lib/securenative/sdk_event.rb +44 -0
  44. data/lib/securenative/user_traits.rb +15 -0
  45. data/lib/securenative/utils/date_utils.rb +13 -0
  46. data/lib/securenative/utils/encryption_utils.rb +48 -0
  47. data/lib/securenative/utils/ip_utils.rb +25 -0
  48. data/lib/securenative/utils/log.rb +46 -0
  49. data/lib/securenative/utils/request_utils.rb +101 -0
  50. data/lib/securenative/utils/signature_utils.rb +18 -0
  51. data/lib/securenative/utils/utils.rb +13 -0
  52. data/lib/securenative/utils/version_utils.rb +15 -0
  53. data/lib/securenative/verify_result.rb +18 -0
  54. data/lib/securenative/version.rb +5 -0
  55. data/securenative.gemspec +33 -0
  56. metadata +56 -3
@@ -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,49 @@
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
+ headers = []
39
+ request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header|
40
+ headers.append(header[0].downcase.gsub("http_", "").gsub("_", "-"))
41
+ }
42
+ return headers
43
+ rescue StandardError
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,51 @@
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
+ headers = []
41
+ request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header|
42
+ headers.append(header[0].downcase.gsub("http_", "").gsub("_", "-"))
43
+ }
44
+ return headers
45
+ rescue StandardError
46
+ nil
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,49 @@
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
+ headers = []
39
+ request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header|
40
+ headers.append(header[0].downcase.gsub("http_", "").gsub("_", "-"))
41
+ }
42
+ return headers
43
+ rescue StandardError
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
49
+ 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