feature/rbac permisos y roles implementados

This commit is contained in:
2026-05-21 07:54:59 -06:00
parent 9bbed42cf3
commit a318b70324
38 changed files with 2596 additions and 901 deletions

View File

@@ -26,11 +26,13 @@ from django.utils import timezone
from django.db.models import Q
from api.utils.storage_service import storage_service
from rest_framework.authentication import TokenAuthentication
from core.permissions import (
IsSameOrganization,
IsSameOrganizationDeveloper,
IsSameOrganizationAndAdmin,
IsSuperUser
get_org_context,
require_permission,
user_has_permission,
IsInternalService,
)
import logging
@@ -142,21 +144,47 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
"""
ViewSet for Document model.
"""
permission_classes = [IsAuthenticated & (IsSuperUser | IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper )]
model = Document
pagination_class = CustomPagination
serializer_class = DocumentSerializer
# Habilitar filtro por pedimento (UUID) y pedimento_numero (campo pedimento del modelo relacionado)
filterset_fields = ['extension', 'size', 'document_type', 'pedimento', 'pedimento__pedimento', 'created_at']
# filterset_fields = ['extension', 'size', 'pedimento', 'pedimento__pedimento']
# Puedes filtrar por pedimento usando: /api/record/documents/?pedimento=<id> o /api/record/documents/?pedimento__pedimento=<numero>
# Ejemplo: /api/record/documents/?pedimento_numero=12345678
my_tags = ['Documents']
def get_permissions(self):
# Service account (Token + superuser): acceso directo sin RBAC de org
if (self.request.user.is_authenticated and self.request.user.is_superuser and
isinstance(getattr(self.request, 'successful_authenticator', None), TokenAuthentication)):
return [IsAuthenticated(), IsInternalService()]
perms = {
'list': 'documentos.view',
'retrieve': 'documentos.view',
'create': 'documentos.upload',
'update': 'documentos.upload',
'partial_update': 'documentos.upload',
'destroy': 'documentos.delete',
'vu_documentos_errores': 'documentos.view',
'bulk_delete': 'documentos.delete',
'bulk_delete_partidas_vu': 'documentos.delete',
'bulk_delete_coves_vu': 'documentos.delete',
'bulk_delete_edocs_vu': 'documentos.delete',
'bulk_upload': 'documentos.upload',
'bulk_upload_vu': 'documentos.upload',
'create_vu_record': 'documentos.upload',
}
codename = perms.get(self.action, 'documentos.view')
return [IsAuthenticated(), require_permission(codename)()]
def get_queryset(self):
queryset = self.get_queryset_filtrado_por_organizacion()
user = self.request.user
if user.is_superuser and isinstance(
getattr(self.request, 'successful_authenticator', None), TokenAuthentication
):
queryset = Document.objects.all()
else:
if not user_has_permission(user, 'documentos.view'):
return Document.objects.none()
queryset = self.get_queryset_filtrado_por_organizacion()
modulo_efc = self.request.query_params.get('modulo')
if modulo_efc:
if modulo_efc == 'expedientes-detalle-pedimentos':
@@ -2017,7 +2045,7 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
class ProtectedDocumentDownloadView(APIView, DocumentosFiltradosMixin):
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
permission_classes = [IsAuthenticated, require_permission('documentos.download')]
serializer_class = DocumentSerializer
model = Document
my_tags = ['Documents']
@@ -2030,17 +2058,14 @@ class ProtectedDocumentDownloadView(APIView, DocumentosFiltradosMixin):
import os
from api.utils.storage_service import storage_service
if not request.user.is_authenticated or not hasattr(request.user, 'organizacion'):
raise Http404("Usuario no autenticado")
try:
doc = Document.objects.get(pk=pk)
except Document.DoesNotExist:
raise Http404("Documento no encontrado")
if not request.user.is_superuser:
if doc.organizacion != request.user.organizacion:
raise Http404("No autorizado")
org = get_org_context(request.user)
if doc.organizacion != org:
raise Http404("No autorizado")
if not doc.archivo:
raise Http404("Documento sin archivo asociado")
@@ -2064,7 +2089,7 @@ class ProtectedDocumentDownloadView(APIView, DocumentosFiltradosMixin):
return response
class BulkDownloadZipView(APIView):
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
permission_classes = [IsAuthenticated, require_permission('documentos.download')]
my_tags = ['Documents']
def post(self, request):
@@ -2172,7 +2197,7 @@ class BulkDownloadZipView(APIView):
logger.warning(f"No se pudo eliminar archivo temporal {tmp_path}: {e}")
class GetFuenteView(APIView):
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
permission_classes = [IsAuthenticated, require_permission('documentos.view')]
serializer_class = FuenteSerializer
my_tags = ['Fuente Documentos']
@@ -2187,7 +2212,7 @@ class GetFuenteView(APIView):
return Response(serializer.data, status=200)
class DocumentTypeView(APIView):
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
permission_classes = [IsAuthenticated, require_permission('documentos.view')]
serializer_class = DocumentTypeSerializer
my_tags = ['Tipo de Documentos']
@@ -2204,7 +2229,7 @@ class DocumentTypeView(APIView):
return Response(serializer.data, status=200)
class ExpedienteZipDownloadView(APIView, DocumentosFiltradosMixin):
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
permission_classes = [IsAuthenticated, require_permission('documentos.download')]
my_tags = ['Documents']
def post(self, request):
@@ -2306,7 +2331,7 @@ class ExpedienteZipDownloadView(APIView, DocumentosFiltradosMixin):
logger.warning(f"No se pudo eliminar archivo temporal {tmp_path}: {e}")
class MultiPedimentoZipDownloadView(APIView):
permission_classes = [IsAuthenticated & (IsSuperUser | IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper)]
permission_classes = [IsAuthenticated, require_permission('documentos.download')]
my_tags = ['Documents']
def post(self, request):
@@ -2375,7 +2400,7 @@ class PedimentoDocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
"""
ViewSet for Document model.
"""
permission_classes = [IsAuthenticated & (IsSuperUser | IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper )]
permission_classes = [IsAuthenticated, require_permission('documentos.view')]
model = Document
pagination_class = CustomPagination
@@ -2389,6 +2414,8 @@ class PedimentoDocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
my_tags = ['Documents']
def get_queryset(self):
if not user_has_permission(self.request.user, 'documentos.view'):
return Document.objects.none()
queryset = self.get_queryset_filtrado_por_organizacion()
pedimento_id = self.request.query_params.get('pedimento')
@@ -2435,8 +2462,7 @@ class TriggerPedimentoCompletoView(APIView):
en el microservicio FastAPI. Reenvía el payload tal cual y devuelve
la respuesta del microservicio (normalmente un `task_id`).
"""
# permission_classes = [IsAuthenticated]
permission_classes = [IsAuthenticated & (IsSuperUser | IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper )]
permission_classes = [IsAuthenticated, require_permission('pedimentos.process')]
my_tags = ['Microservice - Pedimento Completo']