feat: Gestion multi-profil multi-école

This commit is contained in:
N3WT DE COMPET
2025-03-09 16:22:28 +01:00
parent 95c154a4a2
commit 16178296ec
51 changed files with 1621 additions and 802 deletions

View File

@ -19,7 +19,7 @@ from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
import json
from . import validator
from .models import Profile
from .models import Profile, ProfileRole
from rest_framework.decorators import action, api_view
from Auth.serializers import ProfileSerializer, ProfilUpdateSerializer
@ -56,7 +56,10 @@ class SessionView(APIView):
'user': openapi.Schema(type=openapi.TYPE_OBJECT, properties={
'id': openapi.Schema(type=openapi.TYPE_INTEGER),
'email': openapi.Schema(type=openapi.TYPE_STRING),
'role': openapi.Schema(type=openapi.TYPE_STRING)
'roles': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Items(type=openapi.TYPE_OBJECT, properties={
'role_type': openapi.Schema(type=openapi.TYPE_STRING),
'establishment': openapi.Schema(type=openapi.TYPE_STRING)
}))
})
})),
401: openapi.Response('Session invalide')
@ -67,15 +70,16 @@ class SessionView(APIView):
try:
decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
print(f'decode : {decoded_token}')
userid = decoded_token.get('id')
userid = decoded_token.get('user_id')
user = Profile.objects.get(id=userid)
roles = ProfileRole.objects.filter(profile=user).values('role_type', 'establishment__name')
response_data = {
'user': {
'id': user.id,
'email': user.email,
'role': user.droit, # Assure-toi que le champ 'droit' existe et contient le rôle
'roles': list(roles)
}
}
return JsonResponse(response_data, status=status.HTTP_200_OK)
@ -103,13 +107,11 @@ class ProfileView(APIView):
}
)
def post(self, request):
profil_data=JSONParser().parse(request)
print(f'{profil_data}')
profil_data = JSONParser().parse(request)
profil_serializer = ProfileSerializer(data=profil_data)
if profil_serializer.is_valid():
profil_serializer.save()
profil = profil_serializer.save()
return JsonResponse(profil_serializer.data, safe=False)
return JsonResponse(profil_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@ -122,8 +124,8 @@ class ProfileSimpleView(APIView):
responses={200: ProfileSerializer}
)
def get(self, request, id):
profil=bdd.getObject(Profile, "id", id)
profil_serializer=ProfileSerializer(profil)
profil = bdd.getObject(Profile, "id", id)
profil_serializer = ProfileSerializer(profil)
return JsonResponse(profil_serializer.data, safe=False)
@swagger_auto_schema(
@ -135,12 +137,12 @@ class ProfileSimpleView(APIView):
}
)
def put(self, request, id):
data=JSONParser().parse(request)
data = JSONParser().parse(request)
profil = Profile.objects.get(id=id)
profil_serializer = ProfilUpdateSerializer(profil, data=data)
if profil_serializer.is_valid():
profil_serializer.save()
return JsonResponse("Updated Successfully", safe=False)
return JsonResponse(profil_serializer.data, safe=False)
return JsonResponse(profil_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@ -157,10 +159,11 @@ class LoginView(APIView):
operation_description="Connexion utilisateur",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['email', 'password'],
required=['email', 'password', 'role_type'],
properties={
'email': openapi.Schema(type=openapi.TYPE_STRING),
'password': openapi.Schema(type=openapi.TYPE_STRING)
'password': openapi.Schema(type=openapi.TYPE_STRING),
'role_type': openapi.Schema(type=openapi.TYPE_STRING)
}
),
responses={
@ -193,40 +196,45 @@ class LoginView(APIView):
password=data.get('password'),
)
if user is not None:
if user.is_active:
login(request, user)
user.estConnecte = True
user.save()
clear_cache()
retour = ''
# Générer le JWT avec la bonne syntaxe datetime
access_payload = {
'user_id': user.id,
'email': user.email,
'droit': user.droit,
'establishment': user.establishment.id,
'type': 'access',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
}
role_type = data.get('role_type')
primary_role = ProfileRole.objects.filter(profile=user, role_type=role_type, is_active=True).first()
access_token = jwt.encode(access_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
# Générer le Refresh Token (exp: 7 jours)
refresh_payload = {
'user_id': user.id,
'type': 'refresh',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
}
refresh_token = jwt.encode(refresh_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
if not primary_role:
return JsonResponse({"errorMessage": "Role not assigned to the user"}, status=status.HTTP_401_UNAUTHORIZED)
return JsonResponse({
'token': access_token,
'refresh': refresh_token
}, safe=False)
login(request, user)
user.save()
clear_cache()
retour = ''
# Récupérer tous les rôles de l'utilisateur avec le type spécifié
roles = ProfileRole.objects.filter(profile=user, role_type=role_type).values('role_type', 'establishment__id', 'establishment__name')
# Générer le JWT avec la bonne syntaxe datetime
access_payload = {
'user_id': user.id,
'email': user.email,
'roles': list(roles),
'type': 'access',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
}
access_token = jwt.encode(access_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
# Générer le Refresh Token (exp: 7 jours)
refresh_payload = {
'user_id': user.id,
'type': 'refresh',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
}
refresh_token = jwt.encode(refresh_payload, settings.SIMPLE_JWT['SIGNING_KEY'], algorithm=settings.SIMPLE_JWT['ALGORITHM'])
return JsonResponse({
'token': access_token,
'refresh': refresh_token
}, safe=False)
else:
retour = error.returnMessage[error.PROFIL_INACTIVE]
else:
retour = error.returnMessage[error.WRONG_ID]
@ -235,7 +243,6 @@ class LoginView(APIView):
'errorMessage': retour,
}, safe=False, status=status.HTTP_400_BAD_REQUEST)
class RefreshJWTView(APIView):
@swagger_auto_schema(
operation_description="Rafraîchir le token d'accès",
@ -295,13 +302,20 @@ class RefreshJWTView(APIView):
# Récupérer les informations utilisateur
user = Profile.objects.get(id=payload['user_id'])
role_type = payload.get('role_type')
# Récupérer le rôle principal de l'utilisateur
primary_role = ProfileRole.objects.filter(profile=user, role_type=role_type).first()
if not primary_role:
return JsonResponse({'errorMessage': 'No role assigned to the user'}, status=400)
# Générer un nouveau Access Token avec les informations complètes
new_access_payload = {
'user_id': user.id,
'email': user.email,
'droit': user.droit,
'establishment': user.establishment.id,
'role_type': primary_role.get_role_type_display(),
'establishment': primary_role.establishment.id,
'type': 'access',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
@ -311,6 +325,7 @@ class RefreshJWTView(APIView):
new_refresh_payload = {
'user_id': user.id,
'role_type': role_type,
'type': 'refresh',
'exp': datetime.utcnow() + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
'iat': datetime.utcnow(),
@ -335,6 +350,14 @@ class RefreshJWTView(APIView):
class SubscribeView(APIView):
@swagger_auto_schema(
operation_description="Inscription utilisateur",
manual_parameters=[
openapi.Parameter(
'establishment_id', openapi.IN_QUERY,
description="ID de l'établissement",
type=openapi.TYPE_INTEGER,
required=True
)
],
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['email', 'password1', 'password2'],
@ -359,37 +382,54 @@ class SubscribeView(APIView):
def post(self, request):
retourErreur = error.returnMessage[error.BAD_URL]
retour = ''
newProfilConnection=JSONParser().parse(request)
newProfilConnection = JSONParser().parse(request)
establishment_id = request.GET.get('establishment_id')
if not establishment_id:
return JsonResponse({'message': retour, 'errorMessage': 'establishment_id manquant', "errorFields": {}, "id": -1}, safe=False, status=status.HTTP_400_BAD_REQUEST)
validatorSubscription = validator.ValidatorSubscription(data=newProfilConnection)
validationOk, errorFields = validatorSubscription.validate()
if validationOk:
# On vérifie que l'email existe : si ce n'est pas le cas, on retourne une erreur
profil = bdd.getProfile(Profile.objects.all(), newProfilConnection.get('email'))
if profil == None:
if profil is None:
retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS]
else:
if profil.is_active:
retourErreur=error.returnMessage[error.PROFIL_ACTIVE]
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False)
# Vérifier si le profil a déjà un rôle actif pour l'établissement donné
active_roles = ProfileRole.objects.filter(profile=profil, establishment_id=establishment_id, is_active=True)
if active_roles.exists():
retourErreur = error.returnMessage[error.PROFIL_ACTIVE]
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": profil.id}, safe=False)
else:
try:
profil.set_password(newProfilConnection.get('password1'))
profil.is_active = True
profil.full_clean()
profil.save()
# Utiliser le sérialiseur ProfileRoleSerializer pour créer ou mettre à jour le rôle
role_data = {
'profile': profil.id,
'establishment_id': establishment_id,
'role_type': ProfileRole.RoleType.PROFIL_PARENT,
'is_active': True
}
role_serializer = ProfileRoleSerializer(data=role_data)
if role_serializer.is_valid():
role_serializer.save()
else:
return JsonResponse(role_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
clear_cache()
retour = error.returnMessage[error.MESSAGE_ACTIVATION_PROFILE]
retourErreur=''
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields, "id":profil.id}, safe=False)
retourErreur = ''
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": profil.id}, safe=False)
except ValidationError as e:
retourErreur = error.returnMessage[error.WRONG_MAIL_FORMAT]
return JsonResponse({'message':retour,'errorMessage':retourErreur, "errorFields":errorFields}, safe=False)
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields}, safe=False)
return JsonResponse({'message':retour, 'errorMessage':retourErreur, "errorFields":errorFields, "id":-1}, safe=False)
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": -1}, safe=False)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
@ -417,26 +457,29 @@ class NewPasswordView(APIView):
def post(self, request):
retourErreur = error.returnMessage[error.BAD_URL]
retour = ''
newProfilConnection=JSONParser().parse(request)
newProfilConnection = JSONParser().parse(request)
validatorNewPassword = validator.ValidatorNewPassword(data=newProfilConnection)
validationOk, errorFields = validatorNewPassword.validate()
if validationOk:
profil = bdd.getProfile(Profile.objects.all(), newProfilConnection.get('email'))
if profil == None:
if profil is None:
retourErreur = error.returnMessage[error.PROFIL_NOT_EXISTS]
else:
# Génération d'une URL provisoire pour modifier le mot de passe
profil.code = util.genereRandomCode(12)
profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS)
profil.save()
clear_cache()
retourErreur = ''
retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD]%(newProfilConnection.get('email'))
mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code)
try:
# Génération d'une URL provisoire pour modifier le mot de passe
profil.code = util.genereRandomCode(12)
profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS)
profil.save()
clear_cache()
retourErreur = ''
retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD] % (newProfilConnection.get('email'))
mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code)
except ValidationError as e:
retourErreur = error.returnMessage[error.WRONG_MAIL_FORMAT]
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields}, safe=False)
return JsonResponse({'message':retour, 'errorMessage':retourErreur, "errorFields":errorFields}, safe=False)
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields}, safe=False)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
@ -465,7 +508,7 @@ class ResetPasswordView(APIView):
def post(self, request, code):
retourErreur = error.returnMessage[error.BAD_URL]
retour = ''
newProfilConnection=JSONParser().parse(request)
newProfilConnection = JSONParser().parse(request)
validatorResetPassword = validator.ValidatorResetPassword(data=newProfilConnection)
validationOk, errorFields = validatorResetPassword.validate()
@ -474,16 +517,15 @@ class ResetPasswordView(APIView):
if profil:
if datetime.strptime(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'), '%d-%m-%Y %H:%M') > datetime.strptime(profil.datePeremption, '%d-%m-%Y %H:%M'):
retourErreur = error.returnMessage[error.EXPIRED_URL]%(_uuid)
retourErreur = error.returnMessage[error.EXPIRED_URL] % (_uuid)
elif validationOk:
retour = error.returnMessage[error.PASSWORD_CHANGED]
profil.set_password(newProfilConnection.get('password1'))
profil.code = ''
profil.datePeremption = ''
profil.is_active = True
profil.save()
clear_cache()
retourErreur=''
retourErreur = ''
return JsonResponse({'message':retour, "errorMessage":retourErreur, "errorFields":errorFields}, safe=False)
return JsonResponse({'message': retour, "errorMessage": retourErreur, "errorFields": errorFields}, safe=False)