feat: map broker claims to realm roles
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
# Google claim-to-role mapping
|
||||||
|
|
||||||
|
`hd=example.test`인 upstream OIDC identity에는 Keycloak realm role
|
||||||
|
`employee-role`을 부여한다. 매핑 키는 email이 아니라 Google subject이며,
|
||||||
|
role 조건에 쓰는 `hd` claim은 mock provider와 실제 Google provider에서 같은
|
||||||
|
계약을 사용한다.
|
||||||
|
|
||||||
|
Realm import는 `oidc-role-idp-mapper`를 선언한다. 실제 Google 설정 스크립트도
|
||||||
|
같은 mapper를 upsert한다. 따라서 재실행해도 mapper가 중복되지 않는다.
|
||||||
|
|
||||||
|
검증:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/verify-google-claim-to-role.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
검증기는 mock Google 로그인, Authorization Code + PKCE 교환, 최종 Keycloak
|
||||||
|
access token의 `realm_access.roles`를 차례로 확인한다.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { createHash, randomBytes } from "node:crypto";
|
||||||
|
import { chromium } from "playwright-core";
|
||||||
|
|
||||||
|
const keycloakUrl = process.env.KEYCLOAK_URL ?? "http://localhost:8080";
|
||||||
|
const password = process.env.MOCK_GOOGLE_USER_PASSWORD;
|
||||||
|
assert.ok(password);
|
||||||
|
|
||||||
|
const verifier = randomBytes(48).toString("base64url");
|
||||||
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
||||||
|
const redirectUri = "http://localhost:8088/";
|
||||||
|
|
||||||
|
const browser = await chromium.launch({
|
||||||
|
executablePath: process.env.CHROME_BIN ?? "/usr/bin/google-chrome",
|
||||||
|
headless: true,
|
||||||
|
args: ["--no-sandbox"],
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const authorizationUrl = new URL(
|
||||||
|
`${keycloakUrl}/realms/keycloak-patterns/protocol/openid-connect/auth`,
|
||||||
|
);
|
||||||
|
authorizationUrl.search = new URLSearchParams({
|
||||||
|
client_id: "spa-public",
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
response_type: "code",
|
||||||
|
scope: "openid profile email",
|
||||||
|
state: crypto.randomUUID(),
|
||||||
|
nonce: crypto.randomUUID(),
|
||||||
|
code_challenge: challenge,
|
||||||
|
code_challenge_method: "S256",
|
||||||
|
kc_idp_hint: "mock-google",
|
||||||
|
prompt: "login",
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(authorizationUrl.toString());
|
||||||
|
await page.waitForURL(/\/realms\/mock-google\//u);
|
||||||
|
await page.locator("#username").fill("mock-new-user");
|
||||||
|
await page.locator("#password").fill(password);
|
||||||
|
await page.locator("#kc-login").click();
|
||||||
|
await page.waitForURL(/localhost:8088\/\?.*code=/u);
|
||||||
|
|
||||||
|
const code = new URL(page.url()).searchParams.get("code");
|
||||||
|
assert.ok(code);
|
||||||
|
const response = await fetch(
|
||||||
|
`${keycloakUrl}/realms/keycloak-patterns/protocol/openid-connect/token`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: new URLSearchParams({
|
||||||
|
grant_type: "authorization_code",
|
||||||
|
client_id: "spa-public",
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
code,
|
||||||
|
code_verifier: verifier,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.equal(response.status, 200);
|
||||||
|
const tokens = await response.json();
|
||||||
|
const payload = JSON.parse(
|
||||||
|
Buffer.from(tokens.access_token.split(".")[1], "base64url").toString(),
|
||||||
|
);
|
||||||
|
assert.ok(payload.realm_access.roles.includes("employee-role"));
|
||||||
|
console.log("Claim-to-role verified: hd=example.test -> employee-role");
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test:first-broker": "node first-broker-login.mjs",
|
"test:first-broker": "node first-broker-login.mjs",
|
||||||
"test:claim-mapping": "node claim-mapping.mjs"
|
"test:claim-mapping": "node claim-mapping.mjs",
|
||||||
|
"test:claim-to-role": "node claim-to-role.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.55.1"
|
"playwright-core": "1.55.1"
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
{
|
{
|
||||||
"name": "user-role",
|
"name": "user-role",
|
||||||
"description": "Regular authenticated user role"
|
"description": "Regular authenticated user role"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "employee-role",
|
||||||
|
"description": "Assigned to brokered identities whose hosted-domain claim is example.test"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -213,6 +217,17 @@
|
|||||||
"claim": "hd",
|
"claim": "hd",
|
||||||
"user.attribute": "hd"
|
"user.attribute": "hd"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mock-google-example-domain-role",
|
||||||
|
"identityProviderAlias": "mock-google",
|
||||||
|
"identityProviderMapper": "oidc-role-idp-mapper",
|
||||||
|
"config": {
|
||||||
|
"syncMode": "INHERIT",
|
||||||
|
"claim": "hd",
|
||||||
|
"claim.value": "example.test",
|
||||||
|
"role": "employee-role"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"users": [
|
"users": [
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ upsert_mapper \
|
|||||||
"google-hosted-domain" \
|
"google-hosted-domain" \
|
||||||
"oidc-user-attribute-idp-mapper" \
|
"oidc-user-attribute-idp-mapper" \
|
||||||
'{"syncMode":"INHERIT","claim":"hd","user.attribute":"hd"}'
|
'{"syncMode":"INHERIT","claim":"hd","user.attribute":"hd"}'
|
||||||
|
upsert_mapper \
|
||||||
|
"google-example-domain-role" \
|
||||||
|
"oidc-role-idp-mapper" \
|
||||||
|
'{"syncMode":"INHERIT","claim":"hd","claim.value":"example.test","role":"employee-role"}'
|
||||||
|
|
||||||
echo "Google Identity Provider $action for realm '$realm'"
|
echo "Google Identity Provider $action for realm '$realm'"
|
||||||
echo "Register this exact Google redirect URI:"
|
echo "Register this exact Google redirect URI:"
|
||||||
|
|||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
set -a
|
||||||
|
. ./.env
|
||||||
|
set +a
|
||||||
|
|
||||||
|
./scripts/configure-broker-user-profile.sh >/dev/null
|
||||||
|
./scripts/set-first-broker-login-mode.sh secure
|
||||||
|
|
||||||
|
docker compose exec -T keycloak \
|
||||||
|
/opt/keycloak/bin/kcadm.sh create roles -r keycloak-patterns \
|
||||||
|
-s name=employee-role \
|
||||||
|
-s 'description=Assigned from the upstream hd claim' >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
cd google-e2e
|
||||||
|
npm install --ignore-scripts
|
||||||
|
npm run test:claim-to-role
|
||||||
Reference in New Issue
Block a user