K

Začínáme

Autorizace

Každé odmítnutí jde přes laravelí Gate — na co se ho ptá tabulka, formulář, akce a stránka, a kdy.

Na této stránce

Wire používá Laravel Gate a policies. Autorizace tak zůstává kompatibilní s nativními Laravel policies, Spatie Permission a balíčky, které registrují abilities do Gate.

Sdílená pravidla komponent

Sloupce, filtry, akce, pole a widgety mohou používat sdílené autorizační metody, když komponenta podporuje viditelnost nebo autorizaci.

Action::make('approve')
->label('Approve')
->authorize('approve')
->action(fn (Order $record) => $record->approve());
 
TextColumn::make('internal_note')
->label('Internal note')
->permission('orders.internal-notes.view');
 
SelectFilter::make('department_id')
->authorizeUsing(fn (User $user) => $user->is_admin);

Pořadí vyhodnocení:

Pravidlo Chování
Žádná autorizace nakonfigurovaná Povoleno
Žádný přihlášený uživatel Zamítnuto
authorizeUsing() Vlastní callback má prioritu
authorize() Kontroluje Laravel Gate ability
permission() Kontroluje permission řetězec přes Gate

Autorizace po záznamech

Callback authorizeUsing() dostává přihlášeného uživatele a — kde ho povrch má — záznam řádku, takže autorizaci lze zúžit na jednotlivé záznamy:

Action::make('approve')
->authorizeUsing(fn (User $user, $record) => $user->id === $record?->manager_id)
->action(fn (Order $record) => $record->approve());

Záznam je přítomný u řádkových akcí; je null u povrchů bez záznamu (strukturální viditelnost sloupce/filtru, pole, widgety), takže jednoargumentová closura fn ($user) => … zůstává platná všude.

Tohle řídí, zda celý sloupec/akce strukturálně existuje (vyhodnoceno jednou). Pro skrytí nebo redakci jedné buňky na řádek — např. zobrazit salary jen na záznamech, které uživatel smí vidět — použijte místo toho visibleForRecord() sloupce, který běží při renderu buňky se záznamem daného řádku:

TextColumn::make('salary')
->visibleForRecord(fn ($record) => auth()->user()->can('viewSalary', $record));

Policies tabulky

Zapněte kontroly policies na tabulce pomocí authorize().

use NyonCode\WireTable\Table;
 
public function table(Table $table): Table
{
return $table
->model(Order::class)
->authorize()
->columns([
// ...
]);
}

Wire kontroluje tyto policy metody, když jsou potřeba:

Schopnost tabulky Policy ability
Vytvořit záznam create
Aktualizovat záznam update
Smazat záznam delete
Zobrazit záznam view

Přepisy tabulky

Přepisy použijte, když tabulka potřebuje pravidla odlišná od policy modelu.

return $table
->model(Order::class)
->authorize()
->authorizeCreate(fn () => auth()->user()?->can('create', Order::class) ?? false)
->authorizeUpdate(fn (Order $record) => ! $record->is_locked)
->authorizeDelete(fn (Order $record) => $record->status === 'draft')
->authorizeView(fn (Order $record) => $record->tenant_id === auth()->user()?->tenant_id);

Každý přepis přijímá boolean nebo closuru.

Inline editace

Editovatelné sloupce mohou pro inline editaci vyžadovat Gate ability.

TextInputColumn::make('price')
->authorizeInline('orders.update-price');

Pokud uživatel neprojde kontrolou ability, sloupec zůstane viditelný, ale inline editace je zamítnuta.

Akce

Akce lze skrýt nebo zamítnout pomocí Gate abilities, permission řetězců nebo vlastních callbacků.

Action::make('refund')
->label('Refund')
->authorize('refund')
->visible(fn (Order $record) => $record->is_paid)
->requiresConfirmation()
->action(fn (Order $record) => $record->refund());

Pro jednoduché permission řetězce:

Action::make('export')
->permission('orders.export')
->action(fn () => $this->exportTable());

Formuláře

Formuláře mohou používat policies modelu pro create a update.

use NyonCode\WireForms\Forms\Form;
 
public function form(Form $form): Form
{
return $form
->model($this->user ?? User::class)
->authorize()
->schema([
// ...
]);
}

Když je authorize() zapnuto:

Stav formuláře Policy ability
Třída modelu nebo neuložený model create
Existující instance modelu update

Při zamítnutí je formulář read-only a nelze ho uložit.

Pro vlastní pravidla formuláře:

return $form
->model($this->user)
->authorizeUsing(fn (User $user) => $user->hasRole('editor'))
->schema([
// ...
]);

Sortable

Sortable operace by měly být chráněné v hoocích vaší Livewire komponenty.

public function beforeRowsReordered(array $orderedIds): void
{
$this->authorize('reorder', Task::class);
}

Hooky životního cyklu viz Sortable řazení řádků.

Související dokumentace

Dokument Co pokrývá
Core Akce Řádkové, hromadné, hlavičkové akce a modální akce
Přehled tabulek Nastavení tabulky a API na úrovni tabulky
Přehled formulářů Nastavení formuláře a chování ukládání
Audit Log Záznam změn modelů po úspěšné autorizaci

Multi-tenancy

Ve výchozím stavu vypnutá — většina aplikací má jednoho tenanta a scopovat je by byla WHERE klauzule koupená za nic. Jakmile je zapnutá, je striktní.

// config/wire-core.php
'tenancy' => [
'enabled' => true,
'column' => 'tenant_id',
],

Naváž resolver; výchozí odpovídá null, což se zapnutou tenancy znamená prázdnou stránku, dokud to neuděláte:

use NyonCode\WireCore\Core\Tenancy\Contracts\TenantResolver;
 
app()->bind(TenantResolver::class, fn () => new class implements TenantResolver {
public function resolve(): int|string|null
{
return auth()->user()?->tenant_id;
}
});

Pak označ modely, které tenantovi patří:

use NyonCode\WireCore\Core\Tenancy\Concerns\BelongsToTenant;
 
class Invoice extends Model
{
use BelongsToTenant;
}

Zapíná se pro každý model zvlášť, protože framework nemůže vědět, které z vašich tabulek tenant vlastní, a hádat by znamenalo hádat, kdo co smí vidět.

Fail-safe

Zapnutá tenancy bez resolvovaného tenanta vrací nic, nikdy vše.

To je celý bezpečnostní příběh v jedné větě. Každý běžný stav vyrobí null tenanta — před přihlášením, na queue workeru, v konzolovém příkazu — takže scope, který by null četl jako „bez omezení“, by každému z nich podal všechny řádky. Místo toho omezí na 0 = 1.

Záměrně to není ani where tenant_id is null: řádek, který nikdo nevlastní, by pak viděli všichni, což je tentýž únik v jiném kabátě.

Co je scopované

Globální scope, ne plugin hook — a ta volba je pointa. Hook pokryje jednu čtecí cestu, a jen když je zrovna navázaný plugin manager. Globální scope pokryje každý dotaz, který Eloquent postaví:

Scopované
Invoice::query(), výpis tabulky, relace ano
Invoice::find($id) ve vašem vlastním controlleru ano
->update() a ->delete() ano
Frontovaný job resolvující záznamy podle klíče ano
Invoice::create() připíše se aktuálnímu tenantovi

Create bez resolvovaného tenanta vyhodí výjimku místo zápisu řádku s null tenantem — takový řádek by pak byl neviditelný pro každý scopovaný dotaz, takže by uživatelova práce byla pryč a nikdo by to neřekl. Explicitně nastavený sloupec se nechá být, kvůli seederu nebo záměrnému přesunu mezi tenanty.

Sloupec je kvalifikovaný, protože scopovaný model se běžně joinuje a joinovaná tabulka mívá vlastní tenant_id.

Čtení napříč tenanty

Invoice::acrossAllTenants()->count();

Záměrně upovídané a snadno greppovatelné: admin report nebo konzolový příkaz má reálnou potřebu a revize musí najít každé místo, které si ji nárokovalo.

Zdroj, na který Eloquent nedosáhne

Non-Eloquent DataSourceCollectionDataSource, zdroj nad API — nestaví žádný Eloquent dotaz, takže na něj globální scope nedosáhne. Obalte ho:

use NyonCode\WireCore\Core\Tenancy\TenantScopedDataSource;
 
$source = new TenantScopedDataSource(new CollectionDataSource($rows), app(Tenancy::class));

Omezuje plán, ne řádky, které se vrátí — a právě proto je bezpečný na všech metodách, ne jen na té, která řádky vrací: count() a paginate() odpovídá zdroj, aniž by řádky vůbec vydal, a zdroj, který ten filtr neumí, ho hlasitě odmítne (UnsupportedQueryAspectException) místo aby vrátil řádky, které nikdo neomezil.

resolveRecord() bere klíč, ne plán, takže tam se záznam načte a zkontroluje — bez toho se tenant dostane na cizí řádek tím, že si do URL napíše jeho id.

Fail-safe je tentýž, jaký má TenantScope: tenancy zapnutá a žádný tenant rozpoznaný znamená nic. Vypnutá tenancy deleguje beze změny, takže obalení zdroje jednotenantovou aplikaci nic nestojí.

Třetím argumentem předáte sloupec tam, kde si ho zdroj pojmenoval jinak než konfigurace.