76 lines
2.8 KiB
Python
76 lines
2.8 KiB
Python
import pytest
|
|
from flask import request, url_for
|
|
|
|
from app import config
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("app", "expected_ip", "expected_scheme", "expected_host", "expected_prefix"),
|
|
[
|
|
(False, "127.0.0.1", "http", "localhost", ""),
|
|
(True, "192.0.2.1", "https", "files.example.com", "/!/placeholder"),
|
|
],
|
|
indirect=["app"],
|
|
)
|
|
def test_forwarded_headers(app, client, expected_ip, expected_scheme, expected_host, expected_prefix):
|
|
@app.get("/request-info")
|
|
def request_info():
|
|
return {
|
|
"ip": request.remote_addr,
|
|
"scheme": request.scheme,
|
|
"host": request.host,
|
|
"prefix": request.script_root,
|
|
"health_url": url_for("healthz", _external=True),
|
|
}
|
|
|
|
response = client.get(
|
|
"/request-info",
|
|
headers={
|
|
"X-Forwarded-For": "198.51.100.1, 192.0.2.1",
|
|
"X-Forwarded-Proto": "http, https",
|
|
"X-Forwarded-Host": "untrusted.example, files.example.com",
|
|
"X-Forwarded-Port": "1234",
|
|
"X-Forwarded-Prefix": "/untrusted, /!/placeholder",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json == {
|
|
"ip": expected_ip,
|
|
"scheme": expected_scheme,
|
|
"host": expected_host,
|
|
"prefix": expected_prefix,
|
|
"health_url": f"{expected_scheme}://{expected_host}{expected_prefix}/healthz",
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize("app", [True], indirect=True)
|
|
@pytest.mark.parametrize("extension", ["svg", "png"])
|
|
def test_image_with_stripped_proxy_prefix(app, client, extension):
|
|
# Caddy removes the prefix from the path and supplies it separately in a header.
|
|
response = client.get(
|
|
f"/placeholder.{extension}?width=10&height=10",
|
|
headers={"X-Forwarded-Prefix": "/!/placeholder"},
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.mimetype == ("image/svg+xml" if extension == "svg" else "image/png")
|
|
|
|
|
|
@pytest.mark.parametrize(("app", "other_client_status"), [(False, 429), (True, 200)], indirect=["app"])
|
|
@pytest.mark.parametrize("extension", ["svg", "png"])
|
|
def test_rate_limit_client_identity(app, client, monkeypatch, extension, other_client_status):
|
|
monkeypatch.setattr(config, f"{extension.upper()}_RENDER_LIMIT", "1/minute")
|
|
|
|
def render(width, forwarded_for):
|
|
return client.get(
|
|
f"/placeholder.{extension}",
|
|
query_string={"width": width, "height": 10},
|
|
headers={"X-Forwarded-For": forwarded_for},
|
|
)
|
|
|
|
assert render(10, "198.51.100.1, 192.0.2.1").status_code == 200
|
|
# Changing an earlier header value cannot bypass the same client's quota.
|
|
assert render(11, "198.51.100.2, 192.0.2.1").status_code == 429
|
|
# Distinct forwarded clients get separate quotas only when proxy trust is enabled.
|
|
assert render(12, "192.0.2.2").status_code == other_client_status
|