diff --git a/src/Controller/Users/AuthorizationsController.php b/src/Controller/Users/AuthorizationsController.php index c157c04a..d7d401e2 100644 --- a/src/Controller/Users/AuthorizationsController.php +++ b/src/Controller/Users/AuthorizationsController.php @@ -7,6 +7,7 @@ namespace App\Controller\Users; use App\Controller\BaseController; +use App\Entity\Authorization; use App\Entity\User; use App\Repository\AuthorizationRepository; use App\Repository\OrganizationRepository; @@ -188,4 +189,52 @@ public function create( 'uid' => $user->getUid(), ]); } + + #[Route('/authorizations/{uid}/deletion', name: 'delete user authorization', methods: ['POST'])] + public function delete( + Authorization $authorization, + Request $request, + AuthorizationRepository $authorizationRepository, + Security $security, + ): Response { + $this->denyAccessUnlessGranted('admin:manage:users'); + + /** @var \App\Entity\User $currentUser */ + $currentUser = $this->getUser(); + + /** @var string $csrfToken */ + $csrfToken = $request->request->get('_csrf_token', ''); + + $user = $authorization->getHolder(); + $role = $authorization->getRole(); + + if (!$this->isCsrfTokenValid('delete user authorization', $csrfToken)) { + $this->addFlash('error', $this->csrfError()); + return $this->redirectToRoute('user authorizations', [ + 'uid' => $user->getUid(), + ]); + } + + if ( + $role->getType() === 'super' && ( + !$security->isGranted('admin:*') || + $currentUser->getId() === $user->getId() + ) + ) { + $this->addFlash('error', new TranslatableMessage( + 'You can’t revoke this authorization.', + [], + 'validators' + )); + return $this->redirectToRoute('user authorizations', [ + 'uid' => $user->getUid(), + ]); + } + + $authorizationRepository->remove($authorization, true); + + return $this->redirectToRoute('user authorizations', [ + 'uid' => $user->getUid(), + ]); + } } diff --git a/templates/users/authorizations/index.html.twig b/templates/users/authorizations/index.html.twig index e41918e5..9420766a 100644 --- a/templates/users/authorizations/index.html.twig +++ b/templates/users/authorizations/index.html.twig @@ -30,7 +30,7 @@ {% for authorization in authorizations %} -
+
{% if authorization.role.type == 'super' %} {{ icon('crown') }} @@ -61,6 +61,28 @@
{% endif %}
+ + {% if ( + authorization.role.type == 'super' and ( + not is_granted('admin:*') or + app.user.id == authorization.holder.id + ) + ) %} +

+ {{ 'users.authorizations.index.revoke.disabled' | trans }} +

+ {% else %} +
+ + +
+ {% endif %}
{% endfor %} diff --git a/tests/Controller/Users/AuthorizationsControllerTest.php b/tests/Controller/Users/AuthorizationsControllerTest.php index c1783315..e255b64f 100644 --- a/tests/Controller/Users/AuthorizationsControllerTest.php +++ b/tests/Controller/Users/AuthorizationsControllerTest.php @@ -330,4 +330,140 @@ public function testPostCreateFailsIfAccessIsForbidden(): void 'role' => $role->getUid(), ]); } + + public function testPostDeleteDeletesAuthorizationAndRedirects(): void + { + $client = static::createClient(); + $currentUser = UserFactory::createOne(); + $client->loginUser($currentUser->object()); + $this->grantAdmin($currentUser->object(), ['admin:manage:users']); + $user = UserFactory::createOne(); + $role = RoleFactory::createOne([ + 'type' => 'admin', + ]); + $authorization = AuthorizationFactory::createOne([ + 'holder' => $user, + 'role' => $role, + ]); + + $client->request('POST', "/authorizations/{$authorization->getUid()}/deletion", [ + '_csrf_token' => $this->generateCsrfToken($client, 'delete user authorization'), + ]); + + $this->assertResponseRedirects("/users/{$user->getUid()}/authorizations", 302); + AuthorizationFactory::assert()->notExists(['id' => $authorization->getId()]); + } + + public function testPostDeleteCanDeleteSuperRole(): void + { + $client = static::createClient(); + $currentUser = UserFactory::createOne(); + $client->loginUser($currentUser->object()); + $this->grantAdmin($currentUser->object(), ['admin:*']); + $user = UserFactory::createOne(); + $role = RoleFactory::createOne([ + 'type' => 'super', + ]); + $authorization = AuthorizationFactory::createOne([ + 'holder' => $user, + 'role' => $role, + ]); + + $client->request('POST', "/authorizations/{$authorization->getUid()}/deletion", [ + '_csrf_token' => $this->generateCsrfToken($client, 'delete user authorization'), + ]); + + $this->assertResponseRedirects("/users/{$user->getUid()}/authorizations", 302); + AuthorizationFactory::assert()->notExists(['id' => $authorization->getId()]); + } + + public function testPostDeleteFailsIfSuperRoleAndNotCorrectAuthorization(): void + { + $client = static::createClient(); + $currentUser = UserFactory::createOne(); + $client->loginUser($currentUser->object()); + $this->grantAdmin($currentUser->object(), ['admin:manage:users']); + $user = UserFactory::createOne(); + $role = RoleFactory::createOne([ + 'type' => 'super', + ]); + $authorization = AuthorizationFactory::createOne([ + 'holder' => $user, + 'role' => $role, + ]); + + $client->request('POST', "/authorizations/{$authorization->getUid()}/deletion", [ + '_csrf_token' => $this->generateCsrfToken($client, 'delete user authorization'), + ]); + + $this->assertResponseRedirects("/users/{$user->getUid()}/authorizations", 302); + $client->followRedirect(); + $this->assertSelectorTextContains('#notifications', 'You can’t revoke this authorization.'); + AuthorizationFactory::assert()->exists(['id' => $authorization->getId()]); + } + + public function testPostDeleteFailsIfSuperRoleAndCurrentUser(): void + { + $client = static::createClient(); + $currentUser = UserFactory::createOne(); + $client->loginUser($currentUser->object()); + $this->grantAdmin($currentUser->object(), ['admin:*']); + $authorization = AuthorizationFactory::last(); + + $client->request('POST', "/authorizations/{$authorization->getUid()}/deletion", [ + '_csrf_token' => $this->generateCsrfToken($client, 'delete user authorization'), + ]); + + $this->assertResponseRedirects("/users/{$currentUser->getUid()}/authorizations", 302); + $client->followRedirect(); + $this->assertSelectorTextContains('#notifications', 'You can’t revoke this authorization.'); + AuthorizationFactory::assert()->exists(['id' => $authorization->getId()]); + } + + public function testPostDeleteFailsIfCsrfTokenIsInvalid(): void + { + $client = static::createClient(); + $currentUser = UserFactory::createOne(); + $client->loginUser($currentUser->object()); + $this->grantAdmin($currentUser->object(), ['admin:manage:users']); + $user = UserFactory::createOne(); + $role = RoleFactory::createOne([ + 'type' => 'admin', + ]); + $authorization = AuthorizationFactory::createOne([ + 'holder' => $user, + 'role' => $role, + ]); + + $client->request('POST', "/authorizations/{$authorization->getUid()}/deletion", [ + '_csrf_token' => 'not a token', + ]); + + $this->assertResponseRedirects("/users/{$user->getUid()}/authorizations", 302); + $client->followRedirect(); + $this->assertSelectorTextContains('#notifications', 'Invalid CSRF token.'); + AuthorizationFactory::assert()->exists(['id' => $authorization->getId()]); + } + + public function testPostDeleteFailsIfAccessIsForbidden(): void + { + $this->expectException(AccessDeniedException::class); + + $client = static::createClient(); + $currentUser = UserFactory::createOne(); + $client->loginUser($currentUser->object()); + $user = UserFactory::createOne(); + $role = RoleFactory::createOne([ + 'type' => 'admin', + ]); + $authorization = AuthorizationFactory::createOne([ + 'holder' => $user, + 'role' => $role, + ]); + + $client->catchExceptions(false); + $client->request('POST', "/authorizations/{$authorization->getUid()}/deletion", [ + '_csrf_token' => $this->generateCsrfToken($client, 'delete user authorization'), + ]); + } } diff --git a/translations/messages+intl-icu.en_GB.yaml b/translations/messages+intl-icu.en_GB.yaml index eb8dfe7f..93ed6450 100644 --- a/translations/messages+intl-icu.en_GB.yaml +++ b/translations/messages+intl-icu.en_GB.yaml @@ -126,6 +126,9 @@ users.color_scheme.dark: Dark users.email: 'Email address' users.authorizations.index.new_authorization: 'Give an authorization' users.authorizations.index.no_authorizations: 'No authorizations' +users.authorizations.index.revoke: Revoke +users.authorizations.index.revoke.confirm: 'Are you sure that you want to revoke this authorization?' +users.authorizations.index.revoke.disabled: 'You can’t revoke this authorization.' users.authorizations.index.title: 'Authorizations ({name})' users.authorizations.new.global: Global users.authorizations.new.organization: 'Choose an organization' diff --git a/translations/messages+intl-icu.fr_FR.yaml b/translations/messages+intl-icu.fr_FR.yaml index 68c8e3de..31d45f8d 100644 --- a/translations/messages+intl-icu.fr_FR.yaml +++ b/translations/messages+intl-icu.fr_FR.yaml @@ -126,6 +126,9 @@ users.color_scheme.dark: Sombre users.email: 'Adresse email' users.authorizations.index.new_authorization: 'Donner une autorisation' users.authorizations.index.no_authorizations: 'Aucune autorisation' +users.authorizations.index.revoke: Révoquer +users.authorizations.index.revoke.confirm: "Êtes-vous sûr de vouloir révoquer cette autorisation\_?" +users.authorizations.index.revoke.disabled: 'Vous ne pouvez pas révoquer cette autorisation.' users.authorizations.index.title: 'Autorisations ({name})' users.authorizations.new.global: Global users.authorizations.new.organization: 'Choisir une organisation' diff --git a/translations/validators+intl-icu.en_GB.yaml b/translations/validators+intl-icu.en_GB.yaml index 31c567d6..42ed5f40 100644 --- a/translations/validators+intl-icu.en_GB.yaml +++ b/translations/validators+intl-icu.en_GB.yaml @@ -130,3 +130,4 @@ Error: Error 'This user already has an admin role.': 'This user already has an admin role.' 'This user already has an orga role for this organization.': 'This user already has an orga role for this organization.' 'You can’t grant super-admin authorization.': 'You can’t grant super-admin authorization.' +'You can’t revoke this authorization.': 'You can’t revoke this authorization.' diff --git a/translations/validators+intl-icu.fr_FR.yaml b/translations/validators+intl-icu.fr_FR.yaml index 67063951..8f5095be 100644 --- a/translations/validators+intl-icu.fr_FR.yaml +++ b/translations/validators+intl-icu.fr_FR.yaml @@ -130,3 +130,4 @@ Error: Erreur 'This user already has an admin role.': 'Cet utilisateur possède déjà un rôle admin.' 'This user already has an orga role for this organization.': 'Cet utilisateur possède déjà un rôle orga pour cette organisation.' 'You can’t grant super-admin authorization.': 'Vous ne pouvez pas accorder de droit super-admin.' +'You can’t revoke this authorization.': 'Vous ne pouvez pas révoquer cette autorisation.'