# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later import re from django.contrib.contenttypes.models import ContentType from django_filters.rest_framework import DjangoFilterBackend from django.db.models import Q from django.conf import settings from django.contrib.auth.models import User from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet from permission.backends import PermissionBackend from note.models import Alias from .filters import RegexSafeSearchFilter from .serializers import UserSerializer, ContentTypeSerializer def is_regex(pattern): try: re.compile(pattern) return True except (re.error, TypeError): return False class ReadProtectedModelViewSet(ModelViewSet): """ Protect a ModelViewSet by filtering the objects that the user cannot see. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class() def get_queryset(self): return self.queryset.filter(PermissionBackend.filter_queryset(self.request, self.model, "view")).distinct() class ReadOnlyProtectedModelViewSet(ReadOnlyModelViewSet): """ Protect a ReadOnlyModelViewSet by filtering the objects that the user cannot see. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class() def get_queryset(self): return self.queryset.filter(PermissionBackend.filter_queryset(self.request, self.model, "view")).distinct() class UserViewSet(ReadProtectedModelViewSet): """ REST API View set. The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer, then render it on /api/user/ """ queryset = User.objects serializer_class = UserSerializer filter_backends = [DjangoFilterBackend] filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', 'note__alias__name', 'note__alias__normalized_name', ] def get_queryset(self): queryset = super().get_queryset() # Sqlite doesn't support ORDER BY in subqueries queryset = queryset.order_by("username") \ if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset if "search" in self.request.GET: pattern = self.request.GET["search"] # Check if this is a valid regex. If not, we won't check regex valid_regex = is_regex(pattern) suffix = "__iregex" if valid_regex else "__istartswith" prefix = "^" if valid_regex else "" # Filter with different rules # We use union-all to keep each filter rule sorted in result queryset = queryset.filter( # Match without normalization Q(**{f"note__alias__name{suffix}": prefix + pattern}) ).union( queryset.filter( # Match with normalization Q(**{f"note__alias__normalized_name{suffix}": prefix + Alias.normalize(pattern)}) & ~Q(**{f"note__alias__name{suffix}": prefix + pattern}) ), all=True, ).union( queryset.filter( # Match on lower pattern Q(**{f"note__alias__normalized_name{suffix}": prefix + pattern.lower()}) & ~Q(**{f"note__alias__normalized_name{suffix}": prefix + Alias.normalize(pattern)}) & ~Q(**{f"note__alias__name{suffix}": prefix + pattern}) ), all=True, ).union( queryset.filter( # Match on firstname or lastname (Q(**{f"last_name{suffix}": prefix + pattern}) | Q(**{f"first_name{suffix}": prefix + pattern})) & ~Q(**{f"note__alias__normalized_name{suffix}": prefix + pattern.lower()}) & ~Q(**{f"note__alias__normalized_name{suffix}": prefix + Alias.normalize(pattern)}) & ~Q(**{f"note__alias__name{suffix}": prefix + pattern}) ), all=True, ) queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \ else queryset.order_by("username") return queryset # This ViewSet is the only one that is accessible from all authenticated users! class ContentTypeViewSet(ReadOnlyModelViewSet): """ REST API View set. The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer, then render it on /api/models/ """ queryset = ContentType.objects.order_by('id') serializer_class = ContentTypeSerializer filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['id', 'app_label', 'model', ] search_fields = ['$app_label', '$model', ]