static-rails 0.0.4 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14d31f3821438735e307f98dd5f4a48215d0b1d678915369a7abd459b3b40145
4
- data.tar.gz: bc7b15c689693e533a75ac378db060396cde1839edb0acffc04c30e18872bf77
3
+ metadata.gz: 6745d74377412999963201634328706d263efcb29eb445a71c84cd427b618084
4
+ data.tar.gz: 9ffe84b1fc78ada36ab6699cb1826815e8da0fc0cdbe266c952f563a41c577a6
5
5
  SHA512:
6
- metadata.gz: f645e5e515d712acd3c1a81935d560d3ef0f2be3a42e5fd42af4320cf6a4ae25e14843b0c076dfda4ec5b980a12cc436cfbef527768704f97c6e89ac2e7e17f6
7
- data.tar.gz: 61600757a2e549dc5bff49b8fe01da9c0f45eac4a1fd6ba815ea4aafb5655270bfa457ac76c98a0927b98e61bcf741b40a0089f0cfb55f612ad0de386b0fa9d4
6
+ metadata.gz: 679620aed3269f571bbec98a28537a34e03240e7df57319e34e0c100b85332cdbbbc6a01cf34d62552edbbc971411a881da57a33b54ede1d64b4c0833b7fbc6a
7
+ data.tar.gz: a89bc51bded625662eb00666c0d3c2f1034f9d397a84e86d7e30136398d0e53e60d0e8287cce0cda3baea0dc5c7ef037bc629635e7c9854a58b5c8376ddafa2d
@@ -1,3 +1,31 @@
1
+ ## 0.0.9
2
+
3
+ * When using CSRF protection, the artificial path info will now be
4
+ "__static_rails__" instead of a random string, to make logs appear cleaner
5
+ * Attempt to guard against future internal changes to Rails' request forgery
6
+ protection by adding `method_missing` that calls through
7
+
8
+ ## 0.0.8
9
+
10
+ * Add support for the [CSRF
11
+ changes](https://github.com/rails/rails/commit/358ff18975f26e820ea355ec113ffc5228e59af8) in Rails 6.0.3.1
12
+
13
+ ## 0.0.7
14
+
15
+ * Ensure that CSRF tokens are valid, at the cost of some performance and
16
+ reliance on additional Rails internals. As a result CSRF cookie setting is now
17
+ disabled by default [#6](https://github.com/testdouble/static-rails/pull/6)
18
+
19
+ ## 0.0.6
20
+
21
+ * Fix an issue where `ActionDispatch::FileHandler` won't be loaded in the event
22
+ that static-rails is serving compiled assets but Rails is not
23
+
24
+ ## 0.0.5
25
+
26
+ * Add a site option `compile_404_file_path` to specify a file to be used as a
27
+ 404 page when serving compiled assets and no file is found
28
+
1
29
  ## 0.0.4
2
30
 
3
31
  * Add a cookie named `_csrf_token` by default to all static site requests, so
@@ -1,27 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- static-rails (0.0.4)
4
+ static-rails (0.0.9)
5
5
  rack-proxy (~> 0.6)
6
6
  railties (>= 5.0.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actionpack (6.0.3)
12
- actionview (= 6.0.3)
13
- activesupport (= 6.0.3)
11
+ actionpack (6.0.3.1)
12
+ actionview (= 6.0.3.1)
13
+ activesupport (= 6.0.3.1)
14
14
  rack (~> 2.0, >= 2.0.8)
15
15
  rack-test (>= 0.6.3)
16
16
  rails-dom-testing (~> 2.0)
17
17
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
18
- actionview (6.0.3)
19
- activesupport (= 6.0.3)
18
+ actionview (6.0.3.1)
19
+ activesupport (= 6.0.3.1)
20
20
  builder (~> 3.1)
21
21
  erubi (~> 1.4)
22
22
  rails-dom-testing (~> 2.0)
23
23
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
24
- activesupport (6.0.3)
24
+ activesupport (6.0.3.1)
25
25
  concurrent-ruby (~> 1.0, >= 1.0.2)
26
26
  i18n (>= 0.7, < 2)
27
27
  minitest (~> 5.1)
@@ -32,19 +32,18 @@ GEM
32
32
  concurrent-ruby (1.1.6)
33
33
  crass (1.0.6)
34
34
  erubi (1.9.0)
35
- i18n (1.8.2)
35
+ i18n (1.8.3)
36
36
  concurrent-ruby (~> 1.0)
37
- jaro_winkler (1.5.4)
38
37
  loofah (2.5.0)
39
38
  crass (~> 1.0.2)
40
39
  nokogiri (>= 1.5.9)
41
40
  method_source (1.0.0)
42
41
  mini_portile2 (2.4.0)
43
- minitest (5.14.0)
42
+ minitest (5.14.1)
44
43
  nokogiri (1.10.9)
45
44
  mini_portile2 (~> 2.4.0)
46
45
  parallel (1.19.1)
47
- parser (2.7.1.1)
46
+ parser (2.7.1.3)
48
47
  ast (~> 2.4.0)
49
48
  rack (2.2.2)
50
49
  rack-proxy (0.6.5)
@@ -56,34 +55,38 @@ GEM
56
55
  nokogiri (>= 1.6)
57
56
  rails-html-sanitizer (1.3.0)
58
57
  loofah (~> 2.3)
59
- railties (6.0.3)
60
- actionpack (= 6.0.3)
61
- activesupport (= 6.0.3)
58
+ railties (6.0.3.1)
59
+ actionpack (= 6.0.3.1)
60
+ activesupport (= 6.0.3.1)
62
61
  method_source
63
62
  rake (>= 0.8.7)
64
63
  thor (>= 0.20.3, < 2.0)
65
64
  rainbow (3.0.0)
66
65
  rake (13.0.1)
66
+ regexp_parser (1.7.1)
67
67
  rexml (3.2.4)
68
- rubocop (0.80.1)
69
- jaro_winkler (~> 1.5.1)
68
+ rubocop (0.85.1)
70
69
  parallel (~> 1.10)
71
70
  parser (>= 2.7.0.1)
72
71
  rainbow (>= 2.2.2, < 4.0)
72
+ regexp_parser (>= 1.7)
73
73
  rexml
74
+ rubocop-ast (>= 0.0.3)
74
75
  ruby-progressbar (~> 1.7)
75
- unicode-display_width (>= 1.4.0, < 1.7)
76
- rubocop-performance (1.5.2)
76
+ unicode-display_width (>= 1.4.0, < 2.0)
77
+ rubocop-ast (0.0.3)
78
+ parser (>= 2.7.0.1)
79
+ rubocop-performance (1.6.1)
77
80
  rubocop (>= 0.71.0)
78
81
  ruby-progressbar (1.10.1)
79
- standard (0.2.5)
80
- rubocop (~> 0.80.1)
81
- rubocop-performance (~> 1.5.2)
82
+ standard (0.4.7)
83
+ rubocop (~> 0.85.0)
84
+ rubocop-performance (~> 1.6.0)
82
85
  thor (1.0.1)
83
86
  thread_safe (0.3.6)
84
87
  tzinfo (1.2.7)
85
88
  thread_safe (~> 0.1)
86
- unicode-display_width (1.6.1)
89
+ unicode-display_width (1.7.0)
87
90
  zeitwerk (2.3.0)
88
91
 
89
92
  PLATFORMS
data/README.md CHANGED
@@ -84,13 +84,14 @@ overall behavior of the gem itself, across all your static sites:
84
84
  `proxy_requests` is true, that the gem will wait for a response from a static
85
85
  site's server on any given request before timing out and raising an error
86
86
 
87
- * **config.set_csrf_token_cookie** (Default: `true`) when true, the gem's
87
+ * **config.set_csrf_token_cookie** (Default: `false`) when true, the gem's
88
88
  middleware will set a cookie named `_csrf_token` with each request of your
89
89
  static site. You can use this to set the `'x-csrf-token'` header on any
90
90
  requests from your site back to routes hosted by the Rails app that are
91
91
  [protected from CSRF
92
92
  forgery](https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf)
93
- (if you're not using Rails' cookie store for sessions, turn this off)
93
+ (if you're not using Rails' cookie store for sessions or you're okay with API
94
+ calls bypassing Rails CSRF, leave this off)
94
95
 
95
96
  ### Configuring your static sites themselves
96
97
 
@@ -153,6 +154,10 @@ To tell the gem about your static sites, assign an array of hashes as `sites`
153
154
  path to which production assets are compiled, relative to the site's
154
155
  `source_dir`
155
156
 
157
+ * **compile_404_file_path** (Optional) When `serve_compiled_assets` is true,
158
+ this file (relative to the `compile_dir`) will be served whenever the
159
+ request's path does not match a file on disk
160
+
156
161
  ## Configuring your static site generators
157
162
 
158
163
  Assuming you won't be mounting your static site to your app's root `/` path,
@@ -12,7 +12,7 @@ StaticRails.config do |config|
12
12
  # When true, both the proxy & static asset middleware will set a cookie
13
13
  # named "_csrf_token" to the Rails CSRF token, allowing any client-side
14
14
  # API requests to take advantage of Rails' request forgery protection
15
- # config.set_csrf_token_cookie = true
15
+ # config.set_csrf_token_cookie = false
16
16
 
17
17
  # The list of static sites you are hosting with static-rails.
18
18
  # Note that order matters! Request will be forwarded to the first site that
@@ -66,6 +66,9 @@ StaticRails.config do |config|
66
66
  #
67
67
  # # The destination of production-compiled assets, relative to Rails root
68
68
  # compile_dir: "static/blog/dist"
69
+ #
70
+ # # A 404 page to be sent when serving compiled assets and no file matches
71
+ # compile_404_file_path: "404.html"
69
72
  # },
70
73
  ]
71
74
  end
@@ -13,10 +13,10 @@ module StaticRails
13
13
  # When Rails invokes our Railtie, we'll save off a reference to the Rails app
14
14
  attr_accessor :app
15
15
 
16
- # When true, the ProxyMiddleware will be added
16
+ # When true, our middleware will proxy requests to static site servers
17
17
  attr_accessor :proxy_requests
18
18
 
19
- # When true, the StaticMiddleware will be added
19
+ # When true, our middleware will serve sites' compiled asset files
20
20
  attr_accessor :serve_compiled_assets
21
21
 
22
22
  # Number of seconds to wait on sites to confirm servers are ready
@@ -30,7 +30,7 @@ module StaticRails
30
30
  @proxy_requests = !Rails.env.production?
31
31
  @serve_compiled_assets = Rails.env.production?
32
32
  @ping_server_timeout = 5
33
- @set_csrf_token_cookie = true
33
+ @set_csrf_token_cookie = false
34
34
  end
35
35
 
36
36
  attr_reader :sites
@@ -1,15 +1,31 @@
1
+ require_relative "request_forgery_protection_fallback"
2
+
1
3
  module StaticRails
2
4
  class GetsCsrfToken
5
+ include RequestForgeryProtectionFallback
6
+
3
7
  def call(req)
4
8
  masked_authenticity_token(req.session)
5
9
  end
6
10
 
7
11
  private
8
12
 
13
+ def csrf_token_hmac(session, identifier)
14
+ ActionController::RequestForgeryProtection.instance_method(:csrf_token_hmac).bind(self).call(session, identifier)
15
+ end
16
+
17
+ def mask_token(raw_token)
18
+ ActionController::RequestForgeryProtection.instance_method(:mask_token).bind(self).call(raw_token)
19
+ end
20
+
9
21
  def masked_authenticity_token(session, form_options: {})
10
22
  ActionController::RequestForgeryProtection.instance_method(:masked_authenticity_token).bind(self).call(session, form_options)
11
23
  end
12
24
 
25
+ def global_csrf_token(session)
26
+ ActionController::RequestForgeryProtection.instance_method(:global_csrf_token).bind(self).call(session)
27
+ end
28
+
13
29
  def real_csrf_token(session)
14
30
  ActionController::RequestForgeryProtection.instance_method(:real_csrf_token).bind(self).call(session)
15
31
  end
@@ -0,0 +1,19 @@
1
+ module StaticRails
2
+ module RequestForgeryProtectionFallback
3
+ def method_missing(method_name, *args, **kwargs, &blk)
4
+ if respond_to?(method_name)
5
+ ActionController::RequestForgeryProtection.instance_method(method_name).bind(self).call(*args, **kwargs, &blk)
6
+ else
7
+ super
8
+ end
9
+ end
10
+
11
+ def respond_to?(method_name, *args)
12
+ ActionController::RequestForgeryProtection.instance_method(method_name) || super
13
+ end
14
+
15
+ def respond_to_missing?(method_name, *args)
16
+ ActionController::RequestForgeryProtection.instance_method(method_name) || super
17
+ end
18
+ end
19
+ end
@@ -14,6 +14,7 @@ module StaticRails
14
14
  :server_path,
15
15
  :compile_command,
16
16
  :compile_dir,
17
+ :compile_404_file_path,
17
18
  keyword_init: true
18
19
  )
19
20
 
@@ -1,11 +1,10 @@
1
1
  require_relative "proxy_middleware"
2
2
  require_relative "static_middleware"
3
3
  require_relative "determines_whether_to_handle_request"
4
- require_relative "gets_csrf_token"
5
4
 
6
5
  module StaticRails
7
6
  class SiteMiddleware
8
- PATH_INFO_OBFUSCATION = "JujJVj31M3SpzTjIGBJ2-3iE0lKXOIOlbLuk9Lxwe-Ll2uLuwH5KD8dmt1MqyZ"
7
+ PATH_INFO_OBFUSCATION = "__static-rails__"
9
8
 
10
9
  def initialize(app)
11
10
  @app = app
@@ -17,7 +16,7 @@ module StaticRails
17
16
  def call(env)
18
17
  return @app.call(env) unless @determines_whether_to_handle_request.call(env)
19
18
 
20
- if require_csrf_before_processing_request? && !csrf_token_is_set?(env)
19
+ if require_csrf_before_processing_request?
21
20
  # You might be asking yourself what the hell is going on here. In short,
22
21
  # This middleware sits at the top of the stack, which is too early to
23
22
  # set a CSRF token in a cookie. Therefore, we've placed a subclass of
@@ -41,7 +40,7 @@ module StaticRails
41
40
  #
42
41
  # (By the way, this was all Matthew Draper's bright idea. You can
43
42
  # compliment him here: https://github.com/matthewd )
44
- @app.call(env.merge("PATH_INFO" => env["PATH_INFO"] + PATH_INFO_OBFUSCATION))
43
+ @app.call(env.merge("PATH_INFO" => PATH_INFO_OBFUSCATION + env["PATH_INFO"]))
45
44
  elsif StaticRails.config.proxy_requests
46
45
  @proxy_middleware.call(env)
47
46
  elsif StaticRails.config.serve_compiled_assets
@@ -55,9 +54,5 @@ module StaticRails
55
54
  def require_csrf_before_processing_request?
56
55
  StaticRails.config.set_csrf_token_cookie
57
56
  end
58
-
59
- def csrf_token_is_set?(env)
60
- Rack::Request.new(env).cookies.has_key?("_csrf_token")
61
- end
62
57
  end
63
58
  end
@@ -1,30 +1,34 @@
1
1
  require_relative "site_middleware"
2
2
  require_relative "determines_whether_to_handle_request"
3
+ require_relative "validates_csrf_token"
3
4
  require_relative "gets_csrf_token"
4
5
 
5
6
  module StaticRails
6
7
  class SitePlusCsrfMiddleware < SiteMiddleware
7
8
  def initialize(app)
8
9
  @determines_whether_to_handle_request = DeterminesWhetherToHandleRequest.new
10
+ @validates_csrf_token = ValidatesCsrfToken.new
9
11
  @gets_csrf_token = GetsCsrfToken.new
10
12
  super
11
13
  end
12
14
 
13
15
  def call(env)
14
- return @app.call(env) unless @determines_whether_to_handle_request.call(env)
16
+ return @app.call(env) unless env["PATH_INFO"]&.start_with?(PATH_INFO_OBFUSCATION) || @determines_whether_to_handle_request.call(env)
15
17
 
16
18
  env = env.merge(
17
- "PATH_INFO" => env["PATH_INFO"].gsub(/#{PATH_INFO_OBFUSCATION}/, "")
19
+ "PATH_INFO" => env["PATH_INFO"].gsub(/^#{PATH_INFO_OBFUSCATION}/, "")
18
20
  )
19
21
  status, headers, body = super(env)
20
22
 
21
23
  if StaticRails.config.set_csrf_token_cookie
22
24
  req = Rack::Request.new(env)
23
25
  res = Rack::Response.new(body, status, headers)
24
- res.set_cookie("_csrf_token", {
25
- value: @gets_csrf_token.call(req),
26
- path: "/"
27
- })
26
+ if needs_new_csrf_token?(req)
27
+ res.set_cookie("_csrf_token", {
28
+ value: @gets_csrf_token.call(req),
29
+ path: "/"
30
+ })
31
+ end
28
32
  res.finish
29
33
  else
30
34
  [status, headers, body]
@@ -36,5 +40,11 @@ module StaticRails
36
40
  def require_csrf_before_processing_request?
37
41
  false
38
42
  end
43
+
44
+ private
45
+
46
+ def needs_new_csrf_token?(req)
47
+ !req.cookies.has_key?("_csrf_token") || !@validates_csrf_token.call(req)
48
+ end
39
49
  end
40
50
  end
@@ -1,4 +1,5 @@
1
1
  require "rack-proxy"
2
+ require "action_dispatch/middleware/static"
2
3
 
3
4
  require_relative "matches_request_to_static_site"
4
5
 
@@ -17,7 +18,7 @@ module StaticRails
17
18
  if (req.get? || req.head?) && (site = @matches_request_to_static_site.call(req))
18
19
  file_handler = file_handler_for(site)
19
20
  path = req.path_info.gsub(/^#{site.url_root_path}/, "").chomp("/")
20
- if (match = file_handler.match?(path))
21
+ if (match = matching_file_for(file_handler, site, path))
21
22
  req.path_info = match
22
23
  return file_handler.serve(req)
23
24
  end
@@ -35,5 +36,13 @@ module StaticRails
35
36
  StaticRails.config.app.root.join(site.compile_dir).to_s
36
37
  )
37
38
  end
39
+
40
+ def matching_file_for(file_handler, site, path)
41
+ if (match = file_handler.match?(path))
42
+ match
43
+ elsif site.compile_404_file_path.present?
44
+ file_handler.match?(site.compile_404_file_path)
45
+ end
46
+ end
38
47
  end
39
48
  end
@@ -0,0 +1,33 @@
1
+ require_relative "request_forgery_protection_fallback"
2
+
3
+ module StaticRails
4
+ class ValidatesCsrfToken
5
+ include RequestForgeryProtectionFallback
6
+
7
+ def call(req)
8
+ valid_authenticity_token?(req.session, req.cookies["_csrf_token"])
9
+ end
10
+
11
+ private
12
+
13
+ [
14
+ :compare_with_global_token,
15
+ :global_csrf_token,
16
+ :csrf_token_hmac,
17
+ :valid_authenticity_token?,
18
+ :unmask_token,
19
+ :compare_with_real_token,
20
+ :valid_per_form_csrf_token?,
21
+ :xor_byte_strings,
22
+ :real_csrf_token
23
+ ].each do |method|
24
+ define_method method do |*args, **kwargs, &blk|
25
+ ActionController::RequestForgeryProtection.instance_method(method).bind(self).call(*args, **kwargs, &blk)
26
+ end
27
+ end
28
+
29
+ def per_form_csrf_tokens
30
+ false
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module StaticRails
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.9"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: static-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-13 00:00:00.000000000 Z
11
+ date: 2020-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -62,7 +62,6 @@ files:
62
62
  - lib/static-rails.rb
63
63
  - lib/static-rails/compile.rb
64
64
  - lib/static-rails/configuration.rb
65
- - lib/static-rails/csrf_middleware.rb
66
65
  - lib/static-rails/determines_whether_to_handle_request.rb
67
66
  - lib/static-rails/error.rb
68
67
  - lib/static-rails/gets_csrf_token.rb
@@ -70,12 +69,14 @@ files:
70
69
  - lib/static-rails/proxy_middleware.rb
71
70
  - lib/static-rails/rack_server_check.rb
72
71
  - lib/static-rails/railtie.rb
72
+ - lib/static-rails/request_forgery_protection_fallback.rb
73
73
  - lib/static-rails/server.rb
74
74
  - lib/static-rails/server_store.rb
75
75
  - lib/static-rails/site.rb
76
76
  - lib/static-rails/site_middleware.rb
77
77
  - lib/static-rails/site_plus_csrf_middleware.rb
78
78
  - lib/static-rails/static_middleware.rb
79
+ - lib/static-rails/validates_csrf_token.rb
79
80
  - lib/static-rails/version.rb
80
81
  - lib/static-rails/waits_for_connection.rb
81
82
  - lib/tasks/static-rails.rake
@@ -1,20 +0,0 @@
1
- require_relative "gets_csrf_token"
2
-
3
- module StaticRails
4
- class CsrfMiddleware
5
- def initialize(app)
6
- @app = app
7
- @gets_csrf_token = GetsCsrfToken.new
8
- end
9
-
10
- def call(env)
11
- if env["__static_rails_evil_request_for_csrf_token"]
12
- req = Rack::Request.new(env)
13
- [200, {}, [@gets_csrf_token.call(req)]].tap do
14
- end
15
- else
16
- @app.call(env)
17
- end
18
- end
19
- end
20
- end