| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- import dotenv from 'dotenv';
- import axios from 'axios';
- // Load environment variables
- dotenv.config();
- console.log('Environment variables loaded.');
- // Configuration constants
- const KEYCLOAK_URL = process.env.KEYCLOAK_URL || 'https://auth.mrx8086.com';
- const ADMIN_USERNAME = process.env.KEYCLOAK_ADMIN_USER;
- const ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD;
- const REALM_NAME = 'office-automation';
- console.log('Configuration constants set:', { KEYCLOAK_URL, ADMIN_USERNAME, REALM_NAME });
- // Client ID for Paperless
- const PAPERLESS_CLIENT_ID = process.env.PAPERLESS_CLIENT_ID || 'paperless';
- // Helper function for API error handling
- const handleAxiosError = (error, operation, config, response) => {
- console.error(`Error during ${operation}:`);
- if (config) {
- console.error('Request:', {
- method: config.method,
- url: config.url,
- headers: config.headers,
- data: config.data,
- });
- }
- if (error.response) {
- console.error('Response:', {
- status: error.response.status,
- data: error.response.data
- });
- } else {
- console.error('Error Message:', error.message);
- }
- throw error;
- };
- // Get Admin Token
- async function getAdminToken() {
- console.log('Getting admin token...');
- try {
- const response = await axios.post(
- `${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
- new URLSearchParams({
- 'client_id': 'admin-cli',
- 'username': ADMIN_USERNAME,
- 'password': ADMIN_PASSWORD,
- 'grant_type': 'password'
- }),
- {
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- }
- }
- );
- console.log('Admin token received.');
- return response.data.access_token;
- } catch (error) {
- handleAxiosError(error, 'getting admin token');
- }
- }
- // Function to get client information by clientId
- async function getClientByClientId(token, clientId) {
- console.log(`Getting client information for ${clientId}...`);
- try {
- const response = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`, {
- headers: { 'Authorization': `Bearer ${token}` },
- params: { clientId }
- });
- if (response.data.length === 0) {
- console.log(`Client ${clientId} not found`);
- return null;
- }
- console.log(`Client ${clientId} found.`);
- return response.data[0];
- } catch (error) {
- handleAxiosError(error, `getting client ${clientId}`);
- return null;
- }
- }
- // Check if client exists
- const checkClientExists = async (token, clientId) => !!await getClientByClientId(token, clientId);
- // Get client mappers by client ID
- async function getClientMappers(token, clientId) {
- console.log(`Getting client mappers for ${clientId}...`);
- const client = await getClientByClientId(token, clientId);
- if (!client) {
- console.log(`Client ${clientId} not found, no mappers to get.`);
- return [];
- }
- try {
- const response = await axios.get(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
- { headers: { 'Authorization': `Bearer ${token}` } }
- );
- console.log(`Client mappers for ${clientId} retrieved.`);
- return response.data;
- } catch (error) {
- handleAxiosError(error, `getting client mappers for ${clientId}`, error.config, error.response);
- return [];
- }
- }
- // Get client scopes for a client
- async function getClientScopes(token, clientId) {
- console.log(`Getting client scopes for ${clientId}...`);
- const client = await getClientByClientId(token, clientId);
- if (!client) {
- console.log(`Client ${clientId} not found, no client scopes to get.`);
- return [];
- }
- try {
- const response = await axios.get(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/client-scopes`,
- { headers: { 'Authorization': `Bearer ${token}` } }
- );
- console.log(`Client scopes for ${clientId} retrieved.`);
- return response.data;
- } catch (error) {
- handleAxiosError(error, `getting client scopes for ${clientId}`, error.config, error.response);
- return [];
- }
- }
- // Get a specific client scope by name
- async function getClientScope(token, scopeName) {
- console.log(`Getting client scope "${scopeName}"...`);
- try {
- const response = await axios.get(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes`,
- { headers: { 'Authorization': `Bearer ${token}` } }
- );
- const foundScope = response.data.find(scope => scope.name === scopeName);
- if (!foundScope) {
- console.log(`Client Scope "${scopeName}" not found`);
- return null;
- }
- console.log(`Client scope "${scopeName}" found:`, foundScope);
- return foundScope;
- } catch (error) {
- handleAxiosError(error, `getting client scope ${scopeName}`, error.config, error.response);
- return null;
- }
- }
- // Add a default client scope to a client
- async function addDefaultClientScope(token, clientId, scopeName) {
- console.log(`Adding client scope "${scopeName}" as default for client "${clientId}"...`);
- const client = await getClientByClientId(token, clientId);
- const scope = await getClientScope(token, scopeName);
- if (!client || !scope) {
- console.log(`Client "${clientId}" or scope "${scopeName}" not found, cannot add as default scope.`);
- return;
- }
- try {
- await axios.put(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/default-client-scopes/${scope.id}`,
- null,
- {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- }
- );
- console.log(`Client scope "${scopeName}" added as default scope for client "${clientId}"`);
- } catch (error) {
- handleAxiosError(error, `adding client scope "${scopeName}" as default for client "${clientId}"`);
- }
- }
- // Create client and manage mappers
- async function createClient(token, clientId, clientName, redirectUris) {
- console.log(`Creating client "${clientId}"...`);
- let client = await getClientByClientId(token, clientId);
- if (!client) {
- const clientConfig = {
- clientId: clientId,
- name: clientName,
- enabled: true,
- protocol: "openid-connect",
- publicClient: false,
- authorizationServicesEnabled: false,
- serviceAccountsEnabled: false,
- standardFlowEnabled: true,
- implicitFlowEnabled: false,
- directAccessGrantsEnabled: true,
- redirectUris: redirectUris,
- webOrigins: ["+"],
- defaultClientScopes: ["roles"],
- optionalClientScopes: ["address", "phone", "offline_access", "microprofile-jwt"],
- rootUrl: process.env.PAPERLESS_URL,
- baseUrl: process.env.PAPERLESS_URL,
- adminUrl: process.env.PAPERLESS_URL,
- };
- try {
- const response = await axios.post(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
- clientConfig,
- {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- }
- );
- console.log(`Client "${clientId}" created successfully`);
- client = response.data;
- } catch (error) {
- handleAxiosError(error, `creating client: ${clientId}`);
- return;
- }
- } else {
- console.log(`Client "${clientId}" already exists, checking mappers and client secret`);
- }
- if (client) {
- try {
- await axios.put(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`,
- { ...client, secret: process.env[`KEYCLOAK_${clientId.replace(/[^a-zA-Z0-9]/g, '').toUpperCase()}_CLIENT_SECRET`] },
- {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- }
- );
- console.log(`Set client secret for client: ${clientId}`);
- } catch (error) {
- handleAxiosError(error, `setting client secret for client: ${clientId}`, error.config, error.response);
- }
- const existingMappers = await getClientMappers(token, clientId);
- const requiredMappers = [
- {
- name: "groups",
- protocol: "openid-connect",
- protocolMapper: "oidc-group-membership-mapper",
- config: {
- "full.path": "true",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true",
- "claim.name": "groups"
- }
- },
- {
- name: "realm roles",
- protocol: "openid-connect",
- protocolMapper: "oidc-usermodel-realm-role-mapper",
- config: {
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true",
- "claim.name": "roles",
- "jsonType.label": "String",
- "multivalued": "true"
- }
- }
- ];
- for (const mapper of requiredMappers) {
- const existingMapper = existingMappers.find(m => m.name === mapper.name);
- try {
- if (existingMapper) {
- await axios.put(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models/${existingMapper.id}`,
- { ...existingMapper, ...mapper },
- {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- }
- );
- console.log(`Mapper "${mapper.name}" updated for client "${clientId}"`);
- } else {
- await axios.post(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
- mapper,
- {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- }
- );
- console.log(`Mapper "${mapper.name}" created for client "${clientId}"`);
- }
- } catch (error) {
- handleAxiosError(error, `managing mapper "${mapper.name}" for client "${clientId}"`, error.config, error.response);
- }
- }
- await addDefaultClientScope(token, clientId, "openid");
- await addDefaultClientScope(token, clientId, "profile");
- await addDefaultClientScope(token, clientId, "groups-paperless");
- }
- }
- // Create default groups
- async function createDefaultGroups(token) {
- console.log('Creating default groups...');
- const groups = [
- { name: "paperless-admins", path: "/paperless-admins", attributes: { "description": ["Paperless administrators"] } },
- { name: "paperless-users", path: "/paperless-users", attributes: { "description": ["Paperless regular users"] } },
- { name: "paperless-service", path: "/paperless-service", attributes: { "description": ["Paperless service user"] } }
- ];
- for (const group of groups) {
- try {
- const existingGroups = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`, {
- headers: { 'Authorization': `Bearer ${token}` },
- params: { search: group.name, exact: true } // Added exact: true for precise matching
- });
- if (existingGroups.data.length > 0) {
- console.log(`Group "${group.name}" already exists`);
- continue;
- }
- await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`, group, {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- });
- console.log(`Group "${group.name}" created successfully`);
- } catch (error) {
- if (error.response?.status === 409) {
- console.log(`Group "${group.name}" already exists`);
- } else {
- handleAxiosError(error, `creating group: ${group.name}`);
- }
- }
- }
- }
- // Function to add users to groups
- async function addUsersToGroups(token) {
- console.log('Adding users to groups...');
- const users = [
- { username: "testadmin", groups: ["paperless-admins", "paperless-users"] },
- { username: "testuser", groups: ["paperless-users"] },
- { username: "testserviceuser", groups: ["paperless-service"] }
- ];
- for (const user of users) {
- try {
- const existingUsers = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`, {
- headers: { 'Authorization': `Bearer ${token}` },
- params: { username: user.username, exact: true } // Added exact: true for precise matching
- });
- if (existingUsers.data.length === 0) {
- console.log(`User "${user.username}" not found`);
- continue;
- }
- const userId = existingUsers.data[0].id;
- for (const groupName of user.groups) {
- const groupsResponse = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`, {
- headers: { 'Authorization': `Bearer ${token}` },
- params: { search: groupName, exact: true }
- });
- if (groupsResponse.data.length > 0) {
- const groupId = groupsResponse.data[0].id;
- try {
- await axios.put(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users/${userId}/groups/${groupId}`, {}, {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- });
- console.log(`User "${user.username}" added to group "${groupName}"`);
- } catch (error) {
- handleAxiosError(error, `adding user "${user.username}" to group "${groupName}"`, error.config, error.response);
- }
- } else {
- console.log(`Group "${groupName}" not found`);
- }
- }
- } catch (error) {
- handleAxiosError(error, `adding user "${user.username}" to groups`, error.config, error.response);
- }
- }
- }
- async function createGroupsPaperlessScope(token) {
- const scopeName = "groups-paperless";
- const mapperName = "groups-mapper";
- console.log(`Starting createGroupsPaperlessScope`);
- let clientScope = await getClientScope(token, scopeName);
- if (!clientScope) {
- try {
- console.log(`Creating client scope "${scopeName}"`);
- clientScope = await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes`,
- {
- "name": scopeName,
- "protocol": "openid-connect",
- "description": "Provides access to user group information for Paperless.", // Hinzugefügt: Beschreibung
- "attributes": {},
- "consentScreenText": "Grant access to user groups in Paperless",
- "includeInTokenScope": true
- },
- {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- });
- console.log(`Client scope "${scopeName}" created successfully`);
- clientScope = response.data;
- } catch (error) {
- console.error(`Error creating client scope "${scopeName}":`, error);
- handleAxiosError(error, `creating ${scopeName} client scope`, error.config, error.response);
- return;
- }
- } else {
- console.log(`Client scope "${scopeName}" exists, skipping creation`);
- }
- console.log("Client scope creation step finished");
- if (clientScope) {
- console.log(`Check for mapper "${mapperName}" in scope "${scopeName}"`);
- try {
- const mappersResponse = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes/${clientScope.id}/protocol-mappers/models`,
- { headers: { 'Authorization': `Bearer ${token}` } }
- );
- if (!mappersResponse.data.find(m => m.name === mapperName)) {
- try {
- await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes/${clientScope.id}/protocol-mappers/models`,
- {
- "name": mapperName,
- "protocol": "openid-connect",
- "protocolMapper": "oidc-group-membership-mapper",
- "config": {
- "full.path": "false",
- "id.token.claim": "false",
- "access.token.claim": "true",
- "userinfo.token.claim": "true",
- "claim.name": "groups",
- "add.to.introspection": "false"
- }
- },
- {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- });
- console.log(`Mapper "${mapperName}" created for client scope "${scopeName}"`);
- } catch (error) {
- console.error(`Error creating mapper "${mapperName}" for scope "${scopeName}":`, error);
- handleAxiosError(error, `creating mapper for ${scopeName}`, error.config, error.response);
- }
- } else {
- console.log(`Mapper "${mapperName}" exists in client scope "${scopeName}", skipping creation`);
- }
- } catch (error) {
- console.error("Error checking for mappers:", error);
- handleAxiosError(error, `checking mappers for ${scopeName}`, error.config, error.response);
- }
- }
- console.log("Finished createGroupsPaperlessScope");
- }
- // Main function
- async function setupPaperlessRealm() {
- try {
- console.log('Starting Paperless Keycloak setup...');
- const token = await getAdminToken();
- // Create Paperless client
- await createClient(token, PAPERLESS_CLIENT_ID, PAPERLESS_CLIENT_ID, [`https://docs.mrx8086.com/*`]);
- // Create groups
- await createDefaultGroups(token);
- // Create client scope groups-paperless
- await createGroupsPaperlessScope(token);
- // Add users to groups
- await addUsersToGroups(token);
- // Read the configuration of the Office-Automation realm with Admin Token
- if (token) {
- console.log("Master Realm Admin Token:", token);
- try {
- const realmConfig = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- console.log("Office Automation Realm Configuration:", realmConfig.data);
- } catch (error) {
- handleAxiosError(error, 'getting office realm configuration', error.config, error.response);
- }
- } else {
- console.error("Error getting Master Realm admin token");
- }
- console.log('Paperless Keycloak setup completed successfully');
- } catch (error) {
- console.error('Paperless Keycloak setup failed:', error);
- process.exit(1);
- }
- }
- // Execute the script
- setupPaperlessRealm();
|