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.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +158 -43
  3. data/lib/castle.rb +46 -21
  4. data/lib/castle/api.rb +24 -12
  5. data/lib/castle/api/approve_device.rb +25 -0
  6. data/lib/castle/api/authenticate.rb +34 -0
  7. data/lib/castle/api/end_impersonation.rb +29 -0
  8. data/lib/castle/api/get_device.rb +25 -0
  9. data/lib/castle/api/get_devices_for_user.rb +25 -0
  10. data/lib/castle/api/identify.rb +26 -0
  11. data/lib/castle/api/report_device.rb +25 -0
  12. data/lib/castle/api/review.rb +24 -0
  13. data/lib/castle/api/start_impersonation.rb +29 -0
  14. data/lib/castle/api/track.rb +26 -0
  15. data/lib/castle/client.rb +52 -45
  16. data/lib/castle/{extractors/client_id.rb → client_id/extract.rb} +2 -2
  17. data/lib/castle/commands/approve_device.rb +21 -0
  18. data/lib/castle/commands/authenticate.rb +13 -13
  19. data/lib/castle/commands/end_impersonation.rb +25 -0
  20. data/lib/castle/commands/get_device.rb +21 -0
  21. data/lib/castle/commands/get_devices_for_user.rb +21 -0
  22. data/lib/castle/commands/identify.rb +12 -13
  23. data/lib/castle/commands/report_device.rb +21 -0
  24. data/lib/castle/commands/review.rb +6 -3
  25. data/lib/castle/commands/start_impersonation.rb +25 -0
  26. data/lib/castle/commands/track.rb +12 -13
  27. data/lib/castle/configuration.rb +45 -28
  28. data/lib/castle/context/{default.rb → get_default.rb} +5 -6
  29. data/lib/castle/context/{merger.rb → merge.rb} +3 -3
  30. data/lib/castle/context/prepare.rb +18 -0
  31. data/lib/castle/context/{sanitizer.rb → sanitize.rb} +1 -1
  32. data/lib/castle/core/get_connection.rb +25 -0
  33. data/lib/castle/{api/response.rb → core/process_response.rb} +4 -2
  34. data/lib/castle/core/process_webhook.rb +20 -0
  35. data/lib/castle/core/send_request.rb +50 -0
  36. data/lib/castle/errors.rb +2 -0
  37. data/lib/castle/events.rb +1 -1
  38. data/lib/castle/failover/prepare_response.rb +23 -0
  39. data/lib/castle/failover/strategy.rb +20 -0
  40. data/lib/castle/headers/extract.rb +47 -0
  41. data/lib/castle/headers/filter.rb +37 -0
  42. data/lib/castle/headers/format.rb +24 -0
  43. data/lib/castle/ip/extract.rb +83 -0
  44. data/lib/castle/logger.rb +19 -0
  45. data/lib/castle/payload/prepare.rb +27 -0
  46. data/lib/castle/secure_mode.rb +6 -2
  47. data/lib/castle/session.rb +18 -0
  48. data/lib/castle/singleton_configuration.rb +9 -0
  49. data/lib/castle/utils/clean_invalid_chars.rb +24 -0
  50. data/lib/castle/utils/clone.rb +15 -0
  51. data/lib/castle/utils/deep_symbolize_keys.rb +45 -0
  52. data/lib/castle/utils/get_timestamp.rb +15 -0
  53. data/lib/castle/utils/{merger.rb → merge.rb} +3 -3
  54. data/lib/castle/utils/secure_compare.rb +22 -0
  55. data/lib/castle/validators/not_supported.rb +1 -0
  56. data/lib/castle/validators/present.rb +1 -0
  57. data/lib/castle/verdict.rb +13 -0
  58. data/lib/castle/version.rb +1 -1
  59. data/lib/castle/webhooks/verify.rb +43 -0
  60. data/spec/integration/rails/rails_spec.rb +33 -7
  61. data/spec/integration/rails/support/application.rb +3 -1
  62. data/spec/integration/rails/support/home_controller.rb +47 -5
  63. data/spec/lib/castle/api/approve_device_spec.rb +21 -0
  64. data/spec/lib/castle/api/authenticate_spec.rb +140 -0
  65. data/spec/lib/castle/api/end_impersonation_spec.rb +59 -0
  66. data/spec/lib/castle/api/get_device_spec.rb +19 -0
  67. data/spec/lib/castle/api/get_devices_for_user_spec.rb +19 -0
  68. data/spec/lib/castle/api/identify_spec.rb +68 -0
  69. data/spec/lib/castle/api/report_device_spec.rb +21 -0
  70. data/spec/lib/castle/{review_spec.rb → api/review_spec.rb} +3 -3
  71. data/spec/lib/castle/api/start_impersonation_spec.rb +59 -0
  72. data/spec/lib/castle/api/track_spec.rb +68 -0
  73. data/spec/lib/castle/api_spec.rb +16 -1
  74. data/spec/lib/castle/{extractors/client_id_spec.rb → client_id/extract_spec.rb} +2 -2
  75. data/spec/lib/castle/client_spec.rb +41 -23
  76. data/spec/lib/castle/commands/approve_device_spec.rb +24 -0
  77. data/spec/lib/castle/commands/authenticate_spec.rb +7 -16
  78. data/spec/lib/castle/commands/end_impersonation_spec.rb +82 -0
  79. data/spec/lib/castle/commands/get_device_spec.rb +24 -0
  80. data/spec/lib/castle/commands/get_devices_for_user_spec.rb +24 -0
  81. data/spec/lib/castle/commands/identify_spec.rb +5 -16
  82. data/spec/lib/castle/commands/report_device_spec.rb +24 -0
  83. data/spec/lib/castle/commands/review_spec.rb +1 -1
  84. data/spec/lib/castle/commands/{impersonate_spec.rb → start_impersonation_spec.rb} +9 -34
  85. data/spec/lib/castle/commands/track_spec.rb +5 -16
  86. data/spec/lib/castle/configuration_spec.rb +9 -138
  87. data/spec/lib/castle/context/{default_spec.rb → get_default_spec.rb} +1 -2
  88. data/spec/lib/castle/context/{merger_spec.rb → merge_spec.rb} +1 -1
  89. data/spec/lib/castle/context/prepare_spec.rb +44 -0
  90. data/spec/lib/castle/context/{sanitizer_spec.rb → sanitize_spec.rb} +1 -1
  91. data/spec/lib/castle/core/get_connection_spec.rb +59 -0
  92. data/spec/lib/castle/{api/response_spec.rb → core/process_response_spec.rb} +56 -1
  93. data/spec/lib/castle/core/process_webhook_spec.rb +46 -0
  94. data/spec/lib/castle/core/send_request_spec.rb +102 -0
  95. data/spec/lib/castle/failover/strategy_spec.rb +12 -0
  96. data/spec/lib/castle/{extractors/headers_spec.rb → headers/extract_spec.rb} +18 -18
  97. data/spec/lib/castle/{headers_filter_spec.rb → headers/filter_spec.rb} +6 -5
  98. data/spec/lib/castle/headers/format_spec.rb +25 -0
  99. data/spec/lib/castle/{extractors/ip_spec.rb → ip/extract_spec.rb} +35 -7
  100. data/spec/lib/castle/logger_spec.rb +42 -0
  101. data/spec/lib/castle/payload/prepare_spec.rb +54 -0
  102. data/spec/lib/castle/session_spec.rb +88 -0
  103. data/spec/lib/castle/singleton_configuration_spec.rb +18 -0
  104. data/spec/lib/castle/utils/clean_invalid_chars_spec.rb +69 -0
  105. data/spec/lib/castle/utils/{cloner_spec.rb → clone_spec.rb} +3 -3
  106. data/spec/lib/castle/utils/deep_symbolize_keys_spec.rb +50 -0
  107. data/spec/lib/castle/utils/{timestamp_spec.rb → get_timestamp_spec.rb} +1 -1
  108. data/spec/lib/castle/utils/{merger_spec.rb → merge_spec.rb} +3 -3
  109. data/spec/lib/castle/verdict_spec.rb +9 -0
  110. data/spec/lib/castle/webhooks/verify_spec.rb +69 -0
  111. data/spec/spec_helper.rb +2 -0
  112. data/spec/support/shared_examples/configuration.rb +129 -0
  113. metadata +133 -56
  114. data/lib/castle/api/request.rb +0 -42
  115. data/lib/castle/api/session.rb +0 -39
  116. data/lib/castle/commands/impersonate.rb +0 -26
  117. data/lib/castle/extractors/headers.rb +0 -45
  118. data/lib/castle/extractors/ip.rb +0 -68
  119. data/lib/castle/failover_auth_response.rb +0 -21
  120. data/lib/castle/headers_filter.rb +0 -35
  121. data/lib/castle/headers_formatter.rb +0 -22
  122. data/lib/castle/review.rb +0 -11
  123. data/lib/castle/utils.rb +0 -55
  124. data/lib/castle/utils/cloner.rb +0 -11
  125. data/lib/castle/utils/timestamp.rb +0 -12
  126. data/spec/lib/castle/api/request_spec.rb +0 -72
  127. data/spec/lib/castle/headers_formatter_spec.rb +0 -25
  128. data/spec/lib/castle/utils_spec.rb +0 -156
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castle
4
+ module Commands
5
+ # builder for impersonate command
6
+ class EndImpersonation
7
+ class << self
8
+ # @param options [Hash]
9
+ # @return [Castle::Command]
10
+ def build(options = {})
11
+ Castle::Validators::Present.call(options, %i[user_id])
12
+ context = Castle::Context::Sanitize.call(options[:context])
13
+
14
+ Castle::Validators::Present.call(context, %i[user_agent ip])
15
+
16
+ Castle::Command.new(
17
+ 'impersonate',
18
+ options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call),
19
+ :delete
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castle
4
+ module Commands
5
+ # Generates the payload for the GET devices/#{device_token} request
6
+ class GetDevice
7
+ class << self
8
+ # @param options [Hash]
9
+ # @return [Castle::Command]
10
+ def build(options = {})
11
+ Castle::Validators::Present.call(options, %i[device_token])
12
+ Castle::Command.new(
13
+ "devices/#{options[:device_token]}",
14
+ nil,
15
+ :get
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castle
4
+ module Commands
5
+ # Generates the payload for the GET users/#{user_id}/devices request
6
+ class GetDevicesForUser
7
+ class << self
8
+ # @param options [Hash]
9
+ # @return [Castle::Command]
10
+ def build(options = {})
11
+ Castle::Validators::Present.call(options, %i[user_id])
12
+ Castle::Command.new(
13
+ "users/#{options[:user_id]}/devices",
14
+ nil,
15
+ :get
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -3,20 +3,19 @@
3
3
  module Castle
4
4
  module Commands
5
5
  class Identify
6
- def initialize(context)
7
- @context = context
8
- end
9
-
10
- def build(options = {})
11
- Castle::Validators::NotSupported.call(options, %i[properties])
12
- context = Castle::Context::Merger.call(@context, options[:context])
13
- context = Castle::Context::Sanitizer.call(context)
6
+ class << self
7
+ # @param options [Hash]
8
+ # @return [Castle::Command]
9
+ def build(options = {})
10
+ Castle::Validators::NotSupported.call(options, %i[properties])
11
+ context = Castle::Context::Sanitize.call(options[:context])
14
12
 
15
- Castle::Command.new(
16
- 'identify',
17
- options.merge(context: context, sent_at: Castle::Utils::Timestamp.call),
18
- :post
19
- )
13
+ Castle::Command.new(
14
+ 'identify',
15
+ options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call),
16
+ :post
17
+ )
18
+ end
20
19
  end
21
20
  end
22
21
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castle
4
+ module Commands
5
+ # Generates the payload for the PUT devices/#{device_token}/report request
6
+ class ReportDevice
7
+ class << self
8
+ # @param options [Hash]
9
+ # @return [Castle::Command]
10
+ def build(options = {})
11
+ Castle::Validators::Present.call(options, %i[device_token])
12
+ Castle::Command.new(
13
+ "devices/#{options[:device_token]}/report",
14
+ nil,
15
+ :put
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -2,11 +2,14 @@
2
2
 
3
3
  module Castle
4
4
  module Commands
5
+ # Generates the payload for the GET reviews/#{review_id} request
5
6
  class Review
6
7
  class << self
7
- def build(review_id)
8
- Castle::Validators::Present.call({ review_id: review_id }, %i[review_id])
9
- Castle::Command.new("reviews/#{review_id}", nil, :get)
8
+ # @param options [Hash]
9
+ # @return [Castle::Command]
10
+ def build(options = {})
11
+ Castle::Validators::Present.call(options, %i[review_id])
12
+ Castle::Command.new("reviews/#{options[:review_id]}", nil, :get)
10
13
  end
11
14
  end
12
15
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castle
4
+ module Commands
5
+ # builder for impersonate command
6
+ class StartImpersonation
7
+ class << self
8
+ # @param options [Hash]
9
+ # @return [Castle::Command]
10
+ def build(options = {})
11
+ Castle::Validators::Present.call(options, %i[user_id])
12
+ context = Castle::Context::Sanitize.call(options[:context])
13
+
14
+ Castle::Validators::Present.call(context, %i[user_agent ip])
15
+
16
+ Castle::Command.new(
17
+ 'impersonate',
18
+ options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call),
19
+ :post
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,20 +3,19 @@
3
3
  module Castle
4
4
  module Commands
5
5
  class Track
6
- def initialize(context)
7
- @context = context
8
- end
9
-
10
- def build(options = {})
11
- Castle::Validators::Present.call(options, %i[event])
12
- context = Castle::Context::Merger.call(@context, options[:context])
13
- context = Castle::Context::Sanitizer.call(context)
6
+ class << self
7
+ # @param options [Hash]
8
+ # @return [Castle::Command]
9
+ def build(options = {})
10
+ Castle::Validators::Present.call(options, %i[event])
11
+ context = Castle::Context::Sanitize.call(options[:context])
14
12
 
15
- Castle::Command.new(
16
- 'track',
17
- options.merge(context: context, sent_at: Castle::Utils::Timestamp.call),
18
- :post
19
- )
13
+ Castle::Command.new(
14
+ 'track',
15
+ options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call),
16
+ :post
17
+ )
18
+ end
20
19
  end
21
20
  end
22
21
  end
@@ -1,16 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'uri'
4
+
3
5
  module Castle
4
6
  # manages configuration variables
5
7
  class Configuration
6
- include Singleton
7
-
8
- HOST = 'api.castle.io'
9
- PORT = 443
10
- URL_PREFIX = '/v1'
11
- FAILOVER_STRATEGY = :allow
12
- REQUEST_TIMEOUT = 500 # in milliseconds
13
- FAILOVER_STRATEGIES = %i[allow deny challenge throw].freeze
8
+ # API endpoint
9
+ BASE_URL = 'https://api.castle.io/v1'
10
+ REQUEST_TIMEOUT = 1000 # in milliseconds
14
11
  # regexp of trusted proxies which is always appended to the trusted proxy list
15
12
  TRUSTED_PROXIES = [/
16
13
  \A127\.0\.0\.1\Z|
@@ -21,9 +18,9 @@ module Castle
21
18
  \Aunix:
22
19
  /ix].freeze
23
20
 
24
- # @note this value is not assigned as we don't recommend using a whitelist. If you need to use
21
+ # @note this value is not assigned as we don't recommend using a allowlist. If you need to use
25
22
  # one, this constant is provided as a good default.
26
- DEFAULT_WHITELIST = %w[
23
+ DEFAULT_ALLOWLIST = %w[
27
24
  Accept
28
25
  Accept-Charset
29
26
  Accept-Datetime
@@ -33,46 +30,59 @@ module Castle
33
30
  Connection
34
31
  Content-Length
35
32
  Content-Type
33
+ Dnt
36
34
  Host
37
35
  Origin
38
36
  Pragma
39
37
  Referer
40
- TE
38
+ Sec-Fetch-Dest
39
+ Sec-Fetch-Mode
40
+ Sec-Fetch-Site
41
+ Sec-Fetch-User
42
+ Te
41
43
  Upgrade-Insecure-Requests
44
+ User-Agent
42
45
  X-Castle-Client-Id
46
+ X-Requested-With
43
47
  ].freeze
44
48
 
45
- attr_accessor :host, :port, :request_timeout, :url_prefix
46
- attr_reader :api_secret, :whitelisted, :blacklisted, :failover_strategy, :ip_headers, :trusted_proxies
49
+ attr_accessor :request_timeout, :trust_proxy_chain, :logger
50
+ attr_reader :api_secret, :allowlisted, :denylisted, :failover_strategy, :ip_headers,
51
+ :trusted_proxies, :trusted_proxy_depth, :base_url
47
52
 
48
53
  def initialize
49
- @formatter = Castle::HeadersFormatter
54
+ @header_format = Castle::Headers::Format
50
55
  @request_timeout = REQUEST_TIMEOUT
51
56
  reset
52
57
  end
53
58
 
54
59
  def reset
55
- self.failover_strategy = FAILOVER_STRATEGY
56
- self.host = HOST
57
- self.port = PORT
58
- self.url_prefix = URL_PREFIX
59
- self.whitelisted = [].freeze
60
- self.blacklisted = [].freeze
60
+ self.failover_strategy = Castle::Failover::Strategy::ALLOW
61
+ self.base_url = BASE_URL
62
+ self.allowlisted = [].freeze
63
+ self.denylisted = [].freeze
61
64
  self.api_secret = ENV.fetch('CASTLE_API_SECRET', '')
62
65
  self.ip_headers = [].freeze
63
66
  self.trusted_proxies = [].freeze
67
+ self.trust_proxy_chain = false
68
+ self.trusted_proxy_depth = nil
69
+ self.logger = nil
70
+ end
71
+
72
+ def base_url=(value)
73
+ @base_url = URI(value)
64
74
  end
65
75
 
66
76
  def api_secret=(value)
67
77
  @api_secret = value.to_s
68
78
  end
69
79
 
70
- def whitelisted=(value)
71
- @whitelisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
80
+ def allowlisted=(value)
81
+ @allowlisted = (value ? value.map { |header| @header_format.call(header) } : []).freeze
72
82
  end
73
83
 
74
- def blacklisted=(value)
75
- @blacklisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
84
+ def denylisted=(value)
85
+ @denylisted = (value ? value.map { |header| @header_format.call(header) } : []).freeze
76
86
  end
77
87
 
78
88
  # sets ip headers
@@ -80,23 +90,30 @@ module Castle
80
90
  def ip_headers=(value)
81
91
  raise Castle::ConfigurationError, 'ip headers must be an Array' unless value.is_a?(Array)
82
92
 
83
- @ip_headers = value.map { |header| @formatter.call(header) }.freeze
93
+ @ip_headers = value.map { |header| @header_format.call(header) }.freeze
84
94
  end
85
95
 
86
96
  # sets trusted proxies
87
- # @param value [Array<String|Regexp>]
97
+ # @param value [Array<String,Regexp>]
88
98
  def trusted_proxies=(value)
89
99
  raise Castle::ConfigurationError, 'trusted proxies must be an Array' unless value.is_a?(Array)
90
100
 
91
101
  @trusted_proxies = value
92
102
  end
93
103
 
104
+ # @param value [String,Number,NilClass]
105
+ def trusted_proxy_depth=(value)
106
+ @trusted_proxy_depth = value.to_i
107
+ end
108
+
94
109
  def valid?
95
- !api_secret.to_s.empty? && !host.to_s.empty? && !port.to_s.empty?
110
+ !api_secret.to_s.empty? && !base_url.host.to_s.empty? && !base_url.port.to_s.empty?
96
111
  end
97
112
 
98
113
  def failover_strategy=(value)
99
- @failover_strategy = FAILOVER_STRATEGIES.detect { |strategy| strategy == value.to_sym }
114
+ @failover_strategy = Castle::Failover::STRATEGIES.detect do |strategy|
115
+ strategy == value.to_sym
116
+ end
100
117
  raise Castle::ConfigurationError, 'unrecognized failover strategy' if @failover_strategy.nil?
101
118
  end
102
119
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Castle
4
4
  module Context
5
- class Default
5
+ class GetDefault
6
6
  def initialize(request, cookies = nil)
7
- @pre_headers = HeadersFilter.new(request).call
7
+ @pre_headers = Castle::Headers::Filter.new(request).call
8
8
  @cookies = cookies || request.cookies
9
9
  @request = request
10
10
  end
@@ -13,7 +13,6 @@ module Castle
13
13
  {
14
14
  client_id: client_id,
15
15
  active: true,
16
- origin: 'web',
17
16
  headers: headers,
18
17
  ip: ip,
19
18
  library: {
@@ -40,18 +39,18 @@ module Castle
40
39
 
41
40
  # @return [String]
42
41
  def ip
43
- Extractors::IP.new(@pre_headers).call
42
+ Castle::IP::Extract.new(@pre_headers).call
44
43
  end
45
44
 
46
45
  # @return [String]
47
46
  def client_id
48
- Extractors::ClientId.new(@pre_headers, @cookies).call
47
+ Castle::ClientId::Extract.new(@pre_headers, @cookies).call
49
48
  end
50
49
 
51
50
  # formatted and filtered headers
52
51
  # @return [Hash]
53
52
  def headers
54
- Extractors::Headers.new(@pre_headers).call
53
+ Castle::Headers::Extract.new(@pre_headers).call
55
54
  end
56
55
  end
57
56
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  module Castle
4
4
  module Context
5
- class Merger
5
+ class Merge
6
6
  class << self
7
7
  def call(initial_context, request_context)
8
- main_context = Castle::Utils::Cloner.call(initial_context)
9
- Castle::Utils::Merger.call(main_context, request_context || {})
8
+ main_context = Castle::Utils::Clone.call(initial_context)
9
+ Castle::Utils::Merge.call(main_context, request_context || {})
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castle
4
+ module Context
5
+ # prepares the context from the request
6
+ module Prepare
7
+ class << self
8
+ # @param request [Request]
9
+ # @param options [Hash]
10
+ # @return [Hash]
11
+ def call(request, options = {})
12
+ default_context = Castle::Context::GetDefault.new(request, options[:cookies]).call
13
+ Castle::Context::Merge.call(default_context, options[:context])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -3,7 +3,7 @@
3
3
  module Castle
4
4
  module Context
5
5
  # removes not proper active flag values
6
- class Sanitizer
6
+ class Sanitize
7
7
  class << self
8
8
  def call(context)
9
9
  sanitized_active_mode(context) || {}
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castle
4
+ module Core
5
+ # this module returns a new configured Net::HTTP object
6
+ module GetConnection
7
+ HTTPS_SCHEME = 'https'
8
+
9
+ class << self
10
+ # @param config [Castle::Configuration, Castle::SingletonConfiguration]
11
+ def call(config = Castle.config)
12
+ http = Net::HTTP.new(config.base_url.host, config.base_url.port)
13
+ http.read_timeout = config.request_timeout / 1000.0
14
+
15
+ if config.base_url.scheme == HTTPS_SCHEME
16
+ http.use_ssl = true
17
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
18
+ end
19
+
20
+ http
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end