Compare commits

...

4 Commits

Author SHA1 Message Date
shamoon
b3caa52819 Security: add static analysis CI workflow 2026-03-29 13:32:04 -07:00
Andreas Schneider
85e0d1842a Tests: add regression test for redis URL with empty username (#12460)
* Tests: add regression test for redis URL with empty username and password

Covers the unix://:SECRET@/path.sock format (empty username, password only),
which was missing from the existing test cases for PR #12239.

* Update src/paperless/tests/settings/test_custom_parsers.py

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-03-29 06:31:18 -07:00
GitHub Actions
62f79c088e Auto translate strings 2026-03-28 21:00:05 +00:00
shamoon
129da3ade7 Tweakhancement: show file extension in StoragePath test (#12452) 2026-03-28 13:58:33 -07:00
5 changed files with 95 additions and 20 deletions

View File

@@ -0,0 +1,40 @@
name: Static Analysis
on:
push:
branches-ignore:
- 'translations**'
pull_request:
branches-ignore:
- 'translations**'
workflow_dispatch:
concurrency:
group: static-analysis-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
zizmor:
name: GitHub Actions Security Analysis
runs-on: ubuntu-24.04
permissions:
contents: read
actions: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
semgrep:
name: Semgrep CE
runs-on: ubuntu-24.04
container:
image: semgrep/semgrep:1.155.0@sha256:cc869c685dcc0fe497c86258da9f205397d8108e56d21a86082ea4886e52784d
if: github.actor != 'dependabot[bot]'
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Semgrep
run: semgrep scan --config auto

View File

@@ -360,7 +360,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "path/Something")
self.assertEqual(response.data, "path/Something.pdf")
def test_test_storage_path_respects_none_placeholder_setting(self) -> None:
"""
@@ -390,7 +390,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "folder/none/Something")
self.assertEqual(response.data, "folder/none/Something.pdf")
with override_settings(FILENAME_FORMAT_REMOVE_NONE=True):
response = self.client.post(
@@ -399,7 +399,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "folder/Something")
self.assertEqual(response.data, "folder/Something.pdf")
def test_test_storage_path_requires_document_view_permission(self) -> None:
owner = User.objects.create_user(username="owner")
@@ -447,7 +447,27 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "path/Shared")
self.assertEqual(response.data, "path/Shared.pdf")
def test_test_storage_path_prefers_existing_filename_extension(self) -> None:
document = Document.objects.create(
mime_type="image/jpeg",
filename="existing/Document.jpeg",
title="Something",
checksum="123",
)
response = self.client.post(
f"{self.ENDPOINT}test/",
json.dumps(
{
"document": document.id,
"path": "path/{{ title }}",
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "path/Something.jpeg")
def test_test_storage_path_exposes_basic_document_context_but_not_sensitive_owner_data(
self,
@@ -478,12 +498,12 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "owner")
self.assertEqual(response.data, "owner.pdf")
for expression, expected in (
("{{ document.content }}", "Top secret content"),
("{{ document.id }}", str(document.id)),
("{{ document.page_count }}", "2"),
("{{ document.content }}", "Top secret content.pdf"),
("{{ document.id }}", f"{document.id}.pdf"),
("{{ document.page_count }}", "2.pdf"),
):
response = self.client.post(
f"{self.ENDPOINT}test/",
@@ -545,7 +565,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "Private Correspondent")
self.assertEqual(response.data, "Private Correspondent.pdf")
response = self.client.post(
f"{self.ENDPOINT}test/",
@@ -560,7 +580,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "Private Correspondent")
self.assertEqual(response.data, "Private Correspondent.pdf")
def test_test_storage_path_superuser_can_view_private_related_objects(self) -> None:
owner = User.objects.create_user(username="owner")
@@ -589,7 +609,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "Private Correspondent")
self.assertEqual(response.data, "Private Correspondent.pdf")
def test_test_storage_path_includes_doc_type_storage_path_and_tags(
self,
@@ -636,7 +656,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "Private Type/private/path/Private Tag")
self.assertEqual(response.data, "Private Type/private/path/Private Tag.pdf")
response = self.client.post(
f"{self.ENDPOINT}test/",
@@ -649,7 +669,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "Private Type/Private Tag")
self.assertEqual(response.data, "Private Type/Private Tag.pdf")
def test_test_storage_path_includes_custom_fields_for_visible_document(
self,
@@ -685,7 +705,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, "42")
self.assertEqual(response.data, "42.pdf")
class TestBulkEditObjects(APITestCase):

View File

@@ -3290,6 +3290,12 @@ class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
path = serializer.validated_data.get("path")
result = format_filename(document, path)
if result:
extension = (
Path(str(document.filename)).suffix if document.filename else ""
) or document.file_type
result_path = Path(result)
result = str(result_path.with_name(f"{result_path.name}{extension}"))
return Response(result)

View File

@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-26 14:37+0000\n"
"POT-Creation-Date: 2026-03-28 20:59+0000\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
@@ -1341,7 +1341,7 @@ msgstr ""
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2587 documents/views.py:3599
#: documents/serialisers.py:2587 documents/views.py:3605
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1613,20 +1613,20 @@ msgstr ""
msgid "Invalid more_like_id"
msgstr ""
#: documents/views.py:3611
#: documents/views.py:3617
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr ""
#: documents/views.py:3654
#: documents/views.py:3660
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:3711
#: documents/views.py:3717
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:3721
#: documents/views.py:3727
msgid "The share link bundle is unavailable."
msgstr ""

View File

@@ -79,6 +79,15 @@ class TestRedisSocketConversion:
),
id="celery_style_socket_with_credentials",
),
# Empty username, password only: unix://:SECRET@/path.sock
pytest.param(
"unix://:SECRET@/run/redis/paperless.sock",
(
"redis+socket://:SECRET@/run/redis/paperless.sock",
"unix://:SECRET@/run/redis/paperless.sock",
),
id="redis_py_style_socket_with_password_only",
),
],
)
def test_redis_socket_parsing(