keycloak-api-manager 4.0.0 → 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.
- package/Handlers/attackDetectionHandler.js +64 -0
- package/Handlers/clientPoliciesHandler.js +120 -0
- package/Handlers/groupsHandler.js +32 -0
- package/Handlers/organizationsHandler.js +243 -0
- package/Handlers/serverInfoHandler.js +36 -0
- package/Handlers/userProfileHandler.js +121 -0
- package/README.md +83 -7157
- package/docs/architecture.md +47 -0
- package/docs/deployment.md +32 -0
- package/docs/keycloak-setup.md +47 -0
- package/docs/test-configuration.md +43 -0
- package/docs/testing.md +60 -0
- package/index.js +156 -240
- package/package.json +28 -15
- package/test/.mocharc.json +2 -2
- package/test/config/secrets.json +12 -0
- package/test/docker-keycloak/certs/keycloak.crt +58 -0
- package/test/docker-keycloak/certs/keycloak.key +28 -0
- package/test/docker-keycloak/docker-compose-https.yml +2 -0
- package/test/docker-keycloak/docker-compose.yml +4 -4
- package/test/helpers/matrix.js +16 -0
- package/test/matrix/auth.json +27 -0
- package/test/matrix/clients.json +45 -0
- package/test/matrix/realms-components-idp.json +37 -0
- package/test/matrix/users-roles-groups.json +26 -0
- package/test/package-lock.json +3032 -0
- package/test/specs/attackDetection.test.js +102 -0
- package/test/specs/clientCredentials.test.js +79 -0
- package/test/specs/clientPolicies.test.js +162 -0
- package/test/specs/{debugClientLibrary.test.js → diagnostics/debugClientLibrary.test.js} +2 -2
- package/test/specs/groupPermissions.test.js +87 -0
- package/test/specs/matrix/matrix-auth.test.js +112 -0
- package/test/specs/matrix/matrix-clients.test.js +59 -0
- package/test/specs/matrix/matrix-realms-components-idp.test.js +111 -0
- package/test/specs/matrix/matrix-users-roles-groups.test.js +68 -0
- package/test/specs/organizations.test.js +183 -0
- package/test/specs/serverInfo.test.js +140 -0
- package/test/specs/userProfile.test.js +135 -0
- package/test/{enableServerFeatures.js → support/enableServerFeatures.js} +43 -26
- package/test/{setup.js → support/setup.js} +3 -3
- package/test/support/testConfig.js +69 -0
- package/test/testConfig.js +1 -69
- package/test-output.log +72 -0
- package/test/TESTING.md +0 -327
- package/test/config/CONFIGURATION.md +0 -170
- package/test/diagnostic-protocol-mappers.js +0 -189
- package/test/docker-keycloak/DEPLOYMENT_GUIDE.md +0 -262
- package/test/helpers/setup.js +0 -186
|
@@ -0,0 +1,111 @@
|
|
|
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 } = require('../../testConfig');
|
|
9
|
+
const { loadMatrix, uniqueName } = require('../../helpers/matrix');
|
|
10
|
+
|
|
11
|
+
function shouldSkipFeature(err) {
|
|
12
|
+
if (!err || !err.message) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const text = err.message.toLowerCase();
|
|
16
|
+
return (
|
|
17
|
+
(text.includes('provider') && text.includes('not found')) ||
|
|
18
|
+
text.includes('feature not enabled') ||
|
|
19
|
+
text.includes('not supported') ||
|
|
20
|
+
text.includes('http 404') ||
|
|
21
|
+
text.includes('unknown_error')
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('Matrix - Realms, Components, Identity Providers', function () {
|
|
26
|
+
this.timeout(60000);
|
|
27
|
+
|
|
28
|
+
const matrix = loadMatrix('realms-components-idp');
|
|
29
|
+
|
|
30
|
+
before(async function () {
|
|
31
|
+
await keycloakManager.configure({
|
|
32
|
+
baseUrl: KEYCLOAK_CONFIG.baseUrl,
|
|
33
|
+
realmName: KEYCLOAK_CONFIG.realmName,
|
|
34
|
+
clientId: KEYCLOAK_CONFIG.clientId,
|
|
35
|
+
clientSecret: KEYCLOAK_CONFIG.clientSecret,
|
|
36
|
+
username: KEYCLOAK_CONFIG.username,
|
|
37
|
+
password: KEYCLOAK_CONFIG.password,
|
|
38
|
+
grantType: KEYCLOAK_CONFIG.grantType,
|
|
39
|
+
tokenLifeSpan: KEYCLOAK_CONFIG.tokenLifeSpan,
|
|
40
|
+
scope: KEYCLOAK_CONFIG.scope,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
matrix.realms.forEach((testCase) => {
|
|
45
|
+
it(`realm case: ${testCase.name}`, async function () {
|
|
46
|
+
const realmName = uniqueName(`matrix-realm-${testCase.name}`);
|
|
47
|
+
|
|
48
|
+
await keycloakManager.realms.create({
|
|
49
|
+
realm: realmName,
|
|
50
|
+
enabled: true,
|
|
51
|
+
...testCase.realmConfig,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const realms = await keycloakManager.realms.find();
|
|
55
|
+
expect(realms.map((r) => r.realm)).to.include(realmName);
|
|
56
|
+
|
|
57
|
+
await keycloakManager.realms.update(
|
|
58
|
+
{ realm: realmName },
|
|
59
|
+
{
|
|
60
|
+
...testCase.realmConfig,
|
|
61
|
+
displayName: `Updated ${realmName}`,
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const updated = await keycloakManager.realms.findOne({ realm: realmName });
|
|
66
|
+
expect(updated.displayName).to.equal(`Updated ${realmName}`);
|
|
67
|
+
|
|
68
|
+
await keycloakManager.realms.del({ realm: realmName });
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('components: find and check components exist', async function () {
|
|
73
|
+
const components = await keycloakManager.components.find();
|
|
74
|
+
expect(components).to.be.an('array');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
matrix.identityProviders.forEach((testCase) => {
|
|
78
|
+
it(`idp case: ${testCase.name}`, async function () {
|
|
79
|
+
const realmName = uniqueName(`matrix-idp-realm-${testCase.name}`);
|
|
80
|
+
const alias = uniqueName(`matrix-idp-${testCase.name}`);
|
|
81
|
+
|
|
82
|
+
await keycloakManager.realms.create({ realm: realmName, enabled: true });
|
|
83
|
+
keycloakManager.setConfig({ realmName });
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await keycloakManager.identityProviders.create({
|
|
87
|
+
alias,
|
|
88
|
+
providerId: testCase.providerId,
|
|
89
|
+
enabled: true,
|
|
90
|
+
trustEmail: false,
|
|
91
|
+
storeToken: false,
|
|
92
|
+
addReadTokenRoleOnCreate: false,
|
|
93
|
+
authenticateByDefault: false,
|
|
94
|
+
config: testCase.config,
|
|
95
|
+
});
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (shouldSkipFeature(err)) {
|
|
98
|
+
this.skip();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const idps = await keycloakManager.identityProviders.find();
|
|
105
|
+
expect(idps.map((i) => i.alias)).to.include(alias);
|
|
106
|
+
|
|
107
|
+
await keycloakManager.identityProviders.del({ alias });
|
|
108
|
+
await keycloakManager.realms.del({ realm: realmName });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -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
|
+
});
|