actionpack 5.2.7.1 → 6.1.4.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +329 -352
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller/base.rb +38 -4
  6. data/lib/abstract_controller/caching/fragments.rb +6 -22
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/callbacks.rb +14 -2
  9. data/lib/abstract_controller/collector.rb +1 -2
  10. data/lib/abstract_controller/helpers.rb +106 -90
  11. data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
  12. data/lib/abstract_controller/rendering.rb +9 -9
  13. data/lib/abstract_controller/translation.rb +11 -5
  14. data/lib/abstract_controller.rb +1 -0
  15. data/lib/action_controller/api.rb +4 -3
  16. data/lib/action_controller/base.rb +6 -9
  17. data/lib/action_controller/caching.rb +1 -3
  18. data/lib/action_controller/log_subscriber.rb +10 -7
  19. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  20. data/lib/action_controller/metal/conditional_get.rb +19 -5
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  22. data/lib/action_controller/metal/cookies.rb +3 -1
  23. data/lib/action_controller/metal/data_streaming.rb +6 -7
  24. data/lib/action_controller/metal/default_headers.rb +17 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  26. data/lib/action_controller/metal/exceptions.rb +56 -2
  27. data/lib/action_controller/metal/flash.rb +5 -5
  28. data/lib/action_controller/metal/head.rb +7 -4
  29. data/lib/action_controller/metal/helpers.rb +14 -5
  30. data/lib/action_controller/metal/http_authentication.rb +24 -23
  31. data/lib/action_controller/metal/implicit_render.rb +5 -15
  32. data/lib/action_controller/metal/instrumentation.rb +13 -14
  33. data/lib/action_controller/metal/live.rb +39 -32
  34. data/lib/action_controller/metal/logging.rb +20 -0
  35. data/lib/action_controller/metal/mime_responds.rb +19 -4
  36. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  37. data/lib/action_controller/metal/params_wrapper.rb +32 -22
  38. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  39. data/lib/action_controller/metal/redirecting.rb +6 -6
  40. data/lib/action_controller/metal/renderers.rb +4 -4
  41. data/lib/action_controller/metal/rendering.rb +8 -3
  42. data/lib/action_controller/metal/request_forgery_protection.rb +26 -49
  43. data/lib/action_controller/metal/rescue.rb +1 -1
  44. data/lib/action_controller/metal/streaming.rb +0 -1
  45. data/lib/action_controller/metal/strong_parameters.rb +167 -58
  46. data/lib/action_controller/metal/url_for.rb +1 -1
  47. data/lib/action_controller/metal.rb +10 -8
  48. data/lib/action_controller/railties/helpers.rb +1 -1
  49. data/lib/action_controller/renderer.rb +37 -13
  50. data/lib/action_controller/template_assertions.rb +1 -1
  51. data/lib/action_controller/test_case.rb +71 -63
  52. data/lib/action_controller.rb +7 -4
  53. data/lib/action_dispatch/http/cache.rb +31 -27
  54. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  55. data/lib/action_dispatch/http/content_security_policy.rb +39 -17
  56. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  57. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  58. data/lib/action_dispatch/http/headers.rb +4 -4
  59. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  60. data/lib/action_dispatch/http/mime_type.rb +43 -24
  61. data/lib/action_dispatch/http/parameters.rb +14 -23
  62. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  63. data/lib/action_dispatch/http/request.rb +45 -22
  64. data/lib/action_dispatch/http/response.rb +45 -25
  65. data/lib/action_dispatch/http/upload.rb +9 -1
  66. data/lib/action_dispatch/http/url.rb +82 -82
  67. data/lib/action_dispatch/journey/formatter.rb +55 -31
  68. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  69. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  70. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  71. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  72. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  73. data/lib/action_dispatch/journey/parser.rb +13 -13
  74. data/lib/action_dispatch/journey/parser.y +1 -1
  75. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  76. data/lib/action_dispatch/journey/route.rb +10 -20
  77. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  78. data/lib/action_dispatch/journey/router.rb +26 -34
  79. data/lib/action_dispatch/journey/routes.rb +0 -2
  80. data/lib/action_dispatch/journey/scanner.rb +10 -4
  81. data/lib/action_dispatch/journey/visitors.rb +1 -4
  82. data/lib/action_dispatch/journey.rb +0 -2
  83. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  84. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  85. data/lib/action_dispatch/middleware/cookies.rb +128 -109
  86. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  87. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  88. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  89. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  90. data/lib/action_dispatch/middleware/flash.rb +1 -1
  91. data/lib/action_dispatch/middleware/host_authorization.rb +141 -0
  92. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  93. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  94. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  95. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  96. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  97. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
  98. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  99. data/lib/action_dispatch/middleware/stack.rb +56 -2
  100. data/lib/action_dispatch/middleware/static.rb +153 -93
  101. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  107. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  108. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  112. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  114. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  115. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  119. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  120. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  121. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  122. data/lib/action_dispatch/railtie.rb +8 -2
  123. data/lib/action_dispatch/request/session.rb +11 -10
  124. data/lib/action_dispatch/request/utils.rb +26 -2
  125. data/lib/action_dispatch/routing/inspector.rb +100 -52
  126. data/lib/action_dispatch/routing/mapper.rb +155 -103
  127. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  128. data/lib/action_dispatch/routing/redirection.rb +4 -4
  129. data/lib/action_dispatch/routing/route_set.rb +71 -69
  130. data/lib/action_dispatch/routing/url_for.rb +2 -2
  131. data/lib/action_dispatch/routing.rb +21 -20
  132. data/lib/action_dispatch/system_test_case.rb +54 -11
  133. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  134. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  135. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  136. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  137. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  138. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  139. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  140. data/lib/action_dispatch/testing/assertions.rb +1 -1
  141. data/lib/action_dispatch/testing/integration.rb +60 -28
  142. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  143. data/lib/action_dispatch/testing/test_process.rb +29 -4
  144. data/lib/action_dispatch/testing/test_request.rb +3 -3
  145. data/lib/action_dispatch/testing/test_response.rb +4 -32
  146. data/lib/action_dispatch.rb +9 -3
  147. data/lib/action_pack/gem_version.rb +4 -4
  148. data/lib/action_pack.rb +1 -1
  149. metadata +35 -23
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pp"
4
+
5
+ require "action_view"
6
+ require "action_view/base"
7
+
8
+ module ActionDispatch
9
+ class DebugView < ActionView::Base # :nodoc:
10
+ RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
11
+
12
+ def initialize(assigns)
13
+ paths = [RESCUES_TEMPLATE_PATH]
14
+ lookup_context = ActionView::LookupContext.new(paths)
15
+ super(lookup_context, assigns, nil)
16
+ end
17
+
18
+ def compiled_method_container
19
+ self.class
20
+ end
21
+
22
+ def debug_params(params)
23
+ clean_params = params.clone
24
+ clean_params.delete("action")
25
+ clean_params.delete("controller")
26
+
27
+ if clean_params.empty?
28
+ "None"
29
+ else
30
+ PP.pp(clean_params, +"", 200)
31
+ end
32
+ end
33
+
34
+ def debug_headers(headers)
35
+ if headers.present?
36
+ headers.inspect.gsub(",", ",\n")
37
+ else
38
+ "None"
39
+ end
40
+ end
41
+
42
+ def debug_hash(object)
43
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
44
+ end
45
+
46
+ def render(*)
47
+ logger = ActionView::Base.logger
48
+
49
+ if logger && logger.respond_to?(:silence)
50
+ logger.silence { super }
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def protect_against_forgery?
57
+ false
58
+ end
59
+
60
+ def params_valid?
61
+ @request.parameters
62
+ rescue ActionController::BadRequest
63
+ false
64
+ end
65
+ end
66
+ end
@@ -6,44 +6,72 @@ require "rack/utils"
6
6
  module ActionDispatch
7
7
  class ExceptionWrapper
8
8
  cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
9
- "ActionController::RoutingError" => :not_found,
10
- "AbstractController::ActionNotFound" => :not_found,
11
- "ActionController::MethodNotAllowed" => :method_not_allowed,
12
- "ActionController::UnknownHttpMethod" => :method_not_allowed,
13
- "ActionController::NotImplemented" => :not_implemented,
14
- "ActionController::UnknownFormat" => :not_acceptable,
15
- "ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
16
- "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
17
- "ActionDispatch::Http::Parameters::ParseError" => :bad_request,
18
- "ActionController::BadRequest" => :bad_request,
19
- "ActionController::ParameterMissing" => :bad_request,
20
- "Rack::QueryParser::ParameterTypeError" => :bad_request,
21
- "Rack::QueryParser::InvalidParameterError" => :bad_request
9
+ "ActionController::RoutingError" => :not_found,
10
+ "AbstractController::ActionNotFound" => :not_found,
11
+ "ActionController::MethodNotAllowed" => :method_not_allowed,
12
+ "ActionController::UnknownHttpMethod" => :method_not_allowed,
13
+ "ActionController::NotImplemented" => :not_implemented,
14
+ "ActionController::UnknownFormat" => :not_acceptable,
15
+ "ActionDispatch::Http::MimeNegotiation::InvalidType" => :not_acceptable,
16
+ "ActionController::MissingExactTemplate" => :not_acceptable,
17
+ "ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
18
+ "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
19
+ "ActionDispatch::Http::Parameters::ParseError" => :bad_request,
20
+ "ActionController::BadRequest" => :bad_request,
21
+ "ActionController::ParameterMissing" => :bad_request,
22
+ "Rack::QueryParser::ParameterTypeError" => :bad_request,
23
+ "Rack::QueryParser::InvalidParameterError" => :bad_request
22
24
  )
23
25
 
24
26
  cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
25
- "ActionView::MissingTemplate" => "missing_template",
26
- "ActionController::RoutingError" => "routing_error",
27
- "AbstractController::ActionNotFound" => "unknown_action",
28
- "ActiveRecord::StatementInvalid" => "invalid_statement",
29
- "ActionView::Template::Error" => "template_error"
27
+ "ActionView::MissingTemplate" => "missing_template",
28
+ "ActionController::RoutingError" => "routing_error",
29
+ "AbstractController::ActionNotFound" => "unknown_action",
30
+ "ActiveRecord::StatementInvalid" => "invalid_statement",
31
+ "ActionView::Template::Error" => "template_error",
32
+ "ActionController::MissingExactTemplate" => "missing_exact_template",
30
33
  )
31
34
 
32
- attr_reader :backtrace_cleaner, :exception, :line_number, :file
35
+ cattr_accessor :wrapper_exceptions, default: [
36
+ "ActionView::Template::Error"
37
+ ]
38
+
39
+ cattr_accessor :silent_exceptions, default: [
40
+ "ActionController::RoutingError",
41
+ "ActionDispatch::Http::MimeNegotiation::InvalidType"
42
+ ]
43
+
44
+ attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
33
45
 
34
46
  def initialize(backtrace_cleaner, exception)
35
47
  @backtrace_cleaner = backtrace_cleaner
36
- @exception = original_exception(exception)
48
+ @exception = exception
49
+ @exception_class_name = @exception.class.name
50
+ @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
37
51
 
38
52
  expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
39
53
  end
40
54
 
55
+ def unwrapped_exception
56
+ if wrapper_exceptions.include?(@exception_class_name)
57
+ exception.cause
58
+ else
59
+ exception
60
+ end
61
+ end
62
+
41
63
  def rescue_template
42
- @@rescue_templates[@exception.class.name]
64
+ @@rescue_templates[@exception_class_name]
43
65
  end
44
66
 
45
67
  def status_code
46
- self.class.status_code_for_exception(@exception.class.name)
68
+ self.class.status_code_for_exception(unwrapped_exception.class.name)
69
+ end
70
+
71
+ def exception_trace
72
+ trace = application_trace
73
+ trace = framework_trace if trace.empty? && !silent_exceptions.include?(@exception_class_name)
74
+ trace
47
75
  end
48
76
 
49
77
  def application_trace
@@ -64,7 +92,11 @@ module ActionDispatch
64
92
  full_trace_with_ids = []
65
93
 
66
94
  full_trace.each_with_index do |trace, idx|
67
- trace_with_id = { id: idx, trace: trace }
95
+ trace_with_id = {
96
+ exception_object_id: @exception.object_id,
97
+ id: idx,
98
+ trace: trace
99
+ }
68
100
 
69
101
  if application_trace.include?(trace)
70
102
  application_trace_with_ids << trace_with_id
@@ -97,18 +129,31 @@ module ActionDispatch
97
129
  end
98
130
  end
99
131
 
100
- private
132
+ def trace_to_show
133
+ if traces["Application Trace"].empty? && rescue_template != "routing_error"
134
+ "Full Trace"
135
+ else
136
+ "Application Trace"
137
+ end
138
+ end
139
+
140
+ def source_to_show_id
141
+ (traces[trace_to_show].first || {})[:id]
142
+ end
101
143
 
144
+ private
102
145
  def backtrace
103
146
  Array(@exception.backtrace)
104
147
  end
105
148
 
106
- def original_exception(exception)
107
- if @@rescue_responses.has_key?(exception.cause.class.name)
108
- exception.cause
109
- else
110
- exception
111
- end
149
+ def causes_for(exception)
150
+ return enum_for(__method__, exception) unless block_given?
151
+
152
+ yield exception while exception = exception.cause
153
+ end
154
+
155
+ def wrapped_causes_for(exception, backtrace_cleaner)
156
+ causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) }
112
157
  end
113
158
 
114
159
  def clean_backtrace(*args)
@@ -38,7 +38,7 @@ module ActionDispatch
38
38
  #
39
39
  # See docs on the FlashHash class for more details about the flash.
40
40
  class Flash
41
- KEY = "action_dispatch.request.flash_hash".freeze
41
+ KEY = "action_dispatch.request.flash_hash"
42
42
 
43
43
  module RequestMethods
44
44
  # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/http/request"
4
+
5
+ module ActionDispatch
6
+ # This middleware guards from DNS rebinding attacks by explicitly permitting
7
+ # the hosts a request can be sent to, and is passed the options set in
8
+ # +config.host_authorization+.
9
+ #
10
+ # Requests can opt-out of Host Authorization with +exclude+:
11
+ #
12
+ # config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } }
13
+ #
14
+ # When a request comes to an unauthorized host, the +response_app+
15
+ # application will be executed and rendered. If no +response_app+ is given, a
16
+ # default one will run, which responds with <tt>403 Forbidden</tt>.
17
+ class HostAuthorization
18
+ ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
19
+ PORT_REGEX = /(?::\d+)/ # :nodoc:
20
+ IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
21
+ IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
22
+ IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
23
+ VALID_IP_HOSTNAME = Regexp.union( # :nodoc:
24
+ /\A#{IPV4_HOSTNAME}\z/,
25
+ /\A#{IPV6_HOSTNAME}\z/,
26
+ /\A#{IPV6_HOSTNAME_WITH_PORT}\z/,
27
+ )
28
+
29
+ class Permissions # :nodoc:
30
+ def initialize(hosts)
31
+ @hosts = sanitize_hosts(hosts)
32
+ end
33
+
34
+ def empty?
35
+ @hosts.empty?
36
+ end
37
+
38
+ def allows?(host)
39
+ @hosts.any? do |allowed|
40
+ if allowed.is_a?(IPAddr)
41
+ begin
42
+ allowed === extract_hostname(host)
43
+ rescue
44
+ # IPAddr#=== raises an error if you give it a hostname instead of
45
+ # IP. Treat similar errors as blocked access.
46
+ false
47
+ end
48
+ else
49
+ allowed === host
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+ def sanitize_hosts(hosts)
56
+ Array(hosts).map do |host|
57
+ case host
58
+ when Regexp then sanitize_regexp(host)
59
+ when String then sanitize_string(host)
60
+ else host
61
+ end
62
+ end
63
+ end
64
+
65
+ def sanitize_regexp(host)
66
+ /\A#{host}#{PORT_REGEX}?\z/
67
+ end
68
+
69
+ def sanitize_string(host)
70
+ if host.start_with?(".")
71
+ /\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i
72
+ else
73
+ /\A#{Regexp.escape host}#{PORT_REGEX}?\z/i
74
+ end
75
+ end
76
+
77
+ def extract_hostname(host)
78
+ host.slice(VALID_IP_HOSTNAME, "host") || host
79
+ end
80
+ end
81
+
82
+ DEFAULT_RESPONSE_APP = -> env do
83
+ request = Request.new(env)
84
+
85
+ format = request.xhr? ? "text/plain" : "text/html"
86
+ template = DebugView.new(host: request.host)
87
+ body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
88
+
89
+ [403, {
90
+ "Content-Type" => "#{format}; charset=#{Response.default_charset}",
91
+ "Content-Length" => body.bytesize.to_s,
92
+ }, [body]]
93
+ end
94
+
95
+ def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
96
+ @app = app
97
+ @permissions = Permissions.new(hosts)
98
+ @exclude = exclude
99
+
100
+ unless deprecated_response_app.nil?
101
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
102
+ `action_dispatch.hosts_response_app` is deprecated and will be ignored in Rails 6.2.
103
+ Use the Host Authorization `response_app` setting instead.
104
+ MSG
105
+
106
+ response_app ||= deprecated_response_app
107
+ end
108
+
109
+ @response_app = response_app || DEFAULT_RESPONSE_APP
110
+ end
111
+
112
+ def call(env)
113
+ return @app.call(env) if @permissions.empty?
114
+
115
+ request = Request.new(env)
116
+
117
+ if authorized?(request) || excluded?(request)
118
+ mark_as_authorized(request)
119
+ @app.call(env)
120
+ else
121
+ @response_app.call(env)
122
+ end
123
+ end
124
+
125
+ private
126
+ def authorized?(request)
127
+ origin_host = request.get_header("HTTP_HOST")
128
+ forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last
129
+
130
+ @permissions.allows?(origin_host) && (forwarded_host.blank? || @permissions.allows?(forwarded_host))
131
+ end
132
+
133
+ def excluded?(request)
134
+ @exclude && @exclude.call(request)
135
+ end
136
+
137
+ def mark_as_authorized(request)
138
+ request.set_header("action_dispatch.authorized_host", request.host)
139
+ end
140
+ end
141
+ end
@@ -21,14 +21,17 @@ module ActionDispatch
21
21
  def call(env)
22
22
  request = ActionDispatch::Request.new(env)
23
23
  status = request.path_info[1..-1].to_i
24
- content_type = request.formats.first
25
- body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
24
+ begin
25
+ content_type = request.formats.first
26
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
27
+ content_type = Mime[:text]
28
+ end
29
+ body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
26
30
 
27
31
  render(status, content_type, body)
28
32
  end
29
33
 
30
34
  private
31
-
32
35
  def render(status, content_type, body)
33
36
  format = "to_#{content_type.to_sym}" if content_type
34
37
  if format && body.respond_to?(format)
@@ -8,13 +8,13 @@ module ActionDispatch
8
8
  # contain the address, and then picking the last-set address that is not
9
9
  # on the list of trusted IPs. This follows the precedent set by e.g.
10
10
  # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
11
- # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
11
+ # with {reasoning explained at length}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
12
12
  # by @gingerlime. A more detailed explanation of the algorithm is given
13
13
  # at GetIp#calculate_ip.
14
14
  #
15
15
  # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
16
16
  # requires. Some Rack servers simply drop preceding headers, and only report
17
- # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
17
+ # the value that was {given in the last header}[https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
18
18
  # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
19
19
  # then you should test your Rack server to make sure your data is good.
20
20
  #
@@ -33,7 +33,7 @@ module ActionDispatch
33
33
  # not be the ultimate client IP in production, and so are discarded. See
34
34
  # https://en.wikipedia.org/wiki/Private_network for details.
35
35
  TRUSTED_PROXIES = [
36
- "127.0.0.1", # localhost IPv4
36
+ "127.0.0.0/8", # localhost IPv4 range, per RFC-3330
37
37
  "::1", # localhost IPv6
38
38
  "fc00::/7", # private IPv6 range fc00::/7
39
39
  "10.0.0.0/8", # private IPv4 range 10.x.x.x
@@ -102,7 +102,7 @@ module ActionDispatch
102
102
  # proxies, that header may contain a list of IPs. Other proxy services
103
103
  # set the Client-Ip header instead, so we check that too.
104
104
  #
105
- # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
105
+ # As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
106
106
  # while the first IP in the list is likely to be the "originating" IP,
107
107
  # it could also have been set by the client maliciously.
108
108
  #
@@ -143,10 +143,11 @@ module ActionDispatch
143
143
  # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
144
144
  # - Client-Ip is propagated from the outermost proxy, or is blank
145
145
  # - REMOTE_ADDR will be the IP that made the request to Rack
146
- ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
146
+ ips = [forwarded_ips, client_ips].flatten.compact
147
147
 
148
- # If every single IP option is in the trusted list, just return REMOTE_ADDR
149
- filter_proxies(ips).first || remote_addr
148
+ # If every single IP option is in the trusted list, return the IP
149
+ # that's furthest away
150
+ filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr
150
151
  end
151
152
 
152
153
  # Memoizes the value returned by #calculate_ip and returns it for
@@ -156,20 +157,17 @@ module ActionDispatch
156
157
  end
157
158
 
158
159
  private
159
-
160
160
  def ips_from(header) # :doc:
161
161
  return [] unless header
162
162
  # Split the comma-separated list into an array of strings.
163
163
  ips = header.strip.split(/[,\s]+/)
164
164
  ips.select do |ip|
165
- begin
166
- # Only return IPs that are valid according to the IPAddr#new method.
167
- range = IPAddr.new(ip).to_range
168
- # We want to make sure nobody is sneaking a netmask in.
169
- range.begin == range.end
170
- rescue ArgumentError
171
- nil
172
- end
165
+ # Only return IPs that are valid according to the IPAddr#new method.
166
+ range = IPAddr.new(ip).to_range
167
+ # We want to make sure nobody is sneaking a netmask in.
168
+ range.begin == range.end
169
+ rescue ArgumentError
170
+ nil
173
171
  end
174
172
  end
175
173
 
@@ -15,22 +15,21 @@ module ActionDispatch
15
15
  # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
16
16
  # from multiple pieces of the stack.
17
17
  class RequestId
18
- X_REQUEST_ID = "X-Request-Id".freeze #:nodoc:
19
-
20
- def initialize(app)
18
+ def initialize(app, header:)
21
19
  @app = app
20
+ @header = header
22
21
  end
23
22
 
24
23
  def call(env)
25
24
  req = ActionDispatch::Request.new env
26
- req.request_id = make_request_id(req.x_request_id)
27
- @app.call(env).tap { |_status, headers, _body| headers[X_REQUEST_ID] = req.request_id }
25
+ req.request_id = make_request_id(req.headers[@header])
26
+ @app.call(env).tap { |_status, headers, _body| headers[@header] = req.request_id }
28
27
  end
29
28
 
30
29
  private
31
30
  def make_request_id(request_id)
32
31
  if request_id.presence
33
- request_id.gsub(/[^\w\-@]/, "".freeze).first(255)
32
+ request_id.gsub(/[^\w\-@]/, "").first(255)
34
33
  else
35
34
  internal_request_id
36
35
  end
@@ -30,7 +30,6 @@ module ActionDispatch
30
30
  end
31
31
 
32
32
  private
33
-
34
33
  def initialize_sid # :doc:
35
34
  @default_options.delete(:sidbits)
36
35
  @default_options.delete(:secure_random)
@@ -83,7 +82,7 @@ module ActionDispatch
83
82
  include SessionObject
84
83
 
85
84
  private
86
- def set_cookie(request, session_id, cookie)
85
+ def set_cookie(request, response, cookie)
87
86
  request.cookie_jar[key] = cookie
88
87
  end
89
88
  end
@@ -98,7 +97,7 @@ module ActionDispatch
98
97
  end
99
98
 
100
99
  private
101
- def set_cookie(request, session_id, cookie)
100
+ def set_cookie(request, response, cookie)
102
101
  request.cookie_jar[key] = cookie
103
102
  end
104
103
  end
@@ -10,22 +10,17 @@ module ActionDispatch
10
10
  # dramatically faster than the alternatives.
11
11
  #
12
12
  # Sessions typically contain at most a user_id and flash message; both fit
13
- # within the 4K cookie size limit. A CookieOverflow exception is raised if
14
- # you attempt to store more than 4K of data.
13
+ # within the 4096 bytes cookie size limit. A CookieOverflow exception is raised if
14
+ # you attempt to store more than 4096 bytes of data.
15
15
  #
16
16
  # The cookie jar used for storage is automatically configured to be the
17
17
  # best possible option given your application's configuration.
18
18
  #
19
- # If you only have secret_token set, your cookies will be signed, but
20
- # not encrypted. This means a user cannot alter their +user_id+ without
21
- # knowing your app's secret key, but can easily read their +user_id+. This
22
- # was the default for Rails 3 apps.
23
- #
24
19
  # Your cookies will be encrypted using your apps secret_key_base. This
25
20
  # goes a step further than signed cookies in that encrypted cookies cannot
26
21
  # be altered or read by users. This is the default starting in Rails 4.
27
22
  #
28
- # Configure your session store in <tt>config/initializers/session_store.rb</tt>:
23
+ # Configure your session store in an initializer:
29
24
  #
30
25
  # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
31
26
  #
@@ -81,7 +76,6 @@ module ActionDispatch
81
76
  end
82
77
 
83
78
  private
84
-
85
79
  def extract_session_id(req)
86
80
  stale_session_check! do
87
81
  sid = unpacked_cookie_data(req)["session_id"]
@@ -40,14 +40,15 @@ module ActionDispatch
40
40
  end
41
41
 
42
42
  private
43
-
44
43
  def render_exception(request, exception)
45
44
  backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
46
45
  wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
47
46
  status = wrapper.status_code
48
- request.set_header "action_dispatch.exception", wrapper.exception
47
+ request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
49
48
  request.set_header "action_dispatch.original_path", request.path_info
49
+ request.set_header "action_dispatch.original_request_method", request.raw_request_method
50
50
  request.path_info = "/#{status}"
51
+ request.request_method = "GET"
51
52
  response = @exceptions_app.call(request.env)
52
53
  response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
53
54
  rescue Exception => failsafe_error