From 44bfe50a11d5f9127be55c8ed20680662f6eaf02 Mon Sep 17 00:00:00 2001 From: Wojtek Trocki Date: Fri, 5 Jun 2020 17:20:41 +0100 Subject: [PATCH] Update keycloak (#425) --- client/package.json | 2 +- .../integrations/keycloak/docker-compose.yml | 2 +- server/integrations/keycloak/getToken.js | 4 +- server/integrations/keycloak/initKeycloak.js | 332 +-- .../integrations/keycloak/realm-export.json | 2073 +++++++++-------- server/package.json | 4 +- server/src/config/_keycloak.json | 11 +- .../walkthrough.adoc | 21 +- .../disabled_walkthrough.adoc | 407 ---- .../disabled_walkthrough.json | 8 - .../images/arch.png | Bin 24385 -> 0 bytes 11 files changed, 1319 insertions(+), 1545 deletions(-) delete mode 100644 walkthroughs/2-exploring-datasync-local/disabled_walkthrough.adoc delete mode 100644 walkthroughs/2-exploring-datasync-local/disabled_walkthrough.json delete mode 100644 walkthroughs/2-exploring-datasync-local/images/arch.png diff --git a/client/package.json b/client/package.json index 293044c1..11616b8c 100644 --- a/client/package.json +++ b/client/package.json @@ -21,7 +21,7 @@ "graphql": "14.6.0", "graphql-tag": "2.10.3", "ionicons": "5.0.1", - "keycloak-js": "9.0.2", + "keycloak-js": "10.0.2", "offix-cache": "0.15.0", "offix-client": "0.15.0", "react": "16.13.1", diff --git a/server/integrations/keycloak/docker-compose.yml b/server/integrations/keycloak/docker-compose.yml index 51f3c937..3563fe7c 100644 --- a/server/integrations/keycloak/docker-compose.yml +++ b/server/integrations/keycloak/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: keycloak: - image: jboss/keycloak:3.4.3.Final + image: jboss/keycloak:10.0.2 ports: - "8080:8080" environment: diff --git a/server/integrations/keycloak/getToken.js b/server/integrations/keycloak/getToken.js index 4558c3d2..58fa2ff1 100644 --- a/server/integrations/keycloak/getToken.js +++ b/server/integrations/keycloak/getToken.js @@ -8,8 +8,8 @@ const settings = { username: username || 'developer', password: password || 'developer', grant_type: 'password', - client_id: 'voyager-testing-public', - realmName: 'voyager-testing' + client_id: 'datasync-starter-client', + realmName: 'datasync-starter' } tokenRequester(baseUrl, settings) diff --git a/server/integrations/keycloak/initKeycloak.js b/server/integrations/keycloak/initKeycloak.js index ac953ef3..9211f6a0 100644 --- a/server/integrations/keycloak/initKeycloak.js +++ b/server/integrations/keycloak/initKeycloak.js @@ -4,222 +4,239 @@ * http://localhost:8080 * It does the following: * * Imports a new realm located in ./realm-export.json. This realm has two clients - * * voyager-testing-public - public client used by the mobile application - * * voyager-testing-bearer - bearer client used by the server to authenticate requests + * * datasync-starter-client- public client used by the mobile application + * * datasync-starter-server - bearer client used by the server to authenticate requests * * Creates the realm roles and client roles defined in the realmRoleNames and clientRoleNames lists * * Creates the users defined in the users list * * Assigns the client and realm roles to the users */ -const axios = require('axios') -const realmToImport = require('./realm-export.json') +const axios = require("axios"); +const realmToImport = require("./realm-export.json"); // the keycloak server we're working against -const KEYCLOAK_URL = 'http://localhost:8080/auth' +const KEYCLOAK_URL = "http://localhost:8080/auth"; // name of the realm -const APP_REALM = 'voyager-testing' +const APP_REALM = "datasync-starter"; // name of the admin realm -const ADMIN_REALM = 'master' +const ADMIN_REALM = "master"; -const RESOURCE = 'admin-cli' -const ADMIN_USERNAME = 'admin' -const ADMIN_PASSWORD = 'admin' -let token = '' +const RESOURCE = "admin-cli"; +const ADMIN_USERNAME = "admin"; +const ADMIN_PASSWORD = "admin"; +let token = ""; // The keycloak client used by the sample app -const PUBLIC_CLIENT_NAME = 'voyager-testing-public' -const BEARER_CLIENT_NAME = 'voyager-testing-bearer' -let PUBLIC_CLIENT +const PUBLIC_CLIENT_NAME = "datasync-starter-client"; +const BEARER_CLIENT_NAME = "datasync-starter-server"; +let PUBLIC_CLIENT; // The client roles you want created for the BEARER_CLIENT_NAME client -const clientRoleNames = [ - 'admin', - 'developer' -] +const clientRoleNames = ["admin", "developer"]; // The realm roles we want for the realm -const realmRoleNames = [ - 'admin', - 'developer' -] +const realmRoleNames = ["admin", "developer"]; -let realmRoles -let clientRoles +let realmRoles; // The users we want to create const users = [ { - name: 'admin', - password: 'admin', - realmRoles: [ - 'admin' - ], - clientRoles: [ - 'admin' - ] + name: "admin", + password: "admin", + realmRoles: ["admin"], + clientRoles: ["admin"], }, { - name: 'developer', - password: 'developer', - realmRoles: [ - 'developer' - ], - clientRoles: [ - 'developer' - ] - } -] + name: "developer", + password: "developer", + realmRoles: ["developer"], + clientRoles: ["developer"], + }, +]; // This is called by an immediately invoked function expression // at the bottom of the file async function prepareKeycloak() { try { - console.log('Authenticating with keycloak server') - token = await authenticateKeycloak() + console.log("Authenticating with keycloak server"); + token = await authenticateKeycloak(); // Always do a hard reset first just to keep things tidy - console.log('Going to reset keycloak') - await resetKeycloakConfiguration() + console.log("Going to reset keycloak"); + await resetKeycloakConfiguration(); - console.log('Importing sample realm into keycloak') - await importRealm() + console.log("Importing sample realm into keycloak"); + await importRealm(); - console.log('Fetching available clients from keycloak') - const clients = await getClients() + console.log("Fetching available clients from keycloak"); + const clients = await getClients(); // Get the public client object from keycloak // Need this for the ID assigned by keycloak - PUBLIC_CLIENT = clients.find((client) => client.clientId === PUBLIC_CLIENT_NAME) - BEARER_CLIENT = clients.find((client) => client.clientId === BEARER_CLIENT_NAME) - - console.log('creating client roles') + PUBLIC_CLIENT = clients.find( + (client) => client.clientId === PUBLIC_CLIENT_NAME + ); + BEARER_CLIENT = clients.find( + (client) => client.clientId === BEARER_CLIENT_NAME + ); + + console.log("creating client roles"); for (let roleName of clientRoleNames) { - await createClientRole(BEARER_CLIENT, roleName) - await createClientRole(PUBLIC_CLIENT, roleName) + await createClientRole(BEARER_CLIENT, roleName); + await createClientRole(PUBLIC_CLIENT, roleName); } - console.log('creating realm roles') + console.log("creating realm roles"); for (let roleName of realmRoleNames) { - await createRealmRole(roleName) + await createRealmRole(roleName); } // get the actual role objects from keycloak after creating them // need to get the ids that were created on them - realmRoles = await getRealmRoles() - bearerClientRoles = await getClientRoles(BEARER_CLIENT) - publicClientRoles = await getClientRoles(PUBLIC_CLIENT) + realmRoles = await getRealmRoles(); + bearerClientRoles = await getClientRoles(BEARER_CLIENT); + publicClientRoles = await getClientRoles(PUBLIC_CLIENT); for (let user of users) { // Create a new user - console.log(`creating user ${user.name} with password ${user.password}`) - const userIdUrl = await createUser(user.name, user.password) + console.log(`creating user ${user.name} with password ${user.password}`); + const userIdUrl = await createUser(user.name, user.password); // Assign roles to the user - await assignRealmRolesToUser(user, userIdUrl) - await assignClientRolesToUser(user, BEARER_CLIENT, bearerClientRoles, userIdUrl) - await assignClientRolesToUser(user, PUBLIC_CLIENT, publicClientRoles, userIdUrl) + await assignRealmRolesToUser(user, userIdUrl); + await assignClientRolesToUser( + user, + BEARER_CLIENT, + bearerClientRoles, + userIdUrl + ); + await assignClientRolesToUser( + user, + PUBLIC_CLIENT, + publicClientRoles, + userIdUrl + ); } - const publicInstallation = await getClientInstallation(PUBLIC_CLIENT) - - console.log() - console.log('Your keycloak server is set up for local usage and development') - console.log() - console.log('Copy the following app config into the following files:') - console.log('- client/public/keycloak.json') - console.log('- server/src/config/keycloak.json') - console.log() - console.log(JSON.stringify(publicInstallation, null, 2)) - console.log() - console.log('Done. Please follow the instructions printed above to ensure your environment is set up properly.') + const publicInstallation = await getClientInstallation(PUBLIC_CLIENT); + + console.log(); + console.log( + "Your keycloak server is set up for local usage and development" + ); + console.log(); + console.log("Copy the following app config into the following files:"); + console.log("- client/public/keycloak.json"); + console.log("- server/src/config/keycloak.json"); + console.log(); + console.log(JSON.stringify(publicInstallation, null, 2)); + console.log(); + console.log( + "Done. Please follow the instructions printed above to ensure your environment is set up properly." + ); } catch (e) { - console.error(e) - process.exit(1) + console.error(e); + process.exit(1); } } -async function getClientInstallation(client, installationType = 'keycloak-oidc-keycloak-json') { +async function getClientInstallation( + client, + installationType = "keycloak-oidc-keycloak-json" +) { if (client) { const res = await axios({ - method: 'GET', + method: "GET", url: `${KEYCLOAK_URL}/admin/realms/${APP_REALM}/clients/${client.id}/installation/providers/${installationType}`, - headers: { 'Authorization': token } - }) - return res.data + headers: { Authorization: token }, + }); + return res.data; } - throw new Error('client is undefined') + throw new Error("client is undefined"); } async function assignRealmRolesToUser(user, userIdUrl) { for (let roleToAssign of user.realmRoles) { - console.log(`Assigning realm role ${roleToAssign} to user ${user.name}`) - const selectedRealmRole = realmRoles.find(role => role.name === roleToAssign) + console.log(`Assigning realm role ${roleToAssign} to user ${user.name}`); + const selectedRealmRole = realmRoles.find( + (role) => role.name === roleToAssign + ); if (selectedRealmRole) { - await assignRealmRoleToUser(userIdUrl, selectedRealmRole) + await assignRealmRoleToUser(userIdUrl, selectedRealmRole); } else { - console.error(`realm role ${roleToAssign} does not exist`) + console.error(`realm role ${roleToAssign} does not exist`); } } } async function assignClientRolesToUser(user, client, clientRoles, userIdUrl) { for (let roleToAssign of user.clientRoles) { - console.log(`assigning client role ${roleToAssign} from client ${client.clientId} on user ${user.name}`) - const selectedClientRole = clientRoles.find(clientRole => clientRole.name === roleToAssign) + console.log( + `assigning client role ${roleToAssign} from client ${client.clientId} on user ${user.name}` + ); + const selectedClientRole = clientRoles.find( + (clientRole) => clientRole.name === roleToAssign + ); if (selectedClientRole) { - await assignClientRoleToUser(userIdUrl, client, selectedClientRole) + await assignClientRoleToUser(userIdUrl, client, selectedClientRole); } else { - console.error(`client role ${roleToAssign} does not exist on client ${client.clientId}`) + console.error( + `client role ${roleToAssign} does not exist on client ${client.clientId}` + ); } } } async function authenticateKeycloak() { const res = await axios({ - method: 'POST', + method: "POST", url: `${KEYCLOAK_URL}/realms/${ADMIN_REALM}/protocol/openid-connect/token`, - data: `client_id=${RESOURCE}&username=${ADMIN_USERNAME}&password=${ADMIN_PASSWORD}&grant_type=password` - }) - return `Bearer ${res.data['access_token']}` + data: `client_id=${RESOURCE}&username=${ADMIN_USERNAME}&password=${ADMIN_PASSWORD}&grant_type=password`, + }); + return `Bearer ${res.data["access_token"]}`; } async function importRealm() { return await axios({ - method: 'POST', + method: "POST", url: `${KEYCLOAK_URL}/admin/realms`, data: realmToImport, - headers: { 'Authorization': token, 'Content-Type': 'application/json' } - }) + headers: { Authorization: token, "Content-Type": "application/json" }, + }); } async function getRealmRoles() { const res = await axios({ - method: 'GET', + method: "GET", url: `${KEYCLOAK_URL}/admin/realms/${APP_REALM}/roles`, - headers: { 'Authorization': token } - }) - return res.data + headers: { Authorization: token }, + }); + return res.data; } async function createClientRole(client, roleName) { try { return await axios({ - method: 'POST', + method: "POST", url: `${KEYCLOAK_URL}/admin/realms/${APP_REALM}/clients/${client.id}/roles`, - headers: { 'Authorization': token }, + headers: { Authorization: token }, data: { clientRole: true, - name: roleName - } - }) + name: roleName, + }, + }); } catch (e) { - if (e.response.data.errorMessage === `Role with name ${roleName} already exists`) { - console.log(e.response.data.errorMessage) + if ( + e.response.data.errorMessage === + `Role with name ${roleName} already exists` + ) { + console.log(e.response.data.errorMessage); } else { - throw (e) + throw e; } } } @@ -227,102 +244,105 @@ async function createClientRole(client, roleName) { async function createRealmRole(roleName) { try { return await axios({ - method: 'POST', + method: "POST", url: `${KEYCLOAK_URL}/admin/realms/${APP_REALM}/roles`, - headers: { 'Authorization': token }, + headers: { Authorization: token }, data: { clientRole: false, - name: roleName - } - }) + name: roleName, + }, + }); } catch (e) { - if (e.response.data.errorMessage === `Role with name ${roleName} already exists`) { - console.log(e.response.data.errorMessage) + if ( + e.response.data.errorMessage === + `Role with name ${roleName} already exists` + ) { + console.log(e.response.data.errorMessage); } else { - throw (e) + throw e; } } } async function getClients() { const res = await axios({ - method: 'GET', + method: "GET", url: `${KEYCLOAK_URL}/admin/realms/${APP_REALM}/clients`, - headers: { 'Authorization': token } - }) - return res.data + headers: { Authorization: token }, + }); + return res.data; } async function getClientRoles(client) { const res = await axios({ - method: 'GET', + method: "GET", url: `${KEYCLOAK_URL}/admin/realms/${APP_REALM}/clients/${client.id}/roles`, - headers: { 'Authorization': token } - }) - return res.data + headers: { Authorization: token }, + }); + return res.data; } async function createUser(name, password) { const res = await axios({ - method: 'post', + method: "post", url: `${KEYCLOAK_URL}/admin/realms/${APP_REALM}/users`, data: { - 'username': name, - 'credentials': [{ 'type': 'password', 'value': password, 'temporary': false }], - 'enabled': true + username: name, + credentials: [{ type: "password", value: password, temporary: false }], + enabled: true, }, - headers: { 'Authorization': token, 'Content-Type': 'application/json' } - }) + headers: { Authorization: token, "Content-Type": "application/json" }, + }); if (res) { - return res.headers.location + return res.headers.location; } } async function assignRealmRoleToUser(userIdUrl, role) { const res = await axios({ - method: 'POST', + method: "POST", url: `${userIdUrl}/role-mappings/realm`, data: [role], - headers: { 'Authorization': token, 'Content-Type': 'application/json' } - }) - return res.data + headers: { Authorization: token, "Content-Type": "application/json" }, + }); + return res.data; } async function assignClientRoleToUser(userIdUrl, client, role) { const res = await axios({ - method: 'POST', + method: "POST", url: `${userIdUrl}/role-mappings/clients/${client.id}`, data: [role], - headers: { 'Authorization': token, 'Content-Type': 'application/json' } - }) - return res.data + headers: { Authorization: token, "Content-Type": "application/json" }, + }); + return res.data; } async function resetKeycloakConfiguration() { try { await axios({ - method: 'DELETE', + method: "DELETE", url: `${KEYCLOAK_URL}/admin/realms/${APP_REALM}`, - headers: { 'Authorization': token } - }) + headers: { Authorization: token }, + }); } catch (e) { if (e.response.status !== 404) { - throw e + throw e; } - console.log(`404 while deleting realm ${APP_REALM} - ignoring`) + console.log(`404 while deleting realm ${APP_REALM} - ignoring`); } } function getMobileServicesConfig(installationConfig) { return { - id: 'some-id', - name: 'keycloak', - type: 'keycloak', + id: "some-id", + name: "keycloak", + type: "keycloak", url: KEYCLOAK_URL, - config: installationConfig - } + config: installationConfig, + }; } (async () => { - await prepareKeycloak() -})() + await prepareKeycloak(); +})(); diff --git a/server/integrations/keycloak/realm-export.json b/server/integrations/keycloak/realm-export.json index 92b5bd91..ee1233f5 100644 --- a/server/integrations/keycloak/realm-export.json +++ b/server/integrations/keycloak/realm-export.json @@ -1,6 +1,6 @@ { - "id": "voyager-testing", - "realm": "voyager-testing", + "id": "datasync-starter", + "realm": "datasync-starter", "notBefore": 0, "revokeRefreshToken": false, "refreshTokenMaxReuse": 0, @@ -8,7 +8,13 @@ "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300, "accessCodeLifespanLogin": 1800, @@ -35,445 +41,415 @@ "roles": { "realm": [ { - "id": "5652e1c8-ffa1-4c61-84e4-de09e3549faa", + "id": "8c9988bc-80e3-4e50-8cb4-844708c4d2dd", "name": "offline_access", "description": "${role_offline-access}", - "scopeParamRequired": true, "composite": false, "clientRole": false, - "containerId": "voyager-testing" + "containerId": "datasync-starter", + "attributes": {} }, { - "id": "9d966482-180a-4e4b-8337-ee497c623823", + "id": "1afcc4d8-3e0c-42a8-81d5-f250ada56e40", "name": "uma_authorization", "description": "${role_uma_authorization}", - "scopeParamRequired": false, "composite": false, "clientRole": false, - "containerId": "voyager-testing" + "containerId": "datasync-starter", + "attributes": {} } ], "client": { "realm-management": [ { - "id": "a6c934e6-f6ca-4e22-96e7-5871b62157d0", - "name": "manage-authorization", - "description": "${role_manage-authorization}", - "scopeParamRequired": false, - "composite": false, - "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" - }, - { - "id": "f5d12726-5cc5-4b90-b82e-6b46cab202ec", - "name": "manage-realm", - "description": "${role_manage-realm}", - "scopeParamRequired": false, - "composite": false, - "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" - }, - { - "id": "99da2aac-9eda-4789-afa5-c8806844d32c", - "name": "manage-users", - "description": "${role_manage-users}", - "scopeParamRequired": false, - "composite": false, - "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" - }, - { - "id": "4e6549db-2717-4538-be44-513fc3c35629", - "name": "view-clients", - "description": "${role_view-clients}", - "scopeParamRequired": false, + "id": "7411440b-49e3-47a7-abe2-959df110ae29", + "name": "realm-admin", + "description": "${role_realm-admin}", "composite": true, "composites": { "client": { "realm-management": [ - "query-clients" + "view-clients", + "manage-clients", + "manage-authorization", + "create-client", + "query-realms", + "view-realm", + "manage-events", + "view-authorization", + "impersonation", + "manage-users", + "query-users", + "view-identity-providers", + "manage-identity-providers", + "view-users", + "query-groups", + "manage-realm", + "query-clients", + "view-events" ] } }, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" - }, - { - "id": "658bb06a-e6bf-4fb2-8786-792368fce8a7", - "name": "view-realm", - "description": "${role_view-realm}", - "scopeParamRequired": false, - "composite": false, - "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "d812c651-541c-4d6e-903e-4124dc2018bf", - "name": "realm-admin", - "description": "${role_realm-admin}", - "scopeParamRequired": false, + "id": "54f0e159-6214-4867-b371-8d86543ca891", + "name": "view-clients", + "description": "${role_view-clients}", "composite": true, "composites": { "client": { - "realm-management": [ - "manage-authorization", - "manage-users", - "manage-realm", - "view-clients", - "view-realm", - "view-events", - "query-users", - "manage-clients", - "query-groups", - "manage-events", - "query-realms", - "create-client", - "view-identity-providers", - "query-clients", - "view-users", - "manage-identity-providers", - "view-authorization", - "impersonation" - ] + "realm-management": ["query-clients"] } }, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "7b19f10f-7010-4e75-bd0a-de82960ba890", - "name": "view-events", - "description": "${role_view-events}", - "scopeParamRequired": false, + "id": "2425f79a-0689-494c-8375-224875a7e500", + "name": "manage-clients", + "description": "${role_manage-clients}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "6f371202-4a86-424f-b9bc-2af0ff506f53", - "name": "query-users", - "description": "${role_query-users}", - "scopeParamRequired": false, + "id": "5493be29-05e7-455b-ae16-4e407d236bad", + "name": "manage-authorization", + "description": "${role_manage-authorization}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "d1eb2675-f205-4a76-91ec-b6c7ef33ceda", - "name": "manage-clients", - "description": "${role_manage-clients}", - "scopeParamRequired": false, + "id": "f65c84f7-6137-42b3-8b0f-9d5dcccd4d93", + "name": "create-client", + "description": "${role_create-client}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "e194429f-00f5-48d6-89a5-714d80376075", - "name": "query-groups", - "description": "${role_query-groups}", - "scopeParamRequired": false, + "id": "bb794ef2-4317-4b53-baba-67343767cd32", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} + }, + { + "id": "153fefd6-c62f-4a67-a3a0-efe07482b0f6", + "name": "view-realm", + "description": "${role_view-realm}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "b9dbeaa5-2880-4841-9aff-88775102fe69", + "id": "50f83b4e-3a27-4c38-baae-6fa6d1a81b1b", "name": "manage-events", "description": "${role_manage-events}", - "scopeParamRequired": false, "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "dd63b251-b1de-49aa-8cf8-e1194eac9026", - "name": "query-realms", - "description": "${role_query-realms}", - "scopeParamRequired": false, + "id": "dae3aaed-3540-4fc7-98d3-fd9611953c4c", + "name": "view-authorization", + "description": "${role_view-authorization}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "a79915a4-4494-40f7-964c-e22b18481fa1", - "name": "create-client", - "description": "${role_create-client}", - "scopeParamRequired": false, + "id": "fa0af6df-6529-4fde-97c7-903c8e14afc8", + "name": "manage-users", + "description": "${role_manage-users}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "b44ff12a-973c-407b-856f-3e20cddd72e3", + "id": "d715b08e-cd9e-4ee1-94e3-2b5eca9b445c", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} + }, + { + "id": "f3769379-34e6-4438-95d8-3b305f63a681", "name": "view-identity-providers", "description": "${role_view-identity-providers}", - "scopeParamRequired": false, "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "f84d8600-723a-46c3-a8c1-7cb27ed96dd1", - "name": "query-clients", - "description": "${role_query-clients}", - "scopeParamRequired": false, + "id": "f2594ffb-172a-4099-93d7-4e1630072581", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} + }, + { + "id": "1792ef41-426e-4171-b683-a7b164d03175", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "7fc16db3-cfdb-4a8e-bffc-eb3ba35f1857", + "id": "c94bf2f4-985d-4047-9b2b-898647be9110", "name": "view-users", "description": "${role_view-users}", - "scopeParamRequired": false, "composite": true, "composites": { "client": { - "realm-management": [ - "query-users", - "query-groups" - ] + "realm-management": ["query-users", "query-groups"] } }, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "f6656971-899c-4a58-842e-b51a6f871095", - "name": "manage-identity-providers", - "description": "${role_manage-identity-providers}", - "scopeParamRequired": false, + "id": "e9a20a75-0717-4676-8d23-3c730b28addc", + "name": "view-events", + "description": "${role_view-events}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "14412176-e687-4d8d-a09b-35ce2c2047fc", - "name": "view-authorization", - "description": "${role_view-authorization}", - "scopeParamRequired": false, + "id": "3b929968-3b3c-4a55-a187-c71f567ff79a", + "name": "manage-realm", + "description": "${role_manage-realm}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} }, { - "id": "a7dddf65-1698-4ad4-bcfe-29762ee4a1ae", - "name": "impersonation", - "description": "${role_impersonation}", - "scopeParamRequired": false, + "id": "c1b75f38-33a3-4ca1-945c-7ba9505e66cd", + "name": "query-clients", + "description": "${role_query-clients}", "composite": false, "clientRole": true, - "containerId": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418" - } - ], - "security-admin-console": [], - "voyager-testing-public": [ + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} + }, { - "id": "d3e4b174-d5fe-4206-bf32-7566af96e4e3", - "name": "uma_protection", - "scopeParamRequired": false, + "id": "2252086c-c9ba-40c4-9bd6-33515b448fe4", + "name": "query-groups", + "description": "${role_query-groups}", "composite": false, "clientRole": true, - "containerId": "9e4ad964-80b3-4daf-8917-7801f892ceef" + "containerId": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "attributes": {} } ], - "voyager-testing-bearer": [], + "datasync-starter-client": [], + "security-admin-console": [], "admin-cli": [], + "account-console": [], + "datasync-starter-server": [], "broker": [ { - "id": "207f4839-3939-442a-821c-1b9c68f3dfd2", + "id": "201a39a5-4f70-4e00-bf41-7a386a273a8b", "name": "read-token", "description": "${role_read-token}", - "scopeParamRequired": false, "composite": false, "clientRole": true, - "containerId": "1b82d46b-2fc6-44f2-8473-9b5557b5809a" + "containerId": "d62c524b-e9af-4dd0-9fb7-86f75997c4ef", + "attributes": {} } ], "account": [ { - "id": "cc94bf42-b370-488e-b3c2-3c2664866a39", - "name": "manage-account-links", - "description": "${role_manage-account-links}", - "scopeParamRequired": false, + "id": "023e0825-e1d6-43d8-9923-35dfadcc5258", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "f7d49b78-461a-40a2-ad74-f6cbc5802579", + "attributes": {} + }, + { + "id": "e6c26d93-0d9c-41b3-b1ff-19f2fbbedba2", + "name": "view-consent", + "description": "${role_view-consent}", "composite": false, "clientRole": true, - "containerId": "516e481b-27b5-4e24-9960-8fddc3b02f67" + "containerId": "f7d49b78-461a-40a2-ad74-f6cbc5802579", + "attributes": {} + }, + { + "id": "8b4703bd-ba3e-4140-9954-f37d6a7c4a83", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": ["view-consent"] + } + }, + "clientRole": true, + "containerId": "f7d49b78-461a-40a2-ad74-f6cbc5802579", + "attributes": {} }, { - "id": "315368a3-be6c-4078-b9ae-af35e7dc00b0", + "id": "1124ab3e-cda1-43ed-9015-93fd3cd04a80", "name": "manage-account", "description": "${role_manage-account}", - "scopeParamRequired": false, "composite": true, "composites": { "client": { - "account": [ - "manage-account-links" - ] + "account": ["manage-account-links"] } }, "clientRole": true, - "containerId": "516e481b-27b5-4e24-9960-8fddc3b02f67" + "containerId": "f7d49b78-461a-40a2-ad74-f6cbc5802579", + "attributes": {} }, { - "id": "53bc8742-154d-472b-9cdb-9b8569588232", - "name": "view-profile", - "description": "${role_view-profile}", - "scopeParamRequired": false, + "id": "5d574fcb-777f-4364-8ac5-80e8d38a8808", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "f7d49b78-461a-40a2-ad74-f6cbc5802579", + "attributes": {} + }, + { + "id": "a60e36c7-1863-4af6-a472-a665beaa0587", + "name": "manage-account-links", + "description": "${role_manage-account-links}", "composite": false, "clientRole": true, - "containerId": "516e481b-27b5-4e24-9960-8fddc3b02f67" + "containerId": "f7d49b78-461a-40a2-ad74-f6cbc5802579", + "attributes": {} } ] } }, "groups": [], - "defaultRoles": [ - "offline_access", - "uma_authorization" - ], - "requiredCredentials": [ - "password" - ], + "defaultRoles": ["uma_authorization", "offline_access"], + "requiredCredentials": ["password"], "otpPolicyType": "totp", "otpPolicyAlgorithm": "HmacSHA1", "otpPolicyInitialCounter": 0, "otpPolicyDigits": 6, "otpPolicyLookAheadWindow": 1, "otpPolicyPeriod": 30, - "otpSupportedApplications": [ - "FreeOTP", - "Google Authenticator" + "otpSupportedApplications": ["FreeOTP", "Google Authenticator"], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": ["ES256"], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": ["offline_access"] + } ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": ["manage-account"] + } + ] + }, "clients": [ { - "id": "7ad54bc9-b548-4341-a265-db41050da56d", - "clientId": "admin-cli", - "name": "${client_admin-cli}", + "id": "f7d49b78-461a-40a2-ad74-f6cbc5802579", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/datasync-starter/account/", "surrogateAuthRequired": false, "enabled": true, + "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "**********", - "redirectUris": [], + "defaultRoles": ["view-profile", "manage-account"], + "redirectUris": ["/realms/datasync-starter/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, "consentRequired": false, - "standardFlowEnabled": false, + "standardFlowEnabled": true, "implicitFlowEnabled": false, - "directAccessGrantsEnabled": true, + "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, - "publicClient": true, + "publicClient": false, "frontchannelLogout": false, "protocol": "openid-connect", "attributes": {}, + "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "fa82c025-5d2d-40c8-97ae-842c882b4888", - "name": "username", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${username}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "username", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "preferred_username", - "jsonType.label": "String" - } - }, - { - "id": "fa223020-75a5-4a09-99aa-ddf3c4882003", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", - "consentRequired": false, - "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" - } - }, - { - "id": "c12c208a-aa42-4113-ba75-bd8f7f7a9d7e", - "name": "email", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${email}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "email", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "email", - "jsonType.label": "String" - } - }, - { - "id": "9d39b5ec-e0c4-4a1d-84e6-6b1020787015", - "name": "full name", - "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": true, - "consentText": "${fullName}", - "config": { - "id.token.claim": "true", - "access.token.claim": "true", - "userinfo.token.claim": "true" - } - }, - { - "id": "dbcbc9f4-32d3-496f-a474-01d493c9d7f2", - "name": "family name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${familyName}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "lastName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "family_name", - "jsonType.label": "String" - } - }, - { - "id": "6555c167-9902-4657-a9fa-9b509e98317e", - "name": "given name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${givenName}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "firstName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "given_name", - "jsonType.label": "String" - } - } + "defaultClientScopes": [ + "web-origins", + "role_list", + "profile", + "roles", + "email" ], - "useTemplateConfig": false, - "useTemplateScope": false, - "useTemplateMappers": false + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, { - "id": "1b82d46b-2fc6-44f2-8473-9b5557b5809a", - "clientId": "broker", - "name": "${client_broker}", + "id": "2ac7bec8-2880-40c1-adeb-38c03e2dce08", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/datasync-starter/account/", "surrogateAuthRequired": false, "enabled": true, + "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "**********", - "redirectUris": [], + "redirectUris": ["/realms/datasync-starter/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -482,119 +458,88 @@ "implicitFlowEnabled": false, "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, - "publicClient": false, + "publicClient": true, "frontchannelLogout": false, "protocol": "openid-connect", - "attributes": {}, + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "c4261cbb-0df7-4378-ac7f-4e254ce622c6", - "name": "family name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${familyName}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "lastName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "family_name", - "jsonType.label": "String" - } - }, - { - "id": "8a7a6807-fa77-4026-93d8-287e97e979fe", - "name": "full name", - "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": true, - "consentText": "${fullName}", - "config": { - "id.token.claim": "true", - "access.token.claim": "true", - "userinfo.token.claim": "true" - } - }, - { - "id": "4793d59d-ff1e-4094-8cf1-c8a6f28b46d3", - "name": "username", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${username}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "username", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "preferred_username", - "jsonType.label": "String" - } - }, - { - "id": "09046a29-6203-45a1-8878-3d6dcee2dcaa", - "name": "email", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${email}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "email", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "email", - "jsonType.label": "String" - } - }, - { - "id": "cf3b3dfd-2dc3-4d4d-a146-90090eea54e2", - "name": "given name", + "id": "5a3d6a94-0d93-4159-9212-5d58ebbcbc97", + "name": "audience resolve", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${givenName}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "firstName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "given_name", - "jsonType.label": "String" - } - }, - { - "id": "cf280907-200c-4398-9b14-ed04fb4ae5b3", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", + "protocolMapper": "oidc-audience-resolve-mapper", "consentRequired": false, - "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" - } + "config": {} } ], - "useTemplateConfig": false, - "useTemplateScope": false, - "useTemplateMappers": false + "defaultClientScopes": [ + "web-origins", + "role_list", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, { - "id": "9eee3554-e5e0-432a-ab74-e01474a0c4d7", - "clientId": "security-admin-console", - "name": "${client_security-admin-console}", - "baseUrl": "/auth/admin/voyager-testing/console/index.html", + "id": "0acbe47e-55bf-499a-ba1b-55c50b911195", + "clientId": "admin-cli", + "name": "${client_admin-cli}", "surrogateAuthRequired": false, "enabled": true, + "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "**********", - "redirectUris": [ - "/auth/admin/voyager-testing/console/*" + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "role_list", + "profile", + "roles", + "email" ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "d62c524b-e9af-4dd0-9fb7-86f75997c4ef", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -603,145 +548,44 @@ "implicitFlowEnabled": false, "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, - "publicClient": true, + "publicClient": false, "frontchannelLogout": false, "protocol": "openid-connect", "attributes": {}, + "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "c82b3884-b8e9-4554-8bb2-546bcaad5d97", - "name": "username", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${username}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "username", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "preferred_username", - "jsonType.label": "String" - } - }, - { - "id": "fc5b3f93-4676-4187-b718-1a7f3f1a33e1", - "name": "family name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${familyName}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "lastName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "family_name", - "jsonType.label": "String" - } - }, - { - "id": "d68fb42e-1f34-47f4-9a9b-9d123ef698c5", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", - "consentRequired": false, - "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" - } - }, - { - "id": "b21e076c-30a5-4345-8268-0f68de848b39", - "name": "locale", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "consentText": "${locale}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "locale", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "locale", - "jsonType.label": "String" - } - }, - { - "id": "dea03d62-7ec4-4861-9c60-8a451cf03659", - "name": "given name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${givenName}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "firstName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "given_name", - "jsonType.label": "String" - } - }, - { - "id": "40783398-84aa-451b-b32e-13c0788ddfe1", - "name": "full name", - "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": true, - "consentText": "${fullName}", - "config": { - "id.token.claim": "true", - "access.token.claim": "true", - "userinfo.token.claim": "true" - } - }, - { - "id": "dfdfeee9-f3d9-4c59-8516-b63896bbbb9e", - "name": "email", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${email}", - "config": { - "userinfo.token.claim": "true", - "user.attribute": "email", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "email", - "jsonType.label": "String" - } - } + "defaultClientScopes": [ + "web-origins", + "role_list", + "profile", + "roles", + "email" ], - "useTemplateConfig": false, - "useTemplateScope": false, - "useTemplateMappers": false + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] }, { - "id": "9e4ad964-80b3-4daf-8917-7801f892ceef", - "clientId": "voyager-testing-public", - "rootUrl": "localhost:8100", - "baseUrl": "localhost:8100", + "id": "c86c452f-d408-4a0b-b28c-b4cdf808536a", + "clientId": "datasync-starter-client", "surrogateAuthRequired": false, "enabled": true, + "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "**********", - "redirectUris": [ - "http://localhost*" - ], - "webOrigins": [ - "*" - ], + "redirectUris": ["http://localhost*"], + "webOrigins": ["*"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, "directAccessGrantsEnabled": true, - "serviceAccountsEnabled": true, + "serviceAccountsEnabled": false, "publicClient": true, "frontchannelLogout": false, "protocol": "openid-connect", @@ -750,71 +594,249 @@ "saml.force.post.binding": "false", "saml.multivalued.roles": "false", "saml.encrypt": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", "saml_force_name_id_format": "false", "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "role_list", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "0b29c672-81c0-4a7d-adca-d3f10dc45edc", + "clientId": "datasync-starter-server", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "saml.encrypt": "false", "saml.server.signature": "false", "saml.server.signature.keyinfo.ext": "false", + "exclude.session.state.from.auth.response": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", "saml.onetimeuse.condition": "false" }, + "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "role_list", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "fb14ed64-9715-4230-bdff-e42b4be3d01f", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "role_list", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "fb4a344e-0418-4950-9039-410e15d1deca", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/datasync-starter/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": ["/admin/datasync-starter/console/*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "f49d18ee-41b2-4cfd-96ca-27c8974b95bc", - "name": "Client ID", + "id": "d55161be-2c31-4519-b204-3f704410c884", + "name": "locale", "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", + "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, - "consentText": "", "config": { - "user.session.note": "clientId", "userinfo.token.claim": "true", + "user.attribute": "locale", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "clientId", + "claim.name": "locale", "jsonType.label": "String" } - }, + } + ], + "defaultClientScopes": [ + "web-origins", + "role_list", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "50d09dc1-e75c-4ee2-89aa-86dce5d8bc93", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ { - "id": "628f35fd-8590-46f2-a958-55f7d70a4e4f", - "name": "Client Host", + "id": "5a7172f3-5177-42af-b5c2-4b4d6b50cc35", + "name": "address", "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", + "protocolMapper": "oidc-address-mapper", "consentRequired": false, - "consentText": "", "config": { - "user.session.note": "clientHost", + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", "userinfo.token.claim": "true", + "user.attribute.street": "street", "id.token.claim": "true", + "user.attribute.region": "region", "access.token.claim": "true", - "claim.name": "clientHost", - "jsonType.label": "String" + "user.attribute.locality": "locality" } - }, + } + ] + }, + { + "id": "d513a1e3-b337-4557-8d5a-dca3624e88c6", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ { - "id": "8b7a3ded-f0ff-4239-a3c0-a4251d89592c", - "name": "Client IP Address", + "id": "d66b0992-c67d-4e35-9c6a-eba7c63a81bf", + "name": "email verified", "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", + "protocolMapper": "oidc-usermodel-property-mapper", "consentRequired": false, - "consentText": "", "config": { - "user.session.note": "clientAddress", "userinfo.token.claim": "true", + "user.attribute": "emailVerified", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "clientAddress", - "jsonType.label": "String" + "claim.name": "email_verified", + "jsonType.label": "boolean" } }, { - "id": "9f07acfd-967d-4534-805b-c4dabe52bace", + "id": "d5dd3532-031d-4307-bc9e-d971b0017239", "name": "email", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${email}", + "consentRequired": false, "config": { "userinfo.token.claim": "true", "user.attribute": "email", @@ -823,412 +845,336 @@ "claim.name": "email", "jsonType.label": "String" } - }, + } + ] + }, + { + "id": "7d0dffe6-b437-45d1-af3a-4a30abd346a2", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ { - "id": "fd9702c9-60f2-4a26-8941-bf16ca773750", - "name": "given name", + "id": "41b96823-a8ea-401e-959b-c86db0e6fa59", + "name": "upn", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${givenName}", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "firstName", + "user.attribute": "username", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "given_name", + "claim.name": "upn", "jsonType.label": "String" } }, { - "id": "6fe4582e-4b68-40ac-b349-b1d3b489b9bf", - "name": "full name", + "id": "5a8fd0ac-261b-4ca4-9480-afaea44292a7", + "name": "groups", "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": true, - "consentText": "${fullName}", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, "config": { + "multivalued": "true", + "user.attribute": "foo", "id.token.claim": "true", "access.token.claim": "true", - "userinfo.token.claim": "true" - } - }, - { - "id": "82761edb-06fc-43a1-b192-b2ff1a424be6", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", - "consentRequired": false, - "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" + "claim.name": "groups", + "jsonType.label": "String" } - }, + } + ] + }, + { + "id": "e9696dcd-74b7-4897-9a04-64fd0441620f", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "28bbb581-cc8d-4796-a9cf-5ed915a31d75", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ { - "id": "6862b72b-15f9-4554-bd70-e66a4ea780e2", - "name": "username", + "id": "dc26d0b7-de72-41a9-9ebe-437f3445256c", + "name": "phone number", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${username}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "username", + "user.attribute": "phoneNumber", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "preferred_username", + "claim.name": "phone_number", "jsonType.label": "String" } }, { - "id": "ed95097a-a626-4e29-95ef-c363971246cd", - "name": "family name", + "id": "68166bb2-37cd-4aeb-ac6f-4dbb253b2088", + "name": "phone number verified", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${familyName}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "lastName", + "user.attribute": "phoneNumberVerified", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "family_name", - "jsonType.label": "String" + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" } } - ], - "useTemplateConfig": false, - "useTemplateScope": false, - "useTemplateMappers": false + ] }, { - "id": "516e481b-27b5-4e24-9960-8fddc3b02f67", - "clientId": "account", - "name": "${client_account}", - "baseUrl": "/auth/realms/voyager-testing/account", - "surrogateAuthRequired": false, - "enabled": true, - "clientAuthenticatorType": "client-secret", - "secret": "**********", - "defaultRoles": [ - "manage-account", - "view-profile" - ], - "redirectUris": [ - "/auth/realms/voyager-testing/account/*" - ], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": false, - "frontchannelLogout": false, + "id": "1d1a313a-ea73-4ee2-a586-53257551aeb5", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", "protocol": "openid-connect", - "attributes": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, "protocolMappers": [ { - "id": "a4191cd3-d815-41f6-95bf-a7a345a6f55a", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", - "consentRequired": false, - "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" - } - }, - { - "id": "ceafc8f1-95d0-4b09-b72e-34e0d831fd9c", - "name": "full name", + "id": "f747802f-f843-4019-8644-cc62a5281723", + "name": "middle name", "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": true, - "consentText": "${fullName}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", "id.token.claim": "true", "access.token.claim": "true", - "userinfo.token.claim": "true" + "claim.name": "middle_name", + "jsonType.label": "String" } }, { - "id": "4bfa2704-f9b5-41b5-b25d-82d23c2b203a", - "name": "email", + "id": "12404476-eb54-449e-a70d-ac919d5ba385", + "name": "family name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${email}", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "email", + "user.attribute": "lastName", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "email", + "claim.name": "family_name", "jsonType.label": "String" } }, { - "id": "2868efd7-7014-4133-a775-2cf0da867714", - "name": "given name", + "id": "146564fe-296b-40da-b663-4778568dabd5", + "name": "username", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${givenName}", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "firstName", + "user.attribute": "username", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "given_name", + "claim.name": "preferred_username", "jsonType.label": "String" } }, { - "id": "bc40c256-4f60-4df0-8efc-26e50bcf2289", - "name": "family name", + "id": "506e4944-ba78-40ad-83d5-f95ab8f47c9c", + "name": "birthdate", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${familyName}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "lastName", + "user.attribute": "birthdate", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "family_name", + "claim.name": "birthdate", "jsonType.label": "String" } }, { - "id": "45e4d633-d015-4607-b0a8-f3e0941155e9", - "name": "username", + "id": "8904d6c5-25db-45cd-b822-e5da59c05668", + "name": "gender", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${username}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "username", + "user.attribute": "gender", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "preferred_username", + "claim.name": "gender", "jsonType.label": "String" } - } - ], - "useTemplateConfig": false, - "useTemplateScope": false, - "useTemplateMappers": false - }, - { - "id": "7fa54bed-45f0-49a6-b5f7-d0b02f8f0418", - "clientId": "realm-management", - "name": "${client_realm-management}", - "surrogateAuthRequired": false, - "enabled": true, - "clientAuthenticatorType": "client-secret", - "secret": "**********", - "redirectUris": [], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": true, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": false, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ + }, { - "id": "930f36ef-e518-4244-96e0-c34271a8c532", - "name": "given name", + "id": "ffff1110-545a-45c9-8cfa-9868b5dc20ab", + "name": "website", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${givenName}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "firstName", + "user.attribute": "website", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "given_name", + "claim.name": "website", "jsonType.label": "String" } }, { - "id": "fc028cbd-6aca-41e8-b83b-ec28f5b97795", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", + "id": "2dbd85a3-cfe4-434d-8535-5a7e2b0bea96", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" } }, { - "id": "69293161-0df3-46d7-af41-b762b577dcc8", - "name": "family name", + "id": "45a9dc11-ba82-4e76-bc1f-786a5bc44522", + "name": "picture", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${familyName}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "lastName", + "user.attribute": "picture", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "family_name", + "claim.name": "picture", "jsonType.label": "String" } }, { - "id": "6e91c6b4-a39f-4a95-af43-787ddd4776cc", - "name": "full name", + "id": "f82bf921-b2d2-4962-b74c-d2742ee90147", + "name": "nickname", "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": true, - "consentText": "${fullName}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", "id.token.claim": "true", "access.token.claim": "true", - "userinfo.token.claim": "true" + "claim.name": "nickname", + "jsonType.label": "String" } }, { - "id": "fedfdfb2-b4b5-4ffd-bbac-6c7ecfdb384a", - "name": "username", + "id": "70ed6739-58f7-44d3-9294-fd83ba4deb22", + "name": "zoneinfo", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${username}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "username", + "user.attribute": "zoneinfo", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "preferred_username", + "claim.name": "zoneinfo", "jsonType.label": "String" } }, { - "id": "2c55686d-6ec5-40ed-8ae7-53ebd53a7b6c", - "name": "email", + "id": "7297059a-5d61-4c8a-97f2-9585a5e44031", + "name": "given name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${email}", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "email", + "user.attribute": "firstName", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "email", + "claim.name": "given_name", "jsonType.label": "String" } - } - ], - "useTemplateConfig": false, - "useTemplateScope": false, - "useTemplateMappers": false - }, - { - "id": "cceb78c3-d2cf-4c90-b54f-a5dc9e296989", - "clientId": "voyager-testing-bearer", - "surrogateAuthRequired": false, - "enabled": true, - "clientAuthenticatorType": "client-secret", - "secret": "**********", - "redirectUris": [], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": true, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": true, - "serviceAccountsEnabled": false, - "publicClient": false, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": { - "saml.assertion.signature": "false", - "saml.force.post.binding": "false", - "saml.multivalued.roles": "false", - "saml.encrypt": "false", - "saml_force_name_id_format": "false", - "saml.client.signature": "false", - "saml.authnstatement": "false", - "saml.server.signature": "false", - "saml.server.signature.keyinfo.ext": "false", - "saml.onetimeuse.condition": "false" - }, - "fullScopeAllowed": true, - "nodeReRegistrationTimeout": -1, - "protocolMappers": [ + }, { - "id": "fb56f46c-fa8d-4ca7-99a2-b31a9bfdb429", - "name": "email", + "id": "4cb8a61b-0587-4456-aaf8-9307281a58fb", + "name": "updated at", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${email}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "email", + "user.attribute": "updatedAt", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "email", + "claim.name": "updated_at", "jsonType.label": "String" } }, { - "id": "b2b12dc3-8cda-4bc2-8c83-6e0f76a68a44", - "name": "username", + "id": "5b6e6d77-6190-4a95-9b02-841beb8b783e", + "name": "full name", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${username}", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, "config": { - "userinfo.token.claim": "true", - "user.attribute": "username", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "preferred_username", - "jsonType.label": "String" + "userinfo.token.claim": "true" } }, { - "id": "3db002db-3f35-48a8-a1d0-629be7797085", - "name": "given name", + "id": "6a552d8b-d1e6-48ed-82a1-c87d04de1b9f", + "name": "locale", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${givenName}", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, "config": { "userinfo.token.claim": "true", - "user.attribute": "firstName", + "user.attribute": "locale", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "given_name", + "claim.name": "locale", "jsonType.label": "String" } - }, + } + ] + }, + { + "id": "f601ec1a-4205-4a9b-b4f0-5155d15189a3", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ { - "id": "92321c82-9b8a-4a7f-b4ca-ddcbc0e0ded8", + "id": "71df07a4-a375-466a-b172-eed3e4fcaa3e", "name": "role list", "protocol": "saml", "protocolMapper": "saml-role-list-mapper", @@ -1238,197 +1184,233 @@ "attribute.nameformat": "Basic", "attribute.name": "Role" } - }, + } + ] + }, + { + "id": "68889a53-b8e1-4031-882f-fe6ec612fc96", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ { - "id": "f3dcc2c2-3e7a-48c5-95a9-48a4537f512d", - "name": "family name", + "id": "8f52b05e-8b7d-4336-8eb4-dff362c07fec", + "name": "client roles", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": true, - "consentText": "${familyName}", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, "config": { - "userinfo.token.claim": "true", - "user.attribute": "lastName", - "id.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", "access.token.claim": "true", - "claim.name": "family_name", + "claim.name": "resource_access.${client_id}.roles", "jsonType.label": "String" } }, { - "id": "5aceafdd-0874-48ab-a72d-8e53ba807621", - "name": "full name", + "id": "40e65eeb-a028-4bbd-b9d6-27d9f9ee6ddd", + "name": "realm roles", "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": true, - "consentText": "${fullName}", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, "config": { - "id.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", "access.token.claim": "true", - "userinfo.token.claim": "true" + "claim.name": "realm_access.roles", + "jsonType.label": "String" } + }, + { + "id": "eb4e1dc8-a2c8-4669-989f-10fc44e3b906", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} } - ], - "useTemplateConfig": false, - "useTemplateScope": false, - "useTemplateMappers": false + ] + }, + { + "id": "9cef4593-84eb-49a6-8fe8-9ecb6d167baf", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "da3da307-8b21-4465-81cb-f723572f10e1", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] } ], - "clientTemplates": [], + "defaultDefaultClientScopes": [ + "profile", + "roles", + "web-origins", + "email", + "role_list" + ], + "defaultOptionalClientScopes": [ + "phone", + "address", + "microprofile-jwt", + "offline_access" + ], "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", "xContentTypeOptions": "nosniff", "xRobotsTag": "none", "xFrameOptions": "SAMEORIGIN", - "xXSSProtection": "1; mode=block", "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", "strictTransportSecurity": "max-age=31536000; includeSubDomains" }, "smtpServer": {}, "eventsEnabled": false, - "eventsListeners": [ - "jboss-logging" - ], + "eventsListeners": ["jboss-logging"], "enabledEventTypes": [], "adminEventsEnabled": false, "adminEventsDetailsEnabled": false, "components": { "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ { - "id": "a2c4399b-0db2-498e-bb3c-a7d38548cbde", - "name": "Trusted Hosts", - "providerId": "trusted-hosts", + "id": "e77639f4-676e-4ecd-aab5-f1a326f35e3c", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", "subType": "anonymous", "subComponents": {}, "config": { - "host-sending-registration-request-must-match": [ - "true" - ], - "client-uris-must-match": [ - "true" + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-address-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-usermodel-property-mapper" ] } }, { - "id": "8d75a84e-1d02-4b55-b720-178a9a3bf104", - "name": "Full Scope Disabled", - "providerId": "scope", - "subType": "anonymous", + "id": "0558e2f1-13e9-4826-bce4-fef6d0f4ee98", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", "subComponents": {}, - "config": {} + "config": { + "allow-default-scopes": ["true"] + } }, { - "id": "04cc7bb3-dde9-4885-a386-c5891b9a05b4", - "name": "Consent Required", - "providerId": "consent-required", + "id": "e0d8b09e-9c4b-4d56-94cb-43b15f8296f2", + "name": "Max Clients Limit", + "providerId": "max-clients", "subType": "anonymous", "subComponents": {}, - "config": {} + "config": { + "max-clients": ["200"] + } }, { - "id": "e01c2496-c4cc-47de-a220-54e9dba9f83e", - "name": "Allowed Protocol Mapper Types", - "providerId": "allowed-protocol-mappers", - "subType": "authenticated", + "id": "cd55a66e-8789-4be2-9867-3c39e236ee2e", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", "subComponents": {}, "config": { - "allowed-protocol-mapper-types": [ - "oidc-full-name-mapper", - "oidc-sha256-pairwise-sub-mapper", - "saml-user-attribute-mapper", - "saml-user-property-mapper", - "saml-role-list-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-address-mapper", - "oidc-usermodel-property-mapper" - ], - "consent-required-for-all-mappers": [ - "true" - ] + "allow-default-scopes": ["true"] } }, { - "id": "0fe7ef3b-ab4a-449d-bfd9-1afcf35406d0", - "name": "Allowed Protocol Mapper Types", - "providerId": "allowed-protocol-mappers", + "id": "8d940018-7b93-4ca9-ae53-cc7d7707f7e7", + "name": "Consent Required", + "providerId": "consent-required", "subType": "anonymous", "subComponents": {}, - "config": { - "allowed-protocol-mapper-types": [ - "oidc-full-name-mapper", - "oidc-usermodel-property-mapper", - "saml-role-list-mapper", - "saml-user-property-mapper", - "saml-user-attribute-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-address-mapper" - ], - "consent-required-for-all-mappers": [ - "true" - ] - } + "config": {} }, { - "id": "af27b435-28bb-4f3f-87c4-ab934aab1728", - "name": "Allowed Client Templates", - "providerId": "allowed-client-templates", + "id": "d4ecb9c3-0dad-4e0c-b64a-e7c09b1a2293", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", "subType": "anonymous", "subComponents": {}, - "config": {} + "config": { + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] + } }, { - "id": "72ee915f-27cd-45d8-9987-0cb145e1df93", - "name": "Allowed Client Templates", - "providerId": "allowed-client-templates", - "subType": "authenticated", + "id": "1f1fe15d-fa3d-4287-8754-22639d880148", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", "subComponents": {}, "config": {} }, { - "id": "2c62fc95-880f-452d-8ff6-e9df34e77071", - "name": "Max Clients Limit", - "providerId": "max-clients", - "subType": "anonymous", + "id": "26a12bf6-591a-4cf7-82b0-ccc9133fe2a2", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", "subComponents": {}, "config": { - "max-clients": [ - "200" + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-address-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-full-name-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper" ] } } ], "org.keycloak.keys.KeyProvider": [ { - "id": "83b6c41b-05c1-4319-8b07-d94d2a6cfe9a", - "name": "hmac-generated", - "providerId": "hmac-generated", + "id": "6e9269fb-c192-40f8-97e3-304197d0b7b7", + "name": "rsa-generated", + "providerId": "rsa-generated", "subComponents": {}, "config": { - "priority": [ - "100" - ] + "priority": ["100"] } }, { - "id": "c51216c8-1d1c-44c6-930d-ebbae7d0ad8a", - "name": "rsa-generated", - "providerId": "rsa-generated", + "id": "beb580d5-1702-4767-a6b3-cf95d76ed688", + "name": "aes-generated", + "providerId": "aes-generated", "subComponents": {}, "config": { - "priority": [ - "100" - ] + "priority": ["100"] } }, { - "id": "f331122d-020e-40a3-96e9-94a1c55b36fd", - "name": "aes-generated", - "providerId": "aes-generated", + "id": "123fff5f-8f42-48a7-b033-baca5eac39fa", + "name": "hmac-generated", + "providerId": "hmac-generated", "subComponents": {}, "config": { - "priority": [ - "100" - ] + "priority": ["100"], + "algorithm": ["HS256"] } } ] @@ -1437,7 +1419,134 @@ "supportedLocales": [], "authenticationFlows": [ { - "id": "fd17dd4e-d972-466d-b950-88ef9aac169b", + "id": "b53e38c7-a4ec-471d-833f-0c313b383694", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "07a5f3ee-7165-4a4f-9cea-26e8aaef4601", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "basic-auth-otp", + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "requirement": "DISABLED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "43b4c5cc-0e10-4256-9f09-2b623c272523", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "70e0c60e-d8ad-4930-99b3-5377cc1d103b", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "a4a20612-0f69-4769-801d-81f832886976", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "da86108b-4a22-4c82-9e8b-75605cbaf6a8", "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", @@ -1452,23 +1561,65 @@ "autheticatorFlow": false }, { - "authenticator": "idp-email-verification", - "requirement": "ALTERNATIVE", + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Account verification options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "3ba137ce-56c8-4b76-9c4b-0c77f4a33c14", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "requirement": "REQUIRED", "priority": 20, "userSetupAllowed": false, "autheticatorFlow": false + } + ] + }, + { + "id": "aded0f70-a7db-4b2d-a0a0-f4193a2bc676", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false }, { "requirement": "ALTERNATIVE", - "priority": 30, - "flowAlias": "Verify Existing Account by Re-authentication", + "priority": 20, + "flowAlias": "Handle Existing Account", "userSetupAllowed": false, "autheticatorFlow": true } ] }, { - "id": "a99f8598-bc9d-4aba-85c8-340ae9e9be5a", + "id": "e6847989-67a9-4bbb-a08b-976b6651efa9", "alias": "Verify Existing Account by Re-authentication", "description": "Reauthentication of existing account", "providerId": "basic-flow", @@ -1483,16 +1634,16 @@ "autheticatorFlow": false }, { - "authenticator": "auth-otp-form", - "requirement": "OPTIONAL", + "requirement": "CONDITIONAL", "priority": 20, + "flowAlias": "First broker login - Conditional OTP", "userSetupAllowed": false, - "autheticatorFlow": false + "autheticatorFlow": true } ] }, { - "id": "38c96501-91a0-4115-a7e5-966288fca7d0", + "id": "71e0ab55-10b4-4cff-bb57-ada56adcd827", "alias": "browser", "description": "browser based authentication", "providerId": "basic-flow", @@ -1530,7 +1681,7 @@ ] }, { - "id": "1bd2c823-8f76-468a-9b9b-4fed1d3407b2", + "id": "80eaf2d4-a71d-4b2e-b328-d72112632c18", "alias": "clients", "description": "Base authentication for clients", "providerId": "client-flow", @@ -1550,11 +1701,25 @@ "priority": 20, "userSetupAllowed": false, "autheticatorFlow": false + }, + { + "authenticator": "client-secret-jwt", + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-x509", + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false } ] }, { - "id": "124eb298-7357-444f-bd11-d6bd1d0a2b5e", + "id": "eb8db9e4-5a3f-4a77-afa2-fd77bee4ff60", "alias": "direct grant", "description": "OpenID Connect Resource Owner Grant", "providerId": "basic-flow", @@ -1576,16 +1741,16 @@ "autheticatorFlow": false }, { - "authenticator": "direct-grant-validate-otp", - "requirement": "OPTIONAL", + "requirement": "CONDITIONAL", "priority": 30, + "flowAlias": "Direct Grant - Conditional OTP", "userSetupAllowed": false, - "autheticatorFlow": false + "autheticatorFlow": true } ] }, { - "id": "88b62516-7657-44db-b3e0-ba3d50f4581d", + "id": "71c59eca-a5da-4695-95cc-bfd2a5284c9a", "alias": "docker auth", "description": "Used by Docker clients to authenticate against the IDP", "providerId": "basic-flow", @@ -1602,7 +1767,7 @@ ] }, { - "id": "3ac16833-4757-4927-b029-0b227cdbd5e1", + "id": "b0a0cff6-7312-45f1-9255-3d724a386ca4", "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", @@ -1618,24 +1783,16 @@ "autheticatorFlow": false }, { - "authenticatorConfig": "create unique user config", - "authenticator": "idp-create-user-if-unique", - "requirement": "ALTERNATIVE", + "requirement": "REQUIRED", "priority": 20, - "userSetupAllowed": false, - "autheticatorFlow": false - }, - { - "requirement": "ALTERNATIVE", - "priority": 30, - "flowAlias": "Handle Existing Account", + "flowAlias": "User creation or linking", "userSetupAllowed": false, "autheticatorFlow": true } ] }, { - "id": "35064aaf-e0c2-448f-a243-d0cdc086f265", + "id": "b6259680-1e12-45e2-a4d0-ad8f8067e4e4", "alias": "forms", "description": "Username, password, otp and other auth forms.", "providerId": "basic-flow", @@ -1650,16 +1807,40 @@ "autheticatorFlow": false }, { - "authenticator": "auth-otp-form", - "requirement": "OPTIONAL", + "requirement": "CONDITIONAL", "priority": 20, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "c3b49fb3-6e78-4fe7-98a9-67de9c01cb06", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "requirement": "REQUIRED", + "priority": 10, "userSetupAllowed": false, "autheticatorFlow": false + }, + { + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Authentication Options", + "userSetupAllowed": false, + "autheticatorFlow": true } ] }, { - "id": "65edcd16-5d9a-43cd-8f5f-296b6e431584", + "id": "a7db46c0-1735-4248-9e15-20f672853ad4", "alias": "registration", "description": "registration flow", "providerId": "basic-flow", @@ -1677,7 +1858,7 @@ ] }, { - "id": "5cc87186-18a6-4e72-8e35-a170c586294d", + "id": "103c54a2-8768-4bcf-b2b0-34eaa1519cd6", "alias": "registration form", "description": "registration form", "providerId": "form-flow", @@ -1715,7 +1896,7 @@ ] }, { - "id": "e8216272-c0c6-48f8-943f-ee2c65b3939e", + "id": "fb242d6d-c8ba-4c03-a242-08afda118c8b", "alias": "reset credentials", "description": "Reset credentials for a user if they forgot their password or something", "providerId": "basic-flow", @@ -1744,16 +1925,16 @@ "autheticatorFlow": false }, { - "authenticator": "reset-otp", - "requirement": "OPTIONAL", + "requirement": "CONDITIONAL", "priority": 40, + "flowAlias": "Reset - Conditional OTP", "userSetupAllowed": false, - "autheticatorFlow": false + "autheticatorFlow": true } ] }, { - "id": "51e3dbd5-91b4-495d-9957-a09b2391b364", + "id": "0ce93779-add9-47cb-8408-9a3467a86f32", "alias": "saml ecp", "description": "SAML ECP Profile Authentication Flow", "providerId": "basic-flow", @@ -1772,14 +1953,14 @@ ], "authenticatorConfig": [ { - "id": "96d091f2-c654-4baa-83f2-bf4db14999d8", + "id": "744fdd6a-2377-419b-9b56-6f91b8347aa5", "alias": "create unique user config", "config": { "require.password.update.after.registration": "false" } }, { - "id": "94c8a3a7-51ad-4c3b-b956-8387c70538a4", + "id": "92a980b3-e56f-40ee-a63f-6ed3f2fb23c4", "alias": "review profile config", "config": { "update.profile.on.first.login": "missing" @@ -1793,6 +1974,16 @@ "providerId": "CONFIGURE_TOTP", "enabled": true, "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, "config": {} }, { @@ -1801,6 +1992,7 @@ "providerId": "UPDATE_PASSWORD", "enabled": true, "defaultAction": false, + "priority": 30, "config": {} }, { @@ -1809,6 +2001,7 @@ "providerId": "UPDATE_PROFILE", "enabled": true, "defaultAction": false, + "priority": 40, "config": {} }, { @@ -1817,14 +2010,16 @@ "providerId": "VERIFY_EMAIL", "enabled": true, "defaultAction": false, + "priority": 50, "config": {} }, { - "alias": "terms_and_conditions", - "name": "Terms and Conditions", - "providerId": "terms_and_conditions", - "enabled": false, + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, "defaultAction": false, + "priority": 1000, "config": {} } ], @@ -1834,23 +2029,7 @@ "resetCredentialsFlow": "reset credentials", "clientAuthenticationFlow": "clients", "dockerAuthenticationFlow": "docker auth", - "attributes": { - "_browser_header.xXSSProtection": "1; mode=block", - "_browser_header.xFrameOptions": "SAMEORIGIN", - "_browser_header.strictTransportSecurity": "max-age=31536000; includeSubDomains", - "permanentLockout": "false", - "quickLoginCheckMilliSeconds": "1000", - "_browser_header.xRobotsTag": "none", - "maxFailureWaitSeconds": "900", - "minimumQuickLoginWaitSeconds": "60", - "failureFactor": "30", - "actionTokenGeneratedByUserLifespan": "300", - "maxDeltaTimeSeconds": "43200", - "_browser_header.xContentTypeOptions": "nosniff", - "actionTokenGeneratedByAdminLifespan": "43200", - "bruteForceProtected": "false", - "_browser_header.contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "waitIncrementSeconds": "60" - }, - "keycloakVersion": "3.4.3.Final" -} \ No newline at end of file + "attributes": {}, + "keycloakVersion": "10.0.2", + "userManagedAccessAllowed": false +} diff --git a/server/package.json b/server/package.json index 2a46d733..2d0cecd7 100644 --- a/server/package.json +++ b/server/package.json @@ -34,8 +34,8 @@ "express": "4.17.1", "express-session": "1.17.0", "graphql-tag": "2.10.3", - "keycloak-connect": "9.0.2", - "keycloak-connect-graphql": "0.5.0", + "keycloak-connect": "10.0.2", + "keycloak-connect-graphql": "0.6.0", "@graphback/keycloak-authz": "0.12.0" } } diff --git a/server/src/config/_keycloak.json b/server/src/config/_keycloak.json index a835fbe4..b17424c2 100644 --- a/server/src/config/_keycloak.json +++ b/server/src/config/_keycloak.json @@ -1,9 +1,8 @@ { - "realm": "voyager-testing", - "auth-server-url": "http://localhost:8080/auth", - "ssl-required": "none", - "resource": "voyager-testing-public", - "public-client": true, - "use-resource-role-mappings": true, + "realm": "datasync-starter", + "bearer-only": true, + "auth-server-url": "http://localhost:8080/auth/", + "ssl-required": "external", + "resource": "datasync-starter-server", "confidential-port": 0 } \ No newline at end of file diff --git a/walkthroughs/1-exploring-datasync-codeready/walkthrough.adoc b/walkthroughs/1-exploring-datasync-codeready/walkthrough.adoc index 680c3351..964f549b 100644 --- a/walkthroughs/1-exploring-datasync-codeready/walkthrough.adoc +++ b/walkthroughs/1-exploring-datasync-codeready/walkthrough.adoc @@ -267,7 +267,6 @@ Verify that you followed each step in the procedure above. If you are still hav To explore data sync features, you should run multiple instances of the {data-sync-starter} using different browsers. For example, use the browser on your mobile device as well as using the browser on your laptop. - === Exploring real-time sync . On your laptop: @@ -382,22 +381,14 @@ and add required authorization rules to their model. == Add authorization rule for Task deletion -. Go to your GraphQL Schema `./server/src/schema/schema.qraphql`. -Schema contains mutations section that is responsible for data modifications -. In mutation section find `deleteTask(input: TaskInput): Task!` -. Add GraphQL Directive on top of it `@hasRole(role: "admin")` -This will only allow deletion for admin users. -Roles can be also applied in generation process by utilizing graphback plugin -. This directive is already defined in {data-sync-starter} and can be also applied -to any new mutation or query created by users. -We going to verify this directive in next steps +. Go to your GraphQL Schema `./server/src/config/auth.ts`. +This file contains auth rules for all the operations we support. +. Change role from `delete: { roles: ['admin'] }` to delete: { roles: ['test'] }, +This will only allow deletion for test role that we haven't created. +This operation will prevent us from deleting items from the list. === Configuring Authentication for Keycloak (SSO) -> NOTE: instructions here might be updated after next release of the RHMI - -Please skip this if you have keycloak configured in previous step - . In solution explorer open the User SSO service. . Login using your own credentials (You might need to open this tab in incognito mode). . In menu on the left hover over realm name. @@ -441,4 +432,4 @@ For example `https://routex9wvywuq-codeready-workspaces.apps.openshift.io*` . You should see admin user profile with his roles . Go back to the task screen . Try to delete one of the created tasks -. User will not be permitted to delete task as it does not have admin role. +. User will not be permitted to delete task as it does not have test role. diff --git a/walkthroughs/2-exploring-datasync-local/disabled_walkthrough.adoc b/walkthroughs/2-exploring-datasync-local/disabled_walkthrough.adoc deleted file mode 100644 index baf2de6b..00000000 --- a/walkthroughs/2-exploring-datasync-local/disabled_walkthrough.adoc +++ /dev/null @@ -1,407 +0,0 @@ -// update the component versions for each release -:rhmi-version: 1 - -// URLs -:openshift-console-url: {openshift-host}/console -:sso-realm-url: {user-sso-url}/auth/admin/solution-patterns/console/index.html -:data-sync-documentation-url: https://access.redhat.com/documentation/en-us/red_hat_managed_integration/{rhmi-version}/html-single/developing_a_data_sync_app/index - -//attributes -:title: Creating your Data Sync Application -:integreatly-name: Managed Integration -:data-sync-name: Data Sync -:data-sync-starter: Data Sync Starter -:customer-sso-name: Customer Application SSO -:realm-name: solution-patterns -:realm-display-name: Solution Patterns -:shared-realm-username: admin -:realm-password: admin -:standard-fail-text: Verify that you followed all the steps. If you continue to have issues, contact your administrator. - -//id syntax is used here for the custom IDs because that is how the Solution Explorer sorts these within groups -[id='5-adding-data-sync-graphql'] -= {title} - -// word count that fits best is 15-22, with 20 really being the sweet spot. Character count for that space would be 100-125 -Learn how to build applications that can perform realtime data synchronization with DataSync and GraphQL. - -This solution pattern will show you how to: - -* Build DataSync server based on your business model -* Protect the application's frontend and backend using {customer-sso-name}. -* Explore all the capabilities provided by {data-sync-name}. - -The following diagram shows the architecture of the {data-sync-starter}: - -image::images/arch.png[architecture, role="integr8ly-img-responsive"] - -[type=walkthroughResource, serviceName=openshift] -.Red Hat OpenShift -**** -* link:{openshift-console-url}[Console, window="_blank"] -* link:https://docs.openshift.com/dedicated/4/welcome/index.html[OpenShift Documentation, window="_blank"] -* link:https://blog.openshift.com/[OpenShift Blog, window="_blank"] -**** - -[type=walkthroughResource] -.Data Sync -**** -* link:{data-sync-documentation-url}[Getting Started with {data-sync-name}, window="_blank"] -**** - -:sectnums: - -[time=15] -== Creating your DataSync project from DataSync Starter template - -{data-sync-name} allows you to focus on your business model by giving you ability -to generate fully functional GraphQL based API and Node.js Server and Client Side components. - -DataSync will allow your application to recieve live updates thanks to GraphQL subscriptions and -operate independently of network connection. Developers can create GrapQL types as model -and generate underlying backend that will work out of the box with the RHMI services like SSO or AMQ Online - -[time=30] -=== Setting your DataSync project using DataSync Starter - -. Install yarn `npm install -g yarn` -. Clone git repository: `git clone https://github.com/aerogear/datasync-starter#walkthrough` -. Go to cloned repository: `cd datasync-starter` -. Install dependencies `yarn install` - -=== Creating your DataSync model - -DataSync model offers capability to generate underlying GraphQL API and database infrastructure. -Starter template by default will use MongoDB database as datasource for storing all your data. - -. In your project go to `./model/task.graphql` file. -This file contains GraphQL compatible schema -. Please add `@datasync` annotation to the model right bellow `@model` annotation -Those annotations can be used to control various behaviour of datasync. -`@model` will create standard data access methods, while `@datasync` is going to provide data synchronization capabilities. -. Models can be changed by adding new types or fields. Please add new field by creating new line inside `Task` type -and `address: String` -. Your task model should look as follows ----- -""" -@model -@datasync -""" -type Task { - id: ID! - title: String! - description: String! - status: TaskStatus - address: String -} ----- - -=== Generating your DataSync Node.JS server and React Client - -DataSync provides code generation capabilities that will transform your model to the fully -functional client and server application. -DataSync-starter template contains folowing folders: - -- `./server` folder that contains Node.js server implementation -- `./client` folder that contains React based application -- `.graphqlrc.yml` - -. Review `.graphqlrc.yml` file. This file contains configuration for your data access methods -and available plugins that will be used for source code generation -. Please make sure that all fields in `crud` section are enabled -. Execute graphback cli command to generate source code: -`yarn graphback generate` -. Review `./server/src/schema/schema.qraphql`. -This file contains your model along with access methods that were enabled in configuration. -. Review generated resolver files in `./server/src/resolvers/resolvers.ts` -This file contains methods used to fetch data. Each individual method will use -preconfigured MongoDataProvider. Developers can point resolvers to any datasource. -Currently Postres and MongoDB are supported. -. Review your `./client/src/graphql/` folder containing client side queries for your data. - -=== Running DataSync client and server applications - -. Open new terminal window -. Execute `docker-compose up -d` to start MongoDB container -. Execute `yarn prepare:client` to build client side React application and include it in server runtime -. Execute `yarn start:server`. Terminal should print similar output: - ----- - *********************************************************** - 🎮 Ionic PWA application available at http://localhost:4000/ - 🚀 GraphQL Playground is available at http://localhost:4000/graphql - *********************************************************** ----- - -. Open `PWA application URL` printed in terminal - -[type=verification] -**** -. Check if website was loaded properly -. Select + icon to create new item -. On new screen put `name` and `description` -The client should create a task and it should be -. New task should appear on the list ----- -**** - -[type=verificationFail] -**** -Check the logs of the console -Verify that you followed each step in the procedure above. -If you are still having issues, contact your administrator. -**** - -=== Interacting with embeeded GraphQL Playground - -GraphQL Playground acts as GraphQL API client that allows -you to interact with your types without implementing new views in your application. -In this section we are going to focus on learning who to use playground. - -. Open new terminal window -. Execute `yarn start:server` -. Open GraphQL Playground URL printed in console. -You can use the GraphQL playground to interact with the server API as described in the next step. -. Go to the Playground interface and replace the text in the left pane of the screen with the following query and mutation: - ----- -query listTasks { - allTasks { - title, - description, - address, - id - } -} - -mutation createTask { - createTask(title: "complete the walkthrough", description: "complete the GraphQL walkthrough", address: "NA") { - title, - description, - version, - address, - id - } -} ----- - -[type=verification] -**** -. Click the Run icon in the middle of the playground screen. -. Choose createTask from the menu. -The system should create a task. -. Choose listTasks from the Run menu. -. Check that the following is displayed in the right hand panel: -. You should also see field that we have added in previous steps -+ ----- -{ - "data": { - "allTasks": [ - { - "title": "complete the walkthrough", - "description": "complete the GraphQL walkthrough", - "id": "1", - "address": "NA" - } - ] - } -} ----- -**** - -[type=verificationFail] -**** -Check the logs of the `ionic-showcase-server` pod. - -It should include the string `+connected to messaging service+`. -Verify that you followed each step in the procedure above. If you are still having issues, contact your administrator. -**** - -[time=5] -== Running and verifying your DataSync server - -The {data-sync-starter} provides: - - - offline operation support - - out of the box live updates - - conflict resolution - -In this guide we are going to explore capabilities of the datasync by using -sample application available as part of {data-sync-starter}. -Application by default is designed to work with `Task` model but it can be extended -to use very Type automatically exposed by underlying server GraphQL API. - -. Go back to application opened in previous step. -. Create a task by clicking on the plus icon in the bottom right-hand side of the screen. -. Add a title and description, of your choosing, to the task and click *Create*. -. Copy the current url without the '/tasks' endpoint and paste in a different tab, browser or mobile browser. -. Change the status of the task by clicking/unclicking the text box beside the task. - - -[type=verification] -**** -Verify that the status of the task is synced across all tabs in real-time. -**** - -[type=verificationFail] -**** -Verify that you followed each step in the procedure above. If you are still having issues, contact your administrator. -**** - -[time=10] -== Exploring data sync features using the Data Sync showcase application - -To explore data sync features, you should run multiple instances of the {data-sync-starter} using different browsers. -For example, use the browser on your mobile device as well as using the browser on your laptop. - -To get the url of your app - -=== Exploring real-time sync - -. On your laptop: -.. Create a new task using *+* icon. -.. Enter some task text and click *Create*. - -. On your mobile device: -.. Check that the same task appears in the tasks page -.. Make some changes to the task. - -. On your laptop: -.. Check that the task changes are synchronized. - - -[type=verification] -**** -Did the tasks appear as expected? -**** - -[type=verificationFail] -**** -Verify that you followed each step in the procedure above. If you are still having issues, contact your administrator. -**** - -=== Exploring offline support - -. On your mobile device: -.. Activate airplane mode or disable network connectivity. -.. Create a new task. -The task should be created and the *Offline Changes* button in the footer should contain one change. -.. Make a few more changes by either editing existing tasks, or creating new ones. -.. Review all the changes by clicking the *Offline Changes* button. - -. On your laptop: -You do not see any of the changes from the mobile device. - -. On your mobile device: -.. Restore connectivity or deactivate airplane mode. -.. Watch the status of the tasks change. - -. On your laptop: -.. Check that all the tasks are synchronized. - - -[type=verification] -**** -Did the tasks appear as expected? -**** - -[type=verificationFail] -**** -Verify that you followed each step in the procedure above. If you are still having issues, contact your administrator. -**** - -=== Resolving conflicts - -. On your mobile device: -.. Log into the {data-sync-starter}. -.. Create a task `todo A`. -.. Activate airplane mode or disable network connectivity. -.. Edit the task description to add the text `edited on mobile`. - -. On your laptop: -.. Log into the {data-sync-starter}. -.. Simulate offline mode. For example, in Chrome, press F12 to open *Developer Tools* and select *offline* in the *Network* tab. -.. Edit the `todo A` task, change the text to `todo B`. - -. Bring both of your devices back online, the tasks should sync without a conflict. - -. On your mobile device: -.. Activate airplane mode or disable network connectivity. -.. Edit task `todo B` change the description to: -+ ----- -Conflicting description from mobile ----- - -. On your laptop: -.. Simulate offline mode. For example, in Chrome, press F12 to open *Developer Tools* and select *offline* in the *Network* tab. -.. Edit task `todo B` change the description to: -+ ----- -Conflicting description from laptop ----- - -. Bring both of your devices back online, a popup window should appear warning you about conflicts. - -[type=verification] -**** -Did the tasks sync as expected? -**** - -[type=verificationFail] -**** -Verify that you followed each step in the procedure above. If you are still having issues, contact your administrator. -**** - -. Close terminal window running server application - -[time=15] -== Add authentication and authorization to the Data Sync application using Red Hat SSO - -In this task, we will configure both the frontend and the backend of the -{data-sync-starter} with the {customer-sso-name}. - -DataSync starter has authentication and autorization enabled out of the box. -Developers need to configure server and client application to use their keycloak instance -and add required authorization rules to their model. - -=== Add authorization rule for Task deletion - -. Go to your GraphQL Schema `./server/src/schema/schema.qraphql`. -Schema contains mutations section that is responsible for data modifications -. In mutation section find `deleteTask(input: TaskInput): Task!` -. Add GraphQL Directive on top of it `@hasRole(role: "admin")` -This will only allow deletion for admin users. -Roles can be also applied in generation process by utilizing graphback plugin -. This directive is already defined in {data-sync-starter} and can be also applied -to any new mutation or query created by users. -We going to verify this directive in next steps - -=== Configuring Authentication for Keycloak (SSO) (Local setup) - -DataSync starter provides out of the box support for keycloak -when keycloak.json file is provided. -Use this guide if you do not have kecloak instance running. - -Follow these steps to enable authentication - -. Open new terminal and change directory to server `cd server` -. Run keycloak instance `yarn keycloak` -. Wait for server to start -. Open new terminal and change directory to server `cd server` -. Execute `yarn keycloak:init` -. This command will initialize keycloak with sample roles and users. -. Copy keycloak configuration file that was printed in terminal - -=== Testing Keycloak Authentication and Authorization - -. Start server `yarn start:server` -. Login window should appear. -. Login using `admin` username and `admin` password -. Press User icon in the top right corner. -. You should see admin user profile with his roles -. Go back to the task screen -. Try to delete one of the created tasks -. User will be permitted to delete task as it has admin role. - diff --git a/walkthroughs/2-exploring-datasync-local/disabled_walkthrough.json b/walkthroughs/2-exploring-datasync-local/disabled_walkthrough.json deleted file mode 100644 index c362dfae..00000000 --- a/walkthroughs/2-exploring-datasync-local/disabled_walkthrough.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "repos": [], - "managedServices": [ - ], - "serviceInstances": [] - } -} diff --git a/walkthroughs/2-exploring-datasync-local/images/arch.png b/walkthroughs/2-exploring-datasync-local/images/arch.png deleted file mode 100644 index d6692319260bc18ba5d8f203934925dccf1f9340..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24385 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 zɧGlh0p?Zs?v$YSD0@D(8KLwWr-ayGMbP&cNIXl)@KIQFcut_+u+n4lRDlp`6^Rn2vjzSf2Upzo1a>o0 zMPYu0^Q=Mw1OQ-{l~l~l(wg6lSZnG5e|35FaVJb7U4sm)XRpveZc@_D&7ZkMom#PA zMP}Ivf~8*G|I1z!&lch!8`|4{IY&ESFKPXWOv*jdYuT&DHJS$oYj2LuxtR{qdr=#~ zv=XylTk7ThLn90NGhq4Zl#o5Ro80ed@Hl;PFK<-zP7*1=IP5hQ7VFNUXAM4yjZB}B zSPzJSj&)o`Z}-#>#`n$R+;IuLm0AOaZ`xwDF|inBeX0WfStZFWDvgUaC0a+yG0F`p zdHe?+AiCn^5CV*wZvD(@$4sS8(K5sXm1U;sD(tSw~6pq4%2y)e~8RseZ|Np>w7$AMzZ_W|fCSq9ooBv_pY zQOn;h6|<@%%eaVO*)0Z2I_@kR!0xcwk0pn8${?X2mR5~&Nr=6(I_2Z+?2*YUw#EOm2(jiXkuL{$AXW8pnwF$&XDl$&+V)3Cbn*2jkdB9V$k z&yQvL6WAs|5Zs|v_K==Zpw5~cB zAuW?LXk6vY-U4A)G2pK90GjQ!L!ukHbeQ_7?c1VZR6?&;mJrGta%G2Hx!;!3p)Tmd zxtC;?qTF~pt*41J8z3%tfXAk8ci#G?2;(LBY*7(|t z&rBB&b25`QYz+|nmE{@bLQGeb^@^huXjBlh#|NhiKcSuZu9$w1i)mD-aWSeW>E!oT z=TLurcQSc0%5?f66X9oilWxXF{w?y3~VENyEITznhi|&d=wDB1?Ive0^wVqQaJltLz^6;OM zH#aK`C;C%Gh#3hGB`qJT8@p!Xd=upiMd8*y=XO<1=Te8R=yDA`u3`5X$@r>Y-hM2X zUC6}1_t2#36Og_(p=h_reAAUNv~LX3B!&uI!zA3$MHL&h*Uc}@2ZrYR=kiIv1HMv< z&Dt2xfSWUEmx3{Gi`{-vc0lB<0k$M>W2X;x7Uk|;}&g?23wYr2WgZOsXDd0~M|P1Zrju)G-;$h>w@q zUg4r(GDK*1YRpHTv4%ZOk0EK631K{Lxhz8|CZi1&EI4TZWC0z=48Ep=$xA9mZv!JX z_N`KiqkQ)`<9g>l6K$ze6`vbaFz~91>jJY@zPB6y7i;iOEfIW-vzBz9$79&ktg};XReVhnFDfjc^KZET# z2mgi;==q|q`RTJoXTE_AW}qrbj*-%ceKhY)>5RkT^KI%oc-e=J2yuv6-%Hmb6}v@3_LUKawbIdz@W_ddb9qNH zT@ek*kf!j2L&>5BN#q7v_W@-vFX>~DfBxSq4>YdZf~~0e*RAQg&a{}US#7`|dUCxa zF>4B#e_>i$B>;hWJLrZwtDBnFA&2({1{hY#H#JFcex?MZ^F&~VS_jDYb|gtclutiZ z$Lr)^M35vXm32phh+1h0n=*DJ+QQ7OQrq6t0-;G&52xTV6KLW!d9Xr0Z#hZPs8BVC4jNrF~&A97(Wdl1g^W zhs%Q{$-(#p>9Bhdf>?jYuGq=l5>gv9L0)-;IAn1h1-ZttUBP0=cT*`y8%^?9oi&IK z&7pwETsy#yzgtP0eejrKv(CrJyON?cKg$vGxu~#CxxDj|Vwm(i=pLb-kk(hyvtikR z62@Z#kR-Pc;1WnnM;Xj~x+jT6U^o@PBh@{7gRyX0&2Es((v{AQos&~TR1*clvgZlr)x>0PpV+rGJ428A3Z>**+8_2Rw8NhK{5pw$ z$!Oe3v!Vg&Ll|Z{yO(S#SJTBRH^cJIRG+~2fcd>_e1qM~d^2X6nsgXALeXeFtUrPw zx9xKU3vacbWev-52CS1DFQ~sexH$2gR7w$oIo)2R85X>u^HN!;RfuZ>4tJGhsPI|g zt3pqO9dRyfaz!#C1r{M7Bj#5;1>{MgHn?Jxn_ETjzvks6XiG8@^&sId`+mXx3}3r( zpMKfrx0h$?PMik8s^q-?OLhL&V4iP%uiWmRNJruTU_ig+5y7-4_Jx~fc$u$!Sh!nq z;-MqTg!3KJp3aSlSqYR>C*=9nK|C!8l|XYe8aC|ZIdI|{V>ybdiGD?_c?(zw=v=WJ zUa*R-uG`~&f;(6;Nmd6_Y}mmIX`Z4d29H1HmHgA>Zl#|tSX44Qc!cBTfL}l8iDtcZ z`m3DuqNXD1iwly%4Xko$)hwCWSrm#QQ{C} zcJH+zYNQQ}TVQWt1NfdPT*ub4c{wN}sS|h64UH&ZNc{>sb1)n??PzXy8Z6sDa6`K{ao;}iAj44BECZ-1h zHo|zI;1kESKpMd-yfA-%L*RgSs#86$ys(RG+kAbVu7g-Hg@ zj%92M`g&~>1yT%-f{CKRNbGM^5;DGpN#@aHyg}b_e!S}{P2Qc(8dR^uY3nwtZ6SVY z)k#BI&*0TM&lXm`w3!K_Yb#dOl7f6d-Dw#l2elI>fuqy7Wvn0igx}G=}FDY z|N0*xkt3dip1s@2;9L&*q;^XU3Kq>i@?={WyX0BUnL5rP1RP|^6LIqN_GV|7e5c72 z7Is^vaDeJ-G|7Qy{}-vw$T3{{Jzc$cIUQQ=>Dv&m2Fe#oyA$^OfElO z%4;ci_24ZQIf9WW$-VPZSXV4fCd@~6V(t7tqmFaUu1S1TarwURZ7-=p&k84BcgZG1 zu(30_M0TiAPq!MZcq*(Pb(wT-ML3_a-m_v>G#e#(dqkJc>GQRLk+{z_fz+rcq13lg2wLT%Yp58ew!TZ``%eiL z5k2QZD+1qt%Bp8O9#D-hbvdBwF+IugvC%W1svKumBMLJU_!xFDhW$87oeHCSmy$|njW=2G8B@ny#4jh&|-DipVl)PGIzO+?c{lIuql3T#! zRTaW~#-mjM+c~}!iHRGmPt;7DZ4*n1t#{*BkrAv>#ZTcG@ArQzn}o1D-S3ZLxQ%Uz z6%C76agXw6F>lHzQ`9?;a+{n=1dq~^vX&j(SGiSL6SSpDcS(eR_};n1=F~mXegCmI z6F#%ah!8OLM(hVhg*B?vAYoL0yS-V(CTwf?=~kGdzj4<>A`Z`rPt?S(*>&5vYdU+-`*msAU+Kg%g~kTA8mKyA zBaeL5A(KCTDENHJaE6r@9^3_5GVWy??Cs8Rh%YsI2EG8o=BenWB+`M#>A+0=vWmH) zf>oCde`%%U{$d`C=qp}Gy&OIk-Q?{&zS$E-HZo(7PJjxJKAz6Ipj*T0&_4h8FlI3I?w2y}==Y6yb2-u5KM--cc z1^vpU3Z`mX{Or06bHeiB7tzQzM6BJ{SC=G_??gbhPg%SLGfy$k;Ox>V{OUk1k?rJJ zxHo>@Wi3R{n+LGYqEqO3!Zk~<)3iRq=zC!U!}cV-lm+psUov&Co%pOm5Pl~h;Fwl+a%T%xuULb||VR*ghfiT=aFj z(QuXd54)4tw-2|SkKHrocSBywFzzU;?M!My_0+k4@mj?4e_wP^VgqJi^Qa8^ts;Eo z7cHYO&w+yl30{IZNxLBz&i8T)BSl6L0<55Vr=%U>DfbR)33-Ldv9U_j|p!!En% zNsygjeK00&JIUTjuURGQ;`}#jOjxu=B091CsX=}}{#ooeI8&QYmj%V0otCg#c!Khf zLUn~*AXL5i#4D!{!NaJ2T{E`Ni@{iY&0eaeY)6205wM!2mi9XGb?5OKZpzt}RYd8s zgArXENiI8(s6XFwtqZpn8~k5SR4s`ecEfU5%xH}rPR^a}b4NJ?c6aQ)TyZMQ4u|3T1vV^gF6KJ=XZ;PM5LP{C-q!@t)3Rb*m?N zaV$0|RAljmo!n!V;=O_V)UA`xo-(cG${n#{t?BWTcULr~SI$1jE6^9PjsP-X+g@_N zr|Iy`*+09mDz)tTsF{J1=Y#y~tOf;e4i-i0V||@5so%~}36x#U9jN^nrqm1tyGwVc zcD_kzb3a_{qo>;prG9UTylcny$UIm-_5;T`Vb-VDtw5Z7(C?Mi8Kfn6dh%@CqLJ!y z!vqQ6q+m;{7y`RguwZE`l*%Swzu7}lOUzm)%7JIarts*)cBG_*V;78;?#I6!ABugd za_^hN0hWDyQ$iZEfiHCrP?B^LK)F}nTtx6ICklKb@7o<)wFz8<{!=3e01GqWARiMo z({J5pi9_QbJ#YirZd+mqr9}SM_WKbUT9J_`Br`*jvE+ zaaKxd*Uv!?se1Bma%EGUXV9{GKPgriMH#GpuOnq3pjJ{9dbaN#+|}a`HLv6iJwxzw zD0lFEGbXz4-J&SA_*x`jR?W+~h}cQnfu)$%qd9M-2;4zac7@R|V8!C`A;3fJxN(Ia z|HbL-@wdj&A(;o=BclUDptjL3GK)D-$jxE((-0|S{I1JDrA&;KE!LP?Q3)0FVWC*K zYi8^~fr-)rmION{b;>f!$ypJyXlYs2h!OspI!+g^dVVwAr}=d3?9-aqLtDmgyel4Q ze}9#(nExt|9-ly32DI8-EyljgA=~9oc0!6OThgTSMU(uMSYb({mpL_Z@qq9-2(EoB z+3;bsc-i}~`Q9n1sc*P{u=-Z_p8AgqB^S;W`*cDrW64g51%GIRcMZP z#O{fv zWc00ao!4Z|TTY)#!Z|+}Bv;|nWX>w}{8J73#`m32_GvSMc3x)J8U>}<3quC(VdJ|M z+GJdC{BPzT&K?C=Z?MkHf8bD*f*Q}lb2_T5k~ol|SCI4|agzbrlo9uj#d4MTU~k|! z^%I`lf|B>&(TOd~pn0141HEILFOu(2Xgf6|KJo3oLAqM!KAhp;Kh~J9X0(Wpe z75}CUP8;6gD!=EJb4X{L*WA;M&5AY$uNU91FczSmGPwANAQ{Qnl;S@O^`&PY;M0;d zpjVv%4tJF(`ZSrpnjeMei7kOcBs0mQwNUK{h*vBgye!eZf;sb`uLlwA3C{fvwlh4` zektL!=Q;WAZ=u7PeH(UNlUkZAM(Rb=;aI~ z(%9v^d<&~vXmC1>%8@0mh{P2C5`1ksI0%XKwkVTY!XN~DjGG1cV0NJ%S$fv2Jhr>^ zy5PID)x9P``}@tuykuru)6Jap#KUCfJA0(lqQ%6hPb%K>%4fW=xq8$>K0{;?`MdEN z8mUhgkMNo6gO`%yXUUNsAfT1%9pT3x0(p$=%Jv47(SZ{v>c)b2=v|KC6mN_&T?-(4 zTrluWZ?Afs!4U*7XOIGR;wW@Itmpq`_qPbYO3wy2tw4!+-hG$ z$5+1>7r_fvQTr_L;C+IEQr7Ttz5)~NmsmIiCB?66!Aw79iZU=KDo4-nlbOv#@a<;a zKM53WY_?w9*yG@4K$kIl3FHNfYtiM#=f2%U1iMod@CsD57lA2~8Lp=2wv)|P8cB)q z)KKDnWZw19)7a4Ej|#<^WtGD4W!hPltdia$N0Oy*8qFLQCOTIxfn%wumUAW5oqAkd zqzQkz6r5a~zIS0WPBA~_bMbDeXrkRl+fzeT+kM5~a$p0mJf&jrV5_ygGt7lV%5^Q! z2JD0JziDzGo@YkEkN>`@>j3NV`awzh zDB$>b>1C6dylQ?ISGPD{lrhg}O<%yR(F8-zhwN6|)o|Z7W zYIDX~&76kZPm9(t_FKg}rv)d;=>X!1Vx!F9XnND$1P~Z&bG9lHva-A&iJ8ufk{>yPTHYZmEqL9~yO+rxXmvHjooCcTJ8OhyYEz5Y< zei&UPGdE-NA={`MAA(Ya8;K9(dQ-08a%QSeKECOW=@|c|2|U ztk+#5VJ3e*pSK@KkH|YJwaXjq$2zYutbNcJt_xxDNoePysMU1Z{PpU>XYgW$)Q!!Z ziuvvz{a%%*?j478^j4_KEAKIGk`9w*N$S7H653zK{-G^#7Oa-v< m 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