Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions application/tests/web_main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1813,3 +1813,32 @@ def test_openapi_yaml_endpoint_serves_spec(self) -> None:
body = response.data.decode()
self.assertIn("openapi:", body)
self.assertIn("/rest/v1/", body)

def test_per_user_endpoints_are_no_store(self) -> None:
"""Per-user endpoints must be no-store, never publicly cacheable.

/rest/v1/auth/user returns the logged-in user's email; served with
Cache-Control: max-age=300 (no no-store/private/Vary:Cookie) it could be
stored by a shared/browser cache and disclosed to another user. This is
the regression test for the after_request no-store fix — it also guards
the alias /rest/v1/user and the already-covered /rest/v1/user/resources.
"""
with patch.dict(os.environ, {"NO_LOGIN": "1"}):
with self.app.test_client() as client:
for path in (
"/rest/v1/auth/user",
"/rest/v1/user",
"/rest/v1/user/resources",
):
response = client.get(path)
cache_control = response.headers.get("Cache-Control", "")
self.assertIn(
"no-store",
cache_control,
msg=f"{path} must be no-store, got {cache_control!r}",
)
self.assertNotIn(
"max-age=300",
cache_control,
msg=f"{path} must not be publicly cacheable, got {cache_control!r}",
)
10 changes: 8 additions & 2 deletions application/web/web_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,8 +898,14 @@ def before_request():
@app.after_request
def add_header(response):
# Per-user endpoints must never be shared-cached; no-store wins over the
# default max-age for them (this hook runs after the view).
if request.path == "/rest/v1/user/resources":
# default max-age for them (this hook runs after the view). Covers the
# resource selection AND the identity endpoint (returns the user's email)
# plus its deprecated alias.
if request.path in (
"/rest/v1/user/resources",
"/rest/v1/auth/user",
"/rest/v1/user",
):
response.cache_control.no_store = True
return response
response.cache_control.max_age = 300
Expand Down
Loading