test: verify broker identity uses subject not email
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { chromium } from "playwright-core";
|
||||
|
||||
const baseUrl = process.env.KEYCLOAK_URL ?? "http://localhost:8080";
|
||||
const adminUsername = process.env.KC_BOOTSTRAP_ADMIN_USERNAME;
|
||||
const adminPassword = process.env.KC_BOOTSTRAP_ADMIN_PASSWORD;
|
||||
const mockPassword = process.env.MOCK_GOOGLE_USER_PASSWORD;
|
||||
assert.ok(adminUsername && adminPassword && mockPassword);
|
||||
|
||||
async function adminToken() {
|
||||
const response = await fetch(
|
||||
`${baseUrl}/realms/master/protocol/openid-connect/token`,
|
||||
{
|
||||
method: "POST",
|
||||
body: new URLSearchParams({
|
||||
client_id: "admin-cli",
|
||||
grant_type: "password",
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
}),
|
||||
},
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
return (await response.json()).access_token;
|
||||
}
|
||||
|
||||
async function adminJson(token, path, init = {}) {
|
||||
const response = await fetch(`${baseUrl}/admin/realms/${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
...(init.body ? { "Content-Type": "application/json" } : {}),
|
||||
},
|
||||
});
|
||||
assert.ok(response.ok, `${init.method ?? "GET"} ${path}: ${response.status}`);
|
||||
return response.status === 204 ? undefined : response.json();
|
||||
}
|
||||
|
||||
async function users(token, realm, query) {
|
||||
return adminJson(token, `${realm}/users?${new URLSearchParams(query)}`);
|
||||
}
|
||||
|
||||
async function brokerLogin(browser) {
|
||||
const page = await browser.newPage();
|
||||
const url = new URL(
|
||||
`${baseUrl}/realms/keycloak-patterns/protocol/openid-connect/auth`,
|
||||
);
|
||||
url.search = new URLSearchParams({
|
||||
client_id: "spa-public",
|
||||
redirect_uri: "http://localhost:8088/",
|
||||
response_type: "code",
|
||||
scope: "openid profile email",
|
||||
state: crypto.randomUUID(),
|
||||
nonce: crypto.randomUUID(),
|
||||
code_challenge: "K2qUEfBl-nQvF2gB4dNxC2zYVwZc1CVnZb5CsX2L7fI",
|
||||
code_challenge_method: "S256",
|
||||
kc_idp_hint: "mock-google",
|
||||
prompt: "login",
|
||||
});
|
||||
await page.goto(url.toString());
|
||||
await page.waitForURL(/\/realms\/mock-google\//u);
|
||||
await page.locator("#username").fill("mock-new-user");
|
||||
await page.locator("#password").fill(mockPassword);
|
||||
await page.locator("#kc-login").click();
|
||||
await page.waitForURL(/localhost:8088\/\?.*code=/u);
|
||||
await page.close();
|
||||
}
|
||||
|
||||
const token = await adminToken();
|
||||
const mockUsers = await users(token, "mock-google", {
|
||||
username: "mock-new-user",
|
||||
exact: "true",
|
||||
});
|
||||
assert.equal(mockUsers.length, 1);
|
||||
const mockUser = await adminJson(
|
||||
token,
|
||||
`mock-google/users/${mockUsers[0].id}`,
|
||||
);
|
||||
const originalEmail = mockUser.email;
|
||||
const changedEmail = "broker-renamed-user@example.test";
|
||||
|
||||
for (const existing of await users(token, "keycloak-patterns", {
|
||||
username: `mock-google.${mockUser.id}`,
|
||||
exact: "true",
|
||||
})) {
|
||||
await adminJson(token, `keycloak-patterns/users/${existing.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
const browser = await chromium.launch({
|
||||
executablePath: process.env.CHROME_BIN ?? "/usr/bin/google-chrome",
|
||||
headless: true,
|
||||
args: ["--no-sandbox"],
|
||||
});
|
||||
|
||||
try {
|
||||
await brokerLogin(browser);
|
||||
const before = await users(token, "keycloak-patterns", {
|
||||
username: `mock-google.${mockUser.id}`,
|
||||
exact: "true",
|
||||
});
|
||||
assert.equal(before.length, 1);
|
||||
const localUserId = before[0].id;
|
||||
const identities = await adminJson(
|
||||
token,
|
||||
`keycloak-patterns/users/${localUserId}/federated-identity`,
|
||||
);
|
||||
assert.equal(identities[0].userId, mockUser.id);
|
||||
|
||||
await adminJson(token, `mock-google/users/${mockUser.id}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ ...mockUser, email: changedEmail }),
|
||||
});
|
||||
await brokerLogin(browser);
|
||||
|
||||
const after = await users(token, "keycloak-patterns", {
|
||||
username: `mock-google.${mockUser.id}`,
|
||||
exact: "true",
|
||||
});
|
||||
assert.equal(after.length, 1);
|
||||
assert.equal(after[0].id, localUserId);
|
||||
console.log(
|
||||
"Federated identity verified: provider sub stayed linked while upstream email changed",
|
||||
);
|
||||
} finally {
|
||||
await adminJson(token, `mock-google/users/${mockUser.id}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ ...mockUser, email: originalEmail }),
|
||||
});
|
||||
await browser.close();
|
||||
}
|
||||
Reference in New Issue
Block a user