215 lines
7.4 KiB
PHP
215 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Authz;
|
|
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Validation\Rule;
|
|
use Spatie\Permission\Models\Role;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\PermissionRegistrar;
|
|
use App\Helpers\ApiResponse;
|
|
|
|
class RolePermissionService
|
|
{
|
|
protected static string $guard = 'api';
|
|
|
|
/** 현재 테넌트 컨텍스트로 팀 고정 */
|
|
protected static function setTeam(int $tenantId): void
|
|
{
|
|
app(PermissionRegistrar::class)->setPermissionsTeamId($tenantId);
|
|
}
|
|
|
|
/** 역할 로드 (테넌트/가드 검증) */
|
|
protected static function loadRoleOrError(int $roleId, int $tenantId): ?Role
|
|
{
|
|
$role = Role::query()
|
|
->where('tenant_id', $tenantId)
|
|
->where('guard_name', self::$guard)
|
|
->find($roleId);
|
|
|
|
return $role;
|
|
}
|
|
|
|
/** A) permission_names[] → 그대로 사용
|
|
* B) menus[] + actions[] → "menu:{id}.{act}" 배열로 변환(필요 시 Permission 생성)
|
|
*/
|
|
protected static function resolvePermissionNames(int $tenantId, array $params): array
|
|
{
|
|
$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);
|
|
}
|
|
}
|
|
|
|
if (!empty($params['menus']) && is_array($params['menus']) &&
|
|
!empty($params['actions']) && is_array($params['actions'])) {
|
|
|
|
$allowed = config('authz.menu_actions', ['view','create','update','delete','approve']);
|
|
$acts = array_values(array_unique(array_filter(array_map('trim', $params['actions']))));
|
|
$acts = array_intersect($acts, $allowed);
|
|
|
|
$menuIds = array_values(array_unique(array_map('intval', $params['menus'])));
|
|
|
|
foreach ($menuIds as $mid) {
|
|
foreach ($acts as $act) {
|
|
$names[] = "menu:{$mid}.{$act}";
|
|
}
|
|
}
|
|
}
|
|
|
|
// 빈/중복 제거
|
|
$names = array_values(array_unique(array_filter($names)));
|
|
|
|
// 존재하지 않는 Permission은 생성(tenant+guard 포함)
|
|
foreach ($names as $permName) {
|
|
Permission::firstOrCreate([
|
|
'tenant_id' => $tenantId,
|
|
'guard_name' => self::$guard,
|
|
'name' => $permName,
|
|
]);
|
|
}
|
|
|
|
return $names;
|
|
}
|
|
|
|
/** 역할의 퍼미션 목록 */
|
|
public static function list(int $roleId)
|
|
{
|
|
$tenantId = (int) app('tenant_id');
|
|
|
|
$role = self::loadRoleOrError($roleId, $tenantId);
|
|
if (!$role) {
|
|
return ApiResponse::error('역할을 찾을 수 없습니다.', 404);
|
|
}
|
|
|
|
self::setTeam($tenantId);
|
|
|
|
$perms = $role->permissions()
|
|
->where('tenant_id', $tenantId)
|
|
->where('guard_name', self::$guard)
|
|
->orderBy('name')
|
|
->get(['id','tenant_id','name','guard_name','created_at','updated_at']);
|
|
|
|
return ApiResponse::response('result', $perms);
|
|
}
|
|
|
|
/** 부여 (중복 무시) */
|
|
public static function grant(int $roleId, array $params = [])
|
|
{
|
|
$tenantId = (int) app('tenant_id');
|
|
|
|
$role = self::loadRoleOrError($roleId, $tenantId);
|
|
if (!$role) {
|
|
return ApiResponse::error('역할을 찾을 수 없습니다.', 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 ApiResponse::error($v->errors()->first(), 422);
|
|
}
|
|
if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) {
|
|
return ApiResponse::error('permission_names 또는 menus+actions 중 하나는 필요합니다.', 422);
|
|
}
|
|
|
|
self::setTeam($tenantId);
|
|
|
|
$names = self::resolvePermissionNames($tenantId, $params);
|
|
if (empty($names)) {
|
|
return ApiResponse::error('유효한 퍼미션이 없습니다.', 422);
|
|
}
|
|
|
|
// Spatie: 이름 배열 부여 OK (teams 컨텍스트 적용됨)
|
|
$role->givePermissionTo($names);
|
|
|
|
return ApiResponse::response('success');
|
|
}
|
|
|
|
/** 회수 (없는 건 무시) */
|
|
public static function revoke(int $roleId, array $params = [])
|
|
{
|
|
$tenantId = (int) app('tenant_id');
|
|
|
|
$role = self::loadRoleOrError($roleId, $tenantId);
|
|
if (!$role) {
|
|
return ApiResponse::error('역할을 찾을 수 없습니다.', 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 ApiResponse::error($v->errors()->first(), 422);
|
|
}
|
|
if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) {
|
|
return ApiResponse::error('permission_names 또는 menus+actions 중 하나는 필요합니다.', 422);
|
|
}
|
|
|
|
self::setTeam($tenantId);
|
|
|
|
$names = self::resolvePermissionNames($tenantId, $params);
|
|
if (empty($names)) {
|
|
return ApiResponse::error('유효한 퍼미션이 없습니다.', 422);
|
|
}
|
|
|
|
$role->revokePermissionTo($names);
|
|
|
|
return ApiResponse::response('success');
|
|
}
|
|
|
|
/** 동기화(완전 교체) */
|
|
public static function sync(int $roleId, array $params = [])
|
|
{
|
|
$tenantId = (int) app('tenant_id');
|
|
|
|
$role = self::loadRoleOrError($roleId, $tenantId);
|
|
if (!$role) {
|
|
return ApiResponse::error('역할을 찾을 수 없습니다.', 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 ApiResponse::error($v->errors()->first(), 422);
|
|
}
|
|
if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) {
|
|
return ApiResponse::error('permission_names 또는 menus+actions 중 하나는 필요합니다.', 422);
|
|
}
|
|
|
|
self::setTeam($tenantId);
|
|
|
|
$names = self::resolvePermissionNames($tenantId, $params); // 존재하지 않으면 생성
|
|
// 동기화
|
|
$role->syncPermissions($names);
|
|
|
|
return ApiResponse::response('success');
|
|
}
|
|
}
|