castle-rb 4.2.1 → 7.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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +160 -45
  3. data/lib/castle.rb +49 -28
  4. data/lib/castle/api.rb +21 -14
  5. data/lib/castle/api/approve_device.rb +20 -0
  6. data/lib/castle/api/authenticate.rb +37 -0
  7. data/lib/castle/api/end_impersonation.rb +24 -0
  8. data/lib/castle/api/filter.rb +37 -0
  9. data/lib/castle/api/get_device.rb +20 -0
  10. data/lib/castle/api/get_devices_for_user.rb +20 -0
  11. data/lib/castle/api/log.rb +37 -0
  12. data/lib/castle/api/report_device.rb +20 -0
  13. data/lib/castle/api/risk.rb +37 -0
  14. data/lib/castle/api/start_impersonation.rb +24 -0
  15. data/lib/castle/api/track.rb +21 -0
  16. data/lib/castle/client.rb +78 -51
  17. data/lib/castle/{extractors/client_id.rb → client_id/extract.rb} +2 -2
  18. data/lib/castle/commands/approve_device.rb +17 -0
  19. data/lib/castle/commands/authenticate.rb +13 -13
  20. data/lib/castle/commands/end_impersonation.rb +25 -0
  21. data/lib/castle/commands/filter.rb +23 -0
  22. data/lib/castle/commands/get_device.rb +17 -0
  23. data/lib/castle/commands/get_devices_for_user.rb +17 -0
  24. data/lib/castle/commands/log.rb +23 -0
  25. data/lib/castle/commands/report_device.rb +17 -0
  26. data/lib/castle/commands/risk.rb +23 -0
  27. data/lib/castle/commands/start_impersonation.rb +25 -0
  28. data/lib/castle/commands/track.rb +12 -13
  29. data/lib/castle/configuration.rb +57 -32
  30. data/lib/castle/context/{default.rb → get_default.rb} +5 -6
  31. data/lib/castle/context/{merger.rb → merge.rb} +3 -3
  32. data/lib/castle/context/prepare.rb +18 -0
  33. data/lib/castle/context/{sanitizer.rb → sanitize.rb} +1 -1
  34. data/lib/castle/core/get_connection.rb +27 -0
  35. data/lib/castle/{api/response.rb → core/process_response.rb} +8 -3
  36. data/lib/castle/core/process_webhook.rb +25 -0
  37. data/lib/castle/core/send_request.rb +42 -0
  38. data/lib/castle/errors.rb +38 -12
  39. data/lib/castle/failover/prepare_response.rb +18 -0
  40. data/lib/castle/failover/strategy.rb +23 -0
  41. data/lib/castle/headers/extract.rb +47 -0
  42. data/lib/castle/headers/filter.rb +40 -0
  43. data/lib/castle/headers/format.rb +24 -0
  44. data/lib/castle/{extractors/ip.rb → ips/extract.rb} +31 -9
  45. data/lib/castle/logger.rb +19 -0
  46. data/lib/castle/payload/prepare.rb +26 -0
  47. data/lib/castle/secure_mode.rb +7 -2
  48. data/lib/castle/session.rb +18 -0
  49. data/lib/castle/singleton_configuration.rb +9 -0
  50. data/lib/castle/support/hanami.rb +2 -6
  51. data/lib/castle/support/rails.rb +1 -3
  52. data/lib/castle/utils/clean_invalid_chars.rb +22 -0
  53. data/lib/castle/utils/clone.rb +15 -0
  54. data/lib/castle/utils/deep_symbolize_keys.rb +45 -0
  55. data/lib/castle/utils/get_timestamp.rb +15 -0
  56. data/lib/castle/utils/{merger.rb → merge.rb} +3 -3
  57. data/lib/castle/utils/secure_compare.rb +22 -0
  58. data/lib/castle/validators/not_supported.rb +1 -0
  59. data/lib/castle/validators/present.rb +1 -0
  60. data/lib/castle/verdict.rb +15 -0
  61. data/lib/castle/version.rb +1 -1
  62. data/lib/castle/webhooks/verify.rb +45 -0
  63. data/spec/integration/rails/rails_spec.rb +42 -14
  64. data/spec/integration/rails/support/application.rb +3 -1
  65. data/spec/integration/rails/support/home_controller.rb +50 -6
  66. data/spec/lib/castle/api/approve_device_spec.rb +21 -0
  67. data/spec/lib/castle/api/authenticate_spec.rb +136 -0
  68. data/spec/lib/castle/api/end_impersonation_spec.rb +65 -0
  69. data/spec/lib/castle/api/filter_spec.rb +5 -0
  70. data/spec/lib/castle/api/get_device_spec.rb +19 -0
  71. data/spec/lib/castle/api/get_devices_for_user_spec.rb +19 -0
  72. data/spec/lib/castle/api/log_spec.rb +5 -0
  73. data/spec/lib/castle/api/report_device_spec.rb +21 -0
  74. data/spec/lib/castle/api/risk_spec.rb +5 -0
  75. data/spec/lib/castle/api/start_impersonation_spec.rb +65 -0
  76. data/spec/lib/castle/api/track_spec.rb +72 -0
  77. data/spec/lib/castle/api_spec.rb +14 -15
  78. data/spec/lib/castle/{extractors/client_id_spec.rb → client_id/extract_spec.rb} +6 -15
  79. data/spec/lib/castle/client_spec.rb +108 -93
  80. data/spec/lib/castle/commands/approve_device_spec.rb +24 -0
  81. data/spec/lib/castle/commands/authenticate_spec.rb +15 -31
  82. data/spec/lib/castle/commands/end_impersonation_spec.rb +79 -0
  83. data/spec/lib/castle/commands/filter_spec.rb +99 -0
  84. data/spec/lib/castle/commands/get_device_spec.rb +24 -0
  85. data/spec/lib/castle/commands/{review_spec.rb → get_devices_for_user_spec.rb} +7 -7
  86. data/spec/lib/castle/commands/log_spec.rb +100 -0
  87. data/spec/lib/castle/commands/report_device_spec.rb +24 -0
  88. data/spec/lib/castle/commands/risk_spec.rb +100 -0
  89. data/spec/lib/castle/commands/start_impersonation_spec.rb +79 -0
  90. data/spec/lib/castle/commands/track_spec.rb +14 -34
  91. data/spec/lib/castle/configuration_spec.rb +8 -141
  92. data/spec/lib/castle/context/{default_spec.rb → get_default_spec.rb} +9 -10
  93. data/spec/lib/castle/context/{merger_spec.rb → merge_spec.rb} +1 -1
  94. data/spec/lib/castle/context/prepare_spec.rb +43 -0
  95. data/spec/lib/castle/context/{sanitizer_spec.rb → sanitize_spec.rb} +1 -1
  96. data/spec/lib/castle/core/get_connection_spec.rb +43 -0
  97. data/spec/lib/castle/{api/response_spec.rb → core/process_response_spec.rb} +49 -1
  98. data/spec/lib/castle/core/process_webhook_spec.rb +46 -0
  99. data/spec/lib/castle/core/send_request_spec.rb +77 -0
  100. data/spec/lib/castle/failover/strategy_spec.rb +12 -0
  101. data/spec/lib/castle/{extractors/headers_spec.rb → headers/extract_spec.rb} +18 -20
  102. data/spec/lib/castle/headers/filter_spec.rb +39 -0
  103. data/spec/lib/castle/headers/format_spec.rb +25 -0
  104. data/spec/lib/castle/{extractors/ip_spec.rb → ips/extract_spec.rb} +27 -8
  105. data/spec/lib/castle/logger_spec.rb +38 -0
  106. data/spec/lib/castle/payload/prepare_spec.rb +55 -0
  107. data/spec/lib/castle/session_spec.rb +65 -0
  108. data/spec/lib/castle/singleton_configuration_spec.rb +14 -0
  109. data/spec/lib/castle/utils/clean_invalid_chars_spec.rb +69 -0
  110. data/spec/lib/castle/utils/{cloner_spec.rb → clone_spec.rb} +3 -3
  111. data/spec/lib/castle/utils/deep_symbolize_keys_spec.rb +50 -0
  112. data/spec/lib/castle/utils/{timestamp_spec.rb → get_timestamp_spec.rb} +1 -1
  113. data/spec/lib/castle/utils/merge_spec.rb +15 -0
  114. data/spec/lib/castle/validators/present_spec.rb +5 -6
  115. data/spec/lib/castle/verdict_spec.rb +9 -0
  116. data/spec/lib/castle/webhooks/verify_spec.rb +53 -0
  117. data/spec/lib/castle_spec.rb +4 -10
  118. data/spec/spec_helper.rb +3 -3
  119. data/spec/support/shared_examples/action_request.rb +152 -0
  120. data/spec/support/shared_examples/configuration.rb +101 -0
  121. metadata +146 -64
  122. data/lib/castle/api/request.rb +0 -42
  123. data/lib/castle/api/session.rb +0 -39
  124. data/lib/castle/commands/identify.rb +0 -23
  125. data/lib/castle/commands/impersonate.rb +0 -26
  126. data/lib/castle/commands/review.rb +0 -14
  127. data/lib/castle/events.rb +0 -49
  128. data/lib/castle/extractors/headers.rb +0 -45
  129. data/lib/castle/failover_auth_response.rb +0 -21
  130. data/lib/castle/headers_filter.rb +0 -35
  131. data/lib/castle/headers_formatter.rb +0 -22
  132. data/lib/castle/review.rb +0 -11
  133. data/lib/castle/utils.rb +0 -55
  134. data/lib/castle/utils/cloner.rb +0 -11
  135. data/lib/castle/utils/timestamp.rb +0 -12
  136. data/spec/lib/castle/api/request_spec.rb +0 -72
  137. data/spec/lib/castle/commands/identify_spec.rb +0 -88
  138. data/spec/lib/castle/commands/impersonate_spec.rb +0 -107
  139. data/spec/lib/castle/events_spec.rb +0 -5
  140. data/spec/lib/castle/headers_filter_spec.rb +0 -37
  141. data/spec/lib/castle/headers_formatter_spec.rb +0 -25
  142. data/spec/lib/castle/review_spec.rb +0 -19
  143. data/spec/lib/castle/utils/merger_spec.rb +0 -13
  144. data/spec/lib/castle/utils_spec.rb +0 -156
@@ -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,27 @@
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
+ # @return [Net::HTTP]
12
+ def call(config = nil)
13
+ config ||= Castle.config
14
+ http = Net::HTTP.new(config.base_url.host, config.base_url.port)
15
+ http.read_timeout = config.request_timeout / 1000.0
16
+
17
+ if config.base_url.scheme == HTTPS_SCHEME
18
+ http.use_ssl = true
19
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
20
+ end
21
+
22
+ http
23
+ end
24
+ end
25
+ end
26
+ end
27
+ 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,
@@ -14,9 +14,14 @@ module Castle
14
14
  }.freeze
15
15
 
16
16
  class << self
17
- def call(response)
17
+ # @param response [Response]
18
+ # @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
19
+ # @return [Hash]
20
+ def call(response, config = nil)
18
21
  verify!(response)
19
22
 
23
+ Castle::Logger.call('response:', response.body.to_s, config)
24
+
20
25
  return {} if response.body.nil? || response.body.empty?
21
26
 
22
27
  begin
@@ -0,0 +1,25 @@
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
+ # @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
11
+ # @return [String]
12
+ def call(webhook, config = nil)
13
+ webhook
14
+ .body
15
+ .read
16
+ .tap do |result|
17
+ raise Castle::ApiError, 'Invalid webhook from Castle API' if result.blank?
18
+
19
+ Castle::Logger.call('webhook:', result.to_s, config)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,42 @@
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 = { 'Content-Type' => 'application/json' }.freeze
9
+
10
+ private_constant :DEFAULT_HEADERS
11
+
12
+ class << self
13
+ # @param command [String]
14
+ # @param headers [Hash]
15
+ # @param http [Net::HTTP]
16
+ # @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
17
+ def call(command, headers, http = nil, config = nil)
18
+ (http || Castle::Core::GetConnection.call).request(
19
+ build(command, headers.merge(DEFAULT_HEADERS), config)
20
+ )
21
+ end
22
+
23
+ # @param command [String]
24
+ # @param headers [Hash]
25
+ # @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
26
+ def build(command, headers, config)
27
+ url = "#{config.base_url.path}/#{command.path}"
28
+ request_obj = Net::HTTP.const_get(command.method.to_s.capitalize).new(url, headers)
29
+
30
+ unless command.method == :get
31
+ request_obj.body = ::Castle::Utils::CleanInvalidChars.call(command.data).to_json
32
+ end
33
+
34
+ Castle::Logger.call("#{url}:", request_obj.body, config)
35
+
36
+ request_obj.basic_auth('', config.api_secret)
37
+ request_obj
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/castle/errors.rb CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  module Castle
4
4
  # general error
5
- class Error < RuntimeError; end
5
+ class Error < RuntimeError
6
+ end
7
+
6
8
  # Raised when anything is wrong with the request (any unhappy path)
7
9
  # This error indicates that either we would wait too long for a response or something
8
10
  # else happened somewhere in the middle and we weren't able to get the results
@@ -14,28 +16,52 @@ module Castle
14
16
  @reason = reason
15
17
  end
16
18
  end
19
+
17
20
  # security error
18
- class SecurityError < Castle::Error; end
21
+ class SecurityError < Castle::Error
22
+ end
23
+
19
24
  # wrong configuration error
20
- class ConfigurationError < Castle::Error; end
25
+ class ConfigurationError < Castle::Error
26
+ end
27
+
21
28
  # error returned by api
22
- class ApiError < Castle::Error; end
29
+ class ApiError < Castle::Error
30
+ end
31
+
32
+ # webhook signature verification error
33
+ class WebhookVerificationError < Castle::Error
34
+ end
23
35
 
24
36
  # api error bad request 400
25
- class BadRequestError < Castle::ApiError; end
37
+ class BadRequestError < Castle::ApiError
38
+ end
39
+
26
40
  # api error forbidden 403
27
- class ForbiddenError < Castle::ApiError; end
41
+ class ForbiddenError < Castle::ApiError
42
+ end
43
+
28
44
  # api error not found 404
29
- class NotFoundError < Castle::ApiError; end
45
+ class NotFoundError < Castle::ApiError
46
+ end
47
+
30
48
  # api error user unauthorized 419
31
- class UserUnauthorizedError < Castle::ApiError; end
49
+ class UserUnauthorizedError < Castle::ApiError
50
+ end
51
+
32
52
  # api error invalid param 422
33
- class InvalidParametersError < Castle::ApiError; end
53
+ class InvalidParametersError < Castle::ApiError
54
+ end
55
+
34
56
  # api error unauthorized 401
35
- class UnauthorizedError < Castle::ApiError; end
57
+ class UnauthorizedError < Castle::ApiError
58
+ end
59
+
36
60
  # all internal server errors
37
- class InternalServerError < Castle::ApiError; end
61
+ class InternalServerError < Castle::ApiError
62
+ end
38
63
 
39
64
  # impersonation command failed
40
- class ImpersonationFailed < Castle::ApiError; end
65
+ class ImpersonationFailed < Castle::ApiError
66
+ end
41
67
  end
@@ -0,0 +1,18 @@
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:)
8
+ @strategy = strategy
9
+ @reason = reason
10
+ @user_id = user_id
11
+ end
12
+
13
+ def call
14
+ { action: @strategy.to_s, user_id: @user_id, failover: true, failover_reason: @reason }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
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
+
10
+ # deny
11
+ DENY = :deny
12
+
13
+ # challenge
14
+ CHALLENGE = :challenge
15
+
16
+ # throw an error
17
+ THROW = :throw
18
+ end
19
+
20
+ # list of possible strategies
21
+ STRATEGIES = %i[allow deny challenge throw].freeze
22
+ end
23
+ 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, nil]
17
+ def initialize(headers, config = nil)
18
+ @headers = headers
19
+ @config = config || Castle.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,40 @@
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
16
+ .freeze
17
+
18
+ private_constant :VALUABLE_HEADERS
19
+
20
+ # @param request [Rack::Request]
21
+ def initialize(request)
22
+ @request_env = request.env
23
+ @header_format = Castle::Headers::Format
24
+ end
25
+
26
+ # Serialize HTTP headers
27
+ # @return [Hash]
28
+ def call
29
+ @request_env
30
+ .keys
31
+ .each_with_object({}) do |header_name, acc|
32
+ next unless header_name.match(VALUABLE_HEADERS)
33
+
34
+ formatted_name = @header_format.call(header_name)
35
+ acc[formatted_name] = @request_env[header_name]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ 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
@@ -1,19 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castle
4
- module Extractors
4
+ # IPs-related module
5
+ module IPs
5
6
  # used for extraction of ip from the request
6
- class IP
7
+ class Extract
7
8
  # ordered list of ip headers for ip extraction
8
9
  DEFAULT = %w[X-Forwarded-For Remote-Addr].freeze
9
10
 
11
+ # list of header which are used with proxy depth setting
12
+ DEPTH_RELATED = %w[X-Forwarded-For].freeze
13
+
10
14
  private_constant :DEFAULT
11
15
 
12
16
  # @param headers [Hash]
13
- def initialize(headers)
17
+ # @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
18
+ def initialize(headers, config = nil)
19
+ config ||= Castle.config
14
20
  @headers = headers
15
- @ip_headers = Castle.config.ip_headers.empty? ? DEFAULT : Castle.config.ip_headers
16
- @proxies = Castle.config.trusted_proxies + Castle::Configuration::TRUSTED_PROXIES
21
+ @ip_headers = config.ip_headers.empty? ? DEFAULT : config.ip_headers
22
+ @proxies = config.trusted_proxies + Castle::Configuration::TRUSTED_PROXIES
23
+ @trust_proxy_chain = config.trust_proxy_chain
24
+ @trusted_proxy_depth = config.trusted_proxy_depth
17
25
  end
18
26
 
19
27
  # Order of headers:
@@ -26,13 +34,14 @@ module Castle
26
34
 
27
35
  @ip_headers.each do |ip_header|
28
36
  ips = ips_from(ip_header)
29
- ip_value = remove_proxies(ips).last
37
+ ip_value = remove_proxies(ips)
38
+
30
39
  return ip_value if ip_value
31
40
 
32
41
  all_ips.push(*ips)
33
42
  end
34
43
 
35
- # fallback to first whatever ip
44
+ # fallback to first listed ip
36
45
  all_ips.first
37
46
  end
38
47
 
@@ -41,7 +50,9 @@ module Castle
41
50
  # @param ips [Array<String>]
42
51
  # @return [Array<String>]
43
52
  def remove_proxies(ips)
44
- ips.reject { |ip| proxy?(ip) }
53
+ return ips.first if @trust_proxy_chain
54
+
55
+ ips.reject { |ip| proxy?(ip) }.last
45
56
  end
46
57
 
47
58
  # @param ip [String]
@@ -57,7 +68,18 @@ module Castle
57
68
 
58
69
  return [] unless value
59
70
 
60
- value.strip.split(/[,\s]+/)
71
+ ips = value.strip.split(/[,\s]+/)
72
+
73
+ limit_proxy_depth(ips, header)
74
+ end
75
+
76
+ # @param ips [Array<String>]
77
+ # @param ip_header [String]
78
+ # @return [Array<String>]
79
+ def limit_proxy_depth(ips, ip_header)
80
+ ips.pop(@trusted_proxy_depth) if DEPTH_RELATED.include?(ip_header)
81
+
82
+ ips
61
83
  end
62
84
  end
63
85
  end