From 7e089980050a5ff448536d57807cf01b36b5acff Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 19 Nov 2025 16:58:49 +0100 Subject: [PATCH] gestion des roles et applications --- src/Controller/UserController.php | 218 ++++++++++++++---- src/Service/UserOrganizationAppService.php | 21 +- .../user/application/information.html.twig | 126 +++++----- templates/user/show.html.twig | 112 ++++++++- 4 files changed, 356 insertions(+), 121 deletions(-) diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index d083b96..5485746 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -8,7 +8,9 @@ use App\Entity\User; use App\Entity\UserOrganizatonApp; use App\Entity\UsersOrganizations; use App\Form\UserForm; +use App\Repository\AppsRepository; use App\Repository\OrganizationsRepository; +use App\Repository\RolesRepository; use App\Repository\UserRepository; use App\Repository\UsersOrganizationsRepository; use App\Service\ActionService; @@ -44,7 +46,13 @@ class UserController extends AbstractController private readonly UserOrganizationService $userOrganizationService, private readonly UserRepository $userRepository, private readonly UsersOrganizationsRepository $uoRepository, - private readonly OrganizationsRepository $organizationRepository, private readonly LoggerInterface $logger, private readonly EmailService $emailService, private readonly AwsService $awsService, private readonly OrganizationsService $organizationsService, + private readonly OrganizationsRepository $organizationRepository, + private readonly LoggerInterface $logger, + private readonly EmailService $emailService, + private readonly AwsService $awsService, + private readonly OrganizationsService $organizationsService, + private readonly AppsRepository $appsRepository, + private readonly RolesRepository $rolesRepository, ) { } @@ -54,41 +62,148 @@ class UserController extends AbstractController public function view(int $id, Request $request): Response { $this->denyAccessUnlessGranted('ROLE_USER'); + $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); - if ($this->userService->hasAccessTo($actingUser)) { - $user = $this->userRepository->find($id); - try { - $orgId = $request->query->get('organizationId'); - if ($orgId) { - $orgs = $this->organizationRepository->findBy(['id' => $orgId]); - $uo = $this->uoRepository->findBy(['users' => $user, 'organization' => $orgs]); - if (!$uo) { - throw $this->createNotFoundException(self::NOT_FOUND); - } - $uoActive = $uo[0]->isActive(); - } else { - $uo = $this->uoRepository->findBy(['users' => $user, 'isActive' => true]); - foreach ($uo as $u) { - $orgs[] = $u->getOrganization(); - } - } - $uoa = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy(['userOrganization' => $uo, 'isActive' => true]); - $uoas = $this->userOrganizationAppService->groupUserOrganizationAppsByApplication($uoa); - $this->actionService->createAction("View user information", $actingUser, null, $user->getUserIdentifier()); - } catch (\Exception $e) { - //ignore - } - } else { + + if (!$this->userService->hasAccessTo($actingUser)) { throw $this->createAccessDeniedException(self::ACCESS_DENIED); } + + $user = $this->userRepository->find($id); + + try { + $orgId = $request->query->get('organizationId'); + $apps = $this->appsRepository->findAll(); + $roles = $this->rolesRepository->findAll(); + + $data = [ + 'roles' => $roles, + ]; + + $uoList = []; + $singleUo = null; + $uoActive = null; + $orgs = []; + + if ($orgId) { + // Specific organization context + $orgs = $this->organizationRepository->findBy(['id' => $orgId]); + $uoList = $this->uoRepository->findBy([ + 'users' => $user, + 'organization' => $orgs, + ]); + + if (!$uoList) { + throw $this->createNotFoundException(self::NOT_FOUND); + } + + $singleUo = $uoList[0]; + $uoActive = $singleUo->isActive(); + } else { + // All active organizations + $uoList = $this->uoRepository->findBy([ + 'users' => $user, + 'isActive'=> true, + ]); + + foreach ($uoList as $u) { + $orgs[] = $u->getOrganization(); + } + + if (count($uoList) === 1) { + $singleUo = $uoList[0]; + $uoActive = $singleUo->isActive(); + } + } + + $data['uoList'] = $uoList; + $data['singleUo'] = $singleUo; + + // Load user-organization-app roles (can be empty) + $uoa = $this->entityManager + ->getRepository(UserOrganizatonApp::class) + ->findBy([ + 'userOrganization' => $uoList, + 'isActive' => true, + ]); + + // Group existing UOA per app + $uoas = $this->userOrganizationAppService + ->groupUserOrganizationAppsByApplication($uoa); + + // ---------- HERE: create empty groups for apps with no UOA ---------- + // Index existing groups by app id + $indexedUoas = []; + foreach ($uoas as $group) { + $indexedUoas[$group['application']->getId()] = $group; + } + + // Load all possible roles once + $allRoles = $this->entityManager->getRepository(Roles::class)->findAll(); + + foreach ($apps as $app) { + $appId = $app->getId(); + + if (!isset($indexedUoas[$appId])) { + // No UOA for this app yet: create an empty group + $indexedUoas[$appId] = [ + 'uoId' => $singleUo ? $singleUo->getId() : null, + 'application' => $app, + 'roles' => [], + 'rolesArray' => [], + 'selectedRoleIds' => [], + ]; + + foreach ($allRoles as $role) { + // Same security logic: ADMIN cannot assign SUPER ADMIN + if ($this->isGranted('ROLE_ADMIN') + && !$this->isGranted('ROLE_SUPER_ADMIN') + && $role->getName() === 'SUPER ADMIN') { + continue; + } + + $indexedUoas[$appId]['rolesArray'][] = [ + 'id' => $role->getId(), + 'name' => $role->getName(), + ]; + } + } + } + if(!$orgId){ + $data['singleUo'] = null; + } + // Overwrite $uoas to include groups for *all* apps + $uoas = array_values($indexedUoas); + $data['uoas'] = $uoas; + // ------------------------------------------------------------------- + + // Compute "can edit" flag: admin AND exactly one UO + $canEditRoles = $this->isGranted('ROLE_ADMIN') && count($uoList) === 1; + + $this->actionService->createAction( + "View user information", + $actingUser, + null, + $user->getUserIdentifier() + ); + + } catch (\Exception $e) { + $canEditRoles = false; + $this->logger->error($e->getMessage()); + } + return $this->render('user/show.html.twig', [ - 'user' => $user, - 'uoas' => $uoas ?? null, - 'orgs' => $orgs ?? null, + 'user' => $user, + 'uoas' => $uoas ?? null, + 'orgs' => $orgs ?? null, 'organizationId' => $orgId ?? null, - 'uoActive' => $uoActive ?? null// specific for single organization context and deactivate user from said org + 'uoActive' => $uoActive ?? null, + 'apps' => $apps ?? [], + 'data' => $data ?? [], + 'canEditRoles' => $canEditRoles ?? false, ]); } + //TODO : MONOLOG #[Route('/edit/{id}', name: 'edit', methods: ['GET', 'POST'])] public function edit(int $id, Request $request): Response @@ -162,8 +277,8 @@ class UserController extends AbstractController $this->logger->notice("User added to organization " . $org->getName()); $this->emailService->sendExistingUserNotificationEmail($existingUser, $org); $this->logger->notice("Existing user notification email sent to " . $existingUser->getUserIdentifier()); - $data = ['user'=>$uo->getUsers(), 'organization'=>$uo->getOrganization()]; - $this->organizationsService->notifyOrganizationAdmins($data,'USER_INVITED'); + $data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()]; + $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED'); return $this->redirectToRoute('organization_show', ['id' => $orgId]); } @@ -195,8 +310,8 @@ class UserController extends AbstractController $this->logger->notice("User added to organization " . $org->getName()); $this->emailService->sendPasswordSetupEmail($user, $orgId); $this->logger->notice("Password setup email sent to " . $user->getUserIdentifier()); - $data = ['user'=>$uo->getUsers(), 'organization'=>$uo->getOrganization()]; - $this->organizationsService->notifyOrganizationAdmins($data,'USER_INVITED'); + $data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()]; + $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED'); } } $this->actionService->createAction("Create new user", $actingUser, null, $user->getUserIdentifier()); @@ -204,7 +319,7 @@ class UserController extends AbstractController $this->entityManager->persist($user); $this->entityManager->flush(); - if( $orgId) { + if ($orgId) { return $this->redirectToRoute('organization_show', ['organizationId' => $orgId]); } return $this->redirectToRoute('user_index'); @@ -217,7 +332,7 @@ class UserController extends AbstractController ]); } catch (\Exception $e) { $this->logger->error($e->getMessage()); - if( $orgId) { + if ($orgId) { return $this->redirectToRoute('organization_show', ['id' => $orgId]); } return $this->redirectToRoute('user_index'); @@ -300,7 +415,7 @@ class UserController extends AbstractController $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo); $data = ['user' => $user, 'organization' => $org]; - $this->organizationsService->notifyOrganizationAdmins($data,"USER_DEACTIVATED"); + $this->organizationsService->notifyOrganizationAdmins($data, "USER_DEACTIVATED"); $this->entityManager->persist($uo); $this->entityManager->flush(); $this->actionService->createAction("Deactivate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier()); @@ -339,13 +454,14 @@ class UserController extends AbstractController $this->actionService->createAction("Activate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier()); $data = ['user' => $user, 'organization' => $org]; - $this->organizationsService->notifyOrganizationAdmins($data,"USER_ACTIVATED"); + $this->organizationsService->notifyOrganizationAdmins($data, "USER_ACTIVATED"); return $this->redirectToRoute('user_index'); } throw $this->createAccessDeniedException(self::ACCESS_DENIED); } + //TODO : MONOLOG + remove picture from bucket #[Route('/delete/{id}', name: 'delete', methods: ['GET', 'POST'])] public function delete(int $id, Request $request): Response @@ -368,7 +484,7 @@ class UserController extends AbstractController $this->actionService->createAction("Delete user", $actingUser, null, $user->getUserIdentifier()); $data = ['user' => $user, 'organization' => null]; - $this->organizationsService->notifyOrganizationAdmins($data,"USER_DELETED"); + $this->organizationsService->notifyOrganizationAdmins($data, "USER_DELETED"); return new Response('', Response::HTTP_NO_CONTENT); //204 } @@ -382,8 +498,7 @@ class UserController extends AbstractController if ($this->userService->hasAccessTo($actingUser, true)) { $uo = $this->userOrganizationService->getByIdOrFail($id); - - $application = $this->entityManager->getRepository(Apps::class)->find($request->get('applicationId')); + $application = $this->entityManager->getRepository(Apps::class)->find($request->get('appId')); if (!$application) { throw $this->createNotFoundException(self::NOT_FOUND); } @@ -395,12 +510,17 @@ class UserController extends AbstractController } if (!empty($selectedRolesIds)) { - $this->userOrganizationAppService->syncRolesForUserOrganizationApp( - $uo, - $application, - $selectedRolesIds, - $actingUser - ); + if (!in_array((string)$roleUser->getId(), $selectedRolesIds, true)){ + $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application); + }else{ + $this->userOrganizationAppService->syncRolesForUserOrganizationApp( + $uo, + $application, + $selectedRolesIds, + $actingUser + ); + } + } else { $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application); } @@ -558,7 +678,7 @@ class UserController extends AbstractController $picture = $this->awsService->getPublicUrl($_ENV['S3_PORTAL_BUCKET']) . $user->getPictureUrl(); $initials = $user->getName()[0] . $user->getSurname()[0]; return [ - 'pictureUrl' =>$picture, + 'pictureUrl' => $picture, 'email' => $user->getEmail(), 'isConnected' => $this->userService->isUserConnected($user->getUserIdentifier()), 'showUrl' => $this->generateUrl('user_show', ['id' => $user->getId()]), @@ -645,10 +765,10 @@ class UserController extends AbstractController } $uo->setModifiedAt(new \DateTimeImmutable()); try { - $data = ['user'=>$uo->getUsers(), 'organization'=>$uo->getOrganization()]; + $data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()]; $this->emailService->sendPasswordSetupEmail($user, $orgId); $this->logger->info("Invitation email resent to user " . $user->getUserIdentifier() . " for organization " . $org->getName()); - $this->organizationsService->notifyOrganizationAdmins($data,'USER_INVITED'); + $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED'); return $this->json(['message' => 'Invitation envoyée avec success.'], Response::HTTP_OK); } catch (\Exception $e) { $this->logger->error("Error resending invitation email to user " . $user->getUserIdentifier() . " for organization " . $org->getName() . ": " . $e->getMessage()); diff --git a/src/Service/UserOrganizationAppService.php b/src/Service/UserOrganizationAppService.php index 847f6fc..64dbce4 100644 --- a/src/Service/UserOrganizationAppService.php +++ b/src/Service/UserOrganizationAppService.php @@ -30,20 +30,18 @@ class UserOrganizationAppService $grouped = []; foreach ($userOrgApps as $uoa) { - if(!$uoa->getRole()->getName() === 'USER') { - continue; // Skip USER role - } + $app = $uoa->getApplication(); $appId = $app->getId(); $roleEntity = $uoa->getRole(); if (!isset($grouped[$appId])) { $grouped[$appId] = [ - 'uoId' => $uoa->getUserOrganization()->getId(), - 'application' => $app, - 'roles' => [], - 'rolesArray' => [], - 'selectedRoleIds' => [], + 'uoId' => $uoa->getUserOrganization()->getId(), + 'application' => $app, + 'roles' => [], + 'rolesArray' => [], + 'selectedRoleIds' => [], ]; } @@ -60,14 +58,11 @@ class UserOrganizationAppService foreach ($grouped as &$appGroup) { foreach ($allRoles as $role) { // exclude SUPER ADMIN from assignable roles if current user is just ADMIN - if ($this->security->isGranted('ROLE_ADMIN') && !$this->security->isGranted('ROLE_SUPER_ADMIN') + if ($this->security->isGranted('ROLE_ADMIN') + && !$this->security->isGranted('ROLE_SUPER_ADMIN') && $role->getName() === 'SUPER ADMIN') { continue; } - // exclude USER role from assignable roles - if ($role->getName() === 'USER') { - continue; - } $appGroup['rolesArray'][] = [ 'id' => $role->getId(), diff --git a/templates/user/application/information.html.twig b/templates/user/application/information.html.twig index d007599..3ec8914 100644 --- a/templates/user/application/information.html.twig +++ b/templates/user/application/information.html.twig @@ -1,57 +1,75 @@ -{% block body %} - {% set roles = uoa.roles %} -{# TODO: compare style with/without border#} -
-
- {% if uoa.application.logoUrl %} - Logo {{ uoa.application.name }} - {% endif %} -
-

{{ uoa.application.name|title }}

-
+{#{% block body %}#} +{# #}{# TODO: compare style with/without border #} +{#
#} +{#
#} +{# {% if app.logoMiniUrl %}#} +{# Logo {{ app.name }}#} +{# {% endif %}#} +{#
#} +{#

{{ app.name|title }}

#} +{#
#} -
-
-
-{# TODO: pb avec le |raw retour à la ligne#} -{# maybe remove Description label #} -

Description : {{ uoa.application.descriptionSmall|default('Aucune description disponible.')|raw }}

-
- {% if is_granted('ROLE_ADMIN') %} -{# TODO: Can be turn into an ajax function#} -
-
- - -
- - -
- {% else %} - - - {% endif %} -
-
+{#
#} +{#
#} +{#
#} +{#

Description : {{ app.descriptionSmall|default('Aucune description disponible.')|raw }}

#} +{#
#} +{# {% if is_granted("ROLE_ADMIN") %}#} +{#
#} +{#
#} +{# #} +{#
#} +{# {% for role in roles %}#} +{# #} +{#
#} +{#
#} +{#
#} +{# {% endif %}#} +{#
#} +{#
#} +{# #}{#
#} +{# #}{#
#} +{# #}{# TODO: pb avec le |raw retour à la ligne #} +{# #}{# maybe remove Description label #} +{# #}{#

Description : {{ uoa.application.descriptionSmall|default('Aucune description disponible.')|raw }}

#} +{# #}{#
#} +{# #}{# {% if is_granted('ROLE_ADMIN') %} #} + +{# #}{# TODO: Can be turn into an ajax function #} +{# #}{#
#} +{# #}{#
#} +{# #}{# #} +{# #}{# #} +{# #}{#
#} +{# #}{# #} +{# #}{# #} +{# #}{#
#} +{# #}{# {% else %} #} +{# #}{# #} +{# #}{# #} +{# #}{# {% endif %} #} +{# #}{#
#} +{# #}{# #} -{% endblock %} \ No newline at end of file +{#{% endblock %}#} \ No newline at end of file diff --git a/templates/user/show.html.twig b/templates/user/show.html.twig index 19e60eb..0066327 100644 --- a/templates/user/show.html.twig +++ b/templates/user/show.html.twig @@ -45,10 +45,112 @@
- {% for uoa in uoas %} - {% include 'user/application/information.html.twig' %} + {% for app in apps %} +
+
+ {% if app.logoMiniUrl %} + Logo {{ app.name }} + {% endif %} +
+

{{ app.name|title }}

+
+
+ +
+
+

Description + : {{ app.descriptionSmall|default('Aucune description disponible.')|raw }} +

+
+ {# EDITABLE if admin and exactly one UO #} + {% if canEditRoles and data.singleUo is not null %} +
+ {# for this app, find its grouped info #} + {# Find the group for this specific app #} + {% set appGroup = null %} + {% for group in data.uoas|default([]) %} + {% if group.application.id == app.id %} + {% set appGroup = group %} + {% endif %} + {% endfor %} + +
+ +
+ + {% if appGroup %} + {# Use rolesArray: filtered by current user's level (no SUPER ADMIN for plain ADMIN, etc.) #} + {% for role in appGroup.rolesArray %} + + + {% endfor %} + + {% else %} + +

Aucun rôle défini pour cette + application.

+ + {% endif %} +
+ +
+
+ + {# READ ONLY otherwise #} + {% else %} + {% set appGroup = null %} + {% for group in data.uoas|default([]) %} + {% if group.application.id == app.id %} + {% set appGroup = group %} + {% endif %} + {% endfor %} + +
+ +
+ + {% if appGroup %} + {# Use rolesArray: filtered by current user's level (no SUPER ADMIN for plain ADMIN, etc.) #} + {% for role in appGroup.rolesArray %} + + + {% endfor %} + {% else %} +

Aucun rôle défini pour cette + application.

+ {% endif %} + +
+
+ {% endif %} +
+
{% endfor %} -
+ @@ -59,7 +161,7 @@ {% endblock %} -{% block title %} + {% block title %} -{% endblock %} + {% endblock %}