From a01df6345a9b4c57165af93bbef238b485e895d7 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 9 Sep 2025 12:04:15 +0200 Subject: [PATCH] add Admin when Super Admin is added --- src/Service/UserOrganizationAppService.php | 86 ++++++++++++++++++++-- src/Service/UserService.php | 73 +++++++++++++++++- 2 files changed, 148 insertions(+), 11 deletions(-) diff --git a/src/Service/UserOrganizationAppService.php b/src/Service/UserOrganizationAppService.php index 175272d..41cb088 100644 --- a/src/Service/UserOrganizationAppService.php +++ b/src/Service/UserOrganizationAppService.php @@ -93,24 +93,45 @@ class UserOrganizationAppService } } + /** + * Synchronizes user roles for a specific application within an organization. + * + * This method handles the complete lifecycle of user-application role assignments: + * - Activates/deactivates existing role links based on selection + * - Creates new role assignments for newly selected roles + * - Updates the user's global Symfony security roles when ADMIN/SUPER_ADMIN roles are assigned + * + * @param UsersOrganizations $uo The user-organization relationship + * @param Apps $application The target application + * @param array $selectedRoleIds Array of role IDs that should be active for this user-app combination + * @param User $actingUser The user performing this action (for audit logging) + * + * @return void + * + * @throws \Exception If role entities cannot be found or persisted + */ public function syncRolesForUserOrganizationApp( UsersOrganizations $uo, Apps $application, array $selectedRoleIds, User $actingUser ): void { - $repo = $this->entityManager->getRepository(UserOrganizatonApp::class); - $currentLinks = $repo->findBy([ + + // Fetch existing UserOrganizationApp links for this user and application + $uoas = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy([ 'userOrganization' => $uo, 'application' => $application, ]); $currentRoleIds = []; - foreach ($currentLinks as $uoa) { + // Process existing role links - activate or deactivate based on selection + foreach ($uoas as $uoa) { $roleId = $uoa->getRole()->getId(); $currentRoleIds[] = $roleId; + $roleName = $uoa->getRole()->getName(); - if (in_array($roleId, $selectedRoleIds)) { + if (in_array((string) $roleId, $selectedRoleIds, true)) { + // Role is selected - ensure it's active if (!$uoa->isActive()) { $uoa->setIsActive(true); $this->entityManager->persist($uoa); @@ -118,10 +139,19 @@ class UserOrganizationAppService "Re-activate user role for application", $actingUser, $uo->getOrganization(), - "App: {$application->getName()}, Role: {$uoa->getRole()->getName()} for user {$uo->getUsers()->getUserIdentifier()}" + "App: {$application->getName()}, Role: $roleName for user {$uo->getUsers()->getUserIdentifier()}" ); + // Sync Admins roles to user's global Symfony security roles + if (in_array($roleName, ['ADMIN', 'SUPER ADMIN'], true)) { + $this->userService->syncUserRoles($uo->getUsers(), $roleName, true); + } + // Ensure ADMIN role is assigned if SUPER ADMIN is activated + if ($roleName === 'SUPER ADMIN') { + $this->ensureAdminRoleForSuperAdmin($uoa); + } } } else { + // Role is not selected - ensure it's inactive if ($uoa->isActive()) { $uoa->setIsActive(false); $this->entityManager->persist($uoa); @@ -130,23 +160,37 @@ class UserOrganizationAppService "Deactivate user role for application", $actingUser, $uo->getOrganization(), - "App: {$application->getName()}, Role: {$uoa->getRole()->getName()} for user {$uo->getUsers()->getUserIdentifier()}" + "App: {$application->getName()}, Role: $roleName for user {$uo->getUsers()->getUserIdentifier()}" ); + // Sync Admins roles to user's global Symfony security roles + if (in_array($roleName, ['ADMIN', 'SUPER ADMIN'], true)) { + $this->userService->syncUserRoles($uo->getUsers(), $roleName, false); + } + } } } - // Add missing roles + // Create new role assignments for roles that don't exist yet foreach ($selectedRoleIds as $roleId) { if (!in_array($roleId, $currentRoleIds)) { $role = $this->entityManager->getRepository(Roles::class)->find($roleId); if ($role) { + // Create new user-organization-application role link $newUoa = new UserOrganizatonApp(); $newUoa->setUserOrganization($uo); $newUoa->setApplication($application); $newUoa->setRole($role); $newUoa->setIsActive(true); + // Sync Admins roles to user's global Symfony security roles + if (in_array($role->getName(), ['ADMIN', 'SUPER ADMIN'], true)) { + $this->userService->syncUserRoles($uo->getUsers(), $role->getName(), true); + } + // Ensure ADMIN role is assigned if SUPER ADMIN is activated + if ($role->getName() === 'SUPER ADMIN') { + $this->ensureAdminRoleForSuperAdmin($newUoa); + } $this->entityManager->persist($newUoa); $this->actionService->createAction("New user role for application", $actingUser, @@ -155,8 +199,34 @@ class UserOrganizationAppService } } } - $this->entityManager->flush(); } + /** + * Attribute the role Admin to the user if the user has the role Super Admin + * + * @param UserOrganizatonApp $uoa + * + * @return void + */ + public function ensureAdminRoleForSuperAdmin(UserOrganizatonApp $uoa): void + { + $uoaAdmin = $this->entityManager->getRepository(UserOrganizatonApp::class)->findOneBy([ + 'userOrganization' => $uoa->getUserOrganization(), + 'application' => $uoa->getApplication(), + 'role' => $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']) + ]); + if(!$uoaAdmin) { + $uoaAdmin = new UserOrganizatonApp(); + $uoaAdmin->setUserOrganization($uoa->getUserOrganization()); + $uoaAdmin->setApplication($uoa->getApplication()); + $uoaAdmin->setRole($this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN'])); + $uoaAdmin->setIsActive(true); + $this->entityManager->persist($uoaAdmin); + } + // If the ADMIN role link exists but is inactive, activate it + if ($uoaAdmin && !$uoaAdmin->isActive()) { + $uoaAdmin->setIsActive(true); + } + } } diff --git a/src/Service/UserService.php b/src/Service/UserService.php index 8d522e4..6297c14 100644 --- a/src/Service/UserService.php +++ b/src/Service/UserService.php @@ -26,7 +26,7 @@ class UserService public function __construct(private readonly EntityManagerInterface $entityManager, private readonly Security $security, - string $profileDirectory + string $profileDirectory ) { $this->profileDirectory = $profileDirectory; @@ -281,12 +281,79 @@ class UserService $user = $uo->getUsers(); $users[] = [ - 'entity' => $user, + 'entity' => $user, 'connected' => $this->isUserConnected($user->getUserIdentifier()), - 'isActive' => (bool)$uo->isActive(), + 'isActive' => (bool)$uo->isActive(), ]; } return $users; } + + /** + * Handle user's role synchronization + * + * @param User $user + * @param string $role + * @param boolean $add + * @return void + */ + public function syncUserRoles(User $user, string $role, bool $add): void + { + $roleFormatted = $this->formatRoleString($role); + + if ($add) { + // Add the main role if not already present + if (!in_array($roleFormatted, $user->getRoles(), true)) { + $user->setRoles(array_merge($user->getRoles(), [$roleFormatted])); + } + + // If SUPER ADMIN is given → ensure ADMIN is also present + if ($roleFormatted === 'ROLE_SUPER_ADMIN' && !in_array('ROLE_ADMIN', $user->getRoles(), true)) { + $user->setRoles(array_merge($user->getRoles(), ['ROLE_ADMIN'])); + } + } else { + // Remove the role if present and not used elsewhere + if (in_array($roleFormatted, $user->getRoles(), true)) { + $uos = $this->entityManager->getRepository(UsersOrganizations::class) + ->findBy(['users' => $user, 'isActive' => true]); + + $hasRole = false; + foreach ($uos as $uo) { + $uoa = $this->entityManager->getRepository(UserOrganizatonApp::class) + ->findBy([ + 'userOrganization' => $uo, + 'isActive' => true, + 'role' => $this->entityManager->getRepository(Roles::class) + ->findOneBy(['name' => $role]), + ]); + + if ($uoa) { + $hasRole = true; + break; + } + } + + // Only remove globally if no other app gives this role + if (!$hasRole) { + $roles = $user->getRoles(); + $roles = array_filter($roles, fn($r) => $r !== $roleFormatted); + $user->setRoles($roles); + } + } + } + } + + /** + * Format role string to match the ROLE_ convention + */ + public function formatRoleString(string $role): string + { + $role = str_replace(' ', '_', trim($role)); + $role = strtoupper($role); + if (str_starts_with($role, 'ROLE_')) { + return $role; + } + return 'ROLE_' . $role; + } }