mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-09-02 16:07:15 +00:00
Security: Minor additional hardening (#13898)
* Security: bump jinja2 floor to 3.1.6 (CVE-2025-27516) * Security: anchor the /share/ URL pattern * Security: handle missing file on public share view without 500 * Security: scope correspondent last_correspondence to permitted documents * Security: disable PUT/PATCH on share link bundles
This commit is contained in:
@@ -102,6 +102,7 @@ class TestApiObjects(DirectoriesMixin, APITestCase):
|
||||
- API is called
|
||||
THEN:
|
||||
- Last correspondence date is returned only if requested for list, and for detail
|
||||
- The date is scoped to documents the requesting user may view
|
||||
"""
|
||||
|
||||
Document.objects.create(
|
||||
@@ -145,6 +146,32 @@ class TestApiObjects(DirectoriesMixin, APITestCase):
|
||||
response.data["last_correspondence"],
|
||||
)
|
||||
|
||||
# A newer document owned by another user must not leak through the
|
||||
# aggregate for a non-superuser who cannot view it
|
||||
other = User.objects.create_user(username="other")
|
||||
Document.objects.create(
|
||||
mime_type="application/pdf",
|
||||
correspondent=self.c1,
|
||||
created=datetime.date(2023, 6, 1),
|
||||
checksum="hidden",
|
||||
owner=other,
|
||||
)
|
||||
|
||||
user = User.objects.create_user(username="regular")
|
||||
user.user_permissions.add(
|
||||
Permission.objects.get(codename="view_correspondent"),
|
||||
)
|
||||
self.client.force_authenticate(user=user)
|
||||
|
||||
response = self.client.get("/api/correspondents/?last_correspondence=true")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
result = next(r for r in response.data["results"] if r["id"] == self.c1.id)
|
||||
self.assertIn("2022-01-02", result["last_correspondence"])
|
||||
|
||||
response = self.client.get(f"/api/correspondents/{self.c1.id}/")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn("2022-01-02", response.data["last_correspondence"])
|
||||
|
||||
def test_paginated_objects_include_all_only_for_legacy_version(self) -> None:
|
||||
response_v10 = self.client.get("/api/correspondents/")
|
||||
self.assertEqual(response_v10.status_code, status.HTTP_200_OK)
|
||||
|
||||
@@ -192,6 +192,50 @@ class ShareLinkBundleAPITests(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
|
||||
self.assertIn("sharelink_notfound=1", response["Location"])
|
||||
|
||||
def test_share_link_missing_file_redirects(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A share link whose document file is missing from disk
|
||||
WHEN:
|
||||
- The public share link is requested anonymously
|
||||
THEN:
|
||||
- The user is redirected to login instead of a 500 error
|
||||
"""
|
||||
doc = DocumentFactory.create(filename="missing-original.pdf")
|
||||
share_link = ShareLink.objects.create(
|
||||
slug="missingfilelink",
|
||||
document=doc,
|
||||
file_version=ShareLink.FileVersion.ORIGINAL,
|
||||
)
|
||||
|
||||
self.client.logout()
|
||||
response = self.client.get(f"/share/{share_link.slug}/")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
|
||||
self.assertIn("sharelink_notfound=1", response["Location"])
|
||||
|
||||
def test_download_ready_bundle_missing_file_returns_503(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A READY bundle whose zip file is missing from disk
|
||||
WHEN:
|
||||
- The public share link is requested anonymously
|
||||
THEN:
|
||||
- A 503 is returned instead of a 500 error
|
||||
"""
|
||||
bundle = ShareLinkBundle.objects.create(
|
||||
slug="missingbundlefile",
|
||||
file_version=ShareLink.FileVersion.ARCHIVE,
|
||||
status=ShareLinkBundle.Status.READY,
|
||||
file_path="bundles/gone.zip",
|
||||
)
|
||||
bundle.documents.set([self.document])
|
||||
|
||||
self.client.logout()
|
||||
response = self.client.get(f"/share/{bundle.slug}/")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||
|
||||
|
||||
class ShareLinkBundleTaskTests(DirectoriesMixin, APITestCase):
|
||||
def setUp(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user