forked from elixir-plug/plug
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract Plug.RewriteOn from Plug.SSL (elixir-plug#990)
* RewriteOn: Add a new plug with the rewrite on logic from Plug.SSL * SSL: Delegate the "rewrite_on" logic to `Plug.RewriteOn` * SSL: Adjust the moduledoc to refer to `Plug.RewriteOn`
- Loading branch information
Showing
3 changed files
with
156 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
defmodule Plug.RewriteOn do | ||
@moduledoc """ | ||
A plug to rewrite the request's host/port/protocol from `x-forwarded-*` headers. | ||
If your Plug application is behind a proxy that handles HTTPS, you may | ||
need to tell Plug to parse the proper protocol from the `x-forwarded-*` | ||
header. | ||
plug Plug.RewriteOn, [:x_forwarded_host, :x_forwarded_port, :x_forwarded_proto] | ||
The supported values are: | ||
* `:x_forwarded_host` - to override the host based on on the "x-forwarded-host" header | ||
* `:x_forwarded_port` - to override the port based on on the "x-forwarded-port" header | ||
* `:x_forwarded_proto` - to override the protocol based on on the "x-forwarded-proto" header | ||
Since rewriting the scheme based on `x-forwarded-*` headers can open up | ||
security vulnerabilities, only use this plug if: | ||
* your app is behind a proxy | ||
* your proxy strips the given `x-forwarded-*` headers from all incoming requests | ||
* your proxy sets the `x-forwarded-*` headers and sends it to Plug | ||
""" | ||
@behaviour Plug | ||
|
||
import Plug.Conn, only: [get_req_header: 2] | ||
|
||
@impl true | ||
def init(header), do: List.wrap(header) | ||
|
||
@impl true | ||
def call(conn, [:x_forwarded_proto | rewrite_on]) do | ||
conn | ||
|> put_scheme(get_req_header(conn, "x-forwarded-proto")) | ||
|> call(rewrite_on) | ||
end | ||
|
||
def call(conn, [:x_forwarded_port | rewrite_on]) do | ||
conn | ||
|> put_port(get_req_header(conn, "x-forwarded-port")) | ||
|> call(rewrite_on) | ||
end | ||
|
||
def call(conn, [:x_forwarded_host | rewrite_on]) do | ||
conn | ||
|> put_host(get_req_header(conn, "x-forwarded-host")) | ||
|> call(rewrite_on) | ||
end | ||
|
||
def call(_conn, [other | _rewrite_on]) do | ||
raise "unknown rewrite: #{inspect(other)}" | ||
end | ||
|
||
def call(conn, []) do | ||
conn | ||
end | ||
|
||
defp put_scheme(%{scheme: :http, port: 80} = conn, ["https"]), | ||
do: %{conn | scheme: :https, port: 443} | ||
|
||
defp put_scheme(conn, ["https"]), | ||
do: %{conn | scheme: :https} | ||
|
||
defp put_scheme(%{scheme: :https, port: 443} = conn, ["http"]), | ||
do: %{conn | scheme: :http, port: 80} | ||
|
||
defp put_scheme(conn, ["http"]), | ||
do: %{conn | scheme: :http} | ||
|
||
defp put_scheme(conn, _scheme), | ||
do: conn | ||
|
||
defp put_host(conn, [proper_host]), | ||
do: %{conn | host: proper_host} | ||
|
||
defp put_host(conn, _), | ||
do: conn | ||
|
||
defp put_port(conn, headers) do | ||
with [header] <- headers, | ||
{port, ""} <- Integer.parse(header) do | ||
%{conn | port: port} | ||
else | ||
_ -> conn | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
defmodule Plug.RewriteOnTest do | ||
use ExUnit.Case, async: true | ||
use Plug.Test | ||
|
||
defp call(conn, rewrite) do | ||
Plug.RewriteOn.call(conn, Plug.RewriteOn.init(rewrite)) | ||
end | ||
|
||
test "rewrites http to https based on x-forwarded-proto" do | ||
conn = | ||
conn(:get, "http://example.com/") | ||
|> put_req_header("x-forwarded-proto", "https") | ||
|> call(:x_forwarded_proto) | ||
|
||
assert conn.scheme == :https | ||
assert conn.port == 443 | ||
end | ||
|
||
test "doesn't change the port when it doesn't match the scheme" do | ||
conn = | ||
conn(:get, "http://example.com:1234/") | ||
|> put_req_header("x-forwarded-proto", "https") | ||
|> call(:x_forwarded_proto) | ||
|
||
assert conn.scheme == :https | ||
assert conn.port == 1234 | ||
end | ||
|
||
test "rewrites host with a x-forwarder-host header" do | ||
conn = | ||
conn(:get, "http://example.com/") | ||
|> put_req_header("x-forwarded-host", "truessl.example.com") | ||
|> call(:x_forwarded_host) | ||
|
||
assert conn.host == "truessl.example.com" | ||
end | ||
|
||
test "rewrites port with a x-forwarder-port header" do | ||
conn = | ||
conn(:get, "http://example.com/") | ||
|> put_req_header("x-forwarded-port", "3030") | ||
|> call(:x_forwarded_port) | ||
|
||
assert conn.port == 3030 | ||
end | ||
|
||
test "rewrites the host, the port, and the protocol" do | ||
conn = | ||
conn(:get, "http://example.com/") | ||
|> put_req_header("x-forwarded-host", "truessl.example.com") | ||
|> put_req_header("x-forwarded-port", "3030") | ||
|> put_req_header("x-forwarded-proto", "https") | ||
|> call([:x_forwarded_host, :x_forwarded_port, :x_forwarded_proto]) | ||
|
||
assert conn.host == "truessl.example.com" | ||
assert conn.port == 3030 | ||
assert conn.scheme == :https | ||
end | ||
|
||
test "raises when receiving an unknown rewrite" do | ||
assert_raise RuntimeError, "unknown rewrite: :x_forwarded_other", fn -> | ||
call(conn(:get, "http://example.com/"), :x_forwarded_other) | ||
end | ||
end | ||
end |