위임 관리자(부관리자)가 권한·역할·표현식·비밀 콘텐츠 경계를 우회하던 결함군을 계층 대칭성 원칙으로 전건 차단한다. 약한 경로가 정상 응답을 내보내는 것이 유일한 증상이라, 게이트를 생산 지점 한 곳(SSoT)에 두고 같은 데이터를 내보내는 소비 경로 전부가 그 게이트를 경유하도록 맞췄다. - 등급 상한(rank ceiling): 슈퍼관리자 보호·역할/사용자 역할 배정(추가·제거 대칭)·일괄 상태변경·순서변경을 상세 경로와 동일 강도로 재적용. 가드는 DB 쓰기에 선행하여 거부 시 상태 불변. - 레이아웃 표현식: new Function/with 실행을 AST 화이트리스트 평가기로 교체. 비-문자열 computed 키 정규화(normalizeKey)·Object facade(리플렉션 static 제거)·legacy 접근자 차단. 저장측 검증·정적 검사와 3계층 동형. - secret 게이트: 비밀글의 댓글·첨부·문의 독립 경로 재적용, hash 파일서빙 소유권·비밀·발행 상태 검사 통일. - 신뢰 스크립트 호스트: 확장 선언 기반 + same-origin 브라우저 정규화를 런타임·저장측·정적검사 3층 동형화. - 회귀 감지: 단위·Feature·E2E·시나리오 매니페스트 전축 + audit 룰 4종 신설.
773 lines
28 KiB
PHP
773 lines
28 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Services;
|
|
|
|
use App\Enums\ExtensionOwnerType;
|
|
use App\Enums\PermissionType;
|
|
use App\Exceptions\CannotDeleteSuperAdminException;
|
|
use App\Exceptions\PermissionEscalationException;
|
|
use App\Models\Permission;
|
|
use App\Models\Role;
|
|
use App\Models\User;
|
|
use App\Models\UserConsent;
|
|
use App\Services\UserService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Validation\ValidationException;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* UserService 테스트
|
|
*
|
|
* UserService의 사용자 삭제 및 슈퍼 관리자 관련 기능을 검증합니다.
|
|
*/
|
|
class UserServiceTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private UserService $userService;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->userService = app(UserService::class);
|
|
}
|
|
|
|
// ========================================================================
|
|
// deleteUser() - 슈퍼 관리자 삭제 방지 테스트
|
|
// ========================================================================
|
|
|
|
/**
|
|
* 슈퍼 관리자 삭제 시 CannotDeleteSuperAdminException 발생 확인
|
|
*/
|
|
public function test_delete_super_admin_throws_cannot_delete_super_admin_exception(): void
|
|
{
|
|
$superAdmin = User::factory()->create(['is_super' => true]);
|
|
|
|
$this->expectException(CannotDeleteSuperAdminException::class);
|
|
|
|
$this->userService->deleteUser($superAdmin);
|
|
}
|
|
|
|
/**
|
|
* 슈퍼 관리자 삭제 시 예외 메시지가 올바른지 확인
|
|
*/
|
|
public function test_cannot_delete_super_admin_exception_has_correct_message(): void
|
|
{
|
|
$superAdmin = User::factory()->create(['is_super' => true]);
|
|
|
|
try {
|
|
$this->userService->deleteUser($superAdmin);
|
|
$this->fail('Expected CannotDeleteSuperAdminException was not thrown');
|
|
} catch (CannotDeleteSuperAdminException $e) {
|
|
// 예외 메시지가 다국어 키로 번역되는지 확인
|
|
$this->assertNotEmpty($e->getMessage());
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// deleteUser() - 관리자 삭제: 권한/스코프 기반 위임 (하드코딩 차단 제거)
|
|
// ========================================================================
|
|
|
|
/**
|
|
* 관리자 권한을 가진 사용자(target=admin) 도 UserService::deleteUser 단독으로는 삭제 가능해야 한다.
|
|
*
|
|
* 회귀: "target.isAdmin() → CannotDeleteAdminException" 하드코딩이 G7 의 역할/퍼미션/스코프 시스템을
|
|
* 우회해 슈퍼관리자도 다른 관리자를 삭제하지 못하도록 막던 결함. 권한/스코프 검증은 미들웨어
|
|
* (PermissionMiddleware) 가 담당하고, 시스템 불변식인 슈퍼관리자 보호만 Service 가 보장한다.
|
|
*/
|
|
public function test_delete_admin_user_succeeds_when_actor_authorized(): void
|
|
{
|
|
$adminPermission = Permission::create([
|
|
'identifier' => 'core.admin.test',
|
|
'name' => ['ko' => '관리자 권한', 'en' => 'Admin Permission'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
$role = Role::create([
|
|
'identifier' => 'test-admin',
|
|
'name' => ['ko' => '테스트 관리자', 'en' => 'Test Admin'],
|
|
]);
|
|
$role->permissions()->attach($adminPermission->id);
|
|
|
|
$adminUser = User::factory()->create(['is_super' => false]);
|
|
$adminUser->roles()->attach($role->id);
|
|
|
|
$result = $this->userService->deleteUser($adminUser);
|
|
|
|
$this->assertTrue($result);
|
|
$this->assertDatabaseMissing('users', ['id' => $adminUser->id]);
|
|
}
|
|
|
|
// ========================================================================
|
|
// deleteUser() - 일반 사용자 삭제 테스트
|
|
// ========================================================================
|
|
|
|
/**
|
|
* 일반 사용자는 정상적으로 삭제 가능
|
|
*/
|
|
public function test_delete_regular_user_succeeds(): void
|
|
{
|
|
$regularUser = User::factory()->create(['is_super' => false]);
|
|
|
|
$result = $this->userService->deleteUser($regularUser);
|
|
|
|
$this->assertTrue($result);
|
|
$this->assertDatabaseMissing('users', ['id' => $regularUser->id]);
|
|
}
|
|
|
|
/**
|
|
* user 타입 권한만 가진 사용자는 삭제 가능
|
|
*/
|
|
public function test_delete_user_with_only_user_type_permissions_succeeds(): void
|
|
{
|
|
// user 타입 권한 생성
|
|
$userPermission = Permission::create([
|
|
'identifier' => 'frontend.user.test',
|
|
'name' => ['ko' => '사용자 권한', 'en' => 'User Permission'],
|
|
'type' => PermissionType::User,
|
|
]);
|
|
|
|
// 역할 생성 및 권한 연결
|
|
$role = Role::create([
|
|
'identifier' => 'test-user',
|
|
'name' => ['ko' => '테스트 사용자', 'en' => 'Test User'],
|
|
]);
|
|
$role->permissions()->attach($userPermission->id);
|
|
|
|
// 사용자 생성 및 역할 연결
|
|
$user = User::factory()->create(['is_super' => false]);
|
|
$user->roles()->attach($role->id);
|
|
|
|
$result = $this->userService->deleteUser($user);
|
|
|
|
$this->assertTrue($result);
|
|
$this->assertDatabaseMissing('users', ['id' => $user->id]);
|
|
}
|
|
|
|
/**
|
|
* 역할이 없는 사용자도 삭제 가능
|
|
*/
|
|
public function test_delete_user_without_roles_succeeds(): void
|
|
{
|
|
$userWithoutRoles = User::factory()->create(['is_super' => false]);
|
|
|
|
// 역할이 없는지 확인
|
|
$this->assertEquals(0, $userWithoutRoles->roles()->count());
|
|
|
|
$result = $this->userService->deleteUser($userWithoutRoles);
|
|
|
|
$this->assertTrue($result);
|
|
$this->assertDatabaseMissing('users', ['id' => $userWithoutRoles->id]);
|
|
}
|
|
|
|
// ========================================================================
|
|
// 삭제 우선순위 테스트 (슈퍼 관리자 > 관리자)
|
|
// ========================================================================
|
|
|
|
/**
|
|
* 슈퍼 관리자이면서 관리자 권한도 있는 경우, 슈퍼 관리자 예외가 우선
|
|
*/
|
|
public function test_super_admin_exception_takes_priority_over_admin_exception(): void
|
|
{
|
|
// admin 타입 권한 생성
|
|
$adminPermission = Permission::create([
|
|
'identifier' => 'core.priority.test',
|
|
'name' => ['ko' => '우선순위 테스트', 'en' => 'Priority Test'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
// 역할 생성 및 권한 연결
|
|
$role = Role::create([
|
|
'identifier' => 'super-admin-role',
|
|
'name' => ['ko' => '슈퍼관리자 역할', 'en' => 'Super Admin Role'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
]);
|
|
$role->permissions()->attach($adminPermission->id);
|
|
|
|
// 슈퍼 관리자이면서 관리자 역할도 가진 사용자
|
|
$superAdminWithRole = User::factory()->create(['is_super' => true]);
|
|
$superAdminWithRole->roles()->attach($role->id);
|
|
|
|
// 슈퍼 관리자 예외가 먼저 발생해야 함
|
|
$this->expectException(CannotDeleteSuperAdminException::class);
|
|
|
|
$this->userService->deleteUser($superAdminWithRole);
|
|
}
|
|
|
|
// ========================================================================
|
|
// deleteUser() - 관계 레코드 명시적 삭제 검증 (CASCADE 의존 금지)
|
|
// ========================================================================
|
|
|
|
/**
|
|
* 사용자 삭제 시 역할 연결이 해제되는지 확인
|
|
*/
|
|
public function test_delete_user_detaches_roles(): void
|
|
{
|
|
$role = Role::create([
|
|
'identifier' => 'test-detach-role',
|
|
'name' => ['ko' => '역할 해제 테스트', 'en' => 'Detach Test'],
|
|
]);
|
|
|
|
$user = User::factory()->create(['is_super' => false]);
|
|
$user->roles()->attach($role->id);
|
|
|
|
$this->assertDatabaseHas('user_roles', ['user_id' => $user->id, 'role_id' => $role->id]);
|
|
|
|
$this->userService->deleteUser($user);
|
|
|
|
$this->assertDatabaseMissing('user_roles', ['user_id' => $user->id]);
|
|
}
|
|
|
|
/**
|
|
* 사용자 삭제 시 약관 동의 이력이 삭제되는지 확인
|
|
*/
|
|
public function test_delete_user_deletes_consents(): void
|
|
{
|
|
$user = User::factory()->create(['is_super' => false]);
|
|
UserConsent::create([
|
|
'user_id' => $user->id,
|
|
'consent_type' => 'terms',
|
|
'agreed_at' => now(),
|
|
]);
|
|
|
|
$this->assertDatabaseHas('user_consents', ['user_id' => $user->id]);
|
|
|
|
$this->userService->deleteUser($user);
|
|
|
|
$this->assertDatabaseMissing('user_consents', ['user_id' => $user->id]);
|
|
}
|
|
|
|
/**
|
|
* 사용자 삭제 시 API 토큰이 삭제되는지 확인
|
|
*/
|
|
public function test_delete_user_deletes_tokens(): void
|
|
{
|
|
$user = User::factory()->create(['is_super' => false]);
|
|
$user->createToken('test-token');
|
|
|
|
$this->assertDatabaseHas('personal_access_tokens', [
|
|
'tokenable_id' => $user->id,
|
|
'tokenable_type' => User::class,
|
|
]);
|
|
|
|
$this->userService->deleteUser($user);
|
|
|
|
$this->assertDatabaseMissing('personal_access_tokens', [
|
|
'tokenable_id' => $user->id,
|
|
'tokenable_type' => User::class,
|
|
]);
|
|
}
|
|
|
|
// ========================================================================
|
|
// createUser() 테스트 (is_super 필드 처리)
|
|
// ========================================================================
|
|
|
|
/**
|
|
* 사용자 생성 시 is_super 필드가 정상적으로 저장되는지 확인
|
|
*/
|
|
public function test_create_user_with_is_super_true(): void
|
|
{
|
|
$userData = [
|
|
'name' => 'Super Admin Test',
|
|
'email' => 'superadmin@test.com',
|
|
'password' => 'password123',
|
|
'is_super' => true,
|
|
];
|
|
|
|
$user = $this->userService->createUser($userData);
|
|
|
|
$this->assertTrue($user->is_super);
|
|
$this->assertTrue($user->isSuperAdmin());
|
|
}
|
|
|
|
/**
|
|
* 사용자 생성 시 is_super가 기본값 false인지 확인
|
|
*/
|
|
#[Test]
|
|
public function test_create_user_is_super_defaults_to_false(): void
|
|
{
|
|
$userData = [
|
|
'name' => 'Regular User Test',
|
|
'email' => 'regularuser@test.com',
|
|
'password' => 'password123',
|
|
];
|
|
|
|
$user = $this->userService->createUser($userData);
|
|
|
|
$this->assertFalse($user->is_super);
|
|
$this->assertFalse($user->isSuperAdmin());
|
|
}
|
|
|
|
// ========================================================================
|
|
// updateUser() 테스트 (is_super 필드 처리)
|
|
// ========================================================================
|
|
|
|
/**
|
|
* 사용자 업데이트 시 is_super 필드 변경 가능
|
|
*/
|
|
public function test_update_user_can_change_is_super(): void
|
|
{
|
|
$user = User::factory()->create(['is_super' => false]);
|
|
|
|
$this->assertFalse($user->is_super);
|
|
|
|
$updatedUser = $this->userService->updateUser($user, ['is_super' => true]);
|
|
|
|
$this->assertTrue($updatedUser->is_super);
|
|
$this->assertTrue($updatedUser->isSuperAdmin());
|
|
}
|
|
|
|
/**
|
|
* 슈퍼 관리자를 일반 사용자로 강등 가능
|
|
*/
|
|
public function test_update_user_can_demote_super_admin(): void
|
|
{
|
|
$superAdmin = User::factory()->create(['is_super' => true]);
|
|
|
|
$this->assertTrue($superAdmin->isSuperAdmin());
|
|
|
|
$updatedUser = $this->userService->updateUser($superAdmin, ['is_super' => false]);
|
|
|
|
$this->assertFalse($updatedUser->is_super);
|
|
$this->assertFalse($updatedUser->isSuperAdmin());
|
|
}
|
|
|
|
// ========================================================================
|
|
// updateUser() — 역할 할당 권한 체크 테스트
|
|
// ========================================================================
|
|
|
|
/**
|
|
* core.permissions.update 권한이 있으면 자기 자신의 역할도 변경 가능
|
|
*/
|
|
public function test_update_user_can_change_own_roles_with_permission(): void
|
|
{
|
|
// core.permissions.update 권한을 가진 사용자
|
|
$permUpdatePermission = Permission::create([
|
|
'identifier' => 'core.permissions.update',
|
|
'name' => ['ko' => '권한 수정', 'en' => 'Update Permissions'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
$adminRole = Role::create([
|
|
'identifier' => 'test-perm-admin',
|
|
'name' => ['ko' => '권한 관리자', 'en' => 'Permission Admin'],
|
|
]);
|
|
$adminRole->permissions()->attach($permUpdatePermission->id);
|
|
|
|
$newRole = Role::create([
|
|
'identifier' => 'new-role',
|
|
'name' => ['ko' => '새 역할', 'en' => 'New Role'],
|
|
]);
|
|
|
|
$user = User::factory()->create();
|
|
$user->roles()->attach($adminRole->id);
|
|
|
|
// 자기 자신으로 인증
|
|
Auth::login($user);
|
|
|
|
// 자기 자신의 역할 변경 시도 (admin 역할이 아니므로 자기잠금 방지 해당 없음)
|
|
$updatedUser = $this->userService->updateUser($user, [
|
|
'name' => 'Updated Name',
|
|
'role_ids' => [$adminRole->id, $newRole->id],
|
|
]);
|
|
|
|
// 이름과 역할 모두 변경되어야 함
|
|
$this->assertEquals('Updated Name', $updatedUser->name);
|
|
$this->assertTrue($updatedUser->roles->contains('id', $adminRole->id));
|
|
$this->assertTrue($updatedUser->roles->contains('id', $newRole->id));
|
|
}
|
|
|
|
/**
|
|
* 마지막 admin 역할 사용자가 자기 admin 역할을 제거하려는 경우 차단
|
|
*/
|
|
public function test_update_user_prevents_last_admin_role_removal(): void
|
|
{
|
|
$permUpdatePermission = Permission::create([
|
|
'identifier' => 'core.permissions.update',
|
|
'name' => ['ko' => '권한 수정', 'en' => 'Update Permissions'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
$adminRole = Role::create([
|
|
'identifier' => 'admin',
|
|
'name' => ['ko' => '관리자', 'en' => 'Admin'],
|
|
]);
|
|
$adminRole->permissions()->attach($permUpdatePermission->id);
|
|
|
|
$otherRole = Role::create([
|
|
'identifier' => 'other-role',
|
|
'name' => ['ko' => '다른 역할', 'en' => 'Other Role'],
|
|
]);
|
|
|
|
// 유일한 admin 사용자
|
|
$user = User::factory()->create();
|
|
$user->roles()->attach($adminRole->id);
|
|
|
|
Auth::login($user);
|
|
|
|
// admin 역할을 제거하고 다른 역할만 남기려는 시도
|
|
$this->expectException(ValidationException::class);
|
|
|
|
$this->userService->updateUser($user, [
|
|
'role_ids' => [$otherRole->id],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* admin 역할을 가진 다른 사용자가 있으면 자기 admin 역할 제거 가능
|
|
*/
|
|
public function test_update_user_allows_admin_role_removal_when_other_admins_exist(): void
|
|
{
|
|
$permUpdatePermission = Permission::create([
|
|
'identifier' => 'core.permissions.update',
|
|
'name' => ['ko' => '권한 수정', 'en' => 'Update Permissions'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
$adminRole = Role::create([
|
|
'identifier' => 'admin',
|
|
'name' => ['ko' => '관리자', 'en' => 'Admin'],
|
|
]);
|
|
$adminRole->permissions()->attach($permUpdatePermission->id);
|
|
|
|
$otherRole = Role::create([
|
|
'identifier' => 'other-role-2',
|
|
'name' => ['ko' => '다른 역할', 'en' => 'Other Role'],
|
|
]);
|
|
|
|
// admin 역할을 가진 사용자 2명
|
|
$user = User::factory()->create();
|
|
$user->roles()->attach($adminRole->id);
|
|
|
|
$otherAdmin = User::factory()->create();
|
|
$otherAdmin->roles()->attach($adminRole->id);
|
|
|
|
Auth::login($user);
|
|
|
|
// 다른 admin이 있으므로 자기 admin 역할 제거 가능
|
|
$updatedUser = $this->userService->updateUser($user, [
|
|
'role_ids' => [$otherRole->id],
|
|
]);
|
|
|
|
$this->assertFalse($updatedUser->roles->contains('id', $adminRole->id));
|
|
$this->assertTrue($updatedUser->roles->contains('id', $otherRole->id));
|
|
}
|
|
|
|
/**
|
|
* 마지막 admin 역할 제거 시 ValidationException에 role_ids 에러가 포함되는지 확인
|
|
*/
|
|
public function test_last_admin_role_removal_has_role_ids_error_key(): void
|
|
{
|
|
$permUpdatePermission = Permission::create([
|
|
'identifier' => 'core.permissions.update',
|
|
'name' => ['ko' => '권한 수정', 'en' => 'Update Permissions'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
$adminRole = Role::create([
|
|
'identifier' => 'admin',
|
|
'name' => ['ko' => '관리자', 'en' => 'Admin'],
|
|
]);
|
|
$adminRole->permissions()->attach($permUpdatePermission->id);
|
|
|
|
$otherRole = Role::create([
|
|
'identifier' => 'user-role',
|
|
'name' => ['ko' => '사용자', 'en' => 'User'],
|
|
]);
|
|
|
|
$user = User::factory()->create();
|
|
$user->roles()->attach($adminRole->id);
|
|
|
|
Auth::login($user);
|
|
|
|
try {
|
|
$this->userService->updateUser($user, [
|
|
'role_ids' => [$otherRole->id],
|
|
]);
|
|
$this->fail('Expected ValidationException was not thrown');
|
|
} catch (ValidationException $e) {
|
|
$this->assertArrayHasKey('role_ids', $e->errors());
|
|
$this->assertNotEmpty($e->errors()['role_ids']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 역할 부여는 core.permissions.update(역할 정의 수정)를 요구하지 않는다 — 상한(ceiling)
|
|
* 이내라면 그 권한이 없어도 역할을 부여할 수 있다. (역할 부여 = 사용자 관리의 일부)
|
|
*
|
|
* 회귀 방지: 과거에는 core.permissions.update 가 없으면 role_ids 를 조용히 버려(silent
|
|
* no-op) 200 성공을 반환하면서도 역할이 바뀌지 않았다. 이제는 상한만 통과하면 부여된다.
|
|
*/
|
|
public function test_update_user_assigns_role_within_ceiling_without_permissions_update(): void
|
|
{
|
|
$originalRole = Role::create([
|
|
'identifier' => 'original-role',
|
|
'name' => ['ko' => '원래 역할', 'en' => 'Original Role'],
|
|
]);
|
|
|
|
// 권한이 전혀 없는 역할 → 어떤 액터의 상한도 넘지 않는다.
|
|
$newRole = Role::create([
|
|
'identifier' => 'new-role-test',
|
|
'name' => ['ko' => '새 역할', 'en' => 'New Role'],
|
|
]);
|
|
|
|
$targetUser = User::factory()->create();
|
|
$targetUser->roles()->attach($originalRole->id);
|
|
|
|
// core.permissions.update 가 없는 액터로 인증 (사용자 관리 권한은 라우트 단계에서
|
|
// 강제되며, 서비스 계층 테스트에서는 상한만 검사된다)
|
|
$authUser = User::factory()->create();
|
|
Auth::login($authUser);
|
|
|
|
$updatedUser = $this->userService->updateUser($targetUser, [
|
|
'name' => 'Changed Name',
|
|
'role_ids' => [$newRole->id],
|
|
]);
|
|
|
|
// 이름과 역할 모두 변경되어야 한다 (silent no-op 아님)
|
|
$this->assertEquals('Changed Name', $updatedUser->name);
|
|
$this->assertTrue($updatedUser->roles->contains('id', $newRole->id));
|
|
$this->assertFalse($updatedUser->roles->contains('id', $originalRole->id));
|
|
}
|
|
|
|
/**
|
|
* 상한을 넘는 역할(액터가 보유하지 않은 권한을 담은 역할)은 부여 시 명시적으로 거부된다.
|
|
*
|
|
* 회귀 방지: 조용히 무시(silent no-op)하는 대신 PermissionEscalationException 을 던져
|
|
* 컨트롤러가 403 으로 매핑하도록 한다.
|
|
*/
|
|
public function test_update_user_rejects_role_beyond_actor_ceiling(): void
|
|
{
|
|
$elevatedPermission = Permission::create([
|
|
'identifier' => 'core.templates.uninstall',
|
|
'name' => ['ko' => '템플릿 삭제', 'en' => 'Uninstall Templates'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
// 액터가 보유하지 않은 권한을 담은 역할 → 상한 초과
|
|
$elevatedRole = Role::create([
|
|
'identifier' => 'elevated-role',
|
|
'name' => ['ko' => '상위 역할', 'en' => 'Elevated Role'],
|
|
]);
|
|
$elevatedRole->permissions()->attach($elevatedPermission->id);
|
|
|
|
$targetUser = User::factory()->create();
|
|
|
|
// 그 권한을 보유하지 않은 액터
|
|
$authUser = User::factory()->create();
|
|
Auth::login($authUser);
|
|
|
|
$this->expectException(PermissionEscalationException::class);
|
|
|
|
$this->userService->updateUser($targetUser, [
|
|
'role_ids' => [$elevatedRole->id],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* core.permissions.update 권한이 있으면 타인의 역할 변경 가능
|
|
*/
|
|
public function test_update_user_allows_role_change_with_permission(): void
|
|
{
|
|
$permUpdatePermission = Permission::create([
|
|
'identifier' => 'core.permissions.update',
|
|
'name' => ['ko' => '권한 수정', 'en' => 'Update Permissions'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
$adminRole = Role::create([
|
|
'identifier' => 'perm-admin-role',
|
|
'name' => ['ko' => '권한 관리 역할', 'en' => 'Permission Admin Role'],
|
|
]);
|
|
$adminRole->permissions()->attach($permUpdatePermission->id);
|
|
|
|
$originalRole = Role::create([
|
|
'identifier' => 'original-role-2',
|
|
'name' => ['ko' => '원래 역할', 'en' => 'Original Role'],
|
|
]);
|
|
|
|
$newRole = Role::create([
|
|
'identifier' => 'target-role',
|
|
'name' => ['ko' => '대상 역할', 'en' => 'Target Role'],
|
|
]);
|
|
|
|
$targetUser = User::factory()->create();
|
|
$targetUser->roles()->attach($originalRole->id);
|
|
|
|
// 권한 있는 사용자로 인증
|
|
$authUser = User::factory()->create();
|
|
$authUser->roles()->attach($adminRole->id);
|
|
Auth::login($authUser);
|
|
|
|
// 타인의 역할 변경
|
|
$updatedUser = $this->userService->updateUser($targetUser, [
|
|
'role_ids' => [$newRole->id],
|
|
]);
|
|
|
|
// 역할이 새 역할로 변경되어야 함
|
|
$this->assertTrue($updatedUser->roles->contains('id', $newRole->id));
|
|
$this->assertFalse($updatedUser->roles->contains('id', $originalRole->id));
|
|
}
|
|
|
|
// ========================================================================
|
|
// createUser() — 역할 할당 권한 체크 테스트
|
|
// ========================================================================
|
|
|
|
/**
|
|
* 역할을 지정하지 않고 생성하면 기본 역할('user')이 자동 배정된다.
|
|
*
|
|
* 회귀 방지: 기본 역할 배정은 권한 게이트가 아니라 "요청 역할 없음" 조건에 묶인다.
|
|
* (과거에는 core.permissions.update 미보유 조건에 묶여 있어 지정 역할을 덮어썼다)
|
|
*/
|
|
public function test_create_user_assigns_default_role_when_none_requested(): void
|
|
{
|
|
$userRole = Role::create([
|
|
'identifier' => 'user',
|
|
'name' => ['ko' => '사용자', 'en' => 'User'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$authUser = User::factory()->create();
|
|
Auth::login($authUser);
|
|
|
|
$user = $this->userService->createUser([
|
|
'name' => 'Test User',
|
|
'email' => 'testcreate@test.com',
|
|
'password' => 'password123',
|
|
]);
|
|
|
|
$this->assertTrue($user->roles->contains('id', $userRole->id));
|
|
}
|
|
|
|
/**
|
|
* core.permissions.update 가 없어도 상한 이내의 지정 역할은 그대로 부여된다
|
|
* (기본 역할로 덮어쓰지 않는다).
|
|
*
|
|
* 회귀 방지: 과거에는 core.permissions.update 미보유 시 지정 역할을 버리고 기본 역할을
|
|
* 강제 배정했다. 이제는 상한만 통과하면 지정 역할이 부여된다.
|
|
*/
|
|
public function test_create_user_assigns_requested_role_within_ceiling_without_permissions_update(): void
|
|
{
|
|
$userRole = Role::create([
|
|
'identifier' => 'user',
|
|
'name' => ['ko' => '사용자', 'en' => 'User'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'is_active' => true,
|
|
]);
|
|
|
|
// 권한이 없는 역할 → 상한 이내
|
|
$customRole = Role::create([
|
|
'identifier' => 'custom-role',
|
|
'name' => ['ko' => '커스텀 역할', 'en' => 'Custom Role'],
|
|
]);
|
|
|
|
$authUser = User::factory()->create();
|
|
Auth::login($authUser);
|
|
|
|
$user = $this->userService->createUser([
|
|
'name' => 'Test User',
|
|
'email' => 'testcreate@test.com',
|
|
'password' => 'password123',
|
|
'role_ids' => [$customRole->id],
|
|
]);
|
|
|
|
// 지정 역할이 부여되고 기본 역할로 덮어쓰이지 않는다
|
|
$this->assertTrue($user->roles->contains('id', $customRole->id));
|
|
$this->assertFalse($user->roles->contains('id', $userRole->id));
|
|
}
|
|
|
|
/**
|
|
* 상한을 넘는 역할을 지정해 생성하면 명시적으로 거부된다.
|
|
*/
|
|
public function test_create_user_rejects_role_beyond_actor_ceiling(): void
|
|
{
|
|
$elevatedPermission = Permission::create([
|
|
'identifier' => 'core.templates.uninstall',
|
|
'name' => ['ko' => '템플릿 삭제', 'en' => 'Uninstall Templates'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
$elevatedRole = Role::create([
|
|
'identifier' => 'elevated-create-role',
|
|
'name' => ['ko' => '상위 역할', 'en' => 'Elevated Role'],
|
|
]);
|
|
$elevatedRole->permissions()->attach($elevatedPermission->id);
|
|
|
|
$authUser = User::factory()->create();
|
|
Auth::login($authUser);
|
|
|
|
$this->expectException(PermissionEscalationException::class);
|
|
|
|
$this->userService->createUser([
|
|
'name' => 'New User',
|
|
'email' => 'newuser@test.com',
|
|
'password' => 'password123',
|
|
'role_ids' => [$elevatedRole->id],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* core.permissions.update 권한이 있으면 지정한 역할 할당 가능
|
|
*/
|
|
public function test_create_user_assigns_specified_roles_with_permission(): void
|
|
{
|
|
$permUpdatePermission = Permission::create([
|
|
'identifier' => 'core.permissions.update',
|
|
'name' => ['ko' => '권한 수정', 'en' => 'Update Permissions'],
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => PermissionType::Admin,
|
|
]);
|
|
|
|
$adminRole = Role::create([
|
|
'identifier' => 'create-perm-admin',
|
|
'name' => ['ko' => '권한 관리자', 'en' => 'Permission Admin'],
|
|
]);
|
|
$adminRole->permissions()->attach($permUpdatePermission->id);
|
|
|
|
$customRole = Role::create([
|
|
'identifier' => 'assigned-role',
|
|
'name' => ['ko' => '할당 역할', 'en' => 'Assigned Role'],
|
|
]);
|
|
|
|
// 권한 있는 사용자로 인증
|
|
$authUser = User::factory()->create();
|
|
$authUser->roles()->attach($adminRole->id);
|
|
Auth::login($authUser);
|
|
|
|
$user = $this->userService->createUser([
|
|
'name' => 'New User',
|
|
'email' => 'newuser@test.com',
|
|
'password' => 'password123',
|
|
'role_ids' => [$customRole->id],
|
|
]);
|
|
|
|
// 지정한 역할이 할당되어야 함
|
|
$this->assertTrue($user->roles->contains('id', $customRole->id));
|
|
}
|
|
}
|