fix: [users] 슈퍼관리자 보호 기능 복원 라우트 수정

- routes/api.php: 8개 엔티티의 restore 라우트를 super.admin 미들웨어 밖으로 이동
  - tenants, departments, users, menus, boards
  - pm/projects, pm/tasks, pm/issues
- UserService.canAccessUser(): withTrashed() 적용하여 soft-deleted 사용자 권한 체크 가능
- UserPermissionService.canModifyUser(): withTrashed() 적용 (일관성 유지)

권한 정책:
- 복원 (Restore): 일반관리자 가능
- 영구삭제 (Force Delete): 슈퍼관리자 전용

버그 수정:
- 302 Found 에러 해결 (미들웨어 블로킹)
- soft-deleted 사용자 복원 시 권한 체크 실패 해결

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-01 00:13:12 +09:00
parent bbf34e9f3f
commit b39e8b5f2c
14 changed files with 376 additions and 53 deletions

View File

@@ -50,6 +50,14 @@ public function index(Request $request): JsonResponse
*/
public function show(int $id): JsonResponse
{
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자 정보 조회 불가
if (! $this->userService->canAccessUser($id)) {
return response()->json([
'success' => false,
'message' => '사용자를 찾을 수 없습니다.',
], 404);
}
$user = $this->userService->getUserById($id);
if (! $user) {
@@ -165,6 +173,14 @@ public function destroy(int $id): JsonResponse
*/
public function restore(Request $request, int $id): JsonResponse
{
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자 복원 불가 (존재하지 않는 것처럼)
if (! $this->userService->canAccessUser($id)) {
return response()->json([
'success' => false,
'message' => '사용자를 찾을 수 없습니다.',
], 404);
}
$this->userService->restoreUser($id);
// HTMX 요청 시 테이블 새로고침 트리거
@@ -187,6 +203,14 @@ public function restore(Request $request, int $id): JsonResponse
*/
public function modal(Request $request, int $id): JsonResponse
{
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자 정보 조회 불가
if (! $this->userService->canAccessUser($id)) {
return response()->json([
'success' => false,
'message' => '사용자를 찾을 수 없습니다.',
], 404);
}
$user = $this->userService->getUserForModal($id);
if (! $user) {

View File

@@ -62,6 +62,14 @@ public function getMatrix(Request $request)
return view('user-permissions.partials.empty-state');
}
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자 권한 조회 불가
if (! $this->userPermissionService->canModifyUser($userId)) {
return response()->json([
'success' => false,
'message' => '슈퍼관리자의 권한은 조회할 수 없습니다.',
], 403);
}
// 사용자의 tenant_id로 메뉴 필터링
$tenantId = $this->getEffectiveTenantId($request, $userId);
@@ -89,6 +97,14 @@ public function toggle(Request $request)
$guardName = $this->getValidatedGuardName($request);
$tenantId = $this->getEffectiveTenantId($request, $userId);
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자 권한 수정 불가
if (! $this->userPermissionService->canModifyUser($userId)) {
return response()->json([
'success' => false,
'message' => '슈퍼관리자의 권한은 수정할 수 없습니다.',
], 403);
}
$newValue = $this->userPermissionService->togglePermission(
$userId,
$menuId,
@@ -117,6 +133,14 @@ public function allowAll(Request $request)
$guardName = $this->getValidatedGuardName($request);
$tenantId = $this->getEffectiveTenantId($request, $userId);
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자 권한 수정 불가
if (! $this->userPermissionService->canModifyUser($userId)) {
return response()->json([
'success' => false,
'message' => '슈퍼관리자의 권한은 수정할 수 없습니다.',
], 403);
}
$this->userPermissionService->allowAllPermissions($userId, $tenantId, $guardName);
// 전체 매트릭스 다시 로드
@@ -139,6 +163,14 @@ public function denyAll(Request $request)
$guardName = $this->getValidatedGuardName($request);
$tenantId = $this->getEffectiveTenantId($request, $userId);
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자 권한 수정 불가
if (! $this->userPermissionService->canModifyUser($userId)) {
return response()->json([
'success' => false,
'message' => '슈퍼관리자의 권한은 수정할 수 없습니다.',
], 403);
}
$this->userPermissionService->denyAllPermissions($userId, $tenantId, $guardName);
// 전체 매트릭스 다시 로드
@@ -161,6 +193,14 @@ public function reset(Request $request)
$guardName = $this->getValidatedGuardName($request);
$tenantId = $this->getEffectiveTenantId($request, $userId);
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자 권한 수정 불가
if (! $this->userPermissionService->canModifyUser($userId)) {
return response()->json([
'success' => false,
'message' => '슈퍼관리자의 권한은 수정할 수 없습니다.',
], 403);
}
$this->userPermissionService->resetToDefaultPermissions($userId, $tenantId, $guardName);
// 전체 매트릭스 다시 로드

View File

@@ -41,17 +41,17 @@ public function create(): View
*/
public function edit(int $id): View
{
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자에 접근 불가 (존재하지 않는 것처럼)
if (! $this->userService->canAccessUser($id)) {
abort(404, '사용자를 찾을 수 없습니다.');
}
$user = $this->userService->getUserById($id);
if (! $user) {
abort(404, '사용자를 찾을 수 없습니다.');
}
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자를 수정하려는 경우 차단
if ($user->is_super_admin && ! auth()->user()?->is_super_admin) {
abort(403, '슈퍼관리자는 수정할 수 없습니다.');
}
$tenantId = session('selected_tenant_id');
// 역할/부서 목록 (테넌트별)