ef-keycloak-connect 1.3.2 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ef-keycloak-connect",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "Node JS keycloak adapter for authentication and authorization.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -17,6 +17,7 @@
17
17
  "express": "^4.17.1",
18
18
  "express-session": "^1.17.2",
19
19
  "joi": "^17.6.0",
20
- "keycloak-connect": "^10.0.2"
20
+ "keycloak-connect": "^10.0.2",
21
+ "xml2js": "^0.5.0"
21
22
  }
22
23
  }
@@ -1,20 +1,20 @@
1
1
  var requestController = require("../controller/requestController.js");
2
2
  const https = require('https');
3
3
 
4
- class FinesseService{
4
+ class FinesseService {
5
5
 
6
6
  constructor() {
7
7
 
8
8
  }
9
9
 
10
10
 
11
- async authenticateUserViaFinesse(username,password,finesseUrl){
11
+ async authenticateUserViaFinesse(username, password, finesseUrl) {
12
12
 
13
13
  return new Promise(async (resolve, reject) => {
14
-
14
+
15
15
  var URL = finesseUrl + '/finesse/api/User/' + username;
16
-
17
- let encodedCredentials = await this.maskCredentials(username,password);
16
+
17
+ let encodedCredentials = await this.maskCredentials(username, password);
18
18
 
19
19
  let config = {
20
20
  method: 'get',
@@ -23,13 +23,13 @@ class FinesseService{
23
23
  'Authorization': `Basic ${encodedCredentials}`
24
24
  },
25
25
  //disable ssl
26
- httpsAgent: new https.Agent({rejectUnauthorized: false})
26
+ httpsAgent: new https.Agent({ rejectUnauthorized: false })
27
27
  };
28
28
 
29
29
  try {
30
30
 
31
31
  let tokenResponse = await requestController.httpRequest(config, true);
32
-
32
+
33
33
  resolve({
34
34
  'status': tokenResponse.status
35
35
  });
@@ -37,14 +37,14 @@ class FinesseService{
37
37
  }
38
38
  catch (er) {
39
39
 
40
- if(er.code == "ENOTFOUND"){
40
+ if (er.code == "ENOTFOUND") {
41
41
 
42
42
  resolve({
43
43
  'finesse login status': 408,
44
44
  'finesse login message': `finesse server not accessible against URL: ${finesseUrl}`
45
45
  });
46
46
 
47
- }else if(er.response){
47
+ } else if (er.response) {
48
48
 
49
49
  resolve({
50
50
  'finesse login status': er.response.status,
@@ -52,16 +52,16 @@ class FinesseService{
52
52
  });
53
53
 
54
54
  }
55
-
55
+
56
56
  }
57
57
 
58
58
  });
59
59
  }
60
60
 
61
- async authenticateUserViaFinesseSSO(username,finesseToken,finesseUrl){
61
+ async authenticateUserViaFinesseSSO(username, finesseToken, finesseUrl) {
62
62
 
63
63
  return new Promise(async (resolve, reject) => {
64
-
64
+
65
65
  var URL = finesseUrl + '/finesse/api/User/' + username;
66
66
 
67
67
  let config = {
@@ -71,13 +71,13 @@ class FinesseService{
71
71
  'Authorization': `Bearer ${finesseToken}`
72
72
  },
73
73
  //disable ssl
74
- httpsAgent: new https.Agent({rejectUnauthorized: false})
74
+ httpsAgent: new https.Agent({ rejectUnauthorized: false })
75
75
  };
76
76
 
77
77
  try {
78
78
 
79
79
  let tokenResponse = await requestController.httpRequest(config, true);
80
-
80
+
81
81
  resolve({
82
82
  'status': tokenResponse.status
83
83
  });
@@ -85,14 +85,109 @@ class FinesseService{
85
85
  }
86
86
  catch (er) {
87
87
 
88
- if(er.code == "ENOTFOUND"){
88
+ if (er.code == "ENOTFOUND") {
89
+
90
+ resolve({
91
+ 'finesse login status': 408,
92
+ 'finesse login message': `finesse server not accessible against URL: ${finesseUrl}`
93
+ });
94
+
95
+ } else if (er.response) {
96
+
97
+ resolve({
98
+ 'finesse login status': er.response.status,
99
+ 'finesse login message': er.response.statusText
100
+ });
101
+
102
+ }
103
+ }
104
+
105
+ });
106
+ }
107
+
108
+ async getCiscoTeams(username, password, finesseUrl) {
109
+
110
+ return new Promise(async (resolve, reject) => {
111
+
112
+ var URL = finesseUrl + '/finesse/api/Teams';
113
+
114
+ let encodedCredentials = await this.maskCredentials(username, password);
115
+
116
+ let config = {
117
+ method: 'get',
118
+ url: URL,
119
+ headers: {
120
+ 'Authorization': `Basic ${encodedCredentials}`
121
+ },
122
+ //disable ssl
123
+ httpsAgent: new https.Agent({ rejectUnauthorized: false })
124
+ };
125
+
126
+ try {
127
+
128
+ let tokenResponse = await requestController.httpRequest(config, true);
129
+
130
+ resolve(tokenResponse.data);
131
+
132
+ }
133
+ catch (er) {
134
+
135
+ if (er.code == "ENOTFOUND") {
136
+
137
+ resolve({
138
+ 'finesse login status': 408,
139
+ 'finesse login message': `finesse server not accessible against URL: ${finesseUrl}`
140
+ });
141
+
142
+ } else if (er.response) {
143
+
144
+ resolve({
145
+ 'finesse login status': er.response.status,
146
+ 'finesse login message': er.response.statusText
147
+ });
148
+
149
+ }
150
+
151
+ }
152
+
153
+ });
154
+ }
155
+
156
+ async getCiscoUsers(username, password, finesseUrl) {
157
+
158
+ return new Promise(async (resolve, reject) => {
159
+
160
+ var URL = finesseUrl + '/finesse/api/Users';
161
+
162
+ let encodedCredentials = await this.maskCredentials(username, password);
163
+
164
+ let config = {
165
+ method: 'get',
166
+ url: URL,
167
+ headers: {
168
+ 'Authorization': `Basic ${encodedCredentials}`
169
+ },
170
+ //disable ssl
171
+ httpsAgent: new https.Agent({ rejectUnauthorized: false })
172
+ };
173
+
174
+ try {
175
+
176
+ let tokenResponse = await requestController.httpRequest(config, true);
177
+
178
+ resolve(tokenResponse.data);
179
+
180
+ }
181
+ catch (er) {
182
+
183
+ if (er.code == "ENOTFOUND") {
89
184
 
90
185
  resolve({
91
186
  'finesse login status': 408,
92
187
  'finesse login message': `finesse server not accessible against URL: ${finesseUrl}`
93
188
  });
94
189
 
95
- }else if(er.response){
190
+ } else if (er.response) {
96
191
 
97
192
  resolve({
98
193
  'finesse login status': er.response.status,
@@ -100,12 +195,13 @@ class FinesseService{
100
195
  });
101
196
 
102
197
  }
198
+
103
199
  }
104
200
 
105
201
  });
106
202
  }
107
203
 
108
- async maskCredentials(username, password){
204
+ async maskCredentials(username, password) {
109
205
 
110
206
  let token = Buffer.from(`${username}:${password}`, 'utf8').toString('base64');
111
207
  return token;
@@ -1,6 +1,7 @@
1
1
  var session = require("express-session");
2
2
  var Keycloak = require("keycloak-connect");
3
3
  const Joi = require('joi');
4
+ const parseXMLString = require('xml2js').parseString;
4
5
  var requestController = require("../controller/requestController.js");
5
6
  var memory = new session.MemoryStore();
6
7
  var keycloakConfig = null;
@@ -672,14 +673,16 @@ class KeycloakService extends Keycloak {
672
673
  if (userGroup.data.length != 0) {
673
674
 
674
675
 
675
- let group = userGroup.data;
676
+ let groups = userGroup.data;
676
677
  let userTeam = {};
677
678
  let supervisedTeams = [];
678
679
 
680
+ let filteredTeams = groups.filter(group => !group.name.includes('_permission'));
681
+
679
682
 
680
683
  userTeam = {
681
- 'teamId': group[0].id,
682
- 'teamName': group[0].name
684
+ 'teamId': filteredTeams[0].id,
685
+ 'teamName': filteredTeams[0].name
683
686
  }
684
687
 
685
688
  team.userTeam = userTeam;
@@ -700,7 +703,7 @@ class KeycloakService extends Keycloak {
700
703
 
701
704
  let result = await teamsService.getGroupByGroupID(group.id, username, token, keycloakConfig);
702
705
 
703
- if (result) {
706
+ if (result && !result.teamName.includes('_permission')) {
704
707
  supervisedTeams.push(result);
705
708
  }
706
709
  };
@@ -823,6 +826,7 @@ class KeycloakService extends Keycloak {
823
826
  delete config.url
824
827
 
825
828
  groupsData = groups.data;
829
+ groupsData = groupsData.filter(group => !group.name.includes('_permission'));
826
830
 
827
831
 
828
832
  } catch (er) {
@@ -845,6 +849,7 @@ class KeycloakService extends Keycloak {
845
849
  if (groupsIdsArr.length == 0) {
846
850
 
847
851
  let supervisedGroups = keycloakObj.supervisedTeams;
852
+ supervisedGroups = supervisedGroups.filter(group => !group.teamName.includes('_permission'));
848
853
  groupsData = supervisedGroups;
849
854
 
850
855
  //only send the users of provided groups.
@@ -871,6 +876,7 @@ class KeycloakService extends Keycloak {
871
876
  groupsArr.push(group);
872
877
  });
873
878
 
879
+ groupsArr = groupsArr.filter(group => !group.teamName.includes('_permission'));
874
880
  groupsData = groupsArr;
875
881
  }
876
882
  }
@@ -1183,6 +1189,97 @@ class KeycloakService extends Keycloak {
1183
1189
  });
1184
1190
  }
1185
1191
 
1192
+ async createFinesseUser(userObject, token) {
1193
+
1194
+ let assignRole = [];
1195
+
1196
+ return new Promise(async (resolve, reject) => {
1197
+
1198
+
1199
+ let URL = `${keycloakConfig["auth-server-url"]}${keycloakConfig["USERNAME_ADMIN"]}/realms/${keycloakConfig["realm"]}/users`;
1200
+
1201
+ let data = {
1202
+ username: userObject.username,
1203
+ firstName: userObject.firstName,
1204
+ lastName: userObject.lastName,
1205
+ enabled: true,
1206
+ credentials: [
1207
+ {
1208
+ type: 'password',
1209
+ value: '123456',
1210
+ temporary: false
1211
+ }
1212
+ ],
1213
+ groups: userObject.group
1214
+ }
1215
+
1216
+ let config = {
1217
+ method: 'post',
1218
+ url: URL,
1219
+ headers: {
1220
+ 'Content-Type': 'application/json',
1221
+ 'Authorization': `Bearer ${token}`
1222
+ },
1223
+ data: data
1224
+ };
1225
+
1226
+ try {
1227
+
1228
+ let tokenResponse = await requestController.httpRequest(config, false);
1229
+ let userRoles = userObject.roles;
1230
+
1231
+ if (userRoles != []) {
1232
+ //Get the user id at time of creation
1233
+ let userLocation = tokenResponse.headers.location;
1234
+ let userLocationSplit = userLocation.split("/");
1235
+ let userId = userLocationSplit[(userLocationSplit.length) - 1];
1236
+
1237
+
1238
+ //Get list of all the roles in keycloak realm
1239
+ let realmRoles = await this.getRealmRoles(token);
1240
+
1241
+ //checking whether role exist in realmRoles object array:
1242
+ for (let role of realmRoles.data) {
1243
+
1244
+ userRoles.forEach(userRole => {
1245
+
1246
+ if (role.name == userRole.toLocaleLowerCase()) {
1247
+
1248
+ assignRole.push({
1249
+ id: role.id,
1250
+ name: role.name
1251
+ });
1252
+ }
1253
+
1254
+ });
1255
+ }
1256
+
1257
+ //assigning role to user
1258
+ let roleAssigned = await this.assignRoleToUser(userId, assignRole, token);
1259
+
1260
+ //Role assigned with status
1261
+ if (roleAssigned.status == 204) {
1262
+ resolve(tokenResponse);
1263
+ }
1264
+
1265
+ } else {
1266
+
1267
+ resolve(tokenResponse);
1268
+
1269
+ }
1270
+
1271
+
1272
+ }
1273
+ catch (err) {
1274
+ reject({
1275
+ "status": err.response.status,
1276
+ "message": err.response.data.error_description
1277
+ });
1278
+ }
1279
+
1280
+ });
1281
+ }
1282
+
1186
1283
  async createGroup(adminToken, groupName) {
1187
1284
 
1188
1285
  return new Promise(async (resolve, reject) => {
@@ -1208,8 +1305,7 @@ class KeycloakService extends Keycloak {
1208
1305
  let createdGroup = await requestController.httpRequest(config, false);
1209
1306
  resolve(createdGroup.data);
1210
1307
 
1211
- }
1212
- catch (err) {
1308
+ } catch (err) {
1213
1309
  reject({
1214
1310
  "status": err.response.status,
1215
1311
  "message": err.response.data.error_description
@@ -1220,6 +1316,169 @@ class KeycloakService extends Keycloak {
1220
1316
 
1221
1317
  }
1222
1318
 
1319
+ async syncCiscoTeams(administratorUsername, administratorPassword, finesseUrl) {
1320
+
1321
+ return new Promise(async (resolve, reject) => {
1322
+
1323
+ //admin token url
1324
+ let token;
1325
+ let createdGroups;
1326
+ let groupSupervisors = [];
1327
+ var URL = keycloakConfig["auth-server-url"] + 'realms/' + keycloakConfig.realm + '/protocol/openid-connect/token';
1328
+
1329
+ //request config to fetch admin token
1330
+ var config = {
1331
+ method: 'post',
1332
+ url: URL,
1333
+ headers: {
1334
+ 'Accept': 'application/json',
1335
+ 'cache-control': 'no-cache',
1336
+ 'Content-Type': 'application/x-www-form-urlencoded'
1337
+ },
1338
+ data: {
1339
+ client_id: keycloakConfig.CLIENT_ID,
1340
+ username: keycloakConfig.USERNAME_ADMIN,
1341
+ password: keycloakConfig.PASSWORD_ADMIN,
1342
+ grant_type: keycloakConfig.GRANT_TYPE,
1343
+ client_secret: keycloakConfig.credentials.secret
1344
+ }
1345
+ };
1346
+
1347
+ try {
1348
+
1349
+ //http request to fetch admin token
1350
+ let adminTokenResponse = await requestController.httpRequest(config, true);
1351
+ token = adminTokenResponse.data.access_token;
1352
+
1353
+ //Service to get all existing keycloak groups
1354
+ let groupsList = await teamsService.getGroupsList(token, keycloakConfig);
1355
+ let groupNames = null;
1356
+
1357
+ if (groupsList != null) {
1358
+ groupNames = groupsList.map(group => group.name);
1359
+ }
1360
+
1361
+ //service to get all the teams from Cisco Finesse
1362
+ let teamsList = await finesseService.getCiscoTeams(administratorUsername, administratorPassword, finesseUrl);
1363
+
1364
+ //Converting the XML String returned from Finesse to JS Object
1365
+ parseXMLString(teamsList, async (err, result) => {
1366
+ if (err) {
1367
+ console.error(err);
1368
+ } else {
1369
+
1370
+ //iterating through the Cisco finesse teams
1371
+ for (let team of result.Teams.Team) {
1372
+
1373
+ //Converting both finesse teams and keycloak teams to lowercase for non case sensitive comparison
1374
+ let isExist = groupNames.map(group => group.toLowerCase()).includes((team.name[0]).toLowerCase());
1375
+
1376
+ //Creating new group on keycloak side against finesse team if doesn't exist already.
1377
+ if (!isExist) {
1378
+ let newGroup = await this.createGroup(token, team.name[0]);
1379
+ }
1380
+
1381
+ }
1382
+
1383
+ //Get the list of newly created groups in keycloak. We use it later to add supervisor to group
1384
+ groupsList = await teamsService.getGroupsList(token, keycloakConfig);
1385
+ groupNames = null;
1386
+
1387
+ if (groupsList != null) {
1388
+ groupNames = groupsList.map(group => {
1389
+ return {
1390
+ id: group.id,
1391
+ name: group.name
1392
+ }
1393
+ });
1394
+ }
1395
+
1396
+ //Service to get all existing keycloak users
1397
+ let usersList = await teamsService.getUsersList(token, keycloakConfig);
1398
+ let userNames = null;
1399
+
1400
+ if (usersList != null) {
1401
+ userNames = usersList.map(user => user.username);
1402
+ }
1403
+
1404
+ //service to get existing users list from Cisco Finesse
1405
+ let ciscoUsersList = await finesseService.getCiscoUsers(administratorUsername, administratorPassword, finesseUrl);
1406
+
1407
+ parseXMLString(ciscoUsersList, async (err, result) => {
1408
+
1409
+ if (err) {
1410
+ console.error(err);
1411
+ } else {
1412
+
1413
+ for (let user of result.Users.User) {
1414
+
1415
+ //Converting both finesse user and keycloak user to lowercase for non case sensitive comparison
1416
+ let isExist = userNames.map(user => user.toLowerCase()).includes((user.loginName[0]).toLowerCase());
1417
+
1418
+ //Creating new user on keycloak side against finesse user if doesn't exist already.
1419
+ if (!isExist) {
1420
+
1421
+ let userObject = {
1422
+ username: user.loginName[0],
1423
+ firstName: user.firstName[0],
1424
+ lastName: user.lastName[0],
1425
+ roles: (user.roles[0].role).map(role => role.toLowerCase()),
1426
+ group: (user.teamName == '' || user.teamName == null) ? ['Default'] : user.teamName
1427
+ }
1428
+
1429
+ if (user.teams) {
1430
+ if (user.teams[0].Team) {
1431
+ userObject.supervisedGroups = (user.teams[0].Team).map(team => team.name[0]);
1432
+
1433
+
1434
+ userObject.supervisedGroups.forEach(groupName => {
1435
+
1436
+
1437
+ let keycloakGroupToSupervise = groupNames.find(group => group.name == groupName);
1438
+ let isExistGroup = groupSupervisors.findIndex(group => group.name == groupName);
1439
+
1440
+ if (isExistGroup == -1) {
1441
+ groupSupervisors.push({
1442
+ id: keycloakGroupToSupervise.id,
1443
+ name: keycloakGroupToSupervise.name,
1444
+ supervisors: [`${userObject.username}`]
1445
+ })
1446
+ } else {
1447
+ groupSupervisors[isExistGroup].supervisors[0] += `,${userObject.username}`;
1448
+ }
1449
+ });
1450
+
1451
+ }
1452
+ }
1453
+
1454
+ let newUser = await this.createFinesseUser(userObject, token);
1455
+ }
1456
+
1457
+ }
1458
+
1459
+ if (groupSupervisors.length != 0) {
1460
+ //Assigning Groups to Supervisors
1461
+ let assignedSupervisors = await teamsService.addSupervisorToGroup(groupSupervisors, token, keycloakConfig);
1462
+ }
1463
+
1464
+ }
1465
+
1466
+ });
1467
+
1468
+
1469
+ resolve('All Groups Created');
1470
+ }
1471
+ });
1472
+
1473
+
1474
+ } catch (er) {
1475
+
1476
+ let error = await this.checkErrorType(er);
1477
+ reject(error);
1478
+ }
1479
+ });
1480
+ }
1481
+
1223
1482
  //Authenticating Finesse User
1224
1483
  async authenticateFinesse(username, password, finesseUrl, userRoles, finesseToken) {
1225
1484
 
@@ -137,6 +137,126 @@ class TeamsService {
137
137
  }
138
138
  });
139
139
  }
140
+
141
+ async getGroupsList(token, keycloakConfig) {
142
+ return new Promise(async (resolve, reject) => {
143
+
144
+ try {
145
+
146
+ let URL = keycloakConfig["auth-server-url"] + 'admin/realms/' + keycloakConfig.realm + '/groups';
147
+
148
+ var config = {
149
+ method: 'get',
150
+ url: URL,
151
+ headers: {
152
+ 'Accept': 'application/json',
153
+ 'cache-control': 'no-cache',
154
+ 'Content-Type': 'application/x-www-form-urlencoded'
155
+ },
156
+ headers: {
157
+ 'Authorization': 'Bearer ' + token
158
+ }
159
+ };
160
+
161
+ try {
162
+
163
+ let groups = await requestController.httpRequest(config, false);
164
+ let groupsList = groups.data;
165
+
166
+ resolve(groupsList);
167
+
168
+ } catch (error) {
169
+ reject(error);
170
+ }
171
+ } catch (er) {
172
+ reject("error" + er);
173
+ };
174
+ });
175
+ }
176
+
177
+ async getUsersList(token, keycloakConfig) {
178
+ return new Promise(async (resolve, reject) => {
179
+
180
+ try {
181
+
182
+ let URL = keycloakConfig["auth-server-url"] + 'admin/realms/' + keycloakConfig.realm + '/users?max=10000';
183
+
184
+ var config = {
185
+ method: 'get',
186
+ url: URL,
187
+ headers: {
188
+ 'Accept': 'application/json',
189
+ 'cache-control': 'no-cache',
190
+ 'Content-Type': 'application/x-www-form-urlencoded'
191
+ },
192
+ headers: {
193
+ 'Authorization': 'Bearer ' + token
194
+ }
195
+ };
196
+
197
+ try {
198
+
199
+ let users = await requestController.httpRequest(config, false);
200
+ let usersList = users.data;
201
+
202
+ resolve(usersList);
203
+
204
+ } catch (error) {
205
+ reject(error);
206
+ }
207
+ } catch (er) {
208
+ reject("error" + er);
209
+ };
210
+ });
211
+ }
212
+
213
+ async addSupervisorToGroup(supervisedGroups, token, keycloakConfig) {
214
+
215
+ let updatedGroups;
216
+
217
+ return new Promise(async (resolve, reject) => {
218
+
219
+ try {
220
+
221
+ supervisedGroups.forEach(async group => {
222
+
223
+ let URL = keycloakConfig["auth-server-url"] + 'admin/realms/' + keycloakConfig.realm + '/groups/' + group.id;
224
+
225
+ var config = {
226
+ method: 'put',
227
+ url: URL,
228
+ headers: {
229
+ 'Accept': 'application/json',
230
+ 'cache-control': 'no-cache',
231
+ 'Content-Type': 'application/x-www-form-urlencoded'
232
+ },
233
+ headers: {
234
+ 'Authorization': 'Bearer ' + token
235
+ },
236
+ data: {
237
+ name: group.name,
238
+ attributes: {
239
+ supervisor: group.supervisors
240
+ }
241
+ }
242
+ };
243
+
244
+ try {
245
+
246
+ let groups = await requestController.httpRequest(config, false);
247
+
248
+ } catch (error) {
249
+ reject(error);
250
+ }
251
+ });
252
+
253
+ resolve('Supervisors Added');
254
+
255
+ } catch (er) {
256
+ reject("error" + er);
257
+ };
258
+ });
259
+ }
140
260
  }
141
261
 
142
262
  module.exports = TeamsService;