keycloak-api-manager 4.0.1 → 5.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 (48) hide show
  1. package/Handlers/attackDetectionHandler.js +64 -0
  2. package/Handlers/clientPoliciesHandler.js +120 -0
  3. package/Handlers/groupsHandler.js +32 -0
  4. package/Handlers/organizationsHandler.js +243 -0
  5. package/Handlers/serverInfoHandler.js +36 -0
  6. package/Handlers/userProfileHandler.js +121 -0
  7. package/README.md +83 -7157
  8. package/docs/architecture.md +47 -0
  9. package/docs/deployment.md +32 -0
  10. package/docs/keycloak-setup.md +47 -0
  11. package/docs/test-configuration.md +43 -0
  12. package/docs/testing.md +60 -0
  13. package/index.js +156 -250
  14. package/package.json +28 -15
  15. package/test/.mocharc.json +2 -2
  16. package/test/config/secrets.json +12 -0
  17. package/test/docker-keycloak/certs/keycloak.crt +58 -0
  18. package/test/docker-keycloak/certs/keycloak.key +28 -0
  19. package/test/docker-keycloak/docker-compose-https.yml +2 -0
  20. package/test/docker-keycloak/docker-compose.yml +4 -4
  21. package/test/helpers/matrix.js +16 -0
  22. package/test/matrix/auth.json +27 -0
  23. package/test/matrix/clients.json +45 -0
  24. package/test/matrix/realms-components-idp.json +37 -0
  25. package/test/matrix/users-roles-groups.json +26 -0
  26. package/test/package-lock.json +3032 -0
  27. package/test/specs/attackDetection.test.js +102 -0
  28. package/test/specs/clientCredentials.test.js +3 -0
  29. package/test/specs/clientPolicies.test.js +162 -0
  30. package/test/specs/{debugClientLibrary.test.js → diagnostics/debugClientLibrary.test.js} +2 -2
  31. package/test/specs/groupPermissions.test.js +87 -0
  32. package/test/specs/matrix/matrix-auth.test.js +112 -0
  33. package/test/specs/matrix/matrix-clients.test.js +59 -0
  34. package/test/specs/matrix/matrix-realms-components-idp.test.js +111 -0
  35. package/test/specs/matrix/matrix-users-roles-groups.test.js +68 -0
  36. package/test/specs/organizations.test.js +183 -0
  37. package/test/specs/serverInfo.test.js +140 -0
  38. package/test/specs/userProfile.test.js +135 -0
  39. package/test/{enableServerFeatures.js → support/enableServerFeatures.js} +43 -26
  40. package/test/{setup.js → support/setup.js} +3 -3
  41. package/test/support/testConfig.js +69 -0
  42. package/test/testConfig.js +1 -69
  43. package/test-output.log +72 -0
  44. package/test/TESTING.md +0 -327
  45. package/test/config/CONFIGURATION.md +0 -170
  46. package/test/diagnostic-protocol-mappers.js +0 -189
  47. package/test/docker-keycloak/DEPLOYMENT_GUIDE.md +0 -262
  48. package/test/helpers/setup.js +0 -186
@@ -0,0 +1,68 @@
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 { TEST_REALM } = require('../../testConfig');
9
+ const { loadMatrix, uniqueName } = require('../../helpers/matrix');
10
+
11
+ describe('Matrix - Users, Roles, Groups', function () {
12
+ this.timeout(30000);
13
+
14
+ const matrix = loadMatrix('users-roles-groups');
15
+
16
+ before(function () {
17
+ keycloakManager.setConfig({ realmName: TEST_REALM });
18
+ });
19
+
20
+ matrix.cases.forEach((testCase) => {
21
+ it(`user-role-group case: ${testCase.name}`, async function () {
22
+ const roleName = uniqueName(`matrix-role-${testCase.name}`);
23
+ const groupName = uniqueName(`matrix-group-${testCase.name}`);
24
+ const username = uniqueName(`matrix-user-${testCase.name}`);
25
+ const email = `${username}@example.test`;
26
+
27
+ await keycloakManager.roles.create({
28
+ name: roleName,
29
+ description: `Role for ${testCase.name}`,
30
+ });
31
+
32
+ const group = await keycloakManager.groups.create({
33
+ name: groupName,
34
+ attributes: { description: ['Matrix group'] },
35
+ });
36
+
37
+ const user = await keycloakManager.users.create({
38
+ username,
39
+ email,
40
+ enabled: testCase.user.enabled,
41
+ emailVerified: testCase.user.emailVerified,
42
+ firstName: 'Matrix',
43
+ lastName: 'User',
44
+ });
45
+
46
+ await keycloakManager.users.addToGroup({
47
+ id: user.id,
48
+ groupId: group.id,
49
+ });
50
+
51
+ const role = await keycloakManager.roles.findOneByName({ name: roleName });
52
+ await keycloakManager.users.addRealmRoleMappings({
53
+ id: user.id,
54
+ roles: [role],
55
+ });
56
+
57
+ const userGroups = await keycloakManager.users.listGroups({ id: user.id });
58
+ expect(userGroups.map((g) => g.id)).to.include(group.id);
59
+
60
+ const roleMappings = await keycloakManager.users.listRealmRoleMappings({ id: user.id });
61
+ expect(roleMappings.map((r) => r.name)).to.include(roleName);
62
+
63
+ await keycloakManager.users.del({ id: user.id });
64
+ await keycloakManager.groups.del({ id: group.id });
65
+ await keycloakManager.roles.delByName({ name: roleName });
66
+ });
67
+ });
68
+ });
@@ -0,0 +1,183 @@
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('../../index');
9
+ const { TEST_REALM, generateUniqueName } = require('../testConfig');
10
+
11
+ const config = {
12
+ baseUrl: conf.keycloak.baseUrl,
13
+ realmName: conf.keycloak.realmName,
14
+ clientId: conf.keycloak.clientId,
15
+ clientSecret: conf.keycloak.clientSecret,
16
+ grantType: conf.keycloak.grantType,
17
+ username: conf.keycloak.username,
18
+ password: conf.keycloak.password,
19
+ tokenLifeSpan: conf.keycloak.tokenLifeSpan
20
+ };
21
+
22
+ /**
23
+ * Integration tests for Organizations Handler
24
+ * Tests organization CRUD, members, identity providers (Keycloak 25+)
25
+ */
26
+ describe('Organizations Handler Tests', function () {
27
+ this.timeout(10000);
28
+
29
+ const testOrgData = {
30
+ name: `test-org-${Date.now()}`,
31
+ domains: [],
32
+ attributes: {
33
+ customAttr: ['value1']
34
+ }
35
+ };
36
+
37
+ let createdOrgId;
38
+ let testUserId;
39
+
40
+ before(async function () {
41
+ await KeycloakManager.configure(config);
42
+
43
+ // Switch to test realm for operations
44
+ KeycloakManager.setConfig({ realmName: TEST_REALM });
45
+
46
+ // Create a test user for member operations in test realm
47
+ const userResult = await KeycloakManager.users.create({
48
+ username: `org-test-user-${Date.now()}`,
49
+ email: 'orguser@test.com',
50
+ enabled: true
51
+ });
52
+ testUserId = userResult.id;
53
+ });
54
+
55
+ after(async function () {
56
+ // Clean up
57
+ if (createdOrgId) {
58
+ try {
59
+ await KeycloakManager.organizations.del({ id: createdOrgId });
60
+ } catch (e) {
61
+ // Org might already be deleted
62
+ }
63
+ }
64
+ if (testUserId) {
65
+ try {
66
+ await KeycloakManager.users.del({ id: testUserId });
67
+ } catch (e) {
68
+ // User might already be deleted
69
+ }
70
+ }
71
+ KeycloakManager.stop();
72
+ });
73
+
74
+ describe('Organization CRUD Operations', function () {
75
+ it('should create an organization', async function () {
76
+ const result = await KeycloakManager.organizations.create(testOrgData);
77
+ expect(result).to.have.property('id');
78
+ createdOrgId = result.id;
79
+ });
80
+
81
+ it('should find all organizations', async function () {
82
+ if (!createdOrgId) this.skip();
83
+
84
+ const orgs = await KeycloakManager.organizations.find({});
85
+ expect(orgs).to.be.an('array');
86
+ expect(orgs.length).to.be.greaterThan(0);
87
+ });
88
+
89
+ it('should find one organization by ID', async function () {
90
+ if (!createdOrgId) this.skip();
91
+
92
+ const org = await KeycloakManager.organizations.findOne({ id: createdOrgId });
93
+ expect(org).to.have.property('id', createdOrgId);
94
+ expect(org).to.have.property('name', testOrgData.name);
95
+ });
96
+
97
+ it('should update an organization', async function () {
98
+ if (!createdOrgId) this.skip();
99
+
100
+ const updatedData = {
101
+ attributes: {
102
+ customAttr: ['updatedValue']
103
+ }
104
+ };
105
+
106
+ await KeycloakManager.organizations.update({ id: createdOrgId }, updatedData);
107
+
108
+ const org = await KeycloakManager.organizations.findOne({ id: createdOrgId });
109
+ expect(org.attributes.customAttr).to.deep.equal(['updatedValue']);
110
+ });
111
+ });
112
+
113
+ describe('Organization Members', function () {
114
+ it('should add a member to organization', async function () {
115
+ if (!createdOrgId || !testUserId) this.skip();
116
+
117
+ await KeycloakManager.organizations.addMember({
118
+ id: createdOrgId,
119
+ userId: testUserId
120
+ });
121
+
122
+ expect(true).to.be.true;
123
+ });
124
+
125
+ it('should list organization members', async function () {
126
+ if (!createdOrgId) this.skip();
127
+
128
+ const members = await KeycloakManager.organizations.listMembers({
129
+ id: createdOrgId
130
+ });
131
+
132
+ expect(members).to.be.an('array');
133
+ });
134
+
135
+ it('should remove a member from organization', async function () {
136
+ if (!createdOrgId || !testUserId) this.skip();
137
+
138
+ await KeycloakManager.organizations.delMember({
139
+ id: createdOrgId,
140
+ userId: testUserId
141
+ });
142
+
143
+ expect(true).to.be.true;
144
+ });
145
+ });
146
+
147
+ describe('Organization Identity Providers', function () {
148
+ it('should list identity providers for organization', async function () {
149
+ if (!createdOrgId) this.skip();
150
+
151
+ try {
152
+ const idps = await KeycloakManager.organizations.listIdentityProviders({
153
+ id: createdOrgId
154
+ });
155
+
156
+ expect(idps).to.be.an('array');
157
+ } catch (error) {
158
+ // Method might not be available
159
+ if (error.response?.status === 404) {
160
+ this.skip();
161
+ }
162
+ throw error;
163
+ }
164
+ });
165
+ });
166
+
167
+ describe('Organization Deletion', function () {
168
+ it('should delete an organization', async function () {
169
+ if (!createdOrgId) this.skip();
170
+
171
+ await KeycloakManager.organizations.del({ id: createdOrgId });
172
+
173
+ try {
174
+ await KeycloakManager.organizations.findOne({ id: createdOrgId });
175
+ throw new Error('Organization should have been deleted');
176
+ } catch (error) {
177
+ expect(error.response?.status).to.equal(404);
178
+ }
179
+
180
+ createdOrgId = null; // Prevent cleanup attempt
181
+ });
182
+ });
183
+ });
@@ -0,0 +1,140 @@
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('../../index');
9
+ const { TEST_REALM, generateUniqueName } = require('../testConfig');
10
+
11
+ const config = {
12
+ baseUrl: conf.keycloak.baseUrl,
13
+ realmName: conf.keycloak.realmName,
14
+ clientId: conf.keycloak.clientId,
15
+ clientSecret: conf.keycloak.clientSecret,
16
+ grantType: conf.keycloak.grantType,
17
+ username: conf.keycloak.username,
18
+ password: conf.keycloak.password,
19
+ tokenLifeSpan: conf.keycloak.tokenLifeSpan
20
+ };
21
+
22
+ /**
23
+ * Integration tests for Server Info Handler
24
+ * Tests server information and metadata retrieval
25
+ */
26
+ describe('Server Info Handler Tests', function () {
27
+ this.timeout(10000);
28
+
29
+ before(async function () {
30
+ await KeycloakManager.configure(config);
31
+ });
32
+
33
+ after(async function () {
34
+ KeycloakManager.stop();
35
+ });
36
+
37
+ describe('Server Information', function () {
38
+ let serverInfo;
39
+
40
+ it('should get comprehensive server information', async function () {
41
+ serverInfo = await KeycloakManager.serverInfo.getInfo();
42
+
43
+ expect(serverInfo).to.exist;
44
+ expect(serverInfo).to.be.an('object');
45
+ });
46
+
47
+ it('should contain system information', async function () {
48
+ if (!serverInfo) this.skip();
49
+
50
+ expect(serverInfo).to.have.property('systemInfo');
51
+ expect(serverInfo.systemInfo).to.be.an('object');
52
+
53
+ // System info typically includes version, uptime, etc.
54
+ if (serverInfo.systemInfo.version) {
55
+ expect(serverInfo.systemInfo.version).to.be.a('string');
56
+ }
57
+ });
58
+
59
+ it('should contain memory information', async function () {
60
+ if (!serverInfo) this.skip();
61
+
62
+ expect(serverInfo).to.have.property('memoryInfo');
63
+ expect(serverInfo.memoryInfo).to.be.an('object');
64
+ });
65
+
66
+ it('should contain profile information', async function () {
67
+ if (!serverInfo) this.skip();
68
+
69
+ expect(serverInfo).to.have.property('profileInfo');
70
+ expect(serverInfo.profileInfo).to.be.an('object');
71
+ });
72
+
73
+ it('should contain available themes', async function () {
74
+ if (!serverInfo) this.skip();
75
+
76
+ expect(serverInfo).to.have.property('themes');
77
+ expect(serverInfo.themes).to.be.an('object');
78
+
79
+ // Themes should include login, account, admin, etc.
80
+ const themeTypes = Object.keys(serverInfo.themes);
81
+ expect(themeTypes.length).to.be.greaterThan(0);
82
+ });
83
+
84
+ it('should contain available providers', async function () {
85
+ if (!serverInfo) this.skip();
86
+
87
+ expect(serverInfo).to.have.property('providers');
88
+ expect(serverInfo.providers).to.be.an('object');
89
+
90
+ // Providers should include various SPIs
91
+ const providerTypes = Object.keys(serverInfo.providers);
92
+ expect(providerTypes.length).to.be.greaterThan(0);
93
+ });
94
+
95
+ it('should contain protocol mapper types', async function () {
96
+ if (!serverInfo) this.skip();
97
+
98
+ expect(serverInfo).to.have.property('protocolMapperTypes');
99
+ expect(serverInfo.protocolMapperTypes).to.be.an('object');
100
+ });
101
+
102
+ it('should contain component types', async function () {
103
+ if (!serverInfo) this.skip();
104
+
105
+ if (serverInfo.componentTypes) {
106
+ expect(serverInfo.componentTypes).to.be.an('object');
107
+ }
108
+ });
109
+
110
+ it('should contain password policies', async function () {
111
+ if (!serverInfo) this.skip();
112
+
113
+ if (serverInfo.passwordPolicies) {
114
+ expect(serverInfo.passwordPolicies).to.be.an('array');
115
+ }
116
+ });
117
+
118
+ it('should contain enums', async function () {
119
+ if (!serverInfo) this.skip();
120
+
121
+ if (serverInfo.enums) {
122
+ expect(serverInfo.enums).to.be.an('object');
123
+ }
124
+ });
125
+
126
+ it('should verify specific provider categories exist', async function () {
127
+ if (!serverInfo || !serverInfo.providers) this.skip();
128
+
129
+ const providers = serverInfo.providers;
130
+
131
+ // Check for common provider categories
132
+ // Note: These may vary by Keycloak version
133
+ const commonProviders = ['login-protocol', 'realm-cache', 'user-storage'];
134
+ const foundProviders = commonProviders.filter(p => providers[p]);
135
+
136
+ // At least some common providers should exist
137
+ expect(foundProviders.length).to.be.greaterThan(0);
138
+ });
139
+ });
140
+ });
@@ -0,0 +1,135 @@
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('../../index');
9
+ const { TEST_REALM, generateUniqueName } = require('../testConfig');
10
+
11
+ const config = {
12
+ baseUrl: conf.keycloak.baseUrl,
13
+ realmName: conf.keycloak.realmName,
14
+ clientId: conf.keycloak.clientId,
15
+ clientSecret: conf.keycloak.clientSecret,
16
+ grantType: conf.keycloak.grantType,
17
+ username: conf.keycloak.username,
18
+ password: conf.keycloak.password,
19
+ tokenLifeSpan: conf.keycloak.tokenLifeSpan
20
+ };
21
+
22
+ /**
23
+ * Integration tests for User Profile Handler
24
+ * Tests user profile configuration and metadata (Keycloak 15+)
25
+ */
26
+ describe('User Profile Handler Tests', function () {
27
+ this.timeout(10000);
28
+
29
+ before(async function () {
30
+ await KeycloakManager.configure(config);
31
+ });
32
+
33
+ after(async function () {
34
+ KeycloakManager.stop();
35
+ });
36
+
37
+ describe('User Profile Configuration', function () {
38
+ let originalConfig;
39
+
40
+ it('should get user profile configuration', async function () {
41
+ try {
42
+ const profileConfig = await KeycloakManager.userProfile.getConfiguration({});
43
+
44
+ expect(profileConfig).to.exist;
45
+ expect(profileConfig).to.have.property('attributes');
46
+ expect(profileConfig.attributes).to.be.an('array');
47
+
48
+ // Save original config for restoration
49
+ originalConfig = profileConfig;
50
+ } catch (error) {
51
+ // User Profile API only available in Keycloak 15+
52
+ if (error.response?.status === 404 || error.message?.includes('getProfile')) {
53
+ this.skip();
54
+ }
55
+ throw error;
56
+ }
57
+ });
58
+
59
+ it('should verify standard attributes exist in profile', async function () {
60
+ if (!originalConfig) this.skip();
61
+
62
+ const attributeNames = originalConfig.attributes.map(attr => attr.name);
63
+
64
+ // Standard attributes that should exist
65
+ expect(attributeNames).to.include('username');
66
+ expect(attributeNames).to.include('email');
67
+ });
68
+
69
+ it('should update user profile configuration', async function () {
70
+ if (!originalConfig) this.skip();
71
+ if (!originalConfig.attributes || !Array.isArray(originalConfig.attributes)) this.skip();
72
+
73
+ // Add a custom attribute to the configuration
74
+ const updatedConfig = JSON.parse(JSON.stringify(originalConfig));
75
+
76
+ // Check if test attribute already exists
77
+ const testAttrIndex = updatedConfig.attributes.findIndex(
78
+ attr => attr.name === 'testCustomAttribute'
79
+ );
80
+
81
+ if (testAttrIndex === -1) {
82
+ // Add new test attribute
83
+ updatedConfig.attributes.push({
84
+ name: 'testCustomAttribute',
85
+ displayName: 'Test Custom Attribute',
86
+ validations: {},
87
+ permissions: {
88
+ view: ['admin', 'user'],
89
+ edit: ['admin']
90
+ },
91
+ multivalued: false
92
+ });
93
+ }
94
+
95
+ try {
96
+ await KeycloakManager.userProfile.updateConfiguration({}, updatedConfig);
97
+
98
+ // Verify the update
99
+ const verifyConfig = await KeycloakManager.userProfile.getConfiguration({});
100
+ const customAttrExists = verifyConfig.attributes.some(
101
+ attr => attr.name === 'testCustomAttribute'
102
+ );
103
+
104
+ expect(customAttrExists).to.be.true;
105
+
106
+ // Restore original configuration
107
+ await KeycloakManager.userProfile.updateConfiguration({}, originalConfig);
108
+ } catch (error) {
109
+ // Restore on error
110
+ if (originalConfig) {
111
+ await KeycloakManager.userProfile.updateConfiguration({}, originalConfig);
112
+ }
113
+ throw error;
114
+ }
115
+ });
116
+ });
117
+
118
+ describe('User Profile Metadata', function () {
119
+ it('should get user profile metadata', async function () {
120
+ try {
121
+ const metadata = await KeycloakManager.userProfile.getMetadata({});
122
+
123
+ expect(metadata).to.exist;
124
+ // Metadata typically contains validators, attribute types, etc.
125
+ // Structure may vary by Keycloak version
126
+ } catch (error) {
127
+ // Metadata endpoint might not be available in all versions
128
+ if (error.response?.status === 404 || error.message?.includes('getProfileMetadata')) {
129
+ this.skip();
130
+ }
131
+ throw error;
132
+ }
133
+ });
134
+ });
135
+ });
@@ -42,9 +42,9 @@ const path = require('path');
42
42
 
43
43
  // Ensure NODE_ENV and config path are set before requiring testConfig
44
44
  process.env.NODE_ENV = process.env.NODE_ENV || 'test';
45
- process.env.PROPERTIES_PATH = path.join(__dirname, 'config');
45
+ process.env.PROPERTIES_PATH = path.join(__dirname, '../config');
46
46
 
47
- const keycloakManager = require('../index');
47
+ const keycloakManager = require('../../index');
48
48
  const {
49
49
  TEST_REALM,
50
50
  TEST_CLIENT_ID,
@@ -103,11 +103,21 @@ async function enableServerFeatures() {
103
103
  duplicateEmailsAllowed: false,
104
104
  resetPasswordAllowed: true,
105
105
  editUsernameAllowed: false,
106
- bruteForceProtected: false
106
+ bruteForceProtected: false,
107
+ organizationsEnabled: true // Enable Organizations (requires 'organization' feature flag)
107
108
  });
108
109
  console.log(` ✓ Test realm created: ${TEST_REALM}`);
109
110
  } else {
110
111
  console.log(` ✓ Test realm already exists: ${TEST_REALM}`);
112
+ // Update existing realm to ensure all required settings are enabled
113
+ await keycloakManager.realms.update(
114
+ { realm: TEST_REALM },
115
+ {
116
+ enabled: true,
117
+ bruteForceProtected: false,
118
+ organizationsEnabled: true
119
+ }
120
+ );
111
121
  }
112
122
 
113
123
  // Switch to test realm for all subsequent operations
@@ -201,56 +211,63 @@ async function enableServerFeatures() {
201
211
  // 5. Create test group
202
212
  console.log('\n5. Setting up test groups...');
203
213
  const groups = await keycloakManager.groups.find();
214
+ let testGroupId;
204
215
  if (!groups.some(g => g.name === TEST_GROUP_NAME)) {
205
- await keycloakManager.groups.create({
216
+ const groupResult = await keycloakManager.groups.create({
206
217
  name: TEST_GROUP_NAME,
207
218
  attributes: {
208
219
  description: ['Test group for API testing']
209
220
  }
210
221
  });
222
+ testGroupId = groupResult.id;
211
223
  console.log(' ✓ Test group created');
212
224
  } else {
225
+ testGroupId = groups.find(g => g.name === TEST_GROUP_NAME).id;
213
226
  console.log(' ✓ Test group already exists');
214
227
  }
215
228
 
216
229
  // 6. Enable fine-grained admin permissions
217
230
  console.log('\n6. Enabling fine-grained admin permissions...');
218
231
  try {
219
- const currentPerms = await keycloakManager.realms.getUsersManagementPermissions({
232
+ // Enable users management permissions
233
+ const currentUserPerms = await keycloakManager.realms.getUsersManagementPermissions({
220
234
  realm: TEST_REALM
221
235
  });
222
236
 
223
- if (!currentPerms.enabled) {
237
+ if (!currentUserPerms.enabled) {
224
238
  await keycloakManager.realms.updateUsersManagementPermissions({
225
239
  realm: TEST_REALM,
226
240
  enabled: true
227
241
  });
228
- console.log(' ✓ Fine-grained admin permissions enabled');
242
+ console.log(' ✓ Users fine-grained admin permissions enabled');
229
243
  } else {
230
- console.log(' ✓ Fine-grained admin permissions already enabled');
244
+ console.log(' ✓ Users fine-grained admin permissions already enabled');
231
245
  }
232
- } catch (err) {
233
- console.log(` ⚠ Fine-grained permissions: ${err.message}`);
234
- console.log(` ℹ This is typically a server configuration setting that requires`);
235
- console.log(` enabling "authorizationServicesEnabled" in realm settings`);
236
- }
237
-
238
- // 7. Update realm to enable protocol mappers and installation providers
239
- console.log('\n7. Enabling realm features...');
240
- try {
241
- await keycloakManager.realms.update(
242
- { realm: TEST_REALM },
243
- {
244
- installationProviders: ['docker-compose', 'docker', 'kubernetes', 'openshift'],
245
- installationProvidersEnabled: true
246
+
247
+ // Enable groups management permissions for the test group
248
+ if (testGroupId) {
249
+ try {
250
+ const groupPerms = await keycloakManager.groups.listPermissions({ id: testGroupId });
251
+ if (!groupPerms.enabled) {
252
+ await keycloakManager.groups.setPermissions(
253
+ { id: testGroupId },
254
+ { enabled: true }
255
+ );
256
+ console.log(' ✓ Groups fine-grained permissions enabled');
257
+ } else {
258
+ console.log(' ✓ Groups fine-grained permissions already enabled');
259
+ }
260
+ } catch (groupPermErr) {
261
+ console.log(` ⚠ Groups permissions: ${groupPermErr.message}`);
246
262
  }
247
- );
248
- console.log(' ✓ Installation providers enabled');
263
+ }
249
264
  } catch (err) {
250
- console.log(` ⚠ Installation providers: ${err.message}`);
265
+ console.log(` ⚠ Fine-grained permissions: ${err.message}`);
266
+ console.log(` ℹ This feature requires 'admin-fine-grained-authz' to be enabled`);
267
+ console.log(` in KC_FEATURES environment variable`);
251
268
  }
252
269
 
253
- // 7. Update realm to enable protocol mappers and installation providers
270
+ // 7. Realm features requiring server-side configuration
254
271
  console.log('\n7. Enabling realm features...');
255
272
  try {
256
273
  // Note: Some features like installation providers cannot be enabled via API
@@ -31,7 +31,7 @@ const { exec } = require('child_process');
31
31
  const path = require('path');
32
32
  const fs = require('fs');
33
33
  const os = require('os');
34
- const keycloakManager = require('../index');
34
+ const keycloakManager = require('../../index');
35
35
 
36
36
  let sshTunnelProcess = null;
37
37
 
@@ -92,7 +92,7 @@ before(async function() {
92
92
  console.log(`✓ SSH tunnel created: http://${sshTunnelUrl}\n`);
93
93
 
94
94
  // Update config to use tunnel
95
- const configPath = path.join(__dirname, 'config/local.json');
95
+ const configPath = path.join(__dirname, '../config/local.json');
96
96
  let config = {};
97
97
 
98
98
  if (fs.existsSync(configPath)) {
@@ -169,7 +169,7 @@ after(async function() {
169
169
 
170
170
  // Clean up SSH tunnel config file if it was created
171
171
  try {
172
- const configPath = path.join(__dirname, 'config/local.json');
172
+ const configPath = path.join(__dirname, '../config/local.json');
173
173
  if (fs.existsSync(configPath)) {
174
174
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
175
175
  // Only delete local.json if it contains SSH tunnel config