feature/rbac permisos y roles implementados
This commit is contained in:
@@ -17,10 +17,14 @@ from rest_framework.pagination import PageNumberPagination
|
||||
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from core.permissions import (
|
||||
IsSameOrganization,
|
||||
IsSameOrganization,
|
||||
IsSameOrganizationDeveloper,
|
||||
IsSameOrganizationAndAdmin,
|
||||
IsSuperUser
|
||||
IsSuperUser,
|
||||
get_org_context,
|
||||
is_internal_service_request,
|
||||
user_has_permission,
|
||||
require_permission,
|
||||
)
|
||||
|
||||
from .serializers import CustomUserSerializer
|
||||
@@ -74,78 +78,62 @@ class CustomUserViewSet(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
|
||||
"""
|
||||
ViewSet for CustomUser model.
|
||||
"""
|
||||
permission_classes = [IsAuthenticated & (IsSuperUser | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSameOrganization )]
|
||||
pagination_class = CustomPagination
|
||||
model = CustomUser
|
||||
serializer_class = CustomUserSerializer
|
||||
filterset_fields = ['username', 'email', 'first_name', 'last_name', 'organizacion', 'is_importador']
|
||||
my_tags = ['User Profile']
|
||||
|
||||
|
||||
def get_permissions(self):
|
||||
# Permitir eliminar usuarios solo a admin, Agente Aduanal y user de la misma organización
|
||||
if self.action == 'destroy':
|
||||
user = self.request.user
|
||||
if not (
|
||||
user.is_superuser or
|
||||
user.groups.filter(name='admin').exists() or
|
||||
user.groups.filter(name='Agente Aduanal').exists() or
|
||||
user.groups.filter(name='user').exists()
|
||||
):
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
raise PermissionDenied("Solo admin, Agente Aduanal o user pueden eliminar usuarios.")
|
||||
elif self.action in ['create', 'update', 'partial_update']:
|
||||
if not (self.request.user.is_superuser or self.request.user.groups.filter(name='admin').exists() or self.request.user.groups.filter(name='Importador').exists()) :
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
raise PermissionDenied("Solo admin o superusuario pueden modificar usuarios.")
|
||||
return super().get_permissions()
|
||||
if self.action in ('me', 'change_password'):
|
||||
return [IsAuthenticated()]
|
||||
perms = {
|
||||
'list': 'usuarios.view',
|
||||
'retrieve': 'usuarios.view',
|
||||
'create': 'usuarios.create',
|
||||
'update': 'usuarios.edit',
|
||||
'partial_update': 'usuarios.edit',
|
||||
'destroy': 'usuarios.delete',
|
||||
}
|
||||
codename = perms.get(self.action, 'usuarios.view')
|
||||
return [IsAuthenticated(), require_permission(codename)()]
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
# Solo permitir eliminar usuarios de la misma organización
|
||||
if self.request.user.is_superuser or instance.organizacion == self.request.user.organizacion:
|
||||
user = self.request.user
|
||||
org = get_org_context(user)
|
||||
if user.is_superuser or instance.organizacion == org:
|
||||
instance.delete()
|
||||
else:
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
raise PermissionDenied("Solo puedes eliminar usuarios de tu organización.")
|
||||
|
||||
def get_queryset(self):
|
||||
# Si es importador, solo puede ver su propio usuario
|
||||
if self.request.user.groups.filter(name='importador').exists() or self.request.user.groups.filter(name='Importador').exists():
|
||||
return CustomUser.objects.filter(pk=self.request.user.pk)
|
||||
|
||||
# Otros roles: filtrar por organización
|
||||
return self.get_queryset_filtrado_por_organizacion()
|
||||
user = self.request.user
|
||||
if is_internal_service_request(self.request):
|
||||
return CustomUser.objects.all()
|
||||
if not user_has_permission(user, 'usuarios.view'):
|
||||
return CustomUser.objects.none()
|
||||
org = get_org_context(user)
|
||||
if not org:
|
||||
return CustomUser.objects.none()
|
||||
return CustomUser.objects.filter(organizacion=org)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
# Always assign the creator's organization
|
||||
if self.request.user.groups.filter(name='admin').exists() and self.request.user.groups.filter(name='Agente Aduanal').exists():
|
||||
if not self.request.user.organizacion:
|
||||
raise PermissionDenied("Los administradores deben tener una organización asignada para crear usuarios.")
|
||||
user = serializer.save(organizacion=self.request.user.organizacion, is_active=False)
|
||||
send_activation_email(user, self.request) # Usa template HTML
|
||||
return
|
||||
creator = self.request.user
|
||||
|
||||
if self.request.user.is_superuser:
|
||||
# If superuser, allow creating users without organization
|
||||
if creator.is_superuser:
|
||||
user = serializer.save(is_active=False)
|
||||
send_activation_email(user, self.request) # Usa template HTML
|
||||
send_activation_email(user, self.request)
|
||||
return
|
||||
|
||||
if self.request.user.groups.filter(name='developer').exists():
|
||||
# Developers can create users but must assign an organization
|
||||
if not self.request.user.organizacion:
|
||||
raise PermissionDenied("Los desarrolladores deben tener una organización asignada para crear usuarios.")
|
||||
user = serializer.save(organizacion=self.request.user.organizacion, is_active=False)
|
||||
send_activation_email(user, self.request) # Usa template HTML
|
||||
return
|
||||
|
||||
if self.request.user.groups.filter(name='importador').exists():
|
||||
# No puedes crear un usuario si eres importador
|
||||
if creator.is_importador:
|
||||
raise PermissionDenied("Los importadores no pueden crear usuarios.")
|
||||
|
||||
user = serializer.save(organizacion=self.request.user.organizacion, is_active=False)
|
||||
send_activation_email(user, self.request) # Usa template HTML
|
||||
return
|
||||
org = get_org_context(creator)
|
||||
if not org:
|
||||
raise PermissionDenied("Debes tener una organización asignada para crear usuarios.")
|
||||
|
||||
user = serializer.save(organizacion=org, is_active=False)
|
||||
send_activation_email(user, self.request)
|
||||
|
||||
@action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
|
||||
def me(self, request):
|
||||
@@ -167,8 +155,11 @@ class CustomUserViewSet(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
|
||||
"""
|
||||
user = self.get_object()
|
||||
current_user = request.user
|
||||
# Solo el propio usuario, admin o superuser pueden cambiar la contraseña
|
||||
if not (current_user.is_superuser or current_user.groups.filter(name='admin').exists() or user == current_user):
|
||||
puede_cambiar_ajena = (
|
||||
current_user.is_superuser or
|
||||
user_has_permission(current_user, 'usuarios.change_password')
|
||||
)
|
||||
if not (puede_cambiar_ajena or user == current_user):
|
||||
raise PermissionDenied("No tienes permiso para cambiar la contraseña de este usuario.")
|
||||
|
||||
old_password = request.data.get('old_password')
|
||||
@@ -176,8 +167,7 @@ class CustomUserViewSet(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
|
||||
if not new_password:
|
||||
return Response({'detail': 'La nueva contraseña es requerida.'}, status=400)
|
||||
|
||||
# Si no es admin/superuser, debe validar old_password
|
||||
if not (current_user.is_superuser or current_user.groups.filter(name='admin').exists()):
|
||||
if not puede_cambiar_ajena:
|
||||
if not old_password or not user.check_password(old_password):
|
||||
return Response({'detail': 'La contraseña actual es incorrecta.'}, status=400)
|
||||
|
||||
@@ -226,11 +216,11 @@ class ProfilePictureView(LoggingMixin, APIView):
|
||||
my_tags = ['User Profile']
|
||||
|
||||
def get(self, request, user_id):
|
||||
# Obtiene el usuario (automáticamente 404 si no existe)
|
||||
user = get_object_or_404(CustomUser, pk=user_id)
|
||||
|
||||
# El permiso IsOwnerOrAdmin ya verificó que request.user == user o es admin
|
||||
# Así que no necesitas validar manualmente los permisos aquí.
|
||||
|
||||
org = get_org_context(request.user)
|
||||
if not request.user.is_superuser and user.organizacion != org:
|
||||
raise Http404("No autorizado")
|
||||
|
||||
if not user.profile_picture:
|
||||
raise Http404("El usuario no tiene imagen de perfil")
|
||||
@@ -267,6 +257,8 @@ class PasswordResetConfirmView(APIView):
|
||||
return Response({'detail': 'Enlace inválido.'}, status=400)
|
||||
if not default_token_generator.check_token(user, token):
|
||||
return Response({'detail': 'Token inválido o expirado.'}, status=400)
|
||||
if not user.is_active:
|
||||
return Response({'detail': 'La cuenta de usuario no está activa.'}, status=400)
|
||||
password = request.data.get('password')
|
||||
if not password:
|
||||
return Response({'detail': 'La nueva contraseña es requerida.'}, status=400)
|
||||
|
||||
Reference in New Issue
Block a user