Repository: paperless-ngx/paperless-ngx

Issue:
[BUG] 1.14.0-beta.rc1 Suggestions for private tags/correspondents seems to break frontend
### Description

Whenever paperless attempts to assign a category to an uploaded document, that the current user has no access to, the frontend breaks in multiple ways. 
1. The Suggestion sometimes shows nothing instead of "Private".
![2023-04-16_01-36](https://user-images.githubusercontent.com/65538762/232258636-5c99ef98-f152-4ded-999f-9583c7e1f9b6.png)
2. You can't always return to the Details tab while editing the document.
![2023-04-16_01-37](https://user-images.githubusercontent.com/65538762/232258669-a4a7ed1d-397b-41fb-a388-4752b78293e4.png)
3. The dropdown menus - for example to assign a correspondent - no longer collapse.
4. Assigning categories using the dropdown menus doesn't work properly. If you select anything nothing happens, but after reloading it shows the selected category.
5. The popup menus for adding new categories or deleting the file break in interesting ways.
![2023-04-16_01-39](https://user-images.githubusercontent.com/65538762/232258745-2285576a-d6ac-485f-8789-6d2007ba0d6e.png)
![2023-04-16_01-39_1](https://user-images.githubusercontent.com/65538762/232258748-a7c9204e-a1a6-446f-b89a-da369789acc8.png)


### Steps to reproduce

1. Create User A and User B
2. As User A: Create a correspondent that matches on "xyz Co." and is owned by User A permission-wise
3. As User B: Upload a file containing "xyz Co."
4. Try to assign a different correspondent or remove the empty string by clicking the 'x' on the dropdown menu and then do anything really.

### Webserver logs

```bash
None
```


### Browser logs

```bash
main.js:3 ERROR TypeError: Cannot read properties of undefined (reading 'name')
    at SX (main.js:3:595887)
    at ub (main.js:3:58787)
    at Zp (main.js:3:57564)
    at lF (main.js:3:57986)
    at Zp (main.js:3:58012)
    at lF (main.js:3:57986)
    at Zp (main.js:3:58012)
    at uF (main.js:3:64961)
    at V3 (main.js:3:58422)
    at Zp (main.js:3:58433)

This message but around 10 times per second, and not sure if that's actually related to the issue.
```


### Paperless-ngx version

1.14.0-beta.rc1

### Host OS

Ubuntu 22.04 on x86_64

### Installation method

Docker - official image

### Browser

Chrome, Vivaldi, Edge, Firefox

### Configuration changes

_No response_

### Other

I've tried both the release candidate and the most recent beta version.


Discussion:
Hmm, thanks for the detailed explanation. Are you saying only sometimes that happens but other times it (correctly) shows "Private"? I'm not sure suggestions is handled yet so I'll have to look at it, ultimately I think just some more checks will solve this. I'll take a look when I can soon
Thanks for the quick response.
I think it has worked correctly twice so far. Once right after switching from release candidate to the most recent beta and the second time after trying to reproduce the bug. But once it breaks, which happens either instantly or after accessing the document in question a second time, it stays broken. Then all other documents with this kind of automatic assignment result in the same behavior.

Resolving patch:
diff --git a/src/documents/matching.py b/src/documents/matching.py
--- a/src/documents/matching.py
+++ b/src/documents/matching.py
@@ -6,6 +6,7 @@
 from documents.models import MatchingModel
 from documents.models import StoragePath
 from documents.models import Tag
+from documents.permissions import get_objects_for_user_owner_aware
 
 
 logger = logging.getLogger("paperless.matching")
@@ -19,40 +20,64 @@ def log_reason(matching_model, document, reason):
     )
 
 
-def match_correspondents(document, classifier):
+def match_correspondents(document, classifier, user=None):
     pred_id = classifier.predict_correspondent(document.content) if classifier else None
 
-    correspondents = Correspondent.objects.all()
+    if user is not None:
+        correspondents = get_objects_for_user_owner_aware(
+            user,
+            "documents.view_correspondent",
+            Correspondent,
+        )
+    else:
+        correspondents = Correspondent.objects.all()
 
     return list(
         filter(lambda o: matches(o, document) or o.pk == pred_id, correspondents),
     )
 
 
-def match_document_types(document, classifier):
+def match_document_types(document, classifier, user=None):
     pred_id = classifier.predict_document_type(document.content) if classifier else None
 
-    document_types = DocumentType.objects.all()
+    if user is not None:
+        document_types = get_objects_for_user_owner_aware(
+            user,
+            "documents.view_documenttype",
+            DocumentType,
+        )
+    else:
+        document_types = DocumentType.objects.all()
 
     return list(
         filter(lambda o: matches(o, document) or o.pk == pred_id, document_types),
     )
 
 
-def match_tags(document, classifier):
+def match_tags(document, classifier, user=None):
     predicted_tag_ids = classifier.predict_tags(document.content) if classifier else []
 
-    tags = Tag.objects.all()
+    if user is not None:
+        tags = get_objects_for_user_owner_aware(user, "documents.view_tag", Tag)
+    else:
+        tags = Tag.objects.all()
 
     return list(
         filter(lambda o: matches(o, document) or o.pk in predicted_tag_ids, tags),
     )
 
 
-def match_storage_paths(document, classifier):
+def match_storage_paths(document, classifier, user=None):
     pred_id = classifier.predict_storage_path(document.content) if classifier else None
 
-    storage_paths = StoragePath.objects.all()
+    if user is not None:
+        storage_paths = get_objects_for_user_owner_aware(
+            user,
+            "documents.view_storagepath",
+            StoragePath,
+        )
+    else:
+        storage_paths = StoragePath.objects.all()
 
     return list(
         filter(
diff --git a/src/documents/permissions.py b/src/documents/permissions.py
--- a/src/documents/permissions.py
+++ b/src/documents/permissions.py
@@ -4,6 +4,7 @@
 from django.contrib.contenttypes.models import ContentType
 from guardian.models import GroupObjectPermission
 from guardian.shortcuts import assign_perm
+from guardian.shortcuts import get_objects_for_user
 from guardian.shortcuts import get_users_with_perms
 from guardian.shortcuts import remove_perm
 from rest_framework.permissions import BasePermission
@@ -101,3 +102,15 @@ def set_permissions_for_object(permissions, object):
                         group,
                         object,
                     )
+
+
+def get_objects_for_user_owner_aware(user, perms, Model):
+    objects_owned = Model.objects.filter(owner=user)
+    objects_unowned = Model.objects.filter(owner__isnull=True)
+    objects_with_perms = get_objects_for_user(
+        user=user,
+        perms=perms,
+        klass=Model,
+        accept_global_perms=False,
+    )
+    return objects_owned | objects_unowned | objects_with_perms
diff --git a/src/documents/views.py b/src/documents/views.py
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -401,12 +401,16 @@ def suggestions(self, request, pk=None):
 
         return Response(
             {
-                "correspondents": [c.id for c in match_correspondents(doc, classifier)],
-                "tags": [t.id for t in match_tags(doc, classifier)],
+                "correspondents": [
+                    c.id for c in match_correspondents(doc, classifier, request.user)
+                ],
+                "tags": [t.id for t in match_tags(doc, classifier, request.user)],
                 "document_types": [
-                    dt.id for dt in match_document_types(doc, classifier)
+                    dt.id for dt in match_document_types(doc, classifier, request.user)
+                ],
+                "storage_paths": [
+                    dt.id for dt in match_storage_paths(doc, classifier, request.user)
                 ],
-                "storage_paths": [dt.id for dt in match_storage_paths(doc, classifier)],
                 "dates": [
                     date.strftime("%Y-%m-%d") for date in dates if date is not None
                 ],


Test patch: