paperless-ngx/src/paperless/views.py

323 lines
10 KiB
Python
Raw Normal View History

import os
2023-05-06 09:54:45 -07:00
from collections import OrderedDict
from allauth.mfa import signals
from allauth.mfa.adapter import get_adapter as get_mfa_adapter
from allauth.mfa.base.internal.flows import delete_and_cleanup
from allauth.mfa.models import Authenticator
from allauth.mfa.recovery_codes.internal.flows import auto_generate_recovery_codes
from allauth.mfa.totp.internal import auth as totp_auth
from allauth.socialaccount.adapter import get_adapter
from allauth.socialaccount.models import SocialAccount
2022-11-12 18:46:52 +00:00
from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.db.models.functions import Lower
from django.http import HttpResponse
from django.http import HttpResponseBadRequest
from django.http import HttpResponseForbidden
from django.http import HttpResponseNotFound
from django.views.generic import View
2022-11-12 18:46:52 +00:00
from django_filters.rest_framework import DjangoFilterBackend
2023-12-02 08:26:42 -08:00
from rest_framework.authtoken.models import Token
from rest_framework.decorators import action
from rest_framework.filters import OrderingFilter
2023-12-02 08:26:42 -08:00
from rest_framework.generics import GenericAPIView
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import DjangoModelPermissions
from rest_framework.permissions import IsAuthenticated
2023-05-06 09:54:45 -07:00
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
2022-12-05 22:56:03 -08:00
from documents.permissions import PaperlessObjectPermissions
2022-11-12 18:46:52 +00:00
from paperless.filters import GroupFilterSet
from paperless.filters import UserFilterSet
Feature: Allow setting backend configuration settings via the UI (#5126) * Saving some start on this * At least partially working for the tesseract parser * Problems with migration testing need to figure out * Work around that error * Fixes max m_pixels * Moving the settings to main paperless application * Starting some consumer options * More fixes and work * Fixes these last tests * Fix max_length on OcrSettings.mode field * Fix all fields on Common & Ocr settings serializers * Umbrellla config view * Revert "Umbrellla config view" This reverts commit fbaf9f4be30f89afeb509099180158a3406416a5. * Updates to use a single configuration object for all settings * Squashed commit of the following: commit 8a0a49dd5766094f60462fbfbe62e9921fbd2373 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 23:02:47 2023 -0800 Fix formatting commit 66b2d90c507b8afd9507813ff555e46198ea33b9 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 22:36:35 2023 -0800 Refactor frontend data models commit 5723bd8dd823ee855625e250df39393e26709d48 Author: Adam Bogdał <adam@bogdal.pl> Date: Wed Dec 20 01:17:43 2023 +0100 Fix: speed up admin panel for installs with a large number of documents (#5052) commit 9b08ce176199bf9011a6634bb88f616846150d2b Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 15:18:51 2023 -0800 Update PULL_REQUEST_TEMPLATE.md commit a6248bec2d793b7690feed95fcaf5eb34a75bfb6 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 15:02:05 2023 -0800 Chore: Update Angular to v17 (#4980) commit b1f6f52486d5ba5c04af99b41315eb6428fd1fa8 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 13:53:56 2023 -0800 Fix: Dont allow null custom_fields property via API (#5063) commit 638d9970fd468d8c02c91d19bd28f8b0796bdcb1 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 13:43:50 2023 -0800 Enhancement: symmetric document links (#4907) commit 5e8de4c1da6eb4eb8f738b20962595c7536b30ec Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 12:45:04 2023 -0800 Enhancement: shared icon & shared by me filter (#4859) commit 088bad90306025d3f6b139cbd0ad264a1cbecfe5 Author: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Tue Dec 19 12:04:03 2023 -0800 Bulk updates all the backend libraries (#5061) * Saving some work on frontend config * Very basic but dynamically-generated config form * Saving work on slightly less ugly frontend config * JSON validation for user_args field * Fully dynamic config form * Adds in some additional validators for a nicer error message * Cleaning up the testing and coverage more * Reverts unintentional change * Adds documentation about the settings and the precedence * Couple more commenting and style fixes --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2023-12-29 15:42:56 -08:00
from paperless.models import ApplicationConfiguration
from paperless.serialisers import ApplicationConfigurationSerializer
2022-11-12 18:46:52 +00:00
from paperless.serialisers import GroupSerializer
2023-12-02 08:26:42 -08:00
from paperless.serialisers import ProfileSerializer
2022-11-12 18:46:52 +00:00
from paperless.serialisers import UserSerializer
class StandardPagination(PageNumberPagination):
page_size = 25
2020-10-21 12:16:25 +02:00
page_size_query_param = "page_size"
max_page_size = 100000
2023-05-06 09:54:45 -07:00
def get_paginated_response(self, data):
return Response(
OrderedDict(
[
("count", self.page.paginator.count),
("next", self.get_next_link()),
("previous", self.get_previous_link()),
("all", self.get_all_result_ids()),
("results", data),
],
),
)
def get_all_result_ids(self):
ids = []
if hasattr(self.page.paginator.object_list, "saved_results"):
results_page = self.page.paginator.object_list.saved_results[0]
if results_page is not None:
for i in range(len(results_page.results.docs())):
2023-05-06 09:54:45 -07:00
try:
fields = results_page.results.fields(i)
if "id" in fields:
ids.append(fields["id"])
except Exception:
pass
else:
ids = self.page.paginator.object_list.values_list("pk", flat=True)
2023-05-06 09:54:45 -07:00
return ids
def get_paginated_response_schema(self, schema):
response_schema = super().get_paginated_response_schema(schema)
response_schema["properties"]["all"] = {
"type": "array",
"example": "[1, 2, 3]",
}
return response_schema
class FaviconView(View):
Feature: Allow setting backend configuration settings via the UI (#5126) * Saving some start on this * At least partially working for the tesseract parser * Problems with migration testing need to figure out * Work around that error * Fixes max m_pixels * Moving the settings to main paperless application * Starting some consumer options * More fixes and work * Fixes these last tests * Fix max_length on OcrSettings.mode field * Fix all fields on Common & Ocr settings serializers * Umbrellla config view * Revert "Umbrellla config view" This reverts commit fbaf9f4be30f89afeb509099180158a3406416a5. * Updates to use a single configuration object for all settings * Squashed commit of the following: commit 8a0a49dd5766094f60462fbfbe62e9921fbd2373 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 23:02:47 2023 -0800 Fix formatting commit 66b2d90c507b8afd9507813ff555e46198ea33b9 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 22:36:35 2023 -0800 Refactor frontend data models commit 5723bd8dd823ee855625e250df39393e26709d48 Author: Adam Bogdał <adam@bogdal.pl> Date: Wed Dec 20 01:17:43 2023 +0100 Fix: speed up admin panel for installs with a large number of documents (#5052) commit 9b08ce176199bf9011a6634bb88f616846150d2b Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 15:18:51 2023 -0800 Update PULL_REQUEST_TEMPLATE.md commit a6248bec2d793b7690feed95fcaf5eb34a75bfb6 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 15:02:05 2023 -0800 Chore: Update Angular to v17 (#4980) commit b1f6f52486d5ba5c04af99b41315eb6428fd1fa8 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 13:53:56 2023 -0800 Fix: Dont allow null custom_fields property via API (#5063) commit 638d9970fd468d8c02c91d19bd28f8b0796bdcb1 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 13:43:50 2023 -0800 Enhancement: symmetric document links (#4907) commit 5e8de4c1da6eb4eb8f738b20962595c7536b30ec Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 12:45:04 2023 -0800 Enhancement: shared icon & shared by me filter (#4859) commit 088bad90306025d3f6b139cbd0ad264a1cbecfe5 Author: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Tue Dec 19 12:04:03 2023 -0800 Bulk updates all the backend libraries (#5061) * Saving some work on frontend config * Very basic but dynamically-generated config form * Saving work on slightly less ugly frontend config * JSON validation for user_args field * Fully dynamic config form * Adds in some additional validators for a nicer error message * Cleaning up the testing and coverage more * Reverts unintentional change * Adds documentation about the settings and the precedence * Couple more commenting and style fixes --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2023-12-29 15:42:56 -08:00
def get(self, request, *args, **kwargs): # pragma: no cover
favicon = os.path.join(
os.path.dirname(__file__),
"static",
"paperless",
"img",
"favicon.ico",
)
with open(favicon, "rb") as f:
return HttpResponse(f, content_type="image/x-icon")
2022-11-12 18:46:52 +00:00
class UserViewSet(ModelViewSet):
model = User
queryset = User.objects.exclude(
username__in=["consumer", "AnonymousUser"],
).order_by(Lower("username"))
2022-11-12 18:46:52 +00:00
serializer_class = UserSerializer
pagination_class = StandardPagination
2022-12-05 22:56:03 -08:00
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
2022-11-12 18:46:52 +00:00
filter_backends = (DjangoFilterBackend, OrderingFilter)
filterset_class = UserFilterSet
ordering_fields = ("username",)
@action(detail=True, methods=["post"])
def deactivate_totp(self, request, pk=None):
request_user = request.user
user = User.objects.get(pk=pk)
if not request_user.is_superuser and request_user != user:
return HttpResponseForbidden(
"You do not have permission to deactivate TOTP for this user",
)
authenticator = Authenticator.objects.filter(
user=user,
type=Authenticator.Type.TOTP,
).first()
if authenticator is not None:
delete_and_cleanup(request, authenticator)
return Response(True)
else:
return HttpResponseNotFound("TOTP not found")
2022-11-12 18:46:52 +00:00
class GroupViewSet(ModelViewSet):
model = Group
queryset = Group.objects.order_by(Lower("name"))
serializer_class = GroupSerializer
pagination_class = StandardPagination
2022-12-05 22:56:03 -08:00
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
2022-11-12 18:46:52 +00:00
filter_backends = (DjangoFilterBackend, OrderingFilter)
filterset_class = GroupFilterSet
ordering_fields = ("name",)
2023-12-02 08:26:42 -08:00
class ProfileView(GenericAPIView):
"""
User profile view, only available when logged in
"""
permission_classes = [IsAuthenticated]
serializer_class = ProfileSerializer
def get(self, request, *args, **kwargs):
user = self.request.user
serializer = self.get_serializer(data=request.data)
return Response(serializer.to_representation(user))
def patch(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.request.user if hasattr(self.request, "user") else None
if len(serializer.validated_data.get("password").replace("*", "")) > 0:
user.set_password(serializer.validated_data.get("password"))
user.save()
serializer.validated_data.pop("password")
for key, value in serializer.validated_data.items():
setattr(user, key, value)
user.save()
return Response(serializer.to_representation(user))
class TOTPView(GenericAPIView):
"""
TOTP views
"""
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
"""
Generates a new TOTP secret and returns the URL and SVG
"""
user = self.request.user
mfa_adapter = get_mfa_adapter()
secret = totp_auth.get_totp_secret(regenerate=True)
url = mfa_adapter.build_totp_url(user, secret)
svg = mfa_adapter.build_totp_svg(url)
return Response(
{
"url": url,
"qr_svg": svg,
"secret": secret,
},
)
def post(self, request, *args, **kwargs):
"""
Validates a TOTP code and activates the TOTP authenticator
"""
valid = totp_auth.validate_totp_code(
request.data["secret"],
request.data["code"],
)
recovery_codes = None
if valid:
auth = totp_auth.TOTP.activate(
request.user,
request.data["secret"],
).instance
signals.authenticator_added.send(
sender=Authenticator,
request=request,
user=request.user,
authenticator=auth,
)
rc_auth: Authenticator = auto_generate_recovery_codes(request)
if rc_auth:
recovery_codes = rc_auth.wrap().get_unused_codes()
return Response(
{
"success": valid,
"recovery_codes": recovery_codes,
},
)
def delete(self, request, *args, **kwargs):
"""
Deactivates the TOTP authenticator
"""
user = self.request.user
authenticator = Authenticator.objects.filter(
user=user,
type=Authenticator.Type.TOTP,
).first()
if authenticator is not None:
delete_and_cleanup(request, authenticator)
return Response(True)
else:
return HttpResponseNotFound("TOTP not found")
2023-12-02 08:26:42 -08:00
class GenerateAuthTokenView(GenericAPIView):
"""
Generates (or re-generates) an auth token, requires a logged in user
unlike the default DRF endpoint
"""
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
user = self.request.user
existing_token = Token.objects.filter(user=user).first()
if existing_token is not None:
existing_token.delete()
token = Token.objects.create(user=user)
return Response(
token.key,
)
Feature: Allow setting backend configuration settings via the UI (#5126) * Saving some start on this * At least partially working for the tesseract parser * Problems with migration testing need to figure out * Work around that error * Fixes max m_pixels * Moving the settings to main paperless application * Starting some consumer options * More fixes and work * Fixes these last tests * Fix max_length on OcrSettings.mode field * Fix all fields on Common & Ocr settings serializers * Umbrellla config view * Revert "Umbrellla config view" This reverts commit fbaf9f4be30f89afeb509099180158a3406416a5. * Updates to use a single configuration object for all settings * Squashed commit of the following: commit 8a0a49dd5766094f60462fbfbe62e9921fbd2373 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 23:02:47 2023 -0800 Fix formatting commit 66b2d90c507b8afd9507813ff555e46198ea33b9 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 22:36:35 2023 -0800 Refactor frontend data models commit 5723bd8dd823ee855625e250df39393e26709d48 Author: Adam Bogdał <adam@bogdal.pl> Date: Wed Dec 20 01:17:43 2023 +0100 Fix: speed up admin panel for installs with a large number of documents (#5052) commit 9b08ce176199bf9011a6634bb88f616846150d2b Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 15:18:51 2023 -0800 Update PULL_REQUEST_TEMPLATE.md commit a6248bec2d793b7690feed95fcaf5eb34a75bfb6 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 15:02:05 2023 -0800 Chore: Update Angular to v17 (#4980) commit b1f6f52486d5ba5c04af99b41315eb6428fd1fa8 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 13:53:56 2023 -0800 Fix: Dont allow null custom_fields property via API (#5063) commit 638d9970fd468d8c02c91d19bd28f8b0796bdcb1 Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 13:43:50 2023 -0800 Enhancement: symmetric document links (#4907) commit 5e8de4c1da6eb4eb8f738b20962595c7536b30ec Author: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue Dec 19 12:45:04 2023 -0800 Enhancement: shared icon & shared by me filter (#4859) commit 088bad90306025d3f6b139cbd0ad264a1cbecfe5 Author: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Tue Dec 19 12:04:03 2023 -0800 Bulk updates all the backend libraries (#5061) * Saving some work on frontend config * Very basic but dynamically-generated config form * Saving work on slightly less ugly frontend config * JSON validation for user_args field * Fully dynamic config form * Adds in some additional validators for a nicer error message * Cleaning up the testing and coverage more * Reverts unintentional change * Adds documentation about the settings and the precedence * Couple more commenting and style fixes --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2023-12-29 15:42:56 -08:00
class ApplicationConfigurationViewSet(ModelViewSet):
model = ApplicationConfiguration
queryset = ApplicationConfiguration.objects
serializer_class = ApplicationConfigurationSerializer
permission_classes = (IsAuthenticated, DjangoModelPermissions)
class DisconnectSocialAccountView(GenericAPIView):
"""
Disconnects a social account provider from the user account
"""
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
user = self.request.user
try:
account = user.socialaccount_set.get(pk=request.data["id"])
account_id = account.id
account.delete()
return Response(account_id)
except SocialAccount.DoesNotExist:
return HttpResponseBadRequest("Social account not found")
class SocialAccountProvidersView(APIView):
"""
List of social account providers
"""
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
adapter = get_adapter()
providers = adapter.list_providers(request)
resp = [
{"name": p.name, "login_url": p.get_login_url(request, process="connect")}
for p in providers
if p.id != "openid"
]
for openid_provider in filter(lambda p: p.id == "openid", providers):
resp += [
{
"name": b["name"],
"login_url": openid_provider.get_login_url(
request,
process="connect",
openid=b["openid_url"],
),
}
for b in openid_provider.get_brands()
]
return Response(sorted(resp, key=lambda p: p["name"]))