I vantaggi di usare i Model di Eloquent in una applicazione Laravel sono molteplici. Rispettando alcune buone regole sulla nomenclatura di tabelle e colonne del database, è possibile accedere direttamente dal proprio codice a tali dati senza dover scrivere codice necessario a convertire elementi del database in classi e relative property.
Nelle lezioni precedenti abbiamo, principalmente, visto quanto offerto dal framework per la lettura di informazioni dal database, ma (per?) un’ applicazione avr(em)ò sicuramente necessità di scrivere anche nuove righe a fronte di un input dell’utente.
Eloquent mette a disposizione metodi specifici per gestire in modo corretto, sicuro e veloce la scrittura di info tramite Model Eloquent.
Model e salvataggio di informazioni in Laravel
Abbiamo visto come i Model eloquent permettono di usare un’istanza del model stesso per inserire nuovi dati o modificare dati esistenti usando le property dinamiche legate alle colonne su database.
use App\Model\Author; use App\Model\Book; $author = $author = Author::where('name', 'Randal Munroe')->first(); $book = new Book; $book->title = "What If?" $book->author_id = $author->id; // ... $book->save();
Anche se il metodo save inserisce automaticamente data di creazione e modifica, nel caso di model con molte property potrebbe diventare scomodo impostare una a una le varie property dinamiche alla creazione di una nuova riga.
È disponibile, in alternativa, il metodo create che accetta un array chiave/valore di tutte le property da cambiare e i rispettivi valori.
use App\Model\Book; // ... Book::create([ 'title' => 'What If?', 'author_id' => $author->id, 'isbn_13' => '978-0544272996', 'published_at' => '2014-09-02', // .. ]);
Esiste un metodo analogo per l’aggiornamento di record già presenti su database, update.
use App\Model\Book; // ... Book::where('isbn_13', '978-0544272996')->update([ 'price' => '27,50/EUR', 'available' => true, ]);
L’utilizzo di questi due metodi presenta vantaggi e svantaggi. Da un lato, infatti, le azioni di creazione di un nuovo record e di update sono più chiaramente leggibili e “compatte” rispetto all’assegnare singolarmente valori alle singole property. Inoltre, non è necessario chiamare esplicitamente il metodo save per far persistere effettivamente i dati sul database (lato “codice” abbiamo un’ istanza che rappresenta un record su database).
Dall’altro, la possibilità di assegnare un valore a tutte le colonne del database pone interessanti questioni di sicurezza. Se provassimo ad eseguire la create nell’esempio precedente in una versione recente di Laravel, riceveremmo un errore.
Illuminate\Database\Eloquent\MassAssignmentException Add [name] to fillable property to allow mass assignment on [App\Models\Author].
Vulnerabilità “mass assignment” in Laravel
Si parla di vulnerabilità mass assignment in Laravel nel caso in cui le richieste HTTP che una applicazione web riceve vengono mappate automaticamente in variabili o oggetti all’interno del proprio codice. Questo legame tra richiesta HTTP e dati interni all’applicazione potrebbe causare problemi o danni.
Facciamo un esempio pratico e ipotetico. Nella nostra applicazione Laravel abbiamo la tabella users con le colonne email, password e role che indica se l’utente è visualizzatore, editor o admin dell’applicazione stessa. La tabella è connessa a un Model che, a sua volta, è connesso a un Controller che, a sua volta, riceve richieste di signup da un Form. Per creare un nuovo utente, esiste un form html e una metodo store in un controller.
<form> <input name="password" type="text"> <input name="email" text="text"> <input type="submit"> </form>
use App\Model\User; public function store(UserFormRequest $request) { $user = User::create($request->validated()); }
In questa situazione, i dati che arrivano dal form potrebbero essere convertiti automaticamente dal form (html) all’array da passare alla create, finendo, quindi, su database. Se il mapping fosse automatico e basato sulla corrispondenza dei vari nomi delle entità, un utente mavolo potrebbe forzare il form lato HTML, aggiungere un role ed essere automaticamente aggiunto con il role di admin.
Questo tipo di vulnerabilità è dovuta a una serie di automatismi legati a convenzioni sui nomi che causano un legame diretto tra dato sul database e dato nel browser dell’utente, in gran parte introdotto per rendere più semplice la vita degli sviluppatori.
In un applicativo Laravel è possibile applicare metodi di protezione basati su allow-list o su block-list, implementabili a livello di model tramite opportune property: $fillable e $guarded.
Property fillable e property guarded in Laravel
I meccanismi di allow-list e block-list sono regolati in un Model Eloquent tramite la variabile $fillable e la variabile $guarded.
Poiché ogni modello Eloquent creato nella nostra applicazione estende la classe Illuminate\Database\Eloquent\Model, se non specificato altrimenti ogni modello assumerà per queste due variabili i seguenti valori (nessuna fillable, tutte guarded):
$fillable = []; $guarded = ['*'];
Nei due array php è possibile indicare i nomi delle colonne della tabella associata con effetto opposto. Le colonne elencate in $fillable potranno essere usate come chiavi nell’array passato a una create, le colonne elencate in $guarded verranno ignorate, anche se passate alla create.
Se, quindi, volessimo proteggere la nostra ipotetica tabella users con un metodo allow-list potremmo modificare il nostro model indicando:
class User extends Model { protected $fillable= ['email', 'password']; // ... }
Se, invece, preferiamo l’approccio black-list, allora va indicata solo la colonna da non scrivere:
class User extends Model { protected $guarded = ['role']; // ... }
Le property $fillable e $guarded intervengono, ovviamente, come misura di sicurezza per il mass assignment; sarà comunque possibile usare il model e i suoi altri meccanismi per scrivere anche la colonna role nella gestione di quelle richieste che sono autorizzate a modificare il suo valore.
NOTA: è possibile scegliere solo uno dei due approcci, non è possibile cambiare valore per sia per $fillable che per $guarded insieme in un model.