Update role logic for organization management

This commit is contained in:
Charles 2026-02-11 15:11:04 +01:00
parent 252fc775bb
commit 4fc059b2a5
2 changed files with 70 additions and 119 deletions

View File

@ -95,14 +95,14 @@ class OrganizationController extends AbstractController
try { try {
$this->entityManager->persist($organization); $this->entityManager->persist($organization);
$this->entityManager->flush(); $this->entityManager->flush();
$this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getId(), "Organization Created"); $this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getUserIdentifier(), "Organization Created");
$this->loggerService->logSuperAdmin($actingUser->getId(), $actingUser->getId(), "Organization Created", $organization->getId()); $this->loggerService->logSuperAdmin($actingUser->getUserIdentifier(), $actingUser->getUserIdentifier(), "Organization Created", $organization->getId());
$this->actionService->createAction("Create Organization", $actingUser, $organization, $organization->getName()); $this->actionService->createAction("Create Organization", $actingUser, $organization, $organization->getName());
$this->addFlash('success', 'Organisation crée avec succès.'); $this->addFlash('success', 'Organisation crée avec succès.');
return $this->redirectToRoute('organization_index'); return $this->redirectToRoute('organization_index');
} catch (Exception $e) { } catch (Exception $e) {
$this->addFlash('danger', 'Erreur lors de la création de l\'organization'); $this->addFlash('danger', 'Erreur lors de la création de l\'organization');
$this->loggerService->logError('Error creating organization', ['acting_user_id' => $actingUser->getId(), 'error' => $e->getMessage()]); $this->loggerService->logError('Error creating organization', ['acting_user_id' => $actingUser->getUserIdentifier(), 'error' => $e->getMessage()]);
} }
} }
return $this->render('organization/new.html.twig', [ return $this->render('organization/new.html.twig', [
@ -125,35 +125,12 @@ class OrganizationController extends AbstractController
if (!$organization) { if (!$organization) {
$this->loggerService->logEntityNotFound('Organization', [ $this->loggerService->logEntityNotFound('Organization', [
'org_id' => $id, 'org_id' => $id,
'message' => 'Organization not found for edit'], $actingUser->getId() 'message' => 'Organization not found for edit'], $actingUser->getUserIdentifier()
); );
$this->addFlash('danger', 'Erreur, l\'organization est introuvable.'); $this->addFlash('danger', 'Erreur, l\'organization est introuvable.');
return $this->redirectToRoute('organization_index'); return $this->redirectToRoute('organization_index');
} }
if (!$this->isGranted("ROLE_SUPER_ADMIN")) {
//check if the user is admin of the organization
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->findOneBy(['users' => $actingUser, 'organization' => $organization]);
if (!$uo) {
$this->loggerService->logEntityNotFound('UO link', [
'user_id' => $actingUser->getId(),
'org_id' => $organization->getId(),
'message' => 'UO link not found for edit organization'
], $actingUser->getId());
$this->addFlash('danger', 'Erreur, accès refusé.');
return $this->redirectToRoute('organization_index');
}
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']);
$uoaAdmin = $this->entityManager->getRepository(UserOrganizatonApp::class)->findOneBy(['userOrganization' => $uo, 'role' => $roleAdmin]);
if (!$uoaAdmin) {
$this->loggerService->logEntityNotFound('UOA link', [
'uo_id' => $uo->getId(),
'role_id' => $roleAdmin->getId(),
'message' => 'UOA link not found for edit organization, user is not admin of organization'
], $actingUser->getId());
$this->addFlash('danger', 'Erreur, accès refusé.');
return $this->redirectToRoute('organization_index');
}
}
$form = $this->createForm(OrganizationForm::class, $organization); $form = $this->createForm(OrganizationForm::class, $organization);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
@ -164,16 +141,16 @@ class OrganizationController extends AbstractController
try { try {
$this->entityManager->persist($organization); $this->entityManager->persist($organization);
$this->entityManager->flush(); $this->entityManager->flush();
$this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getId(), "Organization Edited"); $this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getUserIdentifier(), "Organization Edited");
if ($this->isGranted("ROLE_SUPER_ADMIN")) { if ($this->isGranted("ROLE_SUPER_ADMIN")) {
$this->loggerService->logSuperAdmin($actingUser->getId(), $actingUser->getId(), "Organization Edited", $organization->getId()); $this->loggerService->logSuperAdmin($actingUser->getUserIdentifier(), $actingUser->getUserIdentifier(), "Organization Edited", $organization->getId());
} }
$this->actionService->createAction("Edit Organization", $actingUser, $organization, $organization->getName()); $this->actionService->createAction("Edit Organization", $actingUser, $organization, $organization->getName());
$this->addFlash('success', 'Organisation modifiée avec succès.'); $this->addFlash('success', 'Organisation modifiée avec succès.');
return $this->redirectToRoute('organization_index'); return $this->redirectToRoute('organization_index');
}catch (Exception $e) { }catch (Exception $e) {
$this->addFlash('danger', 'Erreur lors de la modification de l\'organization'); $this->addFlash('danger', 'Erreur lors de la modification de l\'organization');
$this->loggerService->logError('Error editing organization', ['acting_user_id' => $actingUser->getId(), 'error' => $e->getMessage()]); $this->loggerService->logError('Error editing organization', ['acting_user_id' => $actingUser->getUserIdentifier(), 'error' => $e->getMessage()]);
} }
} }
return $this->render('organization/edit.html.twig', [ return $this->render('organization/edit.html.twig', [
@ -192,17 +169,18 @@ class OrganizationController extends AbstractController
$this->loggerService->logEntityNotFound('Organization', [ $this->loggerService->logEntityNotFound('Organization', [
'org_id' => $id, 'org_id' => $id,
'message' => 'Organization not found for view' 'message' => 'Organization not found for view'
], $actingUser->getId()); ], $actingUser->getUserIdentifier());
$this->addFlash('danger', 'Erreur, l\'organization est introuvable.'); $this->addFlash('danger', 'Erreur, l\'organization est introuvable.');
return $this->redirectToRoute('organization_index'); return $this->redirectToRoute('organization_index');
} }
//check if the user is admin of the organization //check if the user is admin of the organization
if (!$this->userService->isAdminOfOrganization($organization) && !$this->isGranted("ROLE_ADMIN")) { if (!$this->userService->isAdminOfOrganization($organization) && !$this->isGranted("ROLE_ADMIN")) {
$this->loggerService->logAccessDenied($actingUser->getId()); $this->loggerService->logAccessDenied($actingUser->getUserIdentifier());
$this->addFlash('danger', 'Erreur, accès refusé.'); $this->addFlash('danger', 'Erreur, accès refusé.');
throw new AccessDeniedHttpException('Access denied'); throw new AccessDeniedHttpException('Access denied');
} }
//TODO: add project to the response
$allApps = $this->entityManager->getRepository(Apps::class)->findAll(); // appsAll $allApps = $this->entityManager->getRepository(Apps::class)->findAll(); // appsAll
$orgApps = $organization->getApps()->toArray(); // apps $orgApps = $organization->getApps()->toArray(); // apps
@ -222,14 +200,14 @@ class OrganizationController extends AbstractController
#[Route(path: '/delete/{id}', name: 'delete', methods: ['POST'])] #[Route(path: '/delete/{id}', name: 'delete', methods: ['POST'])]
public function delete($id): Response public function delete($id): Response
{ {
$this->denyAccessUnlessGranted("ROLE_ADMIN"); $this->denyAccessUnlessGranted("ROLE_SUPER_ADMIN");
$actingUser = $this->getUser(); $actingUser = $this->getUser();
$organization = $this->organizationsRepository->find($id); $organization = $this->organizationsRepository->find($id);
if (!$organization) { if (!$organization) {
$this->loggerService->logEntityNotFound('Organization', [ $this->loggerService->logEntityNotFound('Organization', [
'org_id' => $id, 'org_id' => $id,
'message' => 'Organization not found for delete' 'message' => 'Organization not found for delete'
], $actingUser->getId()); ], $actingUser->getUserIdentifier());
$this->addFlash('danger', 'Erreur, l\'organization est introuvable.'); $this->addFlash('danger', 'Erreur, l\'organization est introuvable.');
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
@ -243,13 +221,13 @@ class OrganizationController extends AbstractController
$this->entityManager->persist($organization); $this->entityManager->persist($organization);
$this->actionService->createAction("Delete Organization", $actingUser, $organization, $organization->getName()); $this->actionService->createAction("Delete Organization", $actingUser, $organization, $organization->getName());
$this->entityManager->flush(); $this->entityManager->flush();
$this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getId(),'Organization Deleted'); $this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getUserIdentifier(),'Organization Deleted');
if ($this->isGranted("ROLE_SUPER_ADMIN")) { if ($this->isGranted("ROLE_SUPER_ADMIN")) {
$this->loggerService->logSuperAdmin($actingUser->getId(), $actingUser->getId(),'Organization Deleted', $organization->getId()); $this->loggerService->logSuperAdmin($actingUser->getUserIdentifier(), $actingUser->getUserIdentifier(),'Organization Deleted', $organization->getId());
} }
$this->addFlash('success', 'Organisation supprimée avec succès.'); $this->addFlash('success', 'Organisation supprimée avec succès.');
}catch (\Exception $e){ }catch (\Exception $e){
$this->loggerService->logError($actingUser->getId(), ['message' => 'Error deleting organization: '.$e->getMessage()]); $this->loggerService->logError($actingUser->getUserIdentifier(), ['message' => 'Error deleting organization: '.$e->getMessage()]);
$this->addFlash('danger', 'Erreur lors de la suppression de l\'organization.'); $this->addFlash('danger', 'Erreur lors de la suppression de l\'organization.');
} }
@ -266,16 +244,15 @@ class OrganizationController extends AbstractController
$this->loggerService->logEntityNotFound('Organization', [ $this->loggerService->logEntityNotFound('Organization', [
'org_id' => $id, 'org_id' => $id,
'message' => 'Organization not found for deactivate' 'message' => 'Organization not found for deactivate'
], $actingUser->getId()); ], $actingUser->getUserIdentifier());
$this->addFlash('danger', 'Erreur, l\'organization est introuvable.'); $this->addFlash('danger', 'Erreur, l\'organization est introuvable.');
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
$organization->setIsActive(false); $organization->setIsActive(false);
// $this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, null, $organization);
$this->entityManager->persist($organization); $this->entityManager->persist($organization);
$this->actionService->createAction("Deactivate Organization", $actingUser, $organization, $organization->getName()); $this->actionService->createAction("Deactivate Organization", $actingUser, $organization, $organization->getName());
$this->loggerService->logSuperAdmin($actingUser->getId(), $actingUser->getId(),'Organization deactivated', $organization->getId()); $this->loggerService->logSuperAdmin($actingUser->getUserIdentifier(), $actingUser->getUserIdentifier(),'Organization deactivated', $organization->getId());
$this->addFlash('success', 'Organisation désactivé avec succès.'); $this->addFlash('success', 'Organisation désactivé avec succès.');
return $this->redirectToRoute('organization_index'); return $this->redirectToRoute('organization_index');
} }
@ -290,14 +267,14 @@ class OrganizationController extends AbstractController
$this->loggerService->logEntityNotFound('Organization', [ $this->loggerService->logEntityNotFound('Organization', [
'org_id' => $id, 'org_id' => $id,
'message' => 'Organization not found for activate' 'message' => 'Organization not found for activate'
], $actingUser->getId()); ], $actingUser->getUserIdentifier());
$this->addFlash('danger', 'Erreur, l\'organization est introuvable.'); $this->addFlash('danger', 'Erreur, l\'organization est introuvable.');
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
$organization->setIsActive(true); $organization->setIsActive(true);
$this->entityManager->persist($organization); $this->entityManager->persist($organization);
$this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getId(),'Organization Activated'); $this->loggerService->logOrganizationInformation($organization->getId(), $actingUser->getUserIdentifier(),'Organization Activated');
$this->loggerService->logSuperAdmin($actingUser->getId(), $actingUser->getId(),'Organization Activated', $organization->getId()); $this->loggerService->logSuperAdmin($actingUser->getUserIdentifier(), $actingUser->getUserIdentifier(),'Organization Activated', $organization->getId());
$this->actionService->createAction("Activate Organization", $actingUser, $organization, $organization->getName()); $this->actionService->createAction("Activate Organization", $actingUser, $organization, $organization->getName());
$this->addFlash('success', 'Organisation activée avec succès.'); $this->addFlash('success', 'Organisation activée avec succès.');
return $this->redirectToRoute('organization_index'); return $this->redirectToRoute('organization_index');
@ -309,54 +286,21 @@ class OrganizationController extends AbstractController
{ {
$this->denyAccessUnlessGranted('ROLE_USER'); $this->denyAccessUnlessGranted('ROLE_USER');
$page = max(1, $request->query->getInt('page', 1));
$page = max(1, (int)$request->query->get('page', 1)); $size = max(1, $request->query->getInt('size', 10));
$size = max(1, (int)$request->query->get('size', 10));
$filters = $request->query->all('filter'); $filters = $request->query->all('filter');
// Fetch paginated results
$paginator = $this->organizationsRepository->findAdmissibleOrganizations(
$this->getUser(),
$this->isGranted('ROLE_ADMIN'), // Super Admin check
$page,
$size,
$filters
);
$qb = $this->organizationsRepository->createQueryBuilder('o') $total = count($paginator);
->where('o.isDeleted = :del')->setParameter('del', false);
if (!empty($filters['name'])) {
$qb->andWhere('o.name LIKE :name')
->setParameter('name', '%' . $filters['name'] . '%');
}
if (!empty($filters['email'])) {
$qb->andWhere('o.email LIKE :email')
->setParameter('email', '%' . $filters['email'] . '%');
}
if (!$this->isGranted('ROLE_ADMIN')) {
$actingUser = $this->getUser();
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->findBy(['users' => $actingUser]);
$allowedOrgIds = [];
foreach ($uo as $item) {
if ($this->userService->isAdminOfOrganization($item->getOrganization())) {
$allowedOrgIds[] = $item->getOrganization()->getId();
}
}
// If user has no organizations, ensure query returns nothing (or handle typically)
if (empty($allowedOrgIds)) {
$qb->andWhere('1 = 0'); // Force empty result
} else {
$qb->andWhere('o.id IN (:orgIds)')
->setParameter('orgIds', $allowedOrgIds);
}
}
// Count total
$countQb = clone $qb;
$total = (int)$countQb->select('COUNT(o.id)')->getQuery()->getSingleScalarResult();
// Pagination
$offset = ($page - 1) * $size;
$rows = $qb->setFirstResult($offset)->setMaxResults($size)->getQuery()->getResult();
// Map to array
$data = array_map(function (Organizations $org) { $data = array_map(function (Organizations $org) {
return [ return [
'id' => $org->getId(), 'id' => $org->getId(),
@ -366,17 +310,12 @@ class OrganizationController extends AbstractController
'active' => $org->isActive(), 'active' => $org->isActive(),
'showUrl' => $this->generateUrl('organization_show', ['id' => $org->getId()]), 'showUrl' => $this->generateUrl('organization_show', ['id' => $org->getId()]),
]; ];
}, $rows); }, iterator_to_array($paginator));
$lastPage = (int)ceil($total / $size);
return $this->json([ return $this->json([
'data' => $data, 'data' => $data,
'last_page' => $lastPage, 'last_page' => (int)ceil($total / $size),
'total' => $total, // optional, useful for debugging 'total' => $total,
]); ]);
} }
} }

View File

@ -3,8 +3,11 @@
namespace App\Repository; namespace App\Repository;
use App\Entity\Organizations; use App\Entity\Organizations;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use App\Entity\UsersOrganizations;
/** /**
* @extends ServiceEntityRepository<Organizations> * @extends ServiceEntityRepository<Organizations>
@ -16,28 +19,37 @@ class OrganizationsRepository extends ServiceEntityRepository
parent::__construct($registry, Organizations::class); parent::__construct($registry, Organizations::class);
} }
// /** public function findAdmissibleOrganizations(User $user, bool $isSuperAdmin, int $page, int $size, array $filters = []): Paginator
// * @return Organizations[] Returns an array of Organizations objects {
// */ $qb = $this->createQueryBuilder('o')
// public function findByExampleField($value): array ->where('o.isDeleted = :del')
// { ->setParameter('del', false);
// return $this->createQueryBuilder('o')
// ->andWhere('o.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('o.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Organizations // 1. Security Logic: If not Super Admin, join UsersOrganizations to filter
// { if (!$isSuperAdmin) {
// return $this->createQueryBuilder('o') $qb->innerJoin(UsersOrganizations::class, 'uo', 'WITH', 'uo.organization = o')
// ->andWhere('o.exampleField = :val') ->andWhere('uo.users = :user')
// ->setParameter('val', $value) ->andWhere('uo.role = :roleAdmin')
// ->getQuery() ->andWhere('uo.isActive = true')
// ->getOneOrNullResult() ->setParameter('user', $user)
// ; // You can pass the actual Role entity or the string name depending on your mapping
// } ->setParameter('roleAdmin', $this->_em->getRepository(\App\Entity\Roles::class)->findOneBy(['name' => 'ADMIN']));
}
// 2. Filters
if (!empty($filters['name'])) {
$qb->andWhere('o.name LIKE :name')
->setParameter('name', '%' . $filters['name'] . '%');
}
if (!empty($filters['email'])) {
$qb->andWhere('o.email LIKE :email')
->setParameter('email', '%' . $filters['email'] . '%');
}
// 3. Pagination
$qb->setFirstResult(($page - 1) * $size)
->setMaxResults($size);
return new Paginator($qb);
}
} }