securenative 0.1.29 → 0.1.34
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +49 -0
- data/.github/workflows/publish.yml +60 -0
- data/.github/workflows/test.yml +48 -0
- data/.gitignore +40 -0
- data/.rakeTasks +7 -0
- data/.rspec +3 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +270 -0
- data/LICENSE +21 -0
- data/README.md +2 -2
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/securenative/api_manager.rb +34 -0
- data/lib/securenative/client.rb +79 -0
- data/lib/securenative/client_token.rb +14 -0
- data/lib/securenative/config/configuration_builder.rb +29 -0
- data/lib/securenative/config/configuration_manager.rb +57 -0
- data/lib/securenative/context.rb +65 -0
- data/lib/securenative/device.rb +12 -0
- data/lib/securenative/enums/api_route.rb +10 -0
- data/lib/securenative/enums/risk_level.rb +11 -0
- data/lib/securenative/errors/config_error.rb +4 -0
- data/lib/securenative/errors/http_error.rb +4 -0
- data/lib/securenative/errors/invalid_options_error.rb +4 -0
- data/lib/securenative/errors/invalid_uri_error.rb +6 -0
- data/lib/securenative/errors/parse_error.rb +4 -0
- data/lib/securenative/errors/sdk_Illegal_state_error.rb +4 -0
- data/lib/securenative/errors/sdk_error.rb +4 -0
- data/lib/securenative/event_manager.rb +156 -0
- data/lib/securenative/event_options.rb +35 -0
- data/lib/securenative/event_types.rb +25 -0
- data/lib/securenative/failover_strategy.rb +8 -0
- data/lib/securenative/frameworks/hanami.rb +49 -0
- data/lib/securenative/frameworks/rails.rb +51 -0
- data/lib/securenative/frameworks/sinatra.rb +49 -0
- data/lib/securenative/http_client.rb +47 -0
- data/lib/securenative/http_response.rb +14 -0
- data/lib/securenative/options.rb +23 -0
- data/lib/securenative/request_context.rb +20 -0
- data/lib/securenative/request_options.rb +14 -0
- data/lib/securenative/sdk_event.rb +44 -0
- data/lib/securenative/user_traits.rb +15 -0
- data/lib/securenative/utils/date_utils.rb +13 -0
- data/lib/securenative/utils/encryption_utils.rb +48 -0
- data/lib/securenative/utils/ip_utils.rb +25 -0
- data/lib/securenative/utils/log.rb +46 -0
- data/lib/securenative/utils/request_utils.rb +101 -0
- data/lib/securenative/utils/signature_utils.rb +18 -0
- data/lib/securenative/utils/utils.rb +13 -0
- data/lib/securenative/utils/version_utils.rb +15 -0
- data/lib/securenative/verify_result.rb +18 -0
- data/lib/securenative/version.rb +5 -0
- data/securenative.gemspec +33 -0
- 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,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,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
|