「權限管理不就是判斷『這個使用者有沒有某個權限字串』嗎?」
昨天看到的 BannerPolicy 確實是這樣運作的——每個方法委派給底層權限套件,查一個字串權限存不存在。但這個模式有一個邊界情況沒處理到:如果系統裡有一個「角色管理」的權限,理論上任何被授予這個權限的人,都能編輯任何角色——包括最高權限的那個角色本身。 今天要看這個系統怎麼處理這個邊界情況。
RolePolicy 怎麼對「最高權限角色」做特殊判斷Gate 之外,Policy 也可以定義框架不認得的自訂方法昨天看到的權限模式是:一個角色被指派一組權限字串,Policy 方法檢查使用者有沒有對應字串。這個模式對大多數資料類型都夠用——輪播圖、新聞、頁面,權限跟資料是分開的兩件事。
但「角色」這個資料本身有一個特殊之處:如果有一個「角色管理」的權限字串,任何被指派這個字串的角色,都能編輯所有角色——包括最高權限角色。也就是說,只要有人不小心(或蓄意)把「角色管理」權限指派給一個非最高權限的角色,那個角色就能把自己的權限一路提升到跟最高權限角色一樣,形成一個權限提升的漏洞。單純的字串權限判斷,防不了這種情況。
RolePolicy 對特定角色的額外檢查這個系統的 RolePolicy 對「最高權限角色」(角色 ID 為 1)做了特殊處理:
class RolePolicy
{
public function view(User $user, Role $role): ?bool
{
if ($role->id !== 1) {
return $user->can('View:Role');
}
return $user->hasRole('Super Admin');
}
public function update(User $user, Role $role): ?bool
{
if ($role->id !== 1) {
return $user->can('Update:Role');
}
return $user->hasRole('Super Admin');
}
public function delete(User $user, Role $role): ?bool
{
if ($role->id !== 1) {
return $user->can('Delete:Role');
}
return false;
}
}
每個方法先判斷「這個角色是不是最高權限角色本身」——如果不是,走一般的權限字串判斷;如果是,就完全不理會一般的權限字串,改用 $user->hasRole('Super Admin') 這種直接檢查角色本身的方式。注意 delete 方法對最高權限角色永遠回傳 false——這個角色連被授予「角色管理」權限的最高權限使用者,都不能刪除,這是一個完全寫死、沒有任何權限字串能繞過的規則。
這個設計的核心邏輯是:「能不能管理角色」跟「能不能碰到最高權限角色」是兩件不同層級的事,不能用同一組判斷邏輯處理。 一般角色的權限可以透過資料庫設定靈活調整(誰能看、誰能改、誰能刪),但「最高權限角色」牽涉到整個系統的權限邊界,這個邊界不應該被資料庫裡的權限設定意外打開——就算有人不小心把「角色管理」權限指派給一個不該有這麼高權限的角色,那個角色也不會因此能碰到最高權限角色本身。
public function update(User $user, Role $role): ?bool
{
return $user->can('Update:Role');
}
只要有人被指派了 Update:Role 這個權限字串,不管針對哪個角色都能更新——包括最高權限角色。權限提升的路徑完全暢通。
public function update(User $user, Role $role): ?bool
{
if ($role->id !== 1) {
return $user->can('Update:Role');
}
return $user->hasRole('Super Admin');
}
一般角色走靈活的權限字串判斷,最高權限角色本身則跳過權限字串、直接檢查身份。
RolePolicy 還定義了一個框架不會自動呼叫的方法:
public function assignSuperAdmin(User $user): bool
{
return $user->isSuperAdmin();
}
viewAny/view/create/update/delete 這幾個名稱,Laravel 框架認得,會在對應情境自動呼叫;但 Policy 類別本身只是一個普通的 PHP 類別,可以自訂任何方法名稱,只要呼叫端明確用 $user->can('assignSuperAdmin') 呼叫就行。這個方法用在「後台編輯角色時,能不能把最高權限指派給某個角色」這個更細緻的判斷情境,不屬於標準 CRUD 動作,所以用一個自訂方法名稱表達。
你的系統裡有沒有一個「權限管理權限本身」的邊界情況——一個被授予管理權限的角色,能不能把自己的權限一路提升到最高?如果可以,這是刻意設計的,還是被忽略的漏洞?
RolePolicy 對「最高權限角色」(id=1)額外做硬編碼檢查,跳過一般權限字串判斷delete 方法對最高權限角色永遠回傳 false,這是完全寫死、任何權限字串都無法繞過的規則明天要看一支很單純但很真實的 Command——怎麼用一支一次性的指令,修正資料遷移後留下的欄位缺失。