Skip to main content

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 PolicyAzione AutoPermissionsEsempio Permesso
viewAny()indexshared.users.index
view()showshared.users.show
create()createshared.users.create
update()updateshared.users.update
delete()destroyshared.users.destroy
restore()restoreshared.users.restore
forceDelete()force_deleteshared.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: UserPolicyusers)
  • 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:

  1. Metodi standard Laravel: viewAny, view, create, update, delete, restore, forceDelete
  2. Metodi custom: Tutti i metodi pubblici del controller che generano permessi
  3. 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
Model non trovato

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;
}
Metodi Custom

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

  1. Usa authorizeResource() per resource controller:

    public function __construct()
    {
    $this->authorizeResource(User::class);
    }
  2. Separa permessi da business logic: BasePolicy per permessi, metodi policy per logica custom

  3. Testa separatamente: Test per permessi + test per logica custom

  4. Documenta le policy: Commenta perché una policy nega l'accesso

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