[DOCS] 更新 RBAC 實作規範與角色初始化流程建議
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 55s
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 55s
This commit is contained in:
@@ -16,10 +16,7 @@ class PermissionController extends Controller
|
||||
|
||||
// 租戶隔離:租戶只能看到自己公司的角色 + 系統角色 (company_id is null)
|
||||
if (!$user->isSystemAdmin()) {
|
||||
$query->where(function($q) use ($user) {
|
||||
$q->where('company_id', $user->company_id)
|
||||
->orWhereNull('company_id');
|
||||
});
|
||||
$query->where('company_id', $user->company_id);
|
||||
}
|
||||
|
||||
// 搜尋:角色名稱
|
||||
@@ -58,15 +55,21 @@ class PermissionController extends Controller
|
||||
*/
|
||||
public function storeRole(Request $request)
|
||||
{
|
||||
$is_system = auth()->user()->isSystemAdmin() && $request->boolean('is_system');
|
||||
$company_id = $is_system ? null : auth()->user()->company_id;
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255|unique:roles,name',
|
||||
'name' => [
|
||||
'required', 'string', 'max:255',
|
||||
\Illuminate\Validation\Rule::unique('roles', 'name')->where(function ($query) use ($company_id) {
|
||||
return $query->where('company_id', $company_id);
|
||||
})
|
||||
],
|
||||
'permissions' => 'nullable|array',
|
||||
'permissions.*' => 'string|exists:permissions,name',
|
||||
]);
|
||||
|
||||
$is_system = auth()->user()->isSystemAdmin() && $request->boolean('is_system');
|
||||
|
||||
$role = \App\Models\System\Role::create([
|
||||
$role = \App\Models\System\Role::query()->create([
|
||||
'name' => $validated['name'],
|
||||
'guard_name' => 'web',
|
||||
'company_id' => $is_system ? null : auth()->user()->company_id,
|
||||
@@ -92,8 +95,18 @@ class PermissionController extends Controller
|
||||
{
|
||||
$role = \App\Models\System\Role::findOrFail($id);
|
||||
|
||||
$is_system = $role->is_system;
|
||||
$company_id = $role->company_id;
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255|unique:roles,name,' . $id,
|
||||
'name' => [
|
||||
'required', 'string', 'max:255',
|
||||
\Illuminate\Validation\Rule::unique('roles', 'name')
|
||||
->ignore($id)
|
||||
->where(function ($query) use ($company_id) {
|
||||
return $query->where('company_id', $company_id);
|
||||
})
|
||||
],
|
||||
'permissions' => 'nullable|array',
|
||||
'permissions.*' => 'string|exists:permissions,name',
|
||||
]);
|
||||
@@ -177,10 +190,7 @@ class PermissionController extends Controller
|
||||
$companies = auth()->user()->isSystemAdmin() ? \App\Models\System\Company::all() : collect();
|
||||
$roles_query = \App\Models\System\Role::where('name', '!=', 'super-admin');
|
||||
if (!auth()->user()->isSystemAdmin()) {
|
||||
$roles_query->where(function($q) {
|
||||
$q->where('company_id', auth()->user()->company_id)
|
||||
->orWhereNull('company_id');
|
||||
});
|
||||
$roles_query->where('company_id', auth()->user()->company_id);
|
||||
}
|
||||
$roles = $roles_query->get();
|
||||
|
||||
@@ -198,7 +208,7 @@ class PermissionController extends Controller
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255|unique:users,username',
|
||||
'email' => 'nullable|email|max:255|unique:users,email',
|
||||
'email' => 'required|email|max:255|unique:users,email',
|
||||
'password' => 'required|string|min:8',
|
||||
'role' => 'required|string',
|
||||
'status' => 'required|boolean',
|
||||
@@ -206,17 +216,73 @@ class PermissionController extends Controller
|
||||
'phone' => 'nullable|string|max:20',
|
||||
]);
|
||||
|
||||
$company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
|
||||
|
||||
// 查找角色:優先尋找該公司的角色,若無則尋找全域範本
|
||||
$role = \App\Models\System\Role::where('name', $validated['role'])
|
||||
->where(function($q) use ($company_id) {
|
||||
$q->where('company_id', $company_id)->orWhereNull('company_id');
|
||||
})
|
||||
->first();
|
||||
|
||||
if (!$role) {
|
||||
return redirect()->back()->with('error', __('Role not found.'));
|
||||
}
|
||||
|
||||
// 驗證角色與公司的匹配性 (RBAC Safeguard)
|
||||
if ($company_id !== null) {
|
||||
// 如果是租戶帳號,不能選各項系統角色 (is_system = 1)
|
||||
if ($role->is_system) {
|
||||
return redirect()->back()->with('error', __('System roles cannot be assigned to tenant accounts.'));
|
||||
}
|
||||
// 如果角色有特定的 company_id,必須匹配
|
||||
if ($role->company_id !== null && $role->company_id != $company_id) {
|
||||
return redirect()->back()->with('error', __('This role belongs to another company and cannot be assigned.'));
|
||||
}
|
||||
} else {
|
||||
// 如果是系統層級帳號,只能選系統角色 (is_system = 1)
|
||||
if (!$role->is_system) {
|
||||
return redirect()->back()->with('error', __('Only system roles can be assigned to platform administrative accounts.'));
|
||||
}
|
||||
}
|
||||
|
||||
// 角色初始化與克隆邏輯 (只有 super-admin 在幫空白公司開帳號時觸發)
|
||||
$role_to_assign = $validated['role'];
|
||||
$company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
|
||||
|
||||
if ($company_id && $role && !$role->is_system && $role->company_id === null) {
|
||||
// 檢查該公司是否已有名為「管理員」的角色
|
||||
$existingRole = \App\Models\System\Role::where('company_id', $company_id)
|
||||
->where('name', '管理員')
|
||||
->first();
|
||||
|
||||
if (!$existingRole) {
|
||||
// 克隆範本為該公司的「管理員」
|
||||
$clonedRole = \App\Models\System\Role::query()->create([
|
||||
'name' => '管理員',
|
||||
'guard_name' => 'web',
|
||||
'company_id' => $company_id,
|
||||
'is_system' => false,
|
||||
]);
|
||||
$clonedRole->syncPermissions($role->permissions);
|
||||
$role_to_assign = '管理員';
|
||||
} else {
|
||||
// 如果已存在名為「管理員」的角色,則直接使用它
|
||||
$role_to_assign = '管理員';
|
||||
}
|
||||
}
|
||||
|
||||
$user = \App\Models\System\User::create([
|
||||
'name' => $validated['name'],
|
||||
'username' => $validated['username'],
|
||||
'email' => $validated['email'],
|
||||
'password' => \Illuminate\Support\Facades\Hash::make($validated['password']),
|
||||
'status' => $validated['status'],
|
||||
'company_id' => auth()->user()->isSystemAdmin() ? $validated['company_id'] : auth()->user()->company_id,
|
||||
'company_id' => $company_id,
|
||||
'phone' => $validated['phone'],
|
||||
]);
|
||||
|
||||
$user->assignRole($validated['role']);
|
||||
$user->assignRole($role_to_assign);
|
||||
|
||||
return redirect()->back()->with('success', __('Account created successfully.'));
|
||||
}
|
||||
@@ -235,7 +301,7 @@ class PermissionController extends Controller
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255|unique:users,username,' . $id,
|
||||
'email' => 'nullable|email|max:255|unique:users,email,' . $id,
|
||||
'email' => 'required|email|max:255|unique:users,email,' . $id,
|
||||
'password' => 'nullable|string|min:8',
|
||||
'role' => 'required|string',
|
||||
'status' => 'required|boolean',
|
||||
@@ -243,6 +309,35 @@ class PermissionController extends Controller
|
||||
'phone' => 'nullable|string|max:20',
|
||||
]);
|
||||
|
||||
$target_company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
|
||||
|
||||
// 查找角色:優先尋找該公司的角色,若無則尋找全域範本
|
||||
$roleObj = \App\Models\System\Role::where('name', $validated['role'])
|
||||
->where(function($q) use ($target_company_id) {
|
||||
$q->where('company_id', $target_company_id)->orWhereNull('company_id');
|
||||
})
|
||||
->first();
|
||||
|
||||
if (!$roleObj) {
|
||||
return redirect()->back()->with('error', __('Role not found.'));
|
||||
}
|
||||
|
||||
// 驗證角色與公司的匹配性 (RBAC Safeguard)
|
||||
if ($user->id !== auth()->id()) { // 排除編輯自己 (super-admin 有特殊邏輯)
|
||||
if ($target_company_id !== null) {
|
||||
if ($roleObj->is_system) {
|
||||
return redirect()->back()->with('error', __('System roles cannot be assigned to tenant accounts.'));
|
||||
}
|
||||
if ($roleObj->company_id !== null && $roleObj->company_id != $target_company_id) {
|
||||
return redirect()->back()->with('error', __('This role belongs to another company and cannot be assigned.'));
|
||||
}
|
||||
} else {
|
||||
if (!$roleObj->is_system) {
|
||||
return redirect()->back()->with('error', __('Only system roles can be assigned to platform administrative accounts.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$updateData = [
|
||||
'name' => $validated['name'],
|
||||
'username' => $validated['username'],
|
||||
@@ -265,13 +360,37 @@ class PermissionController extends Controller
|
||||
$updateData['password'] = \Illuminate\Support\Facades\Hash::make($validated['password']);
|
||||
}
|
||||
|
||||
// 角色初始化與克隆邏輯
|
||||
$role_to_assign = $validated['role'];
|
||||
$target_company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
|
||||
|
||||
if ($target_company_id && $roleObj && !$roleObj->is_system && $roleObj->company_id === null) {
|
||||
// 檢查該公司是否已有名為「管理員」的角色
|
||||
$existingRole = \App\Models\System\Role::where('company_id', $target_company_id)
|
||||
->where('name', '管理員')
|
||||
->first();
|
||||
|
||||
if (!$existingRole) {
|
||||
$clonedRole = \App\Models\System\Role::query()->create([
|
||||
'name' => '管理員',
|
||||
'guard_name' => 'web',
|
||||
'company_id' => $target_company_id,
|
||||
'is_system' => false,
|
||||
]);
|
||||
$clonedRole->syncPermissions($roleObj->permissions);
|
||||
$role_to_assign = '管理員';
|
||||
} else {
|
||||
$role_to_assign = '管理員';
|
||||
}
|
||||
}
|
||||
|
||||
$user->update($updateData);
|
||||
|
||||
// 如果是編輯自己且原本是超級管理員,強制保留 super-admin 角色
|
||||
if ($user->id === auth()->id() && auth()->user()->isSystemAdmin()) {
|
||||
$user->syncRoles(['super-admin']);
|
||||
} else {
|
||||
$user->syncRoles([$validated['role']]);
|
||||
$user->syncRoles([$role_to_assign]);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Account updated successfully.'));
|
||||
@@ -292,6 +411,12 @@ class PermissionController extends Controller
|
||||
return redirect()->back()->with('error', __('You cannot delete your own account.'));
|
||||
}
|
||||
|
||||
// 為了解決軟刪除導致的唯一索引佔用問題,刪除前先重命名唯一欄位
|
||||
$timestamp = now()->getTimestamp();
|
||||
$user->username = $user->username . '.deleted.' . $timestamp;
|
||||
$user->email = $user->email . '.deleted.' . $timestamp;
|
||||
$user->save();
|
||||
|
||||
$user->delete();
|
||||
|
||||
return redirect()->back()->with('success', __('Account deleted successfully.'));
|
||||
|
||||
Reference in New Issue
Block a user