castle-rb 4.1.0 → 6.0.0
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.
- checksums.yaml +4 -4
- data/README.md +158 -43
- data/lib/castle.rb +46 -21
- data/lib/castle/api.rb +24 -12
- data/lib/castle/api/approve_device.rb +25 -0
- data/lib/castle/api/authenticate.rb +34 -0
- data/lib/castle/api/end_impersonation.rb +29 -0
- data/lib/castle/api/get_device.rb +25 -0
- data/lib/castle/api/get_devices_for_user.rb +25 -0
- data/lib/castle/api/identify.rb +26 -0
- data/lib/castle/api/report_device.rb +25 -0
- data/lib/castle/api/review.rb +24 -0
- data/lib/castle/api/start_impersonation.rb +29 -0
- data/lib/castle/api/track.rb +26 -0
- data/lib/castle/client.rb +52 -45
- data/lib/castle/{extractors/client_id.rb → client_id/extract.rb} +2 -2
- data/lib/castle/commands/approve_device.rb +21 -0
- data/lib/castle/commands/authenticate.rb +13 -13
- data/lib/castle/commands/end_impersonation.rb +25 -0
- data/lib/castle/commands/get_device.rb +21 -0
- data/lib/castle/commands/get_devices_for_user.rb +21 -0
- data/lib/castle/commands/identify.rb +12 -13
- data/lib/castle/commands/report_device.rb +21 -0
- data/lib/castle/commands/review.rb +6 -3
- data/lib/castle/commands/start_impersonation.rb +25 -0
- data/lib/castle/commands/track.rb +12 -13
- data/lib/castle/configuration.rb +45 -28
- data/lib/castle/context/{default.rb → get_default.rb} +5 -6
- data/lib/castle/context/{merger.rb → merge.rb} +3 -3
- data/lib/castle/context/prepare.rb +18 -0
- data/lib/castle/context/{sanitizer.rb → sanitize.rb} +1 -1
- data/lib/castle/core/get_connection.rb +25 -0
- data/lib/castle/{api/response.rb → core/process_response.rb} +4 -2
- data/lib/castle/core/process_webhook.rb +20 -0
- data/lib/castle/core/send_request.rb +50 -0
- data/lib/castle/errors.rb +2 -0
- data/lib/castle/events.rb +1 -1
- data/lib/castle/failover/prepare_response.rb +23 -0
- data/lib/castle/failover/strategy.rb +20 -0
- data/lib/castle/headers/extract.rb +47 -0
- data/lib/castle/headers/filter.rb +37 -0
- data/lib/castle/headers/format.rb +24 -0
- data/lib/castle/ip/extract.rb +83 -0
- data/lib/castle/logger.rb +19 -0
- data/lib/castle/payload/prepare.rb +27 -0
- data/lib/castle/secure_mode.rb +6 -2
- data/lib/castle/session.rb +18 -0
- data/lib/castle/singleton_configuration.rb +9 -0
- data/lib/castle/utils/clean_invalid_chars.rb +24 -0
- data/lib/castle/utils/clone.rb +15 -0
- data/lib/castle/utils/deep_symbolize_keys.rb +45 -0
- data/lib/castle/utils/get_timestamp.rb +15 -0
- data/lib/castle/utils/{merger.rb → merge.rb} +3 -3
- data/lib/castle/utils/secure_compare.rb +22 -0
- data/lib/castle/validators/not_supported.rb +1 -0
- data/lib/castle/validators/present.rb +1 -0
- data/lib/castle/verdict.rb +13 -0
- data/lib/castle/version.rb +1 -1
- data/lib/castle/webhooks/verify.rb +43 -0
- data/spec/integration/rails/rails_spec.rb +33 -7
- data/spec/integration/rails/support/application.rb +3 -1
- data/spec/integration/rails/support/home_controller.rb +47 -5
- data/spec/lib/castle/api/approve_device_spec.rb +21 -0
- data/spec/lib/castle/api/authenticate_spec.rb +140 -0
- data/spec/lib/castle/api/end_impersonation_spec.rb +59 -0
- data/spec/lib/castle/api/get_device_spec.rb +19 -0
- data/spec/lib/castle/api/get_devices_for_user_spec.rb +19 -0
- data/spec/lib/castle/api/identify_spec.rb +68 -0
- data/spec/lib/castle/api/report_device_spec.rb +21 -0
- data/spec/lib/castle/{review_spec.rb → api/review_spec.rb} +3 -3
- data/spec/lib/castle/api/start_impersonation_spec.rb +59 -0
- data/spec/lib/castle/api/track_spec.rb +68 -0
- data/spec/lib/castle/api_spec.rb +16 -1
- data/spec/lib/castle/{extractors/client_id_spec.rb → client_id/extract_spec.rb} +2 -2
- data/spec/lib/castle/client_spec.rb +41 -23
- data/spec/lib/castle/commands/approve_device_spec.rb +24 -0
- data/spec/lib/castle/commands/authenticate_spec.rb +7 -16
- data/spec/lib/castle/commands/end_impersonation_spec.rb +82 -0
- data/spec/lib/castle/commands/get_device_spec.rb +24 -0
- data/spec/lib/castle/commands/get_devices_for_user_spec.rb +24 -0
- data/spec/lib/castle/commands/identify_spec.rb +5 -16
- data/spec/lib/castle/commands/report_device_spec.rb +24 -0
- data/spec/lib/castle/commands/review_spec.rb +1 -1
- data/spec/lib/castle/commands/{impersonate_spec.rb → start_impersonation_spec.rb} +9 -34
- data/spec/lib/castle/commands/track_spec.rb +5 -16
- data/spec/lib/castle/configuration_spec.rb +9 -138
- data/spec/lib/castle/context/{default_spec.rb → get_default_spec.rb} +1 -2
- data/spec/lib/castle/context/{merger_spec.rb → merge_spec.rb} +1 -1
- data/spec/lib/castle/context/prepare_spec.rb +44 -0
- data/spec/lib/castle/context/{sanitizer_spec.rb → sanitize_spec.rb} +1 -1
- data/spec/lib/castle/core/get_connection_spec.rb +59 -0
- data/spec/lib/castle/{api/response_spec.rb → core/process_response_spec.rb} +56 -1
- data/spec/lib/castle/core/process_webhook_spec.rb +46 -0
- data/spec/lib/castle/core/send_request_spec.rb +102 -0
- data/spec/lib/castle/failover/strategy_spec.rb +12 -0
- data/spec/lib/castle/{extractors/headers_spec.rb → headers/extract_spec.rb} +18 -18
- data/spec/lib/castle/{headers_filter_spec.rb → headers/filter_spec.rb} +6 -5
- data/spec/lib/castle/headers/format_spec.rb +25 -0
- data/spec/lib/castle/{extractors/ip_spec.rb → ip/extract_spec.rb} +35 -7
- data/spec/lib/castle/logger_spec.rb +42 -0
- data/spec/lib/castle/payload/prepare_spec.rb +54 -0
- data/spec/lib/castle/session_spec.rb +88 -0
- data/spec/lib/castle/singleton_configuration_spec.rb +18 -0
- data/spec/lib/castle/utils/clean_invalid_chars_spec.rb +69 -0
- data/spec/lib/castle/utils/{cloner_spec.rb → clone_spec.rb} +3 -3
- data/spec/lib/castle/utils/deep_symbolize_keys_spec.rb +50 -0
- data/spec/lib/castle/utils/{timestamp_spec.rb → get_timestamp_spec.rb} +1 -1
- data/spec/lib/castle/utils/{merger_spec.rb → merge_spec.rb} +3 -3
- data/spec/lib/castle/verdict_spec.rb +9 -0
- data/spec/lib/castle/webhooks/verify_spec.rb +69 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/shared_examples/configuration.rb +129 -0
- metadata +133 -56
- data/lib/castle/api/request.rb +0 -42
- data/lib/castle/api/session.rb +0 -39
- data/lib/castle/commands/impersonate.rb +0 -26
- data/lib/castle/extractors/headers.rb +0 -45
- data/lib/castle/extractors/ip.rb +0 -68
- data/lib/castle/failover_auth_response.rb +0 -21
- data/lib/castle/headers_filter.rb +0 -35
- data/lib/castle/headers_formatter.rb +0 -22
- data/lib/castle/review.rb +0 -11
- data/lib/castle/utils.rb +0 -55
- data/lib/castle/utils/cloner.rb +0 -11
- data/lib/castle/utils/timestamp.rb +0 -12
- data/spec/lib/castle/api/request_spec.rb +0 -72
- data/spec/lib/castle/headers_formatter_spec.rb +0 -25
- data/spec/lib/castle/utils_spec.rb +0 -156
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Castle
|
4
|
-
module
|
4
|
+
module Core
|
5
5
|
# parses api response
|
6
|
-
module
|
6
|
+
module ProcessResponse
|
7
7
|
RESPONSE_ERRORS = {
|
8
8
|
400 => Castle::BadRequestError,
|
9
9
|
401 => Castle::UnauthorizedError,
|
@@ -17,6 +17,8 @@ module Castle
|
|
17
17
|
def call(response)
|
18
18
|
verify!(response)
|
19
19
|
|
20
|
+
Castle::Logger.call('response:', response.body.to_s)
|
21
|
+
|
20
22
|
return {} if response.body.nil? || response.body.empty?
|
21
23
|
|
22
24
|
begin
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Core
|
5
|
+
# Parses a webhook
|
6
|
+
module ProcessWebhook
|
7
|
+
class << self
|
8
|
+
# Checks if webhook is valid
|
9
|
+
# @param webhook [Request]
|
10
|
+
def call(webhook)
|
11
|
+
webhook.body.read.tap do |result|
|
12
|
+
raise Castle::ApiError, 'Invalid webhook from Castle API' if result.blank?
|
13
|
+
|
14
|
+
Castle::Logger.call('webhook:', result.to_s)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Core
|
5
|
+
# this class is responsible for making requests to api
|
6
|
+
module SendRequest
|
7
|
+
# Default headers that we add to passed ones
|
8
|
+
DEFAULT_HEADERS = {
|
9
|
+
'Content-Type' => 'application/json'
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
private_constant :DEFAULT_HEADERS
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# @param command [String]
|
16
|
+
# @param headers [Hash]
|
17
|
+
# @param http [Net::HTTP]
|
18
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
19
|
+
def call(command, headers, http = nil, config = Castle.config)
|
20
|
+
(http || Castle::Core::GetConnection.call).request(
|
21
|
+
build(
|
22
|
+
command,
|
23
|
+
headers.merge(DEFAULT_HEADERS),
|
24
|
+
config
|
25
|
+
)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param command [String]
|
30
|
+
# @param headers [Hash]
|
31
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
32
|
+
def build(command, headers, config = Castle.config)
|
33
|
+
url = "#{config.base_url.path}/#{command.path}"
|
34
|
+
request_obj = Net::HTTP.const_get(command.method.to_s.capitalize).new(url, headers)
|
35
|
+
|
36
|
+
unless command.method == :get
|
37
|
+
request_obj.body = ::Castle::Utils::CleanInvalidChars.call(
|
38
|
+
command.data
|
39
|
+
).to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
Castle::Logger.call("#{url}:", request_obj.body)
|
43
|
+
|
44
|
+
request_obj.basic_auth('', config.api_secret)
|
45
|
+
request_obj
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/castle/errors.rb
CHANGED
@@ -20,6 +20,8 @@ module Castle
|
|
20
20
|
class ConfigurationError < Castle::Error; end
|
21
21
|
# error returned by api
|
22
22
|
class ApiError < Castle::Error; end
|
23
|
+
# webhook signature verification error
|
24
|
+
class WebhookVerificationError < Castle::Error; end
|
23
25
|
|
24
26
|
# api error bad request 400
|
25
27
|
class BadRequestError < Castle::ApiError; end
|
data/lib/castle/events.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Castle
|
4
4
|
# list of events based on https://docs.castle.io/api_reference/#list-of-recognized-events
|
5
5
|
module Events
|
6
|
-
# Record when a user
|
6
|
+
# Record when a user successfully logs in.
|
7
7
|
LOGIN_SUCCEEDED = '$login.succeeded'
|
8
8
|
# Record when a user failed to log in.
|
9
9
|
LOGIN_FAILED = '$login.failed'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Failover
|
5
|
+
# generate failover authentication response
|
6
|
+
class PrepareResponse
|
7
|
+
def initialize(user_id, reason:, strategy: Castle.config.failover_strategy)
|
8
|
+
@strategy = strategy
|
9
|
+
@reason = reason
|
10
|
+
@user_id = user_id
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
{
|
15
|
+
action: @strategy.to_s,
|
16
|
+
user_id: @user_id,
|
17
|
+
failover: true,
|
18
|
+
failover_reason: @reason
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Failover
|
5
|
+
# handles failover strategy consts
|
6
|
+
module Strategy
|
7
|
+
# allow
|
8
|
+
ALLOW = :allow
|
9
|
+
# deny
|
10
|
+
DENY = :deny
|
11
|
+
# challenge
|
12
|
+
CHALLENGE = :challenge
|
13
|
+
# throw an error
|
14
|
+
THROW = :throw
|
15
|
+
end
|
16
|
+
|
17
|
+
# list of possible strategies
|
18
|
+
STRATEGIES = %i[allow deny challenge throw].freeze
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Headers
|
5
|
+
# used for extraction of cookies and headers from the request
|
6
|
+
class Extract
|
7
|
+
# Headers that we will never scrub, even if they land on the configuration denylist.
|
8
|
+
ALWAYS_ALLOWLISTED = %w[User-Agent].freeze
|
9
|
+
|
10
|
+
# Headers that will always be scrubbed, even if allowlisted.
|
11
|
+
ALWAYS_DENYLISTED = %w[Cookie Authorization].freeze
|
12
|
+
|
13
|
+
private_constant :ALWAYS_ALLOWLISTED, :ALWAYS_DENYLISTED
|
14
|
+
|
15
|
+
# @param headers [Hash]
|
16
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
17
|
+
def initialize(headers, config = Castle.config)
|
18
|
+
@headers = headers
|
19
|
+
@config = config
|
20
|
+
@no_allowlist = config.allowlisted.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Serialize HTTP headers
|
24
|
+
# @return [Hash]
|
25
|
+
def call
|
26
|
+
@headers.each_with_object({}) do |(name, value), acc|
|
27
|
+
acc[name] = header_value(name, value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# scrub header value
|
34
|
+
# @param name [String]
|
35
|
+
# @param value [String]
|
36
|
+
# @return [TrueClass | FalseClass | String]
|
37
|
+
def header_value(name, value)
|
38
|
+
return true if ALWAYS_DENYLISTED.include?(name)
|
39
|
+
return value if ALWAYS_ALLOWLISTED.include?(name)
|
40
|
+
return true if @config.denylisted.include?(name)
|
41
|
+
return value if @no_allowlist || @config.allowlisted.include?(name)
|
42
|
+
|
43
|
+
true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Headers
|
5
|
+
# used for preparing valuable headers list
|
6
|
+
class Filter
|
7
|
+
# headers filter
|
8
|
+
# HTTP_ - this is how Rack prefixes incoming HTTP headers
|
9
|
+
# CONTENT_LENGTH - for responses without Content-Length or Transfer-Encoding header
|
10
|
+
# REMOTE_ADDR - ip address header returned by web server
|
11
|
+
VALUABLE_HEADERS = /^
|
12
|
+
HTTP(?:_|-).*|
|
13
|
+
CONTENT(?:_|-)LENGTH|
|
14
|
+
REMOTE(?:_|-)ADDR
|
15
|
+
$/xi.freeze
|
16
|
+
|
17
|
+
private_constant :VALUABLE_HEADERS
|
18
|
+
|
19
|
+
# @param request [Rack::Request]
|
20
|
+
def initialize(request)
|
21
|
+
@request_env = request.env
|
22
|
+
@header_format = Castle::Headers::Format
|
23
|
+
end
|
24
|
+
|
25
|
+
# Serialize HTTP headers
|
26
|
+
# @return [Hash]
|
27
|
+
def call
|
28
|
+
@request_env.keys.each_with_object({}) do |header_name, acc|
|
29
|
+
next unless header_name.match(VALUABLE_HEADERS)
|
30
|
+
|
31
|
+
formatted_name = @header_format.call(header_name)
|
32
|
+
acc[formatted_name] = @request_env[header_name]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Headers
|
5
|
+
# formats header name
|
6
|
+
class Format
|
7
|
+
class << self
|
8
|
+
# @param header [String]
|
9
|
+
# @return [String]
|
10
|
+
def call(header)
|
11
|
+
format(header.to_s.gsub(/^HTTP(?:_|-)/i, ''))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# @param header [String]
|
17
|
+
# @return [String]
|
18
|
+
def format(header)
|
19
|
+
header.split(/_|-/).map(&:capitalize).join('-')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module IP
|
5
|
+
# used for extraction of ip from the request
|
6
|
+
class Extract
|
7
|
+
# ordered list of ip headers for ip extraction
|
8
|
+
DEFAULT = %w[X-Forwarded-For Remote-Addr].freeze
|
9
|
+
# list of header which are used with proxy depth setting
|
10
|
+
DEPTH_RELATED = %w[X-Forwarded-For].freeze
|
11
|
+
|
12
|
+
private_constant :DEFAULT
|
13
|
+
|
14
|
+
# @param headers [Hash]
|
15
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
16
|
+
def initialize(headers, config = Castle.config)
|
17
|
+
@headers = headers
|
18
|
+
@ip_headers = config.ip_headers.empty? ? DEFAULT : config.ip_headers
|
19
|
+
@proxies = config.trusted_proxies + Castle::Configuration::TRUSTED_PROXIES
|
20
|
+
@trust_proxy_chain = config.trust_proxy_chain
|
21
|
+
@trusted_proxy_depth = config.trusted_proxy_depth
|
22
|
+
end
|
23
|
+
|
24
|
+
# Order of headers:
|
25
|
+
# .... list of headers defined by ip_headers
|
26
|
+
# X-Forwarded-For
|
27
|
+
# Remote-Addr
|
28
|
+
# @return [String]
|
29
|
+
def call
|
30
|
+
all_ips = []
|
31
|
+
|
32
|
+
@ip_headers.each do |ip_header|
|
33
|
+
ips = ips_from(ip_header)
|
34
|
+
ip_value = remove_proxies(ips)
|
35
|
+
|
36
|
+
return ip_value if ip_value
|
37
|
+
|
38
|
+
all_ips.push(*ips)
|
39
|
+
end
|
40
|
+
|
41
|
+
# fallback to first listed ip
|
42
|
+
all_ips.first
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @param ips [Array<String>]
|
48
|
+
# @return [Array<String>]
|
49
|
+
def remove_proxies(ips)
|
50
|
+
return ips.first if @trust_proxy_chain
|
51
|
+
|
52
|
+
ips.reject { |ip| proxy?(ip) }.last
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param ip [String]
|
56
|
+
# @return [Boolean]
|
57
|
+
def proxy?(ip)
|
58
|
+
@proxies.any? { |proxy| proxy.match(ip) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param header [String]
|
62
|
+
# @return [Array<String>]
|
63
|
+
def ips_from(header)
|
64
|
+
value = @headers[header]
|
65
|
+
|
66
|
+
return [] unless value
|
67
|
+
|
68
|
+
ips = value.strip.split(/[,\s]+/)
|
69
|
+
|
70
|
+
limit_proxy_depth(ips, header)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param ips [Array<String>]
|
74
|
+
# @param ip_header [String]
|
75
|
+
# @return [Array<String>]
|
76
|
+
def limit_proxy_depth(ips, ip_header)
|
77
|
+
ips.pop(@trusted_proxy_depth) if DEPTH_RELATED.include?(ip_header)
|
78
|
+
|
79
|
+
ips
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
# module for logger handling
|
5
|
+
module Logger
|
6
|
+
class << self
|
7
|
+
# @param message [String]
|
8
|
+
# @param data [String]
|
9
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
10
|
+
def call(message, data = nil, config = Castle.config)
|
11
|
+
logger = config.logger
|
12
|
+
|
13
|
+
return unless logger
|
14
|
+
|
15
|
+
logger.info("[CASTLE] #{message} #{data}".strip)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Payload
|
5
|
+
# this prepare payload based on the request
|
6
|
+
module Prepare
|
7
|
+
class << self
|
8
|
+
# @param payload_options [Hash]
|
9
|
+
# @param request [Request]
|
10
|
+
# @param options [Hash] required for context preparation
|
11
|
+
# @return [Hash]
|
12
|
+
def call(payload_options, request, options = {})
|
13
|
+
context = Castle::Context::Prepare.call(request, payload_options.merge(options))
|
14
|
+
|
15
|
+
payload = Castle::Utils::DeepSymbolizeKeys.call(
|
16
|
+
payload_options || {}
|
17
|
+
).merge(context: context)
|
18
|
+
payload[:timestamp] ||= Castle::Utils::GetTimestamp.call
|
19
|
+
|
20
|
+
warn '[DEPRECATION] use user_traits instead of traits key' if payload.key?(:traits)
|
21
|
+
|
22
|
+
payload
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/castle/secure_mode.rb
CHANGED
@@ -4,8 +4,12 @@ require 'openssl'
|
|
4
4
|
|
5
5
|
module Castle
|
6
6
|
module SecureMode
|
7
|
-
|
8
|
-
|
7
|
+
class << self
|
8
|
+
# @param user_id [String]
|
9
|
+
# @param config [Castle::Configuration, Castle::SingletonConfiguration]
|
10
|
+
def signature(user_id, config = Castle.config)
|
11
|
+
OpenSSL::HMAC.hexdigest('sha256', config.api_secret, user_id.to_s)
|
12
|
+
end
|
9
13
|
end
|
10
14
|
end
|
11
15
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
# this module uses the Connection object
|
5
|
+
# and provides start method for persistent connection usage
|
6
|
+
# when there is a need of sending multiple requests at once
|
7
|
+
module Session
|
8
|
+
HTTPS_SCHEME = 'https'
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def call(&block)
|
12
|
+
return unless block_given?
|
13
|
+
|
14
|
+
Castle::Core::GetConnection.call.start(&block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|