Files
star-cloud/app/Http/Controllers/Admin/PermissionController.php
sky121113 f3b2c3e018
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 54s
[FIX] 遷移機台授權為獨立模組:修復變數命名、補齊多語系並強化多租戶數據隔離
2026-03-30 15:30:46 +08:00

540 lines
22 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class PermissionController extends Controller
{
// 權限角色設定
public function roles()
{
$per_page = request()->input('per_page', 10);
$user = auth()->user();
$query = \App\Models\System\Role::query()->with(['permissions', 'users', 'company']);
// 租戶隔離:租戶只能看到自己公司的角色
if (!$user->isSystemAdmin()) {
$query->where('company_id', $user->company_id);
}
// 搜尋:角色名稱
if ($search = request()->input('search')) {
$query->where('name', 'like', "%{$search}%");
}
// 篩選:公司名稱 (僅限系統管理員)
if ($user->isSystemAdmin() && request()->filled('company_id')) {
if (request()->company_id === 'system') {
$query->where('is_system', true);
} else {
$query->where('company_id', request()->company_id);
}
}
$roles = $query->latest()->paginate($per_page)->withQueryString();
$companies = $user->isSystemAdmin() ? \App\Models\System\Company::all() : collect();
// 權限遞迴約束:租戶管理員只能看到並指派自己擁有的權限
$permissionQuery = \Spatie\Permission\Models\Permission::query();
if (!$user->isSystemAdmin()) {
$permissionQuery->whereIn('name', $user->getAllPermissions()->pluck('name'));
}
// 權限分組邏輯
$all_permissions = $permissionQuery->get()
->groupBy(function($perm) {
if (str_starts_with($perm->name, 'menu.')) {
// 主選單權限menu.xxx (兩段)
// 子選單權限menu.xxx.yyy (三段)
return 'menu';
}
return 'other';
});
// 根據路由決定標題
$title = request()->routeIs('*.sub-account-roles') ? __('Sub Account Roles') : __('Role Settings');
$currentUserRoleIds = $user->roles->pluck('id')->toArray();
return view('admin.permission.roles', compact('roles', 'all_permissions', 'title', 'currentUserRoleIds', 'companies'));
}
/**
* Show the form for creating a new role.
*/
public function createRole()
{
$role = new \App\Models\System\Role();
$user = auth()->user();
// 權限遞迴約束
$permissionQuery = \Spatie\Permission\Models\Permission::query();
if (!$user->isSystemAdmin()) {
$permissionQuery->whereIn('name', $user->getAllPermissions()->pluck('name'));
}
$all_permissions = $permissionQuery->get()->groupBy(fn($p) => str_starts_with($p->name, 'menu.') ? 'menu' : 'other');
$title = request()->routeIs('*.sub-account-roles.create') ? __('Create Sub Account Role') : __('Create New Role');
$back_url = request()->routeIs('*.sub-account-roles.create') ? route('admin.data-config.sub-account-roles') : route('admin.permission.roles');
return view('admin.permission.roles-edit', compact('role', 'all_permissions', 'title', 'back_url'));
}
/**
* Show the form for editing the specified role.
*/
public function editRole($id)
{
$role = \App\Models\System\Role::findOrFail($id);
$user = auth()->user();
// 權限遞迴約束:租戶管理員只能看到並指派自己擁有的權限
$permissionQuery = \Spatie\Permission\Models\Permission::query();
if (!$user->isSystemAdmin()) {
$permissionQuery->whereIn('name', $user->getAllPermissions()->pluck('name'));
}
// 權限分組邏輯
$all_permissions = $permissionQuery->get()
->groupBy(function($perm) {
if (str_starts_with($perm->name, 'menu.')) {
return 'menu';
}
return 'other';
});
// 根據路由決定標題
$title = request()->routeIs('*.sub-account-roles.edit') ? __('Edit Sub Account Role') : __('Edit Role Permissions');
// 麵包屑/返回路徑
$back_url = request()->routeIs('*.sub-account-roles.edit') ? route('admin.data-config.sub-account-roles') : route('admin.permission.roles');
return view('admin.permission.roles-edit', compact('role', 'all_permissions', 'title', 'back_url'));
}
/**
* Store a newly created role in storage.
*/
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',
\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',
]);
$role = \App\Models\System\Role::query()->create([
'name' => $validated['name'],
'guard_name' => 'web',
'company_id' => $is_system ? null : auth()->user()->company_id,
'is_system' => $is_system,
]);
if (!empty($validated['permissions'])) {
$perms = $validated['permissions'];
// 權限遞迴約束驗證
if (!auth()->user()->isSystemAdmin()) {
$currentUserPerms = auth()->user()->getAllPermissions()->pluck('name');
if (collect($perms)->diff($currentUserPerms)->isNotEmpty()) {
return redirect()->back()->with('error', __('You cannot assign permissions you do not possess.'));
}
}
// 如果不是系統角色,排除主選單的系統權限
if (!$is_system) {
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
}
$role->syncPermissions($perms);
}
$target_route = request()->routeIs('*.sub-account-roles.*') ? 'admin.data-config.sub-account-roles' : 'admin.permission.roles';
return redirect()->route($target_route)->with('success', __('Role created successfully.'));
}
/**
* Update the specified role in storage.
*/
public function updateRole(Request $request, $id)
{
$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',
\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',
]);
if ($role->name === 'super-admin') {
return redirect()->back()->with('error', __('The Super Admin role is immutable.'));
}
if (!auth()->user()->isSystemAdmin() && $role->is_system) {
return redirect()->back()->with('error', __('System roles cannot be modified by tenant administrators.'));
}
$is_system = auth()->user()->isSystemAdmin() ? $request->boolean('is_system') : $role->is_system;
$updateData = [
'name' => $validated['name'],
'is_system' => $is_system,
'company_id' => $is_system ? null : $role->company_id,
];
$role->update($updateData);
$perms = $validated['permissions'] ?? [];
// 權限遞迴約束驗證
if (!auth()->user()->isSystemAdmin()) {
$currentUserPerms = auth()->user()->getAllPermissions()->pluck('name');
if (collect($perms)->diff($currentUserPerms)->isNotEmpty()) {
return redirect()->back()->with('error', __('You cannot assign permissions you do not possess.'));
}
}
// 如果不是系統角色,排除主選單的系統權限
if (!$is_system) {
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
}
$role->syncPermissions($perms);
$target_route = request()->routeIs('*.sub-account-roles.*') ? 'admin.data-config.sub-account-roles' : 'admin.permission.roles';
return redirect()->route($target_route)->with('success', __('Role updated successfully.'));
}
/**
* Remove the specified role from storage.
*/
public function destroyRole($id)
{
$role = \App\Models\System\Role::findOrFail($id);
if ($role->name === 'super-admin') {
return redirect()->back()->with('error', __('The Super Admin role cannot be deleted.'));
}
if (!auth()->user()->isSystemAdmin() && $role->is_system) {
return redirect()->back()->with('error', __('System roles cannot be deleted by tenant administrators.'));
}
if ($role->users()->count() > 0) {
return redirect()->back()->with('error', __('Cannot delete role with active users.'));
}
$role->delete();
return redirect()->back()->with('success', __('Role deleted successfully.'));
}
// 帳號管理
public function accounts(Request $request)
{
$query = \App\Models\System\User::query()->with(['company', 'roles', 'machines']);
// 租戶隔離:如果不是系統管理員,則只看自己公司的成員
if (!auth()->user()->isSystemAdmin()) {
$query->where('company_id', auth()->user()->company_id);
}
// 搜尋
if ($search = $request->input('search')) {
$query->where(function($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('username', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
});
}
// 公司篩選 (僅限 super-admin)
if (auth()->user()->isSystemAdmin() && $request->filled('company_id')) {
$query->where('company_id', $request->company_id);
}
$per_page = $request->input('per_page', 10);
$users = $query->latest()->paginate($per_page)->withQueryString();
$companies = auth()->user()->isSystemAdmin() ? \App\Models\System\Company::all() : collect();
$roles_query = \App\Models\System\Role::query();
if (!auth()->user()->isSystemAdmin()) {
$roles_query->forCompany(auth()->user()->company_id);
}
$roles = $roles_query->get();
// 根據路由決定標題
$title = request()->routeIs('*.sub-accounts') ? __('Sub Account Management') : __('Account Management');
return view('admin.data-config.accounts', compact('users', 'companies', 'roles', 'title'));
}
/**
* Store a newly created account in storage.
*/
public function storeAccount(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'username' => 'required|string|max:255|unique:users,username',
'email' => 'nullable|email|max:255|unique:users,email',
'password' => 'required|string|min:8',
'role' => 'required|string',
'status' => 'required|boolean',
'company_id' => 'nullable|exists:companies,id',
'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) {
// 如果是租戶帳號,絕對不能指派超級管理員角色 (super-admin)
if ($role->name === 'super-admin') {
return redirect()->back()->with('error', __('Super-admin role 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 在幫空白公司開帳號時觸發)
$company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
if ($company_id && $role && $role->company_id === null && $role->name !== 'super-admin') {
// 檢查該公司是否已有名為「管理員」的角色
$existingRole = \App\Models\System\Role::where('company_id', $company_id)
->where('name', '管理員')
->first();
if (!$existingRole) {
// 克隆範本為該公司的「管理員」
$newRole = \App\Models\System\Role::query()->create([
'name' => '管理員',
'guard_name' => 'web',
'company_id' => $company_id,
'is_system' => false,
]);
$newRole->syncPermissions($role->getPermissionNames());
$role = $newRole;
} else {
// 如果已存在名為「管理員」的角色,則直接使用它
$role = $existingRole;
}
}
$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' => $company_id,
'phone' => $validated['phone'] ?? null,
'is_admin' => (auth()->user()->isSystemAdmin() && !empty($validated['company_id'])),
]);
$user->assignRole($role);
return redirect()->back()->with('success', __('Account created successfully.'));
}
/**
* Update the specified account in storage.
*/
public function updateAccount(Request $request, $id)
{
$user = \App\Models\System\User::findOrFail($id);
if ($user->hasRole('super-admin') && !auth()->user()->hasRole('super-admin')) {
return redirect()->back()->with('error', __('System super admin accounts can only be modified by other super admins.'));
}
$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,
'password' => 'nullable|string|min:8',
'role' => 'required|string',
'status' => 'required|boolean',
'company_id' => 'nullable|exists:companies,id',
'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) {
// 租戶層級排除 super-admin
if ($roleObj->name === 'super-admin') {
return redirect()->back()->with('error', __('Super-admin role 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 global system roles can be assigned to platform administrative accounts.'));
}
}
}
$updateData = [
'name' => $validated['name'],
'username' => $validated['username'],
'email' => $validated['email'],
'status' => $validated['status'],
'phone' => $validated['phone'] ?? null,
];
// 只有系統管理員在編輯租戶帳號時,且該帳號原本不是管理員,才可能觸發標記(視需求而定)
// 這裡我們維持 storeAccount 的邏輯:如果是系統管理員幫公司「開站」或「首配」,才自動標記
// 為求嚴謹,我們檢查該公司是否已經有 is_admin如果沒有當前這個人可以是第一個
if (auth()->user()->isSystemAdmin() && !empty($validated['company_id']) && !$user->is_admin) {
$hasAdmin = \App\Models\System\User::where('company_id', $validated['company_id'])
->where('is_admin', true)
->exists();
if (!$hasAdmin) {
$updateData['is_admin'] = true;
}
}
if (auth()->user()->isSystemAdmin()) {
// 防止超級管理員不小心把自己綁定到租客公司或降級
if ($user->id === auth()->id()) {
$updateData['company_id'] = null;
$validated['role'] = 'super-admin';
} else {
$updateData['company_id'] = $validated['company_id'];
}
}
if (!empty($validated['password'])) {
$updateData['password'] = \Illuminate\Support\Facades\Hash::make($validated['password']);
}
// 角色初始化與克隆邏輯
$target_company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
if ($target_company_id && $roleObj && $roleObj->company_id === null && $roleObj->name !== 'super-admin') {
// 檢查該公司是否已有名為「管理員」的角色
$existingRole = \App\Models\System\Role::where('company_id', $target_company_id)
->where('name', '管理員')
->first();
if (!$existingRole) {
$newRole = \App\Models\System\Role::query()->create([
'name' => '管理員',
'guard_name' => 'web',
'company_id' => $target_company_id,
'is_system' => false,
'is_admin' => true,
]);
$newRole->syncPermissions($roleObj->getPermissionNames());
$roleObj = $newRole;
} else {
$roleObj = $existingRole;
}
}
$user->update($updateData);
// 如果是編輯自己且原本是超級管理員,強制保留 super-admin 角色
if ($user->id === auth()->id() && auth()->user()->isSystemAdmin()) {
$user->syncRoles(['super-admin']);
} else {
$user->syncRoles([$roleObj]);
}
return redirect()->back()->with('success', __('Account updated successfully.'));
}
/**
* Remove the specified account from storage.
*/
public function destroyAccount($id)
{
$user = \App\Models\System\User::findOrFail($id);
if ($user->hasRole('super-admin') && !auth()->user()->hasRole('super-admin')) {
return redirect()->back()->with('error', __('System super admin accounts can only be deleted by other super admins.'));
}
if ($user->id === auth()->id()) {
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.'));
}
public function toggleAccountStatus($id)
{
$user = \App\Models\System\User::findOrFail($id);
// 非超級管理員禁止切換 Super Admin 狀態
if ($user->hasRole('super-admin') && !auth()->user()->hasRole('super-admin')) {
return back()->with('error', __('Only Super Admins can change other Super Admin status.'));
}
$user->status = $user->status ? 0 : 1;
$user->save();
$statusText = $user->status ? __('Enabled') : __('Disabled');
return back()->with('success', __('Account :name status has been changed to :status.', ['name' => $user->name, 'status' => $statusText]));
}
}