refactor: [authz] 역할/권한 API 품질 개선
- Validator::make를 FormRequest로 분리 (6개 생성) - 하드코딩 한글 문자열을 i18n 키로 교체 - RoleMenuPermission 데드코드 제거 - Role 모델 SpatieRole 상속으로 일원화 - 권한 변경 후 캐시 무효화 추가 (AccessService::bumpVersion) - 미문서화 8개 Swagger 엔드포인트 추가 - 역할/권한 라우트에 perm.map+permission 미들웨어 추가
This commit is contained in:
@@ -2,10 +2,8 @@
|
||||
|
||||
namespace App\Services\Authz;
|
||||
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Models\Permissions\Role;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
|
||||
class RolePermissionService
|
||||
@@ -18,6 +16,13 @@ protected static function setTeam(int $tenantId): void
|
||||
app(PermissionRegistrar::class)->setPermissionsTeamId($tenantId);
|
||||
}
|
||||
|
||||
/** 권한 캐시 무효화 */
|
||||
protected static function invalidateCache(int $tenantId): void
|
||||
{
|
||||
AccessService::bumpVersion($tenantId);
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
/** 역할 로드 (테넌트/가드 검증) */
|
||||
protected static function loadRoleOrError(int $roleId, int $tenantId): ?Role
|
||||
{
|
||||
@@ -37,7 +42,6 @@ protected static function resolvePermissionNames(int $tenantId, array $params):
|
||||
$names = [];
|
||||
|
||||
if (! empty($params['permission_names']) && is_array($params['permission_names'])) {
|
||||
// 문자열 배열만 추림
|
||||
foreach ($params['permission_names'] as $n) {
|
||||
if (is_string($n) && $n !== '') {
|
||||
$names[] = trim($n);
|
||||
@@ -83,7 +87,7 @@ public static function list(int $roleId)
|
||||
|
||||
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||
if (! $role) {
|
||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||
return ['error' => __('error.role.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
self::setTeam($tenantId);
|
||||
@@ -104,37 +108,20 @@ public static function grant(int $roleId, array $params = [])
|
||||
|
||||
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||
if (! $role) {
|
||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||
}
|
||||
|
||||
// 유효성: 두 방식 중 하나만 요구하진 않지만, 최소 하나는 있어야 함
|
||||
$v = Validator::make($params, [
|
||||
'permission_names' => 'sometimes|array',
|
||||
'permission_names.*' => 'string|min:1',
|
||||
'menus' => 'sometimes|array',
|
||||
'menus.*' => 'integer|min:1',
|
||||
'actions' => 'sometimes|array',
|
||||
'actions.*' => [
|
||||
'string', Rule::in(config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve'])),
|
||||
],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ['error' => $v->errors()->first(), 'code' => 422];
|
||||
}
|
||||
if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) {
|
||||
return ['error' => 'permission_names 또는 menus+actions 중 하나는 필요합니다.', 'code' => 422];
|
||||
return ['error' => __('error.role.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
self::setTeam($tenantId);
|
||||
|
||||
$names = self::resolvePermissionNames($tenantId, $params);
|
||||
if (empty($names)) {
|
||||
return ['error' => '유효한 퍼미션이 없습니다.', 'code' => 422];
|
||||
return ['error' => __('error.role.no_valid_permissions'), 'code' => 422];
|
||||
}
|
||||
|
||||
// Spatie: 이름 배열 부여 OK (teams 컨텍스트 적용됨)
|
||||
$role->givePermissionTo($names);
|
||||
|
||||
self::invalidateCache($tenantId);
|
||||
|
||||
return 'success';
|
||||
}
|
||||
|
||||
@@ -145,35 +132,20 @@ public static function revoke(int $roleId, array $params = [])
|
||||
|
||||
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||
if (! $role) {
|
||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'permission_names' => 'sometimes|array',
|
||||
'permission_names.*' => 'string|min:1',
|
||||
'menus' => 'sometimes|array',
|
||||
'menus.*' => 'integer|min:1',
|
||||
'actions' => 'sometimes|array',
|
||||
'actions.*' => [
|
||||
'string', Rule::in(config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve'])),
|
||||
],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ['error' => $v->errors()->first(), 'code' => 422];
|
||||
}
|
||||
if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) {
|
||||
return ['error' => 'permission_names 또는 menus+actions 중 하나는 필요합니다.', 'code' => 422];
|
||||
return ['error' => __('error.role.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
self::setTeam($tenantId);
|
||||
|
||||
$names = self::resolvePermissionNames($tenantId, $params);
|
||||
if (empty($names)) {
|
||||
return ['error' => '유효한 퍼미션이 없습니다.', 'code' => 422];
|
||||
return ['error' => __('error.role.no_valid_permissions'), 'code' => 422];
|
||||
}
|
||||
|
||||
$role->revokePermissionTo($names);
|
||||
|
||||
self::invalidateCache($tenantId);
|
||||
|
||||
return 'success';
|
||||
}
|
||||
|
||||
@@ -184,32 +156,16 @@ public static function sync(int $roleId, array $params = [])
|
||||
|
||||
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||
if (! $role) {
|
||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'permission_names' => 'sometimes|array',
|
||||
'permission_names.*' => 'string|min:1',
|
||||
'menus' => 'sometimes|array',
|
||||
'menus.*' => 'integer|min:1',
|
||||
'actions' => 'sometimes|array',
|
||||
'actions.*' => [
|
||||
'string', Rule::in(config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve'])),
|
||||
],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ['error' => $v->errors()->first(), 'code' => 422];
|
||||
}
|
||||
if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) {
|
||||
return ['error' => 'permission_names 또는 menus+actions 중 하나는 필요합니다.', 'code' => 422];
|
||||
return ['error' => __('error.role.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
self::setTeam($tenantId);
|
||||
|
||||
$names = self::resolvePermissionNames($tenantId, $params); // 존재하지 않으면 생성
|
||||
// 동기화
|
||||
$names = self::resolvePermissionNames($tenantId, $params);
|
||||
$role->syncPermissions($names);
|
||||
|
||||
self::invalidateCache($tenantId);
|
||||
|
||||
return 'success';
|
||||
}
|
||||
|
||||
@@ -226,7 +182,7 @@ public static function matrix(int $roleId)
|
||||
|
||||
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||
if (! $role) {
|
||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||
return ['error' => __('error.role.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
self::setTeam($tenantId);
|
||||
@@ -276,7 +232,6 @@ public static function menus()
|
||||
->orderBy('id', 'asc')
|
||||
->get(['id', 'parent_id', 'name', 'url', 'icon', 'sort_order', 'is_active']);
|
||||
|
||||
// 트리 구조를 플랫한 배열로 변환 (depth 정보 포함)
|
||||
$flatMenus = self::flattenMenuTree($menus->toArray(), null, 0);
|
||||
|
||||
return [
|
||||
@@ -298,7 +253,6 @@ protected static function flattenMenuTree(array $menus, ?int $parentId = null, i
|
||||
$menu['has_children'] = count(array_filter($menus, fn ($m) => $m['parent_id'] === $menu['id'])) > 0;
|
||||
$result[] = $menu;
|
||||
|
||||
// 자식 메뉴 재귀적으로 추가
|
||||
$children = self::flattenMenuTree($menus, $menu['id'], $depth + 1);
|
||||
$result = array_merge($result, $children);
|
||||
}
|
||||
@@ -313,15 +267,7 @@ public static function toggle(int $roleId, array $params = [])
|
||||
|
||||
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||
if (! $role) {
|
||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||
}
|
||||
|
||||
$v = Validator::make($params, [
|
||||
'menu_id' => 'required|integer|min:1',
|
||||
'permission_type' => ['required', 'string', Rule::in(self::getPermissionTypes())],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return ['error' => $v->errors()->first(), 'code' => 422];
|
||||
return ['error' => __('error.role.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
$menuId = (int) $params['menu_id'];
|
||||
@@ -345,14 +291,12 @@ public static function toggle(int $roleId, array $params = [])
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
// 권한 제거
|
||||
\Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||
->where('role_id', $roleId)
|
||||
->where('permission_id', $permission->id)
|
||||
->delete();
|
||||
$newValue = false;
|
||||
} else {
|
||||
// 권한 부여
|
||||
\Illuminate\Support\Facades\DB::table('role_has_permissions')->insert([
|
||||
'role_id' => $roleId,
|
||||
'permission_id' => $permission->id,
|
||||
@@ -363,6 +307,8 @@ public static function toggle(int $roleId, array $params = [])
|
||||
// 하위 메뉴에 권한 전파
|
||||
self::propagateToChildren($roleId, $menuId, $permissionType, $newValue, $tenantId);
|
||||
|
||||
self::invalidateCache($tenantId);
|
||||
|
||||
return [
|
||||
'menu_id' => $menuId,
|
||||
'permission_type' => $permissionType,
|
||||
@@ -386,7 +332,6 @@ protected static function propagateToChildren(int $roleId, int $parentMenuId, st
|
||||
]);
|
||||
|
||||
if ($value) {
|
||||
// 권한 부여
|
||||
$exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||
->where('role_id', $roleId)
|
||||
->where('permission_id', $permission->id)
|
||||
@@ -399,14 +344,12 @@ protected static function propagateToChildren(int $roleId, int $parentMenuId, st
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// 권한 제거
|
||||
\Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||
->where('role_id', $roleId)
|
||||
->where('permission_id', $permission->id)
|
||||
->delete();
|
||||
}
|
||||
|
||||
// 재귀적으로 하위 메뉴 처리
|
||||
self::propagateToChildren($roleId, $child->id, $permissionType, $value, $tenantId);
|
||||
}
|
||||
}
|
||||
@@ -418,7 +361,7 @@ public static function allowAll(int $roleId)
|
||||
|
||||
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||
if (! $role) {
|
||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||
return ['error' => __('error.role.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
self::setTeam($tenantId);
|
||||
@@ -438,7 +381,6 @@ public static function allowAll(int $roleId)
|
||||
'tenant_id' => $tenantId,
|
||||
]);
|
||||
|
||||
// 권한 부여
|
||||
$exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||
->where('role_id', $roleId)
|
||||
->where('permission_id', $permission->id)
|
||||
@@ -453,6 +395,8 @@ public static function allowAll(int $roleId)
|
||||
}
|
||||
}
|
||||
|
||||
self::invalidateCache($tenantId);
|
||||
|
||||
return 'success';
|
||||
}
|
||||
|
||||
@@ -463,7 +407,7 @@ public static function denyAll(int $roleId)
|
||||
|
||||
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||
if (! $role) {
|
||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||
return ['error' => __('error.role.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
self::setTeam($tenantId);
|
||||
@@ -491,6 +435,8 @@ public static function denyAll(int $roleId)
|
||||
}
|
||||
}
|
||||
|
||||
self::invalidateCache($tenantId);
|
||||
|
||||
return 'success';
|
||||
}
|
||||
|
||||
@@ -501,7 +447,7 @@ public static function reset(int $roleId)
|
||||
|
||||
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||
if (! $role) {
|
||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||
return ['error' => __('error.role.not_found'), 'code' => 404];
|
||||
}
|
||||
|
||||
self::setTeam($tenantId);
|
||||
@@ -522,7 +468,6 @@ public static function reset(int $roleId)
|
||||
'tenant_id' => $tenantId,
|
||||
]);
|
||||
|
||||
// 권한 부여
|
||||
$exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||
->where('role_id', $roleId)
|
||||
->where('permission_id', $permission->id)
|
||||
@@ -536,6 +481,8 @@ public static function reset(int $roleId)
|
||||
}
|
||||
}
|
||||
|
||||
self::invalidateCache($tenantId);
|
||||
|
||||
return 'success';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user