Compare commits

..

7 Commits

Author SHA1 Message Date
dependabot[bot]
a2df250952 docker(deps): bump astral-sh/uv
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.9.4-python3.12-bookworm-slim to 0.9.5-python3.12-bookworm-slim.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.9.4...0.9.5)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.9.5-python3.12-bookworm-slim
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 21:21:38 +00:00
shamoon
48d21da13b Fix: support ConsumableDocument in email attachments (#11196) 2025-10-27 10:37:57 -07:00
shamoon
701aafce06 Update issue and discussion templates 2025-10-26 12:14:31 -07:00
Tom Hu
1c4fa7237c Chore: Move to using the codecov action instead of the test-results-action (#11179) 2025-10-26 07:07:36 -07:00
shamoon
63dab0ab09 Change: restrict superuser modifications to superusers only 2025-10-24 16:25:59 -07:00
shamoon
276dc31abe Fix: add missing import of ConfirmButtonComponent in user-edit-dialog (#11167) 2025-10-24 15:50:46 -07:00
shamoon
a11a2ec13f Fix: resolve migration warning in 2.19.2 (#11157) 2025-10-23 15:29:49 -07:00
11 changed files with 156 additions and 29 deletions

View File

@@ -51,5 +51,5 @@ body:
id: logs
attributes:
label: Relevant logs or output
description: If you have logs, errors that might help, paste it here.
description: If you have logs, errors that might help, paste it here. For example other containers or services (database, redis, etc).
render: bash

View File

@@ -6,8 +6,8 @@ body:
- type: markdown
attributes:
value: |
### ⚠️ Please remember: issues are for *bugs*
That is, something you believe affects every single user of Paperless-ngx, not just you. If you're not sure, start with one of the other options below.
### ⚠️ Please remember: issues are for *bugs* only! ⚠️
That is, something you believe affects every single user of Paperless-ngx (and the demo, for example), not just you. If you are not sure, start with one of the other options below.
Also, note that **Paperless-ngx does not perform OCR or archive file creation itself**, those are handled by other tools. Problems with OCR or archive versions of specific files should likely be raised 'upstream', see https://github.com/ocrmypdf/OCRmyPDF/issues or https://github.com/tesseract-ocr/tesseract/issues
- type: markdown
@@ -59,6 +59,12 @@ body:
label: Browser logs
description: Logs from the web browser related to your issue, if needed
render: bash
- type: textarea
id: logs_services
attributes:
label: Services logs
description: Logs from other services (or containers) related to your issue, if needed. For example, the database or redis logs.
render: bash
- type: input
id: version
attributes:

View File

@@ -181,10 +181,11 @@ jobs:
pytest
- name: Upload backend test results to Codecov
if: always()
uses: codecov/test-results-action@v1
uses: codecov/codecov-action@v5
with:
flags: backend-python-${{ matrix.python-version }}
files: junit.xml
report_type: test_results
- name: Upload backend coverage to Codecov
uses: codecov/codecov-action@v5
with:
@@ -260,11 +261,12 @@ jobs:
- name: Run Jest unit tests
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
- name: Upload frontend test results to Codecov
uses: codecov/test-results-action@v1
if: always()
uses: codecov/codecov-action@v5
with:
flags: frontend-node-${{ matrix.node-version }}
directory: src-ui/
report_type: test_results
- name: Upload frontend coverage to Codecov
uses: codecov/codecov-action@v5
with:

View File

@@ -32,7 +32,7 @@ RUN set -eux \
# Purpose: Installs s6-overlay and rootfs
# Comments:
# - Don't leave anything extra in here either
FROM ghcr.io/astral-sh/uv:0.9.4-python3.12-bookworm-slim AS s6-overlay-base
FROM ghcr.io/astral-sh/uv:0.9.5-python3.12-bookworm-slim AS s6-overlay-base
WORKDIR /usr/src/s6

View File

@@ -4539,32 +4539,32 @@
<source>Create new user account</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts</context>
<context context-type="linenumber">70</context>
<context context-type="linenumber">72</context>
</context-group>
</trans-unit>
<trans-unit id="2887331217965896363" datatype="html">
<source>Edit user account</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts</context>
<context context-type="linenumber">74</context>
<context context-type="linenumber">76</context>
</context-group>
</trans-unit>
<trans-unit id="5872286584705575476" datatype="html">
<source>Totp deactivated</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts</context>
<context context-type="linenumber">130</context>
<context context-type="linenumber">132</context>
</context-group>
</trans-unit>
<trans-unit id="6439190193788239059" datatype="html">
<source>Totp deactivation failed</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts</context>
<context context-type="linenumber">133</context>
<context context-type="linenumber">135</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts</context>
<context context-type="linenumber">138</context>
<context context-type="linenumber">140</context>
</context-group>
</trans-unit>
<trans-unit id="8419515490539218007" datatype="html">

View File

@@ -14,6 +14,7 @@ import { GroupService } from 'src/app/services/rest/group.service'
import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component'
import { PasswordComponent } from '../../input/password/password.component'
import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component'
@@ -28,6 +29,7 @@ import { PermissionsSelectComponent } from '../../permissions-select/permissions
SelectComponent,
TextComponent,
PasswordComponent,
ConfirmButtonComponent,
FormsModule,
ReactiveFormsModule,
],

View File

@@ -7,6 +7,8 @@ from django.conf import settings
from django.core.mail import EmailMessage
from filelock import FileLock
from documents.data_models import ConsumableDocument
if TYPE_CHECKING:
from documents.models import Document
@@ -15,7 +17,7 @@ def send_email(
subject: str,
body: str,
to: list[str],
attachments: list[Document],
attachments: list[Document | ConsumableDocument],
*,
use_archive: bool,
) -> int:
@@ -45,17 +47,20 @@ def send_email(
# Something could be renaming the file concurrently so it can't be attached
with FileLock(settings.MEDIA_LOCK):
for document in attachments:
attachment_path = (
document.archive_path
if use_archive and document.has_archive_version
else document.source_path
)
friendly_filename = _get_unique_filename(
document,
used_filenames,
archive=use_archive and document.has_archive_version,
)
if isinstance(document, ConsumableDocument):
attachment_path = document.original_file
friendly_filename = document.original_file.name
else:
attachment_path = (
document.archive_path
if use_archive and document.has_archive_version
else document.source_path
)
friendly_filename = _get_unique_filename(
document,
used_filenames,
archive=use_archive and document.has_archive_version,
)
used_filenames.add(friendly_filename)
with attachment_path.open("rb") as f:

View File

@@ -35,15 +35,13 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterField(
model_name="WorkflowAction",
model_name="workflowaction",
name="assign_title",
field=models.TextField(
null=True,
blank=True,
help_text=(
"Assign a document title, can be a JINJA2 template, "
"see documentation.",
),
help_text="Assign a document title, must be a Jinja2 template, see documentation.",
null=True,
verbose_name="assign title",
),
),
migrations.RunPython(

View File

@@ -2,9 +2,11 @@ import types
from unittest.mock import patch
from django.contrib.admin.sites import AdminSite
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from django.test import TestCase
from django.utils import timezone
from rest_framework import status
from documents import index
from documents.admin import DocumentAdmin
@@ -125,3 +127,36 @@ class TestPaperlessAdmin(DirectoriesMixin, TestCase):
form.request = types.SimpleNamespace(user=superuser)
self.assertTrue(form.is_valid())
self.assertEqual({}, form.errors)
def test_superuser_can_only_be_modified_by_superuser(self):
superuser = User.objects.create_superuser(username="superuser", password="test")
user = User.objects.create(
username="test",
is_superuser=False,
is_staff=True,
)
change_user_perm = Permission.objects.get(codename="change_user")
user.user_permissions.add(change_user_perm)
self.client.force_login(user)
response = self.client.patch(
f"/api/users/{superuser.pk}/",
{"first_name": "Updated"},
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(
response.content.decode(),
"Superusers can only be modified by other superusers",
)
self.client.logout()
self.client.force_login(superuser)
response = self.client.patch(
f"/api/users/{superuser.pk}/",
{"first_name": "Updated"},
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
superuser.refresh_from_db()
self.assertEqual(superuser.first_name, "Updated")

View File

@@ -30,6 +30,7 @@ from pytest_django.fixtures import SettingsWrapper
from documents import tasks
from documents.data_models import ConsumableDocument
from documents.data_models import DocumentMetadataOverrides
from documents.data_models import DocumentSource
from documents.matching import document_matches_workflow
from documents.matching import existing_document_matches_workflow
@@ -2788,6 +2789,80 @@ class TestWorkflows(
self.assertEqual(doc.tags.all().count(), 1)
self.assertIn(self.t2, doc.tags.all())
@override_settings(
PAPERLESS_EMAIL_HOST="localhost",
EMAIL_ENABLED=True,
PAPERLESS_URL="http://localhost:8000",
)
@mock.patch("django.core.mail.message.EmailMessage.send")
def test_workflow_assignment_then_email_includes_attachment(self, mock_email_send):
"""
GIVEN:
- Workflow with assignment and email actions
- Email action configured to include the document
WHEN:
- Workflow is run on a newly created document
THEN:
- Email action sends the document as an attachment
"""
storage_path = StoragePath.objects.create(
name="sp2",
path="workflow/{{ document.pk }}",
)
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
)
assignment_action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.ASSIGNMENT,
assign_storage_path=storage_path,
assign_owner=self.user2,
)
assignment_action.assign_tags.add(self.t1)
email_action_config = WorkflowActionEmail.objects.create(
subject="Doc ready {doc_title}",
body="Document URL: {doc_url}",
to="owner@example.com",
include_document=True,
)
email_action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.EMAIL,
email=email_action_config,
)
workflow = Workflow.objects.create(name="Assignment then email", order=0)
workflow.triggers.add(trigger)
workflow.actions.set([assignment_action, email_action])
temp_working_copy = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.dirs.scratch_dir / "working-copy.pdf",
)
Document.objects.create(
title="workflow doc",
correspondent=self.c,
checksum="wf-assignment-email",
mime_type="application/pdf",
)
consumable_document = ConsumableDocument(
source=DocumentSource.ConsumeFolder,
original_file=temp_working_copy,
)
mock_email_send.return_value = 1
with self.assertNoLogs("paperless.handlers", level="ERROR"):
run_workflows(
WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
consumable_document,
overrides=DocumentMetadataOverrides(),
)
mock_email_send.assert_called_once()
@override_settings(
PAPERLESS_EMAIL_HOST="localhost",
EMAIL_ENABLED=True,

View File

@@ -125,6 +125,10 @@ class UserViewSet(ModelViewSet):
def update(self, request, *args, **kwargs):
user_to_update: User = self.get_object()
if not request.user.is_superuser and user_to_update.is_superuser:
return HttpResponseForbidden(
"Superusers can only be modified by other superusers",
)
if (
not request.user.is_superuser
and request.data.get("is_superuser") is not None