Skip to content

Commit

Permalink
Middleware for handling vendor-specific $vendor-forwarded-proto -> …
Browse files Browse the repository at this point in the history
…`x-forwarded-proto`.
  • Loading branch information
ioquatix committed Jun 5, 2024
1 parent d5265bc commit e97a8e0
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/rack/contrib.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def self.release
autoload :ProcTitle, "rack/contrib/proctitle"
autoload :Profiler, "rack/contrib/profiler"
autoload :ResponseHeaders, "rack/contrib/response_headers"
autoload :SetXForwardedProtoHeader, "rack/contrib/set_x_forwarded_proto_header"
autoload :Signals, "rack/contrib/signals"
autoload :SimpleEndpoint, "rack/contrib/simple_endpoint"
autoload :TimeZone, "rack/contrib/time_zone"
Expand Down
31 changes: 31 additions & 0 deletions lib/rack/contrib/set_x_forwarded_proto_header.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module Rack
# Middleware to set the X-Forwarded-Proto header to the value
# of another header.
#
# This header can be used to ensure the scheme matches when comparing
# request.origin and request.base_url for CSRF checking, but Rack
# expects that value to be in the X_FORWARDED_PROTO header.
#
# Example Rails usage:
# If you use a vendor managed proxy or CDN which sends the proto in a header add
#`config.middleware.use Rack::SetXForwardedProtoHeader, 'Vendor-Forwarded-Proto-Header'`
# to your application.rb file

class SetXForwardedProtoHeader
def initialize(app, vendor_forwarded_header)
@app = app
# Rack expects to see UPPER_UNDERSCORED_HEADERS, never SnakeCased-Dashed-Headers
@vendor_forwarded_header = "HTTP_#{vendor_forwarded_header.upcase.gsub "-", "_"}"
end

def call(env)
if value = env[@vendor_forwarded_header]
env["HTTP_X_FORWARDED_PROTO"] = value
end
@app.call(env)
end

end
end
44 changes: 44 additions & 0 deletions test/spec_rack_set_x_forwarded_proto_header.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require 'minitest/autorun'
require 'rack/contrib/runtime'

describe Rack::SetXForwardedProtoHeader do
response = lambda {|e| [200, {}, []] }

it "leaves the value of X_FORWARDED_PROTO intact if there is no vendor header passed in the request" do
vendor_forwarded_header = "not passed in the request"
env = Rack::MockRequest.env_for("/", "HTTP_X_FORWARDED_PROTO" => "http")

Rack::Lint.new(Rack::SetXForwardedProtoHeader.new(response, vendor_forwarded_header)).call env

env["HTTP_X_FORWARDED_PROTO"].must_equal "http"
end

it "does not set X-Forwarded-Proto when there is no vendor header passed in the request" do
vendor_forwarded_header = "not passed in the request"
env = Rack::MockRequest.env_for("/", "FOO" => "bar")

Rack::Lint.new(Rack::SetXForwardedProtoHeader.new(response, vendor_forwarded_header)).call env

env["FOO"].must_equal "bar"
assert_nil(env["HTTP_X_FORWARDED_PROTO"])
end


it "copies the value of the header to X-Forwarded-Proto" do
env = Rack::MockRequest.env_for("/", "HTTP_VENDOR_FORWARDED_PROTO_HEADER" => "https")

Rack::Lint.new(Rack::SetXForwardedProtoHeader.new(response, "Vendor-Forwarded-Proto-Header")).call env

env["HTTP_X_FORWARDED_PROTO"].must_equal "https"
end

it "copies the value of the header to X-Forwarded-Proto overwriting an existing X-Forwarded-Proto" do
env = Rack::MockRequest.env_for("/", "HTTP_VENDOR_FORWARDED_PROTO_HEADER" => "https", "HTTP_X_FORWARDED_PROTO" => "http")

Rack::Lint.new(Rack::SetXForwardedProtoHeader.new(response, "Vendor-Forwarded-Proto-Header")).call env

env["HTTP_X_FORWARDED_PROTO"].must_equal "https"
end
end

0 comments on commit e97a8e0

Please sign in to comment.