Policy-based Authorization
Introduzione
Il sistema AutoPermissions si integra con Laravel Policies attraverso una BasePolicy che verifica automaticamente i permessi prima di eseguire la logica custom delle policy.
Architettura
BasePolicy
Tutte le policy devono estendere App\Shared\Policies\BasePolicy che fornisce:
- Hook
before()per verifica automatica permessi - Naming convention automatico:
{modulo}.{risorsa}.{azione} - Mapping automatico tra metodi policy e azioni AutoPermissions
- Supporto per logica custom aggiuntiva
Flusso di Autorizzazione
Esempio Completo
1. Creare la Policy
<?php
namespace App\Shared\Policies;
use App\Shared\Models\User;
class UserPolicy extends BasePolicy
{
/**
* Verifica se l'utente può vedere la lista utenti.
*
* BasePolicy verifica automaticamente 'shared.users.index'
*/
public function viewAny(User $user): bool
{
// Se arriviamo qui, il permesso è già stato verificato
// Aggiungi logica custom se necessario
return true;
}
/**
* Verifica se l'utente può modificare un altro utente.
*
* BasePolicy verifica 'shared.users.update'
* + logica custom: può modificare solo se stesso
*/
public function update(User $user, User $model): bool
{
// BasePolicy ha già verificato il permesso
// Aggiungi vincolo: può modificare solo se stesso
return $user->id === $model->id;
}
}
2. Registrare la Policy
// app/Providers/AuthServiceProvider.php
protected $policies = [
\App\Shared\Models\User::class => \App\Shared\Policies\UserPolicy::class,
];
3. Usare nel Controller
#[AutoPermissions]
class UserController extends Controller
{
public function index()
{
$this->authorize('viewAny', User::class);
return User::all();
}
public function update(Request $request, User $user)
{
// Verifica sia il permesso CHE la logica custom
$this->authorize('update', $user);
$user->update($request->validated());
return $user;
}
}
Mapping Metodi Policy → Azioni
| Metodo Policy | Azione AutoPermissions | Esempio Permesso |
|---|---|---|
viewAny() | index | shared.users.index |
view() | show | shared.users.show |
create() | create | shared.users.create |
update() | update | shared.users.update |
delete() | destroy | shared.users.destroy |
restore() | restore | shared.users.restore |
forceDelete() | force_delete | shared.users.force_delete |
Personalizzazione BasePolicy
BasePolicy supporta l'override di modulo e risorsa tramite proprietà protette.
Auto-inferenza (Default)
Per default, BasePolicy estrae automaticamente modulo e risorsa:
class UserPolicy extends BasePolicy
{
// ✅ Auto-inferito:
// - Module: 'shared' (da namespace App\Shared\Policies)
// - Resource: 'users' (da nome classe UserPolicy)
// - Permessi: shared.users.index, shared.users.show, etc.
}
Override con Proprietà
Se il nome non corrisponde, puoi dichiarare le proprietà $resourceName e/o $moduleName:
Override della Risorsa
class PersonPolicy extends BasePolicy
{
/**
* Override del nome risorsa (simile a $table nei Model)
*/
protected ?string $resourceName = 'people';
// ✅ Genera: shared.people.index, shared.people.show, etc.
// Invece di: shared.persons.index
}
Override del Modulo
class CustomPolicy extends BasePolicy
{
/**
* Override del modulo quando non corrisponde al namespace
*/
protected ?string $moduleName = 'cont';
// ✅ Genera: cont.customs.index
// Invece di: custom_module.customs.index
}
Override Completo
class MismatchedPolicy extends BasePolicy
{
/**
* Override completo di modulo e risorsa
*/
protected ?string $moduleName = 'cont';
protected ?string $resourceName = 'contracts';
// ✅ Genera: cont.contracts.index, cont.contracts.show, etc.
// Anche se la Policy si chiama MismatchedPolicy e sta in un modulo diverso
}
Quando Usare l'Override
✅ Usa l'override quando:
- Il nome della Policy non corrisponde al nome della risorsa
- La Policy è in un modulo diverso dalla risorsa
- Vuoi riutilizzare la stessa Policy per risorse diverse
❌ Non serve override quando:
- Il nome della Policy corrisponde al nome della risorsa (es:
UserPolicy→users) - La Policy è nel modulo corretto
Azioni Custom
Per azioni non standard:
class DocumentPolicy extends BasePolicy
{
public function publish(User $user, Document $document): bool
{
// BasePolicy cerca 'cont.documents.publish'
// Aggiungi logica custom
return $document->status === 'draft';
}
}
Testing
public function test_user_with_permission_can_update_own_profile()
{
$user = User::factory()->create();
$user->givePermissionTo('shared.users.update');
$response = $this->actingAs($user)->putJson("/api/users/{$user->id}", [
'name' => 'New Name'
]);
$response->assertOk();
}
public function test_user_cannot_update_other_user_profile()
{
$user = User::factory()->create();
$otherUser = User::factory()->create();
$user->givePermissionTo('shared.users.update');
// Ha il permesso MA la policy custom lo blocca
$response = $this->actingAs($user)->putJson("/api/users/{$otherUser->id}", [
'name' => 'New Name'
]);
$response->assertForbidden();
}
Generazione Automatica delle Policy
Il comando auto-permissions:generate può generare automaticamente le classi Policy per i controller processati.
Generare Policy
# Genera permessi e policy insieme
php artisan auto-permissions:generate --all --with-policy
# Genera solo policy (senza rigenerare permessi)
php artisan auto-permissions:generate --all --policy-only
# Genera policy per un controller specifico
php artisan auto-permissions:generate UserController --with-policy
# Sovrascrive policy esistenti
php artisan auto-permissions:generate UserController --with-policy --force-policy
Struttura della Policy Generata
La Policy generata include automaticamente:
- Metodi standard Laravel:
viewAny,view,create,update,delete,restore,forceDelete - Metodi custom: Tutti i metodi pubblici del controller che generano permessi
- Commenti: Ogni metodo include un commento che indica quale permesso viene verificato
<?php
namespace App\Shared\Policies;
use App\Shared\Policies\BasePolicy;
use App\Shared\Models\User\User;
class UserPolicy extends BasePolicy
{
/**
* Determine if the user can view any models.
*
* BasePolicy already verified 'shared.users.index'
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine if the user can view the model.
*
* BasePolicy already verified 'shared.users.show'
*/
public function view(User $user, User $model): bool
{
return true;
}
// ... altri metodi standard ...
/**
* Custom action: exportUsers
*
* BasePolicy already verified 'shared.users.exportUsers'
*/
public function exportUsers(User $user): bool
{
return true;
}
}
Requisiti per la Generazione
- Il controller deve avere l'attributo
#[AutoPermissions] - La Policy viene generata nello stesso modulo del controller
Se il Model non viene rilevato (dai type-hint o dal nome controller), la Policy non viene generata. Lo sviluppatore dovrà creare manualmente la Policy per questi controller.
Il messaggio di skip sarà: Skipped: ControllerName - Model not found
Personalizzazione Post-Generazione
Dopo la generazione, puoi modificare le Policy per aggiungere logica custom:
public function update(User $user, User $model): bool
{
// BasePolicy ha già verificato il permesso 'shared.users.update'
// Aggiungi logica custom: può modificare solo se stesso
return $user->id === $model->id;
}
I metodi custom (non standard Laravel) vengono generati con solo User $user come parametro. Se necessario, puoi aggiungere manualmente il Model come secondo parametro.
Best Practices
-
Usa
authorizeResource()per resource controller:public function __construct()
{
$this->authorizeResource(User::class);
} -
Separa permessi da business logic: BasePolicy per permessi, metodi policy per logica custom
-
Testa separatamente: Test per permessi + test per logica custom
-
Documenta le policy: Commenta perché una policy nega l'accesso
-
Usa il naming convention: Mantieni coerenza tra azioni e metodi policy
Vantaggi del Sistema Policy-based
Laravel-native
Usa il sistema Policy standard di Laravel invece di middleware custom.
Testabile
Policy facilmente testabili con metodi chiari e predicibili.
Flessibile
Combina permessi automatici + business logic custom in un unico posto.
Granulare
Autorizzazione a livello di risorsa, non solo di route HTTP.
Esplicito
$this->authorize() rende chiara la verifica dell'autorizzazione.
Debuggabile
Stack trace chiaro, niente middleware nascosto che intercetta le richieste.