2019-07-10 06:13:23 +01:00
|
|
|
# Pleroma: A lightweight social networking server
|
2021-01-13 06:49:20 +00:00
|
|
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
2019-07-10 06:13:23 +01:00
|
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2019-07-09 17:54:13 +01:00
|
|
|
defmodule Pleroma.ReverseProxyTest do
|
2023-08-01 11:43:50 +01:00
|
|
|
use Pleroma.Web.ConnCase, async: false
|
2019-07-09 17:54:13 +01:00
|
|
|
import ExUnit.CaptureLog
|
2020-03-07 08:01:37 +00:00
|
|
|
|
2019-07-09 17:54:13 +01:00
|
|
|
alias Pleroma.ReverseProxy
|
2020-03-07 08:01:37 +00:00
|
|
|
alias Plug.Conn
|
2019-07-09 17:54:13 +01:00
|
|
|
|
2019-10-01 21:00:27 +01:00
|
|
|
describe "reverse proxy" do
|
|
|
|
test "do not track successful request", %{conn: conn} do
|
|
|
|
url = "/success"
|
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: ^url} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
2019-10-01 21:00:27 +01:00
|
|
|
conn = ReverseProxy.call(conn, url)
|
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
assert response(conn, 200)
|
2019-10-01 21:00:27 +01:00
|
|
|
assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, nil}
|
|
|
|
end
|
2021-02-22 20:46:59 +00:00
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
test "use Pleroma's user agent in the request; don't pass the client's", %{conn: conn} do
|
|
|
|
clear_config([:http, :send_user_agent], true)
|
|
|
|
# Mock will fail if the client's user agent isn't filtered
|
|
|
|
wanted_headers = [{"user-agent", Pleroma.Application.user_agent()}]
|
2019-07-09 17:54:13 +01:00
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: "/user-agent", headers: ^wanted_headers} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
2020-02-11 07:12:57 +00:00
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
conn =
|
|
|
|
conn
|
|
|
|
|> Plug.Conn.put_req_header("user-agent", "fake/1.0")
|
|
|
|
|> ReverseProxy.call("/user-agent")
|
2020-02-11 07:12:57 +00:00
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
assert response(conn, 200)
|
2020-02-11 07:12:57 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "max_body" do
|
2019-07-09 17:54:13 +01:00
|
|
|
test "length returns error if content-length more than option", %{conn: conn} do
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: "/huge-file"} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
headers: [{"content-length", "100"}],
|
|
|
|
body: "This body is too large."
|
|
|
|
}
|
|
|
|
end)
|
2019-07-09 17:54:13 +01:00
|
|
|
|
|
|
|
assert capture_log(fn ->
|
2019-10-01 21:00:27 +01:00
|
|
|
ReverseProxy.call(conn, "/huge-file", max_body_length: 4)
|
2019-07-09 17:54:13 +01:00
|
|
|
end) =~
|
2019-10-01 21:00:27 +01:00
|
|
|
"[error] Elixir.Pleroma.ReverseProxy: request to \"/huge-file\" failed: :body_too_large"
|
|
|
|
|
|
|
|
assert {:ok, true} == Cachex.get(:failed_proxy_url_cache, "/huge-file")
|
|
|
|
|
|
|
|
assert capture_log(fn ->
|
|
|
|
ReverseProxy.call(conn, "/huge-file", max_body_length: 4)
|
|
|
|
end) == ""
|
2019-07-09 17:54:13 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "HEAD requests" do
|
|
|
|
test "common", %{conn: conn} do
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{method: :head, url: "/head"} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
headers: [{"content-type", "text/html; charset=utf-8"}],
|
|
|
|
body: ""
|
|
|
|
}
|
2019-07-09 17:54:13 +01:00
|
|
|
end)
|
|
|
|
|
|
|
|
conn = ReverseProxy.call(Map.put(conn, :method, "HEAD"), "/head")
|
|
|
|
assert html_response(conn, 200) == ""
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "returns error on" do
|
|
|
|
test "500", %{conn: conn} do
|
2019-10-01 21:00:27 +01:00
|
|
|
url = "/status/500"
|
2019-07-09 17:54:13 +01:00
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: ^url} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 500,
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
2019-10-01 21:00:27 +01:00
|
|
|
capture_log(fn -> ReverseProxy.call(conn, url) end) =~
|
2019-07-09 17:54:13 +01:00
|
|
|
"[error] Elixir.Pleroma.ReverseProxy: request to /status/500 failed with HTTP status 500"
|
2019-10-01 21:00:27 +01:00
|
|
|
|
|
|
|
assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true}
|
|
|
|
|
|
|
|
{:ok, ttl} = Cachex.ttl(:failed_proxy_url_cache, url)
|
|
|
|
assert ttl <= 60_000
|
2019-07-09 17:54:13 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
test "400", %{conn: conn} do
|
2019-10-01 21:00:27 +01:00
|
|
|
url = "/status/400"
|
2019-07-09 17:54:13 +01:00
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: ^url} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 400,
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
2019-10-01 21:00:27 +01:00
|
|
|
capture_log(fn -> ReverseProxy.call(conn, url) end) =~
|
2019-07-09 17:54:13 +01:00
|
|
|
"[error] Elixir.Pleroma.ReverseProxy: request to /status/400 failed with HTTP status 400"
|
2019-10-01 21:00:27 +01:00
|
|
|
|
|
|
|
assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true}
|
|
|
|
assert Cachex.ttl(:failed_proxy_url_cache, url) == {:ok, nil}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "403", %{conn: conn} do
|
|
|
|
url = "/status/403"
|
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: ^url} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 403,
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
2019-10-01 21:00:27 +01:00
|
|
|
capture_log(fn ->
|
|
|
|
ReverseProxy.call(conn, url, failed_request_ttl: :timer.seconds(120))
|
|
|
|
end) =~
|
|
|
|
"[error] Elixir.Pleroma.ReverseProxy: request to /status/403 failed with HTTP status 403"
|
|
|
|
|
|
|
|
{:ok, ttl} = Cachex.ttl(:failed_proxy_url_cache, url)
|
|
|
|
assert ttl > 100_000
|
2019-07-09 17:54:13 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "keep request headers" do
|
|
|
|
test "header passes", %{conn: conn} do
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: "/headers"} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
2019-07-09 17:54:13 +01:00
|
|
|
conn =
|
2020-03-07 08:01:37 +00:00
|
|
|
Conn.put_req_header(
|
2019-07-09 17:54:13 +01:00
|
|
|
conn,
|
|
|
|
"accept",
|
|
|
|
"text/html"
|
|
|
|
)
|
|
|
|
|> ReverseProxy.call("/headers")
|
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
assert response(conn, 200)
|
|
|
|
assert {"accept", "text/html"} in conn.req_headers
|
2019-07-09 17:54:13 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
test "header is filtered", %{conn: conn} do
|
2022-07-04 17:30:38 +01:00
|
|
|
# Mock will fail if the accept-language header isn't filtered
|
|
|
|
wanted_headers = [{"accept-encoding", "*"}]
|
|
|
|
|
|
|
|
Tesla.Mock.mock(fn %{url: "/headers", headers: ^wanted_headers} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
2019-07-09 17:54:13 +01:00
|
|
|
conn =
|
2022-07-04 17:30:38 +01:00
|
|
|
conn
|
|
|
|
|> Conn.put_req_header("accept-language", "en-US")
|
|
|
|
|> Conn.put_req_header("accept-encoding", "*")
|
2019-07-09 17:54:13 +01:00
|
|
|
|> ReverseProxy.call("/headers")
|
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
assert response(conn, 200)
|
2019-07-09 17:54:13 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
test "returns 400 on non GET, HEAD requests", %{conn: conn} do
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: "/ip"} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
2019-07-09 17:54:13 +01:00
|
|
|
conn = ReverseProxy.call(Map.put(conn, :method, "POST"), "/ip")
|
2022-07-04 17:30:38 +01:00
|
|
|
assert response(conn, 400)
|
2019-07-09 17:54:13 +01:00
|
|
|
end
|
|
|
|
|
2022-07-04 17:30:38 +01:00
|
|
|
describe "cache resp headers not filtered" do
|
2019-07-09 17:54:13 +01:00
|
|
|
test "add cache-control", %{conn: conn} do
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: "/cache"} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
headers: [
|
|
|
|
{"cache-control", "public, max-age=1209600"},
|
|
|
|
{"etag", "some ETag"},
|
|
|
|
{"expires", "Wed, 21 Oct 2015 07:28:00 GMT"}
|
|
|
|
],
|
|
|
|
body: ""
|
|
|
|
}
|
2019-07-09 17:54:13 +01:00
|
|
|
end)
|
|
|
|
|
|
|
|
conn = ReverseProxy.call(conn, "/cache")
|
2020-03-13 17:20:33 +00:00
|
|
|
assert {"cache-control", "public, max-age=1209600"} in conn.resp_headers
|
2022-07-04 17:30:38 +01:00
|
|
|
assert {"etag", "some ETag"} in conn.resp_headers
|
|
|
|
assert {"expires", "Wed, 21 Oct 2015 07:28:00 GMT"} in conn.resp_headers
|
2019-07-09 17:54:13 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "response content disposition header" do
|
2022-07-04 17:30:38 +01:00
|
|
|
test "not attachment", %{conn: conn} do
|
|
|
|
Tesla.Mock.mock(fn %{url: "/disposition"} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
headers: [
|
|
|
|
{"content-type", "image/gif"},
|
|
|
|
{"content-length", "0"}
|
|
|
|
],
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
2019-07-09 17:54:13 +01:00
|
|
|
|
|
|
|
conn = ReverseProxy.call(conn, "/disposition")
|
|
|
|
|
|
|
|
assert {"content-type", "image/gif"} in conn.resp_headers
|
|
|
|
end
|
|
|
|
|
|
|
|
test "with content-disposition header", %{conn: conn} do
|
2022-07-04 17:30:38 +01:00
|
|
|
Tesla.Mock.mock(fn %{url: "/disposition"} ->
|
|
|
|
%Tesla.Env{
|
|
|
|
status: 200,
|
|
|
|
headers: [
|
|
|
|
{"content-disposition", "attachment; filename=\"filename.jpg\""},
|
|
|
|
{"content-length", "0"}
|
|
|
|
],
|
|
|
|
body: ""
|
|
|
|
}
|
|
|
|
end)
|
2019-07-09 17:54:13 +01:00
|
|
|
|
|
|
|
conn = ReverseProxy.call(conn, "/disposition")
|
|
|
|
|
|
|
|
assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|