Compare commits

...
Author SHA1 Message Date
stumpylog 13ffed0702 Fix: use a constant password mask and consolidate the no-change check
Return a constant 10 character mask instead.

Move ObfuscatedPasswordField into paperless/fields.py so core no longer
imports it from paperless_mail, and add is_obfuscated_password() to
replace the five checks
2026-11-13 15:17:38 -08:00
GitHub Actions b42bf50b7e Auto translate strings 2026-11-13 21:15:07 +00:00
shamoon d31110278b Fix: ensure ownership for manual WF runs (#14762) 2026-11-13 21:13:34 +00:00
GitHub Actions 5d98a5e5c8 Auto translate strings 2026-11-13 19:11:36 +00:00
shamoon fc8c4e91ec Fix: dont expect task ID for manual WF run in bulk dialog (#14763) 2026-11-13 11:10:06 -08:00
11 changed files with 195 additions and 90 deletions

No files matched your search

+67 -60
View File
@@ -3424,19 +3424,19 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1019</context>
<context context-type="linenumber">1034</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1058</context>
<context context-type="linenumber">1073</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1081</context>
<context context-type="linenumber">1096</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1117</context>
<context context-type="linenumber">1132</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/document-attributes/custom-fields/custom-fields.component.ts</context>
@@ -4199,27 +4199,27 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">637</context>
<context context-type="linenumber">652</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">677</context>
<context context-type="linenumber">692</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">715</context>
<context context-type="linenumber">730</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">753</context>
<context context-type="linenumber">768</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">815</context>
<context context-type="linenumber">830</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">953</context>
<context context-type="linenumber">968</context>
</context-group>
</trans-unit>
<trans-unit id="6088768251338567957" datatype="html">
@@ -6668,7 +6668,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">957</context>
<context context-type="linenumber">972</context>
</context-group>
</trans-unit>
<trans-unit id="1616074213645042277" datatype="html">
@@ -8445,7 +8445,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">582</context>
<context context-type="linenumber">597</context>
</context-group>
<note priority="1" from="description">this string is used to separate processing, failed and added on the file upload widget</note>
</trans-unit>
@@ -8774,7 +8774,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">318</context>
<context context-type="linenumber">320</context>
</context-group>
</trans-unit>
<trans-unit id="7049887240439736400" datatype="html">
@@ -9261,7 +9261,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">955</context>
<context context-type="linenumber">970</context>
</context-group>
</trans-unit>
<trans-unit id="5992101894268263750" datatype="html">
@@ -9286,7 +9286,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1015</context>
<context context-type="linenumber">1030</context>
</context-group>
</trans-unit>
<trans-unit id="2951161989614003846" datatype="html">
@@ -9336,7 +9336,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">320</context>
<context context-type="linenumber">322</context>
</context-group>
</trans-unit>
<trans-unit id="523213570140181690" datatype="html">
@@ -9701,7 +9701,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1115</context>
<context context-type="linenumber">1130</context>
</context-group>
</trans-unit>
<trans-unit id="7251533806673754434" datatype="html">
@@ -9736,46 +9736,53 @@
<source>Run a workflow on <x id="PH" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">319</context>
<context context-type="linenumber">321</context>
</context-group>
</trans-unit>
<trans-unit id="8161885405686762788" datatype="html">
<source>Workflow will run on the selected documents in the background.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">339</context>
</context-group>
</trans-unit>
<trans-unit id="3401633333450192408" datatype="html">
<source>Bulk operation queued.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">358</context>
<context context-type="linenumber">373</context>
</context-group>
</trans-unit>
<trans-unit id="5709555629190115111" datatype="html">
<source>View task</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">362</context>
<context context-type="linenumber">377</context>
</context-group>
</trans-unit>
<trans-unit id="1215215387232313677" datatype="html">
<source>Error executing bulk operation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">414</context>
<context context-type="linenumber">429</context>
</context-group>
</trans-unit>
<trans-unit id="7894972847287473517" datatype="html">
<source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">574</context>
<context context-type="linenumber">589</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">580</context>
<context context-type="linenumber">595</context>
</context-group>
</trans-unit>
<trans-unit id="8639884465898458690" datatype="html">
<source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot; and &quot;<x id="PH_1" equiv-text="items[1].name"/>&quot;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">576</context>
<context context-type="linenumber">591</context>
</context-group>
<note priority="1" from="description">This is for messages like &apos;modify &quot;tag1&quot; and &quot;tag2&quot;&apos;</note>
</trans-unit>
@@ -9783,7 +9790,7 @@
<source><x id="PH" equiv-text="list"/> and &quot;<x id="PH_1" equiv-text="items[items.length - 1].name"/>&quot;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">584,586</context>
<context context-type="linenumber">599,601</context>
</context-group>
<note priority="1" from="description">this is for messages like &apos;modify &quot;tag1&quot;, &quot;tag2&quot; and &quot;tag3&quot;&apos;</note>
</trans-unit>
@@ -9791,14 +9798,14 @@
<source>Confirm tags assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">601</context>
<context context-type="linenumber">616</context>
</context-group>
</trans-unit>
<trans-unit id="6619516195038467207" datatype="html">
<source>This operation will add the tag &quot;<x id="PH" equiv-text="tag.name"/>&quot; to <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">607</context>
<context context-type="linenumber">622</context>
</context-group>
</trans-unit>
<trans-unit id="1894412783609570695" datatype="html">
@@ -9807,14 +9814,14 @@
)"/> to <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">612,614</context>
<context context-type="linenumber">627,629</context>
</context-group>
</trans-unit>
<trans-unit id="7181166515756808573" datatype="html">
<source>This operation will remove the tag &quot;<x id="PH" equiv-text="tag.name"/>&quot; from <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">620</context>
<context context-type="linenumber">635</context>
</context-group>
</trans-unit>
<trans-unit id="3819792277998068944" datatype="html">
@@ -9823,7 +9830,7 @@
)"/> from <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">625,627</context>
<context context-type="linenumber">640,642</context>
</context-group>
</trans-unit>
<trans-unit id="2739066218579571288" datatype="html">
@@ -9834,84 +9841,84 @@
)"/> on <x id="PH_2" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">629,633</context>
<context context-type="linenumber">644,648</context>
</context-group>
</trans-unit>
<trans-unit id="2996713129519325161" datatype="html">
<source>Confirm correspondent assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">670</context>
<context context-type="linenumber">685</context>
</context-group>
</trans-unit>
<trans-unit id="6900893559485781849" datatype="html">
<source>This operation will assign the correspondent &quot;<x id="PH" equiv-text="correspondent.name"/>&quot; to <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">672</context>
<context context-type="linenumber">687</context>
</context-group>
</trans-unit>
<trans-unit id="1257522660364398440" datatype="html">
<source>This operation will remove the correspondent from <x id="PH" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">674</context>
<context context-type="linenumber">689</context>
</context-group>
</trans-unit>
<trans-unit id="5393409374423140648" datatype="html">
<source>Confirm document type assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">708</context>
<context context-type="linenumber">723</context>
</context-group>
</trans-unit>
<trans-unit id="332180123895325027" datatype="html">
<source>This operation will assign the document type &quot;<x id="PH" equiv-text="documentType.name"/>&quot; to <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">710</context>
<context context-type="linenumber">725</context>
</context-group>
</trans-unit>
<trans-unit id="2236642492594872779" datatype="html">
<source>This operation will remove the document type from <x id="PH" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">712</context>
<context context-type="linenumber">727</context>
</context-group>
</trans-unit>
<trans-unit id="6386555513013840736" datatype="html">
<source>Confirm storage path assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">746</context>
<context context-type="linenumber">761</context>
</context-group>
</trans-unit>
<trans-unit id="8750527458618415924" datatype="html">
<source>This operation will assign the storage path &quot;<x id="PH" equiv-text="storagePath.name"/>&quot; to <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">748</context>
<context context-type="linenumber">763</context>
</context-group>
</trans-unit>
<trans-unit id="60728365335056946" datatype="html">
<source>This operation will remove the storage path from <x id="PH" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">750</context>
<context context-type="linenumber">765</context>
</context-group>
</trans-unit>
<trans-unit id="4187352575310415704" datatype="html">
<source>Confirm custom field assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">779</context>
<context context-type="linenumber">794</context>
</context-group>
</trans-unit>
<trans-unit id="7966494636326273856" datatype="html">
<source>This operation will assign the custom field &quot;<x id="PH" equiv-text="customField.name"/>&quot; to <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">785</context>
<context context-type="linenumber">800</context>
</context-group>
</trans-unit>
<trans-unit id="5789455969634598553" datatype="html">
@@ -9920,14 +9927,14 @@
)"/> to <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">790,792</context>
<context context-type="linenumber">805,807</context>
</context-group>
</trans-unit>
<trans-unit id="5648572354333199245" datatype="html">
<source>This operation will remove the custom field &quot;<x id="PH" equiv-text="customField.name"/>&quot; from <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">798</context>
<context context-type="linenumber">813</context>
</context-group>
</trans-unit>
<trans-unit id="6666899594015948817" datatype="html">
@@ -9936,7 +9943,7 @@
)"/> from <x id="PH_1" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">803,805</context>
<context context-type="linenumber">818,820</context>
</context-group>
</trans-unit>
<trans-unit id="8050047262594964176" datatype="html">
@@ -9947,105 +9954,105 @@
)"/> on <x id="PH_2" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">807,811</context>
<context context-type="linenumber">822,826</context>
</context-group>
</trans-unit>
<trans-unit id="8615059324209654051" datatype="html">
<source>Move <x id="PH" equiv-text="this.getSelectionSize()"/> selected document(s) to the trash?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">954</context>
<context context-type="linenumber">969</context>
</context-group>
</trans-unit>
<trans-unit id="135114644550399974" datatype="html">
<source>Moved <x id="PH" equiv-text="count"/> document(s) to the trash</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">981</context>
<context context-type="linenumber">996</context>
</context-group>
</trans-unit>
<trans-unit id="8585195717323764335" datatype="html">
<source>This operation will permanently recreate the archive files for <x id="PH" equiv-text="this.getSelectionSize()"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1016</context>
<context context-type="linenumber">1031</context>
</context-group>
</trans-unit>
<trans-unit id="7366623494074776040" datatype="html">
<source>The archive files will be re-generated with the current settings.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1017</context>
<context context-type="linenumber">1032</context>
</context-group>
</trans-unit>
<trans-unit id="6555329262222566158" datatype="html">
<source>Rotate confirm</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1055</context>
<context context-type="linenumber">1070</context>
</context-group>
</trans-unit>
<trans-unit id="5203024009814367559" datatype="html">
<source>This operation will add rotated versions of the <x id="PH" equiv-text="this.getSelectionSize()"/> document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1056</context>
<context context-type="linenumber">1071</context>
</context-group>
</trans-unit>
<trans-unit id="7910756456450124185" datatype="html">
<source>Merge confirm</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1079</context>
<context context-type="linenumber">1094</context>
</context-group>
</trans-unit>
<trans-unit id="7643543647233874431" datatype="html">
<source>This operation will merge <x id="PH" equiv-text="this.getSelectionSize()"/> selected documents into a new document.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1080</context>
<context context-type="linenumber">1095</context>
</context-group>
</trans-unit>
<trans-unit id="7869008840945899895" datatype="html">
<source>Merged document will be queued for consumption.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1103</context>
<context context-type="linenumber">1118</context>
</context-group>
</trans-unit>
<trans-unit id="1549333914563613027" datatype="html">
<source>The selected documents will become versions of the root document.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1116</context>
<context context-type="linenumber">1131</context>
</context-group>
</trans-unit>
<trans-unit id="7099667366799629283" datatype="html">
<source>Documents merged as versions.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1131</context>
<context context-type="linenumber">1146</context>
</context-group>
</trans-unit>
<trans-unit id="3873496751167944011" datatype="html">
<source>Error updating custom fields.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1161</context>
<context context-type="linenumber">1176</context>
</context-group>
</trans-unit>
<trans-unit id="6144801143088984138" datatype="html">
<source>Share link bundle creation requested.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1209</context>
<context context-type="linenumber">1224</context>
</context-group>
</trans-unit>
<trans-unit id="46019676931295023" datatype="html">
<source>Share link bundle creation is not available yet.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">1216</context>
<context context-type="linenumber">1231</context>
</context-group>
</trans-unit>
<trans-unit id="6307402210351946694" datatype="html">
@@ -686,6 +686,8 @@ describe('BulkEditorComponent', () => {
})
it('should run a workflow on the selection via the run workflow dialog', () => {
const toast = jest.spyOn(toastService, 'show')
const infoToast = jest.spyOn(toastService, 'showInfo')
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
@@ -712,7 +714,12 @@ describe('BulkEditorComponent', () => {
workflow: 5,
filter_documents: false,
})
req.flush(true)
req.flush({ result: 'OK' })
expect(infoToast).toHaveBeenCalledWith(
'Workflow will run on the selected documents in the background.'
)
expect(toast.mock.calls.at(-1)[0].actionName).toBeUndefined()
expect(documentListViewService.selected.size).toBe(0)
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&include_selection_data=true`
) // list reload
@@ -721,6 +728,27 @@ describe('BulkEditorComponent', () => {
) // listAllFilteredIds
})
it('should require ownership of selected documents to run a workflow', () => {
component.manualWorkflows = [{ id: 5 }] as any
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
.mockReturnValue(true)
const ownsDocument = jest
.spyOn(permissionsService, 'currentUserOwnsObject')
.mockReturnValue(false)
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue([{ id: 3 }])
jest
.spyOn(documentListViewService, 'selected', 'get')
.mockReturnValue(new Set([3]))
expect(component.canRunWorkflow).toBe(false)
ownsDocument.mockReturnValue(true)
expect(component.canRunWorkflow).toBe(true)
})
it('should set modal dialog text accordingly for tag edit confirmation', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
@@ -308,7 +308,9 @@ export class BulkEditorComponent
}
get canRunWorkflow(): boolean {
return this.userCanEditAll && this.manualWorkflows.length > 0
return (
this.userCanEditAll && this.userOwnsAll && this.manualWorkflows.length > 0
)
}
runWorkflowSelected() {
@@ -322,10 +324,23 @@ export class BulkEditorComponent
modal.componentInstance.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.executeBulkEditMethod(modal, 'run_workflow', {
workflow: modal.componentInstance.workflowId,
filter_documents: !modal.componentInstance.force,
})
modal.componentInstance.buttonsEnabled.set(false)
this.documentService
.bulkEdit(this.getSelectionQuery(), 'run_workflow', {
workflow: modal.componentInstance.workflowId,
filter_documents: !modal.componentInstance.force,
})
.pipe(first())
.subscribe({
next: () => {
modal.close()
this.list.selectNone()
this.toastService.showInfo(
$localize`Workflow will run on the selected documents in the background.`
)
},
error: (error) => this.handleOperationError(modal, error),
})
})
}
+1
View File
@@ -789,6 +789,7 @@ def has_document_operation_permissions(
bulk_edit.edit_pdf,
bulk_edit.merge_as_versions,
bulk_edit.remove_password,
bulk_edit.run_workflow,
]
)
or (
+3 -2
View File
@@ -703,11 +703,12 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
@mock.patch("documents.views.bulk_edit.run_workflow")
def test_run_workflow_requires_ownership(self, m) -> None:
"""A non-owner without document ownership is forbidden (actions mutate docs)."""
"""A non-owner with change permission cannot run mutating workflow actions."""
self.setup_mock(m, "run_workflow")
user = UserFactory(username="no_perms")
user = UserFactory(username="non_owner")
grant_all_global(user)
doc = DocumentFactory(owner=UserFactory(username="other"))
grant_object(user, doc, "change_document")
workflow = self._make_manual_workflow()
self.client.force_authenticate(user=user)
+23
View File
@@ -0,0 +1,23 @@
from rest_framework import serializers
OBFUSCATED_PASSWORD_MASK = "*" * 10
def is_obfuscated_password(value: str) -> bool:
"""
True if the value is only mask characters (or empty), meaning a client echoed
the placeholder back rather than supplying a new secret
"""
return value.replace("*", "") == ""
class ObfuscatedPasswordField(serializers.CharField):
"""
Sends a constant mask string instead of the password in the clear
"""
def to_representation(self, value) -> str:
return OBFUSCATED_PASSWORD_MASK
def to_internal_value(self, data):
return data
+4 -3
View File
@@ -20,18 +20,19 @@ from rest_framework import serializers
from rest_framework.authtoken.serializers import AuthTokenSerializer
from paperless.config import OCR_RESERVED_USER_ARGS
from paperless.fields import ObfuscatedPasswordField
from paperless.fields import is_obfuscated_password
from paperless.models import ApplicationConfiguration
from paperless.network import validate_outbound_http_url
from paperless.validators import reject_dangerous_svg
from paperless.validators import validate_raster_image
from paperless_mail.serialisers import ObfuscatedPasswordField
logger = logging.getLogger("paperless.settings")
class PasswordValidationMixin:
def _has_real_password(self, value: str | None) -> bool:
return bool(value) and value.replace("*", "") != ""
return bool(value) and not is_obfuscated_password(value)
def validate_password(self, value: str) -> str:
if not self._has_real_password(value):
@@ -285,7 +286,7 @@ class ApplicationConfigurationSerializer(
if data[field] == "":
data[field] = None
# Not a real value, don't overwrite the stored one
elif len(data[field].replace("*", "")) == 0:
elif is_obfuscated_password(data[field]):
del data[field]
return super().run_validation(data)
@@ -0,0 +1,39 @@
import pytest
from paperless.fields import OBFUSCATED_PASSWORD_MASK
from paperless.fields import ObfuscatedPasswordField
from paperless.fields import is_obfuscated_password
class TestObfuscatedPasswordField:
@pytest.mark.parametrize(
"secret",
[
"a",
"short",
"exactly10!",
"a-much-longer-secret-that-should-not-leak-its-length",
],
)
def test_representation_is_constant_regardless_of_length(self, secret: str) -> None:
assert (
ObfuscatedPasswordField().to_representation(secret)
== OBFUSCATED_PASSWORD_MASK
)
class TestIsObfuscatedPassword:
@pytest.mark.parametrize(
("value", "expected"),
[
("", True),
("*", True),
("******", True),
(OBFUSCATED_PASSWORD_MASK, True),
("real-secret", False),
("***x***", False),
(" ", False),
],
)
def test_detects_placeholder(self, value: str, *, expected: bool) -> None:
assert is_obfuscated_password(value) is expected
+2 -1
View File
@@ -41,6 +41,7 @@ from documents.models import PaperlessTask
from documents.permissions import PaperlessModelPermissions
from documents.permissions import PaperlessObjectPermissions
from documents.tasks import llmindex_index
from paperless.fields import is_obfuscated_password
from paperless.filters import GroupFilterSet
from paperless.filters import UserFilterSet
from paperless.models import ApplicationConfiguration
@@ -260,7 +261,7 @@ class ProfileView(GenericAPIView[Any]):
user = self.request.user if hasattr(self.request, "user") else None
password = serializer.validated_data.pop("password", None)
if password and password.replace("*", ""):
if password and not is_obfuscated_password(password):
user.set_password(password)
user.save()
+4 -15
View File
@@ -8,23 +8,13 @@ from documents.serialisers import CorrespondentField
from documents.serialisers import DocumentTypeField
from documents.serialisers import OwnedObjectSerializer
from documents.serialisers import TagsField
from paperless.fields import ObfuscatedPasswordField
from paperless.fields import is_obfuscated_password
from paperless_mail.models import MailAccount
from paperless_mail.models import MailRule
from paperless_mail.models import ProcessedMail
class ObfuscatedPasswordField(serializers.CharField):
"""
Sends *** string instead of password in the clear
"""
def to_representation(self, value) -> str:
return "*" * max(10, len(value))
def to_internal_value(self, data):
return data
class MailAccountSerializer(OwnedObjectSerializer):
password = ObfuscatedPasswordField()
imap_port = serializers.IntegerField(required=True, allow_null=False)
@@ -50,9 +40,8 @@ class MailAccountSerializer(OwnedObjectSerializer):
]
def update(self, instance, validated_data):
if (
"password" in validated_data
and len(validated_data.get("password").replace("*", "")) == 0
if "password" in validated_data and is_obfuscated_password(
validated_data["password"],
):
validated_data.pop("password")
super().update(instance, validated_data)
+3 -3
View File
@@ -30,6 +30,7 @@ from documents.permissions import PaperlessObjectPermissions
from documents.permissions import has_perms_owner_aware
from documents.permissions import permitted_object_ids
from documents.views import PassUserMixin
from paperless.fields import is_obfuscated_password
from paperless.views import StandardPagination
from paperless_mail.filters import ProcessedMailFilterSet
from paperless_mail.mail import MailError
@@ -126,9 +127,8 @@ class MailAccountViewSet(PassUserMixin, ModelViewSet[MailAccount]):
return HttpResponseForbidden("Insufficient permissions")
# account exists, use the password from there instead of ***
if (
len(serializer.validated_data.get("password").replace("*", "")) == 0
and existing_account is not None
if existing_account is not None and is_obfuscated_password(
serializer.validated_data.get("password"),
):
serializer.validated_data["password"] = existing_account.password
serializer.validated_data["account_type"] = existing_account.account_type