diff --git a/Pipfile b/Pipfile index e7702898a..dcbd6a611 100644 --- a/Pipfile +++ b/Pipfile @@ -63,6 +63,8 @@ flower = "*" bleach = "*" # https://www.piwheels.org/project/cryptography/ last built version cryptography = "==38.0.1" +django-guardian = "*" +djangorestframework-guardian = "*" [dev-packages] coveralls = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 844668913..c2bc7bcce 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "cbfe9920231de6e7f993962efb3cc371abdb6b08975232d4cf64d1bad1b53d7a" + "sha256": "f3eaa281038ee70cde11a1bf32573ed51d5e09baae13e2d9a1b082d751d07330" }, "pipfile-spec": 6, "requires": {}, @@ -227,7 +227,7 @@ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.1.1" }, "click": { @@ -313,7 +313,6 @@ "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" ], "index": "pypi", - "markers": "python_version >= '3.6'", "version": "==38.0.1" }, "daphne": { @@ -387,6 +386,14 @@ "index": "pypi", "version": "==22.1" }, + "django-guardian": { + "hashes": [ + "sha256:440ca61358427e575323648b25f8384739e54c38b3d655c81d75e0cd0d61b697", + "sha256:c58a68ae76922d33e6bdc0e69af1892097838de56e93e78a8361090bcd9f89a0" + ], + "index": "pypi", + "version": "==2.4.0" + }, "djangorestframework": { "hashes": [ "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8", @@ -395,6 +402,14 @@ "index": "pypi", "version": "==3.14.0" }, + "djangorestframework-guardian": { + "hashes": [ + "sha256:1883756452d9bfcc2a51fb4e039a6837a8f6697c756447aa83af085749b59330", + "sha256:3bd3dd6ea58e1bceca5048faf6f8b1a93bb5dcff30ba5eb91b9a0e190a48a0c7" + ], + "index": "pypi", + "version": "==0.3.0" + }, "filelock": { "hashes": [ "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc", @@ -1397,7 +1412,7 @@ "sha256:f51dcb39e910a853749250c0f82aced80bca3f7315e9c4ee14349eb7cab6a3f8", "sha256:f5808e1dac6b66c109d6205ce2aebf84bb89e1a1493b7e6df38932df5ebfb9cf" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_version >= '3.7' and python_full_version < '4.0.0'", "version": "==3.6.12" }, "requests": { @@ -1405,7 +1420,7 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_version >= '3.7' and python_full_version < '4.0.0'", "version": "==2.28.1" }, "scikit-learn": { @@ -1633,7 +1648,7 @@ "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" ], - "markers": "python_version >= '3.7'", + "markers": "python_version < '3.10'", "version": "==4.4.0" }, "tzdata": { @@ -1657,7 +1672,7 @@ "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_full_version < '4.0.0'", "version": "==1.26.12" }, "uvicorn": { @@ -2070,7 +2085,7 @@ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.1.1" }, "click": { @@ -2090,7 +2105,9 @@ "version": "==0.4.6" }, "coverage": { - "extras": [], + "extras": [ + "toml" + ], "hashes": [ "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79", "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a", @@ -2749,7 +2766,7 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_version >= '3.7' and python_full_version < '4.0.0'", "version": "==2.28.1" }, "scipy": { @@ -2896,7 +2913,7 @@ "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" ], - "markers": "python_full_version < '3.11.0a7'", + "markers": "python_version < '3.11'", "version": "==2.0.1" }, "tornado": { @@ -2929,7 +2946,7 @@ "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" ], - "markers": "python_version >= '3.7'", + "markers": "python_version < '3.10'", "version": "==4.4.0" }, "urllib3": { @@ -2937,7 +2954,7 @@ "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_full_version < '4.0.0'", "version": "==1.26.12" }, "virtualenv": { diff --git a/src/documents/admin.py b/src/documents/admin.py index 6fa06c49b..c51a5783c 100644 --- a/src/documents/admin.py +++ b/src/documents/admin.py @@ -97,7 +97,7 @@ class RuleInline(admin.TabularInline): class SavedViewAdmin(admin.ModelAdmin): - list_display = ("name", "user") + list_display = ("name", "owner") inlines = [RuleInline] diff --git a/src/documents/migrations/1028_remove_savedview_user_correspondent_owner_and_more.py b/src/documents/migrations/1028_remove_savedview_user_correspondent_owner_and_more.py new file mode 100644 index 000000000..d707028e3 --- /dev/null +++ b/src/documents/migrations/1028_remove_savedview_user_correspondent_owner_and_more.py @@ -0,0 +1,87 @@ +# Generated by Django 4.1.3 on 2022-12-06 04:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("documents", "1027_remove_paperlesstask_attempted_task_and_more"), + ] + + operations = [ + migrations.RenameField( + model_name="savedview", + old_name="user", + new_name="owner", + ), + migrations.AlterField( + model_name="savedview", + name="owner", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + migrations.AddField( + model_name="correspondent", + name="owner", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + migrations.AddField( + model_name="document", + name="owner", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + migrations.AddField( + model_name="documenttype", + name="owner", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + migrations.AddField( + model_name="storagepath", + name="owner", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + migrations.AddField( + model_name="tag", + name="owner", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ] diff --git a/src/documents/models.py b/src/documents/models.py index c1b9c88bc..1e592a68d 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -57,14 +57,27 @@ class MatchingModel(models.Model): return self.name -class Correspondent(MatchingModel): +class ModelWithOwner(models.Model): + owner = models.ForeignKey( + User, + blank=True, + null=True, + on_delete=models.SET_NULL, + verbose_name=_("owner"), + ) + + class Meta: + abstract = True + + +class Correspondent(MatchingModel, ModelWithOwner): class Meta: ordering = ("name",) verbose_name = _("correspondent") verbose_name_plural = _("correspondents") -class Tag(MatchingModel): +class Tag(MatchingModel, ModelWithOwner): color = models.CharField(_("color"), max_length=7, default="#a6cee3") @@ -82,13 +95,13 @@ class Tag(MatchingModel): verbose_name_plural = _("tags") -class DocumentType(MatchingModel): +class DocumentType(MatchingModel, ModelWithOwner): class Meta: verbose_name = _("document type") verbose_name_plural = _("document types") -class StoragePath(MatchingModel): +class StoragePath(MatchingModel, ModelWithOwner): path = models.CharField( _("path"), max_length=512, @@ -100,7 +113,7 @@ class StoragePath(MatchingModel): verbose_name_plural = _("storage paths") -class Document(models.Model): +class Document(ModelWithOwner): STORAGE_TYPE_UNENCRYPTED = "unencrypted" STORAGE_TYPE_GPG = "gpg" @@ -356,14 +369,13 @@ class Log(models.Model): return self.message -class SavedView(models.Model): +class SavedView(ModelWithOwner): class Meta: ordering = ("name",) verbose_name = _("saved view") verbose_name_plural = _("saved views") - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_("user")) name = models.CharField(_("name"), max_length=128) show_on_dashboard = models.BooleanField( diff --git a/src/documents/views.py b/src/documents/views.py index 9ffc23693..0657ae93a 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -556,10 +556,10 @@ class SavedViewViewSet(ModelViewSet): def get_queryset(self): user = self.request.user - return SavedView.objects.filter(user=user) + return SavedView.objects.filter(owner=user) def perform_create(self, serializer): - serializer.save(user=self.request.user) + serializer.save(owner=self.request.user) class BulkEditView(GenericAPIView): diff --git a/src/paperless/settings.py b/src/paperless/settings.py index c11e43489..dd078f0f9 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -171,6 +171,7 @@ INSTALLED_APPS = [ "rest_framework.authtoken", "django_filters", "django_celery_results", + "guardian", ] + env_apps if DEBUG: @@ -276,6 +277,7 @@ if ENABLE_HTTP_REMOTE_USER: AUTHENTICATION_BACKENDS = [ "django.contrib.auth.backends.RemoteUserBackend", "django.contrib.auth.backends.ModelBackend", + "guardian.backends.ObjectPermissionBackend", ] REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"].append( "rest_framework.authentication.RemoteUserAuthentication", diff --git a/src/paperless_mail/migrations/0017_mailaccount_owner_mailrule_owner.py b/src/paperless_mail/migrations/0017_mailaccount_owner_mailrule_owner.py new file mode 100644 index 000000000..5eeccf440 --- /dev/null +++ b/src/paperless_mail/migrations/0017_mailaccount_owner_mailrule_owner.py @@ -0,0 +1,38 @@ +# Generated by Django 4.1.3 on 2022-12-06 04:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("paperless_mail", "0016_mailrule_consumption_scope"), + ] + + operations = [ + migrations.AddField( + model_name="mailaccount", + name="owner", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + migrations.AddField( + model_name="mailrule", + name="owner", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ] diff --git a/src/paperless_mail/models.py b/src/paperless_mail/models.py index a7267cc06..f8a30c3be 100644 --- a/src/paperless_mail/models.py +++ b/src/paperless_mail/models.py @@ -3,7 +3,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -class MailAccount(models.Model): +class MailAccount(document_models.ModelWithOwner): class Meta: verbose_name = _("mail account") verbose_name_plural = _("mail accounts") @@ -51,7 +51,7 @@ class MailAccount(models.Model): return self.name -class MailRule(models.Model): +class MailRule(document_models.ModelWithOwner): class Meta: verbose_name = _("mail rule") verbose_name_plural = _("mail rules")