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.'