gestion des roles et applications

This commit is contained in:
Charles 2025-11-19 16:58:49 +01:00
parent 8c9a5da604
commit 7e08998005
4 changed files with 356 additions and 121 deletions

View File

@ -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,
'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
@ -346,6 +461,7 @@ class UserController extends AbstractController
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
@ -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)) {
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);
}

View File

@ -30,9 +30,7 @@ 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();
@ -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(),

View File

@ -1,57 +1,75 @@
{% block body %}
{% set roles = uoa.roles %}
{# TODO: compare style with/without border#}
<div class="card col-6">
<div class="card-header d-flex gap-2">
{% if uoa.application.logoUrl %}
<img src="{{ asset(uoa.application.logoUrl) }}" alt="Logo {{ uoa.application.name }}"
class="rounded-circle " style="width:50px; height:50px;">
{% endif %}
<div class="card-title">
<h1>{{ uoa.application.name|title }}</h1>
</div>
{#{% block body %}#}
{# #}{# TODO: compare style with/without border #}
{# <div class="card col-6">#}
{# <div class="card-header d-flex gap-2">#}
{# {% if app.logoMiniUrl %}#}
{# <img src="{{ aws_url ~ app.logoMiniUrl }}" alt="Logo {{ app.name }}"#}
{# class="rounded-circle " style="width:50px; height:50px;">#}
{# {% endif %}#}
{# <div class="card-title">#}
{# <h1>{{ app.name|title }}</h1>#}
{# </div>#}
</div>
<div class="card-body">
<div class="row">
{# TODO: pb avec le |raw retour à la ligne#}
{# maybe remove Description label #}
<p><b> Description : </b>{{ uoa.application.descriptionSmall|default('Aucune description disponible.')|raw }}</p>
</div>
{% if is_granted('ROLE_ADMIN') %}
{# TODO: Can be turn into an ajax function#}
<form method="POST"
action="{{ path('user_application_role', { id : uoa.uoId }) }}"
data-controller="user"
data-user-roles-array-value="{{ uoa.rolesArray|json_encode }}"
data-user-selected-role-ids-value="{{ uoa.selectedRoleIds|json_encode }}">
<div class="form-group mb-3">
<label for="roles-{{ uoa.application.id }}"><b>Rôles :</b></label>
<select data-user-target="select"
class="choices"
id="roles-{{ uoa.application.id }}"
name="roles[]"
multiple>
</select>
</div>
<input hidden type="text" value="{{ uoa.application.id }}" name="applicationId">
<button type="submit" class="btn btn-primary">Sauvegarder</button>
</form>
{% else %}
<label for="roles-{{ uoa.application.id }}"><b>Rôles :</b></label>
<select data-user-target="select"
class="choices"
id="roles-{{ uoa.application.id }}"
name="roles[]"
data-controller="user"
data-user-roles-array-value="{{ uoa.rolesArray|json_encode }}"
data-user-selected-role-ids-value="{{ uoa.selectedRoleIds|json_encode }}"
multiple
disabled>
</select>
{% endif %}
</div>
</div>
{# </div>#}
{# <div class="card-body">#}
{# <div class="row">#}
{# <p><b> Description : </b>{{ app.descriptionSmall|default('Aucune description disponible.')|raw }}</p>#}
{# </div>#}
{# {% if is_granted("ROLE_ADMIN") %}#}
{# <form method="POST"#}
{# action="{{ path('user_application_role', { id : data.test1 }) }}">#}
{# <div class="form-group mb-3">#}
{# <label for="roles-{{ app.id }}"><b>Rôles :</b></label>#}
{# <div class="form-check">#}
{# {% for role in roles %}#}
{# <input class="form-check"> #}
{# </div>#}
{# </div>#}
{# </form>#}
{# {% endif %}#}
{# </div>#}
{# </div>#}
{# #}{# <div class="card-body"> #}
{# #}{# <div class="row"> #}
{# #}{# TODO: pb avec le |raw retour à la ligne #}
{# #}{# maybe remove Description label #}
{# #}{# <p><b> Description : </b>{{ uoa.application.descriptionSmall|default('Aucune description disponible.')|raw }}</p> #}
{# #}{# </div> #}
{# #}{# {% if is_granted('ROLE_ADMIN') %} #}
{# #}{# TODO: Can be turn into an ajax function #}
{# #}{# <form method="POST" #}
{# #}{# action="{{ path('user_application_role', { id : uoa.uoId }) }}" #}
{# #}{# data-controller="user" #}
{# #}{# data-user-roles-array-value="{{ uoa.rolesArray|json_encode }}" #}
{# #}{# data-user-selected-role-ids-value="{{ uoa.selectedRoleIds|json_encode }}"> #}
{# #}{# <div class="form-group mb-3"> #}
{# #}{# <label for="roles-{{ uoa.application.id }}"><b>Rôles :</b></label> #}
{# #}{# <select data-user-target="select" #}
{# #}{# class="choices" #}
{# #}{# id="roles-{{ uoa.application.id }}" #}
{# #}{# name="roles[]" #}
{# #}{# multiple> #}
{# #}{# </select> #}
{# #}{# </div> #}
{# #}{# <input hidden type="text" value="{{ uoa.application.id }}" name="applicationId"> #}
{# #}{# <button type="submit" class="btn btn-primary">Sauvegarder</button> #}
{# #}{# </form> #}
{# #}{# {% else %} #}
{# #}{# <label for="roles-{{ uoa.application.id }}"><b>Rôles :</b></label> #}
{# #}{# <select data-user-target="select" #}
{# #}{# class="choices" #}
{# #}{# id="roles-{{ uoa.application.id }}" #}
{# #}{# name="roles[]" #}
{# #}{# data-controller="user" #}
{# #}{# data-user-roles-array-value="{{ uoa.rolesArray|json_encode }}" #}
{# #}{# data-user-selected-role-ids-value="{{ uoa.selectedRoleIds|json_encode }}" #}
{# #}{# multiple #}
{# #}{# disabled> #}
{# #}{# </select> #}
{# #}{# {% endif %} #}
{# #}{# </div> #}
{# #}{# </div> #}
{% endblock %}
{#{% endblock %}#}

View File

@ -45,8 +45,110 @@
</div>
<div class="d-flex gap-2 card-body">
{% for uoa in uoas %}
{% include 'user/application/information.html.twig' %}
{% for app in apps %}
<div class="card col-6">
<div class="card-header d-flex gap-2">
{% if app.logoMiniUrl %}
<img src="{{ aws_url ~ app.logoMiniUrl }}" alt="Logo {{ app.name }}"
class="rounded-circle " style="width:50px; height:50px;">
{% endif %}
<div class="card-title">
<h1>{{ app.name|title }}</h1>
</div>
</div>
<div class="card-body">
<div class="row">
<p><b>Description
:</b> {{ app.descriptionSmall|default('Aucune description disponible.')|raw }}
</p>
</div>
{# EDITABLE if admin and exactly one UO #}
{% if canEditRoles and data.singleUo is not null %}
<form method="POST"
action="{{ path('user_application_role', { id: data.singleUo.id }) }}">
{# 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 %}
<div class="form-group mb-3">
<label for="roles-{{ app.id }}"><b>Rôles :</b></label>
<div class="form-check">
{% if appGroup %}
{# Use rolesArray: filtered by current user's level (no SUPER ADMIN for plain ADMIN, etc.) #}
{% for role in appGroup.rolesArray %}
<input class="form-check-input" type="checkbox"
name="roles[]"
value="{{ role.id }}"
id="role-{{ role.id }}-app-{{ app.id }}"
{% if role.id in appGroup.selectedRoleIds %}checked{% endif %}>
<label class="form-check"
for="role-{{ role.id }}-app-{{ app.id }}">
{% if role.name == 'USER' %}
Accès
{% else %}
{{ role.name|capitalize }}
{% endif %}
</label>
{% endfor %}
{% else %}
<p class="text-muted">Aucun rôle défini pour cette
application.</p>
{% endif %}
</div>
<button type="submit" name="appId" value="{{ app.id }}"
class="btn btn-primary mt-2">
Sauvegarder
</button>
</div>
</form>
{# READ ONLY otherwise #}
{% else %}
{% set appGroup = null %}
{% for group in data.uoas|default([]) %}
{% if group.application.id == app.id %}
{% set appGroup = group %}
{% endif %}
{% endfor %}
<div class="form-group mb-3">
<label for="roles-{{ app.id }}"><b>Rôles :</b></label>
<div class="form-check">
{% if appGroup %}
{# Use rolesArray: filtered by current user's level (no SUPER ADMIN for plain ADMIN, etc.) #}
{% for role in appGroup.rolesArray %}
<input class="form-check-input" type="checkbox"
disabled
name="roles[]"
value="{{ role.id }}"
id="role-{{ role.id }}-app-{{ app.id }}"
{% if appGroup and role.id in appGroup.selectedRoleIds %}checked{% endif %}>
<label class="form-check"
for="role-{{ role.id }}-app-{{ app.id }}">
{{ role.name }}
</label>
{% endfor %}
{% else %}
<p class="text-muted">Aucun rôle défini pour cette
application.</p>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>