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 Isolation | Level 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) {
// 로직 중복
}
관련 문서
- 권한 시스템 개념 - 권한 분류 (Level 0~6) 개념
- Tenant 모듈 - 테넌트 격리
- Core 모듈 개요 - 전체 구조