Contract PanelUser
PanelUser memberikan satu method pada User Model untuk menentukan Panel mana yang boleh dimasuki account tersebut.
Contract ini mewakili aturan milik user/account, misalnya:
- account suspended;
- onboarding belum selesai;
- account belum memiliki tenant;
- account sedang disabled.
Ini berbeda dari:
Panel::canAccess()yang merepresentasikan aturan milik Panel.
Gunakan PanelUser untuk rule yang berlaku secara konsisten terhadap account di seluruh Panel.
Contoh Minimal
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use PandaPanel\Contracts\PanelUser;
use PandaPanel\Core\Panel;
final class User
extends Authenticatable
implements PanelUser
{
public function canAccessPanel(
Panel $panel
): bool {
return ! $this->suspended;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Account suspended akan mendapat:
403 Forbiddenpada seluruh Panel.
Tidak ada registration tambahan.
Framework mendeteksinya melalui:
instanceof PanelUserInterface
namespace PandaPanel\Contracts;
use PandaPanel\Core\Panel;
interface PanelUser
{
public function canAccessPanel(
Panel $panel
): bool;
}2
3
4
5
6
7
8
9
10
| Parameter | Type | Arti |
|---|---|---|
$panel | PandaPanel\Core\Panel | Panel yang sedang ingin dimasuki |
| return | bool | false menghasilkan 403 |
Karena Panel object dikirim, satu method dapat menjawab berbeda untuk beberapa Panel:
public function canAccessPanel(
Panel $panel
): bool {
return match (
$panel->getId()
) {
'admin' =>
$this->is_admin
&& ! $this->suspended,
'portal' =>
$this
->hasVerifiedEmail(),
default =>
! $this->suspended,
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Gunakan:
$panel->getId()sebagai discriminator bila memungkinkan.
Panel path dapat berubah, sedangkan id seharusnya stabil.
Di Mana Contract Diperiksa
Implementasi utama:
public function isAccessibleTo(
?Authenticatable $user
): bool {
if (
$user instanceof PanelUser
&& ! $user
->canAccessPanel(
$this
)
) {
return false;
}
return $this->canAccess === null
|| ($this->canAccess)(
$user
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Panel::isAccessibleTo() digunakan dari:
| Caller | Kapan | Jika false |
|---|---|---|
ResolvePanel | setiap request ke Panel | 403 |
SharePanelData | membangun Panel switcher | Panel tidak ditampilkan |
PanelManager::firstAccessibleTo() | menentukan landing Panel | Panel dilewati |
panel:user | setelah membuat account | warning |
ResolvePanel berjalan setelah user diketahui dan sebelum:
Panel::boot()Flow:
$manager
->setCurrentPanel(
$panel
);
abort_unless(
$panel
->isAccessibleTo(
$request->user()
),
403
);
$panel->boot();2
3
4
5
6
7
8
9
10
11
12
13
14
User yang tidak boleh masuk tidak dapat memicu boot work Panel.
Karena check dilakukan di middleware, semua route Panel otomatis ikut terlindungi:
- Page;
- Resource;
- Action endpoint;
- search;
- upload;
- export.
Dua Rule Harus Sama-Sama Mengizinkan
Rule lain berada di Panel:
use Illuminate\Contracts\Auth\Authenticatable;
$panel->canAccess(
static fn (
?Authenticatable $user
): bool =>
$user?->is_admin
=== true
);2
3
4
5
6
7
8
9
API:
public function canAccess(
Closure $callback
): self;
public function isAccessibleTo(
?Authenticatable $user
): bool;2
3
4
5
6
7
Pembagian responsibility:
| Rule | Cocok untuk | Lokasi |
|---|---|---|
Panel::canAccess() | "Panel ini khusus admin" | Panel Provider |
PanelUser::canAccessPanel() | "Account ini sedang suspended" | User Model |
Keduanya menggunakan logika:
ANDPanel tidak dapat mengoverride User Model yang menolak.
User Model juga tidak dapat mengoverride Panel yang menolak.
Model yang tidak mengimplementasikan PanelUser tidak ditolak oleh contract ini.
Guest Tidak Memanggil Contract
null instanceof PanelUseradalah false.
Jadi untuk guest hanya Panel callback yang diperiksa.
Ini penting untuk Panel dengan Login Page.
Contoh callback yang bermasalah:
fn (
?Authenticatable $user
) =>
$user?->is_admin
=== true2
3
4
5
Untuk guest:
falseAkibatnya /admin/login dapat menghasilkan 403.
Jika Panel memiliki own Login Page, izinkan guest secara eksplisit:
->canAccess(
static fn (
?Authenticatable $user
): bool =>
$user === null
|| $user->is_admin
=== true
)2
3
4
5
6
7
8
PanelUser sendiri tidak memiliki masalah tersebut karena contract tidak pernah dipanggil untuk null user.
Bukan Permission System
Panel access adalah pintu masuk, bukan granular authorization.
Pertanyaan lebih spesifik tetap memiliki boundary masing-masing:
| Pertanyaan | Dijawab oleh |
|---|---|
| Boleh list Resource? | Resource::canViewAny() |
| Boleh melihat record? | Resource::canView() |
| Boleh create/edit/delete? | Resource abilities |
| Boleh membuka Page? | Page::canAccess() |
| Boleh melihat Widget? | Widget::canView() |
Jangan menggunakan:
canAccessPanel()untuk rule seperti:
boleh mengedit userGunakan Resource/Policy authorization.
Testing
use PandaPanel\Contracts\PanelUser;
use PandaPanel\Core\Panel;
it(
'lets a user model refuse a panel for itself',
function (): void {
$user =
new class
extends User
implements PanelUser
{
protected $table =
'users';
public function canAccessPanel(
Panel $panel
): bool {
return $panel
->getId()
!== 'door';
}
};
$user
->forceFill(
User::factory()
->make()
->getAttributes()
)
->save();
$this
->actingAs($user)
->get('/door')
->assertForbidden();
}
);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
Tanpa contract:
expect(
$panel
->isAccessibleTo(
User::factory()
->make()
)
)
->toBeTrue();2
3
4
5
6
7
8
Test langsung tanpa HTTP:
use PandaPanel\Core\PanelManager;
app(
PanelManager::class
)
->get('admin')
->isAccessibleTo(
$user
);
app(
PanelManager::class
)
->firstAccessibleTo(
$user
)
?->getId();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Hal yang Perlu Diperhatikan
- Dipanggil pada setiap Panel request. Hindari query mahal.
- Dapat dipanggil di luar HTTP request, misalnya switcher dan
panel:user. - Jangan bergantung pada
request()->route(). - false menghasilkan 403, bukan redirect.
- Contract juga memengaruhi Panel auth Page untuk signed-in user.
- Rule
hasVerifiedEmail()dapat memblokir verification notice milik Panel. - Refusing bukan hiding. Navigation memiliki authorization sendiri.
- Rule khusus satu Panel biasanya lebih tepat ditulis pada
Panel::canAccess().