ef-keycloak-connect 1.8.4-patch-2.0 → 1.8.6
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/README.md +13 -0
- package/package.json +1 -1
- package/services/dynamics365Service.js +109 -0
- package/services/keycloakService.js +564 -116
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ This adapter is extended from keycloak-connect and have functionalities of both
|
|
|
41
41
|
- assignRoleToUser
|
|
42
42
|
- authenticateFinesse
|
|
43
43
|
- createRealmAsTenant
|
|
44
|
+
- dynamics365Sso
|
|
44
45
|
|
|
45
46
|
```
|
|
46
47
|
### Example
|
|
@@ -445,6 +446,9 @@ It takes 5 arguments:
|
|
|
445
446
|
- finesse_server_url: The url of finesse server (In case of normal keycloak auth, send this parameter as **' '**)
|
|
446
447
|
- user_roles: The array containing user_roles, it will be used to assign roles to finesse user while synching it with Keycloak (for normal auth send it as [ ]).
|
|
447
448
|
- finesse_token: acess token for finesse SSO authentication (It will be passed if Finesse SSO instance is connected, in case of non SSO will pass empty string **' '** as argument)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
|
|
448
452
|
|
|
449
453
|
##### Example of SSO Finesse Auth:
|
|
450
454
|
|
|
@@ -455,6 +459,15 @@ It takes 5 arguments:
|
|
|
455
459
|
|
|
456
460
|
authenticateFinesse('johndoe', '12345', `https://${finesse_server_url}:${port}`, ['agent','supervisor'], '')
|
|
457
461
|
|
|
462
|
+
### dynamics365Sso( userRoles, validationToken, dynamics365Url )
|
|
463
|
+
|
|
464
|
+
This function sync microsoft dynamics 365 user in keycloak, it first authenticates user from dynamics365, then check for its existance in keycloak. If it exists in keycloak then generates an access_token along with role mapping and team mapping and return it to user. If user doesn't exist then it creates a user, assign it roles and team and return the access_token along with role mapping/team mapping for newly created user.
|
|
465
|
+
|
|
466
|
+
It takes 3 arguments:
|
|
467
|
+
- userRoles: The array containing user roles, it will be used to assign roles to dynamics365 user while synching it with Keycloak e.g **['agent']**.
|
|
468
|
+
- validationToken: acess token for dynamics365 user validation and to get user details.
|
|
469
|
+
- dynamics365Url: The url of dynamics365 server e.g **'https://{fqdn}/api/data/v9.0'**
|
|
470
|
+
|
|
458
471
|
### generateAccessTokenFromRefreshToken(refreshToken)
|
|
459
472
|
|
|
460
473
|
This function generates a new access_token by using the refreshToken received in parameter.
|
package/package.json
CHANGED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const parseXMLString = require( 'xml2js' ).parseString;
|
|
2
|
+
const https = require( 'https' );
|
|
3
|
+
|
|
4
|
+
let requestController = require( "../controller/requestController.js" );
|
|
5
|
+
|
|
6
|
+
class Dynamics365Service {
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async authenticateUserViaDynamics365( validationToken, dynamics365Url ) {
|
|
10
|
+
|
|
11
|
+
return new Promise( async ( resolve, reject ) => {
|
|
12
|
+
|
|
13
|
+
let URL = dynamics365Url + '/WhoAmI'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
let config = {
|
|
17
|
+
method: 'get',
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'Accept': 'application/json',
|
|
20
|
+
'OData-MaxVersion': '4.0',
|
|
21
|
+
'OData-Version': '4.0',
|
|
22
|
+
url: URL,
|
|
23
|
+
headers: {
|
|
24
|
+
'Authorization': `Bearer ${validationToken}`
|
|
25
|
+
},
|
|
26
|
+
//disable ssl
|
|
27
|
+
httpsAgent: new https.Agent( { rejectUnauthorized: false } )
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
|
|
32
|
+
let whoAmIResponse = await requestController.httpRequest( config, false );
|
|
33
|
+
|
|
34
|
+
let userId = whoAmIResponse?.data?.UserId;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
|
|
38
|
+
let URL1 = `${dynamics365Url}/systemusers(${userId})?$select=fullname,firstname,middlename,lastname,internalemailaddress,title,isdisabled,businessunitid,mobilephone,createdon,modifiedon`
|
|
39
|
+
config.url = URL1;
|
|
40
|
+
config.maxBodyLength = 'Infinity';
|
|
41
|
+
|
|
42
|
+
let userObjectResponse = await requestController.httpRequest( config, true );
|
|
43
|
+
let userObject = userObjectResponse?.data;
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
resolve( {
|
|
47
|
+
'data': userObject,
|
|
48
|
+
'status': userObjectResponse?.status
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
catch ( er ) {
|
|
53
|
+
|
|
54
|
+
if ( er.code == "ENOTFOUND" ) {
|
|
55
|
+
|
|
56
|
+
reject( {
|
|
57
|
+
error_message: "Dynamics365 Authentication Error: An error occurred while getting the user data from Dynamics365.",
|
|
58
|
+
error_detail: {
|
|
59
|
+
status: 408,
|
|
60
|
+
reason: `Dynamics365 server not accessible against URL: ${dynamics365Url}`
|
|
61
|
+
}
|
|
62
|
+
} )
|
|
63
|
+
|
|
64
|
+
} else if ( er.response ) {
|
|
65
|
+
|
|
66
|
+
reject( {
|
|
67
|
+
error_message: "Dynamics365 Authentication Error: An error occurred while getting the user data from Dynamics365.",
|
|
68
|
+
error_detail: {
|
|
69
|
+
status: er.response.status,
|
|
70
|
+
reason: er.response.statusText
|
|
71
|
+
}
|
|
72
|
+
} )
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
catch ( er ) {
|
|
80
|
+
|
|
81
|
+
if ( er.code == "ENOTFOUND" ) {
|
|
82
|
+
|
|
83
|
+
reject( {
|
|
84
|
+
error_message: "Dynamics365 Authentication Error: An error occurred while validating user in Dynamics365.",
|
|
85
|
+
error_detail: {
|
|
86
|
+
status: 408,
|
|
87
|
+
reason: `Finesse server not accessible against URL: ${dynamics365Url}`
|
|
88
|
+
}
|
|
89
|
+
} )
|
|
90
|
+
|
|
91
|
+
} else if ( er.response ) {
|
|
92
|
+
|
|
93
|
+
reject( {
|
|
94
|
+
error_message: "Dynamics365 Authentication Error: An error occurred while validating user in Dynamics365.",
|
|
95
|
+
error_detail: {
|
|
96
|
+
status: er.response.status,
|
|
97
|
+
reason: er.response.statusText
|
|
98
|
+
}
|
|
99
|
+
} )
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
} );
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = Dynamics365Service;
|
|
@@ -17,6 +17,7 @@ const FinesseService = require( "./finesseService" );
|
|
|
17
17
|
const TeamsService = require( "./teamsService" );
|
|
18
18
|
const ErrorService = require( './errorService.js' );
|
|
19
19
|
const CiscoSyncService = require( './ciscoSyncService.js' );
|
|
20
|
+
const Dynamics365Service = require( './dynamics365Service.js' );
|
|
20
21
|
|
|
21
22
|
const twilio = require( 'twilio' )
|
|
22
23
|
let twilioClient = null // will be initialized in constructor using config file
|
|
@@ -25,6 +26,7 @@ const finesseService = new FinesseService();
|
|
|
25
26
|
const teamsService = new TeamsService();
|
|
26
27
|
const errorService = new ErrorService();
|
|
27
28
|
const ciscoSyncService = new CiscoSyncService();
|
|
29
|
+
const dynamics365Service = new Dynamics365Service();
|
|
28
30
|
|
|
29
31
|
class KeycloakService extends Keycloak {
|
|
30
32
|
|
|
@@ -2704,24 +2706,27 @@ class KeycloakService extends Keycloak {
|
|
|
2704
2706
|
|
|
2705
2707
|
let data = {
|
|
2706
2708
|
|
|
2707
|
-
username: userObject
|
|
2708
|
-
firstName: userObject
|
|
2709
|
-
lastName: userObject
|
|
2709
|
+
username: ( userObject?.type == "dynamics365" ) ? userObject?.fullname : userObject?.username,
|
|
2710
|
+
firstName: ( userObject?.type == "dynamics365" ) ? userObject?.firstname : userObject?.firstName,
|
|
2711
|
+
lastName: ( userObject?.type == "dynamics365" ) ? userObject?.lastname : userObject?.lastName,
|
|
2712
|
+
email: ( userObject?.type == "dynamics365" ) ? userObject?.internalemailaddress : '',
|
|
2710
2713
|
enabled: true,
|
|
2711
2714
|
credentials: [
|
|
2712
2715
|
{
|
|
2713
2716
|
type: "password",
|
|
2714
|
-
value: userObject
|
|
2717
|
+
value: userObject?.password,
|
|
2715
2718
|
temporary: false,
|
|
2716
2719
|
},
|
|
2717
2720
|
],
|
|
2718
2721
|
attributes: {
|
|
2719
|
-
"user_name": `${userObject
|
|
2720
|
-
"extension": `${userObject
|
|
2722
|
+
"user_name": `${( userObject?.loginName ) ? userObject?.loginName : ''}`,
|
|
2723
|
+
"extension": `${( userObject?.extension ) ? userObject?.extension : 'CISCO'}`
|
|
2721
2724
|
},
|
|
2722
2725
|
groups: assignGroups
|
|
2723
2726
|
};
|
|
2724
2727
|
|
|
2728
|
+
userObject?.type == "dynamics365" && ( data.attributes.userid = userObject?.ownerid );
|
|
2729
|
+
|
|
2725
2730
|
let config = {
|
|
2726
2731
|
|
|
2727
2732
|
method: "post",
|
|
@@ -2795,7 +2800,96 @@ class KeycloakService extends Keycloak {
|
|
|
2795
2800
|
}
|
|
2796
2801
|
|
|
2797
2802
|
|
|
2798
|
-
|
|
2803
|
+
if ( userObject?.type == "dynamics365" ) {
|
|
2804
|
+
|
|
2805
|
+
let tempUrl1 = `${this.keycloakConfig[ "ef-server-url" ]}team`;
|
|
2806
|
+
|
|
2807
|
+
let tempConfig1 = {
|
|
2808
|
+
|
|
2809
|
+
url: tempUrl1,
|
|
2810
|
+
method: "get",
|
|
2811
|
+
headers: {
|
|
2812
|
+
Accept: "application/json",
|
|
2813
|
+
"cache-control": "no-cache",
|
|
2814
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
};
|
|
2818
|
+
|
|
2819
|
+
try {
|
|
2820
|
+
|
|
2821
|
+
let getTeamsList = await requestController.httpRequest( tempConfig1, false );
|
|
2822
|
+
|
|
2823
|
+
// Access the actual array of teams from the .data property.
|
|
2824
|
+
const teamsData = getTeamsList?.data;
|
|
2825
|
+
|
|
2826
|
+
// Check if the teamsData array is empty or not an array.
|
|
2827
|
+
if ( !Array.isArray( teamsData ) || teamsData.length === 0 ) {
|
|
2828
|
+
|
|
2829
|
+
console.log( "teamsData array is empty or invalid. Assigning 1 to userObject.group.id." );
|
|
2830
|
+
userObject.group.id = 1;
|
|
2831
|
+
|
|
2832
|
+
} else {
|
|
2833
|
+
|
|
2834
|
+
const findDefaultTeamId = ( teamsArray ) => {
|
|
2835
|
+
|
|
2836
|
+
// Rule 1: Find a team with team_name "default" (case-insensitive).
|
|
2837
|
+
const defaultTeam = teamsArray.find(
|
|
2838
|
+
( team ) => team.team_name && team.team_name.toLowerCase() === "default"
|
|
2839
|
+
);
|
|
2840
|
+
|
|
2841
|
+
if ( defaultTeam ) {
|
|
2842
|
+
console.log( 'Rule 1 matched: Found team with name "default".' );
|
|
2843
|
+
return defaultTeam.team_Id;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
console.log( 'Rule 1 failed: No team with name "default" found.' );
|
|
2847
|
+
|
|
2848
|
+
// Rule 2: If no "default" team, find a team with team_Id of 1 whose name is NOT "default".
|
|
2849
|
+
const teamWithIdOne = teamsArray.find(
|
|
2850
|
+
( team ) =>
|
|
2851
|
+
( team.team_Id === 1 || team.team_Id === "1" ) &&
|
|
2852
|
+
team.team_name?.toLowerCase() !== "default"
|
|
2853
|
+
);
|
|
2854
|
+
|
|
2855
|
+
if ( teamWithIdOne ) {
|
|
2856
|
+
console.log( "Rule 2 matched: Found a team with team_Id of 1 and a non-default name." );
|
|
2857
|
+
return crypto.randomUUID();
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
console.log( "Rule 2 failed: No suitable team with team_Id of 1 found." );
|
|
2861
|
+
|
|
2862
|
+
// Rule 3: If no default team was found and no suitable team with ID 1 was found, return a random ID.
|
|
2863
|
+
console.log( "Rule 3 matched: Defaulting to a new random UUID." );
|
|
2864
|
+
return 1;
|
|
2865
|
+
};
|
|
2866
|
+
|
|
2867
|
+
// Call the function with the fetched list of teams
|
|
2868
|
+
const defaultTeamId = findDefaultTeamId( teamsData );
|
|
2869
|
+
|
|
2870
|
+
// Assign the resulting ID to the userObject
|
|
2871
|
+
userObject.groupId = defaultTeamId;
|
|
2872
|
+
|
|
2873
|
+
// You can now use the 'defaultTeamId' variable and the updated userObject
|
|
2874
|
+
console.log( `The determined default Team ID is: ${defaultTeamId}` );
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
} catch ( er ) {
|
|
2878
|
+
|
|
2879
|
+
let error = await errorService.handleError( er );
|
|
2880
|
+
|
|
2881
|
+
reject( {
|
|
2882
|
+
|
|
2883
|
+
error_message: "Dynamics365 Team Sync Error: Error occured while checking for default team in CX.",
|
|
2884
|
+
error_detail: error
|
|
2885
|
+
} );
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
( userObject?.type == "dynamics365" ) && ( userObject.groupName = "default" );
|
|
2891
|
+
|
|
2892
|
+
let ciscoTeamId = ( userObject?.type == "dynamics365" ) ? userObject.groupId : userObject.group.id;
|
|
2799
2893
|
|
|
2800
2894
|
//Check whether team of Agent already exists in CX Core or not
|
|
2801
2895
|
let URL1 = `${this.keycloakConfig[ "ef-server-url" ]}team?ids=${ciscoTeamId}`;
|
|
@@ -2826,7 +2920,7 @@ class KeycloakService extends Keycloak {
|
|
|
2826
2920
|
|
|
2827
2921
|
try {
|
|
2828
2922
|
|
|
2829
|
-
let getAgentCXTeam = await requestController.httpRequest( config1,
|
|
2923
|
+
let getAgentCXTeam = await requestController.httpRequest( config1, true );
|
|
2830
2924
|
|
|
2831
2925
|
let createAgentCXTeam;
|
|
2832
2926
|
|
|
@@ -2837,8 +2931,8 @@ class KeycloakService extends Keycloak {
|
|
|
2837
2931
|
let URL2 = `${this.keycloakConfig[ "ef-server-url" ]}team`;
|
|
2838
2932
|
|
|
2839
2933
|
let data = {
|
|
2840
|
-
"team_Id": userObject.group.id,
|
|
2841
|
-
"team_name": userObject.group.name,
|
|
2934
|
+
"team_Id": ( userObject?.type == "dynamics365" ) ? ciscoTeamId : userObject.group.id,
|
|
2935
|
+
"team_name": ( userObject?.type == "dynamics365" ) ? userObject?.groupName : userObject.group.name,
|
|
2842
2936
|
"supervisor_Id": "",
|
|
2843
2937
|
"source": "CISCO",
|
|
2844
2938
|
"created_by": "1"
|
|
@@ -2847,10 +2941,13 @@ class KeycloakService extends Keycloak {
|
|
|
2847
2941
|
config2.url = URL2;
|
|
2848
2942
|
config2.data = data;
|
|
2849
2943
|
|
|
2944
|
+
console.log( 'create team if does not exist ' )
|
|
2945
|
+
|
|
2850
2946
|
try {
|
|
2851
2947
|
|
|
2852
2948
|
//Creating CX team of Agent
|
|
2853
|
-
createAgentCXTeam = await requestController.httpRequest( config2,
|
|
2949
|
+
createAgentCXTeam = await requestController.httpRequest( config2, true );
|
|
2950
|
+
console.log( createAgentCXTeam );
|
|
2854
2951
|
|
|
2855
2952
|
} catch ( er ) {
|
|
2856
2953
|
|
|
@@ -2870,18 +2967,57 @@ class KeycloakService extends Keycloak {
|
|
|
2870
2967
|
|
|
2871
2968
|
let data = {
|
|
2872
2969
|
"id": userId,
|
|
2873
|
-
"username": userObject
|
|
2874
|
-
"firstName": userObject.firstName,
|
|
2875
|
-
"lastName": userObject.lastName,
|
|
2970
|
+
"username": ( userObject?.type == "dynamics365" ) ? userObject?.fullname : userObject?.username.toLocaleLowerCase(),
|
|
2971
|
+
"firstName": ( userObject?.type == "dynamics365" ) ? userObject.firstname : userObject.firstName,
|
|
2972
|
+
"lastName": ( userObject?.type == "dynamics365" ) ? userObject.lastname : userObject.lastName,
|
|
2876
2973
|
"roles": userObject.roles
|
|
2877
2974
|
}
|
|
2878
2975
|
|
|
2879
2976
|
config2.url = URL3;
|
|
2880
2977
|
config2.data = data;
|
|
2881
2978
|
|
|
2979
|
+
console.log( 'send data to db' );
|
|
2980
|
+
|
|
2882
2981
|
try {
|
|
2883
2982
|
|
|
2884
|
-
let sendSupUserToCX =
|
|
2983
|
+
let sendSupUserToCX = requestController.httpRequest( config2, false ).then( async res => {
|
|
2984
|
+
|
|
2985
|
+
//Assign Agent to a team
|
|
2986
|
+
let URL4 = `${this.keycloakConfig[ "ef-server-url" ]}team/${ciscoTeamId}/member`;
|
|
2987
|
+
|
|
2988
|
+
data = {
|
|
2989
|
+
"type": "agent",
|
|
2990
|
+
"usernames": [ ( userObject?.type == "dynamics365" ) ? userObject?.fullname : userObject?.username.toLocaleLowerCase() ]
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
config2.url = URL4;
|
|
2994
|
+
config2.data = data;
|
|
2995
|
+
|
|
2996
|
+
console.log( 'adding user as member of team' );
|
|
2997
|
+
|
|
2998
|
+
try {
|
|
2999
|
+
|
|
3000
|
+
//Assigning Agent to CX team
|
|
3001
|
+
let assignAgentToTeam = await requestController.httpRequest( config2, false );
|
|
3002
|
+
return assignAgentToTeam;
|
|
3003
|
+
|
|
3004
|
+
} catch ( er ) {
|
|
3005
|
+
|
|
3006
|
+
let error = await errorService.handleError( er );
|
|
3007
|
+
|
|
3008
|
+
return reject( {
|
|
3009
|
+
|
|
3010
|
+
error_message: "Finesse Team Sync Error: Error occured while assigning agent to cx core team.",
|
|
3011
|
+
error_detail: error
|
|
3012
|
+
} );
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
|
|
3016
|
+
|
|
3017
|
+
} ).catch( err => {
|
|
3018
|
+
console.log( err );
|
|
3019
|
+
|
|
3020
|
+
} );
|
|
2885
3021
|
|
|
2886
3022
|
} catch ( er ) {
|
|
2887
3023
|
|
|
@@ -2894,21 +3030,24 @@ class KeycloakService extends Keycloak {
|
|
|
2894
3030
|
} );
|
|
2895
3031
|
}
|
|
2896
3032
|
|
|
2897
|
-
//Assign Agent to a team
|
|
2898
|
-
let URL4 = `${this.keycloakConfig[ "ef-server-url" ]}team/${
|
|
3033
|
+
/* //Assign Agent to a team
|
|
3034
|
+
let URL4 = `${this.keycloakConfig[ "ef-server-url" ]}team/${ciscoTeamId}/member`;
|
|
2899
3035
|
|
|
2900
3036
|
data = {
|
|
2901
3037
|
"type": "agent",
|
|
2902
|
-
"usernames": [ userObject
|
|
3038
|
+
"usernames": [ ( userObject?.type == "dynamics365" ) ? userObject?.fullname : userObject?.username.toLocaleLowerCase() ]
|
|
2903
3039
|
}
|
|
2904
3040
|
|
|
2905
3041
|
config2.url = URL4;
|
|
2906
3042
|
config2.data = data;
|
|
2907
3043
|
|
|
3044
|
+
console.log( 'adding user as member of team' );
|
|
3045
|
+
console.log( config2 );
|
|
3046
|
+
|
|
2908
3047
|
try {
|
|
2909
3048
|
|
|
2910
3049
|
//Assigning Agent to CX team
|
|
2911
|
-
let assignAgentToTeam = await requestController.httpRequest( config2,
|
|
3050
|
+
let assignAgentToTeam = await requestController.httpRequest( config2, true );
|
|
2912
3051
|
|
|
2913
3052
|
} catch ( er ) {
|
|
2914
3053
|
|
|
@@ -2919,7 +3058,7 @@ class KeycloakService extends Keycloak {
|
|
|
2919
3058
|
error_message: "Finesse Team Sync Error: Error occured while assigning agent to cx core team.",
|
|
2920
3059
|
error_detail: error
|
|
2921
3060
|
} );
|
|
2922
|
-
}
|
|
3061
|
+
}*/
|
|
2923
3062
|
|
|
2924
3063
|
} catch ( er ) {
|
|
2925
3064
|
|
|
@@ -2934,7 +3073,7 @@ class KeycloakService extends Keycloak {
|
|
|
2934
3073
|
|
|
2935
3074
|
|
|
2936
3075
|
|
|
2937
|
-
if ( userObject.roles.includes( "supervisor" ) && userObject.supervisedGroups.length > 0 ) {
|
|
3076
|
+
if ( userObject.roles.includes( "supervisor" ) && userObject.supervisedGroups.length > 0 && userObject?.type !== "dynamics365" ) {
|
|
2938
3077
|
|
|
2939
3078
|
for ( let supervisedGroup of userObject.supervisedGroups ) {
|
|
2940
3079
|
|
|
@@ -3120,12 +3259,14 @@ class KeycloakService extends Keycloak {
|
|
|
3120
3259
|
let rptToken = await this.getTokenRPT( username, password, keycloakAuthToken.access_token );
|
|
3121
3260
|
let introspectToken = await this.getIntrospectToken( rptToken.access_token );
|
|
3122
3261
|
|
|
3262
|
+
|
|
3123
3263
|
let keyObj = {
|
|
3124
3264
|
id: introspectToken.sub,
|
|
3125
3265
|
username: introspectToken.username,
|
|
3126
3266
|
firstName: introspectToken.given_name,
|
|
3127
3267
|
lastName: introspectToken.family_name,
|
|
3128
3268
|
roles: introspectToken.realm_access.roles,
|
|
3269
|
+
email: introspectToken.email,
|
|
3129
3270
|
permittedResources: {
|
|
3130
3271
|
Resources: introspectToken.authorization.permissions,
|
|
3131
3272
|
}
|
|
@@ -3147,7 +3288,7 @@ class KeycloakService extends Keycloak {
|
|
|
3147
3288
|
try {
|
|
3148
3289
|
|
|
3149
3290
|
let userDataResponse = await requestController.httpRequest( config, false );
|
|
3150
|
-
userAttributes = userDataResponse
|
|
3291
|
+
userAttributes = userDataResponse?.data?.attributes;
|
|
3151
3292
|
|
|
3152
3293
|
} catch ( err ) {
|
|
3153
3294
|
|
|
@@ -3161,24 +3302,34 @@ class KeycloakService extends Keycloak {
|
|
|
3161
3302
|
|
|
3162
3303
|
}
|
|
3163
3304
|
|
|
3305
|
+
finObj.username = ( finObj?.type == "dynamics365" ) ? finObj?.fullname : finObj?.username;
|
|
3306
|
+
finObj.firstName = ( finObj?.type == "dynamics365" ) ? finObj?.firstname : finObj?.firstName;
|
|
3307
|
+
finObj.lastName = ( finObj?.type == "dynamics365" ) ? finObj?.lastname : finObj?.lastName;
|
|
3308
|
+
finObj.email = ( finObj?.type == "dynamics365" ) ? finObj?.internalemailaddress : '';
|
|
3309
|
+
|
|
3164
3310
|
//Comparing the basic info of Finesse User and Normal User.
|
|
3165
3311
|
if ( ( finObj.username ).toLowerCase() != keyObj.username
|
|
3166
3312
|
|| finObj.firstName != keyObj.firstName
|
|
3167
3313
|
|| finObj.lastName != keyObj.lastName
|
|
3168
|
-
||
|
|
3169
|
-
|| ( userAttributes.
|
|
3170
|
-
|| (
|
|
3314
|
+
|| finObj.email !== keyObj.email
|
|
3315
|
+
|| ( userAttributes.user_name && finObj?.loginName !== userAttributes?.user_name[ 0 ] )
|
|
3316
|
+
|| ( userAttributes.extension && finObj?.extension !== userAttributes?.extension[ 0 ] )
|
|
3317
|
+
|| ( userAttributes.userid && finObj?.ownerid !== userAttributes?.userid )
|
|
3318
|
+
|| ( finObj?.type !== "dynamics365" && !userAttributes.user_name )
|
|
3171
3319
|
) {
|
|
3172
3320
|
|
|
3173
3321
|
data = {
|
|
3174
|
-
username: ( finObj
|
|
3175
|
-
firstName: finObj
|
|
3176
|
-
lastName: finObj
|
|
3322
|
+
username: ( finObj?.username ).toLowerCase(),
|
|
3323
|
+
firstName: finObj?.firstName,
|
|
3324
|
+
lastName: finObj?.lastName,
|
|
3325
|
+
email: finObj?.email,
|
|
3177
3326
|
attributes: {
|
|
3178
|
-
"user_name": `${finObj
|
|
3179
|
-
"extension": `${finObj
|
|
3327
|
+
"user_name": `${( finObj?.loginName ) ? finObj?.loginName : ''}`,
|
|
3328
|
+
"extension": `${( finObj?.extension ) ? finObj?.extension : 'CISCO'}`
|
|
3180
3329
|
}
|
|
3181
3330
|
};
|
|
3331
|
+
|
|
3332
|
+
finObj?.type == "dynamics365" && ( data.attributes.userid = finObj?.ownerid );
|
|
3182
3333
|
}
|
|
3183
3334
|
|
|
3184
3335
|
if ( Object.keys( data ).length > 0 ) {
|
|
@@ -3314,11 +3465,103 @@ class KeycloakService extends Keycloak {
|
|
|
3314
3465
|
|
|
3315
3466
|
let userTeams = await requestController.httpRequest( config1, true );
|
|
3316
3467
|
|
|
3468
|
+
if ( finObj?.type == "dynamics365" ) {
|
|
3469
|
+
|
|
3470
|
+
let tempUrl1 = `${this.keycloakConfig[ "ef-server-url" ]}team`;
|
|
3471
|
+
|
|
3472
|
+
let tempConfig1 = {
|
|
3473
|
+
|
|
3474
|
+
url: tempUrl1,
|
|
3475
|
+
method: "get",
|
|
3476
|
+
headers: {
|
|
3477
|
+
Accept: "application/json",
|
|
3478
|
+
"cache-control": "no-cache",
|
|
3479
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
};
|
|
3483
|
+
|
|
3484
|
+
try {
|
|
3485
|
+
|
|
3486
|
+
let getTeamsList = await requestController.httpRequest( tempConfig1, false );
|
|
3487
|
+
|
|
3488
|
+
// Access the actual array of teams from the .data property.
|
|
3489
|
+
const teamsData = getTeamsList?.data;
|
|
3490
|
+
|
|
3491
|
+
// Check if the teamsData array is empty or not an array.
|
|
3492
|
+
if ( !Array.isArray( teamsData ) || teamsData.length === 0 ) {
|
|
3493
|
+
|
|
3494
|
+
console.log( "teamsData array is empty or invalid. Assigning 1 to finObject.group.id." );
|
|
3495
|
+
finObj.group.id = 1;
|
|
3496
|
+
|
|
3497
|
+
} else {
|
|
3498
|
+
|
|
3499
|
+
const findDefaultTeamId = ( teamsArray ) => {
|
|
3500
|
+
|
|
3501
|
+
// Rule 1: Find a team with team_name "default" (case-insensitive).
|
|
3502
|
+
const defaultTeam = teamsArray.find(
|
|
3503
|
+
( team ) => team.team_name && team.team_name.toLowerCase() === "default"
|
|
3504
|
+
);
|
|
3505
|
+
|
|
3506
|
+
if ( defaultTeam ) {
|
|
3507
|
+
console.log( 'Rule 1 matched: Found team with name "default".' );
|
|
3508
|
+
return defaultTeam.team_Id;
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
console.log( 'Rule 1 failed: No team with name "default" found.' );
|
|
3512
|
+
|
|
3513
|
+
// Rule 2: If no "default" team, find a team with team_Id of 1 whose name is NOT "default".
|
|
3514
|
+
const teamWithIdOne = teamsArray.find(
|
|
3515
|
+
( team ) =>
|
|
3516
|
+
( team.team_Id === 1 || team.team_Id === "1" ) &&
|
|
3517
|
+
team.team_name?.toLowerCase() !== "default"
|
|
3518
|
+
);
|
|
3519
|
+
|
|
3520
|
+
if ( teamWithIdOne ) {
|
|
3521
|
+
console.log( "Rule 2 matched: Found a team with team_Id of 1 and a non-default name." );
|
|
3522
|
+
return crypto.randomUUID();
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
console.log( "Rule 2 failed: No suitable team with team_Id of 1 found." );
|
|
3526
|
+
|
|
3527
|
+
// Rule 3: If no default team was found and no suitable team with ID 1 was found, return a random ID.
|
|
3528
|
+
console.log( "Rule 3 matched: Defaulting to a new random UUID." );
|
|
3529
|
+
return 1;
|
|
3530
|
+
};
|
|
3531
|
+
|
|
3532
|
+
// Call the function with the fetched list of teams
|
|
3533
|
+
const defaultTeamId = findDefaultTeamId( teamsData );
|
|
3534
|
+
|
|
3535
|
+
// Assign the resulting ID to the finObject
|
|
3536
|
+
finObj.groupId = defaultTeamId;
|
|
3537
|
+
|
|
3538
|
+
// You can now use the 'defaultTeamId' variable and the updated userObject
|
|
3539
|
+
console.log( `The determined default Team ID is: ${defaultTeamId}` );
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
} catch ( er ) {
|
|
3543
|
+
|
|
3544
|
+
let error = await errorService.handleError( er );
|
|
3545
|
+
|
|
3546
|
+
reject( {
|
|
3547
|
+
|
|
3548
|
+
error_message: "Dynamics365 Team Sync Error: Error occured while checking for default team in CX.",
|
|
3549
|
+
error_detail: error
|
|
3550
|
+
} );
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
( finObj?.type == "dynamics365" ) && ( finObj.groupName = "default" );
|
|
3556
|
+
|
|
3557
|
+
let ciscoTeamId = ( finObj?.type == "dynamics365" ) ? finObj.groupId : finObj.group.id;
|
|
3558
|
+
|
|
3559
|
+
|
|
3317
3560
|
const { userTeam, supervisedTeams } = userTeams.data;
|
|
3318
3561
|
|
|
3319
3562
|
let supervisedTeamsFiltered = [];
|
|
3320
3563
|
|
|
3321
|
-
if ( supervisedTeams.length > 0 ) {
|
|
3564
|
+
if ( supervisedTeams.length > 0 && !( finObj?.type == "dynamics365" ) ) {
|
|
3322
3565
|
|
|
3323
3566
|
//Fetching list of all primary and seconday supervised teams of current user (Whether in CX or Cisco)
|
|
3324
3567
|
supervisedTeamsFiltered = supervisedTeams.filter( team => {
|
|
@@ -3339,7 +3582,7 @@ class KeycloakService extends Keycloak {
|
|
|
3339
3582
|
}
|
|
3340
3583
|
|
|
3341
3584
|
//If Agent team in finesse is different from Agent Team in finesse
|
|
3342
|
-
if (
|
|
3585
|
+
if ( ciscoTeamId !== userTeam.teamId ) {
|
|
3343
3586
|
|
|
3344
3587
|
//We have to both add agent to a team corresponding to Finesse and remove it from CX team.
|
|
3345
3588
|
//Removing agent from CX team first
|
|
@@ -3364,7 +3607,7 @@ class KeycloakService extends Keycloak {
|
|
|
3364
3607
|
}
|
|
3365
3608
|
|
|
3366
3609
|
//Check whether team of Agent already exists in CX Core or not
|
|
3367
|
-
let URL4 = `${this.keycloakConfig[ "ef-server-url" ]}team?ids=${
|
|
3610
|
+
let URL4 = `${this.keycloakConfig[ "ef-server-url" ]}team?ids=${ciscoTeamId}`;
|
|
3368
3611
|
|
|
3369
3612
|
config1.method = 'get';
|
|
3370
3613
|
config1.url = URL4;
|
|
@@ -3383,8 +3626,8 @@ class KeycloakService extends Keycloak {
|
|
|
3383
3626
|
let URL5 = `${this.keycloakConfig[ "ef-server-url" ]}team`;
|
|
3384
3627
|
|
|
3385
3628
|
let data = {
|
|
3386
|
-
"team_Id":
|
|
3387
|
-
"team_name": finObj.group.name,
|
|
3629
|
+
"team_Id": ciscoTeamId,
|
|
3630
|
+
"team_name": ( finObj?.type == "dynamics365" ) ? finObj?.groupName : finObj.group.name,
|
|
3388
3631
|
"supervisor_Id": "",
|
|
3389
3632
|
"source": "CISCO",
|
|
3390
3633
|
"created_by": "1"
|
|
@@ -3412,7 +3655,7 @@ class KeycloakService extends Keycloak {
|
|
|
3412
3655
|
}
|
|
3413
3656
|
|
|
3414
3657
|
//Assign Agent to a team
|
|
3415
|
-
let URL6 = `${this.keycloakConfig[ "ef-server-url" ]}team/${
|
|
3658
|
+
let URL6 = `${this.keycloakConfig[ "ef-server-url" ]}team/${ciscoTeamId}/member`;
|
|
3416
3659
|
|
|
3417
3660
|
data = {
|
|
3418
3661
|
"type": "agent",
|
|
@@ -3451,7 +3694,7 @@ class KeycloakService extends Keycloak {
|
|
|
3451
3694
|
}
|
|
3452
3695
|
|
|
3453
3696
|
//If no team is assigned to supervise to current user in Cisco, remove its all supervised teams from CX
|
|
3454
|
-
if ( !finObj.supervisedGroups && supervisedTeamsFiltered.length > 0 ) {
|
|
3697
|
+
if ( !finObj.supervisedGroups && supervisedTeamsFiltered.length > 0 && !( finObj?.type == "dynamics365" ) ) {
|
|
3455
3698
|
|
|
3456
3699
|
for ( let supervisedTeam of supervisedTeamsFiltered ) {
|
|
3457
3700
|
|
|
@@ -3519,7 +3762,7 @@ class KeycloakService extends Keycloak {
|
|
|
3519
3762
|
|
|
3520
3763
|
//Supervisor Case. Filtering out teams to add and teams to remove from Supervisor
|
|
3521
3764
|
//First check that We have supervised Groups in finesse
|
|
3522
|
-
if ( finObj.supervisedGroups ) {
|
|
3765
|
+
if ( finObj.supervisedGroups && !( finObj?.type == "dynamics365" ) ) {
|
|
3523
3766
|
|
|
3524
3767
|
let finesseSupervisedGroups = finObj.supervisedGroups;
|
|
3525
3768
|
|
|
@@ -3769,6 +4012,151 @@ class KeycloakService extends Keycloak {
|
|
|
3769
4012
|
} );
|
|
3770
4013
|
}
|
|
3771
4014
|
|
|
4015
|
+
//AppDynamics365 SSO implementation
|
|
4016
|
+
async dynamics365Sso( userRoles, validationToken, dynamics365Url ) {
|
|
4017
|
+
|
|
4018
|
+
return new Promise( async ( resolve, reject ) => {
|
|
4019
|
+
|
|
4020
|
+
//Authentication of Finesse User, it returns a status code 200 if user found and 401 if unauthorized.
|
|
4021
|
+
let dynamics365LoginResponse = {};
|
|
4022
|
+
|
|
4023
|
+
try {
|
|
4024
|
+
//Handle finesse error cases correctly. (for later)
|
|
4025
|
+
if ( Object.keys( dynamics365LoginResponse ).length === 0 ) {
|
|
4026
|
+
|
|
4027
|
+
dynamics365LoginResponse = await dynamics365Service.authenticateUserViaDynamics365( validationToken, dynamics365Url );
|
|
4028
|
+
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
//If user is SSO then password is not provided, we are setting up a pre-defined password.
|
|
4032
|
+
dynamics365LoginResponse.data.password = "123456";
|
|
4033
|
+
|
|
4034
|
+
let authenticatedByKeycloak = false;
|
|
4035
|
+
let keycloakAuthToken = null;
|
|
4036
|
+
let keycloakAdminToken = null;
|
|
4037
|
+
let updateUserPromise = null;
|
|
4038
|
+
|
|
4039
|
+
|
|
4040
|
+
if ( dynamics365LoginResponse?.status == 200 ) {
|
|
4041
|
+
|
|
4042
|
+
dynamics365LoginResponse.data.roles = userRoles;
|
|
4043
|
+
dynamics365LoginResponse.data.type = "dynamics365";
|
|
4044
|
+
|
|
4045
|
+
console.log( dynamics365LoginResponse.data.fullname );
|
|
4046
|
+
|
|
4047
|
+
let trimmedUsername = dynamics365LoginResponse?.data?.fullname.trim().split( ' ' )[ 0 ]; // Removes leading/trailing spaces
|
|
4048
|
+
dynamics365LoginResponse.data.fullname = trimmedUsername;
|
|
4049
|
+
console.log( dynamics365LoginResponse.data.fullname );
|
|
4050
|
+
|
|
4051
|
+
try {
|
|
4052
|
+
|
|
4053
|
+
//Fetching admin token, we pass it in our "Create User" API for authorization
|
|
4054
|
+
keycloakAdminToken = await this.getAccessToken( this.keycloakConfig[ "USERNAME_ADMIN" ], this.keycloakConfig[ "PASSWORD_ADMIN" ] );
|
|
4055
|
+
|
|
4056
|
+
try {
|
|
4057
|
+
|
|
4058
|
+
//Checking whether finesse user already exist in keycloak and fetch its token
|
|
4059
|
+
keycloakAuthToken = await this.getAccessToken( dynamics365LoginResponse?.data?.fullname, dynamics365LoginResponse.data.password, this.keycloakConfig[ "realm" ] );
|
|
4060
|
+
authenticatedByKeycloak = true;
|
|
4061
|
+
|
|
4062
|
+
if ( !updateUserPromise ) {
|
|
4063
|
+
|
|
4064
|
+
updateUserPromise = this.updateUser( dynamics365LoginResponse?.data, keycloakAdminToken, keycloakAuthToken, dynamics365LoginResponse?.data.username, dynamics365LoginResponse.data.password )
|
|
4065
|
+
.then( async ( updatedUser ) => {
|
|
4066
|
+
|
|
4067
|
+
//Calling the Introspect function twice so all the asynchronous operations inside updateUser function are done
|
|
4068
|
+
keycloakAuthToken = await this.getKeycloakTokenWithIntrospect( dynamics365LoginResponse?.data?.username, dynamics365LoginResponse.data.password, this.keycloakConfig[ "realm" ], 'CISCO' );
|
|
4069
|
+
} )
|
|
4070
|
+
.catch( ( err ) => {
|
|
4071
|
+
|
|
4072
|
+
reject( err );
|
|
4073
|
+
} );
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
|
|
4077
|
+
} catch ( err ) {
|
|
4078
|
+
|
|
4079
|
+
if ( err.error_detail ) {
|
|
4080
|
+
|
|
4081
|
+
if ( err.error_detail.status == 401 ) {
|
|
4082
|
+
|
|
4083
|
+
console.log( "User Not Found in Keycloak: The user does not exist in Keycloak. Syncing Finesse user in Keycloak." );
|
|
4084
|
+
} else {
|
|
4085
|
+
|
|
4086
|
+
reject( err );
|
|
4087
|
+
}
|
|
4088
|
+
} else {
|
|
4089
|
+
|
|
4090
|
+
reject( err );
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
}
|
|
4094
|
+
} catch ( err ) {
|
|
4095
|
+
|
|
4096
|
+
let error = await errorService.handleError( err );
|
|
4097
|
+
|
|
4098
|
+
reject( {
|
|
4099
|
+
|
|
4100
|
+
error_message: "Keycloak Admin Token Fetch Error: An error occurred while fetching the keycloak admin token in the authenticate/sync finesse user component.",
|
|
4101
|
+
error_detail: error
|
|
4102
|
+
} );
|
|
4103
|
+
|
|
4104
|
+
|
|
4105
|
+
} finally {
|
|
4106
|
+
|
|
4107
|
+
//Finesse User not found in keycloak, so we are going to create one.
|
|
4108
|
+
if ( !authenticatedByKeycloak ) {
|
|
4109
|
+
|
|
4110
|
+
if ( keycloakAdminToken.access_token ) {
|
|
4111
|
+
|
|
4112
|
+
try {
|
|
4113
|
+
|
|
4114
|
+
//Creating Finesse User inside keycloak.
|
|
4115
|
+
let userCreated = await this.createUser( dynamics365LoginResponse?.data, keycloakAdminToken.access_token );
|
|
4116
|
+
|
|
4117
|
+
if ( userCreated.status == 201 ) {
|
|
4118
|
+
|
|
4119
|
+
//Returning the token of recently created User
|
|
4120
|
+
keycloakAuthToken = await this.getKeycloakTokenWithIntrospect( ( dynamics365LoginResponse?.data?.fullname ).toLowerCase(), dynamics365LoginResponse.data.password, this.keycloakConfig[ "realm" ], 'CISCO' );
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
} catch ( err ) {
|
|
4124
|
+
|
|
4125
|
+
|
|
4126
|
+
let error = await errorService.handleError( err );
|
|
4127
|
+
|
|
4128
|
+
reject( {
|
|
4129
|
+
|
|
4130
|
+
error_message: "Finesse User Creation Error: An error occurred while creating the finesse user in the authenticate/sync finesse user component.",
|
|
4131
|
+
error_detail: error
|
|
4132
|
+
} );
|
|
4133
|
+
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
if ( updateUserPromise ) {
|
|
4140
|
+
await updateUserPromise; // Wait for the updateUser promise to resolve
|
|
4141
|
+
updateUserPromise = null; // Reset the promise
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
resolve( keycloakAuthToken );
|
|
4145
|
+
} else {
|
|
4146
|
+
|
|
4147
|
+
resolve( dynamics365LoginResponse );
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
|
|
4151
|
+
} catch ( er ) {
|
|
4152
|
+
|
|
4153
|
+
reject( er );
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
|
|
4157
|
+
} );
|
|
4158
|
+
}
|
|
4159
|
+
|
|
3772
4160
|
//Sync implementation for teams along with its members (both create and update).
|
|
3773
4161
|
async syncImplementation( finesseAdministratorUsername, finesseAdministratorPassword, finesseURL ) {
|
|
3774
4162
|
|
|
@@ -3790,34 +4178,34 @@ class KeycloakService extends Keycloak {
|
|
|
3790
4178
|
|
|
3791
4179
|
|
|
3792
4180
|
/*
|
|
3793
|
-
|
|
3794
|
-
|
|
4181
|
+
|
|
4182
|
+
|
|
3795
4183
|
workflow:
|
|
3796
|
-
|
|
4184
|
+
|
|
3797
4185
|
call Cisco teams API to get all the teams, call CX Teams List API to get teams from CX. Compare both and see is there is any additional Cisco Team which doesn't exist on CX or CX team with type "CISCO" that doesn't eixst in Cisco. Create Cisco teams that are missing on CX and delete/disable CX teams that are missing on Cisco side. Also check if team_name of any team is updated on Cisco side and update it on CX. After that, call team detail API to fetch all the members of each team one by one. Then call then "CX API to get members of specific team" to get the members on CX side of same team. Check if there is any member missing or additional member of CX side and make the members exactly as in Cisco side. If a member doesn't exist then create it in keycloak and add it in CX Team corresponding to Cisco. After that, check the data of each member by calling "Keycloak API to get member list" having attribute: "Cisco User" and compare it with users in "Cisco API to fetch user list". if any user is additional in Cisco list then create it in CX and add it in its subsequent list. If any user is additional on keycloak side then disable that user. Similary, update the info exactly to the Cisco user if there is any difference i.e change in role, firstname, lastname, loginname, extension, team, supervised teams "represented by <teams> in user respresentation". There should be both create and update scenarios based on existance or non-existance of team and its members (agents, supervisors)
|
|
3798
|
-
|
|
4186
|
+
|
|
3799
4187
|
Requirements:
|
|
3800
|
-
|
|
4188
|
+
|
|
3801
4189
|
All cisco apis require administrator credentials
|
|
3802
|
-
|
|
4190
|
+
|
|
3803
4191
|
Cisco API to fetch all teams: https://uccx12-5p.ucce.ipcc:8445/finesse/api/Teams?nocache=1680864072911&bypassServerCache=true
|
|
3804
4192
|
Cisco API to fetch team detail: https://uccx12-5p.ucce.ipcc:8445/finesse/api/Team/8
|
|
3805
4193
|
Cisco API to fetch user detail: https://uccx12-5p.ucce.ipcc:8445/finesse/api/User/SE2606
|
|
3806
4194
|
Cisco API to fetch user list: https://uccx12-5p.ucce.ipcc:8445/finesse/api/Users
|
|
3807
|
-
|
|
4195
|
+
|
|
3808
4196
|
CX APIs:
|
|
3809
|
-
|
|
4197
|
+
|
|
3810
4198
|
CX API to get all teams: https://cxtd-qa05.expertflow.com/unified-admin/team
|
|
3811
4199
|
CX API to get members of specific team: https://cxtd-qa05.expertflow.com/unified-admin/team/{teamId}/member?limit=25&offset=0
|
|
3812
|
-
|
|
4200
|
+
|
|
3813
4201
|
Keycloak APIs:
|
|
3814
|
-
|
|
4202
|
+
|
|
3815
4203
|
Keycloak API to get member list: https://cxtd-qa05.expertflow.com/auth/admin/realms/{realm}/users
|
|
3816
4204
|
Keycloak API to create user: https://cxtd-qa05.expertflow.com/auth/admin/realms/{realm}/users
|
|
3817
4205
|
Keycloak API to get details of user: https://cxtd-qa05.expertflow.com/auth/admin/realms/{realm}/users/{user-id}
|
|
3818
4206
|
Keycloak API to get roles of user: https://cxtd-qa05.expertflow.com/auth/admin/realms/{realm}/roles
|
|
3819
4207
|
Keycloak API to assign roles to user: https://cxtd-qa05.expertflow.com/auth/admin/realms/{realm}/users/${userId}/role-mappings/realm
|
|
3820
|
-
|
|
4208
|
+
|
|
3821
4209
|
*/
|
|
3822
4210
|
}
|
|
3823
4211
|
|
|
@@ -4104,14 +4492,6 @@ class KeycloakService extends Keycloak {
|
|
|
4104
4492
|
|
|
4105
4493
|
}
|
|
4106
4494
|
|
|
4107
|
-
async createTeamsAndMembers() {
|
|
4108
|
-
|
|
4109
|
-
}
|
|
4110
|
-
|
|
4111
|
-
async updateTeamsAndMembers() {
|
|
4112
|
-
|
|
4113
|
-
}
|
|
4114
|
-
|
|
4115
4495
|
async checkPasswordUpdate( adminToken, userName, password ) {
|
|
4116
4496
|
|
|
4117
4497
|
return new Promise( async ( resolve, reject ) => {
|
|
@@ -4277,53 +4657,19 @@ class KeycloakService extends Keycloak {
|
|
|
4277
4657
|
|
|
4278
4658
|
// !-------------- Multitenancy -----------------!
|
|
4279
4659
|
|
|
4280
|
-
async createRealmAsTenant( tenantName,
|
|
4660
|
+
async createRealmAsTenant( tenantName, realmFile, authzConfigFilePath ) {
|
|
4281
4661
|
|
|
4282
4662
|
return new Promise( async ( resolve, reject ) => {
|
|
4283
4663
|
|
|
4284
|
-
let realmData;
|
|
4285
|
-
let authzConfigData;
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
try {
|
|
4289
|
-
|
|
4290
|
-
realmData = JSON.parse( realmDataString );
|
|
4291
|
-
|
|
4292
|
-
} catch ( parseError ) {
|
|
4293
|
-
|
|
4294
|
-
if ( parseError instanceof SyntaxError ) {
|
|
4295
|
-
|
|
4296
|
-
reject( {
|
|
4297
|
-
"error_message": "Error occurred while parsing Realm file during Tenant creation process.",
|
|
4298
|
-
"error_detail": {
|
|
4299
|
-
"status": 400,
|
|
4300
|
-
"reason": `Invalid JSON in realm configuration file: ${parseError.message} `
|
|
4301
|
-
}
|
|
4302
|
-
} );
|
|
4303
|
-
}
|
|
4304
|
-
|
|
4305
|
-
reject( {
|
|
4306
|
-
"error_message": "Error occurred while parsing Realm file during Tenant creation process.",
|
|
4307
|
-
"error_detail": {
|
|
4308
|
-
"status": 500,
|
|
4309
|
-
"reason": `Error parsing realm configuration file: ${parseError.message}`
|
|
4310
|
-
}
|
|
4311
|
-
} );
|
|
4312
|
-
}
|
|
4313
|
-
|
|
4314
|
-
if ( Object.keys( realmData ).length < 1 ) {
|
|
4315
|
-
|
|
4316
|
-
reject( {
|
|
4317
|
-
errorStatus: 400,
|
|
4318
|
-
errorMessage: `Received no realm data to import while creating tenant. Please send the correct realm data from realm file`
|
|
4319
|
-
} );
|
|
4320
|
-
}
|
|
4321
|
-
|
|
4322
4664
|
let realmImportSuccessful = false;
|
|
4323
4665
|
let mainMessage = "";
|
|
4666
|
+
let realmConfigString = '';
|
|
4667
|
+
let realmData = '';
|
|
4668
|
+
let authzConfigString = '';
|
|
4669
|
+
let authzConfig = '';
|
|
4324
4670
|
|
|
4325
4671
|
let accessToken;
|
|
4326
|
-
let URL = keycloakConfig[ "auth-server-url" ] + "realms/master/protocol/openid-connect/token";
|
|
4672
|
+
let URL = this.keycloakConfig[ "auth-server-url" ] + "realms/master/protocol/openid-connect/token";
|
|
4327
4673
|
|
|
4328
4674
|
let config = {
|
|
4329
4675
|
method: "post",
|
|
@@ -4334,8 +4680,8 @@ class KeycloakService extends Keycloak {
|
|
|
4334
4680
|
data: {
|
|
4335
4681
|
client_id: "admin-cli",
|
|
4336
4682
|
grant_type: "password",
|
|
4337
|
-
username: keycloakConfig[ "MASTER_USERNAME" ],
|
|
4338
|
-
password: keycloakConfig[ "MASTER_PASSWORD" ]
|
|
4683
|
+
username: this.keycloakConfig[ "MASTER_USERNAME" ],
|
|
4684
|
+
password: this.keycloakConfig[ "MASTER_PASSWORD" ]
|
|
4339
4685
|
},
|
|
4340
4686
|
};
|
|
4341
4687
|
|
|
@@ -4345,10 +4691,76 @@ class KeycloakService extends Keycloak {
|
|
|
4345
4691
|
|
|
4346
4692
|
accessToken = adminAccessToken.data.access_token;
|
|
4347
4693
|
|
|
4348
|
-
let createRealmUrl = keycloakConfig[ "auth-server-url" ] + 'admin/realms';
|
|
4694
|
+
let createRealmUrl = this.keycloakConfig[ "auth-server-url" ] + 'admin/realms';
|
|
4349
4695
|
|
|
4350
4696
|
// 1. Read the realm configuration JSON file
|
|
4351
|
-
console.log( `Reading realm configuration from
|
|
4697
|
+
console.log( `Reading realm configuration from: ${realmFile}` );
|
|
4698
|
+
|
|
4699
|
+
try {
|
|
4700
|
+
|
|
4701
|
+
realmConfigString = await fs.readFileSync( realmFile, 'utf-8' );
|
|
4702
|
+
|
|
4703
|
+
} catch ( fileReadError ) {
|
|
4704
|
+
|
|
4705
|
+
if ( fileReadError.code === 'ENOENT' ) {
|
|
4706
|
+
|
|
4707
|
+
reject( {
|
|
4708
|
+
"error_message": "Error occurred while reading Realm file during Tenant creation process.",
|
|
4709
|
+
"error_detail": {
|
|
4710
|
+
"status": 404,
|
|
4711
|
+
"reason": `Realm configuration file not found at provided path: ${realmFile} `
|
|
4712
|
+
}
|
|
4713
|
+
} );
|
|
4714
|
+
}
|
|
4715
|
+
|
|
4716
|
+
reject( {
|
|
4717
|
+
"error_message": "Error occurred while reading Realm file during Tenant creation process.",
|
|
4718
|
+
"error_detail": {
|
|
4719
|
+
"status": 500,
|
|
4720
|
+
"reason": `Error reading realm configuration file: ${fileReadError.message}. {file path: ${realmFile}} `
|
|
4721
|
+
}
|
|
4722
|
+
} )
|
|
4723
|
+
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
if ( !realmConfigString || realmConfigString.trim() === '' ) {
|
|
4727
|
+
|
|
4728
|
+
reject( {
|
|
4729
|
+
"error_message": "Error occurred while reading Realm file during Tenant creation process.",
|
|
4730
|
+
"error_detail": {
|
|
4731
|
+
"status": 404,
|
|
4732
|
+
"reason": `Provided Realm configuration file to create Tenant is empty. Please provide valid realm file in JSON format. `
|
|
4733
|
+
}
|
|
4734
|
+
} );
|
|
4735
|
+
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
try {
|
|
4739
|
+
|
|
4740
|
+
realmData = JSON.parse( realmConfigString );
|
|
4741
|
+
|
|
4742
|
+
} catch ( parseError ) {
|
|
4743
|
+
|
|
4744
|
+
if ( parseError instanceof SyntaxError ) {
|
|
4745
|
+
|
|
4746
|
+
reject( {
|
|
4747
|
+
"error_message": "Error occurred while parsing Realm file during Tenant creation process.",
|
|
4748
|
+
"error_detail": {
|
|
4749
|
+
"status": 400,
|
|
4750
|
+
"reason": `Invalid JSON in realm configuration file: ${parseError.message} `
|
|
4751
|
+
}
|
|
4752
|
+
} );
|
|
4753
|
+
}
|
|
4754
|
+
|
|
4755
|
+
reject( {
|
|
4756
|
+
"error_message": "Error occurred while parsing Realm file during Tenant creation process.",
|
|
4757
|
+
"error_detail": {
|
|
4758
|
+
"status": 500,
|
|
4759
|
+
"reason": `Error parsing realm configuration file: ${parseError.message}`
|
|
4760
|
+
}
|
|
4761
|
+
} );
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4352
4764
|
|
|
4353
4765
|
realmData.id = tenantName;
|
|
4354
4766
|
realmData.realm = tenantName;
|
|
@@ -4376,10 +4788,8 @@ class KeycloakService extends Keycloak {
|
|
|
4376
4788
|
|
|
4377
4789
|
}
|
|
4378
4790
|
|
|
4379
|
-
console.log( mainMessage );
|
|
4380
|
-
|
|
4381
4791
|
// --- Authorization Settings Import (if applicable) ---
|
|
4382
|
-
if ( realmImportSuccessful &&
|
|
4792
|
+
if ( realmImportSuccessful && authzConfigFilePath ) {
|
|
4383
4793
|
|
|
4384
4794
|
let targetClientIdForAuthz = 'cim';
|
|
4385
4795
|
|
|
@@ -4390,7 +4800,7 @@ class KeycloakService extends Keycloak {
|
|
|
4390
4800
|
|
|
4391
4801
|
// 4. Get the internal UUID of the target client
|
|
4392
4802
|
console.log( `Fetching UUID for client '${targetClientIdForAuthz}' in realm '${tenantName}'...` );
|
|
4393
|
-
const getClientUrl = `${keycloakConfig[ "auth-server-url" ]}admin/realms/${tenantName}/clients`;
|
|
4803
|
+
const getClientUrl = `${this.keycloakConfig[ "auth-server-url" ]}admin/realms/${tenantName}/clients`;
|
|
4394
4804
|
|
|
4395
4805
|
let config2 = {
|
|
4396
4806
|
|
|
@@ -4425,13 +4835,51 @@ class KeycloakService extends Keycloak {
|
|
|
4425
4835
|
throw new Error( `Client with clientId '${targetClientIdForAuthz}' not found in realm '${tenantName}'. Response: ${JSON.stringify( clientSearchResponse.data )}` );
|
|
4426
4836
|
}
|
|
4427
4837
|
|
|
4838
|
+
// 5. Read the authorization settings JSON file
|
|
4839
|
+
console.log( `Reading authorization settings from: ${authzConfigFilePath}` );
|
|
4428
4840
|
|
|
4429
|
-
|
|
4430
|
-
|
|
4841
|
+
try {
|
|
4842
|
+
|
|
4843
|
+
authzConfigString = await fs.readFileSync( authzConfigFilePath, 'utf-8' );
|
|
4844
|
+
|
|
4845
|
+
} catch ( fileReadError ) {
|
|
4846
|
+
|
|
4847
|
+
if ( fileReadError.code === 'ENOENT' ) {
|
|
4848
|
+
|
|
4849
|
+
reject( {
|
|
4850
|
+
"error_message": `Error occurred while reading Authz file while importing Permissions/Policies in ${targetClientIdForAuthz} during Tenant creation process.`,
|
|
4851
|
+
"error_detail": {
|
|
4852
|
+
"status": 404,
|
|
4853
|
+
"reason": `Authz file not found at provided path: ${authzConfigString}. `
|
|
4854
|
+
}
|
|
4855
|
+
} );
|
|
4856
|
+
}
|
|
4857
|
+
|
|
4858
|
+
reject( {
|
|
4859
|
+
"error_message": `Error occurred while reading Authz file while importing Permissions/Policies in ${targetClientIdForAuthz} during Tenant creation process.`,
|
|
4860
|
+
"error_detail": {
|
|
4861
|
+
"status": 500,
|
|
4862
|
+
"reason": `Error reading authz file: ${fileReadError.message}. {file path: ${authzConfigString}} `
|
|
4863
|
+
}
|
|
4864
|
+
} )
|
|
4865
|
+
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4868
|
+
if ( !authzConfigString || authzConfigString.trim() === '' ) {
|
|
4869
|
+
|
|
4870
|
+
reject( {
|
|
4871
|
+
"error_message": `Error occurred while reading Authz file while importing Permissions/Policies in ${targetClientIdForAuthz} during Tenant creation process.`,
|
|
4872
|
+
"error_detail": {
|
|
4873
|
+
"status": 404,
|
|
4874
|
+
"reason": `Provided authz file to import Permissions/Policies is empty. Please provide valid authz file in JSON format. `
|
|
4875
|
+
}
|
|
4876
|
+
} );
|
|
4877
|
+
|
|
4878
|
+
}
|
|
4431
4879
|
|
|
4432
4880
|
try {
|
|
4433
4881
|
|
|
4434
|
-
|
|
4882
|
+
authzConfig = JSON.parse( authzConfigString );
|
|
4435
4883
|
|
|
4436
4884
|
} catch ( parseError ) {
|
|
4437
4885
|
|
|
@@ -4455,15 +4903,11 @@ class KeycloakService extends Keycloak {
|
|
|
4455
4903
|
} );
|
|
4456
4904
|
}
|
|
4457
4905
|
|
|
4458
|
-
if ( Object.keys( authzConfigData ).length < 1 ) {
|
|
4459
4906
|
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
errorMessage: `Received no authorization data to import while creating tenant. Please send the correct authorization data from authz file`
|
|
4463
|
-
} );
|
|
4464
|
-
}
|
|
4907
|
+
// 6. Make the API call to Keycloak to import/update authorization settings
|
|
4908
|
+
console.log( `Importing authorization settings for client UUID '${clientUuid}'...` );
|
|
4465
4909
|
|
|
4466
|
-
const importAuthzUrl = `${keycloakConfig[ "auth-server-url" ]}admin/realms/${tenantName}/clients/${clientUuid}/authz/resource-server/import`;
|
|
4910
|
+
const importAuthzUrl = `${this.keycloakConfig[ "auth-server-url" ]}admin/realms/${tenantName}/clients/${clientUuid}/authz/resource-server/import`;
|
|
4467
4911
|
|
|
4468
4912
|
let config3 = {
|
|
4469
4913
|
|
|
@@ -4473,7 +4917,7 @@ class KeycloakService extends Keycloak {
|
|
|
4473
4917
|
"Content-Type": "application/json",
|
|
4474
4918
|
"Authorization": `Bearer ${accessToken}`
|
|
4475
4919
|
},
|
|
4476
|
-
data:
|
|
4920
|
+
data: authzConfig
|
|
4477
4921
|
};
|
|
4478
4922
|
|
|
4479
4923
|
const authzResponse = await requestController.httpRequest( config3, false );
|
|
@@ -4521,6 +4965,10 @@ class KeycloakService extends Keycloak {
|
|
|
4521
4965
|
console.error( 'No response received from Keycloak (Authz Import):', er.request );
|
|
4522
4966
|
|
|
4523
4967
|
|
|
4968
|
+
} else if ( er?.code === 'ENOENT' && er?.path === authzConfigFilePath ) {
|
|
4969
|
+
|
|
4970
|
+
console.error( 'Authorization settings file not found:', authzConfigFilePath );
|
|
4971
|
+
|
|
4524
4972
|
} else {
|
|
4525
4973
|
|
|
4526
4974
|
console.error( 'Error during authz import request setup or client lookup:', er?.message );
|