forked from baron/baron-sso
분리된 tenant 스코프 제어 정책 적용
This commit is contained in:
@@ -68,7 +68,36 @@ vi.mock("../../lib/i18n", () => ({
|
|||||||
|
|
||||||
const roots: Root[] = [];
|
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 {
|
return {
|
||||||
client: {
|
client: {
|
||||||
id: "client-claims",
|
id: "client-claims",
|
||||||
@@ -76,18 +105,14 @@ function makeClientDetail(claimKey: string): ClientDetailResponse {
|
|||||||
type: "private",
|
type: "private",
|
||||||
status: "active",
|
status: "active",
|
||||||
redirectUris: ["https://rp.example.com/callback"],
|
redirectUris: ["https://rp.example.com/callback"],
|
||||||
scopes: ["openid", "profile"],
|
scopes: includeTenantScope
|
||||||
|
? ["openid", "tenant", "profile"]
|
||||||
|
: ["openid", "profile"],
|
||||||
tokenEndpointAuthMethod: "client_secret_basic",
|
tokenEndpointAuthMethod: "client_secret_basic",
|
||||||
metadata: {
|
metadata: {
|
||||||
description: "Claims app",
|
description: "Claims app",
|
||||||
structured_scopes: [
|
tenant_access_restricted: tenantAccessRestricted,
|
||||||
{
|
structured_scopes: structuredScopes,
|
||||||
id: "1",
|
|
||||||
name: "openid",
|
|
||||||
description: "",
|
|
||||||
mandatory: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
id_token_claims: [
|
id_token_claims: [
|
||||||
{
|
{
|
||||||
namespace: "rp_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 () => {
|
it("keeps nullable and default value as separate RP claim settings", async () => {
|
||||||
const { container } = await renderPage();
|
const { container } = await renderPage();
|
||||||
|
|
||||||
|
|||||||
@@ -689,11 +689,18 @@ function ClientGeneralPage() {
|
|||||||
if (scope.name.trim() !== "tenant") {
|
if (scope.name.trim() !== "tenant") {
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
if (restricted) {
|
||||||
|
return {
|
||||||
|
...scope,
|
||||||
|
description: scope.description || tenantScopeDescription,
|
||||||
|
mandatory: true,
|
||||||
|
locked: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...scope,
|
...scope,
|
||||||
description: scope.description || tenantScopeDescription,
|
description: scope.description || tenantScopeDescription,
|
||||||
mandatory: restricted,
|
locked: false,
|
||||||
locked: restricted,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user