Files
sam-manage/app/Services/RolePermissionService.php
hskwon 69b04ae041 feat: 권한 관리 시스템에 Guard 선택 기능 추가
- 부서 권한 아키텍처 재설계 (Role → Department 직접 할당)
- Guard 선택 기능 구현 (API/Web 분리)
- 초기화(Reset) 기능 추가 (view 권한만 허용)
- UI 개선 (Guard 선택기 + 구분선)

부서 권한 관리:
- Department 모델에 HasPermissions trait 추가
- DepartmentPermissionService 완전 재작성 (직접 할당 패턴)
- model_has_permissions 테이블 직접 사용
- guard_name 파라미터 모든 메서드에 추가

역할 권한 관리:
- RolePermissionService에 guard_name 파라미터 추가
- RolePermissionController에 guard_name 처리 구현
- Guard별 독립적인 권한 매트릭스 관리

UI 개선:
- Guard 선택 드롭다운 (Web/API) 추가
- 모든 HTMX 요청에 guard_name 포함
- Guard 변경 시 자동 새로고침 기능
- 시각적 구분선으로 UI 가독성 향상
2025-11-25 20:53:53 +09:00

335 lines
11 KiB
PHP

<?php
namespace App\Services;
use App\Models\Commons\Menu;
use App\Models\Permission;
use Illuminate\Support\Facades\DB;
class RolePermissionService
{
/**
* 권한 유형 목록
*/
private array $permissionTypes = ['view', 'create', 'update', 'delete', 'approve', 'export', 'manage'];
/**
* 역할의 권한 매트릭스 조회
*
* @param int $roleId 역할 ID
* @param int|null $tenantId 테넌트 ID
* @param string $guardName 가드 이름
* @return array 메뉴별 권한 상태 매트릭스
*/
public function getRolePermissionMatrix(int $roleId, ?int $tenantId = null, string $guardName = 'web'): array
{
$query = DB::table('role_has_permissions')
->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
->where('role_has_permissions.role_id', $roleId)
->where('permissions.guard_name', $guardName)
->where('permissions.name', 'like', 'menu:%');
if ($tenantId) {
$query->where('permissions.tenant_id', $tenantId);
}
$rolePermissions = $query->pluck('permissions.name')->toArray();
$permissions = [];
foreach ($rolePermissions as $permName) {
if (preg_match('/^menu:(\d+)\.(\w+)$/', $permName, $matches)) {
$menuId = (int) $matches[1];
$type = $matches[2];
if (! isset($permissions[$menuId])) {
$permissions[$menuId] = [];
}
$permissions[$menuId][$type] = true;
}
}
return $permissions;
}
/**
* 특정 메뉴의 특정 권한 토글
*
* @param int $roleId 역할 ID
* @param int $menuId 메뉴 ID
* @param string $permissionType 권한 유형
* @param int|null $tenantId 테넌트 ID
* @param string $guardName 가드 이름
* @return bool 토글 후 상태 (true: 허용, false: 거부)
*/
public function togglePermission(int $roleId, int $menuId, string $permissionType, ?int $tenantId = null, string $guardName = 'web'): bool
{
$permissionName = "menu:{$menuId}.{$permissionType}";
// 권한 생성 또는 조회
$permission = Permission::firstOrCreate(
['name' => $permissionName, 'guard_name' => $guardName, 'tenant_id' => $tenantId],
['created_by' => auth()->id()]
);
// 현재 권한 상태 확인
$exists = DB::table('role_has_permissions')
->where('role_id', $roleId)
->where('permission_id', $permission->id)
->exists();
if ($exists) {
// 권한 제거
DB::table('role_has_permissions')
->where('role_id', $roleId)
->where('permission_id', $permission->id)
->delete();
$newValue = false;
} else {
// 권한 부여
DB::table('role_has_permissions')->insert([
'role_id' => $roleId,
'permission_id' => $permission->id,
]);
$newValue = true;
}
// 하위 메뉴에 권한 전파
$this->propagateToChildren($roleId, $menuId, $permissionType, $newValue, $tenantId, $guardName);
return $newValue;
}
/**
* 하위 메뉴에 권한 전파
*
* @param int $roleId 역할 ID
* @param int $parentMenuId 부모 메뉴 ID
* @param string $permissionType 권한 유형
* @param bool $value 권한 값 (true: 허용, false: 거부)
* @param int|null $tenantId 테넌트 ID
* @param string $guardName 가드 이름
*/
protected function propagateToChildren(int $roleId, int $parentMenuId, string $permissionType, bool $value, ?int $tenantId = null, string $guardName = 'web'): void
{
$children = Menu::where('parent_id', $parentMenuId)->get();
foreach ($children as $child) {
$permissionName = "menu:{$child->id}.{$permissionType}";
$permission = Permission::firstOrCreate(
['name' => $permissionName, 'guard_name' => $guardName, 'tenant_id' => $tenantId],
['created_by' => auth()->id()]
);
if ($value) {
// 권한 부여
$exists = DB::table('role_has_permissions')
->where('role_id', $roleId)
->where('permission_id', $permission->id)
->exists();
if (! $exists) {
DB::table('role_has_permissions')->insert([
'role_id' => $roleId,
'permission_id' => $permission->id,
]);
}
} else {
// 권한 제거
DB::table('role_has_permissions')
->where('role_id', $roleId)
->where('permission_id', $permission->id)
->delete();
}
// 재귀적으로 하위 메뉴 처리
$this->propagateToChildren($roleId, $child->id, $permissionType, $value, $tenantId, $guardName);
}
}
/**
* 모든 권한 허용
*
* @param int $roleId 역할 ID
* @param int|null $tenantId 테넌트 ID
* @param string $guardName 가드 이름
*/
public function allowAllPermissions(int $roleId, ?int $tenantId = null, string $guardName = 'web'): void
{
$query = Menu::where('is_active', 1);
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
$menus = $query->get();
foreach ($menus as $menu) {
foreach ($this->permissionTypes as $type) {
$permissionName = "menu:{$menu->id}.{$type}";
$permission = Permission::firstOrCreate(
['name' => $permissionName, 'guard_name' => $guardName, 'tenant_id' => $tenantId],
['created_by' => auth()->id()]
);
// 권한 부여
$exists = DB::table('role_has_permissions')
->where('role_id', $roleId)
->where('permission_id', $permission->id)
->exists();
if (! $exists) {
DB::table('role_has_permissions')->insert([
'role_id' => $roleId,
'permission_id' => $permission->id,
]);
}
}
}
}
/**
* 모든 권한 거부
*
* @param int $roleId 역할 ID
* @param int|null $tenantId 테넌트 ID
* @param string $guardName 가드 이름
*/
public function denyAllPermissions(int $roleId, ?int $tenantId = null, string $guardName = 'web'): void
{
$query = Menu::where('is_active', 1);
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
$menus = $query->get();
foreach ($menus as $menu) {
foreach ($this->permissionTypes as $type) {
$permissionName = "menu:{$menu->id}.{$type}";
$permission = Permission::where('name', $permissionName)
->where('guard_name', $guardName)
->first();
if ($permission) {
DB::table('role_has_permissions')
->where('role_id', $roleId)
->where('permission_id', $permission->id)
->delete();
}
}
}
}
/**
* 기본 권한으로 초기화 (view만 허용)
*
* @param int $roleId 역할 ID
* @param int|null $tenantId 테넌트 ID
* @param string $guardName 가드 이름
*/
public function resetToDefaultPermissions(int $roleId, ?int $tenantId = null, string $guardName = 'web'): void
{
// 1. 먼저 모든 권한 제거
$this->denyAllPermissions($roleId, $tenantId, $guardName);
// 2. view 권한만 허용
$query = Menu::where('is_active', 1);
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
$menus = $query->get();
foreach ($menus as $menu) {
$permissionName = "menu:{$menu->id}.view";
$permission = Permission::firstOrCreate(
['name' => $permissionName, 'guard_name' => $guardName, 'tenant_id' => $tenantId],
['created_by' => auth()->id()]
);
// 이미 존재하는지 확인
$exists = DB::table('role_has_permissions')
->where('role_id', $roleId)
->where('permission_id', $permission->id)
->exists();
if (! $exists) {
DB::table('role_has_permissions')->insert([
'role_id' => $roleId,
'permission_id' => $permission->id,
]);
}
}
}
/**
* 메뉴 트리 조회 (권한 매트릭스 표시용)
*
* @param int|null $tenantId 테넌트 ID
* @return \Illuminate\Support\Collection 메뉴 트리
*/
public function getMenuTree(?int $tenantId = null): \Illuminate\Support\Collection
{
$query = Menu::with('parent')
->where('is_active', 1);
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
$allMenus = $query->orderBy('sort_order', 'asc')
->orderBy('id', 'asc')
->get();
// depth 계산하여 플랫한 구조로 변환
return $this->flattenMenuTree($allMenus);
}
/**
* 트리 구조를 플랫한 배열로 변환 (depth 정보 포함)
*
* @param \Illuminate\Support\Collection $menus 메뉴 컬렉션
* @param int|null $parentId 부모 메뉴 ID
* @param int $depth 현재 깊이
*/
private function flattenMenuTree(\Illuminate\Support\Collection $menus, ?int $parentId = null, int $depth = 0): \Illuminate\Support\Collection
{
$result = collect();
$filteredMenus = $menus->where('parent_id', $parentId)->sortBy('sort_order');
foreach ($filteredMenus as $menu) {
$menu->depth = $depth;
// 자식 메뉴 존재 여부 확인
$menu->has_children = $menus->where('parent_id', $menu->id)->count() > 0;
$result->push($menu);
// 자식 메뉴 재귀적으로 추가
$children = $this->flattenMenuTree($menus, $menu->id, $depth + 1);
$result = $result->merge($children);
}
return $result;
}
/**
* 특정 역할의 활성 메뉴 권한 확인
*
* @param int $roleId 역할 ID
* @param int $menuId 메뉴 ID
* @param string $permissionType 권한 유형
* @return bool 권한 존재 여부
*/
public function hasPermission(int $roleId, int $menuId, string $permissionType): bool
{
$permissionName = "menu:{$menuId}.{$permissionType}";
return DB::table('role_has_permissions')
->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
->where('role_has_permissions.role_id', $roleId)
->where('permissions.name', $permissionName)
->exists();
}
}