From e9e70856e64b24e7301f2871fcb1ef46165d45ab Mon Sep 17 00:00:00 2001
From: Dimitri <gritsajuk.dimitri@gmail.com>
Date: Fri, 31 Jan 2025 14:58:03 +0100
Subject: [PATCH] Remove Sensio bundle (#11144)

* Remove Sensio bundle

* Fix security attributes

* Fix tests
---
 .php-cs-fixer.dist.php                        |   1 -
 composer.json                                 |   5 +-
 composer.lock                                 | 306 +++++-------------
 config/bundles.php                            |   1 -
 .../Admin/AdminCommitteeController.php        |  10 +-
 .../Admin/AdminDonationController.php         |   2 +-
 .../Admin/AdminDonatorController.php          |   2 +-
 src/Controller/Admin/AdminFileController.php  |   2 +-
 .../AdminPhoningCampaignStatsController.php   |   2 +-
 .../Admin/AdminQrCodeGeneratorController.php  |   2 +-
 .../Admin/AdminReportController.php           |   2 +-
 .../Admin/AdminUnregistrationController.php   |   2 +-
 ...ativeSimilarAdherentProfilesController.php |   7 +-
 .../Api/Action/CancelActionController.php     |   5 +-
 .../Api/AdherentAutocompleteController.php    |   5 +-
 src/Controller/Api/AdherentController.php     |   2 +-
 .../AdherentList/CountAdherentController.php  |   5 +-
 .../DuplicateMessageController.php            |   5 +-
 .../GetAdherentMessageStatusController.php    |   5 +-
 .../SendAdherentMessageController.php         |   5 +-
 .../SendTestAdherentMessageController.php     |   5 +-
 .../UpdateAdherentMessageFilterController.php |   5 +-
 .../AuthenticatedAppLinkController.php        |   4 +-
 .../Api/CertificationRequestController.php    |   2 +-
 src/Controller/Api/ChangeEmailController.php  |   6 +-
 src/Controller/Api/CharterController.php      |   2 +-
 .../Api/Committee/CommitteesController.php    |   5 +-
 .../GetCommitteesZonesController.php          |   5 +-
 src/Controller/Api/CrmParisController.php     |   2 +-
 src/Controller/Api/ElectProfileController.php |   5 +-
 .../ElectedRepresentativeListController.php   |   5 +-
 .../Api/Event/CancelEventController.php       |   5 +-
 .../Event/GetEventParticipantsController.php  |   5 +-
 .../Filter/GetCollectionFiltersController.php |   5 +-
 .../Api/InternalApiProxyController.php        |   2 +-
 .../JeMengage/GetTimelineFeedsController.php  |   2 +-
 .../Api/Jecoute/DepartmentController.php      |   2 +-
 .../Jecoute/GetSurveyRepliesController.php    |   5 +-
 .../Api/Jecoute/SurveyController.php          |   5 +-
 src/Controller/Api/MoocController.php         |   9 +-
 .../Api/Pap/BuildingEventController.php       |   5 +-
 .../Api/Pap/BuildingHistoryController.php     |   9 +-
 .../Api/Pap/GetBuildingBlocksController.php   |   7 +-
 .../Api/Pap/GetCampaignRankingController.php  |   7 +-
 .../GetPapCampaignSurveyConfigController.php  |   7 +-
 .../Pap/GetPapCampaignSurveyController.php    |   7 +-
 .../GetPapCampaignSurveyRepliesController.php |   7 +-
 .../Pap/GetPapCampaignTutorialController.php  |   5 +-
 .../GetPhoningCampaignSurveyController.php    |   7 +-
 ...PhoningCampaignSurveyRepliesController.php |   7 +-
 src/Controller/Api/ProfileController.php      |  12 +-
 src/Controller/Api/ReportController.php       |   2 +-
 .../Api/ResubscribeEmailController.php        |   2 +-
 .../Api/Security/GetJWTTokenController.php    |   5 +-
 .../Api/Team/AddTeamMembersController.php     |   5 +-
 .../Api/Team/RemoveTeamController.php         |   5 +-
 .../Api/Team/RemoveTeamMemberController.php   |   9 +-
 src/Controller/Api/UserController.php         |   6 +-
 .../AbstractUserListDefinitionController.php  |   2 +-
 .../ElectionBallotsController.php             |   5 +-
 .../ElectionResultsController.php             |   5 +-
 .../ElectionVotersListController.php          |   5 +-
 .../Api/Zone/PlaceAutocompleteController.php  |   2 +-
 .../ZoneAutocompleteRestrictedController.php  |   2 +-
 .../AbstractMessageController.php             |  10 +-
 .../CandidateJecouteMessageController.php     |   5 +-
 .../CandidateMessageController.php            |   5 +-
 .../CommitteeMessageController.php            |  32 +-
 .../CommonMessageController.php               |   2 +-
 .../DeputyMessageController.php               |   5 +-
 .../AdherentProfile/FunnelController.php      |   2 +-
 .../EnMarche/CommitteeController.php          |   2 +-
 .../AbstractDesignationController.php         |  15 +-
 .../CandidatureController.php                 |  24 +-
 .../SupervisorDesignationController.php       |   7 +-
 .../CommitteeEventManagerController.php       |  15 +-
 .../EnMarche/CommitteeManagerController.php   |  30 +-
 .../EnMarche/CommitteeSpaceController.php     |   5 +-
 .../CoordinatorCommitteeController.php        |   2 +-
 src/Controller/EnMarche/DeputyController.php  |   5 +-
 .../DeputyElectedRepresentativeController.php |   2 +-
 .../ElectionResultsReporterController.php     |   2 +-
 src/Controller/EnMarche/EventController.php   |  40 ++-
 .../AbstractEventManagerController.php        |   2 +-
 .../CandidateEventManagerController.php       |   5 +-
 .../DeputyEventManagerController.php          |   5 +-
 .../Filesystem/AbstractFilesController.php    |   2 +-
 .../Filesystem/CandidateFilesController.php   |   5 +-
 .../EnMarche/InvitationController.php         |   2 +-
 .../Jecoute/AbstractJecouteController.php     |  26 +-
 .../Jecoute/JecouteCandidateController.php    |   5 +-
 .../Jecoute/JecouteManagerController.php      |   2 +-
 .../Jecoute/News/AbstractNewsController.php   |  11 +-
 .../Jecoute/News/NewsCandidateController.php  |   5 +-
 .../JecouteCandidateRegionController.php      |   5 +-
 src/Controller/EnMarche/LegacyController.php  |  31 --
 .../CandidateManagedUsersController.php       |   5 +-
 .../DeputyManagedUsersController.php          |   5 +-
 .../EnMarche/Poll/AbstractPollController.php  |   7 +-
 .../EnMarche/Poll/PollCandidateController.php |   5 +-
 src/Controller/EnMarche/ReportController.php  |   2 +-
 src/Controller/EnMarche/UserController.php    |   4 +-
 .../VotingPlatform/ResultsController.php      |   6 +-
 .../OAuth/OAuthServerController.php           |   5 +-
 src/Controller/OAuth/SsoController.php        |   2 +-
 .../FillInformationsController.php            |   2 +-
 .../Contribution/FillRevenueController.php    |   2 +-
 .../Contribution/SeeAmountController.php      |   2 +-
 .../Renaissance/Adherent/EventController.php  |  32 +-
 .../ViewCandidaciesListsController.php        |   2 +-
 .../Consultation/ListController.php           |   2 +-
 .../Consultation/ShowController.php           |  11 +-
 .../DepartmentSiteController.php              |   9 +-
 .../Election/DepartmentElectionController.php |   2 +-
 .../Election/LocalPollElectionController.php  |   2 +-
 .../Election/SasElectionController.php        |   2 +-
 .../Formation/ContentController.php           |  11 +-
 .../Renaissance/Formation/ListController.php  |   2 +-
 .../SaveCommitteeUpdateController.php         |   5 +-
 .../ShowCommitteeListController.php           |   5 +-
 .../ConfirmNewsletterController.php           |   4 +-
 .../Renaissance/SecurityController.php        |  10 +-
 src/Controller/UploadDocumentController.php   |   2 +-
 .../Webhook/Telegram/ChatbotController.php    |  10 +-
 src/Entity/Action/Action.php                  |   2 +-
 .../Segment/AudienceSegment.php               |   8 +-
 src/Entity/AdherentSegment.php                |   2 +-
 src/Entity/Device.php                         |   4 +-
 src/Entity/EntityIdentityTrait.php            |   9 +-
 src/Entity/Event/BaseEvent.php                |   2 +-
 src/Entity/Event/BaseEventCategory.php        |   4 +-
 src/Entity/Event/EventCategory.php            |   2 +-
 src/Entity/Geo/GeoTrait.php                   |   2 +-
 src/Entity/Jecoute/ResourceLink.php           |   2 +-
 src/Entity/Pap/Building.php                   |   4 +-
 src/Entity/PushToken.php                      |   4 +-
 src/Exporter/EventParticipantsExporter.php    |   3 +-
 .../EntityCategoryFromSlugDenormalizer.php    |  36 +++
 src/OAuth/Store/AccessTokenStore.php          |   8 +-
 symfony.lock                                  |  21 --
 .../Admin/SecurityControllerCaseTest.php      |   1 +
 141 files changed, 570 insertions(+), 615 deletions(-)
 delete mode 100644 src/Controller/EnMarche/LegacyController.php
 create mode 100644 src/Normalizer/EntityCategoryFromSlugDenormalizer.php

diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 68221cbc729..f09a1d95220 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -16,7 +16,6 @@
         '@PHP82Migration' => true,
         '@PHP80Migration:risky' => true,
         '@Symfony' => true,
-        '@DoctrineAnnotation' => true,
         'phpdoc_summary' => false,
         'no_unneeded_final_method' => false,
         'declare_strict_types' => false,
diff --git a/composer.json b/composer.json
index 734c69d3a85..2926bbeb93c 100644
--- a/composer.json
+++ b/composer.json
@@ -72,7 +72,7 @@
         "league/iso3166": "^4.3",
         "league/oauth2-server": "^8.2",
         "lexik/paybox-bundle": "dev-master",
-        "longitude-one/doctrine-spatial": "*",
+        "longitude-one/doctrine-spatial": "^4.0",
         "myclabs/php-enum": "^1.5",
         "nelmio/cors-bundle": "^2.2",
         "nyholm/psr7": "^1.5",
@@ -81,11 +81,10 @@
         "phpoffice/phpspreadsheet": "^3.3",
         "ramsey/uuid": "^4.1",
         "ramsey/uuid-doctrine": "^2.0",
-        "runroom-packages/sortable-behavior-bundle": "^0.17.1",
+        "runroom-packages/sortable-behavior-bundle": "^0.18.0",
         "sabre/dav": "^4.1",
         "scheb/2fa-bundle": "^6.3",
         "scheb/2fa-google-authenticator": "^6.3",
-        "sensio/framework-extra-bundle": "^6.0",
         "sentry/sentry-symfony": "^5.0",
         "sonata-project/admin-bundle": "^4.32",
         "sonata-project/doctrine-orm-admin-bundle": "^4.15",
diff --git a/composer.lock b/composer.lock
index bf51c76efb8..0805042a137 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "a49f9b2fc011d3123f4936d6d9a0456f",
+    "content-hash": "7231f03648114592d09649cec7da711f",
     "packages": [
         {
             "name": "a2lix/auto-form-bundle",
@@ -532,27 +532,30 @@
         },
         {
             "name": "beberlei/doctrineextensions",
-            "version": "v1.3.0",
+            "version": "v1.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/beberlei/DoctrineExtensions.git",
-                "reference": "008f162f191584a6c37c03a803f718802ba9dd9a"
+                "reference": "249eab82aa35b65741388f38499b3162403d9956"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/beberlei/DoctrineExtensions/zipball/008f162f191584a6c37c03a803f718802ba9dd9a",
-                "reference": "008f162f191584a6c37c03a803f718802ba9dd9a",
+                "url": "https://api.github.com/repos/beberlei/DoctrineExtensions/zipball/249eab82aa35b65741388f38499b3162403d9956",
+                "reference": "249eab82aa35b65741388f38499b3162403d9956",
                 "shasum": ""
             },
             "require": {
-                "doctrine/orm": "^2.7",
+                "doctrine/orm": "^2.15",
                 "php": "^7.2 || ^8.0"
             },
             "require-dev": {
-                "friendsofphp/php-cs-fixer": "^2.14",
+                "doctrine/annotations": "^1.14 || ^2",
+                "doctrine/coding-standard": "^9.0.2 || ^12.0",
                 "nesbot/carbon": "*",
                 "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
-                "symfony/yaml": "^4.2 || ^5.0",
+                "squizlabs/php_codesniffer": "^3.8",
+                "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
+                "symfony/yaml": "^4.4 || ^5.3 || ^6.0 || ^7.0",
                 "zf1/zend-date": "^1.12",
                 "zf1/zend-registry": "^1.12"
             },
@@ -583,9 +586,9 @@
                 "orm"
             ],
             "support": {
-                "source": "https://github.com/beberlei/DoctrineExtensions/tree/v1.3.0"
+                "source": "https://github.com/beberlei/DoctrineExtensions/tree/v1.4.0"
             },
-            "time": "2020-11-29T07:37:23+00:00"
+            "time": "2024-02-05T17:02:44+00:00"
         },
         {
             "name": "behat/transliterator",
@@ -1557,82 +1560,6 @@
             },
             "time": "2024-07-08T12:26:09+00:00"
         },
-        {
-            "name": "doctrine/annotations",
-            "version": "2.0.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/doctrine/annotations.git",
-                "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7",
-                "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7",
-                "shasum": ""
-            },
-            "require": {
-                "doctrine/lexer": "^2 || ^3",
-                "ext-tokenizer": "*",
-                "php": "^7.2 || ^8.0",
-                "psr/cache": "^1 || ^2 || ^3"
-            },
-            "require-dev": {
-                "doctrine/cache": "^2.0",
-                "doctrine/coding-standard": "^10",
-                "phpstan/phpstan": "^1.10.28",
-                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
-                "symfony/cache": "^5.4 || ^6.4 || ^7",
-                "vimeo/psalm": "^4.30 || ^5.14"
-            },
-            "suggest": {
-                "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
-                {
-                    "name": "Roman Borschel",
-                    "email": "roman@code-factory.org"
-                },
-                {
-                    "name": "Benjamin Eberlei",
-                    "email": "kontakt@beberlei.de"
-                },
-                {
-                    "name": "Jonathan Wage",
-                    "email": "jonwage@gmail.com"
-                },
-                {
-                    "name": "Johannes Schmitt",
-                    "email": "schmittjoh@gmail.com"
-                }
-            ],
-            "description": "Docblock Annotations Parser",
-            "homepage": "https://www.doctrine-project.org/projects/annotations.html",
-            "keywords": [
-                "annotations",
-                "docblock",
-                "parser"
-            ],
-            "support": {
-                "issues": "https://github.com/doctrine/annotations/issues",
-                "source": "https://github.com/doctrine/annotations/tree/2.0.2"
-            },
-            "time": "2024-09-05T10:17:24+00:00"
-        },
         {
             "name": "doctrine/cache",
             "version": "2.2.0",
@@ -5399,16 +5326,16 @@
         },
         {
             "name": "knplabs/knp-menu",
-            "version": "v3.5.0",
+            "version": "v3.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/KnpLabs/KnpMenu.git",
-                "reference": "c39403f7c427d1b72cc56f38df0a075b4b9191fe"
+                "reference": "ac96b711cf7b4178747b0ecde79d7e5e05dbcd28"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/KnpLabs/KnpMenu/zipball/c39403f7c427d1b72cc56f38df0a075b4b9191fe",
-                "reference": "c39403f7c427d1b72cc56f38df0a075b4b9191fe",
+                "url": "https://api.github.com/repos/KnpLabs/KnpMenu/zipball/ac96b711cf7b4178747b0ecde79d7e5e05dbcd28",
+                "reference": "ac96b711cf7b4178747b0ecde79d7e5e05dbcd28",
                 "shasum": ""
             },
             "require": {
@@ -5466,26 +5393,26 @@
             ],
             "support": {
                 "issues": "https://github.com/KnpLabs/KnpMenu/issues",
-                "source": "https://github.com/KnpLabs/KnpMenu/tree/v3.5.0"
+                "source": "https://github.com/KnpLabs/KnpMenu/tree/v3.6.0"
             },
-            "time": "2024-03-23T15:35:09+00:00"
+            "time": "2024-12-20T10:10:51+00:00"
         },
         {
             "name": "knplabs/knp-menu-bundle",
-            "version": "v3.4.2",
+            "version": "v3.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/KnpLabs/KnpMenuBundle.git",
-                "reference": "6a1e3e1f4131f9a5a967e36717a1fe680c183637"
+                "reference": "5f85a4908343c3d6a6009c2f8a74c2d102c0aa80"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/KnpLabs/KnpMenuBundle/zipball/6a1e3e1f4131f9a5a967e36717a1fe680c183637",
-                "reference": "6a1e3e1f4131f9a5a967e36717a1fe680c183637",
+                "url": "https://api.github.com/repos/KnpLabs/KnpMenuBundle/zipball/5f85a4908343c3d6a6009c2f8a74c2d102c0aa80",
+                "reference": "5f85a4908343c3d6a6009c2f8a74c2d102c0aa80",
                 "shasum": ""
             },
             "require": {
-                "knplabs/knp-menu": "^3.3",
+                "knplabs/knp-menu": "^3.6",
                 "php": "^8.1",
                 "symfony/deprecation-contracts": "^2.5 | ^3.3",
                 "symfony/framework-bundle": "^5.4 | ^6.0 | ^7.0"
@@ -5531,9 +5458,9 @@
             ],
             "support": {
                 "issues": "https://github.com/KnpLabs/KnpMenuBundle/issues",
-                "source": "https://github.com/KnpLabs/KnpMenuBundle/tree/v3.4.2"
+                "source": "https://github.com/KnpLabs/KnpMenuBundle/tree/v3.5.0"
             },
-            "time": "2024-06-03T08:48:36+00:00"
+            "time": "2024-12-25T16:34:19+00:00"
         },
         {
             "name": "knplabs/knp-time-bundle",
@@ -9923,28 +9850,28 @@
         },
         {
             "name": "runroom-packages/sortable-behavior-bundle",
-            "version": "0.17.1",
+            "version": "0.18.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Runroom/RunroomSortableBehaviorBundle.git",
-                "reference": "ef29964f1a43c3ffbc297fa4228112c1573fbf6b"
+                "reference": "6004e079a29018d5de469e2a56da45a77235cabe"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Runroom/RunroomSortableBehaviorBundle/zipball/ef29964f1a43c3ffbc297fa4228112c1573fbf6b",
-                "reference": "ef29964f1a43c3ffbc297fa4228112c1573fbf6b",
+                "url": "https://api.github.com/repos/Runroom/RunroomSortableBehaviorBundle/zipball/6004e079a29018d5de469e2a56da45a77235cabe",
+                "reference": "6004e079a29018d5de469e2a56da45a77235cabe",
                 "shasum": ""
             },
             "require": {
                 "doctrine/doctrine-bundle": "^2.8",
                 "doctrine/orm": "^2.14",
                 "php": "^8.1",
-                "symfony/config": "^5.4 || ^6.2",
-                "symfony/dependency-injection": "^5.4 || ^6.2",
-                "symfony/http-foundation": "^5.4 || ^6.2",
-                "symfony/http-kernel": "^5.4 || ^6.2",
-                "symfony/property-access": "^5.4 || ^6.2",
-                "symfony/translation": "^5.4 || ^6.2",
+                "symfony/config": "^5.4 || ^6.4",
+                "symfony/dependency-injection": "^5.4 || ^6.4",
+                "symfony/http-foundation": "^5.4 || ^6.4",
+                "symfony/http-kernel": "^5.4 || ^6.4",
+                "symfony/property-access": "^5.4 || ^6.4",
+                "symfony/translation": "^5.4 || ^6.4",
                 "twig/twig": "^3.0"
             },
             "conflict": {
@@ -9952,22 +9879,22 @@
                 "sonata-project/admin-bundle": "<4.0"
             },
             "require-dev": {
-                "dama/doctrine-test-bundle": "^7.2",
+                "dama/doctrine-test-bundle": "^8.0",
                 "gedmo/doctrine-extensions": "^3.11",
                 "knplabs/knp-menu-bundle": "^3.1",
-                "matthiasnoback/symfony-config-test": "^4.2",
-                "matthiasnoback/symfony-dependency-injection-test": "^4.2",
+                "matthiasnoback/symfony-config-test": "^5.1",
+                "matthiasnoback/symfony-dependency-injection-test": "^5.1",
                 "phpunit/phpunit": "^9.6",
                 "psr/container": "^1.1 || ^2.0",
-                "runroom-packages/testing": "^0.17.1",
+                "runroom-packages/testing": "^0.18",
                 "sonata-project/admin-bundle": "^4.20",
                 "sonata-project/doctrine-orm-admin-bundle": "^4.3",
-                "symfony/browser-kit": "^5.4 || ^6.2",
-                "symfony/framework-bundle": "^5.4 || ^6.2",
-                "symfony/phpunit-bridge": "^6.3",
-                "symfony/security-bundle": "^5.4 || ^6.2",
-                "symfony/twig-bundle": "^5.4 || ^6.2",
-                "zenstruck/foundry": "^1.34"
+                "symfony/browser-kit": "^5.4 || ^6.4",
+                "symfony/framework-bundle": "^5.4 || ^6.4",
+                "symfony/phpunit-bridge": "^7.0",
+                "symfony/security-bundle": "^5.4 || ^6.4",
+                "symfony/twig-bundle": "^5.4 || ^6.4",
+                "zenstruck/foundry": "^1.38.2 || ^2.0"
             },
             "type": "symfony-bundle",
             "extra": {
@@ -9999,9 +9926,9 @@
                 "sortable"
             ],
             "support": {
-                "source": "https://github.com/Runroom/RunroomSortableBehaviorBundle/tree/0.17.1"
+                "source": "https://github.com/Runroom/RunroomSortableBehaviorBundle/tree/0.18.0"
             },
-            "time": "2023-07-18T07:45:45+00:00"
+            "time": "2024-07-09T08:44:46+00:00"
         },
         {
             "name": "sabre/dav",
@@ -10567,84 +10494,6 @@
             },
             "time": "2024-11-29T19:22:48+00:00"
         },
-        {
-            "name": "sensio/framework-extra-bundle",
-            "version": "v6.2.10",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git",
-                "reference": "2f886f4b31f23c76496901acaedfedb6936ba61f"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/2f886f4b31f23c76496901acaedfedb6936ba61f",
-                "reference": "2f886f4b31f23c76496901acaedfedb6936ba61f",
-                "shasum": ""
-            },
-            "require": {
-                "doctrine/annotations": "^1.0|^2.0",
-                "php": ">=7.2.5",
-                "symfony/config": "^4.4|^5.0|^6.0",
-                "symfony/dependency-injection": "^4.4|^5.0|^6.0",
-                "symfony/framework-bundle": "^4.4|^5.0|^6.0",
-                "symfony/http-kernel": "^4.4|^5.0|^6.0"
-            },
-            "conflict": {
-                "doctrine/doctrine-cache-bundle": "<1.3.1",
-                "doctrine/persistence": "<1.3"
-            },
-            "require-dev": {
-                "doctrine/dbal": "^2.10|^3.0",
-                "doctrine/doctrine-bundle": "^1.11|^2.0",
-                "doctrine/orm": "^2.5",
-                "symfony/browser-kit": "^4.4|^5.0|^6.0",
-                "symfony/doctrine-bridge": "^4.4|^5.0|^6.0",
-                "symfony/dom-crawler": "^4.4|^5.0|^6.0",
-                "symfony/expression-language": "^4.4|^5.0|^6.0",
-                "symfony/finder": "^4.4|^5.0|^6.0",
-                "symfony/monolog-bridge": "^4.0|^5.0|^6.0",
-                "symfony/monolog-bundle": "^3.2",
-                "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0",
-                "symfony/security-bundle": "^4.4|^5.0|^6.0",
-                "symfony/twig-bundle": "^4.4|^5.0|^6.0",
-                "symfony/yaml": "^4.4|^5.0|^6.0",
-                "twig/twig": "^1.34|^2.4|^3.0"
-            },
-            "type": "symfony-bundle",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "6.1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Sensio\\Bundle\\FrameworkExtraBundle\\": "src/"
-                },
-                "exclude-from-classmap": [
-                    "/tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                }
-            ],
-            "description": "This bundle provides a way to configure your controllers with annotations",
-            "keywords": [
-                "annotations",
-                "controllers"
-            ],
-            "support": {
-                "source": "https://github.com/sensiolabs/SensioFrameworkExtraBundle/tree/v6.2.10"
-            },
-            "abandoned": "Symfony",
-            "time": "2023-02-24T14:57:12+00:00"
-        },
         {
             "name": "sentry/sentry",
             "version": "4.10.0",
@@ -17117,25 +16966,26 @@
         },
         {
             "name": "symfony/var-exporter",
-            "version": "v7.2.0",
+            "version": "v6.4.13",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-exporter.git",
-                "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d"
+                "reference": "0f605f72a363f8743001038a176eeb2a11223b51"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d",
-                "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d",
+                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51",
+                "reference": "0f605f72a363f8743001038a176eeb2a11223b51",
                 "shasum": ""
             },
             "require": {
-                "php": ">=8.2"
+                "php": ">=8.1",
+                "symfony/deprecation-contracts": "^2.5|^3"
             },
             "require-dev": {
                 "symfony/property-access": "^6.4|^7.0",
                 "symfony/serializer": "^6.4|^7.0",
-                "symfony/var-dumper": "^6.4|^7.0"
+                "symfony/var-dumper": "^5.4|^6.0|^7.0"
             },
             "type": "library",
             "autoload": {
@@ -17173,7 +17023,7 @@
                 "serialize"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-exporter/tree/v7.2.0"
+                "source": "https://github.com/symfony/var-exporter/tree/v6.4.13"
             },
             "funding": [
                 {
@@ -17189,7 +17039,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-10-18T07:58:17+00:00"
+            "time": "2024-09-25T14:18:03+00:00"
         },
         {
             "name": "symfony/web-link",
@@ -18207,20 +18057,20 @@
     "packages-dev": [
         {
             "name": "aeon-php/calendar",
-            "version": "1.0.9",
+            "version": "1.0.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/aeon-php/calendar.git",
-                "reference": "2cfc45a2cd28b78f1450d8155a1c62df2efe45de"
+                "reference": "41f4b0ff07247c36b232ddfbcddc62af738be6fe"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/aeon-php/calendar/zipball/2cfc45a2cd28b78f1450d8155a1c62df2efe45de",
-                "reference": "2cfc45a2cd28b78f1450d8155a1c62df2efe45de",
+                "url": "https://api.github.com/repos/aeon-php/calendar/zipball/41f4b0ff07247c36b232ddfbcddc62af738be6fe",
+                "reference": "41f4b0ff07247c36b232ddfbcddc62af738be6fe",
                 "shasum": ""
             },
             "require": {
-                "php": "~8.1.10 || ~8.2"
+                "php": "~8.2.0 || ~8.3.0 || ~8.4.0"
             },
             "require-dev": {
                 "ext-bcmath": "*",
@@ -18251,9 +18101,19 @@
             ],
             "support": {
                 "issues": "https://github.com/aeon-php/calendar/issues",
-                "source": "https://github.com/aeon-php/calendar/tree/1.0.9"
+                "source": "https://github.com/aeon-php/calendar/tree/1.0.11"
             },
-            "time": "2023-08-29T09:47:37+00:00"
+            "funding": [
+                {
+                    "url": "https://flow-php.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/norberttech",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-01-24T04:47:09+00:00"
         },
         {
             "name": "behat/behat",
@@ -22517,20 +22377,20 @@
         },
         {
             "name": "symfony/process",
-            "version": "v7.2.0",
+            "version": "v6.4.15",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e"
+                "reference": "3cb242f059c14ae08591c5c4087d1fe443564392"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
-                "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
+                "url": "https://api.github.com/repos/symfony/process/zipball/3cb242f059c14ae08591c5c4087d1fe443564392",
+                "reference": "3cb242f059c14ae08591c5c4087d1fe443564392",
                 "shasum": ""
             },
             "require": {
-                "php": ">=8.2"
+                "php": ">=8.1"
             },
             "type": "library",
             "autoload": {
@@ -22558,7 +22418,7 @@
             "description": "Executes commands in sub-processes",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/process/tree/v7.2.0"
+                "source": "https://github.com/symfony/process/tree/v6.4.15"
             },
             "funding": [
                 {
@@ -22574,7 +22434,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-11-06T14:24:19+00:00"
+            "time": "2024-11-06T14:19:14+00:00"
         },
         {
             "name": "symfony/web-profiler-bundle",
@@ -22866,6 +22726,6 @@
         "ext-pdo": "*",
         "ext-zip": "*"
     },
-    "platform-dev": [],
-    "plugin-api-version": "2.3.0"
+    "platform-dev": {},
+    "plugin-api-version": "2.6.0"
 }
diff --git a/config/bundles.php b/config/bundles.php
index 8c2c1162357..fc981901474 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -6,7 +6,6 @@
     Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
     Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
     Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
-    Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
     Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
     Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
     Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
diff --git a/src/Controller/Admin/AdminCommitteeController.php b/src/Controller/Admin/AdminCommitteeController.php
index 133e903bae6..fecadf0fbf3 100644
--- a/src/Controller/Admin/AdminCommitteeController.php
+++ b/src/Controller/Admin/AdminCommitteeController.php
@@ -22,13 +22,13 @@
 use App\Repository\CommitteeRepository;
 use Doctrine\ORM\EntityManagerInterface;
 use Psr\Cache\CacheItemPoolInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Contracts\Translation\TranslatorInterface;
 
 class AdminCommitteeController extends AbstractController
@@ -141,8 +141,8 @@ public function mandatesAction(Committee $committee): Response
         ]);
     }
 
+    #[IsGranted(new Expression("is_granted('ROLE_ADMIN_TERRITOIRES_COMMITTEES') and is_granted('ADD_MANDATE_TO_COMMITTEE', subject)"), 'committee')]
     #[Route(path: '/committee/{id}/mandates/add', name: 'app_admin_committee_add_mandate', methods: ['GET|POST'])]
-    #[Security("is_granted('ROLE_ADMIN_TERRITOIRES_COMMITTEES') and is_granted('ADD_MANDATE_TO_COMMITTEE', committee)")]
     public function addMandateAction(
         Request $request,
         Committee $committee,
@@ -178,8 +178,8 @@ public function addMandateAction(
         ]);
     }
 
+    #[IsGranted(new Expression("is_granted('ROLE_ADMIN_TERRITOIRES_COMMITTEES') and is_granted('CHANGE_MANDATE_OF_COMMITTEE', subject.getCommittee())"), 'mandate')]
     #[Route(path: '/committee/mandates/{id}/replace', name: 'app_admin_committee_replace_mandate', methods: ['GET|POST'])]
-    #[Security("is_granted('ROLE_ADMIN_TERRITOIRES_COMMITTEES') and is_granted('CHANGE_MANDATE_OF_COMMITTEE', mandate.getCommittee())")]
     public function replaceMandateAction(Request $request, CommitteeAdherentMandate $mandate): Response
     {
         $committee = $mandate->getCommittee();
@@ -221,8 +221,8 @@ public function replaceMandateAction(Request $request, CommitteeAdherentMandate
         ]);
     }
 
+    #[IsGranted(new Expression("is_granted('ROLE_ADMIN_TERRITOIRES_COMMITTEES') and is_granted('CHANGE_MANDATE_OF_COMMITTEE', subject.getCommittee())"), 'mandate')]
     #[Route(path: '/committee/mandates/{id}/close', name: 'app_admin_committee_close_mandate', methods: ['GET|POST'])]
-    #[Security("is_granted('ROLE_ADMIN_TERRITOIRES_COMMITTEES') and is_granted('CHANGE_MANDATE_OF_COMMITTEE', mandate.getCommittee())")]
     public function closeMandateAction(
         Request $request,
         CommitteeAdherentMandate $mandate,
diff --git a/src/Controller/Admin/AdminDonationController.php b/src/Controller/Admin/AdminDonationController.php
index 85602aa4a29..ee228352c4b 100644
--- a/src/Controller/Admin/AdminDonationController.php
+++ b/src/Controller/Admin/AdminDonationController.php
@@ -5,10 +5,10 @@
 use App\Entity\Donation;
 use Doctrine\ORM\EntityManagerInterface;
 use League\Flysystem\FilesystemOperator;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_ADMIN_FINANCE')]
 #[Route(path: '/donation')]
diff --git a/src/Controller/Admin/AdminDonatorController.php b/src/Controller/Admin/AdminDonatorController.php
index daa065f8b30..dbaef8702fc 100644
--- a/src/Controller/Admin/AdminDonatorController.php
+++ b/src/Controller/Admin/AdminDonatorController.php
@@ -8,11 +8,11 @@
 use App\Donation\Handler\DonatorMergeCommandHandler;
 use App\Form\Admin\DonatorMergeType;
 use App\Form\Admin\Extract\DonatorExtractType;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_ADMIN_FINANCES_DONATIONS')]
 #[Route(path: '/donator')]
diff --git a/src/Controller/Admin/AdminFileController.php b/src/Controller/Admin/AdminFileController.php
index 18c2a3e581d..ce1d446b0d5 100644
--- a/src/Controller/Admin/AdminFileController.php
+++ b/src/Controller/Admin/AdminFileController.php
@@ -7,12 +7,12 @@
 use App\Utils\HttpUtils;
 use Gedmo\Sluggable\Util\Urlizer;
 use League\Flysystem\FilesystemOperator;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[Route(path: '/filesystem', name: 'app_admin_files_', methods: ['GET'])]
 class AdminFileController extends AbstractController
diff --git a/src/Controller/Admin/AdminPhoningCampaignStatsController.php b/src/Controller/Admin/AdminPhoningCampaignStatsController.php
index 33ce6f889dc..dd6ad1ce262 100644
--- a/src/Controller/Admin/AdminPhoningCampaignStatsController.php
+++ b/src/Controller/Admin/AdminPhoningCampaignStatsController.php
@@ -5,9 +5,9 @@
 use App\Controller\EnMarche\VotingPlatform\AbstractController;
 use App\Entity\Phoning\Campaign;
 use App\Repository\AdherentRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_ADMIN_PHONING_CAMPAIGNS')]
 #[Route(path: '/phoning-campaign/{id}/stats', name: 'app_admin_phoning_campaign_stats', methods: 'GET')]
diff --git a/src/Controller/Admin/AdminQrCodeGeneratorController.php b/src/Controller/Admin/AdminQrCodeGeneratorController.php
index 3b057b6bf88..270f496bdef 100644
--- a/src/Controller/Admin/AdminQrCodeGeneratorController.php
+++ b/src/Controller/Admin/AdminQrCodeGeneratorController.php
@@ -4,10 +4,10 @@
 
 use App\Entity\QrCode;
 use App\QrCode\QrCodeEntityHandler;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_ADMIN_QR_CODES')]
 #[Route(path: '/qr-code/{uuid}/generate.{_format}', name: 'app_admin_qr_code_generate', methods: 'GET', defaults: ['_format' => 'png'], requirements: ['_format' => 'png|svg'])]
diff --git a/src/Controller/Admin/AdminReportController.php b/src/Controller/Admin/AdminReportController.php
index 5fe308a06c7..412f69cf8d7 100644
--- a/src/Controller/Admin/AdminReportController.php
+++ b/src/Controller/Admin/AdminReportController.php
@@ -4,12 +4,12 @@
 
 use App\Entity\Report\Report;
 use App\Report\ReportManager;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[Route(path: '/signalements')]
 class AdminReportController extends AbstractController
diff --git a/src/Controller/Admin/AdminUnregistrationController.php b/src/Controller/Admin/AdminUnregistrationController.php
index 84e3cb9cb50..fce2d2eb2bf 100644
--- a/src/Controller/Admin/AdminUnregistrationController.php
+++ b/src/Controller/Admin/AdminUnregistrationController.php
@@ -4,12 +4,12 @@
 
 use App\Adherent\Unregistration\UnregistrationSerializer;
 use App\Repository\UnregistrationRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[Route(path: '/unregistration')]
 class AdminUnregistrationController extends AbstractController
diff --git a/src/Controller/Admin/ElectedRepresentativeSimilarAdherentProfilesController.php b/src/Controller/Admin/ElectedRepresentativeSimilarAdherentProfilesController.php
index 2fdd23fdc65..f48b9ec4c5a 100644
--- a/src/Controller/Admin/ElectedRepresentativeSimilarAdherentProfilesController.php
+++ b/src/Controller/Admin/ElectedRepresentativeSimilarAdherentProfilesController.php
@@ -8,8 +8,9 @@
 use App\Entity\ElectedRepresentative\ElectedRepresentative;
 use App\Repository\AdherentRepository;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
 use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
 
@@ -26,14 +27,14 @@ public function showSimilarProfilesAction(
         ]);
     }
 
-    #[ParamConverter('adherent', options: ['mapping' => ['adherent_id' => 'id']])]
     #[Route(path: '/elected-representative/{id}/adherent-similar-profiles/{adherent_id}/link', name: 'admin_app_electedrepresentative_adherent_similar_profiles_link', methods: ['GET'])]
     public function linkAdherentToElectedRepresentativeAction(
         ElectedRepresentative $electedRepresentative,
+        #[MapEntity(mapping: ['adherent_id' => 'id'])]
         Adherent $adherent,
         EntityManagerInterface $entityManager,
         EventDispatcherInterface $dispatcher,
-    ) {
+    ): Response {
         $dispatcher->dispatch(new ElectedRepresentativeEvent($electedRepresentative), ElectedRepresentativeEvents::BEFORE_UPDATE);
 
         $electedRepresentative->setAdherent($adherent);
diff --git a/src/Controller/Api/Action/CancelActionController.php b/src/Controller/Api/Action/CancelActionController.php
index d9c1d765746..e3f6c50cf11 100644
--- a/src/Controller/Api/Action/CancelActionController.php
+++ b/src/Controller/Api/Action/CancelActionController.php
@@ -5,13 +5,14 @@
 use App\Entity\Action\Action;
 use App\JeMengage\Push\Command\NotifyForActionCommand;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'actions') and (action.getAuthor() == user or user.hasDelegatedFromUser(action.getAuthor(), 'actions'))")]
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'actions') and (subject.getAuthor() == user or user.hasDelegatedFromUser(subject.getAuthor(), 'actions'))"), subject: 'action')]
 class CancelActionController extends AbstractController
 {
     public function __invoke(Action $action, EntityManagerInterface $manager, MessageBusInterface $bus): Response
diff --git a/src/Controller/Api/AdherentAutocompleteController.php b/src/Controller/Api/AdherentAutocompleteController.php
index 590e791a508..f9cf88846d1 100644
--- a/src/Controller/Api/AdherentAutocompleteController.php
+++ b/src/Controller/Api/AdherentAutocompleteController.php
@@ -5,18 +5,19 @@
 use App\Adherent\AdherentAutocompleteFilter;
 use App\Repository\AdherentRepository;
 use App\Scope\ScopeGeneratorResolver;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
 use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
 use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', ['team', 'my_team', 'committee'])"))]
 #[Route(path: '/v3/adherents/autocomplete', name: 'api_adherent_autocomplete', methods: ['GET'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', ['team', 'my_team', 'committee'])")]
 class AdherentAutocompleteController extends AbstractController
 {
     public function __invoke(
diff --git a/src/Controller/Api/AdherentController.php b/src/Controller/Api/AdherentController.php
index 1c2f8263ac3..2b2713ed9a4 100644
--- a/src/Controller/Api/AdherentController.php
+++ b/src/Controller/Api/AdherentController.php
@@ -5,12 +5,12 @@
 use App\Entity\Adherent;
 use App\Security\Voter\ManagedUserVoter;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Component\Validator\Validator\ValidatorInterface;
 
diff --git a/src/Controller/Api/AdherentList/CountAdherentController.php b/src/Controller/Api/AdherentList/CountAdherentController.php
index eeaed825710..4b4b588cf6a 100644
--- a/src/Controller/Api/AdherentList/CountAdherentController.php
+++ b/src/Controller/Api/AdherentList/CountAdherentController.php
@@ -8,19 +8,20 @@
 use App\Repository\Geo\ZoneRepository;
 use App\Scope\ScopeGeneratorResolver;
 use Ramsey\Uuid\Uuid;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\Encoder\JsonEncoder;
 use Symfony\Component\Serializer\Exception\NotEncodableValueException;
 use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
 use Symfony\Component\Serializer\SerializerInterface;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', ['contacts', 'committee', 'designation'])"))]
 #[Route(path: '/v3/adherents/count', name: 'app_adherents_count_get', methods: ['GET', 'POST'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', ['contacts', 'committee', 'designation'])")]
 class CountAdherentController extends AbstractController
 {
     public function __construct(
diff --git a/src/Controller/Api/AdherentMessage/DuplicateMessageController.php b/src/Controller/Api/AdherentMessage/DuplicateMessageController.php
index acd70f3d952..c816b72aa2b 100644
--- a/src/Controller/Api/AdherentMessage/DuplicateMessageController.php
+++ b/src/Controller/Api/AdherentMessage/DuplicateMessageController.php
@@ -4,11 +4,12 @@
 
 use App\AdherentMessage\AdherentMessageManager;
 use App\Entity\AdherentMessage\AbstractAdherentMessage;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'messages') and (data.getAuthor() == user or user.hasDelegatedFromUser(data.getAuthor(), 'messages'))")]
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'messages') and (subject.getAuthor() == user or user.hasDelegatedFromUser(subject.getAuthor(), 'messages'))"), subject: 'data')]
 class DuplicateMessageController extends AbstractController
 {
     public function __construct(private readonly AdherentMessageManager $manager)
diff --git a/src/Controller/Api/AdherentMessage/GetAdherentMessageStatusController.php b/src/Controller/Api/AdherentMessage/GetAdherentMessageStatusController.php
index 72783f78150..8b5473648c7 100644
--- a/src/Controller/Api/AdherentMessage/GetAdherentMessageStatusController.php
+++ b/src/Controller/Api/AdherentMessage/GetAdherentMessageStatusController.php
@@ -3,14 +3,15 @@
 namespace App\Controller\Api\AdherentMessage;
 
 use App\Entity\AdherentMessage\AbstractAdherentMessage;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 
+#[IsGranted(new Expression("is_granted('ROLE_USER') and (subject.getAuthor() == user or user.hasDelegatedFromUser(subject.getAuthor(), 'messages'))"), subject: 'message')]
 #[Route(path: '/adherent_messages/{uuid}', name: 'app_api_get_adherent_message_status', methods: ['GET'])]
-#[Security("is_granted('ROLE_USER') and (message.getAuthor() == user or user.hasDelegatedFromUser(message.getAuthor(), 'messages'))")]
 class GetAdherentMessageStatusController extends AbstractController
 {
     public function __invoke(AbstractAdherentMessage $message, SerializerInterface $serializer): Response
diff --git a/src/Controller/Api/AdherentMessage/SendAdherentMessageController.php b/src/Controller/Api/AdherentMessage/SendAdherentMessageController.php
index f2d09eefece..aa057de043f 100644
--- a/src/Controller/Api/AdherentMessage/SendAdherentMessageController.php
+++ b/src/Controller/Api/AdherentMessage/SendAdherentMessageController.php
@@ -4,12 +4,13 @@
 
 use App\AdherentMessage\AdherentMessageManager;
 use App\Entity\AdherentMessage\AbstractAdherentMessage;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'messages') and (message.getAuthor() == user or user.hasDelegatedFromUser(message.getAuthor(), 'messages'))")]
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'messages') and (subject.getAuthor() == user or user.hasDelegatedFromUser(subject.getAuthor(), 'messages'))"), subject: 'message')]
 class SendAdherentMessageController extends AbstractController
 {
     public function __construct(private readonly AdherentMessageManager $manager)
diff --git a/src/Controller/Api/AdherentMessage/SendTestAdherentMessageController.php b/src/Controller/Api/AdherentMessage/SendTestAdherentMessageController.php
index 8aaf58b1b52..044bb929566 100644
--- a/src/Controller/Api/AdherentMessage/SendTestAdherentMessageController.php
+++ b/src/Controller/Api/AdherentMessage/SendTestAdherentMessageController.php
@@ -5,11 +5,12 @@
 use App\AdherentMessage\AdherentMessageManager;
 use App\Entity\Adherent;
 use App\Entity\AdherentMessage\AbstractAdherentMessage;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'messages') and (message.getAuthor() == user or user.hasDelegatedFromUser(message.getAuthor(), 'messages'))")]
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'messages') and (subject.getAuthor() == user or user.hasDelegatedFromUser(subject.getAuthor(), 'messages'))"), subject: 'message')]
 class SendTestAdherentMessageController extends AbstractController
 {
     public function __construct(private readonly AdherentMessageManager $manager)
diff --git a/src/Controller/Api/AdherentMessage/UpdateAdherentMessageFilterController.php b/src/Controller/Api/AdherentMessage/UpdateAdherentMessageFilterController.php
index bfdcc211d88..18d7e0c26b2 100644
--- a/src/Controller/Api/AdherentMessage/UpdateAdherentMessageFilterController.php
+++ b/src/Controller/Api/AdherentMessage/UpdateAdherentMessageFilterController.php
@@ -6,18 +6,19 @@
 use App\Entity\AdherentMessage\AbstractAdherentMessage;
 use App\Entity\AdherentMessage\Filter\AudienceFilter;
 use App\Scope\ScopeGeneratorResolver;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\Encoder\JsonEncoder;
 use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
 use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Component\Validator\Validator\ValidatorInterface;
 
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'messages') and (data.getAuthor() == user or user.hasDelegatedFromUser(data.getAuthor(), 'messages'))")]
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'messages') and (subject.getAuthor() == user or user.hasDelegatedFromUser(subject.getAuthor(), 'messages'))"), subject: 'data')]
 class UpdateAdherentMessageFilterController extends AbstractController
 {
     public function __construct(
diff --git a/src/Controller/Api/AppLink/AuthenticatedAppLinkController.php b/src/Controller/Api/AppLink/AuthenticatedAppLinkController.php
index d733cf29f69..7e085c773ae 100644
--- a/src/Controller/Api/AppLink/AuthenticatedAppLinkController.php
+++ b/src/Controller/Api/AppLink/AuthenticatedAppLinkController.php
@@ -3,7 +3,6 @@
 namespace App\Controller\Api\AppLink;
 
 use App\Controller\Renaissance\Adhesion\AdhesionController;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
@@ -11,10 +10,11 @@
 use Symfony\Component\Routing\Attribute\Route;
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
 use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface;
 
+#[IsGranted('ROLE_USER')]
 #[Route(path: '/v3/app-link/{key}', name: 'api_app_link_authenticated', methods: ['GET'])]
-#[Security("is_granted('ROLE_USER')")]
 class AuthenticatedAppLinkController extends AbstractController
 {
     private const KEYS_TO_ROUTES = [
diff --git a/src/Controller/Api/CertificationRequestController.php b/src/Controller/Api/CertificationRequestController.php
index dc75681c417..1f113ba08ce 100644
--- a/src/Controller/Api/CertificationRequestController.php
+++ b/src/Controller/Api/CertificationRequestController.php
@@ -6,12 +6,12 @@
 use App\Adherent\Certification\CertificationPermissions;
 use App\Api\DTO\ImageContent;
 use App\Entity\Adherent;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\Encoder\JsonEncoder;
 use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
 use Symfony\Component\Serializer\SerializerInterface;
diff --git a/src/Controller/Api/ChangeEmailController.php b/src/Controller/Api/ChangeEmailController.php
index 84a1d27b463..58aa7d7a846 100644
--- a/src/Controller/Api/ChangeEmailController.php
+++ b/src/Controller/Api/ChangeEmailController.php
@@ -6,12 +6,12 @@
 use App\Entity\Adherent;
 use App\Membership\AdherentChangeEmailHandler;
 use App\Repository\AdherentChangeEmailTokenRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -24,8 +24,8 @@ public function __construct(
     ) {
     }
 
+    #[IsGranted('ROLE_OAUTH_SCOPE_WRITE:PROFILE')]
     #[Route(path: '/request', name: '_request', methods: ['POST'])]
-    #[Security("is_granted('ROLE_OAUTH_SCOPE_WRITE:PROFILE')")]
     public function request(
         Request $request,
         SerializerInterface $serializer,
@@ -70,8 +70,8 @@ public function request(
         ]);
     }
 
+    #[IsGranted('ROLE_OAUTH_SCOPE_WRITE:PROFILE')]
     #[Route(path: '/send-validation', name: '_send_validation', methods: ['POST'])]
-    #[Security("is_granted('ROLE_OAUTH_SCOPE_WRITE:PROFILE')")]
     public function sendValidationEmail(
         AdherentChangeEmailTokenRepository $changeEmailTokenRepository,
     ): JsonResponse {
diff --git a/src/Controller/Api/CharterController.php b/src/Controller/Api/CharterController.php
index 611fce78edf..31205132be7 100644
--- a/src/Controller/Api/CharterController.php
+++ b/src/Controller/Api/CharterController.php
@@ -7,11 +7,11 @@
 use App\CmsBlock\CmsBlockManager;
 use App\Entity\Adherent;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Contracts\Translation\TranslatorInterface;
 
 class CharterController extends AbstractController
diff --git a/src/Controller/Api/Committee/CommitteesController.php b/src/Controller/Api/Committee/CommitteesController.php
index 6ac000b0bea..25258673990 100644
--- a/src/Controller/Api/Committee/CommitteesController.php
+++ b/src/Controller/Api/Committee/CommitteesController.php
@@ -9,13 +9,12 @@
 use App\Entity\CommitteeMembership;
 use App\Repository\CommitteeMembershipRepository;
 use App\Security\Voter\Committee\CommitteeElectionVoter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 class CommitteesController extends AbstractController
 {
@@ -25,8 +24,8 @@ public function getApprovedCommitteesAction(CommitteeProvider $provider): Respon
         return new JsonResponse($provider->getApprovedCommittees());
     }
 
+    #[IsGranted('MEMBER_OF_COMMITTEE', subject: 'committee')]
     #[Route(path: '/committees/{uuid}/candidacies', name: 'app_api_committee_candidacies_get', methods: ['GET'])]
-    #[Security("is_granted('MEMBER_OF_COMMITTEE', committee)")]
     public function getCommitteeCandidaciesAction(
         Committee $committee,
         CommitteeMembershipRepository $repository,
diff --git a/src/Controller/Api/Committee/GetCommitteesZonesController.php b/src/Controller/Api/Committee/GetCommitteesZonesController.php
index 90a8f175cf2..a39f3aca1fb 100644
--- a/src/Controller/Api/Committee/GetCommitteesZonesController.php
+++ b/src/Controller/Api/Committee/GetCommitteesZonesController.php
@@ -5,13 +5,14 @@
 use App\Geo\Http\ZoneAutocompleteFilter;
 use App\Repository\Geo\ZoneRepository;
 use App\Scope\ScopeGeneratorResolver;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'committee')"))]
 #[Route(path: '/v3/committees/used-zones', name: 'api_committee_get_used_zones', methods: ['GET'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'committee')")]
 class GetCommitteesZonesController extends AbstractController
 {
     public function __invoke(ScopeGeneratorResolver $scopeGeneratorResolver, ZoneRepository $zoneRepository): Response
diff --git a/src/Controller/Api/CrmParisController.php b/src/Controller/Api/CrmParisController.php
index e4f59446dad..053026e0b8b 100644
--- a/src/Controller/Api/CrmParisController.php
+++ b/src/Controller/Api/CrmParisController.php
@@ -6,10 +6,10 @@
 use App\Repository\AdherentRepository;
 use League\Csv\CharsetConverter;
 use League\Csv\Writer;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_OAUTH_SCOPE_CRM_PARIS')]
 #[Route(path: '/crm-paris')]
diff --git a/src/Controller/Api/ElectProfileController.php b/src/Controller/Api/ElectProfileController.php
index 8f44fde11bc..3864933dbb7 100644
--- a/src/Controller/Api/ElectProfileController.php
+++ b/src/Controller/Api/ElectProfileController.php
@@ -8,19 +8,20 @@
 use App\Adherent\Tag\Command\RefreshAdherentTagCommand;
 use App\Entity\Adherent;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Messenger\MessageBusInterface;
 use Symfony\Component\Routing\Attribute\Route;
 use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Component\Validator\Validator\ValidatorInterface;
 
+#[IsGranted(new Expression("is_granted('ROLE_OAUTH_SCOPE_READ:PROFILE') and is_granted('ongoing_eletected_representative')"))]
 #[Route(path: '/v3/profile', name: 'app_api_user_profile')]
-#[Security('is_granted(\'ROLE_OAUTH_SCOPE_READ:PROFILE\') and is_granted(\'ongoing_eletected_representative\')')]
 class ElectProfileController extends AbstractController
 {
     #[Route(path: '/elect-declaration', methods: ['POST'])]
diff --git a/src/Controller/Api/ElectedRepresentative/ElectedRepresentativeListController.php b/src/Controller/Api/ElectedRepresentative/ElectedRepresentativeListController.php
index f819fa2ff3d..1b88d9eae89 100644
--- a/src/Controller/Api/ElectedRepresentative/ElectedRepresentativeListController.php
+++ b/src/Controller/Api/ElectedRepresentative/ElectedRepresentativeListController.php
@@ -5,17 +5,18 @@
 use App\ElectedRepresentative\Filter\ListFilter;
 use App\Repository\ElectedRepresentative\ElectedRepresentativeRepository;
 use App\Scope\ScopeGeneratorResolver;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
 use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
 use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'elected_representative')"))]
 #[Route(path: '/v3/elected_representatives', name: 'app_elected_representatives_list_get', methods: ['GET'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'elected_representative')")]
 class ElectedRepresentativeListController extends AbstractController
 {
     public function __construct(
diff --git a/src/Controller/Api/Event/CancelEventController.php b/src/Controller/Api/Event/CancelEventController.php
index 21fe636318e..10c083f1254 100644
--- a/src/Controller/Api/Event/CancelEventController.php
+++ b/src/Controller/Api/Event/CancelEventController.php
@@ -4,13 +4,14 @@
 
 use App\Entity\Event\BaseEvent;
 use App\Event\EventCanceledHandler;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'events') and is_granted('CAN_MANAGE_EVENT', event)")]
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'events') and is_granted('CAN_MANAGE_EVENT', subject)"), subject: 'event')]
 class CancelEventController extends AbstractController
 {
     public function __invoke(EventCanceledHandler $handler, Request $request, BaseEvent $event): Response
diff --git a/src/Controller/Api/Event/GetEventParticipantsController.php b/src/Controller/Api/Event/GetEventParticipantsController.php
index 075ee99cb28..420cbed9a5e 100644
--- a/src/Controller/Api/Event/GetEventParticipantsController.php
+++ b/src/Controller/Api/Event/GetEventParticipantsController.php
@@ -7,14 +7,15 @@
 use App\Normalizer\ImageExposeNormalizer;
 use App\Normalizer\TranslateAdherentTagNormalizer;
 use App\Repository\EventRegistrationRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'events') and is_granted('MANAGE_ZONEABLE_ITEM__FOR_SCOPE', subject)"), subject: 'event')]
 #[Route(path: '/v3/events/{uuid}/participants.{_format}', name: 'api_events_get_participants', requirements: ['uuid' => '%pattern_uuid%', '_format' => 'json|xlsx'], defaults: ['_format' => 'json'], methods: ['GET'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'events') and is_granted('MANAGE_ZONEABLE_ITEM__FOR_SCOPE', event)")]
 class GetEventParticipantsController extends AbstractController
 {
     public function __invoke(
diff --git a/src/Controller/Api/Filter/GetCollectionFiltersController.php b/src/Controller/Api/Filter/GetCollectionFiltersController.php
index 1c9f2e2893d..d8829b6569c 100644
--- a/src/Controller/Api/Filter/GetCollectionFiltersController.php
+++ b/src/Controller/Api/Filter/GetCollectionFiltersController.php
@@ -4,15 +4,16 @@
 
 use App\JMEFilter\FiltersGenerator;
 use App\Scope\ScopeGeneratorResolver;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', ['contacts', 'messages'])"))]
 #[Route(path: '/v3/filters', name: 'app_collection_filters_get', methods: ['GET'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', ['contacts', 'messages'])")]
 class GetCollectionFiltersController extends AbstractController
 {
     public function __construct(
diff --git a/src/Controller/Api/InternalApiProxyController.php b/src/Controller/Api/InternalApiProxyController.php
index c6f603f026a..09f30036dcd 100644
--- a/src/Controller/Api/InternalApiProxyController.php
+++ b/src/Controller/Api/InternalApiProxyController.php
@@ -5,12 +5,12 @@
 use App\Entity\Adherent;
 use App\Entity\InternalApiApplication;
 use App\Scope\GeneralScopeGenerator;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Contracts\HttpClient\HttpClientInterface;
 
diff --git a/src/Controller/Api/JeMengage/GetTimelineFeedsController.php b/src/Controller/Api/JeMengage/GetTimelineFeedsController.php
index c4bfc8584f9..50d07d79d3f 100644
--- a/src/Controller/Api/JeMengage/GetTimelineFeedsController.php
+++ b/src/Controller/Api/JeMengage/GetTimelineFeedsController.php
@@ -6,12 +6,12 @@
 use App\JeMengage\Timeline\DataProvider;
 use App\JeMengage\Timeline\TimelineFeedTypeEnum;
 use App\OAuth\Model\DeviceApiUser;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Attribute\Route;
 use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_OAUTH_SCOPE_JEMARCHE_APP')]
 #[Route(path: '/v3/je-mengage/timeline_feeds', name: 'api_get_jemarche_timeline_feeds', methods: ['GET'])]
diff --git a/src/Controller/Api/Jecoute/DepartmentController.php b/src/Controller/Api/Jecoute/DepartmentController.php
index f18402a2482..e6b1a680e6d 100644
--- a/src/Controller/Api/Jecoute/DepartmentController.php
+++ b/src/Controller/Api/Jecoute/DepartmentController.php
@@ -3,11 +3,11 @@
 namespace App\Controller\Api\Jecoute;
 
 use App\Repository\Geo\DepartmentRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 
 #[IsGranted('ROLE_OAUTH_SCOPE_JEMARCHE_APP')]
diff --git a/src/Controller/Api/Jecoute/GetSurveyRepliesController.php b/src/Controller/Api/Jecoute/GetSurveyRepliesController.php
index 9a92f35867f..c6862d43341 100644
--- a/src/Controller/Api/Jecoute/GetSurveyRepliesController.php
+++ b/src/Controller/Api/Jecoute/GetSurveyRepliesController.php
@@ -8,15 +8,16 @@
 use App\Exporter\SurveyExporter;
 use App\Repository\Jecoute\DataSurveyRepository;
 use App\Scope\ScopeGeneratorResolver;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'survey') and is_granted('SCOPE_CAN_MANAGE', subject)"), subject: 'survey')]
 #[Route(path: '/v3/surveys/{uuid}/replies.{_format}', name: 'api_survey_get_survey_replies', methods: ['GET'], requirements: ['uuid' => '%pattern_uuid%', '_format' => 'json|csv|xlsx'], defaults: ['_format' => 'json'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'survey') and is_granted('SCOPE_CAN_MANAGE', survey)")]
 class GetSurveyRepliesController extends AbstractController
 {
     public function __construct(private readonly ScopeGeneratorResolver $scopeGeneratorResolver)
diff --git a/src/Controller/Api/Jecoute/SurveyController.php b/src/Controller/Api/Jecoute/SurveyController.php
index bdbcf9d92fc..4a494ff5b5f 100644
--- a/src/Controller/Api/Jecoute/SurveyController.php
+++ b/src/Controller/Api/Jecoute/SurveyController.php
@@ -9,18 +9,19 @@
 use App\Repository\Geo\ZoneRepository;
 use App\Repository\Jecoute\LocalSurveyRepository;
 use App\Repository\Jecoute\NationalSurveyRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Form\FormFactoryInterface;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 
+#[IsGranted(new Expression("(is_granted('ROLE_USER') or is_granted('ROLE_OAUTH_DEVICE')) and (is_granted('ROLE_OAUTH_SCOPE_JECOUTE_SURVEYS') or is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP'))"))]
 #[Route(path: '/jecoute/survey')]
-#[Security("(is_granted('ROLE_USER') or is_granted('ROLE_OAUTH_DEVICE')) and (is_granted('ROLE_OAUTH_SCOPE_JECOUTE_SURVEYS') or is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP'))")]
 class SurveyController extends AbstractController
 {
     #[Route(name: 'api_public_surveys_list', methods: ['GET'])]
diff --git a/src/Controller/Api/MoocController.php b/src/Controller/Api/MoocController.php
index 4cbe2181a31..a2d65ef7d1b 100644
--- a/src/Controller/Api/MoocController.php
+++ b/src/Controller/Api/MoocController.php
@@ -4,7 +4,7 @@
 
 use App\Entity\Mooc\Mooc;
 use App\Repository\MoocRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
@@ -18,10 +18,11 @@ public function moocLandingPageAction(MoocRepository $moocRepository): Response
         return $this->json($moocRepository->findAllOrdered(), Response::HTTP_OK, [], ['groups' => ['mooc_list']]);
     }
 
-    #[Entity('mooc', expr: 'repository.findOneBySlug(slug)')]
     #[Route(path: '/{slug}', name: 'api_mooc', methods: ['GET'])]
-    public function moocAction(Mooc $mooc): Response
-    {
+    public function moocAction(
+        #[MapEntity(expr: 'repository.findOneBySlug(slug)')]
+        Mooc $mooc,
+    ): Response {
         return $this->json($mooc, Response::HTTP_OK, [], ['groups' => ['mooc_read']]);
     }
 }
diff --git a/src/Controller/Api/Pap/BuildingEventController.php b/src/Controller/Api/Pap/BuildingEventController.php
index c8a6b29e77c..2b026106917 100644
--- a/src/Controller/Api/Pap/BuildingEventController.php
+++ b/src/Controller/Api/Pap/BuildingEventController.php
@@ -7,20 +7,21 @@
 use App\Pap\Command\BuildingEventAsyncCommand;
 use App\Pap\Command\BuildingEventCommand;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Messenger\MessageBusInterface;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\Encoder\JsonEncoder;
 use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
 use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Component\Validator\Validator\ValidatorInterface;
 
-#[Security("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')")]
+#[IsGranted(new Expression("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')"))]
 class BuildingEventController extends AbstractController
 {
     #[Route(path: '/v3/pap/buildings/{uuid}/events', requirements: ['uuid' => '%pattern_uuid%'], name: 'api_create_building_event', methods: ['POST'])]
diff --git a/src/Controller/Api/Pap/BuildingHistoryController.php b/src/Controller/Api/Pap/BuildingHistoryController.php
index 1a12d182599..e01f04fb2a1 100644
--- a/src/Controller/Api/Pap/BuildingHistoryController.php
+++ b/src/Controller/Api/Pap/BuildingHistoryController.php
@@ -5,17 +5,18 @@
 use App\Entity\Pap\Building;
 use App\Repository\Pap\CampaignHistoryRepository;
 use App\Repository\Pap\CampaignRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Security("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')")]
+#[IsGranted(new Expression("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')"))]
 class BuildingHistoryController extends AbstractController
 {
-    #[Route(path: '/v3/pap/buildings/{uuid}/history', requirements: ['uuid' => '%pattern_uuid%'], name: 'api_get_building_history', methods: ['GET'])]
+    #[Route(path: '/v3/pap/buildings/{uuid}/history', name: 'api_get_building_history', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
     public function __invoke(
         Request $request,
         Building $building,
@@ -35,7 +36,7 @@ public function __invoke(
 
         return $this->json(
             $campaignHistoryRepository->findHistoryForBuilding($building, $campaign),
-            JsonResponse::HTTP_OK,
+            Response::HTTP_OK,
             [],
             ['groups' => ['pap_building_history']]
         );
diff --git a/src/Controller/Api/Pap/GetBuildingBlocksController.php b/src/Controller/Api/Pap/GetBuildingBlocksController.php
index 694517e54a6..14c175ed4d4 100644
--- a/src/Controller/Api/Pap/GetBuildingBlocksController.php
+++ b/src/Controller/Api/Pap/GetBuildingBlocksController.php
@@ -3,13 +3,14 @@
 namespace App\Controller\Api\Pap;
 
 use App\Entity\Pap\Building;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Route(path: '/v3/pap/buildings/{uuid}/building_blocks', name: 'api_get_building_blocks', methods: ['GET'], requirements: ['uuid' => '%pattern_uuid%'])]
-#[Security("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')")]
+#[IsGranted(new Expression("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')"))]
+#[Route(path: '/v3/pap/buildings/{uuid}/building_blocks', name: 'api_get_building_blocks', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
 class GetBuildingBlocksController extends AbstractController
 {
     public function __invoke(Building $building): Response
diff --git a/src/Controller/Api/Pap/GetCampaignRankingController.php b/src/Controller/Api/Pap/GetCampaignRankingController.php
index cebacc88a6e..ab61a086b2c 100644
--- a/src/Controller/Api/Pap/GetCampaignRankingController.php
+++ b/src/Controller/Api/Pap/GetCampaignRankingController.php
@@ -9,13 +9,14 @@
 use App\Repository\Geo\ZoneRepository;
 use App\Repository\Pap\CampaignHistoryRepository;
 use Psr\Log\LoggerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Route(path: '/v3/pap_campaigns/{uuid}/ranking', requirements: ['uuid' => '%pattern_uuid%'], name: 'api_get_pap_campaign_ranking', methods: ['GET'])]
-#[Security("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')")]
+#[IsGranted(new Expression("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')"))]
+#[Route(path: '/v3/pap_campaigns/{uuid}/ranking', name: 'api_get_pap_campaign_ranking', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
 class GetCampaignRankingController extends AbstractController
 {
     private CampaignHistoryRepository $campaignHistoryRepository;
diff --git a/src/Controller/Api/Pap/GetPapCampaignSurveyConfigController.php b/src/Controller/Api/Pap/GetPapCampaignSurveyConfigController.php
index 72007278a7f..3d21880e8db 100644
--- a/src/Controller/Api/Pap/GetPapCampaignSurveyConfigController.php
+++ b/src/Controller/Api/Pap/GetPapCampaignSurveyConfigController.php
@@ -6,13 +6,14 @@
 use App\Jecoute\ProfessionEnum;
 use App\Pap\CampaignHistoryStatusEnum;
 use App\ValueObject\Genders;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Route(path: '/v3/pap_campaigns/{uuid}/survey-config', requirements: ['uuid' => '%pattern_uuid%'], name: 'api_get_pap_campaign_survey_config', methods: ['GET'])]
-#[Security("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')")]
+#[IsGranted(new Expression("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')"))]
+#[Route(path: '/v3/pap_campaigns/{uuid}/survey-config', name: 'api_get_pap_campaign_survey_config', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
 class GetPapCampaignSurveyConfigController extends AbstractController
 {
     public function __invoke(): JsonResponse
diff --git a/src/Controller/Api/Pap/GetPapCampaignSurveyController.php b/src/Controller/Api/Pap/GetPapCampaignSurveyController.php
index 8b0052648d7..15e54cd3c84 100644
--- a/src/Controller/Api/Pap/GetPapCampaignSurveyController.php
+++ b/src/Controller/Api/Pap/GetPapCampaignSurveyController.php
@@ -3,13 +3,14 @@
 namespace App\Controller\Api\Pap;
 
 use App\Entity\Pap\Campaign;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Route(path: '/v3/pap_campaigns/{uuid}/survey', name: 'api_pap_camapign_get_campaign_survey', methods: ['GET'], requirements: ['uuid' => '%pattern_uuid%'])]
-#[Security("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')")]
+#[IsGranted(new Expression("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')"))]
+#[Route(path: '/v3/pap_campaigns/{uuid}/survey', name: 'api_pap_camapign_get_campaign_survey', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
 class GetPapCampaignSurveyController extends AbstractController
 {
     public function __invoke(Campaign $campaign): Response
diff --git a/src/Controller/Api/Pap/GetPapCampaignSurveyRepliesController.php b/src/Controller/Api/Pap/GetPapCampaignSurveyRepliesController.php
index 051ce9aff9b..eca9e91186c 100644
--- a/src/Controller/Api/Pap/GetPapCampaignSurveyRepliesController.php
+++ b/src/Controller/Api/Pap/GetPapCampaignSurveyRepliesController.php
@@ -6,14 +6,15 @@
 use App\Exporter\PapCampaignSurveyRepliesExporter;
 use App\Repository\Jecoute\DataSurveyRepository;
 use App\Scope\ScopeGeneratorResolver;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Route(path: '/v3/pap_campaigns/{uuid}/replies.{_format}', name: 'api_pap_camapign_get_campaign_survey_replies', methods: ['GET'], requirements: ['uuid' => '%pattern_uuid%', '_format' => 'json|csv|xlsx'], defaults: ['_format' => 'json'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', ['pap_v2', 'pap']) and is_granted('SCOPE_CAN_MANAGE', campaign)")]
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', ['pap_v2', 'pap']) and is_granted('SCOPE_CAN_MANAGE', subject)"), subject: 'campaign')]
+#[Route(path: '/v3/pap_campaigns/{uuid}/replies.{_format}', name: 'api_pap_camapign_get_campaign_survey_replies', requirements: ['uuid' => '%pattern_uuid%', '_format' => 'json|csv|xlsx'], defaults: ['_format' => 'json'], methods: ['GET'])]
 class GetPapCampaignSurveyRepliesController extends AbstractController
 {
     public function __invoke(
diff --git a/src/Controller/Api/Pap/GetPapCampaignTutorialController.php b/src/Controller/Api/Pap/GetPapCampaignTutorialController.php
index 5697926a814..2ab87a90d2a 100644
--- a/src/Controller/Api/Pap/GetPapCampaignTutorialController.php
+++ b/src/Controller/Api/Pap/GetPapCampaignTutorialController.php
@@ -3,13 +3,14 @@
 namespace App\Controller\Api\Pap;
 
 use App\CmsBlock\CmsBlockManager;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')"))]
 #[Route(path: '/v3/pap_campaigns/tutorial', name: 'api_get_pap_campaigns_tutorial', methods: ['GET'])]
-#[Security("is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')")]
 class GetPapCampaignTutorialController extends AbstractController
 {
     public function __invoke(CmsBlockManager $manager): JsonResponse
diff --git a/src/Controller/Api/Phoning/GetPhoningCampaignSurveyController.php b/src/Controller/Api/Phoning/GetPhoningCampaignSurveyController.php
index 540f04f020d..455f7cb695b 100644
--- a/src/Controller/Api/Phoning/GetPhoningCampaignSurveyController.php
+++ b/src/Controller/Api/Phoning/GetPhoningCampaignSurveyController.php
@@ -3,13 +3,14 @@
 namespace App\Controller\Api\Phoning;
 
 use App\Entity\Phoning\Campaign;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Route(path: '/v3/phoning_campaigns/{uuid}/survey', name: 'api_phoning_camapign_get_campaign_survey', methods: ['GET'], requirements: ['uuid' => '%pattern_uuid%'])]
-#[Security("campaign.isPermanent() or is_granted('ROLE_PHONING_CAMPAIGN_MEMBER')")]
+#[IsGranted(new Expression("subject.isPermanent() or is_granted('ROLE_PHONING_CAMPAIGN_MEMBER')"), subject: 'campaign')]
+#[Route(path: '/v3/phoning_campaigns/{uuid}/survey', name: 'api_phoning_camapign_get_campaign_survey', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
 class GetPhoningCampaignSurveyController extends AbstractController
 {
     public function __invoke(Campaign $campaign): Response
diff --git a/src/Controller/Api/Phoning/GetPhoningCampaignSurveyRepliesController.php b/src/Controller/Api/Phoning/GetPhoningCampaignSurveyRepliesController.php
index 6f17f716277..50271097aa0 100644
--- a/src/Controller/Api/Phoning/GetPhoningCampaignSurveyRepliesController.php
+++ b/src/Controller/Api/Phoning/GetPhoningCampaignSurveyRepliesController.php
@@ -5,14 +5,15 @@
 use App\Entity\Phoning\Campaign;
 use App\Exporter\PhoningCampaignSurveyRepliesExporter;
 use App\Repository\Jecoute\DataSurveyRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Route(path: '/v3/phoning_campaigns/{uuid}/replies.{_format}', name: 'api_phoning_camapign_get_campaign_survey_replies', methods: ['GET'], requirements: ['uuid' => '%pattern_uuid%', '_format' => 'json|csv|xlsx'], defaults: ['_format' => 'json'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'phoning_campaign')")]
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'phoning_campaign')"))]
+#[Route(path: '/v3/phoning_campaigns/{uuid}/replies.{_format}', name: 'api_phoning_campaign_get_campaign_survey_replies', requirements: ['uuid' => '%pattern_uuid%', '_format' => 'json|csv|xlsx'], defaults: ['_format' => 'json'], methods: ['GET'])]
 class GetPhoningCampaignSurveyRepliesController extends AbstractController
 {
     public function __invoke(
diff --git a/src/Controller/Api/ProfileController.php b/src/Controller/Api/ProfileController.php
index 9d71272a4a5..15926a07e52 100644
--- a/src/Controller/Api/ProfileController.php
+++ b/src/Controller/Api/ProfileController.php
@@ -30,14 +30,14 @@
 use Doctrine\ORM\EntityManagerInterface;
 use League\Flysystem\FilesystemOperator;
 use Psr\Log\LoggerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
 use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
 use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
 use Symfony\Component\Serializer\SerializerInterface;
@@ -135,8 +135,8 @@ public function cancelDonations(
         return new JsonResponse('OK');
     }
 
+    #[IsGranted(new Expression("is_granted('ROLE_OAUTH_SCOPE_WRITE:PROFILE') and user == subject"), 'adherent')]
     #[Route(path: '/{uuid}', name: '_update', methods: ['PUT'])]
-    #[Security("is_granted('ROLE_OAUTH_SCOPE_WRITE:PROFILE') and user == adherent")]
     public function update(
         Request $request,
         SerializerInterface $serializer,
@@ -174,8 +174,8 @@ public function update(
         return JsonResponse::fromJsonString($errors, Response::HTTP_BAD_REQUEST);
     }
 
+    #[IsGranted('ROLE_OAUTH_SCOPE_WRITE:PROFILE')]
     #[Route(path: '/me/password-change', name: '_password_change', methods: ['POST'])]
-    #[Security("is_granted('ROLE_OAUTH_SCOPE_WRITE:PROFILE')")]
     public function changePassword(
         Request $request,
         SerializerInterface $serializer,
@@ -222,8 +222,8 @@ public function myInstances(UserInterface $adherent, AdherentInstances $adherent
         return $this->json(array_values(array_filter($adherentInstances->generate($adherent))));
     }
 
+    #[IsGranted(new Expression('is_granted("ROLE_OAUTH_SCOPE_WRITE:PROFILE") and user.isRenaissanceAdherent()'))]
     #[Route(path: '/committees/{uuid}/join', methods: ['PUT'])]
-    #[Security('is_granted("ROLE_OAUTH_SCOPE_WRITE:PROFILE") and user.isRenaissanceAdherent()')]
     public function saveMyNewCommittee(
         Committee $committee,
         UserInterface $adherent,
@@ -273,8 +273,8 @@ public function configuration(AdherentProfileConfiguration $adherentProfileConfi
         return new JsonResponse($adherentProfileConfiguration->build());
     }
 
+    #[IsGranted('UNREGISTER', subject: 'user')]
     #[Route(path: '/unregister', name: '_unregister', methods: ['POST'])]
-    #[Security("is_granted('UNREGISTER', user)")]
     public function terminateMembershipAction(
         Request $request,
         SerializerInterface $serializer,
diff --git a/src/Controller/Api/ReportController.php b/src/Controller/Api/ReportController.php
index bd17ac417bd..6d8427bd6ca 100644
--- a/src/Controller/Api/ReportController.php
+++ b/src/Controller/Api/ReportController.php
@@ -7,12 +7,12 @@
 use App\Report\ReportCreationCommandHandler;
 use App\Report\ReportManager;
 use App\Report\ReportType;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Component\Validator\Validator\ValidatorInterface;
 use Symfony\Contracts\Translation\TranslatorInterface;
diff --git a/src/Controller/Api/ResubscribeEmailController.php b/src/Controller/Api/ResubscribeEmailController.php
index 6e961232219..356ef866679 100644
--- a/src/Controller/Api/ResubscribeEmailController.php
+++ b/src/Controller/Api/ResubscribeEmailController.php
@@ -4,10 +4,10 @@
 
 use App\Entity\Adherent;
 use App\Mailchimp\SignUp\SignUpHandler;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_USER')]
 #[Route(path: '/resubscribe-email', name: 'api_resubscribe_email_payload', methods: ['GET'])]
diff --git a/src/Controller/Api/Security/GetJWTTokenController.php b/src/Controller/Api/Security/GetJWTTokenController.php
index 82a63cc1623..3403c504fb2 100644
--- a/src/Controller/Api/Security/GetJWTTokenController.php
+++ b/src/Controller/Api/Security/GetJWTTokenController.php
@@ -5,13 +5,14 @@
 use App\Entity\OAuth\Client;
 use App\OAuth\JWTTokenGenerator;
 use App\Security\Voter\OAuthClientVoter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'featurebase')"))]
 #[Route(path: '/v3/sso/jwt/{uuid}', name: 'api_security_get_jwt_token', methods: ['GET'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'featurebase')")]
 class GetJWTTokenController extends AbstractController
 {
     public function __invoke(Client $client, JWTTokenGenerator $tokenGenerator): Response
diff --git a/src/Controller/Api/Team/AddTeamMembersController.php b/src/Controller/Api/Team/AddTeamMembersController.php
index f01ff56a23c..ebe7ae6080b 100644
--- a/src/Controller/Api/Team/AddTeamMembersController.php
+++ b/src/Controller/Api/Team/AddTeamMembersController.php
@@ -5,18 +5,19 @@
 use App\Api\DTO\AdherentUuid;
 use App\Entity\Team\Team;
 use App\Team\TeamMemberManagementHandler;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\Encoder\JsonEncoder;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Component\Validator\Validator\ValidatorInterface;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'team') and is_granted('SCOPE_CAN_MANAGE', subject)"), subject: 'team')]
 #[Route(path: '/v3/teams/{uuid}/add-members', requirements: ['uuid' => '%pattern_uuid%'], name: 'api_team_add_members', methods: ['PUT'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'team') and is_granted('SCOPE_CAN_MANAGE', team)")]
 class AddTeamMembersController extends AbstractController
 {
     public function __invoke(
diff --git a/src/Controller/Api/Team/RemoveTeamController.php b/src/Controller/Api/Team/RemoveTeamController.php
index 7ae1483f9e4..39779e921c3 100644
--- a/src/Controller/Api/Team/RemoveTeamController.php
+++ b/src/Controller/Api/Team/RemoveTeamController.php
@@ -5,14 +5,15 @@
 use App\Entity\Team\Team;
 use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'team') and is_granted('SCOPE_CAN_MANAGE', subject)"), subject: 'team')]
 #[Route(path: '/v3/teams/{uuid}', requirements: ['uuid' => '%pattern_uuid%'], name: 'api_team_remove', methods: ['DELETE'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'team') and is_granted('SCOPE_CAN_MANAGE', team)")]
 class RemoveTeamController extends AbstractController
 {
     public function __invoke(Team $team, EntityManagerInterface $entityManager): JsonResponse
diff --git a/src/Controller/Api/Team/RemoveTeamMemberController.php b/src/Controller/Api/Team/RemoveTeamMemberController.php
index d81751dfcde..4283d6b039a 100644
--- a/src/Controller/Api/Team/RemoveTeamMemberController.php
+++ b/src/Controller/Api/Team/RemoveTeamMemberController.php
@@ -5,20 +5,21 @@
 use App\Entity\Adherent;
 use App\Entity\Team\Team;
 use App\Team\TeamMemberManagementHandler;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Entity('adherent', expr: 'repository.findOneByUuid(adherent_uuid)')]
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'team') and is_granted('SCOPE_CAN_MANAGE', subject)"), subject: 'team')]
 #[Route(path: '/v3/teams/{uuid}/members/{adherent_uuid}', requirements: ['uuid' => '%pattern_uuid%', 'adherent_uuid' => '%pattern_uuid%'], name: 'api_team_remove_member', methods: ['DELETE'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'team') and is_granted('SCOPE_CAN_MANAGE', team)")]
 class RemoveTeamMemberController extends AbstractController
 {
     public function __invoke(
         Team $team,
+        #[MapEntity(expr: 'repository.findOneByUuid(adherent_uuid)')]
         Adherent $adherent,
         TeamMemberManagementHandler $teamMemberManagementHandler,
     ): JsonResponse {
diff --git a/src/Controller/Api/UserController.php b/src/Controller/Api/UserController.php
index ab086761974..9a71b5a558f 100644
--- a/src/Controller/Api/UserController.php
+++ b/src/Controller/Api/UserController.php
@@ -17,7 +17,7 @@
 use App\OAuth\OAuthTokenGenerator;
 use League\OAuth2\Server\Exception\OAuthServerException;
 use Nyholm\Psr7\Response as PsrResponse;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
@@ -64,12 +64,12 @@ private function getGrantedNormalizationContext(): array
         return $context;
     }
 
-    #[Entity('user', expr: 'repository.findOneByUuid(user_uuid)')]
-    #[Entity('createPasswordToken', expr: 'repository.findByToken(create_password_token)')]
     #[Route(path: '/profile/mot-de-passe/{user_uuid}/{create_password_token}', name: 'user_create_password', requirements: ['user_uuid' => '%pattern_uuid%', 'reset_password_token' => '%pattern_sha1%'], methods: ['POST'])]
     public function createPassword(
         Request $request,
+        #[MapEntity(expr: 'repository.findOneByUuid(user_uuid)')]
         Adherent $user,
+        #[MapEntity(expr: 'repository.findByToken(create_password_token)')]
         AdherentResetPasswordToken $createPasswordToken,
         AdherentResetPasswordHandler $handler,
         SerializerInterface $serializer,
diff --git a/src/Controller/Api/UserListDefinition/AbstractUserListDefinitionController.php b/src/Controller/Api/UserListDefinition/AbstractUserListDefinitionController.php
index b5ef97d6efa..ea7f8f39467 100644
--- a/src/Controller/Api/UserListDefinition/AbstractUserListDefinitionController.php
+++ b/src/Controller/Api/UserListDefinition/AbstractUserListDefinitionController.php
@@ -3,12 +3,12 @@
 namespace App\Controller\Api\UserListDefinition;
 
 use App\UserListDefinition\UserListDefinitionManager;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 abstract class AbstractUserListDefinitionController extends AbstractController
 {
diff --git a/src/Controller/Api/VotingPlatform/ElectionBallotsController.php b/src/Controller/Api/VotingPlatform/ElectionBallotsController.php
index 82f7751c8cb..61c60e9e3fc 100644
--- a/src/Controller/Api/VotingPlatform/ElectionBallotsController.php
+++ b/src/Controller/Api/VotingPlatform/ElectionBallotsController.php
@@ -5,14 +5,15 @@
 use App\Entity\VotingPlatform\Designation\Designation;
 use App\Repository\VotingPlatform\ElectionRepository;
 use App\Repository\VotingPlatform\VoteResultRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Sonata\Exporter\ExporterInterface;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'designation')"))]
 #[Route('/v3/designations/{uuid}/ballots.{_format}', name: 'app_designation_get_ballots', requirements: ['uuid' => '%pattern_uuid%', '_format' => 'json|xlsx'], defaults: ['_format' => 'json'], methods: ['GET'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'designation')")]
 class ElectionBallotsController extends AbstractController
 {
     public function __invoke(
diff --git a/src/Controller/Api/VotingPlatform/ElectionResultsController.php b/src/Controller/Api/VotingPlatform/ElectionResultsController.php
index eb3f7e57c24..f3344d1e287 100644
--- a/src/Controller/Api/VotingPlatform/ElectionResultsController.php
+++ b/src/Controller/Api/VotingPlatform/ElectionResultsController.php
@@ -4,13 +4,14 @@
 
 use App\Entity\VotingPlatform\Designation\Designation;
 use App\Repository\VotingPlatform\ElectionRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'designation')"))]
 #[Route('/v3/designations/{uuid}/results', name: 'app_designation_get_results', methods: ['GET'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'designation')")]
 class ElectionResultsController extends AbstractController
 {
     public function __invoke(Designation $designation, ElectionRepository $electionRepository): Response
diff --git a/src/Controller/Api/VotingPlatform/ElectionVotersListController.php b/src/Controller/Api/VotingPlatform/ElectionVotersListController.php
index 99705e75c0f..54bc169341e 100644
--- a/src/Controller/Api/VotingPlatform/ElectionVotersListController.php
+++ b/src/Controller/Api/VotingPlatform/ElectionVotersListController.php
@@ -5,15 +5,16 @@
 use App\Entity\VotingPlatform\Designation\Designation;
 use App\Repository\VotingPlatform\ElectionRepository;
 use App\Repository\VotingPlatform\VoterRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Sonata\Exporter\ExporterInterface;
 use Sonata\Exporter\Source\IteratorCallbackSourceIterator;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('REQUEST_SCOPE_GRANTED', 'designation')"))]
 #[Route('/v3/designations/{uuid}/voters.{_format}', name: 'app_designation_get_voters', requirements: ['uuid' => '%pattern_uuid%', '_format' => 'json|xlsx'], defaults: ['_format' => 'json'], methods: ['GET'])]
-#[Security("is_granted('REQUEST_SCOPE_GRANTED', 'designation')")]
 class ElectionVotersListController extends AbstractController
 {
     public function __invoke(
diff --git a/src/Controller/Api/Zone/PlaceAutocompleteController.php b/src/Controller/Api/Zone/PlaceAutocompleteController.php
index 5af63a3e551..927608dd3c4 100644
--- a/src/Controller/Api/Zone/PlaceAutocompleteController.php
+++ b/src/Controller/Api/Zone/PlaceAutocompleteController.php
@@ -2,11 +2,11 @@
 
 namespace App\Controller\Api\Zone;
 
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Contracts\HttpClient\HttpClientInterface;
 
 #[IsGranted('ROLE_OAUTH_SCOPE_JEMARCHE_APP')]
diff --git a/src/Controller/Api/Zone/ZoneAutocompleteRestrictedController.php b/src/Controller/Api/Zone/ZoneAutocompleteRestrictedController.php
index 48464e51eb3..85c079761a2 100644
--- a/src/Controller/Api/Zone/ZoneAutocompleteRestrictedController.php
+++ b/src/Controller/Api/Zone/ZoneAutocompleteRestrictedController.php
@@ -9,10 +9,10 @@
 use App\Scope\AuthorizationChecker;
 use App\Scope\ScopeEnum;
 use App\Scope\ScopeGeneratorResolver;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('REQUEST_SCOPE_GRANTED')]
 #[Route(path: '/v3/zone/autocomplete', name: 'api_v3_zone_autocomplete_for_scope', methods: ['GET'])]
diff --git a/src/Controller/EnMarche/AdherentMessage/AbstractMessageController.php b/src/Controller/EnMarche/AdherentMessage/AbstractMessageController.php
index 4c4456ee2f6..d347ccc7c8a 100644
--- a/src/Controller/EnMarche/AdherentMessage/AbstractMessageController.php
+++ b/src/Controller/EnMarche/AdherentMessage/AbstractMessageController.php
@@ -16,13 +16,13 @@
 use App\Form\AdherentMessage\AdherentMessageType;
 use App\Repository\AdherentMessageRepository;
 use Doctrine\ORM\EntityManagerInterface as ObjectManager;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 abstract class AbstractMessageController extends AbstractController
 {
@@ -198,8 +198,8 @@ public function filterMessageAction(
         ]);
     }
 
+    #[IsGranted(new Expression("is_granted('IS_AUTHOR_OF', subject) and is_granted('USER_CAN_SEND_MESSAGE', subject)"), subject: 'message')]
     #[Route(path: '/{uuid}/send', name: 'send', methods: ['GET'])]
-    #[Security("is_granted('IS_AUTHOR_OF', message) and is_granted('USER_CAN_SEND_MESSAGE', message)")]
     public function sendMessageAction(AbstractAdherentMessage $message, AdherentMessageManager $manager): Response
     {
         $this->checkAccess();
@@ -235,8 +235,8 @@ public function sendMessageAction(AbstractAdherentMessage $message, AdherentMess
         return $this->redirectToMessageRoute('list');
     }
 
+    #[IsGranted(new Expression("is_granted('IS_AUTHOR_OF', subject) and subject.isSent()"), subject: 'message')]
     #[Route(path: '/{uuid}/confirmation', name: 'send_success', methods: ['GET'])]
-    #[Security("is_granted('IS_AUTHOR_OF', message) and message.isSent()")]
     public function sendSuccessAction(AbstractAdherentMessage $message): Response
     {
         $this->checkAccess();
@@ -287,7 +287,7 @@ protected function renderTemplate(string $template, array $parameters = []): Res
 
     protected function redirectToMessageRoute(string $subName, array $parameters = []): Response
     {
-        return $this->redirectToRoute("app_message_{$this->getMessageType()}_{$subName}", $parameters);
+        return $this->redirectToRoute("app_message_{$this->getMessageType()}_$subName", $parameters);
     }
 
     protected function checkAccess(): void
diff --git a/src/Controller/EnMarche/AdherentMessage/CandidateJecouteMessageController.php b/src/Controller/EnMarche/AdherentMessage/CandidateJecouteMessageController.php
index 864b02e7549..1e88bb9b432 100644
--- a/src/Controller/EnMarche/AdherentMessage/CandidateJecouteMessageController.php
+++ b/src/Controller/EnMarche/AdherentMessage/CandidateJecouteMessageController.php
@@ -3,11 +3,12 @@
 namespace App\Controller\EnMarche\AdherentMessage;
 
 use App\AdherentMessage\AdherentMessageTypeEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_MESSAGES'))"))]
 #[Route(path: '/espace-candidat/messagerie-jemarche', name: 'app_message_candidate_jecoute_')]
-#[Security("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_MESSAGES'))")]
 class CandidateJecouteMessageController extends AbstractMessageController
 {
     protected function getMessageType(): string
diff --git a/src/Controller/EnMarche/AdherentMessage/CandidateMessageController.php b/src/Controller/EnMarche/AdherentMessage/CandidateMessageController.php
index 4b8f3470e0f..fd236704dc2 100644
--- a/src/Controller/EnMarche/AdherentMessage/CandidateMessageController.php
+++ b/src/Controller/EnMarche/AdherentMessage/CandidateMessageController.php
@@ -3,11 +3,12 @@
 namespace App\Controller\EnMarche\AdherentMessage;
 
 use App\AdherentMessage\AdherentMessageTypeEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_MESSAGES'))"))]
 #[Route(path: '/espace-candidat/messagerie', name: 'app_message_candidate_')]
-#[Security("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_MESSAGES'))")]
 class CandidateMessageController extends AbstractMessageController
 {
     protected function getMessageType(): string
diff --git a/src/Controller/EnMarche/AdherentMessage/CommitteeMessageController.php b/src/Controller/EnMarche/AdherentMessage/CommitteeMessageController.php
index a4aeda97d6b..ba48bd6a402 100644
--- a/src/Controller/EnMarche/AdherentMessage/CommitteeMessageController.php
+++ b/src/Controller/EnMarche/AdherentMessage/CommitteeMessageController.php
@@ -17,24 +17,24 @@
 use App\Mailchimp\Manager;
 use App\Repository\AdherentMessageRepository;
 use Doctrine\ORM\EntityManagerInterface as ObjectManager;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[ParamConverter('committee', options: ['mapping' => ['committee_slug' => 'slug']])]
+#[IsGranted('HOST_COMMITTEE', subject: 'committee')]
 #[Route(path: '/espace-animateur/{committee_slug}/messagerie', name: 'app_message_committee_')]
-#[Security("is_granted('HOST_COMMITTEE', committee) and committee.isApproved()")]
 class CommitteeMessageController extends AbstractController
 {
     #[Route(name: 'list', methods: ['GET'])]
     public function messageListAction(
         Request $request,
         AdherentMessageRepository $repository,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
     ): Response {
         /** @var Adherent $adherent */
@@ -63,6 +63,7 @@ public function messageListAction(
     #[Route(path: '/creer', name: 'create', methods: ['GET', 'POST'])]
     public function createMessageAction(
         Request $request,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
         AdherentMessageManager $manager,
     ): Response {
@@ -108,6 +109,7 @@ public function updateMessageAction(
         Request $request,
         CommitteeAdherentMessage $message,
         AdherentMessageManager $manager,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
     ): Response {
         if ($message->isSent()) {
@@ -145,6 +147,7 @@ public function updateMessageAction(
     public function filterMessageAction(
         Request $request,
         CommitteeAdherentMessage $message,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
         AdherentMessageManager $manager,
         FilterFormFactory $formFactory,
@@ -194,8 +197,11 @@ public function filterMessageAction(
 
     #[IsGranted('IS_AUTHOR_OF', subject: 'message')]
     #[Route(path: '/{uuid}/visualiser', name: 'preview', methods: ['GET'])]
-    public function previewMessageAction(CommitteeAdherentMessage $message, Committee $committee): Response
-    {
+    public function previewMessageAction(
+        CommitteeAdherentMessage $message,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
+        Committee $committee,
+    ): Response {
         if (!$message->isSynchronized()) {
             throw new BadRequestHttpException('Message preview is not ready yet.');
         }
@@ -208,6 +214,7 @@ public function previewMessageAction(CommitteeAdherentMessage $message, Committe
     public function deleteMessageAction(
         CommitteeAdherentMessage $message,
         ObjectManager $manager,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
     ): Response {
         $manager->remove($message);
@@ -223,6 +230,7 @@ public function deleteMessageAction(
     public function sendMessageAction(
         CommitteeAdherentMessage $message,
         AdherentMessageManager $manager,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
     ): Response {
         if (!$message->isSynchronized()) {
@@ -248,10 +256,13 @@ public function sendMessageAction(
         return $this->redirectToRoute('app_message_committee_list', ['committee_slug' => $committee->getSlug()]);
     }
 
+    #[IsGranted(new Expression("is_granted('IS_AUTHOR_OF', subject) and subject.isSent()"), subject: 'message')]
     #[Route(path: '/{uuid}/confirmation', name: 'send_success', methods: ['GET'])]
-    #[Security("is_granted('IS_AUTHOR_OF', message) and message.isSent()")]
-    public function sendSuccessAction(AbstractAdherentMessage $message, Committee $committee): Response
-    {
+    public function sendSuccessAction(
+        AbstractAdherentMessage $message,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
+        Committee $committee,
+    ): Response {
         return $this->renderTemplate('message/send_success/committee.html.twig', $committee, ['message' => $message]);
     }
 
@@ -260,6 +271,7 @@ public function sendSuccessAction(AbstractAdherentMessage $message, Committee $c
     public function sendTestMessageAction(
         CommitteeAdherentMessage $message,
         Manager $manager,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
     ): Response {
         if (!$message->isSynchronized()) {
diff --git a/src/Controller/EnMarche/AdherentMessage/CommonMessageController.php b/src/Controller/EnMarche/AdherentMessage/CommonMessageController.php
index e70acb09113..970676c8511 100644
--- a/src/Controller/EnMarche/AdherentMessage/CommonMessageController.php
+++ b/src/Controller/EnMarche/AdherentMessage/CommonMessageController.php
@@ -6,10 +6,10 @@
 use App\AdherentMessage\StatisticsAggregator;
 use App\Entity\AdherentMessage\AbstractAdherentMessage;
 use App\Mailchimp\Campaign\MailchimpObjectIdMapping;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_MESSAGE_REDACTOR')]
 #[Route(path: '/adherent-message', name: 'app_message_common_')]
diff --git a/src/Controller/EnMarche/AdherentMessage/DeputyMessageController.php b/src/Controller/EnMarche/AdherentMessage/DeputyMessageController.php
index c961ac07674..e28d24bad74 100644
--- a/src/Controller/EnMarche/AdherentMessage/DeputyMessageController.php
+++ b/src/Controller/EnMarche/AdherentMessage/DeputyMessageController.php
@@ -3,11 +3,12 @@
 namespace App\Controller\EnMarche\AdherentMessage;
 
 use App\AdherentMessage\AdherentMessageTypeEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_DEPUTY') or (is_granted('ROLE_DELEGATED_DEPUTY') and is_granted('HAS_DELEGATED_ACCESS_MESSAGES'))"))]
 #[Route(path: '/espace-depute/messagerie', name: 'app_message_deputy_')]
-#[Security("is_granted('ROLE_DEPUTY') or (is_granted('ROLE_DELEGATED_DEPUTY') and is_granted('HAS_DELEGATED_ACCESS_MESSAGES'))")]
 class DeputyMessageController extends AbstractMessageController
 {
     protected function getMessageType(): string
diff --git a/src/Controller/EnMarche/AdherentProfile/FunnelController.php b/src/Controller/EnMarche/AdherentProfile/FunnelController.php
index 71cae385015..5ac56748b9e 100644
--- a/src/Controller/EnMarche/AdherentProfile/FunnelController.php
+++ b/src/Controller/EnMarche/AdherentProfile/FunnelController.php
@@ -6,10 +6,10 @@
 use App\AdherentProfile\AdherentProfileHandler;
 use App\Controller\EnMarche\VotingPlatform\AbstractController;
 use App\Form\AdherentFunnelGeneralType;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_USER')]
 class FunnelController extends AbstractController
diff --git a/src/Controller/EnMarche/CommitteeController.php b/src/Controller/EnMarche/CommitteeController.php
index aa56e23a03d..46d6ffff344 100644
--- a/src/Controller/EnMarche/CommitteeController.php
+++ b/src/Controller/EnMarche/CommitteeController.php
@@ -10,7 +10,6 @@
 use App\Mailchimp\Synchronisation\Command\AdherentChangeCommand;
 use App\Security\Http\Session\AnonymousFollowerSession;
 use App\Security\Voter\Committee\ChangeCommitteeVoter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
@@ -18,6 +17,7 @@
 use Symfony\Component\Messenger\MessageBusInterface;
 use Symfony\Component\Routing\Attribute\Route;
 use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[Route(path: '/comites/{slug}')]
 class CommitteeController extends AbstractController
diff --git a/src/Controller/EnMarche/CommitteeDesignation/AbstractDesignationController.php b/src/Controller/EnMarche/CommitteeDesignation/AbstractDesignationController.php
index 7cf265bf975..330ec5f8f20 100644
--- a/src/Controller/EnMarche/CommitteeDesignation/AbstractDesignationController.php
+++ b/src/Controller/EnMarche/CommitteeDesignation/AbstractDesignationController.php
@@ -11,7 +11,7 @@
 use App\Repository\VotingPlatform\ElectionRepository;
 use App\Repository\VotingPlatform\VoteResultRepository;
 use App\Repository\VotingPlatform\VoterRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
@@ -29,6 +29,7 @@ public function __construct(ElectionRepository $electionRepository)
     #[Route(path: '', name: '_list', methods: ['GET'])]
     public function listDesignationsAction(
         Request $request,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
         CommitteeElectionRepository $repository,
     ): Response {
@@ -41,12 +42,13 @@ public function listDesignationsAction(
     /**
      * @param Committee $committee used in Security notation in concretes classes
      */
-    #[ParamConverter('electionRound', options: ['mapping' => ['election_round_uuid' => 'uuid']])]
     #[Route(path: '/{uuid}/{election_round_uuid}', name: '_dashboard', methods: ['GET'], defaults: ['election_round_uuid' => null])]
     public function dashboardAction(
         Request $request,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
         Election $election,
+        #[MapEntity(mapping: ['election_round_uuid' => 'uuid'])]
         ?ElectionRound $electionRound = null,
     ): Response {
         if (!$electionRound) {
@@ -62,13 +64,14 @@ public function dashboardAction(
         ]);
     }
 
-    #[ParamConverter('electionRound', options: ['mapping' => ['election_round_uuid' => 'uuid']])]
     #[Route(path: '/{uuid}/liste-emargement/{election_round_uuid}', name: '_voters_list', methods: ['GET'], defaults: ['election_round_uuid' => null])]
     public function listVotersAction(
         Request $request,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
         Election $election,
         VoterRepository $voterRepository,
+        #[MapEntity(mapping: ['election_round_uuid' => 'uuid'])]
         ?ElectionRound $electionRound = null,
     ): Response {
         if (!$electionRound) {
@@ -89,12 +92,13 @@ public function listVotersAction(
         ]);
     }
 
-    #[ParamConverter('electionRound', options: ['mapping' => ['election_round_uuid' => 'uuid']])]
     #[Route(path: '/{uuid}/resultats/{election_round_uuid}', name: '_results', methods: ['GET'], defaults: ['election_round_uuid' => null])]
     public function showResultsAction(
         Request $request,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
         Election $election,
+        #[MapEntity(mapping: ['election_round_uuid' => 'uuid'])]
         ?ElectionRound $electionRound = null,
     ): Response {
         if (!$electionRound) {
@@ -122,13 +126,14 @@ function (ElectionPoolResult $poolResult) use ($poolCode) {
         ]);
     }
 
-    #[ParamConverter('electionRound', options: ['mapping' => ['election_round_uuid' => 'uuid']])]
     #[Route(path: '/{uuid}/bulletins/{election_round_uuid}', name: '_votes', methods: ['GET'], defaults: ['election_round_uuid' => null])]
     public function listVotesAction(
         Request $request,
+        #[MapEntity(mapping: ['committee_slug' => 'slug'])]
         Committee $committee,
         Election $election,
         VoteResultRepository $voteResultRepository,
+        #[MapEntity(mapping: ['election_round_uuid' => 'uuid'])]
         ?ElectionRound $electionRound = null,
     ): Response {
         if (!$electionRound) {
diff --git a/src/Controller/EnMarche/CommitteeDesignation/CandidatureController.php b/src/Controller/EnMarche/CommitteeDesignation/CandidatureController.php
index bbbf212b40b..168e94d9e3c 100644
--- a/src/Controller/EnMarche/CommitteeDesignation/CandidatureController.php
+++ b/src/Controller/EnMarche/CommitteeDesignation/CandidatureController.php
@@ -15,15 +15,15 @@
 use App\Security\Voter\Committee\CommitteeElectionVoter;
 use App\ValueObject\Genders;
 use App\VotingPlatform\Designation\DesignationTypeEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('MEMBER_OF_COMMITTEE', subject: 'committee')]
 #[Route(path: '/comites/{slug}/candidature', name: 'app_committee_candidature')]
@@ -217,13 +217,13 @@ public function invitationListAction(
         ]);
     }
 
-    #[ParamConverter('committee', class: Committee::class, options: ['mapping' => ['slug' => 'slug']])]
-    #[ParamConverter('votePlace', class: CommitteeCandidacyInvitation::class, options: ['mapping' => ['uuid' => 'uuid']])]
+    #[IsGranted(new Expression('subject.getMembership() == user.getMembershipFor(committee)'), subject: 'invitation')]
     #[Route(path: '/mes-invitations/{uuid}/accepter', name: '_invitation_accept', methods: ['GET', 'POST'])]
-    #[Security('invitation.getMembership() == user.getMembershipFor(committee)')]
     public function acceptInvitationAction(
+        #[MapEntity(mapping: ['slug' => 'slug'])]
         Committee $committee,
         Request $request,
+        #[MapEntity(mapping: ['uuid' => 'uuid'])]
         CommitteeCandidacyInvitation $invitation,
     ): Response {
         /** @var Adherent $adherent */
@@ -266,12 +266,14 @@ public function acceptInvitationAction(
         ]);
     }
 
-    #[ParamConverter('committee', class: Committee::class, options: ['mapping' => ['slug' => 'slug']])]
-    #[ParamConverter('votePlace', class: CommitteeCandidacyInvitation::class, options: ['mapping' => ['uuid' => 'uuid']])]
+    #[IsGranted(new Expression('subject.getMembership() == user.getMembershipFor(committee)'), subject: 'invitation')]
     #[Route(path: '/mes-invitations/{uuid}/decliner', name: '_invitation_decline', methods: ['GET'])]
-    #[Security('invitation.getMembership() == user.getMembershipFor(committee)')]
-    public function declineInvitationAction(Committee $committee, CommitteeCandidacyInvitation $invitation): Response
-    {
+    public function declineInvitationAction(
+        #[MapEntity(mapping: ['slug' => 'slug'])]
+        Committee $committee,
+        #[MapEntity(mapping: ['uuid' => 'uuid'])]
+        CommitteeCandidacyInvitation $invitation,
+    ): Response {
         if (!($election = $committee->getCommitteeElection()) || !$election->isCandidacyPeriodActive()) {
             return $this->redirectToRoute('app_committee_show', ['slug' => $committee->getSlug()]);
         }
diff --git a/src/Controller/EnMarche/CommitteeDesignation/SupervisorDesignationController.php b/src/Controller/EnMarche/CommitteeDesignation/SupervisorDesignationController.php
index 9fb72708a57..8e7299acca1 100644
--- a/src/Controller/EnMarche/CommitteeDesignation/SupervisorDesignationController.php
+++ b/src/Controller/EnMarche/CommitteeDesignation/SupervisorDesignationController.php
@@ -2,13 +2,12 @@
 
 namespace App\Controller\EnMarche\CommitteeDesignation;
 
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[ParamConverter('committee', options: ['mapping' => ['committee_slug' => 'slug']])]
+#[IsGranted(new Expression("is_granted('MANAGE_COMMITTEE_DESIGNATIONS', subject) and subject.isApproved()"), subject: 'committee')]
 #[Route(path: '/espace-animateur/{committee_slug}/designations', name: 'app_supervisor_designations')]
-#[Security("is_granted('MANAGE_COMMITTEE_DESIGNATIONS', committee) and committee.isApproved()")]
 class SupervisorDesignationController extends AbstractDesignationController
 {
     protected function getSpaceType(): string
diff --git a/src/Controller/EnMarche/CommitteeEventManagerController.php b/src/Controller/EnMarche/CommitteeEventManagerController.php
index 1bbea4449a7..f6d01f2d530 100644
--- a/src/Controller/EnMarche/CommitteeEventManagerController.php
+++ b/src/Controller/EnMarche/CommitteeEventManagerController.php
@@ -16,8 +16,7 @@
 use App\Form\ContactMembersType;
 use App\Form\EventCommandType;
 use App\Repository\EventRegistrationRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\Form\Extension\Core\Type\FormType;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
@@ -25,6 +24,7 @@
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('HOST_EVENT', subject: 'event')]
 #[Route(path: '/evenements/{slug}')]
@@ -45,10 +45,13 @@ public function __construct(EventRegistrationRepository $eventRegistrationReposi
         $this->eventRegistrationRepository = $eventRegistrationRepository;
     }
 
-    #[Entity('event', expr: 'repository.findOneActiveBySlug(slug)')]
     #[Route(path: '/modifier', name: 'app_committee_event_edit', methods: ['GET', 'POST'])]
-    public function editAction(Request $request, CommitteeEvent $event, EventCommandHandler $handler): Response
-    {
+    public function editAction(
+        Request $request,
+        #[MapEntity(expr: 'repository.findOneActiveBySlug(slug)')]
+        CommitteeEvent $event,
+        EventCommandHandler $handler,
+    ): Response {
         $form = $this->createForm(
             EventCommandType::class,
             $command = EventCommand::createFromEvent($event),
@@ -74,10 +77,10 @@ public function editAction(Request $request, CommitteeEvent $event, EventCommand
         ]);
     }
 
-    #[Entity('event', expr: 'repository.findOneActiveBySlug(slug)')]
     #[Route(path: '/annuler', name: 'app_committee_event_cancel', methods: ['GET', 'POST'])]
     public function cancelAction(
         Request $request,
+        #[MapEntity(expr: 'repository.findOneActiveBySlug(slug)')]
         CommitteeEvent $event,
         EventCanceledHandler $eventCanceledHandler,
     ): Response {
diff --git a/src/Controller/EnMarche/CommitteeManagerController.php b/src/Controller/EnMarche/CommitteeManagerController.php
index c3162ca4b0a..2a6b0af5627 100644
--- a/src/Controller/EnMarche/CommitteeManagerController.php
+++ b/src/Controller/EnMarche/CommitteeManagerController.php
@@ -18,15 +18,15 @@
 use App\Form\EventCommandType;
 use App\Repository\AdherentRepository;
 use App\Repository\CommitteeMembershipRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Sonata\Exporter\Exporter;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Form\Extension\Core\Type\FormType;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 
 #[IsGranted('HOST_COMMITTEE', subject: 'committee')]
@@ -69,8 +69,8 @@ public function editAction(
         ]);
     }
 
+    #[IsGranted(new Expression('subject.isApproved()'), subject: 'committee')]
     #[Route(path: '/evenements/ajouter', name: 'app_committee_manager_add_event', methods: ['GET', 'POST'])]
-    #[Security('committee.isApproved()')]
     public function addEventAction(
         Request $request,
         Committee $committee,
@@ -102,8 +102,8 @@ public function addEventAction(
         ]);
     }
 
+    #[IsGranted(new Expression('subject.isApproved()'), subject: 'committee')]
     #[Route(path: '/membres', name: 'app_committee_manager_list_members', methods: ['GET'])]
-    #[Security('committee.isApproved()')]
     public function listMembersAction(
         Request $request,
         Committee $committee,
@@ -160,11 +160,14 @@ public function listMembersAction(
         ]);
     }
 
-    #[Entity('member', expr: 'repository.findOneByUuid(member_uuid)')]
+    #[IsGranted(new Expression("is_granted('SUPERVISE_COMMITTEE', subject) and is_granted('PROMOTE_TO_HOST_IN_COMMITTEE', subject)"), subject: 'committee')]
     #[Route(path: '/promouvoir-suppleant/{member_uuid}', name: 'app_committee_promote_host', methods: ['GET', 'POST'])]
-    #[Security("is_granted('SUPERVISE_COMMITTEE', committee) and is_granted('PROMOTE_TO_HOST_IN_COMMITTEE', committee)")]
-    public function promoteHostAction(Request $request, Committee $committee, Adherent $member): Response
-    {
+    public function promoteHostAction(
+        Request $request,
+        Committee $committee,
+        #[MapEntity(expr: 'repository.findOneByUuid(member_uuid)')]
+        Adherent $member,
+    ): Response {
         if (!$this->manager->isPromotableHost($member, $committee)) {
             throw $this->createNotFoundException(\sprintf('Member "%s" of committee "%s" can not be promoted as a host privileged person.', $member->getUuid(), $committee->getUuid()));
         }
@@ -194,11 +197,14 @@ public function promoteHostAction(Request $request, Committee $committee, Adhere
         ]);
     }
 
-    #[Entity('member', expr: 'repository.findOneByUuid(member_uuid)')]
     #[IsGranted('SUPERVISE_COMMITTEE', subject: 'committee')]
     #[Route(path: '/retirer-suppleant/{member_uuid}', name: 'app_committee_demote_host', methods: ['GET', 'POST'])]
-    public function demoteHostAction(Request $request, Committee $committee, Adherent $member): Response
-    {
+    public function demoteHostAction(
+        Request $request,
+        Committee $committee,
+        #[MapEntity(expr: 'repository.findOneByUuid(member_uuid)')]
+        Adherent $member,
+    ): Response {
         if (!$this->manager->isDemotableHost($member, $committee)) {
             throw $this->createNotFoundException(\sprintf('Member "%s" of committee "%s" can not be demoted as a simple follower.', $member->getUuid(), $committee->getUuid()));
         }
diff --git a/src/Controller/EnMarche/CommitteeSpaceController.php b/src/Controller/EnMarche/CommitteeSpaceController.php
index ed08051c3fc..a1f945427be 100644
--- a/src/Controller/EnMarche/CommitteeSpaceController.php
+++ b/src/Controller/EnMarche/CommitteeSpaceController.php
@@ -4,13 +4,14 @@
 
 use App\Entity\Adherent;
 use App\Repository\CommitteeRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_SUPERVISOR') or is_granted('ROLE_HOST')"))]
 #[Route(path: '/espace-comite', name: 'app_committee_space_dashboard')]
-#[Security("is_granted('ROLE_SUPERVISOR') or is_granted('ROLE_HOST')")]
 class CommitteeSpaceController extends AbstractController
 {
     public function __invoke(CommitteeRepository $repository): Response
diff --git a/src/Controller/EnMarche/Coordinator/CoordinatorCommitteeController.php b/src/Controller/EnMarche/Coordinator/CoordinatorCommitteeController.php
index abaf844e65e..89942506c01 100644
--- a/src/Controller/EnMarche/Coordinator/CoordinatorCommitteeController.php
+++ b/src/Controller/EnMarche/Coordinator/CoordinatorCommitteeController.php
@@ -8,12 +8,12 @@
 use App\Entity\Committee;
 use App\Exception\BaseGroupException;
 use App\Form\CoordinatorAreaType;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_REGIONAL_COORDINATOR')]
 #[Route(path: '/espace-coordinateur/comites')]
diff --git a/src/Controller/EnMarche/DeputyController.php b/src/Controller/EnMarche/DeputyController.php
index 40bfef1c538..934e59c538d 100644
--- a/src/Controller/EnMarche/DeputyController.php
+++ b/src/Controller/EnMarche/DeputyController.php
@@ -4,14 +4,15 @@
 
 use App\Referent\ManagedCommitteesExporter;
 use App\Repository\CommitteeRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_DEPUTY') or (is_granted('ROLE_DELEGATED_DEPUTY') and is_granted('HAS_DELEGATED_ACCESS_COMMITTEE'))"))]
 #[Route(path: '/espace-depute', name: 'app_deputy_')]
-#[Security("is_granted('ROLE_DEPUTY') or (is_granted('ROLE_DELEGATED_DEPUTY') and is_granted('HAS_DELEGATED_ACCESS_COMMITTEE'))")]
 class DeputyController extends AbstractController
 {
     use AccessDelegatorTrait;
diff --git a/src/Controller/EnMarche/ElectedRepresentative/DeputyElectedRepresentativeController.php b/src/Controller/EnMarche/ElectedRepresentative/DeputyElectedRepresentativeController.php
index 2e96b58ecd1..8d6293c3c57 100644
--- a/src/Controller/EnMarche/ElectedRepresentative/DeputyElectedRepresentativeController.php
+++ b/src/Controller/EnMarche/ElectedRepresentative/DeputyElectedRepresentativeController.php
@@ -4,8 +4,8 @@
 
 use App\AdherentSpace\AdherentSpaceEnum;
 use App\Entity\Adherent;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_DEPUTY')]
 #[Route(path: '/espace-depute', name: 'app_deputy_elected_representatives_')]
diff --git a/src/Controller/EnMarche/ElectionResultsReporterController.php b/src/Controller/EnMarche/ElectionResultsReporterController.php
index 2dcac5b0b82..737f77122bd 100644
--- a/src/Controller/EnMarche/ElectionResultsReporterController.php
+++ b/src/Controller/EnMarche/ElectionResultsReporterController.php
@@ -5,11 +5,11 @@
 use App\AssociationCity\Filter\AssociationCityFilter;
 use App\Form\CityFilterType;
 use App\Repository\CityRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_ELECTION_RESULTS_REPORTER')]
 #[Route(path: '/espace-rapporteur-resultats', name: 'app_election_results_reporter_space')]
diff --git a/src/Controller/EnMarche/EventController.php b/src/Controller/EnMarche/EventController.php
index 7956d8df38c..7b5e009863e 100644
--- a/src/Controller/EnMarche/EventController.php
+++ b/src/Controller/EnMarche/EventController.php
@@ -17,8 +17,7 @@
 use App\Repository\EventRepository;
 use App\Security\Http\Session\AnonymousFollowerSession;
 use App\Serializer\Encoder\ICalEncoder;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
@@ -26,17 +25,20 @@
 use Symfony\Component\HttpFoundation\ResponseHeaderBag;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Component\Validator\Validator\ValidatorInterface;
 
-#[Entity('event', expr: 'repository.findOnePublishedBySlug(slug)')]
 #[IsGranted('CAN_ACCESS_EVENT', subject: 'event')]
 #[Route(path: '/evenements/{slug}', name: 'app_committee_event')]
 class EventController extends AbstractController
 {
     #[Route(name: '_show', methods: ['GET'])]
-    public function showAction(BaseEvent $event, EventRepository $eventRepository): Response
-    {
+    public function showAction(
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
+        BaseEvent $event,
+        EventRepository $eventRepository,
+    ): Response {
         $params = [
             'event' => $event,
             'eventsNearby' => null,
@@ -54,8 +56,11 @@ public function showAction(BaseEvent $event, EventRepository $eventRepository):
     }
 
     #[Route(path: '/ical', name: '_export_ical', methods: ['GET'])]
-    public function exportIcalAction(BaseEvent $event, SerializerInterface $serializer): Response
-    {
+    public function exportIcalAction(
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
+        BaseEvent $event,
+        SerializerInterface $serializer,
+    ): Response {
         $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT.'; filename='.$event->getSlug().'.ics';
 
         $response = new Response($serializer->serialize($event, ICalEncoder::FORMAT), Response::HTTP_OK);
@@ -65,10 +70,10 @@ public function exportIcalAction(BaseEvent $event, SerializerInterface $serializ
         return $response;
     }
 
-    #[Entity('event', expr: 'repository.findOneActiveBySlug(slug)')]
     #[IsGranted('ROLE_USER')]
     #[Route(path: '/inscription-adherent', name: '_attend_adherent', methods: ['GET'])]
     public function attendAdherentAction(
+        #[MapEntity(expr: 'repository.findOneActiveBySlug(slug)')]
         BaseEvent $event,
         ValidatorInterface $validator,
         EventRegistrationCommandHandler $eventRegistrationCommandHandler,
@@ -103,10 +108,10 @@ public function attendAdherentAction(
         return $this->redirectToRoute('app_committee_event_show', ['slug' => $event->getSlug()]);
     }
 
-    #[Entity('event', expr: 'repository.findOneActiveBySlug(slug)')]
     #[Route(path: '/inscription', name: '_attend', methods: ['GET', 'POST'])]
     public function attendAction(
         Request $request,
+        #[MapEntity(expr: 'repository.findOneActiveBySlug(slug)')]
         BaseEvent $event,
         EventRegistrationCommandHandler $eventRegistrationCommandHandler,
         AnonymousFollowerSession $anonymousFollowerSession,
@@ -155,6 +160,7 @@ public function attendAction(
     #[Route(path: '/confirmation', name: '_attend_confirmation', condition: "request.query.has('registration')", methods: ['GET'])]
     public function attendConfirmationAction(
         Request $request,
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
         BaseEvent $event,
         EventRegistrationManager $manager,
     ): Response {
@@ -177,8 +183,12 @@ public function attendConfirmationAction(
     }
 
     #[Route(path: '/invitation', name: '_invite', methods: ['GET', 'POST'])]
-    public function inviteAction(Request $request, BaseEvent $event, EventInvitationHandler $handler): Response
-    {
+    public function inviteAction(
+        Request $request,
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
+        BaseEvent $event,
+        EventInvitationHandler $handler,
+    ): Response {
         $eventInvitation = EventInvitation::createFromAdherent(
             $this->getUser(),
             $request->request->get('g-recaptcha-response')
@@ -208,8 +218,11 @@ public function inviteAction(Request $request, BaseEvent $event, EventInvitation
     }
 
     #[Route(path: '/invitation/merci', name: '_invitation_sent', methods: ['GET'])]
-    public function invitationSentAction(Request $request, BaseEvent $event): Response
-    {
+    public function invitationSentAction(
+        Request $request,
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
+        BaseEvent $event,
+    ): Response {
         if (!$invitationsCount = $request->getSession()->remove('event_invitations_count')) {
             return $this->redirectToRoute('app_committee_event_invite', [
                 'slug' => $event->getSlug(),
@@ -225,6 +238,7 @@ public function invitationSentAction(Request $request, BaseEvent $event): Respon
     #[Route(path: '/desinscription', name: '_unregistration', condition: 'request.isXmlHttpRequest()', methods: ['GET', 'POST'])]
     public function unregistrationAction(
         Request $request,
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
         BaseEvent $event,
         EventRegistrationManager $eventRegistrationManager,
     ): JsonResponse {
diff --git a/src/Controller/EnMarche/EventManager/AbstractEventManagerController.php b/src/Controller/EnMarche/EventManager/AbstractEventManagerController.php
index 0f91ea8c152..b4b1e8dabea 100644
--- a/src/Controller/EnMarche/EventManager/AbstractEventManagerController.php
+++ b/src/Controller/EnMarche/EventManager/AbstractEventManagerController.php
@@ -13,13 +13,13 @@
 use App\Event\EventCommand;
 use App\Event\EventCommandHandler;
 use App\Form\EventCommandType;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 abstract class AbstractEventManagerController extends AbstractController
 {
diff --git a/src/Controller/EnMarche/EventManager/CandidateEventManagerController.php b/src/Controller/EnMarche/EventManager/CandidateEventManagerController.php
index 009f18f262f..e46a4feb523 100644
--- a/src/Controller/EnMarche/EventManager/CandidateEventManagerController.php
+++ b/src/Controller/EnMarche/EventManager/CandidateEventManagerController.php
@@ -10,11 +10,12 @@
 use App\Geo\ManagedZoneProvider;
 use App\Repository\Event\DefaultEventRepository;
 use App\Repository\EventGroupCategoryRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_EVENTS'))"))]
 #[Route(path: '/espace-candidat', name: 'app_candidate_event_manager')]
-#[Security("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_EVENTS'))")]
 class CandidateEventManagerController extends AbstractEventManagerController
 {
     private $repository;
diff --git a/src/Controller/EnMarche/EventManager/DeputyEventManagerController.php b/src/Controller/EnMarche/EventManager/DeputyEventManagerController.php
index d3e3f546015..84b5693985d 100644
--- a/src/Controller/EnMarche/EventManager/DeputyEventManagerController.php
+++ b/src/Controller/EnMarche/EventManager/DeputyEventManagerController.php
@@ -8,11 +8,12 @@
 use App\Event\EventManagerSpaceEnum;
 use App\Geo\ManagedZoneProvider;
 use App\Repository\Event\BaseEventRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_DEPUTY') or (is_granted('ROLE_DELEGATED_DEPUTY') and is_granted('HAS_DELEGATED_ACCESS_EVENTS'))"))]
 #[Route(path: '/espace-depute', name: 'app_deputy_event_manager')]
-#[Security("is_granted('ROLE_DEPUTY') or (is_granted('ROLE_DELEGATED_DEPUTY') and is_granted('HAS_DELEGATED_ACCESS_EVENTS'))")]
 class DeputyEventManagerController extends AbstractEventManagerController
 {
     private $repository;
diff --git a/src/Controller/EnMarche/Filesystem/AbstractFilesController.php b/src/Controller/EnMarche/Filesystem/AbstractFilesController.php
index 85f55bf0783..a9202fb43db 100644
--- a/src/Controller/EnMarche/Filesystem/AbstractFilesController.php
+++ b/src/Controller/EnMarche/Filesystem/AbstractFilesController.php
@@ -8,11 +8,11 @@
 use App\Utils\HttpUtils;
 use Gedmo\Sluggable\Util\Urlizer;
 use League\Flysystem\FilesystemOperator;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 abstract class AbstractFilesController extends AbstractController
 {
diff --git a/src/Controller/EnMarche/Filesystem/CandidateFilesController.php b/src/Controller/EnMarche/Filesystem/CandidateFilesController.php
index 770acc626ab..e99ca352023 100644
--- a/src/Controller/EnMarche/Filesystem/CandidateFilesController.php
+++ b/src/Controller/EnMarche/Filesystem/CandidateFilesController.php
@@ -3,11 +3,12 @@
 namespace App\Controller\EnMarche\Filesystem;
 
 use App\AdherentSpace\AdherentSpaceEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_FILES'))"))]
 #[Route(path: '/espace-candidat', name: 'app_candidate_files_', methods: ['GET'])]
-#[Security("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_FILES'))")]
 class CandidateFilesController extends AbstractFilesController
 {
     protected function getSpaceType(): string
diff --git a/src/Controller/EnMarche/InvitationController.php b/src/Controller/EnMarche/InvitationController.php
index bdd0841695e..1b3e66eeffd 100644
--- a/src/Controller/EnMarche/InvitationController.php
+++ b/src/Controller/EnMarche/InvitationController.php
@@ -8,12 +8,12 @@
 use App\Form\InvitationType;
 use App\Form\SimpleInvitationType;
 use App\Invitation\InvitationRequestHandler;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 class InvitationController extends AbstractController
 {
diff --git a/src/Controller/EnMarche/Jecoute/AbstractJecouteController.php b/src/Controller/EnMarche/Jecoute/AbstractJecouteController.php
index bd3cd3eb05f..ca802a5602e 100644
--- a/src/Controller/EnMarche/Jecoute/AbstractJecouteController.php
+++ b/src/Controller/EnMarche/Jecoute/AbstractJecouteController.php
@@ -18,13 +18,13 @@
 use App\Repository\Jecoute\NationalSurveyRepository;
 use App\Repository\Jecoute\SuggestedQuestionRepository;
 use Doctrine\ORM\EntityManagerInterface as ObjectManager;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 abstract class AbstractJecouteController extends AbstractController
 {
@@ -125,10 +125,11 @@ public function jecouteSurveyEditAction(
         ]);
     }
 
-    #[Entity('survey', expr: 'repository.findOnePublishedByUuid(uuid)')]
     #[Route(path: '/questionnaire/{uuid}', name: 'survey_show', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
-    public function jecouteSurveyShowAction(Survey $survey): Response
-    {
+    public function jecouteSurveyShowAction(
+        #[MapEntity(expr: 'repository.findOnePublishedByUuid(uuid)')]
+        Survey $survey,
+    ): Response {
         $isLocalSurvey = $survey instanceof LocalSurvey;
         $form = $this->createForm(SurveyFormType::class, $survey, [
             'zones' => $isLocalSurvey ? [$survey->getZone()] : [],
@@ -141,11 +142,11 @@ public function jecouteSurveyShowAction(Survey $survey): Response
         ]);
     }
 
-    #[Entity('survey', expr: 'repository.findOneByUuid(uuid)')]
+    #[IsGranted(new Expression("(is_granted('IS_AUTHOR_OF', subject) or is_granted('IS_SURVEY_MANAGER_OF', subject)) or subject.isNational()"), subject: 'survey')]
     #[Route(path: '/questionnaire/{uuid}/stats', name: 'survey_stats', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
-    #[Security("(is_granted('IS_AUTHOR_OF', survey) or is_granted('IS_SURVEY_MANAGER_OF', survey)) or survey.isNational()")]
     public function jecouteSurveyStatsAction(
         Request $request,
+        #[MapEntity(expr: 'repository.findOneByUuid(uuid)')]
         Survey $survey,
         StatisticsProvider $provider,
         SurveyExporter $exporter,
@@ -175,11 +176,10 @@ public function jecouteSurveyStatsAction(
         return $this->renderTemplate('jecoute/stats.html.twig', ['data' => $provider->getStatsBySurvey($survey)]);
     }
 
-    #[Entity('survey', expr: 'repository.findOneByUuid(uuid)')]
+    #[IsGranted(new Expression("is_granted('IS_AUTHOR_OF', subject) or is_granted('IS_SURVEY_MANAGER_OF', subject)"), subject: 'survey')]
     #[Route(path: '/questionnaire/{uuid}/dupliquer', name: 'local_survey_duplicate', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
-    #[Security("is_granted('IS_AUTHOR_OF', survey) or is_granted('IS_SURVEY_MANAGER_OF', survey)")]
     public function jecouteSurveyDuplicateAction(
-        Request $request,
+        #[MapEntity(expr: 'repository.findOneByUuid(uuid)')]
         LocalSurvey $survey,
         ObjectManager $manager,
     ): Response {
@@ -193,8 +193,8 @@ public function jecouteSurveyDuplicateAction(
         return $this->redirectToJecouteRoute('local_surveys_list');
     }
 
+    #[IsGranted(new Expression("is_granted('IS_AUTHOR_OF', subject.getSurvey()) or is_granted('IS_SURVEY_MANAGER_OF', subject.getSurvey()) or subject.getSurvey().isNational()"), subject: 'surveyQuestion')]
     #[Route(path: '/question/{uuid}/reponses', name: 'survey_stats_answers_list', condition: 'request.isXmlHttpRequest()')]
-    #[Security("is_granted('IS_AUTHOR_OF', surveyQuestion.getSurvey()) or is_granted('IS_SURVEY_MANAGER_OF', surveyQuestion.getSurvey()) or surveyQuestion.getSurvey().isNational()")]
     public function jecouteSurveyAnswersListAction(
         SurveyQuestion $surveyQuestion,
         DataAnswerRepository $dataAnswerRepository,
@@ -229,7 +229,7 @@ protected function renderTemplate(string $template, array $parameters = []): Res
 
     protected function redirectToJecouteRoute(string $subName, array $parameters = []): Response
     {
-        return $this->redirectToRoute("app_jecoute_{$this->getSpaceName()}_{$subName}", $parameters);
+        return $this->redirectToRoute("app_jecoute_{$this->getSpaceName()}_$subName", $parameters);
     }
 
     protected function checkCreateAccess(): void
diff --git a/src/Controller/EnMarche/Jecoute/JecouteCandidateController.php b/src/Controller/EnMarche/Jecoute/JecouteCandidateController.php
index 158c43d32e1..cffb7bcb6a7 100644
--- a/src/Controller/EnMarche/Jecoute/JecouteCandidateController.php
+++ b/src/Controller/EnMarche/Jecoute/JecouteCandidateController.php
@@ -4,11 +4,12 @@
 
 use App\Entity\Adherent;
 use App\Jecoute\JecouteSpaceEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_JECOUTE'))"))]
 #[Route(path: '/espace-candidat/questionnaires', name: 'app_jecoute_candidate_')]
-#[Security("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_JECOUTE'))")]
 class JecouteCandidateController extends AbstractJecouteController
 {
     protected function getSpaceName(): string
diff --git a/src/Controller/EnMarche/Jecoute/JecouteManagerController.php b/src/Controller/EnMarche/Jecoute/JecouteManagerController.php
index 21c0bb14fb0..a92bf78e5bc 100644
--- a/src/Controller/EnMarche/Jecoute/JecouteManagerController.php
+++ b/src/Controller/EnMarche/Jecoute/JecouteManagerController.php
@@ -4,8 +4,8 @@
 
 use App\Entity\Adherent;
 use App\Jecoute\JecouteSpaceEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_JECOUTE_MANAGER')]
 #[Route(path: '/espace-responsable-des-questionnaires', name: 'app_jecoute_manager_')]
diff --git a/src/Controller/EnMarche/Jecoute/News/AbstractNewsController.php b/src/Controller/EnMarche/Jecoute/News/AbstractNewsController.php
index 4d8b9fd38b8..73f8872ad6e 100644
--- a/src/Controller/EnMarche/Jecoute/News/AbstractNewsController.php
+++ b/src/Controller/EnMarche/Jecoute/News/AbstractNewsController.php
@@ -10,12 +10,13 @@
 use App\Repository\Geo\ZoneRepository;
 use App\Repository\Jecoute\NewsRepository;
 use Doctrine\ORM\EntityManagerInterface as ObjectManager;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 abstract class AbstractNewsController extends AbstractController
 {
@@ -76,8 +77,8 @@ public function jecouteNewsCreateAction(Request $request, ObjectManager $manager
         ]);
     }
 
+    #[IsGranted(new Expression("is_granted('IS_AUTHOR_OF', subject) or is_granted('CAN_EDIT_CANDIDATE_JECOUTE_NEWS', subject) or is_granted('CAN_EDIT_REFERENT_JECOUTE_NEWS', subject)"), subject: 'news')]
     #[Route(path: '/{uuid}/editer', name: 'news_edit', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET|POST'])]
-    #[Security("is_granted('IS_AUTHOR_OF', news) or is_granted('CAN_EDIT_CANDIDATE_JECOUTE_NEWS', news) or is_granted('CAN_EDIT_REFERENT_JECOUTE_NEWS', news)")]
     public function jecouteNewsEditAction(Request $request, News $news, ObjectManager $manager): Response
     {
         $zones = $this->getZones($this->getMainUser($request->getSession()));
@@ -105,8 +106,8 @@ public function jecouteNewsEditAction(Request $request, News $news, ObjectManage
         ]);
     }
 
+    #[IsGranted(new Expression("is_granted('IS_AUTHOR_OF', subject) or is_granted('IS_ALLOWED_TO_PUBLISH_JECOUTE_NEWS', subject)"), subject: 'news')]
     #[Route(path: '/{uuid}/publier', name: 'news_publish', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
-    #[Security("is_granted('IS_AUTHOR_OF', news) or is_granted('IS_ALLOWED_TO_PUBLISH_JECOUTE_NEWS', news)")]
     public function jecouteNewsPublishAction(Request $request, News $news, NewsHandler $handler): Response
     {
         if ($news->isPublished()) {
@@ -120,8 +121,8 @@ public function jecouteNewsPublishAction(Request $request, News $news, NewsHandl
         return $this->redirectToNewsRoute('news_list');
     }
 
+    #[IsGranted(new Expression("is_granted('IS_AUTHOR_OF', subject) or is_granted('IS_ALLOWED_TO_PUBLISH_JECOUTE_NEWS', subject)"), subject: 'news')]
     #[Route(path: '/{uuid}/depublier', name: 'news_unpublish', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
-    #[Security("is_granted('IS_AUTHOR_OF', news) or is_granted('IS_ALLOWED_TO_PUBLISH_JECOUTE_NEWS', news)")]
     public function jecouteNewsUnpublishAction(Request $request, News $news, NewsHandler $handler): Response
     {
         if (!$news->isPublished()) {
@@ -160,6 +161,6 @@ protected function renderTemplate(string $template, array $parameters = []): Res
 
     protected function redirectToNewsRoute(string $subName, array $parameters = []): Response
     {
-        return $this->redirectToRoute("app_jecoute_news_{$this->getSpaceName()}_{$subName}", $parameters);
+        return $this->redirectToRoute("app_jecoute_news_{$this->getSpaceName()}_$subName", $parameters);
     }
 }
diff --git a/src/Controller/EnMarche/Jecoute/News/NewsCandidateController.php b/src/Controller/EnMarche/Jecoute/News/NewsCandidateController.php
index be7354d2270..e5fc57404b1 100644
--- a/src/Controller/EnMarche/Jecoute/News/NewsCandidateController.php
+++ b/src/Controller/EnMarche/Jecoute/News/NewsCandidateController.php
@@ -4,11 +4,12 @@
 
 use App\Entity\Adherent;
 use App\Jecoute\JecouteSpaceEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_JECOUTE_NEWS') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_JECOUTE_NEWS'))"))]
 #[Route(path: '/espace-candidat/actualites', name: 'app_jecoute_news_candidate_')]
-#[Security("is_granted('ROLE_JECOUTE_NEWS') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_JECOUTE_NEWS'))")]
 class NewsCandidateController extends AbstractNewsController
 {
     protected function getSpaceName(): string
diff --git a/src/Controller/EnMarche/Jecoute/Personalization/JecouteCandidateRegionController.php b/src/Controller/EnMarche/Jecoute/Personalization/JecouteCandidateRegionController.php
index 1352c62c6ea..cd8fbb9f3f6 100644
--- a/src/Controller/EnMarche/Jecoute/Personalization/JecouteCandidateRegionController.php
+++ b/src/Controller/EnMarche/Jecoute/Personalization/JecouteCandidateRegionController.php
@@ -4,11 +4,12 @@
 
 use App\Entity\Adherent;
 use App\Jecoute\JecouteSpaceEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_JECOUTE_REGION') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_JECOUTE_REGION'))"))]
 #[Route(path: '/espace-candidat/campagne', name: 'app_jecoute_candidate_region_')]
-#[Security("is_granted('ROLE_JECOUTE_REGION') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_JECOUTE_REGION'))")]
 class JecouteCandidateRegionController extends AbstractPersonalizationController
 {
     protected function getSpaceName(): string
diff --git a/src/Controller/EnMarche/LegacyController.php b/src/Controller/EnMarche/LegacyController.php
deleted file mode 100644
index cda4d4c299d..00000000000
--- a/src/Controller/EnMarche/LegacyController.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-namespace App\Controller\EnMarche;
-
-use App\Entity\Committee;
-use App\Entity\Event\CommitteeEvent;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
-use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\Routing\Attribute\Route;
-
-class LegacyController extends AbstractController
-{
-    #[Entity('event', expr: 'repository.find(id)')]
-    #[Route(path: '/espaceperso/evenement/{id}-{slug}', requirements: ['id' => '\d+'], methods: ['GET'])]
-    public function redirectEventAction(CommitteeEvent $event): Response
-    {
-        return $this->redirectToRoute('app_committee_event_show', [
-            'slug' => $event->getSlug(),
-        ], Response::HTTP_MOVED_PERMANENTLY);
-    }
-
-    #[Entity('committee', expr: 'repository.find(id)')]
-    #[Route(path: '/espaceperso/comite/{id}-{slug}', requirements: ['id' => '\d+'], methods: ['GET'])]
-    public function redirectCommitteeAction(Committee $committee): Response
-    {
-        return $this->redirectToRoute('app_committee_show', [
-            'slug' => $committee->getSlug(),
-        ], Response::HTTP_MOVED_PERMANENTLY);
-    }
-}
diff --git a/src/Controller/EnMarche/ManagedUsers/CandidateManagedUsersController.php b/src/Controller/EnMarche/ManagedUsers/CandidateManagedUsersController.php
index 8d149174a31..7789ae3a56c 100644
--- a/src/Controller/EnMarche/ManagedUsers/CandidateManagedUsersController.php
+++ b/src/Controller/EnMarche/ManagedUsers/CandidateManagedUsersController.php
@@ -7,13 +7,14 @@
 use App\Form\ManagedUsers\CandidateManagedUsersFilterType;
 use App\ManagedUsers\ManagedUsersFilter;
 use App\Scope\ScopeEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_ADHERENTS'))"))]
 #[Route(path: '/espace-candidat', name: 'app_candidate_managed_users_', methods: ['GET'])]
-#[Security("is_granted('ROLE_CANDIDATE') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_ADHERENTS'))")]
 class CandidateManagedUsersController extends AbstractManagedUsersController
 {
     protected function getSpaceType(): string
diff --git a/src/Controller/EnMarche/ManagedUsers/DeputyManagedUsersController.php b/src/Controller/EnMarche/ManagedUsers/DeputyManagedUsersController.php
index 48c887d7f55..e0f362b4add 100644
--- a/src/Controller/EnMarche/ManagedUsers/DeputyManagedUsersController.php
+++ b/src/Controller/EnMarche/ManagedUsers/DeputyManagedUsersController.php
@@ -4,11 +4,12 @@
 
 use App\AdherentSpace\AdherentSpaceEnum;
 use App\Scope\ScopeEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_DEPUTY') or (is_granted('ROLE_DELEGATED_DEPUTY') and is_granted('HAS_DELEGATED_ACCESS_ADHERENTS'))"))]
 #[Route(path: '/espace-depute', name: 'app_deputy_managed_users_', methods: ['GET'])]
-#[Security("is_granted('ROLE_DEPUTY') or (is_granted('ROLE_DELEGATED_DEPUTY') and is_granted('HAS_DELEGATED_ACCESS_ADHERENTS'))")]
 class DeputyManagedUsersController extends AbstractManagedUsersController
 {
     protected function getSpaceType(): string
diff --git a/src/Controller/EnMarche/Poll/AbstractPollController.php b/src/Controller/EnMarche/Poll/AbstractPollController.php
index b3e3f661c5f..f64b8219941 100644
--- a/src/Controller/EnMarche/Poll/AbstractPollController.php
+++ b/src/Controller/EnMarche/Poll/AbstractPollController.php
@@ -11,11 +11,12 @@
 use App\Repository\Geo\ZoneRepository;
 use App\Repository\Poll\LocalPollRepository;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 abstract class AbstractPollController extends AbstractController
 {
@@ -72,8 +73,8 @@ public function createLocalPoll(Request $request, PollManager $pollManager): Res
         ]);
     }
 
+    #[IsGranted(new Expression("is_granted('CAN_EDIT_CANDIDATE_LOCAL_POLL', subject) or is_granted('CAN_EDIT_REFERENT_LOCAL_POLL', subject)"), subject: 'localPoll')]
     #[Route(path: '/{uuid}/editer', name: 'local_edit', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET|POST'])]
-    #[Security("is_granted('CAN_EDIT_CANDIDATE_LOCAL_POLL', localPoll) or is_granted('CAN_EDIT_REFERENT_LOCAL_POLL', localPoll)")]
     public function editLocalPoll(
         Request $request,
         LocalPoll $localPoll,
@@ -109,9 +110,9 @@ public function editLocalPoll(
         ]);
     }
 
+    #[IsGranted(new Expression("is_granted('CAN_EDIT_CANDIDATE_LOCAL_POLL', subject) or is_granted('CAN_EDIT_REFERENT_LOCAL_POLL', subject)"), subject: 'poll')]
     #[Route(path: '/{uuid}/depublier', name: 'unpublish', methods: ['GET'], defaults: ['publish' => false])]
     #[Route(path: '/{uuid}/publier', name: 'publish', methods: ['GET'], defaults: ['publish' => true])]
-    #[Security("is_granted('CAN_EDIT_CANDIDATE_LOCAL_POLL', poll) or is_granted('CAN_EDIT_REFERENT_LOCAL_POLL', poll)")]
     public function togglePublish(bool $publish, Poll $poll, PollManager $pollManager): Response
     {
         if (!($publish xor $poll->isPublished())) {
diff --git a/src/Controller/EnMarche/Poll/PollCandidateController.php b/src/Controller/EnMarche/Poll/PollCandidateController.php
index 041ce57ba83..af143ab758a 100644
--- a/src/Controller/EnMarche/Poll/PollCandidateController.php
+++ b/src/Controller/EnMarche/Poll/PollCandidateController.php
@@ -4,11 +4,12 @@
 
 use App\AdherentSpace\AdherentSpaceEnum;
 use App\Entity\Adherent;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression("is_granted('ROLE_CANDIDATE_REGIONAL_HEADED') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_POLLS'))"))]
 #[Route(path: '/espace-candidat/question-du-jour', name: 'app_candidate_polls_')]
-#[Security("is_granted('ROLE_CANDIDATE_REGIONAL_HEADED') or (is_granted('ROLE_DELEGATED_CANDIDATE') and is_granted('HAS_DELEGATED_ACCESS_POLLS'))")]
 class PollCandidateController extends AbstractPollController
 {
     protected function getSpaceName(): string
diff --git a/src/Controller/EnMarche/ReportController.php b/src/Controller/EnMarche/ReportController.php
index d79a9713f72..cd2f7f596b7 100644
--- a/src/Controller/EnMarche/ReportController.php
+++ b/src/Controller/EnMarche/ReportController.php
@@ -7,7 +7,6 @@
 use App\Report\ReportCreationCommandHandler;
 use App\Report\ReportManager;
 use App\Report\ReportType;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
@@ -15,6 +14,7 @@
 use Symfony\Component\Routing\Exception\MethodNotAllowedException;
 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
 use Symfony\Component\Routing\RouterInterface;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 class ReportController extends AbstractController
 {
diff --git a/src/Controller/EnMarche/UserController.php b/src/Controller/EnMarche/UserController.php
index 0914d146880..97b7acdd908 100644
--- a/src/Controller/EnMarche/UserController.php
+++ b/src/Controller/EnMarche/UserController.php
@@ -17,7 +17,6 @@
 use App\Membership\MembershipRequestHandler;
 use App\OAuth\App\AuthAppUrlManager;
 use Doctrine\ORM\EntityManagerInterface as ObjectManager;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Bundle\SecurityBundle\Security as CoreSecurity;
 use Symfony\Component\HttpFoundation\JsonResponse;
@@ -26,6 +25,7 @@
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\Routing\Attribute\Route;
 use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[Route(path: '/parametres/mon-compte')]
 class UserController extends AbstractController
@@ -105,8 +105,8 @@ public function changePasswordAction(
         );
     }
 
+    #[IsGranted('UNREGISTER', subject: 'user')]
     #[Route(path: '/desadherer', name: 'app_user_terminate_membership', methods: ['GET', 'POST'])]
-    #[Security("is_granted('UNREGISTER', user)")]
     public function terminateMembershipAction(
         Request $request,
         MembershipRequestHandler $handler,
diff --git a/src/Controller/EnMarche/VotingPlatform/ResultsController.php b/src/Controller/EnMarche/VotingPlatform/ResultsController.php
index b5f1825ce43..715497db40a 100644
--- a/src/Controller/EnMarche/VotingPlatform/ResultsController.php
+++ b/src/Controller/EnMarche/VotingPlatform/ResultsController.php
@@ -5,18 +5,18 @@
 use App\Entity\VotingPlatform\Election;
 use App\Entity\VotingPlatform\ElectionRound;
 use App\Security\Voter\VotingPlatformAbleToVoteVoter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted(VotingPlatformAbleToVoteVoter::PERMISSION_RESULTS, subject: 'election')]
-#[ParamConverter('electionRound', options: ['mapping' => ['election_round_uuid' => 'uuid']])]
 #[Route(path: '/resultats/{election_round_uuid}', name: 'app_voting_platform_results', defaults: ['election_round_uuid' => null], methods: ['GET'])]
 class ResultsController extends AbstractController
 {
     public function __invoke(
         Election $election,
+        #[MapEntity(mapping: ['election_round_uuid' => 'uuid'])]
         ?ElectionRound $electionRound = null,
     ): Response {
         if (!$election->isResultsDisplayable() || !$election->isResultPeriodActive()) {
diff --git a/src/Controller/OAuth/OAuthServerController.php b/src/Controller/OAuth/OAuthServerController.php
index 351502512ca..9792f1a5eb9 100644
--- a/src/Controller/OAuth/OAuthServerController.php
+++ b/src/Controller/OAuth/OAuthServerController.php
@@ -15,11 +15,12 @@
 use Nyholm\Psr7\Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Ramsey\Uuid\Uuid;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 class OAuthServerController extends AbstractController
 {
@@ -29,8 +30,8 @@ public function __construct(
     ) {
     }
 
+    #[IsGranted(new Expression("is_granted('IS_AUTHENTICATED_REMEMBERED') and not is_granted('ROLE_ADMIN_DASHBOARD')"))]
     #[Route(path: '/auth', name: 'app_front_oauth_authorize', methods: ['GET', 'POST'])]
-    #[Security("is_granted('IS_AUTHENTICATED_REMEMBERED') and not is_granted('ROLE_ADMIN_DASHBOARD')")]
     public function authorizeAction(Request $request, ClientRepository $repository, OAuthAuthorizationManager $manager)
     {
         try {
diff --git a/src/Controller/OAuth/SsoController.php b/src/Controller/OAuth/SsoController.php
index e4f406d9511..fff5b73fd02 100644
--- a/src/Controller/OAuth/SsoController.php
+++ b/src/Controller/OAuth/SsoController.php
@@ -6,11 +6,11 @@
 use App\Entity\OAuth\Client;
 use App\OAuth\JWTTokenGenerator;
 use App\Security\Voter\OAuthClientVoter;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_USER')]
 #[Route(path: '/sso/{uuid}', name: 'app_front_oauth_sso', requirements: ['uuid' => '%pattern_uuid%'], methods: ['GET'])]
diff --git a/src/Controller/Renaissance/Adherent/Contribution/FillInformationsController.php b/src/Controller/Renaissance/Adherent/Contribution/FillInformationsController.php
index ed8743f2334..b520daac523 100644
--- a/src/Controller/Renaissance/Adherent/Contribution/FillInformationsController.php
+++ b/src/Controller/Renaissance/Adherent/Contribution/FillInformationsController.php
@@ -5,10 +5,10 @@
 use App\Adherent\AdherentRoleEnum;
 use App\Adherent\Contribution\ContributionRequestHandler;
 use App\Form\Renaissance\Adherent\Contribution\InformationsType;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted(AdherentRoleEnum::ONGOING_ELECTED_REPRESENTATIVE)]
 #[Route(path: '/espace-elus/cotisation/informations', name: 'app_renaissance_contribution_fill_informations', methods: ['GET|POST'])]
diff --git a/src/Controller/Renaissance/Adherent/Contribution/FillRevenueController.php b/src/Controller/Renaissance/Adherent/Contribution/FillRevenueController.php
index 90b28b4127e..440b5e2368f 100644
--- a/src/Controller/Renaissance/Adherent/Contribution/FillRevenueController.php
+++ b/src/Controller/Renaissance/Adherent/Contribution/FillRevenueController.php
@@ -9,11 +9,11 @@
 use App\Entity\Adherent;
 use App\Form\Renaissance\Adherent\Contribution\RevenueType;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Messenger\MessageBusInterface;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted(AdherentRoleEnum::ONGOING_ELECTED_REPRESENTATIVE)]
 #[Route(path: '/espace-elus/cotisation', name: 'app_renaissance_contribution_fill_revenue', methods: ['GET|POST'])]
diff --git a/src/Controller/Renaissance/Adherent/Contribution/SeeAmountController.php b/src/Controller/Renaissance/Adherent/Contribution/SeeAmountController.php
index 8f58a79138d..fb9dcb1429a 100644
--- a/src/Controller/Renaissance/Adherent/Contribution/SeeAmountController.php
+++ b/src/Controller/Renaissance/Adherent/Contribution/SeeAmountController.php
@@ -3,10 +3,10 @@
 namespace App\Controller\Renaissance\Adherent\Contribution;
 
 use App\Adherent\AdherentRoleEnum;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted(AdherentRoleEnum::ONGOING_ELECTED_REPRESENTATIVE)]
 #[Route(path: '/espace-elus/cotisation/montant', name: 'app_renaissance_contribution_see_amount', methods: ['GET'])]
diff --git a/src/Controller/Renaissance/Adherent/EventController.php b/src/Controller/Renaissance/Adherent/EventController.php
index 3554b87fcfa..3e7aa2a4869 100644
--- a/src/Controller/Renaissance/Adherent/EventController.php
+++ b/src/Controller/Renaissance/Adherent/EventController.php
@@ -19,14 +19,14 @@
 use App\Repository\Event\BaseEventRepository;
 use App\Repository\EventRegistrationRepository;
 use App\Serializer\Encoder\ICalEncoder;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\ResponseHeaderBag;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\Serializer\SerializerInterface;
 use Symfony\Component\Validator\Validator\ValidatorInterface;
 use Symfony\Contracts\Translation\TranslatorInterface;
@@ -91,9 +91,9 @@ public function myEventsListAction(Request $request): Response
         ]);
     }
 
-    #[Entity('event', expr: 'repository.findOneActiveBySlug(slug)')]
     #[Route(path: '/{slug}/inscription', name: '_registration', methods: ['GET'])]
     public function registrationAction(
+        #[MapEntity(expr: 'repository.findOneActiveBySlug(slug)')]
         BaseEvent $event,
         ValidatorInterface $validator,
         EventRegistrationCommandHandler $eventRegistrationCommandHandler,
@@ -135,10 +135,13 @@ public function registrationAction(
         return $this->redirectToRoute('app_renaissance_event_show', ['slug' => $event->getSlug()]);
     }
 
-    #[Entity('event', expr: 'repository.findOnePublishedBySlug(slug)')]
     #[Route(path: '/{slug}/invitation', name: '_invitation', methods: ['GET', 'POST'])]
-    public function invitationAction(Request $request, BaseEvent $event, EventInvitationHandler $handler): Response
-    {
+    public function invitationAction(
+        Request $request,
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
+        BaseEvent $event,
+        EventInvitationHandler $handler,
+    ): Response {
         $eventInvitation = EventInvitation::createFromAdherent(
             $this->getUser(),
             $request->request->get('frc-captcha-solution')
@@ -166,10 +169,11 @@ public function invitationAction(Request $request, BaseEvent $event, EventInvita
         ]);
     }
 
-    #[Entity('event', expr: 'repository.findOnePublishedBySlug(slug)')]
     #[Route(path: '/{slug}/desinscription', name: '_unregistration', methods: ['GET'])]
-    public function unregistrationAction(BaseEvent $event): Response
-    {
+    public function unregistrationAction(
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
+        BaseEvent $event,
+    ): Response {
         if (!$adherentEventRegistration = $this->manager->searchRegistration($event, $this->getUser()->getEmailAddress(), null)) {
             throw $this->createNotFoundException('Impossible de se désinscrire à cet évévenement. Inscription non trouvée.');
         }
@@ -181,10 +185,12 @@ public function unregistrationAction(BaseEvent $event): Response
         return $this->redirectToRoute('app_renaissance_event_show', ['slug' => $event->getSlug()]);
     }
 
-    #[Entity('event', expr: 'repository.findOnePublishedBySlug(slug)')]
     #[Route(path: '/{slug}/ical', name: '_ical', methods: ['GET'])]
-    public function icalAction(BaseEvent $event, SerializerInterface $serializer): Response
-    {
+    public function icalAction(
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
+        BaseEvent $event,
+        SerializerInterface $serializer,
+    ): Response {
         $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT.'; filename='.$event->getSlug().'.ics';
 
         $response = new Response($serializer->serialize($event, ICalEncoder::FORMAT), Response::HTTP_OK);
@@ -194,10 +200,10 @@ public function icalAction(BaseEvent $event, SerializerInterface $serializer): R
         return $response;
     }
 
-    #[Entity('event', expr: 'repository.findOnePublishedBySlug(slug)')]
     #[Route(path: '/{slug}/contact', name: '_contact_organizer', methods: ['GET', 'POST'])]
     public function contactAction(
         Request $request,
+        #[MapEntity(expr: 'repository.findOnePublishedBySlug(slug)')]
         BaseEvent $event,
         ContactMessageHandler $handler,
     ): Response {
diff --git a/src/Controller/Renaissance/CommitteeElection/ViewCandidaciesListsController.php b/src/Controller/Renaissance/CommitteeElection/ViewCandidaciesListsController.php
index 1272c1be518..9c6cabf4629 100644
--- a/src/Controller/Renaissance/CommitteeElection/ViewCandidaciesListsController.php
+++ b/src/Controller/Renaissance/CommitteeElection/ViewCandidaciesListsController.php
@@ -3,10 +3,10 @@
 namespace App\Controller\Renaissance\CommitteeElection;
 
 use App\Entity\Committee;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('IS_VOTER_IN_COMMITTEE', subject: 'committee')]
 #[Route('/comites/{uuid}/listes-candidats', name: 'app_renaissance_committee_election_candidacies_lists_view', methods: ['GET'])]
diff --git a/src/Controller/Renaissance/Consultation/ListController.php b/src/Controller/Renaissance/Consultation/ListController.php
index f855b8c7bee..046cc700b82 100644
--- a/src/Controller/Renaissance/Consultation/ListController.php
+++ b/src/Controller/Renaissance/Consultation/ListController.php
@@ -3,10 +3,10 @@
 namespace App\Controller\Renaissance\Consultation;
 
 use App\Repository\ConsultationRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('RENAISSANCE_ADHERENT')]
 #[Route(path: '/espace-adherent/consultations', name: 'app_renaissance_consultation_list', methods: ['GET'])]
diff --git a/src/Controller/Renaissance/Consultation/ShowController.php b/src/Controller/Renaissance/Consultation/ShowController.php
index 02c6c026d18..daeba7ba82f 100644
--- a/src/Controller/Renaissance/Consultation/ShowController.php
+++ b/src/Controller/Renaissance/Consultation/ShowController.php
@@ -3,19 +3,20 @@
 namespace App\Controller\Renaissance\Consultation;
 
 use App\Entity\Consultation;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Entity('consultation', expr: 'repository.findOnePublished(uuid)')]
 #[IsGranted('RENAISSANCE_ADHERENT')]
 #[Route(path: '/espace-adherent/consultations/{uuid}', name: 'app_renaissance_consultation_show', methods: ['GET'])]
 class ShowController extends AbstractController
 {
-    public function __invoke(Consultation $consultation): Response
-    {
+    public function __invoke(
+        #[MapEntity(expr: 'repository.findOnePublished(uuid)')]
+        Consultation $consultation,
+    ): Response {
         return $this->render('renaissance/consultation/show.html.twig', [
             'consultation' => $consultation,
         ]);
diff --git a/src/Controller/Renaissance/DepartmentSite/DepartmentSiteController.php b/src/Controller/Renaissance/DepartmentSite/DepartmentSiteController.php
index 0c4c922b6ea..48d4e8e38b7 100644
--- a/src/Controller/Renaissance/DepartmentSite/DepartmentSiteController.php
+++ b/src/Controller/Renaissance/DepartmentSite/DepartmentSiteController.php
@@ -4,7 +4,7 @@
 
 use App\Entity\DepartmentSite\DepartmentSite;
 use App\Repository\Geo\ZoneRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
@@ -20,10 +20,11 @@ public function departmentSiteListAction(ZoneRepository $zoneRepository): Respon
         ]);
     }
 
-    #[Entity('departmentSite', expr: 'repository.findOneBySlug(slug)')]
     #[Route(path: '/{slug}', name: 'view')]
-    public function departmentSiteAction(DepartmentSite $departmentSite): Response
-    {
+    public function departmentSiteAction(
+        #[MapEntity(expr: 'repository.findOneBySlug(slug)')]
+        DepartmentSite $departmentSite,
+    ): Response {
         return $this->render('renaissance/department_site/department_site.html.twig', [
             'department_site' => $departmentSite,
         ]);
diff --git a/src/Controller/Renaissance/Election/DepartmentElectionController.php b/src/Controller/Renaissance/Election/DepartmentElectionController.php
index c35783e6659..f7f4607f507 100644
--- a/src/Controller/Renaissance/Election/DepartmentElectionController.php
+++ b/src/Controller/Renaissance/Election/DepartmentElectionController.php
@@ -4,10 +4,10 @@
 
 use App\Entity\VotingPlatform\Designation\Designation;
 use App\Repository\LocalElection\LocalElectionRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('RENAISSANCE_ADHERENT')]
 #[Route(path: '/elections-departementales/{uuid}')]
diff --git a/src/Controller/Renaissance/Election/LocalPollElectionController.php b/src/Controller/Renaissance/Election/LocalPollElectionController.php
index 1abc8a5960d..261495854b4 100644
--- a/src/Controller/Renaissance/Election/LocalPollElectionController.php
+++ b/src/Controller/Renaissance/Election/LocalPollElectionController.php
@@ -5,11 +5,11 @@
 use App\Entity\VotingPlatform\Designation\Designation;
 use App\VotingPlatform\Designation\DesignationTypeEnum;
 use App\VotingPlatform\ElectionManager;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('ROLE_USER')]
 #[Route(path: '/election-locale', name: 'app_renaissance_local_election_home', methods: 'GET')]
diff --git a/src/Controller/Renaissance/Election/SasElectionController.php b/src/Controller/Renaissance/Election/SasElectionController.php
index 40964572fa4..f838b208b2f 100644
--- a/src/Controller/Renaissance/Election/SasElectionController.php
+++ b/src/Controller/Renaissance/Election/SasElectionController.php
@@ -6,12 +6,12 @@
 use App\Repository\VotingPlatform\ElectionRepository;
 use App\Repository\VotingPlatform\VoteResultRepository;
 use App\Security\Http\Session\AnonymousFollowerSession;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Sonata\Exporter\Exporter;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Component\String\Slugger\AsciiSlugger;
 
 #[IsGranted('ROLE_USER')]
diff --git a/src/Controller/Renaissance/Formation/ContentController.php b/src/Controller/Renaissance/Formation/ContentController.php
index 0d6c080baa4..8f7ee0471a8 100644
--- a/src/Controller/Renaissance/Formation/ContentController.php
+++ b/src/Controller/Renaissance/Formation/ContentController.php
@@ -8,13 +8,12 @@
 use Cocur\Slugify\Slugify;
 use Doctrine\ORM\EntityManagerInterface;
 use League\Flysystem\FilesystemOperator;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
-#[Entity('formation', expr: 'repository.findOnePublished(uuid)')]
 #[IsGranted('RENAISSANCE_ADHERENT')]
 #[Route(path: '/espace-adherent/formations/{uuid}/contenu', name: 'app_renaissance_adherent_formation_content', methods: ['GET'])]
 class ContentController extends AbstractController
@@ -25,8 +24,10 @@ public function __construct(
     ) {
     }
 
-    public function __invoke(Formation $formation): Response
-    {
+    public function __invoke(
+        #[MapEntity(expr: 'repository.findOnePublished(uuid)')]
+        Formation $formation,
+    ): Response {
         /** @var Adherent $adherent */
         $adherent = $this->getUser();
 
diff --git a/src/Controller/Renaissance/Formation/ListController.php b/src/Controller/Renaissance/Formation/ListController.php
index 566e7ebfe6f..9714431447b 100644
--- a/src/Controller/Renaissance/Formation/ListController.php
+++ b/src/Controller/Renaissance/Formation/ListController.php
@@ -4,10 +4,10 @@
 
 use App\Entity\Adherent;
 use App\Repository\AdherentFormation\FormationRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
 #[IsGranted('RENAISSANCE_ADHERENT')]
 #[Route(path: '/espace-adherent/formations', name: 'app_renaissance_adherent_formation_list', methods: ['GET'])]
diff --git a/src/Controller/Renaissance/MyCommittee/SaveCommitteeUpdateController.php b/src/Controller/Renaissance/MyCommittee/SaveCommitteeUpdateController.php
index c6667fe4390..5642eca0bac 100644
--- a/src/Controller/Renaissance/MyCommittee/SaveCommitteeUpdateController.php
+++ b/src/Controller/Renaissance/MyCommittee/SaveCommitteeUpdateController.php
@@ -8,13 +8,14 @@
 use App\Entity\Committee;
 use App\Geo\ManagedZoneProvider;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression('is_granted("ABLE_TO_CHANGE_COMMITTEE") or is_granted("IS_IMPERSONATOR")'))]
 #[Route(path: '/espace-adherent/mon-comite-local/modifier/{uuid}', name: 'app_my_committee_update', methods: ['GET'])]
-#[Security('is_granted("ABLE_TO_CHANGE_COMMITTEE") or is_granted("IS_IMPERSONATOR")')]
 class SaveCommitteeUpdateController extends AbstractController
 {
     public function __invoke(
diff --git a/src/Controller/Renaissance/MyCommittee/ShowCommitteeListController.php b/src/Controller/Renaissance/MyCommittee/ShowCommitteeListController.php
index 3025d8ed7f0..ceb46cf17b0 100644
--- a/src/Controller/Renaissance/MyCommittee/ShowCommitteeListController.php
+++ b/src/Controller/Renaissance/MyCommittee/ShowCommitteeListController.php
@@ -4,13 +4,14 @@
 
 use App\Entity\Adherent;
 use App\Repository\CommitteeRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 
+#[IsGranted(new Expression('is_granted("ABLE_TO_CHANGE_COMMITTEE") or is_granted("IS_IMPERSONATOR")'))]
 #[Route(path: '/espace-adherent/mon-comite-local/modifier', name: 'app_my_committee_show_list', methods: ['GET'])]
-#[Security('is_granted("ABLE_TO_CHANGE_COMMITTEE") or is_granted("IS_IMPERSONATOR")')]
 class ShowCommitteeListController extends AbstractController
 {
     public function __invoke(CommitteeRepository $committeeRepository): Response
diff --git a/src/Controller/Renaissance/Newsletter/ConfirmNewsletterController.php b/src/Controller/Renaissance/Newsletter/ConfirmNewsletterController.php
index 89891b865e0..360d5b2e652 100644
--- a/src/Controller/Renaissance/Newsletter/ConfirmNewsletterController.php
+++ b/src/Controller/Renaissance/Newsletter/ConfirmNewsletterController.php
@@ -7,15 +7,15 @@
 use App\Newsletter\NewsletterEvent;
 use App\Newsletter\NewsletterTypeEnum;
 use Doctrine\ORM\EntityManagerInterface;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
 
 class ConfirmNewsletterController extends AbstractController
 {
-    #[Entity('subscription', expr: 'repository.findOneByUuidAndToken(uuid, confirm_token)')]
     public function __invoke(
+        #[MapEntity(expr: 'repository.findOneByUuidAndToken(uuid, confirm_token)')]
         NewsletterSubscription $subscription,
         EntityManagerInterface $entityManager,
         EventDispatcherInterface $eventDispatcher,
diff --git a/src/Controller/Renaissance/SecurityController.php b/src/Controller/Renaissance/SecurityController.php
index 213df7ebc64..473dfa5c3d8 100644
--- a/src/Controller/Renaissance/SecurityController.php
+++ b/src/Controller/Renaissance/SecurityController.php
@@ -14,7 +14,7 @@
 use App\OAuth\App\AuthAppUrlManager;
 use App\OAuth\App\PlatformAuthUrlGenerator;
 use App\Repository\AdherentRepository;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\Form\Extension\Core\Type\EmailType;
 use Symfony\Component\HttpFoundation\Request;
@@ -86,12 +86,12 @@ public function retrieveForgotPasswordAction(
         ]);
     }
 
-    #[Entity('adherent', expr: 'repository.findOneByUuid(adherent_uuid)')]
-    #[Entity('resetPasswordToken', expr: 'repository.findByToken(reset_password_token)')]
     #[Route(path: '/changer-mot-de-passe/{adherent_uuid}/{reset_password_token}', name: 'app_adherent_reset_password', methods: ['GET', 'POST'])]
     public function resetPasswordAction(
         Request $request,
+        #[MapEntity(expr: 'repository.findOneByUuid(adherent_uuid)')]
         Adherent $adherent,
+        #[MapEntity(expr: 'repository.findByToken(reset_password_token)')]
         AdherentResetPasswordToken $resetPasswordToken,
         AdherentResetPasswordHandler $handler,
         AuthAppUrlManager $appUrlManager,
@@ -134,11 +134,11 @@ public function resetPasswordAction(
         ]);
     }
 
-    #[Entity('adherent', expr: 'repository.findOneByUuid(adherent_uuid)')]
-    #[Entity('token', expr: 'repository.findByToken(change_email_token)')]
     #[Route(path: '/valider-changement-email/{adherent_uuid}/{change_email_token}', name: 'user_validate_new_email', requirements: ['adherent_uuid' => '%pattern_uuid%', 'change_email_token' => '%pattern_sha1%'], methods: ['GET'])]
     public function activateNewEmailAction(
+        #[MapEntity(expr: 'repository.findOneByUuid(adherent_uuid)')]
         Adherent $adherent,
+        #[MapEntity(expr: 'repository.findByToken(change_email_token)')]
         AdherentChangeEmailToken $token,
         AdherentChangeEmailHandler $handler,
         TokenStorageInterface $tokenStorage,
diff --git a/src/Controller/UploadDocumentController.php b/src/Controller/UploadDocumentController.php
index 5a6c374fe67..e8b96c078e9 100644
--- a/src/Controller/UploadDocumentController.php
+++ b/src/Controller/UploadDocumentController.php
@@ -6,7 +6,6 @@
 use App\UserDocument\UserDocumentManager;
 use Gedmo\Sluggable\Util\Urlizer;
 use League\Flysystem\FilesystemException;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\HeaderUtils;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,6 +14,7 @@
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\Routing\Attribute\Route;
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\Contracts\Translation\TranslatorInterface;
 
 class UploadDocumentController extends AbstractController
diff --git a/src/Controller/Webhook/Telegram/ChatbotController.php b/src/Controller/Webhook/Telegram/ChatbotController.php
index 1ba632d8341..11094d915ef 100644
--- a/src/Controller/Webhook/Telegram/ChatbotController.php
+++ b/src/Controller/Webhook/Telegram/ChatbotController.php
@@ -5,7 +5,7 @@
 use App\Chatbot\Telegram\ConversationManager;
 use App\Controller\CanaryControllerTrait;
 use App\Entity\Chatbot\Chatbot;
-use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
+use Symfony\Bridge\Doctrine\Attribute\MapEntity;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
@@ -14,7 +14,6 @@
 use TelegramBot\Api\BotApi;
 use TelegramBot\Api\Types\Update;
 
-#[Entity('chatbot', expr: 'repository.findOneEnabledBySecret(secret)')]
 #[Route('/telegram/chatbot/{secret}', name: self::ROUTE_NAME, methods: ['POST'])]
 class ChatbotController extends AbstractController
 {
@@ -26,8 +25,11 @@ public function __construct(private readonly ConversationManager $conversationMa
     {
     }
 
-    public function __invoke(Request $request, Chatbot $chatbot): Response
-    {
+    public function __invoke(
+        Request $request,
+        #[MapEntity(expr: 'repository.findOneEnabledBySecret(secret)')]
+        Chatbot $chatbot,
+    ): Response {
         $this->disableInProduction();
 
         if (!$content = $request->getContent()) {
diff --git a/src/Entity/Action/Action.php b/src/Entity/Action/Action.php
index 76f4037e6a6..c3ae8dd55f8 100644
--- a/src/Entity/Action/Action.php
+++ b/src/Entity/Action/Action.php
@@ -53,7 +53,7 @@
         ),
         new Put(
             uriTemplate: '/v3/actions/{uuid}',
-            security: 'object.getAuthor() == user or user.hasDelegatedFromUser(object.getAuthor(), \'actions\')'
+            security: "object.getAuthor() == user or user.hasDelegatedFromUser(object.getAuthor(), 'actions')"
         ),
         new Put(
             uriTemplate: '/v3/actions/{uuid}/cancel',
diff --git a/src/Entity/AdherentMessage/Segment/AudienceSegment.php b/src/Entity/AdherentMessage/Segment/AudienceSegment.php
index cfc70718bc1..6d8438afc25 100644
--- a/src/Entity/AdherentMessage/Segment/AudienceSegment.php
+++ b/src/Entity/AdherentMessage/Segment/AudienceSegment.php
@@ -26,21 +26,21 @@
         new Get(
             uriTemplate: '/v3/audience-segments/{uuid}',
             requirements: ['uuid' => '%pattern_uuid%'],
-            security: 'is_granted(\'ROLE_MESSAGE_REDACTOR\') and (object.getAuthor() == user or user.hasDelegatedFromUser(object.getAuthor(), \'messages\'))'
+            security: "is_granted('ROLE_MESSAGE_REDACTOR') and (object.getAuthor() == user or user.hasDelegatedFromUser(object.getAuthor(), 'messages'))"
         ),
         new Put(
             uriTemplate: '/v3/audience-segments/{uuid}',
             requirements: ['uuid' => '%pattern_uuid%'],
-            security: 'is_granted(\'ROLE_MESSAGE_REDACTOR\') and (object.getAuthor() == user or user.hasDelegatedFromUser(object.getAuthor(), \'messages\'))'
+            security: "is_granted('ROLE_MESSAGE_REDACTOR') and (object.getAuthor() == user or user.hasDelegatedFromUser(object.getAuthor(), 'messages'))"
         ),
         new Delete(
             uriTemplate: '/v3/audience-segments/{uuid}',
             requirements: ['uuid' => '%pattern_uuid%'],
-            security: 'is_granted(\'ROLE_MESSAGE_REDACTOR\') and (object.getAuthor() == user or user.hasDelegatedFromUser(object.getAuthor(), \'messages\'))'
+            security: "is_granted('ROLE_MESSAGE_REDACTOR') and (object.getAuthor() == user or user.hasDelegatedFromUser(object.getAuthor(), 'messages'))"
         ),
         new Post(
             uriTemplate: '/v3/audience-segments',
-            security: 'is_granted(\'ROLE_MESSAGE_REDACTOR\')'
+            security: "is_granted('ROLE_MESSAGE_REDACTOR')"
         ),
     ],
     normalizationContext: ['groups' => ['audience_segment_read']],
diff --git a/src/Entity/AdherentSegment.php b/src/Entity/AdherentSegment.php
index 22289264d5c..de36bb7ae5c 100644
--- a/src/Entity/AdherentSegment.php
+++ b/src/Entity/AdherentSegment.php
@@ -20,7 +20,7 @@
             uriTemplate: '/adherent-segments',
             normalizationContext: ['iri' => true, 'groups' => ['public']],
             denormalizationContext: ['groups' => ['write']],
-            security: 'is_granted(\'ROLE_MESSAGE_REDACTOR\')'
+            security: "is_granted('ROLE_MESSAGE_REDACTOR')"
         ),
     ]
 )]
diff --git a/src/Entity/Device.php b/src/Entity/Device.php
index 9edca461380..e00b14988d6 100644
--- a/src/Entity/Device.php
+++ b/src/Entity/Device.php
@@ -18,12 +18,12 @@
         new Get(
             uriTemplate: '/v3/device/{deviceUuid}',
             requirements: ['deviceUuid' => '[\w-]+'],
-            security: 'is_granted(\'ROLE_OAUTH_DEVICE\') and object.equals(user.getDevice())'
+            security: "is_granted('ROLE_OAUTH_DEVICE') and object.equals(user.getDevice())"
         ),
         new Put(
             uriTemplate: '/v3/device/{deviceUuid}',
             requirements: ['deviceUuid' => '[\w-]+'],
-            security: 'is_granted(\'ROLE_OAUTH_DEVICE\') and object.equals(user.getDevice())'
+            security: "is_granted('ROLE_OAUTH_DEVICE') and object.equals(user.getDevice())"
         ),
     ],
     normalizationContext: ['groups' => ['device_read']],
diff --git a/src/Entity/EntityIdentityTrait.php b/src/Entity/EntityIdentityTrait.php
index c76ce0f1530..e21f8ce62f1 100644
--- a/src/Entity/EntityIdentityTrait.php
+++ b/src/Entity/EntityIdentityTrait.php
@@ -2,7 +2,7 @@
 
 namespace App\Entity;
 
-use ApiPlatform\Core\Annotation\ApiProperty;
+use ApiPlatform\Metadata\ApiProperty;
 use Doctrine\ORM\Mapping as ORM;
 use Ramsey\Uuid\UuidInterface;
 use Symfony\Component\Serializer\Attribute\Groups;
@@ -35,12 +35,19 @@ trait EntityIdentityTrait
         'adherent_message_update_filter',
         'audience_list_read',
         'audience_read',
+        'committee_candidacies_group:write',
+        'elected_representative_write',
+        'elected_mandate_write',
         'audience_segment_read',
+        'my_team_member_write',
         'cause_read',
         'committee:list',
+        'committee:update_animator',
         'committee:read',
         'committee_candidacies_group:read',
         'committee_candidacy:read',
+        'jecoute_news_write',
+        'pap_campaign_history_write',
         'committee_election:read',
         'contact_read',
         'contact_read_after_write',
diff --git a/src/Entity/Event/BaseEvent.php b/src/Entity/Event/BaseEvent.php
index 1a100f765a3..2602ea541db 100644
--- a/src/Entity/Event/BaseEvent.php
+++ b/src/Entity/Event/BaseEvent.php
@@ -325,7 +325,7 @@ abstract class BaseEvent implements ReportableInterface, GeoPointInterface, Addr
     /**
      * @var EventCategoryInterface|EventCategory|null
      */
-    #[Groups(['event_read', 'event_list_read', 'event_write'])]
+    #[Groups(['event_read', 'event_list_read', 'event_write', 'event_write_creation'])]
     #[ORM\ManyToOne(targetEntity: EventCategory::class)]
     protected $category;
 
diff --git a/src/Entity/Event/BaseEventCategory.php b/src/Entity/Event/BaseEventCategory.php
index 1d7270e8b08..738182a6e98 100644
--- a/src/Entity/Event/BaseEventCategory.php
+++ b/src/Entity/Event/BaseEventCategory.php
@@ -2,7 +2,7 @@
 
 namespace App\Entity\Event;
 
-use ApiPlatform\Core\Annotation\ApiProperty;
+use ApiPlatform\Metadata\ApiProperty;
 use Doctrine\ORM\Mapping as ORM;
 use Gedmo\Mapping\Annotation as Gedmo;
 use Symfony\Component\Serializer\Attribute\Groups;
@@ -33,7 +33,7 @@ abstract class BaseEventCategory implements EventCategoryInterface
     #[Assert\Length(max: 100)]
     #[Assert\NotBlank]
     #[Gedmo\Slug(fields: ['name'], unique: true)]
-    #[Groups(['event_read', 'event_list_read', 'event_category_read'])]
+    #[Groups(['event_read', 'event_list_read', 'event_category_read', 'event_write'])]
     #[ORM\Column(length: 100, unique: true)]
     protected $slug;
 
diff --git a/src/Entity/Event/EventCategory.php b/src/Entity/Event/EventCategory.php
index bc65d139306..93029f05cad 100644
--- a/src/Entity/Event/EventCategory.php
+++ b/src/Entity/Event/EventCategory.php
@@ -14,7 +14,7 @@
 #[ApiResource(
     operations: [
         new Get(),
-        new GetCollection(uriTemplate: '/event_categories'),
+        new GetCollection(),
     ],
     normalizationContext: ['groups' => ['event_category_read']],
     order: ['slug' => 'ASC'],
diff --git a/src/Entity/Geo/GeoTrait.php b/src/Entity/Geo/GeoTrait.php
index 18620bd27a2..0b45de7e970 100644
--- a/src/Entity/Geo/GeoTrait.php
+++ b/src/Entity/Geo/GeoTrait.php
@@ -2,7 +2,7 @@
 
 namespace App\Entity\Geo;
 
-use ApiPlatform\Core\Annotation\ApiProperty;
+use ApiPlatform\Metadata\ApiProperty;
 use App\Entity\GeoData;
 use App\Entity\GeoPointTrait;
 use Doctrine\ORM\Mapping as ORM;
diff --git a/src/Entity/Jecoute/ResourceLink.php b/src/Entity/Jecoute/ResourceLink.php
index 7c69d89f93a..a5acc7a2d5b 100644
--- a/src/Entity/Jecoute/ResourceLink.php
+++ b/src/Entity/Jecoute/ResourceLink.php
@@ -22,7 +22,7 @@
     operations: [
         new GetCollection(
             uriTemplate: '/v3/jecoute/resource-links',
-            security: 'is_granted(\'ROLE_OAUTH_SCOPE_JEMARCHE_APP\')'
+            security: "is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP')"
         ),
     ],
     normalizationContext: ['groups' => ['jecoute_resource_links_read', ImageExposeNormalizer::NORMALIZATION_GROUP]],
diff --git a/src/Entity/Pap/Building.php b/src/Entity/Pap/Building.php
index 07fb4113398..1a8063ddcd7 100644
--- a/src/Entity/Pap/Building.php
+++ b/src/Entity/Pap/Building.php
@@ -22,12 +22,12 @@
         new Get(
             uriTemplate: '/v3/pap/buildings/{uuid}',
             requirements: ['uuid' => '%pattern_uuid%'],
-            security: 'is_granted(\'ROLE_OAUTH_SCOPE_JEMARCHE_APP\') and is_granted(\'ROLE_PAP_USER\')'
+            security: "is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')"
         ),
         new Put(
             uriTemplate: '/v3/pap/buildings/{uuid}',
             requirements: ['uuid' => '%pattern_uuid%'],
-            security: 'is_granted(\'ROLE_OAUTH_SCOPE_JEMARCHE_APP\') and is_granted(\'ROLE_PAP_USER\')'
+            security: "is_granted('ROLE_OAUTH_SCOPE_JEMARCHE_APP') and is_granted('ROLE_PAP_USER')"
         ),
     ],
     normalizationContext: ['groups' => ['pap_building_read'], 'iri' => true],
diff --git a/src/Entity/PushToken.php b/src/Entity/PushToken.php
index 3d7961d2b42..3de64af7d01 100644
--- a/src/Entity/PushToken.php
+++ b/src/Entity/PushToken.php
@@ -20,11 +20,11 @@
     operations: [
         new Get(
             uriTemplate: '/v3/push-token/{identifier}',
-            security: 'is_granted(\'IS_AUTHOR_OF_PUSH_TOKEN\', object)'
+            security: "is_granted('IS_AUTHOR_OF_PUSH_TOKEN', object)"
         ),
         new Delete(
             uriTemplate: '/v3/push-token/{identifier}',
-            security: 'is_granted(\'IS_AUTHOR_OF_PUSH_TOKEN\', object)'
+            security: "is_granted('IS_AUTHOR_OF_PUSH_TOKEN', object)"
         ),
         new Post(
             uriTemplate: '/v3/push-token',
diff --git a/src/Exporter/EventParticipantsExporter.php b/src/Exporter/EventParticipantsExporter.php
index 26fdf4be048..b4605ccf2cc 100644
--- a/src/Exporter/EventParticipantsExporter.php
+++ b/src/Exporter/EventParticipantsExporter.php
@@ -47,7 +47,8 @@ function (array $data) {
                     $row['Date d\'inscription'] = $registration->getCreatedAt()->format('Y-m-d H:i:s');
 
                     return $row;
-                })
+                }
+            )
         );
     }
 }
diff --git a/src/Normalizer/EntityCategoryFromSlugDenormalizer.php b/src/Normalizer/EntityCategoryFromSlugDenormalizer.php
new file mode 100644
index 00000000000..ee31e3e6dd4
--- /dev/null
+++ b/src/Normalizer/EntityCategoryFromSlugDenormalizer.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Normalizer;
+
+use ApiPlatform\Exception\ItemNotFoundException;
+use App\Entity\Event\EventCategory;
+use App\Repository\EventCategoryRepository;
+use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
+
+class EntityCategoryFromSlugDenormalizer implements DenormalizerInterface
+{
+    public function __construct(private readonly EventCategoryRepository $repository)
+    {
+    }
+
+    public function denormalize($data, $type, $format = null, array $context = []): mixed
+    {
+        if ($object = $this->repository->findOneBy(['slug' => $data])) {
+            return $object;
+        }
+
+        throw new ItemNotFoundException(\sprintf('Category "%s" with slug "%s" not found', $type, $data));
+    }
+
+    public function getSupportedTypes(?string $format): array
+    {
+        return [
+            '*' => true,
+        ];
+    }
+
+    public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
+    {
+        return \is_string($data) && EventCategory::class === $type;
+    }
+}
diff --git a/src/OAuth/Store/AccessTokenStore.php b/src/OAuth/Store/AccessTokenStore.php
index 905ace95082..b9441499988 100644
--- a/src/OAuth/Store/AccessTokenStore.php
+++ b/src/OAuth/Store/AccessTokenStore.php
@@ -26,7 +26,7 @@ public function __construct(
         $this->accessTokenRepository = $accessTokenRepository;
     }
 
-    public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null)
+    public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null): InMemoryAccessToken
     {
         $token = new InMemoryAccessToken();
         $token->setClient($clientEntity);
@@ -37,7 +37,7 @@ public function getNewToken(ClientEntityInterface $clientEntity, array $scopes,
         return $token;
     }
 
-    public function persistNewAccessToken(AccessTokenEntityInterface $accessToken)
+    public function persistNewAccessToken(AccessTokenEntityInterface $accessToken): void
     {
         $newToken = $this->persistentTokenFactory->createAccessToken($accessToken);
         $user = $newToken->getUser();
@@ -49,7 +49,7 @@ public function persistNewAccessToken(AccessTokenEntityInterface $accessToken)
         $this->store($newToken);
     }
 
-    public function revokeAccessToken($tokenId)
+    public function revokeAccessToken($tokenId): void
     {
         if (!$token = $this->findAccessToken($tokenId)) {
             return;
@@ -61,7 +61,7 @@ public function revokeAccessToken($tokenId)
         $this->store($token);
     }
 
-    public function isAccessTokenRevoked($tokenId)
+    public function isAccessTokenRevoked($tokenId): bool
     {
         if (!$token = $this->findAccessToken($tokenId)) {
             return true;
diff --git a/symfony.lock b/symfony.lock
index 3f7fc500fb9..95b0f4d673d 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -37,15 +37,6 @@
             "ref": "56eaa387b5e48ebcc7c95a893b47dfa1ad51449c"
         }
     },
-    "doctrine/annotations": {
-        "version": "2.0",
-        "recipe": {
-            "repo": "github.com/symfony/recipes",
-            "branch": "main",
-            "version": "1.10",
-            "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05"
-        }
-    },
     "doctrine/doctrine-bundle": {
         "version": "2.5",
         "recipe": {
@@ -253,18 +244,6 @@
             "config/routes/scheb_2fa.yaml"
         ]
     },
-    "sensio/framework-extra-bundle": {
-        "version": "6.2",
-        "recipe": {
-            "repo": "github.com/symfony/recipes",
-            "branch": "main",
-            "version": "5.2",
-            "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b"
-        },
-        "files": [
-            "config/packages/sensio_framework_extra.yaml"
-        ]
-    },
     "sentry/sentry-symfony": {
         "version": "4.3",
         "recipe": {
diff --git a/tests/Controller/Admin/SecurityControllerCaseTest.php b/tests/Controller/Admin/SecurityControllerCaseTest.php
index 7aec0ae21ad..4ce4638c0ac 100644
--- a/tests/Controller/Admin/SecurityControllerCaseTest.php
+++ b/tests/Controller/Admin/SecurityControllerCaseTest.php
@@ -53,6 +53,7 @@ public function testAuthenticationIfSecretCode(): void
         $this->assertClientIsRedirectedTo('/login/2fa', $this->client, true);
 
         $crawler = $this->client->followRedirect();
+
         $this->assertResponseStatusCode(Response::HTTP_OK, $this->client->getResponse());
         $this->assertCount(1, $crawler->filter('#_auth_code'));