본문으로 건너뛰기

Permission 모듈

📝 초안 (Draft)

이 문서는 검토 중입니다. 내용이 변경될 수 있습니다.

권한 시스템 (Level 0~6, ADR-058) Core 모듈의 상세 사용법입니다.

개요

Permission 모듈은 Multi-SaaS Kit의 **권한 시스템 (Level 0~6, ADR-058)**을 구현합니다. 숫자가 낮을수록 높은 권한을 가지며, 상위 권한은 하위 권한을 포함합니다.

packages/core/Base/Permission/
├── Contracts/
│ └── PermissionServiceInterface.php
├── Enums/
│ └── UserLevel.php # 권한 분류 (Level 0~6) Enum
├── Middleware/
│ └── EnsureUserLevel.php # 라우트 권한 체크
├── Policies/
│ ├── UserPolicy.php # User CRUD 권한
│ └── TenantOwnedPolicy.php # 테넌트 소유 모델
├── Services/
│ └── PermissionService.php # 권한 검증 서비스
└── Traits/
└── HasLevel.php # User 모델 Trait

UserLevel Enum

권한 레벨을 정의하는 Enum입니다.

namespace App\Core\Base\Permission\Enums;

enum UserLevel: int
{
case PLATFORM_ADMIN = 0; // 플랫폼 운영자
case SAAS_ADMIN = 1; // SaaS 서비스 관리자
case TENANT_ADMIN = 2; // 테넌트(고객사) 관리자
case ORGANIZATION_ADMIN = 3; // 조직/부서 관리자
case WORKSPACE_ADMIN = 4; // 프로젝트/작업공간 관리자
case GROUP_LEADER = 5; // 팀/그룹 리더
case MEMBER = 6; // 일반 멤버
}

Enum 메서드

use App\Core\Base\Permission\Enums\UserLevel;

// 레벨 값 가져오기
UserLevel::TENANT_ADMIN->value; // 2

// 레벨 라벨 가져오기
UserLevel::TENANT_ADMIN->label(); // "Tenant Admin"

// 값으로 Enum 생성
UserLevel::tryFromValue(2); // UserLevel::TENANT_ADMIN

// 플랫폼 레벨 여부 (0-1)
UserLevel::TENANT_ADMIN->isPlatformLevel(); // false

// 테넌트 레벨 여부 (2-6)
UserLevel::TENANT_ADMIN->isTenantLevel(); // true

// 테넌트 격리 우회 가능 여부
UserLevel::SAAS_ADMIN->canBypassTenantIsolation(); // true

HasLevel Trait

User 모델에 적용하여 권한 분류 (Level 0~6) 기능을 추가합니다.

적용 방법

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Core\Base\Permission\Traits\HasLevel;
use App\Core\Contracts\UserInterface;

class User extends Authenticatable implements UserInterface
{
use HasLevel;

protected $fillable = [
'name', 'email', 'password', 'level', 'tenant_id',
];

protected $casts = [
'level' => 'integer',
];
}

권한 확인 메서드

$user = auth()->user();

// 현재 레벨 가져오기
$user->getLevel(); // int: 2
$user->getUserLevel(); // UserLevel::TENANT_ADMIN
$user->getLevelLabel(); // "Tenant Admin"

// 최소 레벨 확인 (숫자가 작거나 같으면 true)
$user->hasLevelAtLeast(UserLevel::TENANT_ADMIN); // true
$user->hasLevelAtLeast(UserLevel::SAAS_ADMIN); // false

// 정확한 레벨 확인
$user->hasLevelExact(UserLevel::TENANT_ADMIN); // true

// 범위 확인
$user->hasLevelBetween(
UserLevel::TENANT_ADMIN, // min
UserLevel::GROUP_LEADER // max
); // true (Level 2-5 사이)

역할 확인 메서드

$user->isPlatformAdmin();   // Level 0인지
$user->isSaasAdmin(); // Level 0-1인지
$user->isTenantAdmin(); // Level 0-2인지
$user->isOrgAdmin(); // Level 0-3인지
$user->isWorkspaceAdmin(); // Level 0-4인지
$user->isGroupLeader(); // Level 0-5인지

사용자 관리 권한 확인

// 대상 사용자를 관리할 수 있는지
$admin = User::where('level', 2)->first(); // Tenant Admin
$member = User::where('level', 6)->first(); // Member

$admin->canManageUser($member); // true (하위 레벨)
$member->canManageUser($admin); // false (상위 레벨)

테넌트 격리 우회

// 테넌트 격리 우회 가능한지 (Level 0-1)
$user->canBypassTenantIsolation();

쿼리 스코프

// 특정 레벨 사용자만 조회
User::withLevel(UserLevel::MEMBER)->get();

// 특정 레벨 이상 (더 높은 권한)
User::withLevelOrAbove(UserLevel::TENANT_ADMIN)->get(); // Level 0-2

// 특정 레벨 이하 (더 낮은 권한)
User::withLevelOrBelow(UserLevel::GROUP_LEADER)->get(); // Level 5-6

PermissionService

권한 검증 로직을 캡슐화한 서비스입니다.

의존성 주입

use App\Core\Base\Permission\Contracts\PermissionServiceInterface;

class UserController extends Controller
{
public function __construct(
private PermissionServiceInterface $permissionService
) {}
}

주요 메서드

// 레벨 접근 가능 여부
$this->permissionService->canAccessLevel($user, 3);

// 패널 접근 가능 여부
$this->permissionService->canAccessPanel($user, 'tenant');

// 사용자 관리 가능 여부
$this->permissionService->canManageUser($actor, $target);

// 특정 레벨 사용자 생성 가능 여부
$this->permissionService->canCreateUserWithLevel($actor, 4);

// 할당 가능한 레벨 목록
$this->permissionService->getAssignableLevels($actor);
// [3 => 'Organization Admin', 4 => 'Workspace Admin', ...]

// 모든 레벨 목록
$this->permissionService->getAllLevels();

// 레벨 계층 구조
$this->permissionService->getLevelHierarchy();

UserPolicy

User 모델의 CRUD 권한을 정의합니다.

핵심 원칙

원칙설명
Downward Access상위 레벨만 하위 레벨 관리 가능
Tenant IsolationLevel 2+는 같은 테넌트만 접근
Self Protection자기 자신 수정/삭제 불가

Policy 메서드

namespace App\Core\Base\Permission\Policies;

class UserPolicy
{
// 목록 조회 - 항상 허용 (패널 미들웨어에서 제어)
public function viewAny(UserInterface $user): bool
{
return true;
}

// 개별 조회
public function view(UserInterface $user, UserInterface $model): bool
{
// 자기 자신 항상 조회 가능
// 상위 레벨만 하위/동일 레벨 조회
// Level 2+는 같은 테넌트만
}

// 생성
public function create(UserInterface $user, int $targetLevel): bool
{
// 자신보다 하위 레벨만 생성 가능
return $user->getLevel() < $targetLevel;
}

// 수정
public function update(UserInterface $user, UserInterface $model): bool
{
// 자기 자신 수정 불가 (프로필 사용)
// 자신보다 하위 레벨만 수정 가능
// Level 2+는 같은 테넌트만
}

// 삭제
public function delete(UserInterface $user, UserInterface $model): bool
{
// update와 동일
}

// Bulk Delete - Level 0-1만
public function deleteAny(UserInterface $user): bool
{
return $user->getLevel() <= UserLevel::SAAS_ADMIN->value;
}

// Force Delete - Level 0만
public function forceDelete(UserInterface $user, UserInterface $model): bool
{
return $user->getLevel() === UserLevel::PLATFORM_ADMIN->value
&& $user->getLevel() < $model->getLevel();
}
}

Controller에서 사용

class UserController extends Controller
{
public function update(User $user, UpdateUserRequest $request)
{
$this->authorize('update', $user);

// 수정 로직...
}

public function destroy(User $user)
{
$this->authorize('delete', $user);

// 삭제 로직...
}
}

TenantOwnedPolicy

테넌트 소속 모델(Post, Order 등)의 CRUD 권한을 정의합니다.

등록 방법

// config/core.php
'authorization' => [
'tenant_owned_models' => [
\App\Models\Post::class,
\App\Models\Order::class,
],
],

동작 원리

class TenantOwnedPolicy
{
// 목록 조회 - 항상 허용 (TenantScope가 필터링)
public function viewAny(): bool
{
return true;
}

// 개별 조회 - 같은 테넌트만 또는 플랫폼 관리자
public function view(UserInterface $user, $model): bool
{
if ($user->canBypassTenantIsolation()) {
return true;
}
return $user->getTenantId() === $model->tenant_id;
}

// 생성 - 인증된 사용자
public function create(UserInterface $user): bool
{
return true;
}

// 수정/삭제 - 같은 테넌트만
public function update(UserInterface $user, $model): bool
{
if ($user->canBypassTenantIsolation()) {
return true;
}
return $user->getTenantId() === $model->tenant_id;
}
}

EnsureUserLevel Middleware

라우트에서 특정 레벨만 접근을 허용합니다.

등록 (자동)

// CoreServiceProvider에서 자동 등록
$router->aliasMiddleware('level', EnsureUserLevel::class);

사용법

// 단일 레벨
Route::middleware(['auth', 'level:0'])
->prefix('platform')
->group(function () {
// Platform Admin 전용
});

// 여러 레벨
Route::middleware(['auth', 'level:0,1,2'])
->prefix('admin')
->group(function () {
// Level 0, 1, 2 접근 가능
});

Filament 패널 적용

// app/Providers/Filament/TenantPanelProvider.php

public function panel(Panel $panel): Panel
{
return $panel
->id('tenant')
->path(env('PANEL_PATH_TENANT', 'tenant'))
->authMiddleware([
Authenticate::class,
EnsureUserLevel::class.':0,1,2',
]);
}

Gate 정의

Gate로 전역 권한 규칙을 정의합니다.

예시

// app/Providers/AppServiceProvider.php

use Illuminate\Support\Facades\Gate;
use App\Core\Base\Permission\Enums\UserLevel;

public function boot(): void
{
// 관리자 접근
Gate::define('access-admin', function ($user) {
return $user->hasLevelAtLeast(UserLevel::TENANT_ADMIN);
});

// 시스템 설정
Gate::define('manage-system', function ($user) {
return $user->isPlatformAdmin();
});

// 사용자 관리
Gate::define('manage-users', function ($user, $target = null) {
if ($target) {
return $user->canManageUser($target);
}
return $user->hasLevelAtLeast(UserLevel::GROUP_LEADER);
});
}

사용

// Controller
if (Gate::allows('manage-system')) {
// 시스템 설정 가능
}

// Blade
@can('access-admin')
<a href="/admin">관리자</a>
@endcan

Filament Resource 통합

canViewAny / canCreate

namespace App\Filament\Tenant\Resources;

use Filament\Resources\Resource;
use App\Core\Base\Permission\Enums\UserLevel;

class OrganizationResource extends Resource
{
public static function canViewAny(): bool
{
return auth()->user()->hasLevelAtLeast(UserLevel::TENANT_ADMIN);
}

public static function canCreate(): bool
{
return auth()->user()->hasLevelAtLeast(UserLevel::TENANT_ADMIN);
}
}

canEdit / canDelete

public static function canEdit($record): bool
{
$user = auth()->user();

// Platform/SaaS Admin은 모두 수정 가능
if ($user->isSaasAdmin()) {
return true;
}

// Tenant Admin은 자신의 테넌트만
if ($user->isTenantAdmin()) {
return $user->tenant_id === $record->tenant_id;
}

return false;
}

public static function canDelete($record): bool
{
return static::canEdit($record)
&& auth()->user()->hasLevelAtLeast(UserLevel::TENANT_ADMIN);
}

레벨 선택 필드

use App\Core\Base\Permission\Enums\UserLevel;
use App\Core\Base\Permission\Contracts\PermissionServiceInterface;

public static function form(Form $form): Form
{
$permissionService = app(PermissionServiceInterface::class);
$assignableLevels = $permissionService->getAssignableLevels(auth()->user());

return $form->schema([
Select::make('level')
->label('권한 레벨')
->options($assignableLevels)
->required(),
]);
}

테스트 작성

Unit 테스트

namespace Tests\Unit\Core\Permission;

use Tests\TestCase;
use App\Models\User;
use App\Core\Base\Permission\Enums\UserLevel;

class HasLevelTest extends TestCase
{
public function test_has_level_at_least(): void
{
$admin = User::factory()->create(['level' => 2]);

$this->assertTrue($admin->hasLevelAtLeast(UserLevel::TENANT_ADMIN));
$this->assertTrue($admin->hasLevelAtLeast(UserLevel::MEMBER));
$this->assertFalse($admin->hasLevelAtLeast(UserLevel::SAAS_ADMIN));
}

public function test_can_manage_user(): void
{
$admin = User::factory()->create([
'level' => 2,
'tenant_id' => 1,
]);

$member = User::factory()->create([
'level' => 6,
'tenant_id' => 1,
]);

$otherTenantMember = User::factory()->create([
'level' => 6,
'tenant_id' => 2,
]);

$this->assertTrue($admin->canManageUser($member));
$this->assertFalse($admin->canManageUser($otherTenantMember));
}
}

Feature 테스트

namespace Tests\Feature\Core\Permission;

use Tests\TestCase;
use App\Models\User;

class UserPolicyTest extends TestCase
{
public function test_tenant_admin_cannot_edit_other_tenant_user(): void
{
$admin = User::factory()->create([
'level' => 2,
'tenant_id' => 1,
]);

$otherUser = User::factory()->create([
'level' => 6,
'tenant_id' => 2,
]);

$response = $this->actingAs($admin)
->putJson("/api/users/{$otherUser->id}", ['name' => 'Hacked']);

$response->assertForbidden();
}

public function test_platform_admin_can_edit_any_user(): void
{
$platformAdmin = User::factory()->create(['level' => 0]);
$anyUser = User::factory()->create(['level' => 6, 'tenant_id' => 99]);

$response = $this->actingAs($platformAdmin)
->putJson("/api/users/{$anyUser->id}", ['name' => 'Updated']);

$response->assertOk();
}
}

모범 사례

최소 권한 원칙

// ✅ 좋은 예: 필요한 최소 레벨 확인
if ($user->hasLevelAtLeast(UserLevel::TENANT_ADMIN)) {
// 테넌트 관리 기능
}

// ❌ 나쁜 예: 불필요하게 높은 권한 요구
if ($user->isPlatformAdmin()) {
// 모든 기능에 Platform Admin 요구
}

테넌트 컨텍스트 확인

// ✅ 좋은 예: 테넌트 소속 확인
if ($user->isTenantAdmin() && $user->tenant_id === $resource->tenant_id) {
// 자신의 테넌트 리소스만
}

// ❌ 나쁜 예: 테넌트 확인 누락
if ($user->isTenantAdmin()) {
// 다른 테넌트 데이터 접근 가능
}

Policy 사용 권장

// ✅ 좋은 예: Policy 사용
$this->authorize('update', $organization);

// ❌ 나쁜 예: 직접 권한 로직 작성
if ($user->level <= 2 && $user->tenant_id === $org->tenant_id) {
// 로직 중복
}

관련 문서