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

@@ -6,10 +6,15 @@
use App\Models\User;
use App\Models\UserRole;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class UserService
{
public function __construct(
private readonly ArchiveService $archiveService
) {}
/**
* 사용자 목록 조회 (페이지네이션)
*/
@@ -18,6 +23,11 @@ public function getUsers(array $filters = [], int $perPage = 15): LengthAwarePag
$tenantId = session('selected_tenant_id');
$query = User::query()->withTrashed();
// 슈퍼관리자 보호: 일반관리자는 슈퍼관리자를 볼 수 없음
if (! auth()->user()?->is_super_admin) {
$query->where('is_super_admin', false);
}
// 역할/부서 관계 eager loading (테넌트별)
if ($tenantId) {
$query->with([
@@ -223,15 +233,25 @@ public function restoreUser(int $id): bool
/**
* 사용자 영구 삭제 (슈퍼관리자 전용)
*
* 1. 사용자 데이터를 아카이브에 저장
* 2. 관련 데이터 삭제
* 3. 사용자 영구 삭제
*/
public function forceDeleteUser(int $id): bool
{
$user = User::withTrashed()->findOrFail($id);
// 관련 데이터 먼저 삭제
$user->tenants()->detach(); // user_tenants 관계 삭제
return DB::transaction(function () use ($user) {
// 1. 아카이브에 저장 (복원 가능하도록)
$this->archiveService->archiveUser($user);
return $user->forceDelete();
// 2. 관련 데이터 삭제
$user->tenants()->detach(); // user_tenants 관계 삭제
// 3. 사용자 영구 삭제
return $user->forceDelete();
});
}
/**
@@ -368,6 +388,11 @@ public function getActiveUsers()
$tenantId = session('selected_tenant_id');
$query = User::query()->where('is_active', true);
// 슈퍼관리자 보호: 일반관리자는 슈퍼관리자를 볼 수 없음
if (! auth()->user()?->is_super_admin) {
$query->where('is_super_admin', false);
}
// 테넌트 필터링 (user_tenants pivot을 통한 필터링)
if ($tenantId) {
$query->whereHas('tenants', function ($q) use ($tenantId) {
@@ -377,4 +402,24 @@ public function getActiveUsers()
return $query->orderBy('name')->get();
}
/**
* 슈퍼관리자 보호: 일반관리자가 슈퍼관리자에 접근할 수 있는지 확인
*
* @param int $targetUserId 대상 사용자 ID
* @return bool true면 접근 가능, false면 접근 불가
*/
public function canAccessUser(int $targetUserId): bool
{
// withTrashed()를 사용하여 soft-deleted 사용자도 확인 (복원 시 필요)
$targetUser = User::withTrashed()->find($targetUserId);
$currentUser = auth()->user();
// 대상 사용자가 슈퍼관리자이고 현재 사용자가 슈퍼관리자가 아니면 접근 불가
if ($targetUser?->is_super_admin && ! $currentUser?->is_super_admin) {
return false;
}
return true;
}
}