keycloak-api-manager 3.2.0 → 4.0.0

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.
Files changed (45) hide show
  1. package/.env.example +27 -0
  2. package/Handlers/authenticationManagementHandler.js +1 -1
  3. package/Handlers/clientScopesHandler.js +5 -5
  4. package/Handlers/clientsHandler.js +241 -31
  5. package/Handlers/componentsHandler.js +2 -2
  6. package/Handlers/groupsHandler.js +18 -3
  7. package/Handlers/httpApiHelper.js +87 -0
  8. package/Handlers/identityProvidersHandler.js +3 -3
  9. package/Handlers/realmsHandler.js +26 -4
  10. package/Handlers/usersHandler.js +43 -10
  11. package/README.md +361 -26
  12. package/index.js +149 -29
  13. package/index.mjs +21 -0
  14. package/package.json +3 -2
  15. package/test/.mocharc.json +4 -0
  16. package/test/TESTING.md +327 -0
  17. package/test/config/CONFIGURATION.md +170 -0
  18. package/test/config/default.json +36 -0
  19. package/test/config/local.json.example +7 -0
  20. package/test/config/secrets.json.example +7 -0
  21. package/test/diagnostic-protocol-mappers.js +189 -0
  22. package/test/docker-keycloak/DEPLOYMENT_GUIDE.md +262 -0
  23. package/test/docker-keycloak/certs/.gitkeep +7 -0
  24. package/test/docker-keycloak/docker-compose-https.yml +50 -0
  25. package/test/docker-keycloak/docker-compose.yml +59 -0
  26. package/test/docker-keycloak/setup-keycloak.js +501 -0
  27. package/test/enableServerFeatures.js +315 -0
  28. package/test/helpers/config.js +218 -0
  29. package/test/helpers/docker-helpers.js +513 -0
  30. package/test/helpers/setup.js +186 -0
  31. package/test/package.json +18 -0
  32. package/test/setup.js +194 -0
  33. package/test/specs/authenticationManagement.test.js +224 -0
  34. package/test/specs/clientScopes.test.js +388 -0
  35. package/test/specs/clients.test.js +791 -0
  36. package/test/specs/components.test.js +151 -0
  37. package/test/specs/debugClientLibrary.test.js +88 -0
  38. package/test/specs/groups.test.js +362 -0
  39. package/test/specs/identityProviders.test.js +292 -0
  40. package/test/specs/realms.test.js +390 -0
  41. package/test/specs/roles.test.js +322 -0
  42. package/test/specs/users.test.js +445 -0
  43. package/test/testConfig.js +69 -0
  44. package/.idea/vcs.xml +0 -6
  45. package/.idea/workspace.xml +0 -94
@@ -0,0 +1,322 @@
1
+ const path = require('path');
2
+ const http = require('http');
3
+ const https = require('https');
4
+ const { expect } = require('chai');
5
+
6
+ process.env.NODE_ENV = process.env.NODE_ENV || 'test';
7
+ process.env.PROPERTIES_PATH = path.join(__dirname, '..', 'config');
8
+
9
+ const { conf } = require('propertiesmanager');
10
+ const keycloakManager = require('keycloak-api-manager');
11
+
12
+ function requestAdmin(baseUrl, token, apiPath, method = 'GET', body) {
13
+ const url = new URL(apiPath, baseUrl);
14
+ const transport = url.protocol === 'https:' ? https : http;
15
+ const payload = body ? JSON.stringify(body) : null;
16
+
17
+ const options = {
18
+ method,
19
+ headers: {
20
+ Accept: 'application/json',
21
+ Authorization: `Bearer ${token}`,
22
+ ...(payload ? { 'Content-Type': 'application/json' } : {}),
23
+ },
24
+ };
25
+
26
+ return new Promise((resolve, reject) => {
27
+ const req = transport.request(url, options, (res) => {
28
+ let data = '';
29
+ res.on('data', (chunk) => {
30
+ data += chunk;
31
+ });
32
+ res.on('end', () => {
33
+ let parsed = data;
34
+ try {
35
+ parsed = data ? JSON.parse(data) : null;
36
+ } catch (err) {
37
+ parsed = data;
38
+ }
39
+ resolve({ status: res.statusCode, body: parsed });
40
+ });
41
+ });
42
+
43
+ req.on('error', reject);
44
+ if (payload) {
45
+ req.write(payload);
46
+ }
47
+ req.end();
48
+ });
49
+ }
50
+
51
+ async function getAdminToken(baseUrl, username, password) {
52
+ const url = new URL('/realms/master/protocol/openid-connect/token', baseUrl);
53
+ const transport = url.protocol === 'https:' ? https : http;
54
+
55
+ const params = new URLSearchParams({
56
+ grant_type: 'password',
57
+ client_id: 'admin-cli',
58
+ username,
59
+ password,
60
+ });
61
+
62
+ const options = {
63
+ method: 'POST',
64
+ headers: {
65
+ 'Content-Type': 'application/x-www-form-urlencoded',
66
+ },
67
+ };
68
+
69
+ return new Promise((resolve, reject) => {
70
+ const req = transport.request(url, options, (res) => {
71
+ let data = '';
72
+ res.on('data', (chunk) => {
73
+ data += chunk;
74
+ });
75
+ res.on('end', () => {
76
+ if (res.statusCode === 200) {
77
+ const parsed = JSON.parse(data);
78
+ resolve(parsed.access_token);
79
+ } else {
80
+ reject(new Error(`HTTP ${res.statusCode}: ${data}`));
81
+ }
82
+ });
83
+ });
84
+
85
+ req.on('error', reject);
86
+ req.write(params.toString());
87
+ req.end();
88
+ });
89
+ }
90
+
91
+ describe('Roles Handler', function () {
92
+ this.timeout(60000);
93
+
94
+ const keycloakConfig = (conf && conf.keycloak) || {};
95
+ const testRealm = `roles-realm-${Date.now()}`;
96
+ const roleName = `roles-base-${Date.now()}`;
97
+ const secondRoleName = `roles-second-${Date.now()}`;
98
+ const compositeRoleName = `roles-composite-${Date.now()}`;
99
+ const clientId = `roles-client-${Date.now()}`;
100
+ const clientRoleName = `roles-client-role-${Date.now()}`;
101
+ const userName = `roles-user-${Date.now()}`;
102
+
103
+ let adminToken = null;
104
+ let baseRole = null;
105
+ let secondRole = null;
106
+ let compositeRole = null;
107
+ let user = null;
108
+ let client = null;
109
+ let clientRole = null;
110
+
111
+ before(async function () {
112
+ await keycloakManager.configure({
113
+ baseUrl: keycloakConfig.baseUrl,
114
+ realmName: keycloakConfig.realmName,
115
+ clientId: keycloakConfig.clientId,
116
+ clientSecret: keycloakConfig.clientSecret,
117
+ username: keycloakConfig.username,
118
+ password: keycloakConfig.password,
119
+ grantType: keycloakConfig.grantType,
120
+ tokenLifeSpan: keycloakConfig.tokenLifeSpan,
121
+ scope: keycloakConfig.scope,
122
+ });
123
+
124
+ adminToken = await getAdminToken(
125
+ keycloakConfig.baseUrl,
126
+ keycloakConfig.username,
127
+ keycloakConfig.password
128
+ );
129
+
130
+ await keycloakManager.realms.create({
131
+ realm: testRealm,
132
+ enabled: true,
133
+ });
134
+
135
+ keycloakManager.setConfig({ realmName: testRealm });
136
+
137
+ await keycloakManager.roles.create({ name: roleName, description: 'base role' });
138
+ await keycloakManager.roles.create({ name: secondRoleName, description: 'second role' });
139
+ await keycloakManager.roles.create({ name: compositeRoleName, description: 'composite role' });
140
+
141
+ baseRole = await keycloakManager.roles.findOneByName({ name: roleName });
142
+ secondRole = await keycloakManager.roles.findOneByName({ name: secondRoleName });
143
+ compositeRole = await keycloakManager.roles.findOneByName({ name: compositeRoleName });
144
+
145
+ user = await keycloakManager.users.create({
146
+ username: userName,
147
+ enabled: true,
148
+ email: `${userName}@example.com`,
149
+ });
150
+
151
+ await keycloakManager.users.addRealmRoleMappings({
152
+ id: user.id,
153
+ roles: [{ id: baseRole.id, name: baseRole.name }],
154
+ });
155
+
156
+ await keycloakManager.clients.create({
157
+ clientId,
158
+ name: clientId,
159
+ enabled: true,
160
+ publicClient: false,
161
+ protocol: 'openid-connect',
162
+ directAccessGrantsEnabled: true,
163
+ standardFlowEnabled: true,
164
+ });
165
+
166
+ const clients = await keycloakManager.clients.find({ clientId });
167
+ client = clients[0];
168
+
169
+ await keycloakManager.clients.createRole({
170
+ id: client.id,
171
+ name: clientRoleName,
172
+ description: 'role for composite test',
173
+ });
174
+
175
+ clientRole = await keycloakManager.clients.findRole({
176
+ id: client.id,
177
+ roleName: clientRoleName,
178
+ });
179
+ });
180
+
181
+ after(async function () {
182
+ try {
183
+ keycloakManager.setConfig({ realmName: testRealm });
184
+ } catch (err) {
185
+ // best effort
186
+ }
187
+
188
+ try {
189
+ if (user) {
190
+ await keycloakManager.users.del({ id: user.id });
191
+ }
192
+ } catch (err) {
193
+ // best effort
194
+ }
195
+
196
+ try {
197
+ if (client) {
198
+ await keycloakManager.clients.del({ id: client.id });
199
+ }
200
+ } catch (err) {
201
+ // best effort
202
+ }
203
+
204
+ try {
205
+ await keycloakManager.roles.delByName({ name: compositeRoleName });
206
+ } catch (err) {
207
+ // best effort
208
+ }
209
+
210
+ try {
211
+ await keycloakManager.roles.delByName({ name: secondRoleName });
212
+ } catch (err) {
213
+ // best effort
214
+ }
215
+
216
+ try {
217
+ await keycloakManager.roles.delByName({ name: roleName });
218
+ } catch (err) {
219
+ // best effort
220
+ }
221
+
222
+ try {
223
+ await keycloakManager.realms.del({ realm: testRealm });
224
+ } catch (err) {
225
+ // best effort
226
+ }
227
+
228
+ if (typeof keycloakManager.stop === 'function') {
229
+ keycloakManager.stop();
230
+ }
231
+ });
232
+
233
+ it('should create, find, find by id/name, and update roles', async function () {
234
+ const roles = await keycloakManager.roles.find({ search: roleName });
235
+ expect(roles).to.be.an('array');
236
+ expect(roles.some((role) => role.name === roleName)).to.equal(true);
237
+
238
+ const byName = await keycloakManager.roles.findOneByName({ name: roleName });
239
+ expect(byName).to.be.an('object');
240
+ expect(byName.id).to.equal(baseRole.id);
241
+
242
+ const byId = await keycloakManager.roles.findOneById({ id: baseRole.id });
243
+ expect(byId).to.be.an('object');
244
+ expect(byId.name).to.equal(roleName);
245
+
246
+ const updatedName = `${roleName}-updated`;
247
+ await keycloakManager.roles.updateByName(
248
+ { name: roleName },
249
+ { name: updatedName, description: 'updated by name' }
250
+ );
251
+
252
+ const updatedByName = await keycloakManager.roles.findOneByName({ name: updatedName });
253
+ expect(updatedByName).to.be.an('object');
254
+
255
+ await keycloakManager.roles.updateById(
256
+ { id: updatedByName.id },
257
+ { name: roleName, description: 'updated by id' }
258
+ );
259
+
260
+ const restored = await keycloakManager.roles.findOneByName({ name: roleName });
261
+ expect(restored).to.be.an('object');
262
+ expect(restored.description).to.equal('updated by id');
263
+
264
+ const direct = await requestAdmin(
265
+ keycloakConfig.baseUrl,
266
+ adminToken,
267
+ `/admin/realms/${testRealm}/roles/${roleName}`
268
+ );
269
+ expect(direct.status).to.equal(200);
270
+ expect(direct.body.name).to.equal(roleName);
271
+ });
272
+
273
+ it('should find users with a role', async function () {
274
+ const users = await keycloakManager.roles.findUsersWithRole({ name: roleName });
275
+ expect(users).to.be.an('array');
276
+ expect(users.some((item) => item.id === user.id)).to.equal(true);
277
+ });
278
+
279
+ it('should manage composite roles for realm and client', async function () {
280
+ await keycloakManager.roles.createComposite(
281
+ { roleId: compositeRole.id },
282
+ [
283
+ { id: secondRole.id, name: secondRole.name },
284
+ {
285
+ id: clientRole.id,
286
+ name: clientRole.name,
287
+ clientRole: true,
288
+ containerId: client.id,
289
+ },
290
+ ]
291
+ );
292
+
293
+ const allComposite = await keycloakManager.roles.getCompositeRoles({ id: compositeRole.id });
294
+ expect(allComposite).to.be.an('array');
295
+ expect(allComposite.some((role) => role.id === secondRole.id)).to.equal(true);
296
+
297
+ const realmComposite = await keycloakManager.roles.getCompositeRolesForRealm({ id: compositeRole.id });
298
+ expect(realmComposite).to.be.an('array');
299
+ expect(realmComposite.some((role) => role.id === secondRole.id)).to.equal(true);
300
+
301
+ const clientComposite = await keycloakManager.roles.getCompositeRolesForClient({
302
+ id: compositeRole.id,
303
+ clientId: client.id,
304
+ });
305
+ expect(clientComposite).to.be.an('array');
306
+ expect(clientComposite.some((role) => role.id === clientRole.id)).to.equal(true);
307
+ });
308
+
309
+ it('should delete role by name and verify via admin API', async function () {
310
+ const tempRoleName = `roles-temp-${Date.now()}`;
311
+ await keycloakManager.roles.create({ name: tempRoleName });
312
+
313
+ await keycloakManager.roles.delByName({ name: tempRoleName });
314
+
315
+ const direct = await requestAdmin(
316
+ keycloakConfig.baseUrl,
317
+ adminToken,
318
+ `/admin/realms/${testRealm}/roles/${tempRoleName}`
319
+ );
320
+ expect(direct.status).to.equal(404);
321
+ });
322
+ });