actionpack 3.2.22.5 → 5.2.4

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 (271) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +279 -603
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -297
  5. data/lib/abstract_controller/asset_paths.rb +4 -2
  6. data/lib/abstract_controller/base.rb +82 -52
  7. data/lib/abstract_controller/caching/fragments.rb +166 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +117 -103
  10. data/lib/abstract_controller/collector.rb +18 -7
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +65 -38
  13. data/lib/abstract_controller/logger.rb +3 -2
  14. data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
  15. data/lib/abstract_controller/rendering.rb +77 -129
  16. data/lib/abstract_controller/translation.rb +21 -3
  17. data/lib/abstract_controller/url_for.rb +9 -7
  18. data/lib/abstract_controller.rb +12 -13
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +81 -40
  22. data/lib/action_controller/caching.rb +22 -62
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +30 -18
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +190 -47
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +40 -65
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  32. data/lib/action_controller/metal/exceptions.rb +19 -12
  33. data/lib/action_controller/metal/flash.rb +42 -9
  34. data/lib/action_controller/metal/force_ssl.rb +79 -19
  35. data/lib/action_controller/metal/head.rb +35 -10
  36. data/lib/action_controller/metal/helpers.rb +31 -21
  37. data/lib/action_controller/metal/http_authentication.rb +182 -134
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +28 -26
  40. data/lib/action_controller/metal/live.rb +312 -0
  41. data/lib/action_controller/metal/mime_responds.rb +159 -163
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +146 -93
  44. data/lib/action_controller/metal/redirecting.rb +80 -56
  45. data/lib/action_controller/metal/renderers.rb +119 -47
  46. data/lib/action_controller/metal/rendering.rb +89 -32
  47. data/lib/action_controller/metal/request_forgery_protection.rb +373 -41
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +39 -45
  50. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  51. data/lib/action_controller/metal/testing.rb +8 -29
  52. data/lib/action_controller/metal/url_for.rb +43 -32
  53. data/lib/action_controller/metal.rb +112 -106
  54. data/lib/action_controller/railtie.rb +56 -18
  55. data/lib/action_controller/railties/helpers.rb +24 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +402 -347
  59. data/lib/action_controller.rb +31 -30
  60. data/lib/action_dispatch/http/cache.rb +133 -34
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +40 -24
  63. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  64. data/lib/action_dispatch/http/headers.rb +117 -16
  65. data/lib/action_dispatch/http/mime_negotiation.rb +98 -33
  66. data/lib/action_dispatch/http/mime_type.rb +198 -146
  67. data/lib/action_dispatch/http/mime_types.rb +22 -7
  68. data/lib/action_dispatch/http/parameter_filter.rb +61 -49
  69. data/lib/action_dispatch/http/parameters.rb +94 -51
  70. data/lib/action_dispatch/http/rack_cache.rb +4 -3
  71. data/lib/action_dispatch/http/request.rb +262 -117
  72. data/lib/action_dispatch/http/response.rb +400 -86
  73. data/lib/action_dispatch/http/upload.rb +66 -29
  74. data/lib/action_dispatch/http/url.rb +232 -60
  75. data/lib/action_dispatch/journey/formatter.rb +189 -0
  76. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  79. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  80. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  83. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  84. data/lib/action_dispatch/journey/parser.rb +199 -0
  85. data/lib/action_dispatch/journey/parser.y +50 -0
  86. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  87. data/lib/action_dispatch/journey/path/pattern.rb +199 -0
  88. data/lib/action_dispatch/journey/route.rb +203 -0
  89. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  90. data/lib/action_dispatch/journey/router.rb +156 -0
  91. data/lib/action_dispatch/journey/routes.rb +82 -0
  92. data/lib/action_dispatch/journey/scanner.rb +64 -0
  93. data/lib/action_dispatch/journey/visitors.rb +268 -0
  94. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  95. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  96. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  97. data/lib/action_dispatch/journey.rb +7 -0
  98. data/lib/action_dispatch/middleware/callbacks.rb +17 -13
  99. data/lib/action_dispatch/middleware/cookies.rb +494 -162
  100. data/lib/action_dispatch/middleware/debug_exceptions.rb +176 -53
  101. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  102. data/lib/action_dispatch/middleware/exception_wrapper.rb +103 -38
  103. data/lib/action_dispatch/middleware/executor.rb +21 -0
  104. data/lib/action_dispatch/middleware/flash.rb +128 -91
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +43 -16
  106. data/lib/action_dispatch/middleware/reloader.rb +6 -83
  107. data/lib/action_dispatch/middleware/remote_ip.rb +151 -49
  108. data/lib/action_dispatch/middleware/request_id.rb +19 -15
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +38 -34
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +94 -44
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -4
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +36 -61
  114. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  115. data/lib/action_dispatch/middleware/stack.rb +33 -41
  116. data/lib/action_dispatch/middleware/static.rb +92 -48
  117. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +134 -5
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  136. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  138. data/lib/action_dispatch/railtie.rb +29 -8
  139. data/lib/action_dispatch/request/session.rb +234 -0
  140. data/lib/action_dispatch/request/utils.rb +78 -0
  141. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  142. data/lib/action_dispatch/routing/inspector.rb +225 -0
  143. data/lib/action_dispatch/routing/mapper.rb +1329 -582
  144. data/lib/action_dispatch/routing/polymorphic_routes.rb +237 -94
  145. data/lib/action_dispatch/routing/redirection.rb +120 -50
  146. data/lib/action_dispatch/routing/route_set.rb +545 -322
  147. data/lib/action_dispatch/routing/routes_proxy.rb +37 -7
  148. data/lib/action_dispatch/routing/url_for.rb +103 -34
  149. data/lib/action_dispatch/routing.rb +66 -99
  150. data/lib/action_dispatch/system_test_case.rb +147 -0
  151. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  152. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  153. data/lib/action_dispatch/system_testing/server.rb +31 -0
  154. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  158. data/lib/action_dispatch/testing/assertions/response.rb +53 -42
  159. data/lib/action_dispatch/testing/assertions/routing.rb +79 -74
  160. data/lib/action_dispatch/testing/assertions.rb +15 -9
  161. data/lib/action_dispatch/testing/integration.rb +361 -207
  162. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  163. data/lib/action_dispatch/testing/test_process.rb +28 -19
  164. data/lib/action_dispatch/testing/test_request.rb +30 -33
  165. data/lib/action_dispatch/testing/test_response.rb +35 -11
  166. data/lib/action_dispatch.rb +42 -32
  167. data/lib/action_pack/gem_version.rb +17 -0
  168. data/lib/action_pack/version.rb +7 -7
  169. data/lib/action_pack.rb +4 -2
  170. metadata +116 -175
  171. data/lib/abstract_controller/layouts.rb +0 -423
  172. data/lib/abstract_controller/view_paths.rb +0 -96
  173. data/lib/action_controller/caching/actions.rb +0 -185
  174. data/lib/action_controller/caching/fragments.rb +0 -127
  175. data/lib/action_controller/caching/pages.rb +0 -187
  176. data/lib/action_controller/caching/sweeping.rb +0 -97
  177. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  178. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  179. data/lib/action_controller/deprecated.rb +0 -3
  180. data/lib/action_controller/metal/compatibility.rb +0 -65
  181. data/lib/action_controller/metal/hide_actions.rb +0 -41
  182. data/lib/action_controller/metal/rack_delegation.rb +0 -26
  183. data/lib/action_controller/metal/responder.rb +0 -286
  184. data/lib/action_controller/metal/session_management.rb +0 -14
  185. data/lib/action_controller/middleware.rb +0 -39
  186. data/lib/action_controller/railties/paths.rb +0 -25
  187. data/lib/action_controller/record_identifier.rb +0 -85
  188. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  189. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  190. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  191. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  192. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  193. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  194. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  195. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  196. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  197. data/lib/action_dispatch/middleware/head.rb +0 -18
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -75
  199. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  200. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  201. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  202. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  203. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  204. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  205. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  206. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  207. data/lib/action_dispatch/testing/assertions/dom.rb +0 -37
  208. data/lib/action_dispatch/testing/assertions/selector.rb +0 -435
  209. data/lib/action_dispatch/testing/assertions/tag.rb +0 -138
  210. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  211. data/lib/action_view/asset_paths.rb +0 -142
  212. data/lib/action_view/base.rb +0 -220
  213. data/lib/action_view/buffers.rb +0 -43
  214. data/lib/action_view/context.rb +0 -36
  215. data/lib/action_view/flows.rb +0 -79
  216. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  217. data/lib/action_view/helpers/asset_paths.rb +0 -7
  218. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  219. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  220. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  221. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  222. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  223. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  224. data/lib/action_view/helpers/cache_helper.rb +0 -64
  225. data/lib/action_view/helpers/capture_helper.rb +0 -203
  226. data/lib/action_view/helpers/controller_helper.rb +0 -25
  227. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  228. data/lib/action_view/helpers/date_helper.rb +0 -1062
  229. data/lib/action_view/helpers/debug_helper.rb +0 -40
  230. data/lib/action_view/helpers/form_helper.rb +0 -1486
  231. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  232. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  233. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  234. data/lib/action_view/helpers/number_helper.rb +0 -622
  235. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  236. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  237. data/lib/action_view/helpers/rendering_helper.rb +0 -92
  238. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  239. data/lib/action_view/helpers/tag_helper.rb +0 -167
  240. data/lib/action_view/helpers/text_helper.rb +0 -426
  241. data/lib/action_view/helpers/translation_helper.rb +0 -91
  242. data/lib/action_view/helpers/url_helper.rb +0 -693
  243. data/lib/action_view/helpers.rb +0 -60
  244. data/lib/action_view/locale/en.yml +0 -160
  245. data/lib/action_view/log_subscriber.rb +0 -28
  246. data/lib/action_view/lookup_context.rb +0 -258
  247. data/lib/action_view/path_set.rb +0 -101
  248. data/lib/action_view/railtie.rb +0 -55
  249. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  250. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  251. data/lib/action_view/renderer/renderer.rb +0 -61
  252. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  253. data/lib/action_view/renderer/template_renderer.rb +0 -95
  254. data/lib/action_view/template/error.rb +0 -128
  255. data/lib/action_view/template/handlers/builder.rb +0 -26
  256. data/lib/action_view/template/handlers/erb.rb +0 -125
  257. data/lib/action_view/template/handlers.rb +0 -50
  258. data/lib/action_view/template/resolver.rb +0 -298
  259. data/lib/action_view/template/text.rb +0 -30
  260. data/lib/action_view/template.rb +0 -337
  261. data/lib/action_view/test_case.rb +0 -246
  262. data/lib/action_view/testing/resolvers.rb +0 -49
  263. data/lib/action_view.rb +0 -84
  264. data/lib/sprockets/assets.rake +0 -99
  265. data/lib/sprockets/bootstrap.rb +0 -37
  266. data/lib/sprockets/compressors.rb +0 -83
  267. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  268. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  269. data/lib/sprockets/helpers.rb +0 -6
  270. data/lib/sprockets/railtie.rb +0 -62
  271. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,81 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+
1
5
  module ActionDispatch
6
+ # This middleware calculates the IP address of the remote client that is
7
+ # making the request. It does this by checking various headers that could
8
+ # contain the address, and then picking the last-set address that is not
9
+ # on the list of trusted IPs. This follows the precedent set by e.g.
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]
12
+ # by @gingerlime. A more detailed explanation of the algorithm is given
13
+ # at GetIp#calculate_ip.
14
+ #
15
+ # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
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].
18
+ # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
19
+ # then you should test your Rack server to make sure your data is good.
20
+ #
21
+ # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
22
+ # This middleware assumes that there is at least one proxy sitting around
23
+ # and setting headers with the client's remote IP address. If you don't use
24
+ # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
25
+ # claim to have any IP address by setting the X-Forwarded-For header. If you
26
+ # care about that, then you need to explicitly drop or ignore those headers
27
+ # sometime before this middleware runs.
2
28
  class RemoteIp
3
- class IpSpoofAttackError < StandardError ; end
4
-
5
- # IP addresses that are "trusted proxies" that can be stripped from
6
- # the comma-delimited list in the X-Forwarded-For header. See also:
7
- # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
8
- TRUSTED_PROXIES = %r{
9
- ^127\.0\.0\.1$ | # localhost
10
- ^(10 | # private IP 10.x.x.x
11
- 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
12
- 192\.168 # private IP 192.168.x.x
13
- )\.
14
- }x
29
+ class IpSpoofAttackError < StandardError; end
30
+
31
+ # The default trusted IPs list simply includes IP addresses that are
32
+ # guaranteed by the IP specification to be private addresses. Those will
33
+ # not be the ultimate client IP in production, and so are discarded. See
34
+ # https://en.wikipedia.org/wiki/Private_network for details.
35
+ TRUSTED_PROXIES = [
36
+ "127.0.0.1", # localhost IPv4
37
+ "::1", # localhost IPv6
38
+ "fc00::/7", # private IPv6 range fc00::/7
39
+ "10.0.0.0/8", # private IPv4 range 10.x.x.x
40
+ "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
41
+ "192.168.0.0/16", # private IPv4 range 192.168.x.x
42
+ ].map { |proxy| IPAddr.new(proxy) }
15
43
 
16
44
  attr_reader :check_ip, :proxies
17
45
 
18
- def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
46
+ # Create a new +RemoteIp+ middleware instance.
47
+ #
48
+ # The +ip_spoofing_check+ option is on by default. When on, an exception
49
+ # is raised if it looks like the client is trying to lie about its own IP
50
+ # address. It makes sense to turn off this check on sites aimed at non-IP
51
+ # clients (like WAP devices), or behind proxies that set headers in an
52
+ # incorrect or confusing way (like AWS ELB).
53
+ #
54
+ # The +custom_proxies+ argument can take an Array of string, IPAddr, or
55
+ # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
56
+ # single string, IPAddr, or Regexp object is provided, it will be used in
57
+ # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
58
+ # want in the middle (or at the beginning) of the X-Forwarded-For list,
59
+ # with your proxy servers after it. If your proxies aren't removed, pass
60
+ # them in via the +custom_proxies+ parameter. That way, the middleware will
61
+ # ignore those IP addresses, and return the one that you want.
62
+ def initialize(app, ip_spoofing_check = true, custom_proxies = nil)
19
63
  @app = app
20
- @check_ip = check_ip_spoofing
21
- if custom_proxies
22
- custom_regexp = Regexp.new(custom_proxies)
23
- @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp)
64
+ @check_ip = ip_spoofing_check
65
+ @proxies = if custom_proxies.blank?
66
+ TRUSTED_PROXIES
67
+ elsif custom_proxies.respond_to?(:any?)
68
+ custom_proxies
24
69
  else
25
- @proxies = TRUSTED_PROXIES
70
+ Array(custom_proxies) + TRUSTED_PROXIES
26
71
  end
27
72
  end
28
73
 
74
+ # Since the IP address may not be needed, we store the object here
75
+ # without calculating the IP to keep from slowing down the majority of
76
+ # requests. For those requests that do need to know the IP, the
77
+ # GetIp#calculate_ip method will calculate the memoized client IP address.
29
78
  def call(env)
30
- env["action_dispatch.remote_ip"] = GetIp.new(env, self)
31
- @app.call(env)
79
+ req = ActionDispatch::Request.new env
80
+ req.remote_ip = GetIp.new(req, check_ip, proxies)
81
+ @app.call(req.env)
32
82
  end
33
83
 
84
+ # The GetIp class exists as a way to defer processing of the request data
85
+ # into an actual IP address. If the ActionDispatch::Request#remote_ip method
86
+ # is called, this class will calculate the value and then memoize it.
34
87
  class GetIp
35
- def initialize(env, middleware)
36
- @env = env
37
- @middleware = middleware
38
- @calculated_ip = false
88
+ def initialize(req, check_ip, proxies)
89
+ @req = req
90
+ @check_ip = check_ip
91
+ @proxies = proxies
39
92
  end
40
93
 
41
- # Determines originating IP address. REMOTE_ADDR is the standard
42
- # but will be wrong if the user is behind a proxy. Proxies will set
43
- # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
44
- # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
45
- # multiple chained proxies. The last address which is not a known proxy
46
- # will be the originating IP.
94
+ # Sort through the various IP address headers, looking for the IP most
95
+ # likely to be the address of the actual remote client making this
96
+ # request.
97
+ #
98
+ # REMOTE_ADDR will be correct if the request is made directly against the
99
+ # Ruby process, on e.g. Heroku. When the request is proxied by another
100
+ # server like HAProxy or NGINX, the IP address that made the original
101
+ # request will be put in an X-Forwarded-For header. If there are multiple
102
+ # proxies, that header may contain a list of IPs. Other proxy services
103
+ # set the Client-Ip header instead, so we check that too.
104
+ #
105
+ # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
106
+ # while the first IP in the list is likely to be the "originating" IP,
107
+ # it could also have been set by the client maliciously.
108
+ #
109
+ # In order to find the first address that is (probably) accurate, we
110
+ # take the list of IPs, remove known and trusted proxies, and then take
111
+ # the last address left, which was presumably set by one of those proxies.
47
112
  def calculate_ip
48
- client_ip = @env['HTTP_CLIENT_IP']
49
- forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR')
50
- remote_addrs = ips_from('REMOTE_ADDR')
113
+ # Set by the Rack web server, this is a single value.
114
+ remote_addr = ips_from(@req.remote_addr).last
51
115
 
52
- check_ip = client_ip && forwarded_ips.present? && @middleware.check_ip
53
- if check_ip && !forwarded_ips.include?(client_ip)
116
+ # Could be a CSV list and/or repeated headers that were concatenated.
117
+ client_ips = ips_from(@req.client_ip).reverse
118
+ forwarded_ips = ips_from(@req.x_forwarded_for).reverse
119
+
120
+ # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
121
+ # If they are both set, it means that either:
122
+ #
123
+ # 1) This request passed through two proxies with incompatible IP header
124
+ # conventions.
125
+ # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+
126
+ # (whichever the proxy servers weren't using) themselves.
127
+ #
128
+ # Either way, there is no way for us to determine which header is the
129
+ # right one after the fact. Since we have no idea, if we are concerned
130
+ # about IP spoofing we need to give up and explode. (If you're not
131
+ # concerned about IP spoofing you can turn the +ip_spoofing_check+
132
+ # option off.)
133
+ should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
134
+ if should_check_ip && !forwarded_ips.include?(client_ips.last)
54
135
  # We don't know which came from the proxy, and which from the user
55
- raise IpSpoofAttackError, "IP spoofing attack?!" \
56
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
57
- "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
136
+ raise IpSpoofAttackError, "IP spoofing attack?! " \
137
+ "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
138
+ "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
58
139
  end
59
140
 
60
- not_proxy = client_ip || forwarded_ips.last || remote_addrs.first
141
+ # We assume these things about the IP headers:
142
+ #
143
+ # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
144
+ # - Client-Ip is propagated from the outermost proxy, or is blank
145
+ # - REMOTE_ADDR will be the IP that made the request to Rack
146
+ ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
61
147
 
62
- # Return first REMOTE_ADDR if there are no other options
63
- not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
148
+ # If every single IP option is in the trusted list, just return REMOTE_ADDR
149
+ filter_proxies(ips).first || remote_addr
64
150
  end
65
151
 
152
+ # Memoizes the value returned by #calculate_ip and returns it for
153
+ # ActionDispatch::Request to use.
66
154
  def to_s
67
- return @ip if @calculated_ip
68
- @calculated_ip = true
69
- @ip = calculate_ip
155
+ @ip ||= calculate_ip
70
156
  end
71
157
 
72
- protected
158
+ private
73
159
 
74
- def ips_from(header, allow_proxies = false)
75
- ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
76
- allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
160
+ def ips_from(header) # :doc:
161
+ return [] unless header
162
+ # Split the comma-separated list into an array of strings.
163
+ ips = header.strip.split(/[,\s]+/)
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
173
+ end
77
174
  end
78
- end
79
175
 
176
+ def filter_proxies(ips) # :doc:
177
+ ips.reject do |ip|
178
+ @proxies.any? { |proxy| proxy === ip }
179
+ end
180
+ end
181
+ end
80
182
  end
81
183
  end
@@ -1,39 +1,43 @@
1
- require 'securerandom'
2
- require 'active_support/core_ext/string/access'
3
- require 'active_support/core_ext/object/blank'
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "active_support/core_ext/string/access"
4
5
 
5
6
  module ActionDispatch
6
- # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
7
- # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
7
+ # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
8
+ # through <tt>ActionDispatch::Request#request_id</tt> or the alias <tt>ActionDispatch::Request#uuid</tt>) and sends
9
+ # the same id to the client via the X-Request-Id header.
8
10
  #
9
- # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
11
+ # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
10
12
  # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
11
13
  # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
12
14
  #
13
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
14
16
  # from multiple pieces of the stack.
15
17
  class RequestId
18
+ X_REQUEST_ID = "X-Request-Id".freeze #:nodoc:
19
+
16
20
  def initialize(app)
17
21
  @app = app
18
22
  end
19
23
 
20
24
  def call(env)
21
- env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
22
- status, headers, body = @app.call(env)
23
-
24
- headers["X-Request-Id"] = env["action_dispatch.request_id"]
25
- [ status, headers, body ]
25
+ 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 }
26
28
  end
27
29
 
28
30
  private
29
- def external_request_id(env)
30
- if request_id = env["HTTP_X_REQUEST_ID"].presence
31
- request_id.gsub(/[^\w\-]/, "").first(255)
31
+ def make_request_id(request_id)
32
+ if request_id.presence
33
+ request_id.gsub(/[^\w\-@]/, "".freeze).first(255)
34
+ else
35
+ internal_request_id
32
36
  end
33
37
  end
34
38
 
35
39
  def internal_request_id
36
- SecureRandom.hex(16)
40
+ SecureRandom.uuid
37
41
  end
38
42
  end
39
43
  end
@@ -1,47 +1,44 @@
1
- require 'rack/utils'
2
- require 'rack/request'
3
- require 'rack/session/abstract/id'
4
- require 'action_dispatch/middleware/cookies'
5
- require 'active_support/core_ext/object/blank'
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/utils"
4
+ require "rack/request"
5
+ require "rack/session/abstract/id"
6
+ require "action_dispatch/middleware/cookies"
7
+ require "action_dispatch/request/session"
6
8
 
7
9
  module ActionDispatch
8
10
  module Session
9
11
  class SessionRestoreError < StandardError #:nodoc:
10
- end
11
-
12
- module DestroyableSession
13
- def destroy
14
- clear
15
- options = @env[Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY] if @env
16
- options ||= {}
17
- @by.send(:destroy_session, @env, options[:id], options) if @by
18
- options[:id] = nil
19
- @loaded = false
12
+ def initialize
13
+ super("Session contains objects whose class definition isn't available.\n" \
14
+ "Remember to require the classes for all objects kept in the session.\n" \
15
+ "(Original exception: #{$!.message} [#{$!.class}])\n")
16
+ set_backtrace $!.backtrace
20
17
  end
21
18
  end
22
19
 
23
- ::Rack::Session::Abstract::SessionHash.send :include, DestroyableSession
24
-
25
20
  module Compatibility
26
21
  def initialize(app, options = {})
27
- options[:key] ||= '_session_id'
28
- # FIXME Rack's secret is not being used
29
- options[:secret] ||= SecureRandom.hex(30)
22
+ options[:key] ||= "_session_id"
30
23
  super
31
24
  end
32
25
 
33
26
  def generate_sid
34
27
  sid = SecureRandom.hex(16)
35
- sid.encode!('UTF-8') if sid.respond_to?(:encode!)
28
+ sid.encode!(Encoding::UTF_8)
36
29
  sid
37
30
  end
38
31
 
39
- protected
32
+ private
40
33
 
41
- def initialize_sid
34
+ def initialize_sid # :doc:
42
35
  @default_options.delete(:sidbits)
43
36
  @default_options.delete(:secure_random)
44
37
  end
38
+
39
+ def make_request(env)
40
+ ActionDispatch::Request.new env
41
+ end
45
42
  end
46
43
 
47
44
  module StaleSessionCheck
@@ -58,13 +55,10 @@ module ActionDispatch
58
55
  rescue ArgumentError => argument_error
59
56
  if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
60
57
  begin
61
- # Note that the regexp does not allow $1 to end with a ':'
58
+ # Note that the regexp does not allow $1 to end with a ':'.
62
59
  $1.constantize
63
- rescue LoadError, NameError => const_error
64
- raise ActionDispatch::Session::SessionRestoreError,
65
- "Session contains objects whose class definition isn't available.\n" +
66
- "Remember to require the classes for all objects kept in the session.\n" +
67
- "(Original exception: #{const_error.message} [#{const_error.class}])\n"
60
+ rescue LoadError, NameError
61
+ raise ActionDispatch::Session::SessionRestoreError
68
62
  end
69
63
  retry
70
64
  else
@@ -73,16 +67,26 @@ module ActionDispatch
73
67
  end
74
68
  end
75
69
 
76
- class AbstractStore < Rack::Session::Abstract::ID
70
+ module SessionObject # :nodoc:
71
+ def prepare_session(req)
72
+ Request::Session.create(self, req, @default_options)
73
+ end
74
+
75
+ def loaded_session?(session)
76
+ !session.is_a?(Request::Session) || session.loaded?
77
+ end
78
+ end
79
+
80
+ class AbstractStore < Rack::Session::Abstract::Persisted
77
81
  include Compatibility
78
82
  include StaleSessionCheck
83
+ include SessionObject
79
84
 
80
85
  private
81
86
 
82
- def set_cookie(env, session_id, cookie)
83
- request = ActionDispatch::Request.new(env)
84
- request.cookie_jar[key] = cookie
85
- end
87
+ def set_cookie(request, session_id, cookie)
88
+ request.cookie_jar[key] = cookie
89
+ end
86
90
  end
87
91
  end
88
92
  end
@@ -1,13 +1,18 @@
1
- require 'action_dispatch/middleware/session/abstract_store'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/middleware/session/abstract_store"
2
4
 
3
5
  module ActionDispatch
4
6
  module Session
5
- # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
7
+ # A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
6
8
  # if you don't store critical data in your sessions and you don't need them to live for extended periods
7
9
  # of time.
10
+ #
11
+ # ==== Options
12
+ # * <tt>cache</tt> - The cache to use. If it is not specified, <tt>Rails.cache</tt> will be used.
13
+ # * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring.
14
+ # By default, the <tt>:expires_in</tt> option of the cache is used.
8
15
  class CacheStore < AbstractStore
9
- # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
10
- # not specified, <tt>Rails.cache</tt> will be used.
11
16
  def initialize(app, options = {})
12
17
  @cache = options[:cache] || Rails.cache
13
18
  options[:expire_after] ||= @cache.options[:expires_in]
@@ -15,18 +20,18 @@ module ActionDispatch
15
20
  end
16
21
 
17
22
  # Get a session from the cache.
18
- def get_session(env, sid)
19
- unless sid and session = @cache.read(cache_key(sid))
23
+ def find_session(env, sid)
24
+ unless sid && (session = @cache.read(cache_key(sid)))
20
25
  sid, session = generate_sid, {}
21
26
  end
22
27
  [sid, session]
23
28
  end
24
29
 
25
30
  # Set a session in the cache.
26
- def set_session(env, sid, session, options)
31
+ def write_session(env, sid, session, options)
27
32
  key = cache_key(sid)
28
33
  if session
29
- @cache.write(key, session, :expires_in => options[:expire_after])
34
+ @cache.write(key, session, expires_in: options[:expire_after])
30
35
  else
31
36
  @cache.delete(key)
32
37
  end
@@ -34,7 +39,7 @@ module ActionDispatch
34
39
  end
35
40
 
36
41
  # Remove a session from the cache.
37
- def destroy_session(env, sid, options)
42
+ def delete_session(env, sid, options)
38
43
  @cache.delete(cache_key(sid))
39
44
  generate_sid
40
45
  end
@@ -1,68 +1,118 @@
1
- require 'active_support/core_ext/hash/keys'
2
- require 'active_support/core_ext/object/blank'
3
- require 'action_dispatch/middleware/session/abstract_store'
4
- require 'rack/session/cookie'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+ require "action_dispatch/middleware/session/abstract_store"
5
+ require "rack/session/cookie"
5
6
 
6
7
  module ActionDispatch
7
8
  module Session
8
- # This cookie-based session store is the Rails default. Sessions typically
9
- # contain at most a user_id and flash message; both fit within the 4K cookie
10
- # size limit. Cookie-based sessions are dramatically faster than the
11
- # alternatives.
9
+ # This cookie-based session store is the Rails default. It is
10
+ # dramatically faster than the alternatives.
11
+ #
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.
15
+ #
16
+ # The cookie jar used for storage is automatically configured to be the
17
+ # best possible option given your application's configuration.
12
18
  #
13
- # If you have more than 4K of session data or don't want your data to be
14
- # visible to the user, pick another session store.
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.
15
23
  #
16
- # CookieOverflow is raised if you attempt to store more than 4K of data.
24
+ # Your cookies will be encrypted using your apps secret_key_base. This
25
+ # goes a step further than signed cookies in that encrypted cookies cannot
26
+ # be altered or read by users. This is the default starting in Rails 4.
17
27
  #
18
- # A message digest is included with the cookie to ensure data integrity:
19
- # a user cannot alter his +user_id+ without knowing the secret key
20
- # included in the hash. New apps are generated with a pregenerated secret
21
- # in config/environment.rb. Set your own for old apps you're upgrading.
28
+ # Configure your session store in <tt>config/initializers/session_store.rb</tt>:
22
29
  #
23
- # Session options:
30
+ # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
24
31
  #
25
- # * <tt>:secret</tt>: An application-wide key string. It's important that
26
- # the secret is not vulnerable to a dictionary attack. Therefore, you
27
- # should choose a secret consisting of random numbers and letters and
28
- # more than 30 characters.
32
+ # In the development and test environments your application's secret key base is
33
+ # generated by Rails and stored in a temporary file in <tt>tmp/development_secret.txt</tt>.
34
+ # In all other environments, it is stored encrypted in the
35
+ # <tt>config/credentials.yml.enc</tt> file.
29
36
  #
30
- # :secret => '449fe2e7daee471bffae2fd8dc02313d'
37
+ # If your application was not updated to Rails 5.2 defaults, the secret_key_base
38
+ # will be found in the old <tt>config/secrets.yml</tt> file.
31
39
  #
32
- # * <tt>:digest</tt>: The message digest algorithm used to verify session
33
- # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
34
- # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
40
+ # Note that changing your secret_key_base will invalidate all existing session.
41
+ # Additionally, you should take care to make sure you are not relying on the
42
+ # ability to decode signed cookies generated by your app in external
43
+ # applications or JavaScript before changing it.
35
44
  #
36
- # To generate a secret key for an existing application, run
37
- # "rake secret" and set the key in config/initializers/secret_token.rb.
45
+ # Because CookieStore extends Rack::Session::Abstract::Persisted, many of the
46
+ # options described there can be used to customize the session cookie that
47
+ # is generated. For example:
38
48
  #
39
- # Note that changing digest or secret invalidates all existing sessions!
40
- class CookieStore < Rack::Session::Cookie
41
- include Compatibility
42
- include StaleSessionCheck
49
+ # Rails.application.config.session_store :cookie_store, expire_after: 14.days
50
+ #
51
+ # would set the session cookie to expire automatically 14 days after creation.
52
+ # Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
53
+ # <tt>:httponly</tt>.
54
+ class CookieStore < AbstractStore
55
+ def initialize(app, options = {})
56
+ super(app, options.merge!(cookie_only: true))
57
+ end
58
+
59
+ def delete_session(req, session_id, options)
60
+ new_sid = generate_sid unless options[:drop]
61
+ # Reset hash and Assign the new session id
62
+ req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid } : {})
63
+ new_sid
64
+ end
65
+
66
+ def load_session(req)
67
+ stale_session_check! do
68
+ data = unpacked_cookie_data(req)
69
+ data = persistent_session_id!(data)
70
+ [data["session_id"], data]
71
+ end
72
+ end
43
73
 
44
74
  private
45
75
 
46
- def unpacked_cookie_data(env)
47
- env["action_dispatch.request.unsigned_session_cookie"] ||= begin
76
+ def extract_session_id(req)
48
77
  stale_session_check! do
49
- request = ActionDispatch::Request.new(env)
50
- if data = request.cookie_jar.signed[@key]
51
- data.stringify_keys!
78
+ unpacked_cookie_data(req)["session_id"]
79
+ end
80
+ end
81
+
82
+ def unpacked_cookie_data(req)
83
+ req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
84
+ v = stale_session_check! do
85
+ if data = get_cookie(req)
86
+ data.stringify_keys!
87
+ end
88
+ data || {}
52
89
  end
53
- data || {}
90
+ req.set_header k, v
54
91
  end
55
92
  end
56
- end
57
93
 
58
- def set_session(env, sid, session_data, options)
59
- session_data.merge!("session_id" => sid)
60
- end
94
+ def persistent_session_id!(data, sid = nil)
95
+ data ||= {}
96
+ data["session_id"] ||= sid || generate_sid
97
+ data
98
+ end
61
99
 
62
- def set_cookie(env, session_id, cookie)
63
- request = ActionDispatch::Request.new(env)
64
- request.cookie_jar.signed[@key] = cookie
65
- end
100
+ def write_session(req, sid, session_data, options)
101
+ session_data["session_id"] = sid
102
+ session_data
103
+ end
104
+
105
+ def set_cookie(request, session_id, cookie)
106
+ cookie_jar(request)[@key] = cookie
107
+ end
108
+
109
+ def get_cookie(req)
110
+ cookie_jar(req)[@key]
111
+ end
112
+
113
+ def cookie_jar(request)
114
+ request.cookie_jar.signed_or_encrypted
115
+ end
66
116
  end
67
117
  end
68
118
  end