1
0
forked from baron/baron-sso

분리된 tenant 스코프 제어 정책 적용

This commit is contained in:
2026-06-11 14:25:03 +09:00
parent 22afe6654e
commit d480a01857
2 changed files with 112 additions and 12 deletions

View File

@@ -68,7 +68,36 @@ vi.mock("../../lib/i18n", () => ({
const roots: Root[] = [];
function makeClientDetail(claimKey: string): ClientDetailResponse {
function makeClientDetail(
claimKey: string,
options?: {
includeTenantScope?: boolean;
tenantAccessRestricted?: boolean;
tenantScopeMandatory?: boolean;
},
): ClientDetailResponse {
const includeTenantScope = options?.includeTenantScope ?? false;
const tenantAccessRestricted = options?.tenantAccessRestricted ?? false;
const tenantScopeMandatory = options?.tenantScopeMandatory ?? false;
const structuredScopes = [
{
id: "1",
name: "openid",
description: "",
mandatory: true,
},
];
if (includeTenantScope) {
structuredScopes.push({
id: "2",
name: "tenant",
description: "Tenant access",
mandatory: tenantScopeMandatory,
locked: tenantAccessRestricted,
});
}
return {
client: {
id: "client-claims",
@@ -76,18 +105,14 @@ function makeClientDetail(claimKey: string): ClientDetailResponse {
type: "private",
status: "active",
redirectUris: ["https://rp.example.com/callback"],
scopes: ["openid", "profile"],
scopes: includeTenantScope
? ["openid", "tenant", "profile"]
: ["openid", "profile"],
tokenEndpointAuthMethod: "client_secret_basic",
metadata: {
description: "Claims app",
structured_scopes: [
{
id: "1",
name: "openid",
description: "",
mandatory: true,
},
],
tenant_access_restricted: tenantAccessRestricted,
structured_scopes: structuredScopes,
id_token_claims: [
{
namespace: "rp_claims",
@@ -304,6 +329,74 @@ describe("ClientGeneralPage RP claims", () => {
);
});
it("preserves tenant scope mandatory state when tenant access restriction is off", async () => {
fetchClientMock.mockResolvedValue(
makeClientDetail("old_claim", {
includeTenantScope: true,
tenantAccessRestricted: false,
tenantScopeMandatory: true,
}),
);
updateClientMock.mockResolvedValue(
makeClientDetail("old_claim", {
includeTenantScope: true,
tenantAccessRestricted: false,
tenantScopeMandatory: false,
}),
);
const { container } = await renderPage();
const tenantScopeRow = Array.from(
container.querySelectorAll("tr"),
).find((row) =>
Array.from(row.querySelectorAll("input")).some(
(input) => (input as HTMLInputElement).value === "tenant",
),
);
expect(tenantScopeRow).toBeDefined();
const mandatorySwitch =
tenantScopeRow?.querySelector<HTMLButtonElement>('[role="switch"]');
expect(mandatorySwitch).toBeDefined();
expect(mandatorySwitch?.getAttribute("aria-checked")).toBe("true");
await act(async () => {
mandatorySwitch?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flush();
expect(mandatorySwitch?.getAttribute("aria-checked")).toBe("false");
const saveButton = Array.from(container.querySelectorAll("button")).find(
(button) =>
button.textContent?.includes("저장") ||
button.textContent?.includes("Save"),
);
expect(saveButton).toBeDefined();
await act(async () => {
saveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flush();
expect(updateClientMock).toHaveBeenCalledWith(
"client-claims",
expect.objectContaining({
metadata: expect.objectContaining({
tenant_access_restricted: false,
structured_scopes: expect.arrayContaining([
expect.objectContaining({
name: "tenant",
mandatory: false,
locked: false,
}),
]),
}),
}),
);
});
it("keeps nullable and default value as separate RP claim settings", async () => {
const { container } = await renderPage();

View File

@@ -689,11 +689,18 @@ function ClientGeneralPage() {
if (scope.name.trim() !== "tenant") {
return scope;
}
if (restricted) {
return {
...scope,
description: scope.description || tenantScopeDescription,
mandatory: true,
locked: true,
};
}
return {
...scope,
description: scope.description || tenantScopeDescription,
mandatory: restricted,
locked: restricted,
locked: false,
};
});