fix: API 메뉴 권한 로직을 mng와 동일하게 수정
- MenuService, MemberService 권한 조회 로직 재작성 - 부서 권한: permission_overrides 테이블 사용 (Department 타입) - 개인 권한: permission_overrides 테이블 사용 (User 타입) - 권한 계산: (역할 ∪ 부서 ∪ 개인ALLOW) - 개인DENY - User model_type을 'App\Models\User'로 하드코딩 (mng 호환)
This commit is contained in:
@@ -237,88 +237,96 @@ public static function getUserInfoForLogin(int $userId): array
|
||||
})->values()->toArray(),
|
||||
];
|
||||
|
||||
// 4. 메뉴 권한 체크 (menu:{menu_id}.view 패턴)
|
||||
// 4-1. 사용자 역할 기반 권한
|
||||
$userRolePermissions = DB::table('model_has_roles')
|
||||
->join('role_has_permissions', 'model_has_roles.role_id', '=', 'role_has_permissions.role_id')
|
||||
// 4. 메뉴 권한 체크 (mng UserPermissionService와 동일한 로직)
|
||||
$now = now();
|
||||
|
||||
// 4-1. 역할 권한 (user_roles 테이블)
|
||||
$rolePermissions = DB::table('user_roles')
|
||||
->join('role_has_permissions', 'user_roles.role_id', '=', 'role_has_permissions.role_id')
|
||||
->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
|
||||
->where('model_has_roles.model_type', User::class)
|
||||
->where('model_has_roles.model_id', $userId)
|
||||
->where('model_has_roles.tenant_id', $tenant->id)
|
||||
->where('user_roles.user_id', $userId)
|
||||
->where('user_roles.tenant_id', $tenant->id)
|
||||
->whereNull('user_roles.deleted_at')
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->select('permissions.name');
|
||||
|
||||
// 4-2. 사용자 직접 권한
|
||||
$userDirectPermissions = DB::table('model_has_permissions')
|
||||
->join('permissions', 'model_has_permissions.permission_id', '=', 'permissions.id')
|
||||
->where('model_has_permissions.model_type', User::class)
|
||||
->where('model_has_permissions.model_id', $userId)
|
||||
->where('model_has_permissions.tenant_id', $tenant->id)
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->select('permissions.name');
|
||||
|
||||
// 4-3. 부서 역할 기반 권한 (User → department_user → Department → role → permissions)
|
||||
$departmentRolePermissions = DB::table('department_user')
|
||||
->join('model_has_roles', function ($join) {
|
||||
$join->on('department_user.department_id', '=', 'model_has_roles.model_id')
|
||||
->where('model_has_roles.model_type', '=', Department::class);
|
||||
})
|
||||
->join('role_has_permissions', 'model_has_roles.role_id', '=', 'role_has_permissions.role_id')
|
||||
->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
|
||||
->where('department_user.user_id', $userId)
|
||||
->where('department_user.tenant_id', $tenant->id)
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->select('permissions.name');
|
||||
|
||||
// 4-4. 모든 권한 통합 (UNION)
|
||||
$rolePermissions = $userRolePermissions
|
||||
->union($userDirectPermissions)
|
||||
->union($departmentRolePermissions)
|
||||
->pluck('name')
|
||||
->pluck('permissions.name')
|
||||
->toArray();
|
||||
|
||||
// 4-2. Override 권한 (명시적 허용/차단)
|
||||
$overrides = DB::table('permission_overrides')
|
||||
->join('permissions', 'permission_overrides.permission_id', '=', 'permissions.id')
|
||||
// 4-2. 부서 권한 (permission_overrides에서 Department 타입, effect=1)
|
||||
$deptPermissions = DB::table('department_user')
|
||||
->join('permission_overrides', function ($join) use ($now) {
|
||||
$join->on('permission_overrides.model_id', '=', 'department_user.department_id')
|
||||
->where('permission_overrides.model_type', '=', Department::class)
|
||||
->whereNull('permission_overrides.deleted_at')
|
||||
->where('permission_overrides.effect', 1)
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_from')
|
||||
->orWhere('permission_overrides.effective_from', '<=', $now);
|
||||
})
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_to')
|
||||
->orWhere('permission_overrides.effective_to', '>=', $now);
|
||||
});
|
||||
})
|
||||
->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id')
|
||||
->whereNull('department_user.deleted_at')
|
||||
->where('department_user.user_id', $userId)
|
||||
->where('department_user.tenant_id', $tenant->id)
|
||||
->where('permission_overrides.tenant_id', $tenant->id)
|
||||
->where('permission_overrides.model_type', User::class)
|
||||
->where('permission_overrides.model_id', $userId)
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->where(function ($q) {
|
||||
->pluck('permissions.name')
|
||||
->toArray();
|
||||
|
||||
// 4-3. 개인 ALLOW 권한 (permission_overrides에서 User 타입, effect=1)
|
||||
// Note: mng는 App\Models\User를 사용하므로 하드코딩
|
||||
$personalAllows = DB::table('permission_overrides')
|
||||
->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id')
|
||||
->where('permission_overrides.model_type', 'App\\Models\\User')
|
||||
->where('permission_overrides.model_id', $userId)
|
||||
->where('permission_overrides.tenant_id', $tenant->id)
|
||||
->where('permission_overrides.effect', 1)
|
||||
->whereNull('permission_overrides.deleted_at')
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_from')
|
||||
->orWhere('permission_overrides.effective_from', '<=', now());
|
||||
->orWhere('permission_overrides.effective_from', '<=', $now);
|
||||
})
|
||||
->where(function ($q) {
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_to')
|
||||
->orWhere('permission_overrides.effective_to', '>=', now());
|
||||
->orWhere('permission_overrides.effective_to', '>=', $now);
|
||||
})
|
||||
->select('permissions.name', 'permission_overrides.effect')
|
||||
->get()
|
||||
->keyBy('name');
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->pluck('permissions.name')
|
||||
->toArray();
|
||||
|
||||
// 4-3. 최종 권한 계산: (기본 || override allow) && !override deny
|
||||
// 4-4. 개인 DENY 권한 (permission_overrides에서 User 타입, effect=0)
|
||||
// Note: mng는 App\Models\User를 사용하므로 하드코딩
|
||||
$personalDenies = DB::table('permission_overrides')
|
||||
->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id')
|
||||
->where('permission_overrides.model_type', 'App\\Models\\User')
|
||||
->where('permission_overrides.model_id', $userId)
|
||||
->where('permission_overrides.tenant_id', $tenant->id)
|
||||
->where('permission_overrides.effect', 0)
|
||||
->whereNull('permission_overrides.deleted_at')
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_from')
|
||||
->orWhere('permission_overrides.effective_from', '<=', $now);
|
||||
})
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_to')
|
||||
->orWhere('permission_overrides.effective_to', '>=', $now);
|
||||
})
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->pluck('permissions.name')
|
||||
->toArray();
|
||||
|
||||
// 4-5. 최종 권한 계산: (역할 OR 부서 OR 개인ALLOW) - 개인DENY
|
||||
$allAllowed = array_unique(array_merge($rolePermissions, $deptPermissions, $personalAllows));
|
||||
$effectivePermissions = array_diff($allAllowed, $personalDenies);
|
||||
|
||||
// 메뉴 ID 추출
|
||||
$allowedMenuIds = [];
|
||||
$allMenuPermissions = array_unique(array_merge(
|
||||
$rolePermissions,
|
||||
$overrides->keys()->toArray()
|
||||
));
|
||||
|
||||
foreach ($allMenuPermissions as $permName) {
|
||||
foreach ($effectivePermissions as $permName) {
|
||||
if (preg_match('/^menu:(\d+)\.view$/', $permName, $matches)) {
|
||||
$menuId = (int) $matches[1];
|
||||
|
||||
// Override deny 체크
|
||||
if (isset($overrides[$permName]) && $overrides[$permName]->effect === -1) {
|
||||
continue; // 강제 차단
|
||||
}
|
||||
|
||||
// Override allow 또는 기본 Role 권한
|
||||
if (
|
||||
(isset($overrides[$permName]) && $overrides[$permName]->effect === 1) ||
|
||||
in_array($permName, $rolePermissions, true)
|
||||
) {
|
||||
$allowedMenuIds[] = $menuId;
|
||||
}
|
||||
$allowedMenuIds[] = (int) $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,12 +342,12 @@ public static function getUserInfoForLogin(int $userId): array
|
||||
->toArray();
|
||||
}
|
||||
|
||||
// 6. 역할(Role) 정보 조회
|
||||
$roles = DB::table('model_has_roles')
|
||||
->join('roles', 'model_has_roles.role_id', '=', 'roles.id')
|
||||
->where('model_has_roles.model_type', User::class)
|
||||
->where('model_has_roles.model_id', $userId)
|
||||
->where('model_has_roles.tenant_id', $tenant->id)
|
||||
// 6. 역할(Role) 정보 조회 (user_roles 테이블 사용 - mng와 동일)
|
||||
$roles = DB::table('user_roles')
|
||||
->join('roles', 'user_roles.role_id', '=', 'roles.id')
|
||||
->where('user_roles.user_id', $userId)
|
||||
->where('user_roles.tenant_id', $tenant->id)
|
||||
->whereNull('user_roles.deleted_at')
|
||||
->select('roles.id', 'roles.name', 'roles.description')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Commons\Menu;
|
||||
use App\Models\Members\User;
|
||||
use App\Models\Tenants\Department;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
@@ -16,20 +18,32 @@ protected static function tenantId(): ?int
|
||||
|
||||
protected static function actorId(): ?int
|
||||
{
|
||||
$user = app('api_user'); // 컨테이너에 주입된 인증 사용자(객체 or 배열)
|
||||
$uid = app('api_user');
|
||||
|
||||
return is_object($user) ? ($user->id ?? null) : ($user['id'] ?? null);
|
||||
return $uid ? (int) $uid : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 목록 조회
|
||||
* 메뉴 목록 조회 (사용자 권한 기반 필터링)
|
||||
*/
|
||||
public static function index(array $params)
|
||||
{
|
||||
$tenantId = self::tenantId();
|
||||
$userId = self::actorId();
|
||||
|
||||
// 권한이 있는 메뉴 ID 목록 조회
|
||||
$allowedMenuIds = self::getAllowedMenuIds($userId, $tenantId);
|
||||
|
||||
$q = Menu::query()->withShared($tenantId);
|
||||
|
||||
// 권한 기반 필터링 적용
|
||||
if (! empty($allowedMenuIds)) {
|
||||
$q->whereIn('id', $allowedMenuIds);
|
||||
} else {
|
||||
// 권한이 없으면 빈 결과 반환
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
if (array_key_exists('parent_id', $params)) {
|
||||
$q->where('parent_id', $params['parent_id']);
|
||||
}
|
||||
@@ -46,6 +60,110 @@ public static function index(array $params)
|
||||
return $q->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자가 접근 가능한 메뉴 ID 목록 조회 (mng UserPermissionService와 동일한 로직)
|
||||
*/
|
||||
protected static function getAllowedMenuIds(?int $userId, ?int $tenantId): array
|
||||
{
|
||||
if (! $userId || ! $tenantId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$now = now();
|
||||
|
||||
// 1. 역할 권한 (user_roles 테이블)
|
||||
$rolePermissions = DB::table('user_roles')
|
||||
->join('role_has_permissions', 'user_roles.role_id', '=', 'role_has_permissions.role_id')
|
||||
->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
|
||||
->where('user_roles.user_id', $userId)
|
||||
->where('user_roles.tenant_id', $tenantId)
|
||||
->whereNull('user_roles.deleted_at')
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->pluck('permissions.name')
|
||||
->toArray();
|
||||
|
||||
// 2. 부서 권한 (permission_overrides에서 Department 타입, effect=1)
|
||||
$deptPermissions = DB::table('department_user')
|
||||
->join('permission_overrides', function ($join) use ($now) {
|
||||
$join->on('permission_overrides.model_id', '=', 'department_user.department_id')
|
||||
->where('permission_overrides.model_type', '=', Department::class)
|
||||
->whereNull('permission_overrides.deleted_at')
|
||||
->where('permission_overrides.effect', 1)
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_from')
|
||||
->orWhere('permission_overrides.effective_from', '<=', $now);
|
||||
})
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_to')
|
||||
->orWhere('permission_overrides.effective_to', '>=', $now);
|
||||
});
|
||||
})
|
||||
->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id')
|
||||
->whereNull('department_user.deleted_at')
|
||||
->where('department_user.user_id', $userId)
|
||||
->where('department_user.tenant_id', $tenantId)
|
||||
->where('permission_overrides.tenant_id', $tenantId)
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->pluck('permissions.name')
|
||||
->toArray();
|
||||
|
||||
// 3. 개인 ALLOW 권한 (permission_overrides에서 User 타입, effect=1)
|
||||
// Note: mng는 App\Models\User를 사용하므로 하드코딩
|
||||
$personalAllows = DB::table('permission_overrides')
|
||||
->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id')
|
||||
->where('permission_overrides.model_type', 'App\\Models\\User')
|
||||
->where('permission_overrides.model_id', $userId)
|
||||
->where('permission_overrides.tenant_id', $tenantId)
|
||||
->where('permission_overrides.effect', 1)
|
||||
->whereNull('permission_overrides.deleted_at')
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_from')
|
||||
->orWhere('permission_overrides.effective_from', '<=', $now);
|
||||
})
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_to')
|
||||
->orWhere('permission_overrides.effective_to', '>=', $now);
|
||||
})
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->pluck('permissions.name')
|
||||
->toArray();
|
||||
|
||||
// 4. 개인 DENY 권한 (permission_overrides에서 User 타입, effect=0)
|
||||
// Note: mng는 App\Models\User를 사용하므로 하드코딩
|
||||
$personalDenies = DB::table('permission_overrides')
|
||||
->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id')
|
||||
->where('permission_overrides.model_type', 'App\\Models\\User')
|
||||
->where('permission_overrides.model_id', $userId)
|
||||
->where('permission_overrides.tenant_id', $tenantId)
|
||||
->where('permission_overrides.effect', 0)
|
||||
->whereNull('permission_overrides.deleted_at')
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_from')
|
||||
->orWhere('permission_overrides.effective_from', '<=', $now);
|
||||
})
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('permission_overrides.effective_to')
|
||||
->orWhere('permission_overrides.effective_to', '>=', $now);
|
||||
})
|
||||
->where('permissions.name', 'like', 'menu:%.view')
|
||||
->pluck('permissions.name')
|
||||
->toArray();
|
||||
|
||||
// 5. 최종 권한 계산: (역할 OR 부서 OR 개인ALLOW) - 개인DENY
|
||||
$allAllowed = array_unique(array_merge($rolePermissions, $deptPermissions, $personalAllows));
|
||||
$effectivePermissions = array_diff($allAllowed, $personalDenies);
|
||||
|
||||
// 메뉴 ID 추출
|
||||
$allowedMenuIds = [];
|
||||
foreach ($effectivePermissions as $permName) {
|
||||
if (preg_match('/^menu:(\d+)\.view$/', $permName, $matches)) {
|
||||
$allowedMenuIds[] = (int) $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $allowedMenuIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 단건 조회
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user