diff --git a/docker/management_script.sh b/docker/management_script.sh
index 91a6336d0..6d5e84549 100755
--- a/docker/management_script.sh
+++ b/docker/management_script.sh
@@ -7,6 +7,11 @@ cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py management_command "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py management_command "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py management_command "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/convert_mariadb_uuid b/docker/rootfs/usr/local/bin/convert_mariadb_uuid
index 019c558f1..7adb0a1af 100755
--- a/docker/rootfs/usr/local/bin/convert_mariadb_uuid
+++ b/docker/rootfs/usr/local/bin/convert_mariadb_uuid
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py convert_mariadb_uuid "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py convert_mariadb_uuid "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py convert_mariadb_uuid "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py convert_mariadb_uuid "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/createsuperuser b/docker/rootfs/usr/local/bin/createsuperuser
index 2b56869f6..b91cee3c5 100755
--- a/docker/rootfs/usr/local/bin/createsuperuser
+++ b/docker/rootfs/usr/local/bin/createsuperuser
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py createsuperuser "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py createsuperuser "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py createsuperuser "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py createsuperuser "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_archiver b/docker/rootfs/usr/local/bin/document_archiver
index 8d7771d26..4200aa7aa 100755
--- a/docker/rootfs/usr/local/bin/document_archiver
+++ b/docker/rootfs/usr/local/bin/document_archiver
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_archiver "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_archiver "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_archiver "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_archiver "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_create_classifier b/docker/rootfs/usr/local/bin/document_create_classifier
index 23acc6741..518551a4b 100755
--- a/docker/rootfs/usr/local/bin/document_create_classifier
+++ b/docker/rootfs/usr/local/bin/document_create_classifier
@@ -6,7 +6,16 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_create_classifier "$@"
+ python3 manage.py document_create_classifier "$@"
+elif [[ $(id -u) == 0 ]]; then
+ s6-setuidgid paperless python3 manage.py document_create_classifier "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_create_classifier "$@"
+else
+ echo "Unknown user."
+ exit 1
+fi
+er "$@"
elif [[ $(id -un) == "paperless" ]]; then
s6-setuidgid paperless python3 manage.py document_create_classifier "$@"
fi
diff --git a/docker/rootfs/usr/local/bin/document_exporter b/docker/rootfs/usr/local/bin/document_exporter
index d55f01d48..a82d70a16 100755
--- a/docker/rootfs/usr/local/bin/document_exporter
+++ b/docker/rootfs/usr/local/bin/document_exporter
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_exporter "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_exporter "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_exporter "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_exporter "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_fuzzy_match b/docker/rootfs/usr/local/bin/document_fuzzy_match
index c6e4edadc..b97c2a9ba 100755
--- a/docker/rootfs/usr/local/bin/document_fuzzy_match
+++ b/docker/rootfs/usr/local/bin/document_fuzzy_match
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_fuzzy_match "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_fuzzy_match "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_fuzzy_match "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_fuzzy_match "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_importer b/docker/rootfs/usr/local/bin/document_importer
index 07c92bb04..dbfb40a57 100755
--- a/docker/rootfs/usr/local/bin/document_importer
+++ b/docker/rootfs/usr/local/bin/document_importer
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_importer "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_importer "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_importer "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_importer "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_index b/docker/rootfs/usr/local/bin/document_index
index 47c893c10..b05f765da 100755
--- a/docker/rootfs/usr/local/bin/document_index
+++ b/docker/rootfs/usr/local/bin/document_index
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_index "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_index "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_index "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_index "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_renamer b/docker/rootfs/usr/local/bin/document_renamer
index 3406182ee..720edc0d8 100755
--- a/docker/rootfs/usr/local/bin/document_renamer
+++ b/docker/rootfs/usr/local/bin/document_renamer
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_renamer "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_renamer "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_renamer "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_renamer "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_retagger b/docker/rootfs/usr/local/bin/document_retagger
index b0d1047ff..6cbe03c19 100755
--- a/docker/rootfs/usr/local/bin/document_retagger
+++ b/docker/rootfs/usr/local/bin/document_retagger
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_retagger "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_retagger "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_retagger "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_retagger "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_sanity_checker b/docker/rootfs/usr/local/bin/document_sanity_checker
index d792124fc..8fff13a52 100755
--- a/docker/rootfs/usr/local/bin/document_sanity_checker
+++ b/docker/rootfs/usr/local/bin/document_sanity_checker
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_sanity_checker "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_sanity_checker "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_sanity_checker "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_sanity_checker "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_thumbnails b/docker/rootfs/usr/local/bin/document_thumbnails
index 71d80e00d..3c0f0de4c 100755
--- a/docker/rootfs/usr/local/bin/document_thumbnails
+++ b/docker/rootfs/usr/local/bin/document_thumbnails
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_thumbnails "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_thumbnails "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_thumbnails "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_thumbnails "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/mail_fetcher b/docker/rootfs/usr/local/bin/mail_fetcher
index 654c07389..762b850b9 100755
--- a/docker/rootfs/usr/local/bin/mail_fetcher
+++ b/docker/rootfs/usr/local/bin/mail_fetcher
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py mail_fetcher "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py mail_fetcher "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py mail_fetcher "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py mail_fetcher "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/manage_superuser b/docker/rootfs/usr/local/bin/manage_superuser
index a6e41168c..8f550cd1a 100755
--- a/docker/rootfs/usr/local/bin/manage_superuser
+++ b/docker/rootfs/usr/local/bin/manage_superuser
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py manage_superuser "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py manage_superuser "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py manage_superuser "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py manage_superuser "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/prune_audit_logs b/docker/rootfs/usr/local/bin/prune_audit_logs
index 04446df17..8a3ab3299 100755
--- a/docker/rootfs/usr/local/bin/prune_audit_logs
+++ b/docker/rootfs/usr/local/bin/prune_audit_logs
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py prune_audit_logs "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py prune_audit_logs "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py prune_audit_logs "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py prune_audit_logs "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docs/changelog.md b/docs/changelog.md
index f222a7305..d6a87e299 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,5 +1,24 @@
# Changelog
+## paperless-ngx 2.20.6
+
+### Bug Fixes
+
+- Fix: extract all ids for nested tags [@shamoon](https://github.com/shamoon) ([#11888](https://github.com/paperless-ngx/paperless-ngx/pull/11888))
+- Fixhancement: change date calculation for 'this year' to include future documents [@shamoon](https://github.com/shamoon) ([#11884](https://github.com/paperless-ngx/paperless-ngx/pull/11884))
+- Fix: Running management scripts under rootless could fail [@stumpylog](https://github.com/stumpylog) ([#11870](https://github.com/paperless-ngx/paperless-ngx/pull/11870))
+- Fix: use correct field id for overrides [@shamoon](https://github.com/shamoon) ([#11869](https://github.com/paperless-ngx/paperless-ngx/pull/11869))
+
+### All App Changes
+
+
+3 changes
+
+- Fix: extract all ids for nested tags [@shamoon](https://github.com/shamoon) ([#11888](https://github.com/paperless-ngx/paperless-ngx/pull/11888))
+- Fixhancement: change date calculation for 'this year' to include future documents [@shamoon](https://github.com/shamoon) ([#11884](https://github.com/paperless-ngx/paperless-ngx/pull/11884))
+- Fix: use correct field id for overrides [@shamoon](https://github.com/shamoon) ([#11869](https://github.com/paperless-ngx/paperless-ngx/pull/11869))
+
+
## paperless-ngx 2.20.5
### Bug Fixes
diff --git a/pyproject.toml b/pyproject.toml
index b25c4ea34..b9bbca96b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "paperless-ngx"
-version = "2.20.5"
+version = "2.20.6"
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
readme = "README.md"
requires-python = ">=3.10"
diff --git a/src-ui/package.json b/src-ui/package.json
index 0536c0489..560c88037 100644
--- a/src-ui/package.json
+++ b/src-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "paperless-ngx-ui",
- "version": "2.20.5",
+ "version": "2.20.6",
"scripts": {
"preinstall": "npx only-allow pnpm",
"ng": "ng",
diff --git a/src-ui/src/app/components/manage/saved-views/saved-views.component.html b/src-ui/src/app/components/manage/saved-views/saved-views.component.html
index 10487fec8..2f5ef3338 100644
--- a/src-ui/src/app/components/manage/saved-views/saved-views.component.html
+++ b/src-ui/src/app/components/manage/saved-views/saved-views.component.html
@@ -51,6 +51,7 @@
@if (displayFields) {
}
+ Note: ordering is not preserved
diff --git a/src-ui/src/environments/environment.prod.ts b/src-ui/src/environments/environment.prod.ts
index 9ebf29d16..3ce1d16cc 100644
--- a/src-ui/src/environments/environment.prod.ts
+++ b/src-ui/src/environments/environment.prod.ts
@@ -6,7 +6,7 @@ export const environment = {
apiVersion: '9', // match src/paperless/settings.py
appTitle: 'Paperless-ngx',
tag: 'prod',
- version: '2.20.5',
+ version: '2.20.6',
webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
webSocketBaseUrl: base_url.pathname + 'ws/',
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index de5f4d33f..f7ed197da 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -43,6 +43,7 @@ from guardian.utils import get_group_obj_perms_model
from guardian.utils import get_user_obj_perms_model
from rest_framework import fields
from rest_framework import serializers
+from rest_framework.exceptions import PermissionDenied
from rest_framework.fields import SerializerMethodField
from rest_framework.filters import OrderingFilter
@@ -444,6 +445,19 @@ class OwnedObjectSerializer(
return instance
def update(self, instance, validated_data):
+ user = getattr(self, "user", None)
+ is_superuser = user.is_superuser if user is not None else False
+ is_owner = instance.owner == user if user is not None else False
+ is_unowned = instance.owner is None
+
+ if (
+ ("owner" in validated_data and validated_data["owner"] != instance.owner)
+ or "set_permissions" in validated_data
+ ) and not (is_superuser or is_owner or is_unowned):
+ raise PermissionDenied(
+ _("Insufficient permissions."),
+ )
+
if "set_permissions" in validated_data:
self._set_permissions(validated_data["set_permissions"], instance)
self.validate_unique_together(validated_data, instance)
diff --git a/src/documents/tests/test_api_documents.py b/src/documents/tests/test_api_documents.py
index 96d22dc2c..ef06ce648 100644
--- a/src/documents/tests/test_api_documents.py
+++ b/src/documents/tests/test_api_documents.py
@@ -1220,6 +1220,17 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+ def test_upload_insufficient_permissions(self):
+ self.client.force_authenticate(user=User.objects.create_user("testuser2"))
+
+ with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
+ response = self.client.post(
+ "/api/documents/post_document/",
+ {"document": f},
+ )
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
def test_upload_empty_metadata(self):
self.consume_file_mock.return_value = celery.result.AsyncResult(
id=str(uuid.uuid4()),
diff --git a/src/documents/tests/test_api_permissions.py b/src/documents/tests/test_api_permissions.py
index bc81dabe9..31b860745 100644
--- a/src/documents/tests/test_api_permissions.py
+++ b/src/documents/tests/test_api_permissions.py
@@ -441,6 +441,59 @@ class TestApiAuth(DirectoriesMixin, APITestCase):
self.assertTrue(checker.has_perm("change_document", doc))
self.assertIn("change_document", get_perms(group1, doc))
+ def test_document_permissions_change_requires_owner(self):
+ owner = User.objects.create_user(username="owner")
+ editor = User.objects.create_user(username="editor")
+ editor.user_permissions.add(
+ *Permission.objects.all(),
+ )
+
+ doc = Document.objects.create(
+ title="Ownered doc",
+ content="sensitive",
+ checksum="abc123",
+ mime_type="application/pdf",
+ owner=owner,
+ )
+
+ assign_perm("view_document", editor, doc)
+ assign_perm("change_document", editor, doc)
+
+ self.client.force_authenticate(editor)
+ response = self.client.patch(
+ f"/api/documents/{doc.pk}/",
+ json.dumps(
+ {
+ "set_permissions": {
+ "view": {
+ "users": [editor.id],
+ "groups": [],
+ },
+ "change": {
+ "users": None,
+ "groups": None,
+ },
+ },
+ },
+ ),
+ content_type="application/json",
+ )
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ self.client.force_authenticate(editor)
+ response = self.client.patch(
+ f"/api/documents/{doc.pk}/",
+ json.dumps(
+ {
+ "owner": editor.id,
+ },
+ ),
+ content_type="application/json",
+ )
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
def test_dynamic_permissions_fields(self):
user1 = User.objects.create_user(username="user1")
user1.user_permissions.add(*Permission.objects.filter(codename="view_document"))
diff --git a/src/documents/views.py b/src/documents/views.py
index c0f3b5db4..df1fa9827 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -1180,7 +1180,7 @@ class DocumentViewSet(
):
return HttpResponseForbidden("Insufficient permissions to delete notes")
- note = Note.objects.get(id=int(request.GET.get("id")))
+ note = Note.objects.get(id=int(request.GET.get("id")), document=doc)
if settings.AUDIT_LOG_ENABLED:
LogEntry.objects.log_create(
instance=doc,
@@ -1840,6 +1840,8 @@ class PostDocumentView(GenericAPIView):
parser_classes = (parsers.MultiPartParser,)
def post(self, request, *args, **kwargs):
+ if not request.user.has_perm("documents.add_document"):
+ return HttpResponseForbidden("Insufficient permissions")
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
diff --git a/src/paperless/version.py b/src/paperless/version.py
index aeeee68e0..ec6eaed08 100644
--- a/src/paperless/version.py
+++ b/src/paperless/version.py
@@ -1,6 +1,6 @@
from typing import Final
-__version__: Final[tuple[int, int, int]] = (2, 20, 5)
+__version__: Final[tuple[int, int, int]] = (2, 20, 6)
# Version string like X.Y.Z
__full_version_str__: Final[str] = ".".join(map(str, __version__))
# Version string like X.Y
diff --git a/uv.lock b/uv.lock
index 067d4cfb7..c4839e61b 100644
--- a/uv.lock
+++ b/uv.lock
@@ -3157,7 +3157,7 @@ wheels = [
[[package]]
name = "paperless-ngx"
-version = "2.20.5"
+version = "2.20.6"
source = { virtual = "." }
dependencies = [
{ name = "azure-ai-documentintelligence", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },