keycloak-api-manager 3.2.1 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +27 -0
- package/Handlers/clientsHandler.js +240 -30
- package/Handlers/groupsHandler.js +16 -1
- package/Handlers/httpApiHelper.js +87 -0
- package/Handlers/realmsHandler.js +26 -4
- package/Handlers/usersHandler.js +43 -10
- package/README.md +341 -10
- package/index.js +159 -29
- package/package.json +3 -14
- package/test/.mocharc.json +4 -0
- package/test/TESTING.md +327 -0
- package/test/config/CONFIGURATION.md +170 -0
- package/test/config/default.json +36 -0
- package/test/config/local.json.example +7 -0
- package/test/config/secrets.json.example +7 -0
- package/test/diagnostic-protocol-mappers.js +189 -0
- package/test/docker-keycloak/DEPLOYMENT_GUIDE.md +262 -0
- package/test/docker-keycloak/certs/.gitkeep +7 -0
- package/test/docker-keycloak/docker-compose-https.yml +50 -0
- package/test/docker-keycloak/docker-compose.yml +59 -0
- package/test/docker-keycloak/setup-keycloak.js +501 -0
- package/test/enableServerFeatures.js +315 -0
- package/test/helpers/config.js +218 -0
- package/test/helpers/docker-helpers.js +513 -0
- package/test/helpers/setup.js +186 -0
- package/test/package.json +18 -0
- package/test/setup.js +194 -0
- package/test/specs/authenticationManagement.test.js +224 -0
- package/test/specs/clientCredentials.test.js +76 -0
- package/test/specs/clientScopes.test.js +388 -0
- package/test/specs/clients.test.js +791 -0
- package/test/specs/components.test.js +151 -0
- package/test/specs/debugClientLibrary.test.js +88 -0
- package/test/specs/groups.test.js +362 -0
- package/test/specs/identityProviders.test.js +292 -0
- package/test/specs/realms.test.js +390 -0
- package/test/specs/roles.test.js +322 -0
- package/test/specs/users.test.js +445 -0
- package/test/testConfig.js +69 -0
- package/.mocharc.json +0 -7
- package/docker-compose.yml +0 -27
- package/test/authenticationManagement.test.js +0 -329
- package/test/clientScopes.test.js +0 -256
- package/test/clients.test.js +0 -284
- package/test/components.test.js +0 -122
- package/test/config.js +0 -137
- package/test/docker-helpers.js +0 -111
- package/test/groups.test.js +0 -284
- package/test/identityProviders.test.js +0 -197
- package/test/mocha.env.js +0 -55
- package/test/realms.test.js +0 -349
- package/test/roles.test.js +0 -215
- package/test/users.test.js +0 -405
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "keycloak-api-manager-tests",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "NODE_ENV=test NODE_PATH=./node_modules mocha --exit"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@keycloak/keycloak-admin-client": "^26.5.3",
|
|
10
|
+
"chai": "^4.3.10",
|
|
11
|
+
"express": "^5.1.0",
|
|
12
|
+
"keycloak-api-manager": "file:..",
|
|
13
|
+
"keycloak-connect": "^26.1.1",
|
|
14
|
+
"mocha": "^10.2.0",
|
|
15
|
+
"propertiesmanager": "^4.1.0",
|
|
16
|
+
"request": "^2.88.2"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/test/setup.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Test Setup - Mocha Hooks
|
|
3
|
+
*
|
|
4
|
+
* This file is loaded by Mocha before any tests run (configured in .mocharc.json).
|
|
5
|
+
*
|
|
6
|
+
* Purpose:
|
|
7
|
+
* - Creates shared test realm infrastructure once before all tests
|
|
8
|
+
* - Avoids repeated realm creation/deletion, improving test performance by ~5x
|
|
9
|
+
* - Ensures consistent test environment across all test suites
|
|
10
|
+
* - Automatically creates SSH tunnel if direct connection fails
|
|
11
|
+
*
|
|
12
|
+
* What gets created:
|
|
13
|
+
* - Test realm (keycloak-api-manager-test-realm)
|
|
14
|
+
* - Test client with proper configuration
|
|
15
|
+
* - Test user with credentials
|
|
16
|
+
* - Test roles (test-role-1, test-role-2, test-admin-role)
|
|
17
|
+
* - Test group
|
|
18
|
+
* - Test client scope
|
|
19
|
+
*
|
|
20
|
+
* SSH Tunnel Retry Logic:
|
|
21
|
+
* - First attempts direct connection to 127.0.0.1:9998
|
|
22
|
+
* - If connection refused, automatically creates SSH tunnel to smart-dell-sml.crs4.it
|
|
23
|
+
* - If SSH tunnel also fails, provides troubleshooting steps
|
|
24
|
+
*
|
|
25
|
+
* Note: Individual tests should create unique resources (e.g., user-${timestamp})
|
|
26
|
+
* to avoid conflicts when multiple tests run in parallel or sequence.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const enableServerFeatures = require('./enableServerFeatures');
|
|
30
|
+
const { exec } = require('child_process');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const os = require('os');
|
|
34
|
+
const keycloakManager = require('../index');
|
|
35
|
+
|
|
36
|
+
let sshTunnelProcess = null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create SSH tunnel for Keycloak access
|
|
40
|
+
*/
|
|
41
|
+
function createSSHTunnelSimple() {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const sshUser = 'smart';
|
|
44
|
+
const sshHost = 'smart-dell-sml.crs4.it';
|
|
45
|
+
const localPort = 9998;
|
|
46
|
+
const remotePort = 8080;
|
|
47
|
+
const homeDir = os.homedir();
|
|
48
|
+
const keyPath = `${homeDir}/.ssh/id_ed25519`;
|
|
49
|
+
|
|
50
|
+
// Create tunnel in background
|
|
51
|
+
const cmd = `ssh -fN -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -L 127.0.0.1:${localPort}:127.0.0.1:${remotePort} -i ${keyPath} ${sshUser}@${sshHost}`;
|
|
52
|
+
|
|
53
|
+
exec(cmd, (err, stdout, stderr) => {
|
|
54
|
+
if (err) {
|
|
55
|
+
reject(new Error(`SSH tunnel failed: ${err.message}`));
|
|
56
|
+
} else {
|
|
57
|
+
// Give tunnel time to establish
|
|
58
|
+
setTimeout(() => resolve(`127.0.0.1:${localPort}`), 500);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Global before hook - runs once before all test suites
|
|
65
|
+
before(async function() {
|
|
66
|
+
// Increase timeout - realm creation may take 10-20 seconds
|
|
67
|
+
this.timeout(120000);
|
|
68
|
+
|
|
69
|
+
console.log('\n=== Running global test setup ===\n');
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await enableServerFeatures();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// Extract the root cause message (might be nested in error chain)
|
|
75
|
+
let rootMessage = err.message || '';
|
|
76
|
+
let currentErr = err;
|
|
77
|
+
while (currentErr?.cause) {
|
|
78
|
+
currentErr = currentErr.cause;
|
|
79
|
+
rootMessage = currentErr.message || rootMessage;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check if connection was refused on 127.0.0.1:9998
|
|
83
|
+
if (rootMessage.includes('ECONNREFUSED') && rootMessage.includes('127.0.0.1:9998')) {
|
|
84
|
+
console.log('\n⚠ Direct connection failed (ECONNREFUSED on 127.0.0.1:9998)');
|
|
85
|
+
console.log(' Attempting to create SSH tunnel to smart-dell-sml.crs4.it...\n');
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// Create SSH tunnel
|
|
89
|
+
const sshTunnelUrl = await createSSHTunnelSimple();
|
|
90
|
+
|
|
91
|
+
if (sshTunnelUrl) {
|
|
92
|
+
console.log(`✓ SSH tunnel created: http://${sshTunnelUrl}\n`);
|
|
93
|
+
|
|
94
|
+
// Update config to use tunnel
|
|
95
|
+
const configPath = path.join(__dirname, 'config/local.json');
|
|
96
|
+
let config = {};
|
|
97
|
+
|
|
98
|
+
if (fs.existsSync(configPath)) {
|
|
99
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!config.test) config.test = {};
|
|
103
|
+
if (!config.test.keycloak) config.test.keycloak = {};
|
|
104
|
+
config.test.keycloak.baseUrl = `http://${sshTunnelUrl}`;
|
|
105
|
+
|
|
106
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
107
|
+
console.log(`✓ Updated test/config/local.json to use SSH tunnel\n`);
|
|
108
|
+
|
|
109
|
+
// Clear propertiesmanager cache and retry
|
|
110
|
+
delete require.cache[require.resolve('propertiesmanager')];
|
|
111
|
+
|
|
112
|
+
// Retry setup
|
|
113
|
+
await enableServerFeatures();
|
|
114
|
+
sshTunnelProcess = sshTunnelUrl; // Mark that we created a tunnel
|
|
115
|
+
} else {
|
|
116
|
+
throw new Error('SSH tunnel creation returned null');
|
|
117
|
+
}
|
|
118
|
+
} catch (tunnelErr) {
|
|
119
|
+
console.error('\n✗ SSH tunnel connection failed:');
|
|
120
|
+
console.error(` Error: ${tunnelErr.message}`);
|
|
121
|
+
console.error('\nTroubleshooting steps:');
|
|
122
|
+
console.error(' 1. Verify SSH key exists: ls -la ~/.ssh/id_ed25519');
|
|
123
|
+
console.error(' 2. Verify SSH access: ssh -i ~/.ssh/id_ed25519 smart@smart-dell-sml.crs4.it echo OK');
|
|
124
|
+
console.error(' 3. Verify remote Keycloak is running on the server');
|
|
125
|
+
console.error(' 4. Try manual tunnel: ssh -L 127.0.0.1:9998:127.0.0.1:8080 smart@smart-dell-sml.crs4.it');
|
|
126
|
+
console.error('\nTest execution cancelled.\n');
|
|
127
|
+
throw new Error(`Failed to connect to Keycloak. Direct connection (127.0.0.1:9998) failed and SSH tunnel creation also failed: ${tunnelErr.message}`);
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
// Not a connection refused error - propagate as-is
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log('\n=== Global setup complete ===\n');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Global after hook - cleanup test realm and SSH tunnel
|
|
139
|
+
after(async function() {
|
|
140
|
+
this.timeout(60000);
|
|
141
|
+
|
|
142
|
+
console.log('\n=== Running global test teardown ===\n');
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// Load test config to get realm name
|
|
146
|
+
const { TEST_REALM } = require('./testConfig');
|
|
147
|
+
|
|
148
|
+
// Delete test realm to restore server to initial state
|
|
149
|
+
try {
|
|
150
|
+
console.log(`Deleting test realm: ${TEST_REALM}`);
|
|
151
|
+
await keycloakManager.realms.del({ realm: TEST_REALM });
|
|
152
|
+
console.log(`✓ Test realm deleted: ${TEST_REALM}`);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
if (err.message && err.message.includes('404')) {
|
|
155
|
+
console.log(`⚠ Test realm already deleted or not found: ${TEST_REALM}`);
|
|
156
|
+
} else {
|
|
157
|
+
console.error(`⚠ Error deleting test realm: ${err.message}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Stop Keycloak admin client to close connections
|
|
162
|
+
try {
|
|
163
|
+
keycloakManager.stop();
|
|
164
|
+
console.log('✓ Closed Keycloak admin client connection');
|
|
165
|
+
} catch (err) {
|
|
166
|
+
// Connection already closed or other state
|
|
167
|
+
console.log('✓ Keycloak admin client stopped');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Clean up SSH tunnel config file if it was created
|
|
171
|
+
try {
|
|
172
|
+
const configPath = path.join(__dirname, 'config/local.json');
|
|
173
|
+
if (fs.existsSync(configPath)) {
|
|
174
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
175
|
+
// Only delete local.json if it contains SSH tunnel config
|
|
176
|
+
if (config.test?.keycloak?.baseUrl?.includes('127.0.0.1:9998')) {
|
|
177
|
+
fs.unlinkSync(configPath);
|
|
178
|
+
console.log('✓ Cleaned up SSH tunnel config (test/config/local.json)');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.log('⚠ Could not cleanup config file:', err.message);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error('⚠ Error during test teardown:', err.message);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (sshTunnelProcess) {
|
|
190
|
+
console.log('✓ SSH tunnel will close automatically');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log('\n=== Global test teardown complete ===\n');
|
|
194
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { expect } = require('chai');
|
|
3
|
+
|
|
4
|
+
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
|
|
5
|
+
process.env.PROPERTIES_PATH = path.join(__dirname, '..', 'config');
|
|
6
|
+
|
|
7
|
+
const { conf } = require('propertiesmanager');
|
|
8
|
+
const keycloakManager = require('keycloak-api-manager');
|
|
9
|
+
|
|
10
|
+
function shouldSkipFeature(err) {
|
|
11
|
+
if (!err || !err.message) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const text = err.message.toLowerCase();
|
|
15
|
+
return (
|
|
16
|
+
text.includes('not supported') ||
|
|
17
|
+
text.includes('unknown_error') ||
|
|
18
|
+
text.includes('http 404') ||
|
|
19
|
+
text.includes('http 400')
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function getFlowByAliasOrId(aliasOrId, candidateId) {
|
|
24
|
+
try {
|
|
25
|
+
return await keycloakManager.authenticationManagement.getFlow({ flowId: aliasOrId });
|
|
26
|
+
} catch (firstErr) {
|
|
27
|
+
if (candidateId) {
|
|
28
|
+
return await keycloakManager.authenticationManagement.getFlow({ flowId: candidateId });
|
|
29
|
+
}
|
|
30
|
+
throw firstErr;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function deleteFlowByAliasOrId(aliasOrId, candidateId) {
|
|
35
|
+
try {
|
|
36
|
+
await keycloakManager.authenticationManagement.deleteFlow({ flowId: aliasOrId });
|
|
37
|
+
} catch (firstErr) {
|
|
38
|
+
if (candidateId) {
|
|
39
|
+
await keycloakManager.authenticationManagement.deleteFlow({ flowId: candidateId });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
throw firstErr;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('AuthenticationManagement Handler', function () {
|
|
47
|
+
this.timeout(60000);
|
|
48
|
+
|
|
49
|
+
const keycloakConfig = (conf && conf.keycloak) || {};
|
|
50
|
+
const testRealm = `auth-mgmt-realm-${Date.now()}`;
|
|
51
|
+
const customFlowAlias = `custom-flow-${Date.now()}`;
|
|
52
|
+
const copiedFlowAlias = `copied-flow-${Date.now()}`;
|
|
53
|
+
|
|
54
|
+
before(async function () {
|
|
55
|
+
await keycloakManager.configure({
|
|
56
|
+
baseUrl: keycloakConfig.baseUrl,
|
|
57
|
+
realmName: keycloakConfig.realmName,
|
|
58
|
+
clientId: keycloakConfig.clientId,
|
|
59
|
+
clientSecret: keycloakConfig.clientSecret,
|
|
60
|
+
username: keycloakConfig.username,
|
|
61
|
+
password: keycloakConfig.password,
|
|
62
|
+
grantType: keycloakConfig.grantType,
|
|
63
|
+
tokenLifeSpan: keycloakConfig.tokenLifeSpan,
|
|
64
|
+
scope: keycloakConfig.scope,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await keycloakManager.realms.create({ realm: testRealm, enabled: true });
|
|
68
|
+
keycloakManager.setConfig({ realmName: testRealm });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
after(async function () {
|
|
72
|
+
try {
|
|
73
|
+
keycloakManager.setConfig({ realmName: testRealm });
|
|
74
|
+
} catch (err) {
|
|
75
|
+
// best effort
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await keycloakManager.authenticationManagement.deleteFlow({ flowId: copiedFlowAlias });
|
|
80
|
+
} catch (err) {
|
|
81
|
+
// best effort
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await keycloakManager.authenticationManagement.deleteFlow({ flowId: customFlowAlias });
|
|
86
|
+
} catch (err) {
|
|
87
|
+
// best effort
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await keycloakManager.realms.del({ realm: testRealm });
|
|
92
|
+
} catch (err) {
|
|
93
|
+
// best effort
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof keycloakManager.stop === 'function') {
|
|
97
|
+
keycloakManager.stop();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should list required actions and provider metadata', async function () {
|
|
102
|
+
const requiredActions = await keycloakManager.authenticationManagement.getRequiredActions();
|
|
103
|
+
expect(requiredActions).to.be.an('array');
|
|
104
|
+
|
|
105
|
+
const unregistered = await keycloakManager.authenticationManagement.getUnregisteredRequiredActions();
|
|
106
|
+
expect(unregistered).to.be.an('array');
|
|
107
|
+
|
|
108
|
+
const updatePassword = await keycloakManager.authenticationManagement.getRequiredActionForAlias({
|
|
109
|
+
alias: 'UPDATE_PASSWORD',
|
|
110
|
+
});
|
|
111
|
+
expect(updatePassword).to.be.an('object');
|
|
112
|
+
|
|
113
|
+
const actionProviders = await keycloakManager.authenticationManagement.getFormActionProviders();
|
|
114
|
+
expect(actionProviders).to.be.an('array');
|
|
115
|
+
|
|
116
|
+
const authenticatorProviders = await keycloakManager.authenticationManagement.getAuthenticatorProviders();
|
|
117
|
+
expect(authenticatorProviders).to.be.an('array');
|
|
118
|
+
|
|
119
|
+
const clientAuthenticatorProviders = await keycloakManager.authenticationManagement.getClientAuthenticatorProviders();
|
|
120
|
+
expect(clientAuthenticatorProviders).to.be.an('array');
|
|
121
|
+
|
|
122
|
+
const formProviders = await keycloakManager.authenticationManagement.getFormProviders();
|
|
123
|
+
expect(formProviders).to.be.an('array');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should read and modify required action settings when available', async function () {
|
|
127
|
+
try {
|
|
128
|
+
const before = await keycloakManager.authenticationManagement.getRequiredActionForAlias({
|
|
129
|
+
alias: 'UPDATE_PASSWORD',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const description = await keycloakManager.authenticationManagement.getRequiredActionConfigDescription({
|
|
133
|
+
alias: 'UPDATE_PASSWORD',
|
|
134
|
+
});
|
|
135
|
+
expect(description).to.be.an('object');
|
|
136
|
+
|
|
137
|
+
await keycloakManager.authenticationManagement.updateRequiredAction(
|
|
138
|
+
{ alias: 'UPDATE_PASSWORD' },
|
|
139
|
+
{
|
|
140
|
+
...before,
|
|
141
|
+
enabled: true,
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
await keycloakManager.authenticationManagement.raiseRequiredActionPriority({
|
|
146
|
+
alias: 'UPDATE_PASSWORD',
|
|
147
|
+
});
|
|
148
|
+
await keycloakManager.authenticationManagement.lowerRequiredActionPriority({
|
|
149
|
+
alias: 'UPDATE_PASSWORD',
|
|
150
|
+
});
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (shouldSkipFeature(err)) {
|
|
153
|
+
this.skip();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should manage authentication flows and executions', async function () {
|
|
161
|
+
const flows = await keycloakManager.authenticationManagement.getFlows();
|
|
162
|
+
expect(flows).to.be.an('array');
|
|
163
|
+
expect(flows.length).to.be.greaterThan(0);
|
|
164
|
+
|
|
165
|
+
const browserFlow = flows.find((flow) => flow.alias === 'browser') || flows[0];
|
|
166
|
+
const flowDetails = await getFlowByAliasOrId(browserFlow.alias, browserFlow.id);
|
|
167
|
+
expect(flowDetails).to.be.an('object');
|
|
168
|
+
|
|
169
|
+
await keycloakManager.authenticationManagement.createFlow({
|
|
170
|
+
alias: customFlowAlias,
|
|
171
|
+
providerId: 'basic-flow',
|
|
172
|
+
topLevel: true,
|
|
173
|
+
builtIn: false,
|
|
174
|
+
description: 'Test custom flow',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const flowsAfterCreate = await keycloakManager.authenticationManagement.getFlows();
|
|
178
|
+
const customFlowMeta = flowsAfterCreate.find((flow) => flow.alias === customFlowAlias);
|
|
179
|
+
const customFlow = await getFlowByAliasOrId(
|
|
180
|
+
customFlowAlias,
|
|
181
|
+
customFlowMeta && customFlowMeta.id
|
|
182
|
+
);
|
|
183
|
+
expect(customFlow).to.be.an('object');
|
|
184
|
+
expect(customFlow.alias).to.equal(customFlowAlias);
|
|
185
|
+
|
|
186
|
+
await keycloakManager.authenticationManagement.copyFlow({
|
|
187
|
+
flow: customFlowAlias,
|
|
188
|
+
newName: copiedFlowAlias,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const flowsAfterCopy = await keycloakManager.authenticationManagement.getFlows();
|
|
192
|
+
const copiedFlowMeta = flowsAfterCopy.find((flow) => flow.alias === copiedFlowAlias);
|
|
193
|
+
const copiedFlow = await getFlowByAliasOrId(
|
|
194
|
+
copiedFlowAlias,
|
|
195
|
+
copiedFlowMeta && copiedFlowMeta.id
|
|
196
|
+
);
|
|
197
|
+
expect(copiedFlow).to.be.an('object');
|
|
198
|
+
expect(copiedFlow.alias).to.equal(copiedFlowAlias);
|
|
199
|
+
|
|
200
|
+
const executions = await keycloakManager.authenticationManagement.getExecutions({
|
|
201
|
+
flow: copiedFlowAlias,
|
|
202
|
+
});
|
|
203
|
+
expect(executions).to.be.an('array');
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
await keycloakManager.authenticationManagement.addExecutionToFlow({
|
|
207
|
+
flow: copiedFlowAlias,
|
|
208
|
+
provider: 'auth-cookie',
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const executionsAfter = await keycloakManager.authenticationManagement.getExecutions({
|
|
212
|
+
flow: copiedFlowAlias,
|
|
213
|
+
});
|
|
214
|
+
expect(executionsAfter).to.be.an('array');
|
|
215
|
+
} catch (err) {
|
|
216
|
+
if (!shouldSkipFeature(err)) {
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
await deleteFlowByAliasOrId(copiedFlowAlias, copiedFlowMeta && copiedFlowMeta.id);
|
|
222
|
+
await deleteFlowByAliasOrId(customFlowAlias, customFlowMeta && customFlowMeta.id);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { expect } = require('chai');
|
|
3
|
+
|
|
4
|
+
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
|
|
5
|
+
process.env.PROPERTIES_PATH = path.join(__dirname, '..', 'config');
|
|
6
|
+
|
|
7
|
+
const keycloakManager = require('keycloak-api-manager');
|
|
8
|
+
const { KEYCLOAK_CONFIG, TEST_CLIENT_ID, TEST_REALM } = require('../testConfig');
|
|
9
|
+
|
|
10
|
+
function buildConfig(overrides = {}) {
|
|
11
|
+
return {
|
|
12
|
+
...KEYCLOAK_CONFIG,
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('Authentication - client_credentials grant', function () {
|
|
18
|
+
this.timeout(20000);
|
|
19
|
+
|
|
20
|
+
let testClientId = null;
|
|
21
|
+
let testClientSecret = null;
|
|
22
|
+
|
|
23
|
+
before(async function () {
|
|
24
|
+
const clients = await keycloakManager.clients.find({ clientId: TEST_CLIENT_ID });
|
|
25
|
+
const testClient = clients.find((client) => client.clientId === TEST_CLIENT_ID);
|
|
26
|
+
|
|
27
|
+
if (!testClient) {
|
|
28
|
+
this.skip();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
testClientId = testClient.clientId;
|
|
33
|
+
const secret = await keycloakManager.clients.getClientSecret({ id: testClient.id });
|
|
34
|
+
testClientSecret = secret?.value;
|
|
35
|
+
|
|
36
|
+
if (!testClientSecret) {
|
|
37
|
+
this.skip();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
after(async function () {
|
|
42
|
+
if (!KEYCLOAK_CONFIG?.username || !KEYCLOAK_CONFIG?.password) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await keycloakManager.configure(
|
|
47
|
+
buildConfig({
|
|
48
|
+
grantType: KEYCLOAK_CONFIG.grantType || 'password',
|
|
49
|
+
tokenLifeSpan: KEYCLOAK_CONFIG.tokenLifeSpan || 60,
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('authenticates without refresh token errors', async function () {
|
|
55
|
+
if (!testClientId || !testClientSecret) {
|
|
56
|
+
this.skip();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await keycloakManager.configure(
|
|
61
|
+
buildConfig({
|
|
62
|
+
realmName: TEST_REALM,
|
|
63
|
+
clientId: testClientId,
|
|
64
|
+
clientSecret: testClientSecret,
|
|
65
|
+
grantType: 'client_credentials',
|
|
66
|
+
tokenLifeSpan: 60,
|
|
67
|
+
username: undefined,
|
|
68
|
+
password: undefined,
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const token = keycloakManager.getToken();
|
|
73
|
+
expect(token).to.have.property('accessToken');
|
|
74
|
+
expect(token.accessToken).to.be.a('string').and.to.have.length.greaterThan(0);
|
|
75
|
+
});
|
|
76
|
+
});
|