HTTP signatures respect allowlist federation
[akkoma] / test / pleroma / reverse_proxy_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.ReverseProxyTest do
6 use Pleroma.Web.ConnCase
7 import ExUnit.CaptureLog
8
9 alias Pleroma.ReverseProxy
10 alias Plug.Conn
11
12 describe "reverse proxy" do
13 test "do not track successful request", %{conn: conn} do
14 url = "/success"
15
16 Tesla.Mock.mock(fn %{url: ^url} ->
17 %Tesla.Env{
18 status: 200,
19 body: ""
20 }
21 end)
22
23 conn = ReverseProxy.call(conn, url)
24
25 assert response(conn, 200)
26 assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, nil}
27 end
28
29 test "use Pleroma's user agent in the request; don't pass the client's", %{conn: conn} do
30 clear_config([:http, :send_user_agent], true)
31 # Mock will fail if the client's user agent isn't filtered
32 wanted_headers = [{"user-agent", Pleroma.Application.user_agent()}]
33
34 Tesla.Mock.mock(fn %{url: "/user-agent", headers: ^wanted_headers} ->
35 %Tesla.Env{
36 status: 200,
37 body: ""
38 }
39 end)
40
41 conn =
42 conn
43 |> Plug.Conn.put_req_header("user-agent", "fake/1.0")
44 |> ReverseProxy.call("/user-agent")
45
46 assert response(conn, 200)
47 end
48 end
49
50 describe "max_body" do
51 test "length returns error if content-length more than option", %{conn: conn} do
52 Tesla.Mock.mock(fn %{url: "/huge-file"} ->
53 %Tesla.Env{
54 status: 200,
55 headers: [{"content-length", "100"}],
56 body: "This body is too large."
57 }
58 end)
59
60 assert capture_log(fn ->
61 ReverseProxy.call(conn, "/huge-file", max_body_length: 4)
62 end) =~
63 "[error] Elixir.Pleroma.ReverseProxy: request to \"/huge-file\" failed: :body_too_large"
64
65 assert {:ok, true} == Cachex.get(:failed_proxy_url_cache, "/huge-file")
66
67 assert capture_log(fn ->
68 ReverseProxy.call(conn, "/huge-file", max_body_length: 4)
69 end) == ""
70 end
71 end
72
73 describe "HEAD requests" do
74 test "common", %{conn: conn} do
75 Tesla.Mock.mock(fn %{method: :head, url: "/head"} ->
76 %Tesla.Env{
77 status: 200,
78 headers: [{"content-type", "text/html; charset=utf-8"}],
79 body: ""
80 }
81 end)
82
83 conn = ReverseProxy.call(Map.put(conn, :method, "HEAD"), "/head")
84 assert html_response(conn, 200) == ""
85 end
86 end
87
88 describe "returns error on" do
89 test "500", %{conn: conn} do
90 url = "/status/500"
91
92 Tesla.Mock.mock(fn %{url: ^url} ->
93 %Tesla.Env{
94 status: 500,
95 body: ""
96 }
97 end)
98
99 capture_log(fn -> ReverseProxy.call(conn, url) end) =~
100 "[error] Elixir.Pleroma.ReverseProxy: request to /status/500 failed with HTTP status 500"
101
102 assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true}
103
104 {:ok, ttl} = Cachex.ttl(:failed_proxy_url_cache, url)
105 assert ttl <= 60_000
106 end
107
108 test "400", %{conn: conn} do
109 url = "/status/400"
110
111 Tesla.Mock.mock(fn %{url: ^url} ->
112 %Tesla.Env{
113 status: 400,
114 body: ""
115 }
116 end)
117
118 capture_log(fn -> ReverseProxy.call(conn, url) end) =~
119 "[error] Elixir.Pleroma.ReverseProxy: request to /status/400 failed with HTTP status 400"
120
121 assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true}
122 assert Cachex.ttl(:failed_proxy_url_cache, url) == {:ok, nil}
123 end
124
125 test "403", %{conn: conn} do
126 url = "/status/403"
127
128 Tesla.Mock.mock(fn %{url: ^url} ->
129 %Tesla.Env{
130 status: 403,
131 body: ""
132 }
133 end)
134
135 capture_log(fn ->
136 ReverseProxy.call(conn, url, failed_request_ttl: :timer.seconds(120))
137 end) =~
138 "[error] Elixir.Pleroma.ReverseProxy: request to /status/403 failed with HTTP status 403"
139
140 {:ok, ttl} = Cachex.ttl(:failed_proxy_url_cache, url)
141 assert ttl > 100_000
142 end
143 end
144
145 describe "keep request headers" do
146 test "header passes", %{conn: conn} do
147 Tesla.Mock.mock(fn %{url: "/headers"} ->
148 %Tesla.Env{
149 status: 200,
150 body: ""
151 }
152 end)
153
154 conn =
155 Conn.put_req_header(
156 conn,
157 "accept",
158 "text/html"
159 )
160 |> ReverseProxy.call("/headers")
161
162 assert response(conn, 200)
163 assert {"accept", "text/html"} in conn.req_headers
164 end
165
166 test "header is filtered", %{conn: conn} do
167 # Mock will fail if the accept-language header isn't filtered
168 wanted_headers = [{"accept-encoding", "*"}]
169
170 Tesla.Mock.mock(fn %{url: "/headers", headers: ^wanted_headers} ->
171 %Tesla.Env{
172 status: 200,
173 body: ""
174 }
175 end)
176
177 conn =
178 conn
179 |> Conn.put_req_header("accept-language", "en-US")
180 |> Conn.put_req_header("accept-encoding", "*")
181 |> ReverseProxy.call("/headers")
182
183 assert response(conn, 200)
184 end
185 end
186
187 test "returns 400 on non GET, HEAD requests", %{conn: conn} do
188 Tesla.Mock.mock(fn %{url: "/ip"} ->
189 %Tesla.Env{
190 status: 200,
191 body: ""
192 }
193 end)
194
195 conn = ReverseProxy.call(Map.put(conn, :method, "POST"), "/ip")
196 assert response(conn, 400)
197 end
198
199 describe "cache resp headers not filtered" do
200 test "add cache-control", %{conn: conn} do
201 Tesla.Mock.mock(fn %{url: "/cache"} ->
202 %Tesla.Env{
203 status: 200,
204 headers: [
205 {"cache-control", "public, max-age=1209600"},
206 {"etag", "some ETag"},
207 {"expires", "Wed, 21 Oct 2015 07:28:00 GMT"}
208 ],
209 body: ""
210 }
211 end)
212
213 conn = ReverseProxy.call(conn, "/cache")
214 assert {"cache-control", "public, max-age=1209600"} in conn.resp_headers
215 assert {"etag", "some ETag"} in conn.resp_headers
216 assert {"expires", "Wed, 21 Oct 2015 07:28:00 GMT"} in conn.resp_headers
217 end
218 end
219
220 describe "response content disposition header" do
221 test "not attachment", %{conn: conn} do
222 Tesla.Mock.mock(fn %{url: "/disposition"} ->
223 %Tesla.Env{
224 status: 200,
225 headers: [
226 {"content-type", "image/gif"},
227 {"content-length", "0"}
228 ],
229 body: ""
230 }
231 end)
232
233 conn = ReverseProxy.call(conn, "/disposition")
234
235 assert {"content-type", "image/gif"} in conn.resp_headers
236 end
237
238 test "with content-disposition header", %{conn: conn} do
239 Tesla.Mock.mock(fn %{url: "/disposition"} ->
240 %Tesla.Env{
241 status: 200,
242 headers: [
243 {"content-disposition", "attachment; filename=\"filename.jpg\""},
244 {"content-length", "0"}
245 ],
246 body: ""
247 }
248 end)
249
250 conn = ReverseProxy.call(conn, "/disposition")
251
252 assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers
253 end
254 end
255 end