Authorization Resource
Setiap ability milik Resource di-resolve ke Laravel policy biasa melalui Gate. Tidak ada permission system khusus Panel yang perlu dipelajari: tulis policy seperti yang memang akan Anda tulis pada aplikasi Laravel, lalu Panel akan menanyakannya — pada route, sebelum write, dan sekali lagi sebelum menggambar button. Halaman ini membahas seluruh ability yang ditanyakan Resource, lokasi setiap check, dan perubahan behavior ketika strict mode aktif.
Setup minimal
Buat policy untuk model lalu daftarkan seperti policy Laravel biasa:
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
final class PostPolicy
{
public function viewAny(User $user): bool
{
return $user->is_editor;
}
public function view(User $user, Post $post): bool
{
return $user->is_editor || $user->is($post->author);
}
public function create(User $user): bool
{
return $user->is_editor;
}
public function update(User $user, Post $post): bool
{
return $user->is_editor || $user->is($post->author);
}
public function delete(User $user, Post $post): bool
{
return $user->is_editor;
}
public function deleteAny(User $user): bool
{
return $user->is_editor;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Tidak ada deklarasi authorization yang perlu ditambahkan pada Resource. Dengan policy tersebut, /admin/posts menjawab 403 untuk user non-editor, sidebar entry menghilang, create button tidak muncul, dan guessed record URL ditolak oleh rule yang sama dengan rule yang menyembunyikan link.
Daftar ability
| Method Resource | Signature | Gate ability |
|---|---|---|
canViewAny() | public static function canViewAny(): bool | viewAny |
canView() | public static function canView(Model $record): bool | view |
canCreate() | public static function canCreate(): bool | create |
canEdit() | public static function canEdit(Model $record): bool | update |
canDelete() | public static function canDelete(Model $record): bool | delete |
canDeleteAny() | public static function canDeleteAny(): bool | deleteAny |
canRestore() | public static function canRestore(Model $record): bool | restore |
canRestoreAny() | public static function canRestoreAny(): bool | restoreAny |
canForceDelete() | public static function canForceDelete(Model $record): bool | forceDelete |
canForceDeleteAny() | public static function canForceDeleteAny(): bool | forceDeleteAny |
canEdit() menanyakan ability update, bukan edit. Panel mengikuti vocabulary Laravel sehingga policy yang sama dapat mengontrol console command, API controller, dan Panel.
Method tersebut dapat dipanggil kapan pun Anda membutuhkan jawabannya:
use App\Panels\Admin\Resources\Posts\PostResource;
if (PostResource::canEdit($post)) {
// ...
}2
3
4
5
Tempat setiap ability diperiksa
| Surface | Ability |
|---|---|
ListRecords::render() | canViewAny(), menghasilkan 403 bila ditolak |
CreateRecord::render() dan handle() | canCreate(), 403 |
ViewRecord::render() | canView() pada resolved record, 403 |
EditRecord::render(), handle(), validateStep() | canEdit() pada resolved record, 403 |
Custom page yang memakai InteractsWithRecord | canView(), kecuali page meng-override authorizeRecord() |
| Sidebar entry | canViewAny() |
| Create button pada list page | canCreate() |
| Edit button pada view page | canEdit() |
| Record sub-navigation links | canView() / canEdit() sesuai link |
| Global search | canViewAny() per Resource sebelum query dijalankan |
| Actions | Check milik Action itu sendiri yang ditanyakan endpoint |
Authorization ditanyakan secara independen pada setiap surface. Fakta bahwa sebuah button sudah dirender tidak pernah dianggap bukti bahwa operation boleh dijalankan. Action endpoint tetap mengecek authorization sebelum menjalankan apa pun, dan route juga mengecek authorization sebelum page dirender.
Ability *Any
canDeleteAny(), canRestoreAny(), dan canForceDeleteAny() digunakan bulk Action sebelum ada record spesifik yang dapat ditanyakan. Setelah collective check tersebut, setiap record di selection tetap di-authorize satu per satu sebelum satu pun record ditulis, sehingga selection yang memiliki satu record terlarang tidak mengubah data apa pun.
public function deleteAny(User $user): bool
{
return $user->is_editor;
}2
3
4
Policy yang mendefinisikan delete tetapi tidak mendefinisikan deleteAny tidak akan menawarkan bulk deletion. Pada strict mode, kondisi ini dijelaskan sebagai missing ability, bukan terlihat seperti penolakan yang disengaja.
Menambahkan rule tanpa melewati ability check framework
Seluruh ability melewati satu protected method. Karena itu override salah satu can*() tetap dapat mempertahankan behavior framework dengan memanggil method tersebut:
use Illuminate\Database\Eloquent\Model;
public static function canDelete(Model $record): bool
{
return $record->getAttribute('published_at') === null
&& static::authorize('delete', $record);
}2
3
4
5
6
7
protected static function authorize(string $ability, Model|string $argument): boolauthorize() mendelegasikan pemeriksaan ke PandaPanel\Support\PolicyGate::allows(), yaitu lokasi strict mode diterapkan. Memanggil Gate::allows() secara langsung akan melewati strict mode tersebut.
Strict authorization
$panel->strictAuthorization();Default-nya off. Ketika aktif, policy yang hilang atau method ability yang tidak ada akan melempar PandaPanel\Exceptions\PanelAuthorizationException alih-alih diam-diam menghasilkan denial:
No policy is registered for [App\Models\Post], so the ability [viewAny] can only ever be denied.
Register one, or turn off strictAuthorization() for this panel.2
Dari luar, policy yang hilang dan policy yang sengaja menolak terlihat sama: keduanya menghasilkan 403. Ini adalah behavior yang benar untuk production, tetapi tidak membantu selama development karena policy yang lupa dibuat terlihat seperti authorization rule yang memang sengaja menolak. Aktifkan secara lokal, nonaktifkan di production, atau sesuaikan berdasarkan environment:
$panel->strictAuthorization(! app()->isProduction());Policy yang mendefinisikan before() tidak diwajibkan memiliki method individual untuk setiap ability. before() memang dapat menjawab seluruh ability, sehingga missing method dalam kondisi tersebut tidak dianggap konfigurasi yang salah.
Rule strict mode diletakkan pada PolicyGate, bukan langsung pada Resource, karena relation manager menanyakan ability yang bahkan tidak memiliki method pada Resource. Menaruh logic yang sama di dua tempat akan menciptakan dua implementasi yang harus selalu dijaga tetap identik.
Di luar strict mode tetap ada versi diagnostic yang lebih ringan. Ketika Resource hilang dari sidebar karena model-nya sama sekali tidak memiliki policy, development log menuliskan informasi tersebut satu kali per model. Log ini tidak dibuat pada production karena Resource yang sengaja tidak dapat dilihat siapa pun merupakan konfigurasi yang valid.
Scope adalah separuh lain dari authorization
Authorization menjawab pertanyaan: "Apakah user ini boleh melakukan operasi ini terhadap record ini?" Resource query menjawab pertanyaan berbeda: "Apakah record ini dapat dijangkau oleh Resource ini?" Keduanya menghasilkan jenis penolakan yang berbeda:
- Record di luar
Resource::query()menghasilkan 404. Dari perspektif Resource, record tersebut memang tidak ada. - Record berada di dalam query tetapi ditolak policy menghasilkan 403.
Tenant, team, atau narrowing khusus Panel seharusnya berada pada query, bukan diulang di policy. Lihat Resource queries. Policy yang harus mengulang tenant check menjadi tempat kedua yang dapat terlupa ketika rule berubah.
Access ke Panel
Resource policy tidak dapat memberikan akses ke user yang sejak awal tidak diizinkan masuk ke Panel. Predicate milik Panel dijalankan terlebih dahulu:
use App\Models\User;
use Illuminate\Contracts\Auth\Authenticatable;
$panel->canAccess(
static fn (?Authenticatable $user): bool => $user instanceof User && $user->is_admin,
);2
3
4
5
6
Authenticated user yang gagal check ini menerima 403, bukan redirect. Lihat Panel access.
Nested resources
Parent milik nested resource di-resolve melalui query() milik parent Resource lalu di-authorize menggunakan canView() milik parent sebelum route child dijalankan. Karena itu /users/9/posts tidak dapat menjadi jalan untuk membaca child milik user 9 ketika /users/9 sendiri ditolak. Lihat Nested resources.
Relation managers
Relation manager memisahkan dua pertanyaan authorization. Membaca dan mengubah related record menggunakan ability pada policy milik related record tersebut. Attach/detach menggunakan ability pada policy owner — apakah tag boleh dipasang pada post adalah urusan post, bukan tag. Lihat Relation policies.
Catatan penting
- Menyembunyikan bukan berarti meng-authorize. Navigation visibility hanya layer kenyamanan UI. Route dan Action endpoint tetap melakukan check sendiri. Test keamanan sebaiknya meng-assert response 403, bukan sekadar tidak adanya link.
canEdit()dipetakan keupdate. Method policy bernamaedittidak pernah dipanggil.deleteAnyyang hilang akan menghilangkan bulk delete secara diam-diam jika strict mode tidak aktif. Ini penyebab paling umum "bulk action tiba-tiba tidak muncul".- Method
can*bersifat static dan tidak menerima user. Method tersebut menanyakan Gate terhadap authenticated user saat ini. Tidak ada API untuk mengajukan pertanyaan yang sama seolah-olah sebagai user lain, karena framework memang tidak merender sidebar sebagai user lain. - Strict mode dapat mengubah 403 menjadi 500. Gunakan sebagai development aid, bukan production behavior default.
- Action melakukan authorization secara independen dari page yang merendernya. Action pada view page di-resolve dari
Resource::infolist(), sedangkan Action pada table di-resolve dariResource::table(). Keduanya memiliki whitelist terpisah, sehingga Action yang ditawarkan pada satu surface tidak otomatis dapat dijalankan melalui surface lain.