From ab6cb1331ea0a1ca8368dee226252890b512bdb6 Mon Sep 17 00:00:00 2001 From: chan Date: Tue, 2 Jun 2026 19:32:28 +0900 Subject: [PATCH] test: add security role access control smoke test - Added Playwright test to verify super_admin vs user access control in adminfront. - Validates menu visibility and direct route access restrictions. --- adminfront/tests/security_roles.spec.ts | 218 ++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 adminfront/tests/security_roles.spec.ts diff --git a/adminfront/tests/security_roles.spec.ts b/adminfront/tests/security_roles.spec.ts new file mode 100644 index 00000000..0f56006e --- /dev/null +++ b/adminfront/tests/security_roles.spec.ts @@ -0,0 +1,218 @@ +import { expect, test } from "@playwright/test"; + +test.describe("보안 및 접근 제어: 시스템 관리자 vs 일반 사용자", () => { + const setupAuth = async (page, role: string) => { + // 1. Inject initial state and mock tokens + await page.addInitScript( + ({ role }) => { + window.localStorage.setItem("locale", "ko"); + window.localStorage.setItem("admin_session", "fake-token"); + ( + window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean } + )._IS_TEST_MODE = true; + + const auth = "https://ssologin.hmac.kr/oidc"; + const client_id = "adminfront"; + const key = `oidc.user:${auth}:${client_id}`; + const authData = { + access_token: "fake-token", + token_type: "Bearer", + profile: { sub: "test-user", name: "테스트 사용자", role: role }, + expires_at: Math.floor(Date.now() / 1000) + 36000, + }; + window.localStorage.setItem(key, JSON.stringify(authData)); + window.localStorage.setItem("X-Mock-Role-Enabled", "true"); + window.localStorage.setItem("X-Mock-Role", role); + }, + { role }, + ); + + // 2. OIDC Configuration Mocking + await page.route(/.*\/oidc\/.*/, async (route) => { + const url = route.request().url(); + if (url.includes("openid-configuration")) { + await route.fulfill({ + json: { + issuer: "https://ssologin.hmac.kr/oidc", + authorization_endpoint: "https://ssologin.hmac.kr/oidc/auth", + token_endpoint: "https://ssologin.hmac.kr/oidc/token", + jwks_uri: "https://ssologin.hmac.kr/oidc/jwks", + userinfo_endpoint: "https://ssologin.hmac.kr/oidc/userinfo", + end_session_endpoint: "https://ssologin.hmac.kr/oidc/session/end", + response_types_supported: ["code", "id_token"], + subject_types_supported: ["public"], + id_token_signing_alg_values_supported: ["RS256"], + }, + headers: { "Access-Control-Allow-Origin": "*" }, + }); + } else { + await route.fulfill({ + status: 200, + json: {}, + headers: { "Access-Control-Allow-Origin": "*" }, + }); + } + }); + + // 3. API Mocking + await page.route(/.*\/user\/me/, async (route) => { + await route.fulfill({ + json: { + id: "test-user", + name: "테스트 사용자", + role: role, + manageableTenants: [], + }, + headers: { "Access-Control-Allow-Origin": "*" }, + }); + }); + + // Mock generic API responses + await page.route(/.*\/api\/v1\/.*/, async (route) => { + const url = route.request().url(); + if (url.includes("/tenants")) { + await route.fulfill({ + json: { + items: [ + { + id: "t1", + name: "테넌트 1", + slug: "t1", + status: "active", + type: "COMPANY", + }, + ], + total: 1, + }, + headers: { "Access-Control-Allow-Origin": "*" }, + }); + } else if (url.includes("/users")) { + await route.fulfill({ + json: { + items: [ + { + id: "u1", + name: "사용자 1", + email: "u1@example.com", + role: "user", + status: "active", + createdAt: new Date().toISOString(), + }, + ], + total: 1, + }, + headers: { "Access-Control-Allow-Origin": "*" }, + }); + } else if (url.includes("/password-policy")) { + await route.fulfill({ + json: { minLength: 8, minCharacterTypes: 2 }, + headers: { "Access-Control-Allow-Origin": "*" }, + }); + } else { + await route.fulfill({ + json: { items: [], total: 0 }, + headers: { "Access-Control-Allow-Origin": "*" }, + }); + } + }); + }; + + test.describe("시스템 관리자 (Super Admin) 권한", () => { + test.beforeEach(async ({ page }) => { + await setupAuth(page, "super_admin"); + await page.goto("/"); + await expect(page.locator("aside")).toBeVisible({ timeout: 10000 }); + }); + + test("모든 행정 메뉴가 보여야 함", async ({ page }) => { + await expect(page.locator('a[href="/tenants"]')).toBeVisible(); + await expect(page.locator('a[href="/api-keys"]')).toBeVisible(); + await expect(page.locator('a[href="/audit-logs"]')).toBeVisible(); + await expect( + page.locator('a[href="/system/projections/users"]'), + ).toBeVisible(); + await expect( + page.locator('a[href="/system/data-integrity"]'), + ).toBeVisible(); + }); + + test("테넌트 관리 기능에 접근 가능해야 함", async ({ page }) => { + await page.goto("/tenants"); + // "테넌트 추가" 버튼 확인 + await expect( + page.getByRole("link", { name: /테넌트 추가/i }), + ).toBeVisible(); + // "데이터 관리" 드롭다운 확인 + await expect(page.getByTestId("tenant-data-mgmt-btn")).toBeVisible(); + }); + + test("사용자 관리에서 역할 변경이 가능해야 함", async ({ page }) => { + await page.goto("/users"); + // 테이블 내 역할 선택 버튼 확인 (RoleSwitcher/Select) + // i18n에 따라 "일반 사용자" 또는 "Tenant Member" 등으로 표시될 수 있음 + const roleSelect = page.locator('button[id^="radix-"]').first(); + await expect(roleSelect).toBeEnabled(); + }); + + test("사용자 상세에서 '저장하기' 및 '삭제' 버튼이 보여야 함", async ({ + page, + }) => { + await page.goto("/users/u1"); + await expect( + page.getByRole("button", { name: /저장하기/i }), + ).toBeVisible(); + await expect( + page.getByRole("button", { name: /사용자 삭제/i }), + ).toBeVisible(); + }); + }); + + test.describe("일반 사용자 (Tenant Member) 제한", () => { + test.beforeEach(async ({ page }) => { + await setupAuth(page, "user"); + await page.goto("/"); + await expect(page.locator("aside")).toBeVisible({ timeout: 10000 }); + }); + + test("행정 메뉴가 보이지 않아야 함", async ({ page }) => { + await expect(page.locator('a[href="/tenants"]')).not.toBeVisible(); + await expect(page.locator('a[href="/api-keys"]')).not.toBeVisible(); + await expect(page.locator('a[href="/audit-logs"]')).not.toBeVisible(); + await expect( + page.locator('a[href="/system/projections/users"]'), + ).not.toBeVisible(); + await expect( + page.locator('a[href="/system/data-integrity"]'), + ).not.toBeVisible(); + await expect(page.locator('a[href="/users"]')).not.toBeVisible(); + }); + + test("테넌트 페이지 직접 접근 시 차단되어야 함", async ({ page }) => { + await page.goto("/tenants"); + await expect(page.getByText(/접근 권한이 없습니다/i)).toBeVisible(); + }); + + test("사용자 관리 페이지 직접 접근 시 차단되어야 함", async ({ page }) => { + await page.goto("/users"); + await expect( + page.getByText(/이 작업을 수행할 권한이 없습니다/i), + ).toBeVisible(); + }); + + test("사용자 상세 페이지 직접 접근 시 차단되어야 함 (본인 제외)", async ({ + page, + }) => { + await page.goto("/users/other-user"); + await expect( + page.getByText(/이 작업을 수행할 권한이 없습니다/i), + ).toBeVisible(); + }); + + test("사용자 생성 페이지 직접 접근 시 차단되어야 함", async ({ page }) => { + await page.goto("/users/new"); + await expect( + page.getByText(/이 작업을 수행할 권한이 없습니다/i), + ).toBeVisible(); + }); + }); +});