- {{option.title}}
-
-
-
+
+
+ {{option.title}}
+
+
+
+ @if (isSet(option.key)) {
+
+ }
@switch (option.type) {
diff --git a/src-ui/src/app/components/admin/config/config.component.spec.ts b/src-ui/src/app/components/admin/config/config.component.spec.ts
index 079bd1420..f4f4799a6 100644
--- a/src-ui/src/app/components/admin/config/config.component.spec.ts
+++ b/src-ui/src/app/components/admin/config/config.component.spec.ts
@@ -144,4 +144,18 @@ describe('ConfigComponent', () => {
component.uploadFile(new File([], 'test.png'), 'app_logo')
expect(initSpy).toHaveBeenCalled()
})
+
+ it('should reset option to null', () => {
+ component.configForm.patchValue({ output_type: OutputTypeConfig.PDF_A })
+ expect(component.isSet('output_type')).toBeTruthy()
+ component.resetOption('output_type')
+ expect(component.configForm.get('output_type').value).toBeNull()
+ expect(component.isSet('output_type')).toBeFalsy()
+ component.configForm.patchValue({ app_title: 'Test Title' })
+ component.resetOption('app_title')
+ expect(component.configForm.get('app_title').value).toBeNull()
+ component.configForm.patchValue({ barcodes_enabled: true })
+ component.resetOption('barcodes_enabled')
+ expect(component.configForm.get('barcodes_enabled').value).toBeNull()
+ })
})
diff --git a/src-ui/src/app/components/admin/config/config.component.ts b/src-ui/src/app/components/admin/config/config.component.ts
index eee617310..44e482e75 100644
--- a/src-ui/src/app/components/admin/config/config.component.ts
+++ b/src-ui/src/app/components/admin/config/config.component.ts
@@ -208,4 +208,12 @@ export class ConfigComponent
},
})
}
+
+ public isSet(key: string): boolean {
+ return this.configForm.get(key).value != null
+ }
+
+ public resetOption(key: string) {
+ this.configForm.get(key).setValue(null)
+ }
}
From b010f65ae7c61a49ed1aa43c0620c4fc733ab428 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Sat, 28 Feb 2026 01:16:53 -0800
Subject: [PATCH 3/4] Fix GHSA-386h-chg4-cfw9
---
src/documents/serialisers.py | 12 ++++++
src/documents/tests/test_api_documents.py | 48 +++++++++++++++++++++++
2 files changed, 60 insertions(+)
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index bec1254c8..647e0e8b5 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -75,6 +75,7 @@ from documents.parsers import is_mime_type_supported
from documents.permissions import get_document_count_filter_for_user
from documents.permissions import get_groups_with_only_permission
from documents.permissions import get_objects_for_user_owner_aware
+from documents.permissions import has_perms_owner_aware
from documents.permissions import set_permissions_for_object
from documents.regex import validate_regex_pattern
from documents.templating.filepath import validate_filepath_template_and_render
@@ -2179,6 +2180,17 @@ class ShareLinkSerializer(OwnedObjectSerializer):
validated_data["slug"] = get_random_string(50)
return super().create(validated_data)
+ def validate_document(self, document):
+ if self.user is not None and has_perms_owner_aware(
+ self.user,
+ "view_document",
+ document,
+ ):
+ return document
+ raise PermissionDenied(
+ _("Insufficient permissions."),
+ )
+
class BulkEditObjectsSerializer(SerializerWithPerms, SetPermissionsMixin):
objects = serializers.ListField(
diff --git a/src/documents/tests/test_api_documents.py b/src/documents/tests/test_api_documents.py
index 700f56568..baa0ffc56 100644
--- a/src/documents/tests/test_api_documents.py
+++ b/src/documents/tests/test_api_documents.py
@@ -2905,6 +2905,54 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
+ def test_create_share_link_requires_view_permission_for_document(self):
+ """
+ GIVEN:
+ - A user with add_sharelink but without view permission on a document
+ WHEN:
+ - API request is made to create a share link for that document
+ THEN:
+ - Share link creation is denied until view permission is granted
+ """
+ user1 = User.objects.create_user(username="test1")
+ user1.user_permissions.add(*Permission.objects.filter(codename="add_sharelink"))
+ user1.save()
+
+ user2 = User.objects.create_user(username="test2")
+ user2.save()
+
+ doc = Document.objects.create(
+ title="test",
+ mime_type="application/pdf",
+ content="this is a document which will be protected",
+ owner=user2,
+ )
+
+ self.client.force_authenticate(user1)
+
+ create_resp = self.client.post(
+ "/api/share_links/",
+ data={
+ "document": doc.pk,
+ "file_version": "original",
+ },
+ format="json",
+ )
+ self.assertEqual(create_resp.status_code, status.HTTP_403_FORBIDDEN)
+
+ assign_perm("view_document", user1, doc)
+
+ create_resp = self.client.post(
+ "/api/share_links/",
+ data={
+ "document": doc.pk,
+ "file_version": "original",
+ },
+ format="json",
+ )
+ self.assertEqual(create_resp.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(create_resp.data["document"], doc.pk)
+
def test_next_asn(self):
"""
GIVEN:
From c7f83212a3cc5ec03d3008ee90aedc12f157f11b Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Sat, 28 Feb 2026 01:27:40 -0800
Subject: [PATCH 4/4] Enforce on selection_data too
---
src/documents/tests/test_api_bulk_edit.py | 16 ++++++++++++++++
src/documents/views.py | 7 +++++++
2 files changed, 23 insertions(+)
diff --git a/src/documents/tests/test_api_bulk_edit.py b/src/documents/tests/test_api_bulk_edit.py
index 945f06b67..be7a81cd4 100644
--- a/src/documents/tests/test_api_bulk_edit.py
+++ b/src/documents/tests/test_api_bulk_edit.py
@@ -773,6 +773,22 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
],
)
+ def test_api_selection_data_requires_view_permission(self):
+ self.doc2.owner = self.user
+ self.doc2.save()
+
+ user1 = User.objects.create(username="user1")
+ self.client.force_authenticate(user=user1)
+
+ response = self.client.post(
+ "/api/documents/selection_data/",
+ json.dumps({"documents": [self.doc2.id]}),
+ content_type="application/json",
+ )
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+ self.assertEqual(response.content, b"Insufficient permissions")
+
@mock.patch("documents.serialisers.bulk_edit.set_permissions")
def test_set_permissions(self, m):
self.setup_mock(m, "set_permissions")
diff --git a/src/documents/views.py b/src/documents/views.py
index 2ce12c330..52687d135 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -1850,6 +1850,13 @@ class SelectionDataView(GenericAPIView):
serializer.is_valid(raise_exception=True)
ids = serializer.validated_data.get("documents")
+ permitted_documents = get_objects_for_user_owner_aware(
+ request.user,
+ "documents.view_document",
+ Document,
+ )
+ if permitted_documents.filter(pk__in=ids).count() != len(ids):
+ return HttpResponseForbidden("Insufficient permissions")
correspondents = Correspondent.objects.annotate(
document_count=Count(