diff --git a/cosiap_api/common/views.py b/cosiap_api/common/views.py index 07591b1aee65a28ade8b87e967cc0ba00c87de46..6d51bb9831a78754c36d29e7de5eae133a7cfaf5 100644 --- a/cosiap_api/common/views.py +++ b/cosiap_api/common/views.py @@ -2,7 +2,7 @@ from django.shortcuts import render from rest_framework.views import APIView from rest_framework.permissions import AllowAny, IsAuthenticated from users.permisos import es_admin - +from django.http import HttpResponse, Http404 class BasePermissionAPIView(APIView): """ @@ -36,3 +36,19 @@ class BasePermissionAPIView(APIView): for permission in permissions: if not permission().has_permission(request, self): self.permission_denied(request, message=getattr(permission, 'message', None)) + + +def serve_file(file_url): + """ + Lógica para servir un archivo de forma segura. + `file_url` es la ruta en el servidor del archivo. + """ + + prefix = 'media/' + try: + with open(prefix+file_url, 'rb') as file: + response = HttpResponse(file.read(), content_type="application/octet-stream") + response['Content-Disposition'] = f'attachment; filename={file_url.split("/")[-1]}' + return response + except FileNotFoundError: + raise Http404("Archivo no encontrado.") \ No newline at end of file diff --git a/cosiap_api/users/urls.py b/cosiap_api/users/urls.py index 87e664c81bcecbea661883c221bbc3d5c5dc14ac..5cd077cb8e0add8b160ef7233d90f7d7b341684a 100644 --- a/cosiap_api/users/urls.py +++ b/cosiap_api/users/urls.py @@ -23,4 +23,5 @@ urlpatterns = [ path('restablecer-password/', views.ResetPassword.as_view(), name='reset_password'), path('nueva-password///', views.NuevaPassword.as_view(), name='nueva_password'), path('administradores/', AdminAPIView.as_view() , name = 'administrador_list_create'), + path('descargar-archivo/', views.FileDownloadAPIView.as_view() , name = 'descargar_archivo'), ] \ No newline at end of file diff --git a/cosiap_api/users/views.py b/cosiap_api/users/views.py index 24a0a4848a21f2f44f58414be6df6e18e8567af3..3e6163fb9bf168cc5dd88081294597689be9b2d0 100644 --- a/cosiap_api/users/views.py +++ b/cosiap_api/users/views.py @@ -27,6 +27,10 @@ from .permisos import es_admin, primer_login from notificaciones.mensajes import Mensaje from common.views import BasePermissionAPIView from dynamic_tables.views import DynamicTableAPIView +from rest_framework.exceptions import PermissionDenied +from common.views import serve_file +from dynamic_forms.models import RDocumento, RegistroSeccion +from solicitudes.models import Solicitud class CustomTokenObtainPairView(TokenObtainPairView): def post(self, request, *args, **kwargs): @@ -412,4 +416,99 @@ class VerificarCorreo(APIView): usuario.save() return redirect(f'http://localhost:5173/authentication/register?status=success&message=Cuenta verificada exitosamente.') else: - return redirect(f'http://localhost:5173/authentication/register?status=error&message=El token de verificación es inválido o ha expirado.') \ No newline at end of file + return redirect(f'http://localhost:5173/authentication/register?status=error&message=El token de verificación es inválido o ha expirado.') + + +class FileDownloadAPIView(BasePermissionAPIView): + ''' + Api view para la descarga de un archivo subido en el sistema + ''' + permission_classes_list = [IsAuthenticated] + + def get(self, request): + ''' + Método get para verificar los derechos sobre el archivo y manejar la descarga + ''' + data = {} + try: + + file_url = request.query_params.get('file_url', None) + + if not file_url: + raise PermissionDenied("El parámetro 'file_url' es requerido.") + + + user = request.user + + # Si es admin puede descargar cualquier archivo + if user.is_staff: + return serve_file(file_url) + + # Si no es administrador, obtener el solicitante asociado al usuario + solicitante = Solicitante.objects.get(id=user.id) + + # Verificar si el archivo pertenece al solicitante + if self._file_belongs_to_solicitante(solicitante, file_url): + return serve_file(file_url) + + # Verificar si el archivo pertenece a alguna solicitud del solicitante + if self._file_belongs_to_solicitud(solicitante, file_url): + return serve_file(file_url) + + # Verificar si el archivo pertenece a los datos bancarios del solicitante + if self._file_belongs_to_datos_bancarios(solicitante, file_url): + return serve_file(file_url) + + # Si el archivo no pertenece al solicitante ni a sus datos, denegar el acceso + raise PermissionDenied("No tienes permiso para descargar este archivo.") + + except Exception as e: + Mensaje.error(data, str(e)) + return Response(data, status = status.HTTP_400_BAD_REQUEST) + + def _file_belongs_to_solicitante(self, solicitante, file_url): + """ + Verifica si el archivo pertenece al solicitante, ya sea en su campo INE o cualquier otro campo. + """ + return solicitante.INE == file_url + + def _file_belongs_to_solicitud(self, solicitante, file_url): + """ + Verifica si el archivo pertenece a alguna solicitud del solicitante. + Recorre la relación entre Solicitud -> RegistroSeccion -> RDocumento + para verificar si el archivo se encuentra en RDocumento.valor. + """ + solicitudes = Solicitud.objects.filter(solicitante=solicitante) + + # Recorre todas las solicitudes del solicitante + for solicitud in solicitudes: + registro_formulario = solicitud.registro_formulario + + # Verifica si la solicitud tiene un registro_formulario + if registro_formulario: + # Busca todas las secciones relacionadas al registro_formulario + registro_secciones = RegistroSeccion.objects.filter(registro_formulario=registro_formulario) + + # Recorre todas las secciones relacionadas + for registro_seccion in registro_secciones: + # Busca los documentos relacionados a la sección + documentos = RDocumento.objects.filter(registro_seccion=registro_seccion) + + # Recorre los documentos y verifica si el archivo solicitado está en el campo valor + for documento in documentos: + if documento.valor == file_url: + return True + return False + + def _file_belongs_to_datos_bancarios(self, solicitante, file_url): + """ + Verifica si el archivo pertenece a los datos bancarios del solicitante. + Revisa si el archivo solicitado es el estado de cuenta o la constancia SAT. + """ + datos_bancarios = DatosBancarios.objects.filter(solicitante=solicitante) + + for datos in datos_bancarios: + if datos.doc_estado_cuenta == file_url or datos.doc_constancia_sat == file_url: + return True + + return False \ No newline at end of file