castle-rb 5.0.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +107 -33
  3. data/lib/castle.rb +46 -22
  4. data/lib/castle/api.rb +22 -13
  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 +48 -62
  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 +17 -19
  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/{extractors/headers.rb → headers/extract.rb} +8 -6
  41. data/lib/castle/headers/filter.rb +37 -0
  42. data/lib/castle/headers/format.rb +24 -0
  43. data/lib/castle/{extractors/ip.rb → ip/extract.rb} +8 -7
  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 +39 -21
  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} +7 -32
  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/{api/connection_spec.rb → core/get_connection_spec.rb} +3 -3
  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/{api/request_spec.rb → core/send_request_spec.rb} +20 -16
  95. data/spec/lib/castle/failover/strategy_spec.rb +12 -0
  96. data/spec/lib/castle/{extractors/headers_spec.rb → headers/extract_spec.rb} +7 -7
  97. data/spec/lib/castle/{headers_filter_spec.rb → headers/filter_spec.rb} +3 -3
  98. data/spec/lib/castle/headers/format_spec.rb +25 -0
  99. data/spec/lib/castle/{extractors/ip_spec.rb → ip/extract_spec.rb} +1 -1
  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/{api/session_spec.rb → session_spec.rb} +6 -4
  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 +129 -57
  114. data/lib/castle/api/connection.rb +0 -24
  115. data/lib/castle/api/request.rb +0 -42
  116. data/lib/castle/api/session.rb +0 -20
  117. data/lib/castle/commands/impersonate.rb +0 -26
  118. data/lib/castle/failover_auth_response.rb +0 -21
  119. data/lib/castle/headers_filter.rb +0 -35
  120. data/lib/castle/headers_formatter.rb +0 -22
  121. data/lib/castle/review.rb +0 -11
  122. data/lib/castle/utils.rb +0 -55
  123. data/lib/castle/utils/cloner.rb +0 -11
  124. data/lib/castle/utils/timestamp.rb +0 -12
  125. data/spec/lib/castle/headers_formatter_spec.rb +0 -25
  126. data/spec/lib/castle/utils_spec.rb +0 -156
@@ -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,18 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'singleton'
4
3
  require 'uri'
5
4
 
6
5
  module Castle
7
6
  # manages configuration variables
8
7
  class Configuration
9
- include Singleton
10
-
11
8
  # API endpoint
12
- URL = 'https://api.castle.io/v1'
13
- FAILOVER_STRATEGY = :allow
14
- REQUEST_TIMEOUT = 500 # in milliseconds
15
- FAILOVER_STRATEGIES = %i[allow deny challenge throw].freeze
9
+ BASE_URL = 'https://api.castle.io/v1'
10
+ REQUEST_TIMEOUT = 1000 # in milliseconds
16
11
  # regexp of trusted proxies which is always appended to the trusted proxy list
17
12
  TRUSTED_PROXIES = [/
18
13
  \A127\.0\.0\.1\Z|
@@ -51,19 +46,19 @@ module Castle
51
46
  X-Requested-With
52
47
  ].freeze
53
48
 
54
- attr_accessor :request_timeout, :trust_proxy_chain
49
+ attr_accessor :request_timeout, :trust_proxy_chain, :logger
55
50
  attr_reader :api_secret, :allowlisted, :denylisted, :failover_strategy, :ip_headers,
56
- :trusted_proxies, :trusted_proxy_depth, :url
51
+ :trusted_proxies, :trusted_proxy_depth, :base_url
57
52
 
58
53
  def initialize
59
- @formatter = Castle::HeadersFormatter
54
+ @header_format = Castle::Headers::Format
60
55
  @request_timeout = REQUEST_TIMEOUT
61
56
  reset
62
57
  end
63
58
 
64
59
  def reset
65
- self.failover_strategy = FAILOVER_STRATEGY
66
- self.url = URL
60
+ self.failover_strategy = Castle::Failover::Strategy::ALLOW
61
+ self.base_url = BASE_URL
67
62
  self.allowlisted = [].freeze
68
63
  self.denylisted = [].freeze
69
64
  self.api_secret = ENV.fetch('CASTLE_API_SECRET', '')
@@ -71,10 +66,11 @@ module Castle
71
66
  self.trusted_proxies = [].freeze
72
67
  self.trust_proxy_chain = false
73
68
  self.trusted_proxy_depth = nil
69
+ self.logger = nil
74
70
  end
75
71
 
76
- def url=(value)
77
- @url = URI(value)
72
+ def base_url=(value)
73
+ @base_url = URI(value)
78
74
  end
79
75
 
80
76
  def api_secret=(value)
@@ -82,11 +78,11 @@ module Castle
82
78
  end
83
79
 
84
80
  def allowlisted=(value)
85
- @allowlisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
81
+ @allowlisted = (value ? value.map { |header| @header_format.call(header) } : []).freeze
86
82
  end
87
83
 
88
84
  def denylisted=(value)
89
- @denylisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
85
+ @denylisted = (value ? value.map { |header| @header_format.call(header) } : []).freeze
90
86
  end
91
87
 
92
88
  # sets ip headers
@@ -94,7 +90,7 @@ module Castle
94
90
  def ip_headers=(value)
95
91
  raise Castle::ConfigurationError, 'ip headers must be an Array' unless value.is_a?(Array)
96
92
 
97
- @ip_headers = value.map { |header| @formatter.call(header) }.freeze
93
+ @ip_headers = value.map { |header| @header_format.call(header) }.freeze
98
94
  end
99
95
 
100
96
  # sets trusted proxies
@@ -111,11 +107,13 @@ module Castle
111
107
  end
112
108
 
113
109
  def valid?
114
- !api_secret.to_s.empty? && !url.host.to_s.empty? && !url.port.to_s.empty?
110
+ !api_secret.to_s.empty? && !base_url.host.to_s.empty? && !base_url.port.to_s.empty?
115
111
  end
116
112
 
117
113
  def failover_strategy=(value)
118
- @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
119
117
  raise Castle::ConfigurationError, 'unrecognized failover strategy' if @failover_strategy.nil?
120
118
  end
121
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
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castle
4
- module API
4
+ module Core
5
5
  # parses api response
6
- module Response
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