fedale / access-control-voter-bundle
Symfony bundle for dynamic, DB-driven authorization voters (#[IsGranted] backed by a database)
Package info
github.com/fedale/access-control-voter-bundle
Type:symfony-bundle
pkg:composer/fedale/access-control-voter-bundle
Requires
- php: ^8.2
- fedale/access-control-bundle: ^1.0
- psr/cache: ^3.0
- symfony/config: ^6.4 || ^7.0
- symfony/console: ^6.4 || ^7.0
- symfony/dependency-injection: ^6.4 || ^7.0
- symfony/http-kernel: ^6.4 || ^7.0
- symfony/security-bundle: ^6.4 || ^7.0
- symfony/security-core: ^6.4 || ^7.0
Requires (Dev)
- doctrine/doctrine-bundle: ^2.8
- doctrine/orm: ^2.14 || ^3.0
- phpunit/phpunit: ^11.0
- symfony/cache: ^6.4 || ^7.0
Suggests
- doctrine/doctrine-bundle: Necessario quando 'provider: doctrine'
- doctrine/orm: Necessario quando 'provider: doctrine' (sorgente regole di default)
This package is auto-updated.
Last update: 2026-06-19 23:09:38 UTC
README
Autorizzazione nativa Symfony (#[IsGranted] / isGranted() / AccessDecisionManager) guidata da
regole dinamiche e persistite su database.
È il complemento di fedale/access-control-bundle:
access-control-bundle |
access-control-voter-bundle (questo) |
|
|---|---|---|
| Livello | Firewall su kernel.request |
Voter nativo Symfony |
| Protegge | URL (path/host/ip/method) | Attributi/azioni (#[IsGranted('EDIT_INVOICE')]) |
| Semantica | first-match-wins | first-match-wins |
| Sorgente regole | DB (Doctrine) + cache | DB (Doctrine) + cache |
Questo bundle dipende da access-control-bundle e ne riusa i pattern (provider, cache PSR-6,
bridge Doctrine, config AbstractBundle), ma non lo modifica e vive in un pacchetto separato.
Installazione
composer require fedale/access-control-voter-bundle
Registra il bundle (se non usi Symfony Flex):
// config/bundles.php return [ // ... Fedale\AccessControlVoterBundle\FedaleAccessControlVoterBundle::class => ['all' => true], ];
Col provider Doctrine (default) il mapping ORM dell'entità viene registrato automaticamente
(prependExtension): non serve aggiungere voci a doctrine.orm.mappings. Genera ed esegui la
migration per creare la tabella permission_rule:
php bin/console make:migration php bin/console doctrine:migrations:migrate
Quick start
-
Annota un controller/azione con un attributo a piacere:
use Symfony\Component\Security\Http\Attribute\IsGranted; #[IsGranted('EDIT_INVOICE')] public function edit(Invoice $invoice): Response { /* ... */ }
-
Inserisci una regola nella tabella
permission_rule(o via la tua UI/CRUD):attribute roles allow sort active EDIT_INVOICE["ROLE_EDITOR"]true0true -
Un utente con
ROLE_EDITORottiene 200, gli altri 403. Disattiva la regola (active = false) o cambiala a runtime: la decisione cambia senza deploy (ricordati di invalidare il pool di cache, vedi sotto).
Come decide il voter
Per ogni attributo (EDIT_INVOICE):
super_admin_roleconcesso →ACCESS_GRANTED(short-circuit).- Regole attive dell'attributo, ordinate per
sortASC: la prima applicabile (ruoli soddisfatti,rolesvuoto = sempre applicabile) decide via il suoallow→ grant/deny. (first-match-wins) - Nessuna regola per quell'attributo →
supports()èfalse→ ABSTAIN (decidono gli altri voter). - Regole presenti ma nessuna applicabile all'utente →
ACCESS_DENIED.
I ruoli sono valutati con isGranted(), quindi la role_hierarchy dell'app è rispettata.
Edge-case: evita di usare come
attributeuna stringa che coincide con un ruolo (ROLE_*): il voter è attributo-scoped proprio per non intercettare i voti sui ruoli ed evitare ricorsione.
Configurazione
# config/packages/fedale_access_control_voter.yaml fedale_access_control_voter: enabled: true # Ruolo che bypassa tutte le regole (stringa vuota per disabilitare). super_admin_role: ROLE_SUPER_ADMIN cache: enabled: true pool: cache.app # ttl: 3600 # secondi; null = nessuna scadenza (invalida il pool a mano) # 'doctrine' = provider built-in, oppure l'id di un servizio custom. provider: doctrine
Le regole sono cachate in blocco sotto un'unica chiave
(CachedPermissionRuleProvider::CACHE_KEY): il voter le interroga ad ogni voto, quindi il DB non viene
toccato per ogni richiesta. Dopo aver modificato le regole, invalida il pool (es.
php bin/console cache:pool:clear cache.app) oppure imposta un ttl.
Diagnostica delle regole effettive:
php bin/console fedale:access-control-voter:list
Provider custom
Imposta provider all'id di un servizio che implementa
Fedale\AccessControlVoterBundle\Contract\PermissionRuleProviderInterface (YAML, API, in-memory, ...).
La decorazione di cache resta opzionale e indipendente dalla sorgente.
Ispirazione: Yii2 RBAC
Il design prende spunto dall'auth manager di Yii2 e ne mappa i concetti su quelli nativi di Symfony:
| Yii2 RBAC | Qui |
|---|---|
| Permission (auth item) | attribute (es. EDIT_INVOICE) |
Yii::$app->user->can($permission, $params) |
isGranted($attribute, $subject) |
$params passato a can() |
$subject del voter |
| Assignment ruolo→utente, role hierarchy | roles sulla regola + role_hierarchy di Symfony |
Rule (execute($user, $item, $params): bool) |
PermissionConditionInterface::evaluate($subject, $token, $context) |
L'idea più utile di Yii2 è la Rule: una condizione contestuale e riusabile, agganciata a un permesso, che decide in base all'oggetto (es. "puoi modificare questa fattura solo se ne sei l'autore").
Predisposizione object-level (non ancora attiva)
L'entità/DTO espongono già due campi pensati per questo:
subjectType— FQCN del soggetto a cui la regola si applica;condition— id di un servizioPermissionConditionInterface(la "Rule" stile Yii2).
interface PermissionConditionInterface { public function evaluate(mixed $subject, TokenInterface $token, array $context = []): bool; }
Allo stato attuale il DynamicVoter legge questi campi ma non li valuta: la decisione dipende
dai soli roles. La valutazione del $subject e l'esecuzione delle condizioni sono predisposte ma
fuori scope (estensione futura), insieme a un eventuale supporto per ExpressionLanguage.
Test
composer install vendor/bin/phpunit