From f43544e1343d44c6afd6688c5ba8edb1c3d48086 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Thu, 19 Dec 2024 17:42:26 +0100 Subject: [PATCH] ensure no diff if config hasn't changed --- scripts/dump-keycloak-realm.ts | 18 +- .../{ => ParsedRealmJson}/ParsedRealmJson.ts | 20 +-- .../realmConfig/ParsedRealmJson/index.ts | 3 + .../ParsedRealmJson/readRealmJsonFile.ts | 20 +++ .../ParsedRealmJson/writeRealmJsonFile.ts | 29 ++++ .../defaultConfig/defaultConfig.ts | 7 +- .../defaultConfig/realm-kc-18.json | 84 +++++---- .../defaultConfig/realm-kc-19.json | 78 +++++---- .../defaultConfig/realm-kc-20.json | 82 +++++---- .../defaultConfig/realm-kc-21.json | 42 +++-- .../defaultConfig/realm-kc-23.json | 34 +++- .../defaultConfig/realm-kc-24.json | 34 +++- .../defaultConfig/realm-kc-25.json | 34 +++- .../defaultConfig/realm-kc-26.json | 18 +- .../start-keycloak/realmConfig/realmConfig.ts | 34 ++-- src/bin/tools/Stringifyable.ts | 99 +++++++++++ src/bin/tools/canonicalStringify.ts | 164 ++++++++++++++++++ 17 files changed, 607 insertions(+), 193 deletions(-) rename src/bin/start-keycloak/realmConfig/{ => ParsedRealmJson}/ParsedRealmJson.ts (88%) create mode 100644 src/bin/start-keycloak/realmConfig/ParsedRealmJson/index.ts create mode 100644 src/bin/start-keycloak/realmConfig/ParsedRealmJson/readRealmJsonFile.ts create mode 100644 src/bin/start-keycloak/realmConfig/ParsedRealmJson/writeRealmJsonFile.ts create mode 100644 src/bin/tools/Stringifyable.ts create mode 100644 src/bin/tools/canonicalStringify.ts diff --git a/scripts/dump-keycloak-realm.ts b/scripts/dump-keycloak-realm.ts index 95b740b3..16ceaca7 100644 --- a/scripts/dump-keycloak-realm.ts +++ b/scripts/dump-keycloak-realm.ts @@ -3,10 +3,9 @@ import child_process from "child_process"; import { SemVer } from "../src/bin/tools/SemVer"; import { dumpContainerConfig } from "../src/bin/start-keycloak/realmConfig/dumpContainerConfig"; import { cacheDirPath } from "./shared/cacheDirPath"; -import { runPrettier } from "../src/bin/tools/runPrettier"; import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath"; +import { writeRealmJsonFile } from "../src/bin/start-keycloak/realmConfig/ParsedRealmJson"; import { join as pathJoin } from "path"; -import * as fs from "fs"; import chalk from "chalk"; (async () => { @@ -26,9 +25,7 @@ import chalk from "chalk"; realmName: "myrealm" }); - let sourceCode = JSON.stringify(parsedRealmJson, null, 2); - - const filePath = pathJoin( + const realmJsonFilePath = pathJoin( getThisCodebaseRootDirPath(), "src", "bin", @@ -38,12 +35,11 @@ import chalk from "chalk"; `realm-kc-${keycloakMajorVersionNumber}.json` ); - sourceCode = await runPrettier({ - sourceCode, - filePath + await writeRealmJsonFile({ + parsedRealmJson, + realmJsonFilePath, + keycloakMajorVersionNumber }); - fs.writeFileSync(filePath, Buffer.from(sourceCode, "utf8")); - - console.log(chalk.green(`Realm config dumped to ${filePath}`)); + console.log(chalk.green(`Realm config dumped to ${realmJsonFilePath}`)); })(); diff --git a/src/bin/start-keycloak/realmConfig/ParsedRealmJson.ts b/src/bin/start-keycloak/realmConfig/ParsedRealmJson/ParsedRealmJson.ts similarity index 88% rename from src/bin/start-keycloak/realmConfig/ParsedRealmJson.ts rename to src/bin/start-keycloak/realmConfig/ParsedRealmJson/ParsedRealmJson.ts index 396a13d1..43b0cb11 100644 --- a/src/bin/start-keycloak/realmConfig/ParsedRealmJson.ts +++ b/src/bin/start-keycloak/realmConfig/ParsedRealmJson/ParsedRealmJson.ts @@ -1,8 +1,6 @@ import { z } from "zod"; import { assert, type Equals } from "tsafe/assert"; -import { is } from "tsafe/is"; import { id } from "tsafe/id"; -import * as fs from "fs"; export type ParsedRealmJson = { realm: string; @@ -50,7 +48,7 @@ export type ParsedRealmJson = { }[]; }; -const zParsedRealmJson = (() => { +export const zParsedRealmJson = (() => { type TargetType = ParsedRealmJson; const zTargetType = z.object({ @@ -118,19 +116,3 @@ const zParsedRealmJson = (() => { return id>(zTargetType); })(); - -export function readRealmJsonFile(params: { - realmJsonFilePath: string; -}): ParsedRealmJson { - const { realmJsonFilePath } = params; - - const parsedRealmJson = JSON.parse( - fs.readFileSync(realmJsonFilePath).toString("utf8") - ) as unknown; - - zParsedRealmJson.parse(parsedRealmJson); - - assert(is(parsedRealmJson)); - - return parsedRealmJson; -} diff --git a/src/bin/start-keycloak/realmConfig/ParsedRealmJson/index.ts b/src/bin/start-keycloak/realmConfig/ParsedRealmJson/index.ts new file mode 100644 index 00000000..096b9c0c --- /dev/null +++ b/src/bin/start-keycloak/realmConfig/ParsedRealmJson/index.ts @@ -0,0 +1,3 @@ +export type { ParsedRealmJson } from "./ParsedRealmJson"; +export { readRealmJsonFile } from "./readRealmJsonFile"; +export { writeRealmJsonFile } from "./writeRealmJsonFile"; diff --git a/src/bin/start-keycloak/realmConfig/ParsedRealmJson/readRealmJsonFile.ts b/src/bin/start-keycloak/realmConfig/ParsedRealmJson/readRealmJsonFile.ts new file mode 100644 index 00000000..81293bb1 --- /dev/null +++ b/src/bin/start-keycloak/realmConfig/ParsedRealmJson/readRealmJsonFile.ts @@ -0,0 +1,20 @@ +import { assert } from "tsafe/assert"; +import { is } from "tsafe/is"; +import * as fs from "fs"; +import { type ParsedRealmJson, zParsedRealmJson } from "./ParsedRealmJson"; + +export function readRealmJsonFile(params: { + realmJsonFilePath: string; +}): ParsedRealmJson { + const { realmJsonFilePath } = params; + + const parsedRealmJson = JSON.parse( + fs.readFileSync(realmJsonFilePath).toString("utf8") + ) as unknown; + + zParsedRealmJson.parse(parsedRealmJson); + + assert(is(parsedRealmJson)); + + return parsedRealmJson; +} diff --git a/src/bin/start-keycloak/realmConfig/ParsedRealmJson/writeRealmJsonFile.ts b/src/bin/start-keycloak/realmConfig/ParsedRealmJson/writeRealmJsonFile.ts new file mode 100644 index 00000000..998ed0d6 --- /dev/null +++ b/src/bin/start-keycloak/realmConfig/ParsedRealmJson/writeRealmJsonFile.ts @@ -0,0 +1,29 @@ +import * as fsPr from "fs/promises"; +import { getIsPrettierAvailable, runPrettier } from "../../../tools/runPrettier"; +import { canonicalStringify } from "../../../tools/canonicalStringify"; +import type { ParsedRealmJson } from "./ParsedRealmJson"; +import { getDefaultConfig } from "../defaultConfig"; + +export async function writeRealmJsonFile(params: { + realmJsonFilePath: string; + parsedRealmJson: ParsedRealmJson; + keycloakMajorVersionNumber: number; +}): Promise { + const { realmJsonFilePath, parsedRealmJson, keycloakMajorVersionNumber } = params; + + let sourceCode = canonicalStringify({ + data: parsedRealmJson, + referenceData: getDefaultConfig({ + keycloakMajorVersionNumber + }) + }); + + if (await getIsPrettierAvailable()) { + sourceCode = await runPrettier({ + sourceCode: sourceCode, + filePath: realmJsonFilePath + }); + } + + await fsPr.writeFile(realmJsonFilePath, Buffer.from(sourceCode, "utf8")); +} diff --git a/src/bin/start-keycloak/realmConfig/defaultConfig/defaultConfig.ts b/src/bin/start-keycloak/realmConfig/defaultConfig/defaultConfig.ts index 2fd970bf..c954c83e 100644 --- a/src/bin/start-keycloak/realmConfig/defaultConfig/defaultConfig.ts +++ b/src/bin/start-keycloak/realmConfig/defaultConfig/defaultConfig.ts @@ -3,11 +3,10 @@ import { getThisCodebaseRootDirPath } from "../../../tools/getThisCodebaseRootDi import * as fs from "fs"; import { exclude } from "tsafe/exclude"; import { assert } from "tsafe/assert"; -import { type ParsedRealmJson, readRealmJsonFile } from "../ParsedRealmJson"; +import { readRealmJsonFile } from "../ParsedRealmJson/readRealmJsonFile"; +import type { ParsedRealmJson } from "../ParsedRealmJson/ParsedRealmJson"; -export function getDefaultRealmJsonFilePath(params: { - keycloakMajorVersionNumber: number; -}) { +function getDefaultRealmJsonFilePath(params: { keycloakMajorVersionNumber: number }) { const { keycloakMajorVersionNumber } = params; return pathJoin( diff --git a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-18.json b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-18.json index 7b46daf1..57400afc 100644 --- a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-18.json +++ b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-18.json @@ -756,6 +756,24 @@ "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, "protocolMappers": [ + { + "id": "8fd0d584-7052-4d04-a615-d18a71050873", + "name": "allowed-origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "allowed-origins", + "jsonType.label": "JSON", + "access.tokenResponse.claim": "false", + "claim.value": "[\"*\"]", + "introspection.token.claim": "true", + "lightweight.claim": "true" + } + }, { "id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13", "name": "locale", @@ -1336,13 +1354,13 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", - "oidc-full-name-mapper", - "saml-user-property-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-address-mapper", - "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper" ] } @@ -1393,13 +1411,13 @@ "config": { "allowed-protocol-mapper-types": [ "oidc-full-name-mapper", - "oidc-usermodel-property-mapper", - "saml-user-property-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-sha256-pairwise-sub-mapper", - "saml-role-list-mapper", "oidc-address-mapper", - "saml-user-attribute-mapper" + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper" ] } }, @@ -1517,7 +1535,7 @@ "defaultLocale": "en", "authenticationFlows": [ { - "id": "223ce532-2038-4f24-a606-2a5c73f7bd65", + "id": "f664efe4-102d-4ec1-bf11-11af67e3f178", "alias": "Account verification options", "description": "Method with which to verity the existing account", "providerId": "basic-flow", @@ -1543,7 +1561,7 @@ ] }, { - "id": "57e47732-79cc-4d60-bee7-4f0b8fd44540", + "id": "8a5630c5-eca1-4b6a-8e59-459cb6c84535", "alias": "Authentication Options", "description": "Authentication options.", "providerId": "basic-flow", @@ -1577,7 +1595,7 @@ ] }, { - "id": "c2735d89-60c0-45a4-9b3c-ae5df17df395", + "id": "c1a3eed3-25ce-44ae-93d1-f0b8148a0f8c", "alias": "Browser - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1603,7 +1621,7 @@ ] }, { - "id": "11a5a507-2b9a-443f-961b-dffd66f4318d", + "id": "6eb188ad-1041-44dd-bf8f-37cae0d98bf1", "alias": "Direct Grant - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1629,7 +1647,7 @@ ] }, { - "id": "963bd753-6ea7-4d93-ab56-30f9ab59d597", + "id": "4ee215ac-f4e5-4edb-bf76-65dc9e211543", "alias": "First broker login - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1655,7 +1673,7 @@ ] }, { - "id": "1db6a489-a3b4-44c4-b480-1d1e8c123d20", + "id": "5a1eac7e-06a0-46d8-b9ae-1f2c934331f9", "alias": "Handle Existing Account", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId": "basic-flow", @@ -1681,7 +1699,7 @@ ] }, { - "id": "7a38f32d-4f34-450f-8f03-64802d7cb8f1", + "id": "ed165166-4521-4a62-b185-c4b51643cbb1", "alias": "Reset - Conditional OTP", "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId": "basic-flow", @@ -1707,7 +1725,7 @@ ] }, { - "id": "0df88739-3739-4d70-8893-47c546f19003", + "id": "4788fb1f-fd81-4f5d-9abe-4199dd641c1e", "alias": "User creation or linking", "description": "Flow for the existing/non-existing user alternatives", "providerId": "basic-flow", @@ -1734,7 +1752,7 @@ ] }, { - "id": "35025424-e291-4c54-8a29-70aadba549ce", + "id": "d778a70f-f472-4dd3-ac40-cb5612ddc171", "alias": "Verify Existing Account by Re-authentication", "description": "Reauthentication of existing account", "providerId": "basic-flow", @@ -1760,7 +1778,7 @@ ] }, { - "id": "1813b7f2-c3c2-4b92-8ffc-9ff2d12186c6", + "id": "9c1ea8ea-7c23-4e60-b02d-1900d9dc4109", "alias": "browser", "description": "browser based authentication", "providerId": "basic-flow", @@ -1802,7 +1820,7 @@ ] }, { - "id": "954283ac-f1c2-40b6-a39f-bf23ff9f3ce8", + "id": "0ebdf418-d57d-4318-9359-7bd0cb2381f2", "alias": "clients", "description": "Base authentication for clients", "providerId": "client-flow", @@ -1844,7 +1862,7 @@ ] }, { - "id": "52a789ce-2cad-4f0f-93b2-295b7fd519f0", + "id": "5cc89293-c72e-4c5e-b31c-15558588a60d", "alias": "direct grant", "description": "OpenID Connect Resource Owner Grant", "providerId": "basic-flow", @@ -1878,7 +1896,7 @@ ] }, { - "id": "5a6a71e1-9105-45b6-b5f0-52538461357b", + "id": "5ae5a321-ccac-449e-9c19-d6dc22ab8085", "alias": "docker auth", "description": "Used by Docker clients to authenticate against the IDP", "providerId": "basic-flow", @@ -1896,7 +1914,7 @@ ] }, { - "id": "8392b6e7-bdbf-4d7f-97b6-885761c200db", + "id": "7737fdd1-0875-47e6-977b-12561cddfdc3", "alias": "first broker login", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId": "basic-flow", @@ -1923,7 +1941,7 @@ ] }, { - "id": "52136d70-8d08-42ea-b04b-cf40ea2807aa", + "id": "90f975c3-9826-461f-88ca-27c697aff86b", "alias": "forms", "description": "Username, password, otp and other auth forms.", "providerId": "basic-flow", @@ -1949,7 +1967,7 @@ ] }, { - "id": "26bbc7e6-ef01-4cdb-9dba-520e2f3f8993", + "id": "ce2722d5-9f4f-41a2-8f81-e01f7b6cee57", "alias": "http challenge", "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId": "basic-flow", @@ -1975,7 +1993,7 @@ ] }, { - "id": "f0887979-04eb-4033-8f19-0ffd8c8b7f6a", + "id": "31b5bfa7-98ad-47a2-b8e6-0669022cd8cb", "alias": "registration", "description": "registration flow", "providerId": "basic-flow", @@ -1994,7 +2012,7 @@ ] }, { - "id": "a3b7b94b-bfbf-4760-a8c9-7d9cd98d262e", + "id": "bf8a950b-be3b-4e44-8602-64e0bba492eb", "alias": "registration form", "description": "registration form", "providerId": "form-flow", @@ -2036,7 +2054,7 @@ ] }, { - "id": "dc68a665-2e51-4a22-aaad-bd693ddc77cc", + "id": "e3519800-971b-4b1d-b64e-3983ccd02dea", "alias": "reset credentials", "description": "Reset credentials for a user if they forgot their password or something", "providerId": "basic-flow", @@ -2078,7 +2096,7 @@ ] }, { - "id": "ae6b73aa-1318-4ae8-a3d9-d01b5e7d957e", + "id": "9d5a33a2-e777-4beb-95de-b84812f69c56", "alias": "saml ecp", "description": "SAML ECP Profile Authentication Flow", "providerId": "basic-flow", @@ -2098,14 +2116,14 @@ ], "authenticatorConfig": [ { - "id": "0c18de7f-0714-41f4-9a3f-ed4edd53ae9c", + "id": "4901c91d-59bd-4727-b585-8e4e44828d0a", "alias": "create unique user config", "config": { "require.password.update.after.registration": "false" } }, { - "id": "65b3c8bb-34a4-4d19-b578-245dc8ff53ea", + "id": "5062a078-83a7-4933-b0d5-3f75cc2a5003", "alias": "review profile config", "config": { "update.profile.on.first.login": "missing" diff --git a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-19.json b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-19.json index 136f1321..4b5f1e42 100644 --- a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-19.json +++ b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-19.json @@ -764,6 +764,24 @@ "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, "protocolMappers": [ + { + "id": "8fd0d584-7052-4d04-a615-d18a71050873", + "name": "allowed-origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "allowed-origins", + "jsonType.label": "JSON", + "access.tokenResponse.claim": "false", + "claim.value": "[\"*\"]", + "introspection.token.claim": "true", + "lightweight.claim": "true" + } + }, { "id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13", "name": "locale", @@ -1344,14 +1362,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", - "oidc-usermodel-property-mapper", - "oidc-usermodel-attribute-mapper", "oidc-address-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", "saml-role-list-mapper", - "oidc-sha256-pairwise-sub-mapper" + "oidc-usermodel-property-mapper" ] } }, @@ -1400,14 +1418,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", - "oidc-usermodel-property-mapper", - "saml-role-list-mapper", "oidc-full-name-mapper", - "saml-user-property-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", "oidc-address-mapper", - "saml-user-attribute-mapper" + "saml-role-list-mapper" ] } }, @@ -1525,7 +1543,7 @@ "defaultLocale": "en", "authenticationFlows": [ { - "id": "1f4d4e13-1591-4751-8985-17886a8c98a9", + "id": "8ccfe057-5ce6-499b-9fae-3cd89b62bf01", "alias": "Account verification options", "description": "Method with which to verity the existing account", "providerId": "basic-flow", @@ -1551,7 +1569,7 @@ ] }, { - "id": "126f07c3-1bcb-4a02-bf16-bb44674bf55d", + "id": "f3b9ab2e-41c2-4e73-876b-e2c275d6d14e", "alias": "Authentication Options", "description": "Authentication options.", "providerId": "basic-flow", @@ -1585,7 +1603,7 @@ ] }, { - "id": "eb3a08c8-5f99-49b6-b02b-16b62571f273", + "id": "df1329cc-777c-42d8-aa2f-c5d5ddaaf5a4", "alias": "Browser - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1611,7 +1629,7 @@ ] }, { - "id": "3dc19838-5025-4bbb-b569-b574bd5a8d90", + "id": "f78a4cbc-66ff-4caa-8066-67aff94946f4", "alias": "Direct Grant - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1637,7 +1655,7 @@ ] }, { - "id": "70d6fd40-d740-4dae-b0e6-350f8e9d4a1c", + "id": "4b20995b-5553-45db-86b0-05c3fe14edb1", "alias": "First broker login - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1663,7 +1681,7 @@ ] }, { - "id": "6e24dcb3-5818-483c-8e44-883858171901", + "id": "0a7cc6b7-e427-4f72-b44e-a02133241bad", "alias": "Handle Existing Account", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId": "basic-flow", @@ -1689,7 +1707,7 @@ ] }, { - "id": "ac6254cd-403b-457b-b308-22a2a0e4f99d", + "id": "e24e73c0-dd51-4fdc-a916-284f11f38487", "alias": "Reset - Conditional OTP", "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId": "basic-flow", @@ -1715,7 +1733,7 @@ ] }, { - "id": "485e74e6-9b3e-4b2c-a9b9-927802dc4f06", + "id": "37ee5a12-01c2-41b0-aafa-e9c6661ff544", "alias": "User creation or linking", "description": "Flow for the existing/non-existing user alternatives", "providerId": "basic-flow", @@ -1742,7 +1760,7 @@ ] }, { - "id": "ff9bb879-1d6a-4d1c-9836-1e4fab6f8997", + "id": "8902a1a7-c2ee-4648-869f-dd5ef89184fc", "alias": "Verify Existing Account by Re-authentication", "description": "Reauthentication of existing account", "providerId": "basic-flow", @@ -1768,7 +1786,7 @@ ] }, { - "id": "af8b2470-d581-401c-9984-762b966ebcc2", + "id": "77c78eed-4bcd-4779-b39f-10135be84946", "alias": "browser", "description": "browser based authentication", "providerId": "basic-flow", @@ -1810,7 +1828,7 @@ ] }, { - "id": "414dbda4-eb3f-4baa-b23a-d3423af1eae6", + "id": "c6398883-01e6-47a1-bb97-c09f2983155d", "alias": "clients", "description": "Base authentication for clients", "providerId": "client-flow", @@ -1852,7 +1870,7 @@ ] }, { - "id": "1cae0c4b-8dfb-4f5d-a781-e74d0a13c940", + "id": "78ab5fb8-f35b-4053-b264-94b208000b13", "alias": "direct grant", "description": "OpenID Connect Resource Owner Grant", "providerId": "basic-flow", @@ -1886,7 +1904,7 @@ ] }, { - "id": "e798b655-7d85-4b6b-aee7-1448a3e1e0ea", + "id": "959e154b-034e-413d-9b19-211e7d9ba33d", "alias": "docker auth", "description": "Used by Docker clients to authenticate against the IDP", "providerId": "basic-flow", @@ -1904,7 +1922,7 @@ ] }, { - "id": "eb94b723-1041-426a-87bf-f7b4bd2f485d", + "id": "001e253d-bdbd-41e2-81c7-1c7b239feeb1", "alias": "first broker login", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId": "basic-flow", @@ -1931,7 +1949,7 @@ ] }, { - "id": "452d1d5f-7632-44d7-bc89-77ff2b209b3e", + "id": "45481bb0-18fe-4a26-a77c-35a5afe58436", "alias": "forms", "description": "Username, password, otp and other auth forms.", "providerId": "basic-flow", @@ -1957,7 +1975,7 @@ ] }, { - "id": "7c1b9e8f-6b57-49d1-a9a7-494862f93c0f", + "id": "bb47b847-5a55-4c08-909e-9f6f8d8a0636", "alias": "http challenge", "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId": "basic-flow", @@ -1983,7 +2001,7 @@ ] }, { - "id": "2b38f34a-1739-499e-bb24-1dff96f32009", + "id": "77e6e169-05b7-4b89-af00-09cfe1604eed", "alias": "registration", "description": "registration flow", "providerId": "basic-flow", @@ -2002,7 +2020,7 @@ ] }, { - "id": "d26ae72b-a933-44dc-9927-1c82757004b2", + "id": "aef03fe8-1a70-40c3-879f-25588f75c119", "alias": "registration form", "description": "registration form", "providerId": "form-flow", @@ -2044,7 +2062,7 @@ ] }, { - "id": "222ee8d6-1892-4768-9ada-720274b6bf9a", + "id": "990abff7-e2ba-4217-984e-8890cbc2b3a9", "alias": "reset credentials", "description": "Reset credentials for a user if they forgot their password or something", "providerId": "basic-flow", @@ -2086,7 +2104,7 @@ ] }, { - "id": "e8b4d92c-27c1-4a9b-9b16-7ceb810fa230", + "id": "d9894cf6-2f99-493e-ac47-853f54bfc9c6", "alias": "saml ecp", "description": "SAML ECP Profile Authentication Flow", "providerId": "basic-flow", @@ -2106,14 +2124,14 @@ ], "authenticatorConfig": [ { - "id": "e5847a0b-855d-4d93-85fd-94714be3ed92", + "id": "101ed8ff-4383-4539-aa52-2d1e69698b78", "alias": "create unique user config", "config": { "require.password.update.after.registration": "false" } }, { - "id": "a2a18aa4-bd4c-4c2a-9286-e9d6c64f4812", + "id": "049042a5-3551-4c16-81a1-64d86f5aa1e5", "alias": "review profile config", "config": { "update.profile.on.first.login": "missing" diff --git a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-20.json b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-20.json index c30db8f6..420fbd07 100644 --- a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-20.json +++ b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-20.json @@ -775,6 +775,24 @@ "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, "protocolMappers": [ + { + "id": "8fd0d584-7052-4d04-a615-d18a71050873", + "name": "allowed-origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "allowed-origins", + "jsonType.label": "JSON", + "access.tokenResponse.claim": "false", + "claim.value": "[\"*\"]", + "introspection.token.claim": "true", + "lightweight.claim": "true" + } + }, { "id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13", "name": "locale", @@ -1355,14 +1373,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "oidc-address-mapper", - "oidc-full-name-mapper", - "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", - "saml-user-property-mapper", - "saml-user-attribute-mapper" + "saml-user-attribute-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper" ] } }, @@ -1411,14 +1429,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "saml-user-attribute-mapper", - "saml-role-list-mapper", - "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", + "oidc-usermodel-attribute-mapper", + "saml-role-list-mapper", + "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", - "saml-user-property-mapper", - "oidc-usermodel-attribute-mapper" + "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper" ] } }, @@ -1536,7 +1554,7 @@ "defaultLocale": "en", "authenticationFlows": [ { - "id": "c40791b4-4d59-4df2-bebd-2b71e793704f", + "id": "30a878f0-57aa-4d20-bab0-6cf1d7317a5c", "alias": "Account verification options", "description": "Method with which to verity the existing account", "providerId": "basic-flow", @@ -1562,7 +1580,7 @@ ] }, { - "id": "8813b6d1-8b88-4672-b29b-8420ce3f3975", + "id": "d386affe-d1fe-472a-bee6-54105d0101f5", "alias": "Authentication Options", "description": "Authentication options.", "providerId": "basic-flow", @@ -1596,7 +1614,7 @@ ] }, { - "id": "a9937c40-a1ee-4c57-adf7-ede0a9983953", + "id": "77b95bc0-bd0c-46b7-8240-3182023e9d50", "alias": "Browser - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1622,7 +1640,7 @@ ] }, { - "id": "2d494b5a-eb73-40d0-94d3-a8d8024a7db4", + "id": "bc96d3d6-29a1-42af-a63e-bb67a8c6d78f", "alias": "Direct Grant - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1648,7 +1666,7 @@ ] }, { - "id": "2e977f5a-8110-412b-b704-3e15164dbb1b", + "id": "7697ca74-5c2b-45ab-9335-e0f6dec59b5c", "alias": "First broker login - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1674,7 +1692,7 @@ ] }, { - "id": "6f171b4b-8723-4e6d-bb1e-6b4293a7bb3f", + "id": "534cb120-f600-4f40-9707-7b781bdbce48", "alias": "Handle Existing Account", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId": "basic-flow", @@ -1700,7 +1718,7 @@ ] }, { - "id": "2dbb7f27-757d-4178-8217-4a24fdb0163c", + "id": "f884b048-b223-4ed6-ae16-e49a4255131e", "alias": "Reset - Conditional OTP", "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId": "basic-flow", @@ -1726,7 +1744,7 @@ ] }, { - "id": "7295aaf7-acf4-4b78-8186-d2415ea4ede0", + "id": "61c7966c-ad72-49f5-84dd-376152348092", "alias": "User creation or linking", "description": "Flow for the existing/non-existing user alternatives", "providerId": "basic-flow", @@ -1753,7 +1771,7 @@ ] }, { - "id": "e0d34d7c-7bbb-4847-8864-fbd97a1f3e89", + "id": "72412d0f-dd1b-49fe-bb0b-9dad99eb0491", "alias": "Verify Existing Account by Re-authentication", "description": "Reauthentication of existing account", "providerId": "basic-flow", @@ -1779,7 +1797,7 @@ ] }, { - "id": "5f3d0fb0-d95e-4841-89d3-a27d0cdbbcb4", + "id": "6b76613e-0d39-440d-aab4-98eaffb1e96a", "alias": "browser", "description": "browser based authentication", "providerId": "basic-flow", @@ -1821,7 +1839,7 @@ ] }, { - "id": "c246380d-af25-4151-ab19-1f1e5b553008", + "id": "0ff60395-fa89-41be-ad22-fab339e67c49", "alias": "clients", "description": "Base authentication for clients", "providerId": "client-flow", @@ -1863,7 +1881,7 @@ ] }, { - "id": "abacf398-0f1f-4f28-a310-8d306d588048", + "id": "bbb3ece7-7dbf-4aba-80c3-dde4b9cdd0b6", "alias": "direct grant", "description": "OpenID Connect Resource Owner Grant", "providerId": "basic-flow", @@ -1897,7 +1915,7 @@ ] }, { - "id": "a0f87683-619a-44d4-8b4f-4b053bba2346", + "id": "f5f2c0f6-7dbf-4978-845e-6cacac23aa13", "alias": "docker auth", "description": "Used by Docker clients to authenticate against the IDP", "providerId": "basic-flow", @@ -1915,7 +1933,7 @@ ] }, { - "id": "e8820c7c-22a7-4618-beb7-3e09be72c00c", + "id": "cf463104-19e2-41a8-8a53-d3dd30b75344", "alias": "first broker login", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId": "basic-flow", @@ -1942,7 +1960,7 @@ ] }, { - "id": "cac00c38-ee44-44c9-b95e-cc755bab36ef", + "id": "b99b60dc-41ad-487d-be69-a2eefa954a9d", "alias": "forms", "description": "Username, password, otp and other auth forms.", "providerId": "basic-flow", @@ -1968,7 +1986,7 @@ ] }, { - "id": "688cde36-507e-4a68-afdf-18ec4ad626a7", + "id": "18731296-2c96-4f98-a884-027e629e4f9d", "alias": "http challenge", "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId": "basic-flow", @@ -1994,7 +2012,7 @@ ] }, { - "id": "e058697c-f450-4f14-ae64-04e9299fa24f", + "id": "9a9dce17-5425-4fd5-b3b8-81410e1dbce4", "alias": "registration", "description": "registration flow", "providerId": "basic-flow", @@ -2013,7 +2031,7 @@ ] }, { - "id": "ad768088-32c9-4979-90dd-61bf111fd72e", + "id": "d0a24e08-cb69-4949-9518-50ae7a96ee49", "alias": "registration form", "description": "registration form", "providerId": "form-flow", @@ -2055,7 +2073,7 @@ ] }, { - "id": "47d4b090-f965-4588-b5bc-029ccb59876f", + "id": "6a9aa554-afba-487f-9c82-e94c81c15b3b", "alias": "reset credentials", "description": "Reset credentials for a user if they forgot their password or something", "providerId": "basic-flow", @@ -2097,7 +2115,7 @@ ] }, { - "id": "1f68feec-7f99-4c49-afe6-45d46684ca21", + "id": "e0361d46-eab4-41a6-bb2e-1dc6a5a6b073", "alias": "saml ecp", "description": "SAML ECP Profile Authentication Flow", "providerId": "basic-flow", @@ -2117,14 +2135,14 @@ ], "authenticatorConfig": [ { - "id": "bd7365c7-842b-4bc6-a4ca-498cf025c210", + "id": "053d6017-e54c-418a-abe7-44dd4752eacb", "alias": "create unique user config", "config": { "require.password.update.after.registration": "false" } }, { - "id": "b929192d-f650-4a09-9701-3d3216547552", + "id": "8b545cf4-ab9e-4226-b3c0-d7ac773eae2f", "alias": "review profile config", "config": { "update.profile.on.first.login": "missing" diff --git a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-21.json b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-21.json index a4e9f84b..da70a945 100644 --- a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-21.json +++ b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-21.json @@ -408,9 +408,9 @@ "otpPolicyPeriod": 30, "otpPolicyCodeReusable": false, "otpSupportedApplications": [ - "totpAppGoogleName", "totpAppFreeOTPName", - "totpAppMicrosoftAuthenticatorName" + "totpAppMicrosoftAuthenticatorName", + "totpAppGoogleName" ], "webAuthnPolicyRpEntityName": "keycloak", "webAuthnPolicySignatureAlgorithms": ["ES256"], @@ -779,6 +779,24 @@ "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, "protocolMappers": [ + { + "id": "8fd0d584-7052-4d04-a615-d18a71050873", + "name": "allowed-origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "allowed-origins", + "jsonType.label": "JSON", + "access.tokenResponse.claim": "false", + "claim.value": "[\"*\"]", + "introspection.token.claim": "true", + "lightweight.claim": "true" + } + }, { "id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13", "name": "locale", @@ -1359,13 +1377,13 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "saml-user-attribute-mapper", - "saml-user-property-mapper", - "oidc-sha256-pairwise-sub-mapper", - "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", - "oidc-full-name-mapper", "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper", + "saml-role-list-mapper", + "oidc-full-name-mapper", + "saml-user-attribute-mapper", "oidc-address-mapper" ] } @@ -1415,14 +1433,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "oidc-address-mapper", "oidc-usermodel-property-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", - "saml-user-property-mapper", "saml-role-list-mapper", - "saml-user-attribute-mapper" + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "oidc-full-name-mapper" ] } }, diff --git a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-23.json b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-23.json index 8e720d31..de8cd0f0 100644 --- a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-23.json +++ b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-23.json @@ -789,6 +789,24 @@ "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, "protocolMappers": [ + { + "id": "8fd0d584-7052-4d04-a615-d18a71050873", + "name": "allowed-origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "allowed-origins", + "jsonType.label": "JSON", + "access.tokenResponse.claim": "false", + "claim.value": "[\"*\"]", + "lightweight.claim": "true" + } + }, { "id": "59cde7ae-2218-4a8e-83af-cad992c3a700", "name": "locale", @@ -1401,14 +1419,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", - "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-user-property-mapper", - "oidc-usermodel-property-mapper" + "saml-role-list-mapper" ] } }, @@ -1477,14 +1495,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", "saml-role-list-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-address-mapper", "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-address-mapper", "oidc-full-name-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-usermodel-property-mapper" + "oidc-sha256-pairwise-sub-mapper" ] } } diff --git a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-24.json b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-24.json index 73793582..b39c66c4 100644 --- a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-24.json +++ b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-24.json @@ -919,6 +919,24 @@ "claim.name": "locale", "jsonType.label": "String" } + }, + { + "id": "8fd0d584-7052-4d04-a615-d18a71050873", + "name": "allowed-origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "allowed-origins", + "jsonType.label": "JSON", + "access.tokenResponse.claim": "false", + "claim.value": "[\"*\"]", + "lightweight.claim": "true" + } } ], "defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"], @@ -1545,14 +1563,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", "saml-role-list-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper", - "saml-user-attribute-mapper", "saml-user-property-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-full-name-mapper" + "oidc-sha256-pairwise-sub-mapper" ] } }, @@ -1584,14 +1602,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", - "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", - "oidc-address-mapper", - "oidc-usermodel-attribute-mapper", - "saml-user-property-mapper" + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper" ] } }, diff --git a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-25.json b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-25.json index 969f3bc9..832d3d3c 100644 --- a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-25.json +++ b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-25.json @@ -964,6 +964,24 @@ "claim.name": "locale", "jsonType.label": "String" } + }, + { + "id": "8fd0d584-7052-4d04-a615-d18a71050873", + "name": "allowed-origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "allowed-origins", + "jsonType.label": "JSON", + "access.tokenResponse.claim": "false", + "claim.value": "[\"*\"]", + "lightweight.claim": "true" + } } ], "defaultClientScopes": [ @@ -1618,14 +1636,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "saml-role-list-mapper", - "oidc-full-name-mapper", - "saml-user-property-mapper", - "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", - "oidc-usermodel-property-mapper" + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper" ] } }, @@ -1657,12 +1675,12 @@ "allowed-protocol-mapper-types": [ "oidc-address-mapper", "saml-user-attribute-mapper", - "oidc-full-name-mapper", "saml-role-list-mapper", + "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", - "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", - "oidc-usermodel-property-mapper" + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper" ] } }, diff --git a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-26.json b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-26.json index 281406ca..916465c3 100644 --- a/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-26.json +++ b/src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-26.json @@ -1657,14 +1657,14 @@ "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ - "saml-user-attribute-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-usermodel-property-mapper", - "oidc-full-name-mapper", - "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", - "saml-user-property-mapper" + "oidc-full-name-mapper" ] } }, @@ -1695,13 +1695,13 @@ "config": { "allowed-protocol-mapper-types": [ "oidc-usermodel-attribute-mapper", - "oidc-full-name-mapper", - "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "oidc-address-mapper", + "oidc-full-name-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", - "oidc-sha256-pairwise-sub-mapper" + "saml-user-attribute-mapper" ] } }, diff --git a/src/bin/start-keycloak/realmConfig/realmConfig.ts b/src/bin/start-keycloak/realmConfig/realmConfig.ts index dbba7e1e..72ab2fef 100644 --- a/src/bin/start-keycloak/realmConfig/realmConfig.ts +++ b/src/bin/start-keycloak/realmConfig/realmConfig.ts @@ -1,6 +1,5 @@ import type { BuildContext } from "../../shared/buildContext"; import { assert } from "tsafe/assert"; -import { runPrettier, getIsPrettierAvailable } from "../../tools/runPrettier"; import { getDefaultConfig } from "./defaultConfig"; import { prepareRealmConfig, @@ -14,7 +13,11 @@ import { sep as pathSep } from "path"; import { existsAsync } from "../../tools/fs.existsAsync"; -import { readRealmJsonFile, type ParsedRealmJson } from "./ParsedRealmJson"; +import { + readRealmJsonFile, + writeRealmJsonFile, + type ParsedRealmJson +} from "./ParsedRealmJson"; import { dumpContainerConfig, type BuildContextLike as BuildContextLike_dumpContainerConfig @@ -80,22 +83,11 @@ export async function getRealmConfig(params: { } } - const writeRealmJsonFile = async (params: { parsedRealmJson: ParsedRealmJson }) => { - const { parsedRealmJson } = params; - - let sourceCode = JSON.stringify(parsedRealmJson, null, 2); - - if (await getIsPrettierAvailable()) { - sourceCode = await runPrettier({ - sourceCode, - filePath: realmJsonFilePath - }); - } - - fs.writeFileSync(realmJsonFilePath, sourceCode); - }; - - await writeRealmJsonFile({ parsedRealmJson }); + await writeRealmJsonFile({ + realmJsonFilePath, + parsedRealmJson, + keycloakMajorVersionNumber + }); const { onRealmConfigChange } = (() => { const run = runExclusive.build(async () => { @@ -119,7 +111,11 @@ export async function getRealmConfig(params: { return; } - await writeRealmJsonFile({ parsedRealmJson }); + await writeRealmJsonFile({ + realmJsonFilePath, + parsedRealmJson, + keycloakMajorVersionNumber + }); console.log( [ diff --git a/src/bin/tools/Stringifyable.ts b/src/bin/tools/Stringifyable.ts new file mode 100644 index 00000000..64422067 --- /dev/null +++ b/src/bin/tools/Stringifyable.ts @@ -0,0 +1,99 @@ +import { z } from "zod"; +import { same } from "evt/tools/inDepth/same"; +import { assert, type Equals } from "tsafe/assert"; +import { id } from "tsafe/id"; + +export type Stringifyable = + | StringifyableAtomic + | StringifyableObject + | StringifyableArray; + +export type StringifyableAtomic = string | number | boolean | null; + +// NOTE: Use Record +interface StringifyableObject { + [key: string]: Stringifyable; +} + +// NOTE: Use Stringifyable[] +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +interface StringifyableArray extends Array {} + +export const zStringifyableAtomic = (() => { + type TargetType = StringifyableAtomic; + + const zTargetType = z.union([z.string(), z.number(), z.boolean(), z.null()]); + + assert, TargetType>>(); + + return id>(zTargetType); +})(); + +export const zStringifyable: z.ZodType = z + .any() + .superRefine((val, ctx) => { + const isStringifyable = same(JSON.parse(JSON.stringify(val)), val); + if (!isStringifyable) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Not stringifyable" + }); + } + }); + +export function getIsAtomic( + stringifyable: Stringifyable +): stringifyable is StringifyableAtomic { + return ( + ["string", "number", "boolean"].includes(typeof stringifyable) || + stringifyable === null + ); +} + +export const { getValueAtPath } = (() => { + function getValueAtPath_rec( + stringifyable: Stringifyable, + path: (string | number)[] + ): Stringifyable | undefined { + if (path.length === 0) { + return stringifyable; + } + + if (getIsAtomic(stringifyable)) { + return undefined; + } + + const [first, ...rest] = path; + + let dereferenced: Stringifyable | undefined; + + if (stringifyable instanceof Array) { + if (typeof first !== "number") { + return undefined; + } + + dereferenced = stringifyable[first]; + } else { + if (typeof first !== "string") { + return undefined; + } + + dereferenced = stringifyable[first]; + } + + if (dereferenced === undefined) { + return undefined; + } + + return getValueAtPath_rec(dereferenced, rest); + } + + function getValueAtPath( + stringifyableObjectOrArray: Record | Stringifyable[], + path: (string | number)[] + ): Stringifyable | undefined { + return getValueAtPath_rec(stringifyableObjectOrArray, path); + } + + return { getValueAtPath }; +})(); diff --git a/src/bin/tools/canonicalStringify.ts b/src/bin/tools/canonicalStringify.ts new file mode 100644 index 00000000..facefdc6 --- /dev/null +++ b/src/bin/tools/canonicalStringify.ts @@ -0,0 +1,164 @@ +import { getIsAtomic, getValueAtPath, type Stringifyable } from "./Stringifyable"; + +export function canonicalStringify(params: { + data: Record | Stringifyable[]; + referenceData: Record | Stringifyable[]; +}): string { + const { data, referenceData } = params; + + return JSON.stringify( + makeDeterministicCopy({ + path: [], + data, + getCanonicalKeys: path => { + const referenceValue = (() => { + const path_patched: (string | number)[] = []; + + for (let i = 0; i < path.length; i++) { + let value_i = getValueAtPath(referenceData, [ + ...path_patched, + path[i] + ]); + + if (value_i !== undefined) { + path_patched.push(path[i]); + continue; + } + + if (typeof path[i] !== "number") { + return undefined; + } + + value_i = getValueAtPath(referenceData, [...path_patched, 0]); + + if (value_i !== undefined) { + path_patched.push(0); + continue; + } + + return undefined; + } + + return getValueAtPath(referenceData, path_patched); + })(); + + if (referenceValue === undefined) { + return undefined; + } + + if (getIsAtomic(referenceValue)) { + return undefined; + } + + if (referenceValue instanceof Array) { + return undefined; + } + + return Object.keys(referenceValue); + } + }), + null, + 2 + ); +} + +function makeDeterministicCopy(params: { + path: (string | number)[]; + data: Stringifyable; + getCanonicalKeys: (path: (string | number)[]) => string[] | undefined; +}): Stringifyable { + const { path, data, getCanonicalKeys } = params; + + if (getIsAtomic(data)) { + return data; + } + + if (data instanceof Array) { + return makeDeterministicCopy_array({ + path, + data, + getCanonicalKeys + }); + } + + return makeDeterministicCopy_record({ + path, + data, + getCanonicalKeys + }); +} + +function makeDeterministicCopy_record(params: { + path: (string | number)[]; + data: Record; + getCanonicalKeys: (path: (string | number)[]) => string[] | undefined; +}): Record { + const { path, data, getCanonicalKeys } = params; + + const keysOfAtomicValues: string[] = []; + const keysOfNonAtomicValues: string[] = []; + + for (const [key, value] of Object.entries(data)) { + if (getIsAtomic(value)) { + keysOfAtomicValues.push(key); + } else { + keysOfNonAtomicValues.push(key); + } + } + + keysOfAtomicValues.sort(); + keysOfNonAtomicValues.sort(); + + const keys = [...keysOfAtomicValues, ...keysOfNonAtomicValues]; + + reorder_according_to_canonical: { + const canonicalKeys = getCanonicalKeys(path); + + if (canonicalKeys === undefined) { + break reorder_according_to_canonical; + } + + const keys_toPrepend: string[] = []; + + for (const key of canonicalKeys) { + const indexOfKey = keys.indexOf(key); + + if (indexOfKey === -1) { + continue; + } + + keys.splice(indexOfKey, 1); + keys_toPrepend.push(key); + } + + keys.unshift(...keys_toPrepend); + } + + const result: Record = {}; + + for (const key of keys) { + result[key] = makeDeterministicCopy({ + path: [...path, key], + data: data[key], + getCanonicalKeys + }); + } + + return result; +} + +function makeDeterministicCopy_array(params: { + path: (string | number)[]; + data: Stringifyable[]; + getCanonicalKeys: (path: (string | number)[]) => string[] | undefined; +}): Stringifyable[] { + const { path, data, getCanonicalKeys } = params; + + return [...data].map((entry, i) => + makeDeterministicCopy({ + path: [...path, i], + data: entry, + getCanonicalKeys + }) + ); +}