scimgateway 5.0.1 → 5.0.3
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 +46 -14
- package/bun.lockb +0 -0
- package/index.ts +5 -5
- package/lib/helper-rest.ts +7 -7
- package/lib/plugin-ldap.ts +34 -34
- package/lib/scimgateway.ts +86 -85
- package/lib/utils-scim.ts +35 -35
- package/lib/utils.ts +25 -25
- package/package.json +9 -4
- package/tsconfig.json +33 -34
- package/types/index.d.ts +1 -0
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ If internet connection is blocked, we could install on another machine and copy
|
|
|
123
123
|
|
|
124
124
|
bun c:\my-scimgateway
|
|
125
125
|
|
|
126
|
-
If using Node.js instead of Bun: node --experimental-strip-types c:\my-scimgateway\index.ts
|
|
126
|
+
If using Node.js instead of Bun, scimgateway must be downloaded from github and startup: node --experimental-strip-types c:\my-scimgateway\index.ts
|
|
127
127
|
|
|
128
128
|
Start a browser (note, Edge do not pop-up logon dialog box when using http)
|
|
129
129
|
|
|
@@ -569,8 +569,9 @@ docker-compose**
|
|
|
569
569
|
|
|
570
570
|
mkdir /opt/my-scimgateway
|
|
571
571
|
cd /opt/my-scimgateway
|
|
572
|
-
|
|
573
|
-
|
|
572
|
+
bun init -y
|
|
573
|
+
bun install scimgateway
|
|
574
|
+
bun pm trust scimgateway
|
|
574
575
|
cp ./config/docker/* .
|
|
575
576
|
|
|
576
577
|
**docker-compose.yml** <== Here is where you would set the exposed port and environment
|
|
@@ -819,15 +820,29 @@ If using proxy, set proxy.host to `"http://<FQDN-ProxyHost>:<port>"` e.g `"http:
|
|
|
819
820
|
"endpoint": {
|
|
820
821
|
"entity": {
|
|
821
822
|
"undefined": {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
823
|
+
"connection": {
|
|
824
|
+
"baseUrls": [
|
|
825
|
+
"not in use for Entra ID when tenantIdGUID is defined"
|
|
826
|
+
],
|
|
827
|
+
"auth": {
|
|
828
|
+
"type": "oauth",
|
|
829
|
+
"options": {
|
|
830
|
+
"tokenUrl": "oauth token_url - not in use when tenantIdGUID is defined",
|
|
831
|
+
"tenantIdGUID": "Entra ID Tenant ID (GUID) or Primary domain name - only used by plugin-entra-id",
|
|
832
|
+
"clientId": "oauth client_id - Entra ID: Application ID",
|
|
833
|
+
"clientSecret": "oauth client_secret - Entra ID: generated application secret value"
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
"proxy": {
|
|
837
|
+
"host": null,
|
|
838
|
+
"username": null,
|
|
839
|
+
"password": null
|
|
840
|
+
}
|
|
841
|
+
}
|
|
830
842
|
}
|
|
843
|
+
},
|
|
844
|
+
"map": {
|
|
845
|
+
...
|
|
831
846
|
}
|
|
832
847
|
}
|
|
833
848
|
|
|
@@ -946,7 +961,7 @@ Preparation:
|
|
|
946
961
|
|
|
947
962
|
* Copy "best matching" example plugin e.g. `lib\plugin-mssql.ts` and `config\plugin-mssql.json` and rename both copies to your plugin name prefix e.g. plugin-mine.ts and plugin-mine.json (for SOAP Webservice endpoint we might use plugin-soap as a template)
|
|
948
963
|
* Edit plugin-mine.json and define a unique port number for the gateway setting
|
|
949
|
-
* Edit index.ts and
|
|
964
|
+
* Edit index.ts and include your plugin in the startup e.g. `const plugins = ['mine']');`
|
|
950
965
|
* Start SCIM Gateway and verify. If using CA Provisioning you could setup a SCIM endpoint using the port number you defined
|
|
951
966
|
|
|
952
967
|
Now we are ready for custom coding by editing plugin-mine.ts
|
|
@@ -1071,11 +1086,28 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1071
1086
|
|
|
1072
1087
|
## Change log
|
|
1073
1088
|
|
|
1089
|
+
### v5.0.3
|
|
1090
|
+
|
|
1091
|
+
[Fixed]
|
|
1092
|
+
|
|
1093
|
+
- unauthorized connection when using configuration bearerJwtAzure
|
|
1094
|
+
|
|
1095
|
+
[Improved]
|
|
1096
|
+
|
|
1097
|
+
- minor type cosmetics
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
### v5.0.2
|
|
1101
|
+
|
|
1102
|
+
[Improved]
|
|
1103
|
+
|
|
1104
|
+
- minor cosmetics readme updates
|
|
1105
|
+
|
|
1074
1106
|
### v5.0.1
|
|
1075
1107
|
|
|
1076
1108
|
[Fixed]
|
|
1077
1109
|
|
|
1078
|
-
|
|
1110
|
+
- postinstall did not update index.ts when default bun index.ts did exist
|
|
1079
1111
|
|
|
1080
1112
|
|
|
1081
1113
|
### v5.0.0
|
|
@@ -1088,7 +1120,7 @@ Besides going from JavaScript to TypeScript, following can be mentioned:
|
|
|
1088
1120
|
|
|
1089
1121
|
* Code editor now having IntelliSense showing available methods and documentation details for scimgateway methods
|
|
1090
1122
|
* index.ts having new logic for starting plugins e.g.: `const plugins = ['ldap']` for starting plugin-ldap
|
|
1091
|
-
* If using Node.js
|
|
1123
|
+
* If using Node.js: node must be version >= 22.6.0, scimgateway must be downloaded from github (because stripping types is currently unsupported for files under node_modules) and startup argument `--experimental-strip-types` e.g.; `node --experimental-strip-types index.ts`
|
|
1092
1124
|
* Plugins can use `scimgateway.HelperRest()` for REST functionality. Previously this logic was included in each plugin that used REST.
|
|
1093
1125
|
|
|
1094
1126
|
// start - mandatory plugin initialization
|
package/bun.lockb
CHANGED
|
Binary file
|
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
//
|
|
4
|
-
// for
|
|
4
|
+
// for Node.js (version >= 22.6.0) use shebang: #!/usr/bin/env -S node --experimental-strip-types
|
|
5
5
|
//
|
|
6
6
|
// SCIM Gateway plugin startup
|
|
7
7
|
// One or more plugin can be started having unique port listener (configuration scimgateway.port)
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
// example starting all default plugins:
|
|
10
10
|
// const plugins = ['loki', 'scim', 'entra-id', 'ldap', 'mssql', 'api', 'mongodb', 'saphana', 'soap']
|
|
11
11
|
//
|
|
12
|
-
// some
|
|
13
|
-
// soap
|
|
14
|
-
// mssql
|
|
15
|
-
// saphana - bun
|
|
12
|
+
// some plugin requires module to be installed e.g.,:
|
|
13
|
+
// plugin-soap: bun install soap
|
|
14
|
+
// plugin-mssql: bun install tedious
|
|
15
|
+
// plugin-saphana - bun install hdb
|
|
16
16
|
//
|
|
17
17
|
|
|
18
18
|
const plugins = ['loki']
|
package/lib/helper-rest.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type ScimGateway from 'scimgateway'
|
|
|
11
11
|
*/
|
|
12
12
|
export class HelperRest {
|
|
13
13
|
private lock = new utils.Lock()
|
|
14
|
-
private _serviceClient = {}
|
|
14
|
+
private _serviceClient: Record<string, any> = {}
|
|
15
15
|
private config_entity: any
|
|
16
16
|
private scimgateway: ScimGateway
|
|
17
17
|
private graphUrl = 'https://graph.microsoft.com/beta' // beta instead of 'v1.0' gives all user attributes when no $select
|
|
@@ -47,8 +47,8 @@ export class HelperRest {
|
|
|
47
47
|
* @param ctx having format { autorization: "<type>:xxxxx" }
|
|
48
48
|
* @returns user_secret
|
|
49
49
|
**/
|
|
50
|
-
private getClientIdentifier(ctx
|
|
51
|
-
if (!ctx?.headers?.authorization) return undefined
|
|
50
|
+
private getClientIdentifier(ctx: Record<string, any> | undefined): string {
|
|
51
|
+
if (!ctx?.headers?.authorization) return 'undefined'
|
|
52
52
|
const [user, secret] = this.getCtxAuth(ctx)
|
|
53
53
|
return `${encodeURIComponent(user)}_${encodeURIComponent(secret)}` // user_password or undefined_password
|
|
54
54
|
}
|
|
@@ -58,7 +58,7 @@ export class HelperRest {
|
|
|
58
58
|
* @param ctx includes Auth PassThrough having format {headers:{autorization:"<type>:xxxxx"}}
|
|
59
59
|
* @returns [username, secret]
|
|
60
60
|
**/
|
|
61
|
-
private getCtxAuth(ctx): any[] {
|
|
61
|
+
private getCtxAuth(ctx: Record<string, any> | undefined): any[] {
|
|
62
62
|
if (!ctx?.headers?.authorization) return []
|
|
63
63
|
const [authType, authToken] = (ctx.headers.authorization || '').split(' ') // [0] = 'Basic' or 'Bearer'
|
|
64
64
|
let username, password
|
|
@@ -73,7 +73,7 @@ export class HelperRest {
|
|
|
73
73
|
* @param ctx
|
|
74
74
|
* @returns oauth accesstoken
|
|
75
75
|
*/
|
|
76
|
-
private async getAccessToken(baseEntity: string, ctx: string | undefined) {
|
|
76
|
+
private async getAccessToken(baseEntity: string, ctx: Record<string, any> | undefined) {
|
|
77
77
|
await this.lock.acquire()
|
|
78
78
|
const clientIdentifier = this.getClientIdentifier(ctx)
|
|
79
79
|
const d = Math.floor(Date.now() / 1000) // seconds (unix time)
|
|
@@ -381,7 +381,7 @@ export class HelperRest {
|
|
|
381
381
|
* @param clientIdentifier
|
|
382
382
|
* @param obj
|
|
383
383
|
*/
|
|
384
|
-
private updateServiceClient(baseEntity: string, clientIdentifier: string
|
|
384
|
+
private updateServiceClient(baseEntity: string, clientIdentifier: string, obj: any) {
|
|
385
385
|
if (this._serviceClient[baseEntity] && this._serviceClient[baseEntity][clientIdentifier]) this._serviceClient[baseEntity][clientIdentifier] = utils.extendObj(this._serviceClient[baseEntity][clientIdentifier], obj)
|
|
386
386
|
}
|
|
387
387
|
|
|
@@ -396,7 +396,7 @@ export class HelperRest {
|
|
|
396
396
|
* @param opt web-standard fetch client options, e.g., options not defined as general options in configuration file
|
|
397
397
|
* @param retryCount internal use only - internal counter for retry and failover logic to other baseUrls defined
|
|
398
398
|
**/
|
|
399
|
-
private async doRequestHandler(baseEntity: string, method: string, path: string, body?: any, ctx?: any, opt?: any, retryCount?: number) {
|
|
399
|
+
private async doRequestHandler(baseEntity: string, method: string, path: string, body?: any, ctx?: any, opt?: any, retryCount?: number): Promise<any> {
|
|
400
400
|
let retryAfter = 0
|
|
401
401
|
try {
|
|
402
402
|
const cli = await this.getServiceClient(baseEntity, method, path, opt, ctx)
|
package/lib/plugin-ldap.ts
CHANGED
|
@@ -219,7 +219,7 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
219
219
|
try {
|
|
220
220
|
const users: any = await doRequest(baseEntity, method, base, ldapOptions, ctx) // ignoring SCIM paging startIndex/count - get all
|
|
221
221
|
result.totalResults = users.length
|
|
222
|
-
result.Resources = await Promise.all(users.map(async (user) => { // Promise.all because of async map
|
|
222
|
+
result.Resources = await Promise.all(users.map(async (user: any) => { // Promise.all because of async map
|
|
223
223
|
// endpoint spesific attribute handling
|
|
224
224
|
// "active" must be handled separate
|
|
225
225
|
if (user.userAccountControl !== undefined) { // SCIM "active" - Active Directory
|
|
@@ -368,7 +368,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
368
368
|
delete attrObj.groups // make sure to be removed from attrObj
|
|
369
369
|
|
|
370
370
|
const [groupsAttr] = scimgateway.endpointMapper('outbound', 'groups.value', config.map.user)
|
|
371
|
-
const grp = { add: {}, remove: {} }
|
|
371
|
+
const grp: any = { add: {}, remove: {} }
|
|
372
372
|
grp.add[groupsAttr] = []
|
|
373
373
|
grp.remove[groupsAttr] = []
|
|
374
374
|
|
|
@@ -484,7 +484,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
484
484
|
// clean up zoombie group members and use the new user DN incase not handled by ldap server
|
|
485
485
|
const [memberAttr] = scimgateway.endpointMapper('outbound', 'members.value', config.map.group)
|
|
486
486
|
if (memberAttr) {
|
|
487
|
-
const grp = { add: {}, remove: {} }
|
|
487
|
+
const grp: any = { add: {}, remove: {} }
|
|
488
488
|
grp.add[memberAttr] = []
|
|
489
489
|
grp.remove[memberAttr] = []
|
|
490
490
|
let r
|
|
@@ -644,7 +644,7 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
644
644
|
if (ldapOptions === 'getMemberOfGroups') result.Resources = await getMemberOfGroups(baseEntity, getObj.value, ctx)
|
|
645
645
|
else {
|
|
646
646
|
const groups: any = await doRequest(baseEntity, method, base, ldapOptions, ctx)
|
|
647
|
-
result.Resources = await Promise.all(groups.map(async (group) => { // Promise.all because of async map
|
|
647
|
+
result.Resources = await Promise.all(groups.map(async (group: any) => { // Promise.all because of async map
|
|
648
648
|
if (config.useSID_id || config.useGUID_id) {
|
|
649
649
|
if (group.member) {
|
|
650
650
|
const arr: string[] = []
|
|
@@ -756,7 +756,7 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
756
756
|
const [memberAttr] = scimgateway.endpointMapper('outbound', 'members.value', config.map.group)
|
|
757
757
|
if (!memberAttr && attrObj.members) throw new Error(`${action} error: missing attribute mapping configuration for group members`)
|
|
758
758
|
|
|
759
|
-
const grp = { add: {}, remove: {} }
|
|
759
|
+
const grp: any = { add: {}, remove: {} }
|
|
760
760
|
grp.add[memberAttr] = []
|
|
761
761
|
grp.remove[memberAttr] = []
|
|
762
762
|
|
|
@@ -821,14 +821,14 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
821
821
|
// helpers
|
|
822
822
|
// =================================================
|
|
823
823
|
|
|
824
|
-
const _serviceClient = {}
|
|
824
|
+
const _serviceClient: Record<string, any> = {}
|
|
825
825
|
|
|
826
826
|
//
|
|
827
827
|
// createAndFilter creates AndFilter object to be used as filter instead of standard string filter
|
|
828
828
|
// Using AndFilter object for eliminating internal ldapjs escaping problems related to values with some
|
|
829
829
|
// combinations of parentheses e.g. ab(c)d
|
|
830
830
|
//
|
|
831
|
-
const createAndFilter = (baseEntity, type, arrObj) => {
|
|
831
|
+
const createAndFilter = (baseEntity: string, type: string, arrObj: any) => {
|
|
832
832
|
const objFilters: ldap.PresenceFilter[] | ldap.SubstringFilter[] = []
|
|
833
833
|
|
|
834
834
|
// add arrObj
|
|
@@ -906,7 +906,7 @@ const createAndFilter = (baseEntity, type, arrObj) => {
|
|
|
906
906
|
//
|
|
907
907
|
// dnToSidGuid is used for Active Directory to return objectGUID based on dn
|
|
908
908
|
//
|
|
909
|
-
const dnToSidGuid = async (baseEntity, dn, ctx): Promise<string> => {
|
|
909
|
+
const dnToSidGuid = async (baseEntity: string, dn: any, ctx: any): Promise<string> => {
|
|
910
910
|
const method = 'search'
|
|
911
911
|
const ldapOptions: any = {}
|
|
912
912
|
if (config.useSID_id) ldapOptions.attributes = ['objectSid']
|
|
@@ -928,7 +928,7 @@ const dnToSidGuid = async (baseEntity, dn, ctx): Promise<string> => {
|
|
|
928
928
|
//
|
|
929
929
|
// guidToDn is used for Active Directory to return dn based on objectGUID
|
|
930
930
|
//
|
|
931
|
-
const sidGuidToDn = async (baseEntity, id, ctx): Promise<string> => {
|
|
931
|
+
const sidGuidToDn = async (baseEntity: string, id: string, ctx: any): Promise<string> => {
|
|
932
932
|
const method = 'search'
|
|
933
933
|
const ldapOptions = {
|
|
934
934
|
attributes: ['dn'],
|
|
@@ -959,8 +959,8 @@ const sidGuidToDn = async (baseEntity, id, ctx): Promise<string> => {
|
|
|
959
959
|
// output: S-1-5-21-2657077294-4200173015-2627628055-1146
|
|
960
960
|
// ref: https://gist.github.com/Krizzzn/0ae47f280cca9749c67759a9adedc015
|
|
961
961
|
//
|
|
962
|
-
const pad = function (s) { if (s.length < 2) { return `0${s}` } else { return s } }
|
|
963
|
-
const convertSidToString = (buf) => {
|
|
962
|
+
const pad = function (s: any) { if (s.length < 2) { return `0${s}` } else { return s } }
|
|
963
|
+
const convertSidToString = (buf: any) => {
|
|
964
964
|
let asc: any, end: any
|
|
965
965
|
let i: number
|
|
966
966
|
if (buf == null) { return null }
|
|
@@ -997,7 +997,7 @@ const convertSidToString = (buf) => {
|
|
|
997
997
|
// output: 010500000000000515000000a065cf7e784b9b5fe77c8770091c0100
|
|
998
998
|
// ref: https://devblogs.microsoft.com/oldnewthing/20040315-00/?p=40253
|
|
999
999
|
//
|
|
1000
|
-
const convertStringToSid = (sidStr) => {
|
|
1000
|
+
const convertStringToSid = (sidStr: string) => {
|
|
1001
1001
|
const arr = sidStr.split('-')
|
|
1002
1002
|
if (arr.length !== 8) return null
|
|
1003
1003
|
try {
|
|
@@ -1025,7 +1025,7 @@ const convertStringToSid = (sidStr) => {
|
|
|
1025
1025
|
// getMemberOfGroups returns all groups the user is member of
|
|
1026
1026
|
// [{ id: <id-group>> , displayName: <displayName-group>, members [{value: <id-user>}] }]
|
|
1027
1027
|
//
|
|
1028
|
-
const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
1028
|
+
const getMemberOfGroups = async (baseEntity: string, id: string, ctx: any) => {
|
|
1029
1029
|
const action = 'getMemberOfGroups'
|
|
1030
1030
|
if (!config.map.group) throw new Error('missing configuration endpoint.map.group') // not using groups
|
|
1031
1031
|
|
|
@@ -1075,7 +1075,7 @@ const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
|
1075
1075
|
|
|
1076
1076
|
try {
|
|
1077
1077
|
const groups: any = await doRequest(baseEntity, method, base, ldapOptions, ctx)
|
|
1078
|
-
return groups.map((grp) => {
|
|
1078
|
+
return groups.map((grp: any) => {
|
|
1079
1079
|
return { // { id: <id-group>> , displayName: <displayName-group>, members [{value: <id-user>}] }
|
|
1080
1080
|
id: encodeURIComponent(grp[attrs[0]]), // not mandatory, but included anyhow
|
|
1081
1081
|
displayName: grp[attrs[1]], // displayName is mandatory
|
|
@@ -1093,7 +1093,7 @@ const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
|
1093
1093
|
// using OpenLDAP, DN must be escaped - national characters and special ldap characters
|
|
1094
1094
|
// using Active Directory (none OpenLDAP), DN should not be escaped, but DN retrieved from AD is character escaped
|
|
1095
1095
|
//
|
|
1096
|
-
const ldapEscDn = (isOpenLdap, str) => {
|
|
1096
|
+
const ldapEscDn = (isOpenLdap: any, str: string) => {
|
|
1097
1097
|
if (typeof str !== 'string' || str.length < 1) return str
|
|
1098
1098
|
|
|
1099
1099
|
if (!isOpenLdap && str.indexOf('\\') > 0) {
|
|
@@ -1150,13 +1150,13 @@ const ldapEscDn = (isOpenLdap, str) => {
|
|
|
1150
1150
|
if (i === 0) {
|
|
1151
1151
|
const ua = new Uint8Array(Buffer.from(a[1], 'utf-8'))
|
|
1152
1152
|
const buf = Buffer.from(new Uint8Array([4, ua.length, ...ua]))
|
|
1153
|
-
const rdn = {}
|
|
1153
|
+
const rdn: any = {}
|
|
1154
1154
|
rdn[a[0]] = new BerReader(buf)
|
|
1155
1155
|
dn.push(new ldap.RDN(rdn))
|
|
1156
1156
|
// new BerReader(Buffer.from([0x04, 0x05, 0x4B, 0xc3, 0xbc, 0x72, 0x74])) // Kürt
|
|
1157
1157
|
// the leading 04 is the tag for "octet string" and the following 05 is the length in bytes of the string.
|
|
1158
1158
|
} else {
|
|
1159
|
-
const rdn = {}
|
|
1159
|
+
const rdn: any = {}
|
|
1160
1160
|
rdn[a[0]] = a[1]
|
|
1161
1161
|
dn.push(new ldap.RDN(rdn))
|
|
1162
1162
|
}
|
|
@@ -1172,7 +1172,7 @@ const ldapEscDn = (isOpenLdap, str) => {
|
|
|
1172
1172
|
// Hex encoded escaping (extended and unicode ascii) is not included because
|
|
1173
1173
|
// automatically handled by ldapjs when not using BER encoded DN
|
|
1174
1174
|
//
|
|
1175
|
-
const ldapEsc = (str) => {
|
|
1175
|
+
const ldapEsc = (str: any) => {
|
|
1176
1176
|
if (!str) return str
|
|
1177
1177
|
let newStr = ''
|
|
1178
1178
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -1228,7 +1228,7 @@ const ldapEsc = (str) => {
|
|
|
1228
1228
|
// only using BER on first part of dn
|
|
1229
1229
|
// Having BER decoding for Active Directory, but not for OpenLDAP
|
|
1230
1230
|
//
|
|
1231
|
-
const berDecodeDn = (dn) => {
|
|
1231
|
+
const berDecodeDn = (dn: any) => {
|
|
1232
1232
|
if (Object.prototype.toString.call(dn) !== '[object LdapDn]') return dn // OpenLDAP
|
|
1233
1233
|
const str = dn.toString()
|
|
1234
1234
|
if (str.indexOf('#') < 1) return str
|
|
@@ -1262,7 +1262,7 @@ const berDecodeDn = (dn) => {
|
|
|
1262
1262
|
return str
|
|
1263
1263
|
}
|
|
1264
1264
|
|
|
1265
|
-
const getNamingAttribute = (baseEntity, type) => {
|
|
1265
|
+
const getNamingAttribute = (baseEntity: string, type: string) => {
|
|
1266
1266
|
let arr
|
|
1267
1267
|
switch (type) {
|
|
1268
1268
|
case 'user':
|
|
@@ -1278,7 +1278,7 @@ const getNamingAttribute = (baseEntity, type) => {
|
|
|
1278
1278
|
return [arr[0].attribute, arr[0].mapTo]
|
|
1279
1279
|
}
|
|
1280
1280
|
|
|
1281
|
-
const checkIfNewDN = (baseEntity, base, type, obj, endpointObj) => {
|
|
1281
|
+
const checkIfNewDN = (baseEntity: string, base: any, type: string, obj: any, endpointObj: any) => {
|
|
1282
1282
|
if (typeof obj !== 'object' || Object.keys(obj).length < 1) return ''
|
|
1283
1283
|
if (typeof endpointObj !== 'object' || Object.keys(endpointObj).length < 1) return ''
|
|
1284
1284
|
|
|
@@ -1326,7 +1326,7 @@ const checkIfNewDN = (baseEntity, base, type, obj, endpointObj) => {
|
|
|
1326
1326
|
//
|
|
1327
1327
|
// getCtxAuth returns username/secret from ctx header when using Auth PassThrough
|
|
1328
1328
|
//
|
|
1329
|
-
const getCtxAuth = (ctx) => {
|
|
1329
|
+
const getCtxAuth = (ctx: any) => {
|
|
1330
1330
|
if (!ctx?.request?.header?.authorization) return []
|
|
1331
1331
|
const [authType, authToken] = (ctx.request.header.authorization || '').split(' ') // [0] = 'Basic' or 'Bearer'
|
|
1332
1332
|
let username, password
|
|
@@ -1338,7 +1338,7 @@ const getCtxAuth = (ctx) => {
|
|
|
1338
1338
|
//
|
|
1339
1339
|
// getServiceClient returns LDAP client used by doRequest
|
|
1340
1340
|
//
|
|
1341
|
-
const getServiceClient = async (baseEntity, ctx) => {
|
|
1341
|
+
const getServiceClient = async (baseEntity: string, ctx: any) => {
|
|
1342
1342
|
const action = 'getServiceClient'
|
|
1343
1343
|
// TODO if (!config.entity[baseEntity].passwordDecrypted) config.entity[baseEntity].passwordDecrypted = scimgateway.getPassword(`endpoint.entity.${baseEntity}.password`, configFile)
|
|
1344
1344
|
if (!config.entity[baseEntity].baseUrl) config.entity[baseEntity].baseUrl = config.entity[baseEntity].baseUrls[0] // failover logic also updates baseUrl
|
|
@@ -1390,7 +1390,7 @@ const getServiceClient = async (baseEntity, ctx) => {
|
|
|
1390
1390
|
// "attributes": ["sAMAccountName","displayName","mail"]
|
|
1391
1391
|
// }
|
|
1392
1392
|
//
|
|
1393
|
-
const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
1393
|
+
const doRequest = async (baseEntity: string, method: string, base: any, options: any, ctx: any) => {
|
|
1394
1394
|
if (!config.entity[baseEntity]) throw new Error(`unsupported baseEntity: ${baseEntity}`)
|
|
1395
1395
|
let result: any = null
|
|
1396
1396
|
let client: any = null
|
|
@@ -1413,15 +1413,15 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1413
1413
|
result = await new Promise((resolve, reject) => {
|
|
1414
1414
|
const results: any = []
|
|
1415
1415
|
|
|
1416
|
-
client.search(base, options, (err, search) => {
|
|
1416
|
+
client.search(base, options, (err: any, search: any) => {
|
|
1417
1417
|
if (err) {
|
|
1418
1418
|
return reject(err)
|
|
1419
1419
|
}
|
|
1420
1420
|
|
|
1421
|
-
search.on('searchEntry', (entry) => {
|
|
1421
|
+
search.on('searchEntry', (entry: any) => {
|
|
1422
1422
|
if (!entry.pojo || !entry.pojo.attributes) return
|
|
1423
1423
|
const obj: any = { dn: entry.pojo.objectName }
|
|
1424
|
-
entry.pojo.attributes.map((el) => {
|
|
1424
|
+
entry.pojo.attributes.map((el: any) => {
|
|
1425
1425
|
if (el.values.length > 1) obj[el.type] = el.values
|
|
1426
1426
|
else obj[el.type] = el.values[0]
|
|
1427
1427
|
return null
|
|
@@ -1449,7 +1449,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1449
1449
|
// for OpenLDAP ensure dn is not hex escaped e.g.: cn=K\c3\bcrt => cn=Kürt
|
|
1450
1450
|
// because dn may be be used as value in standard attributes like group memberOf
|
|
1451
1451
|
obj.dn = obj.dn.replace(/\\\\/g, '__') // temp
|
|
1452
|
-
let conv = obj.dn.replace(/\\([0-9A-Fa-f]{2})/g, (_, hex) => {
|
|
1452
|
+
let conv = obj.dn.replace(/\\([0-9A-Fa-f]{2})/g, (_: any, hex: any) => {
|
|
1453
1453
|
const intAscii = parseInt(hex, 16)
|
|
1454
1454
|
if (intAscii > 128) { // extended ascii - will be unescaped by decodeURIComponent
|
|
1455
1455
|
return '%' + hex
|
|
@@ -1470,12 +1470,12 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1470
1470
|
})
|
|
1471
1471
|
*/
|
|
1472
1472
|
|
|
1473
|
-
search.on('error', (err) => {
|
|
1473
|
+
search.on('error', (err: any) => {
|
|
1474
1474
|
if (err.message.includes('LdapErr: DSID-0C0909F2') || err.message.includes('NO_OBJECT')) return resolve([]) // object not found when using base <SID=...> or <GUID=...> ref. objectSid/objectGUID
|
|
1475
1475
|
reject(err)
|
|
1476
1476
|
})
|
|
1477
1477
|
|
|
1478
|
-
search.on('end', (_) => { resolve(results) })
|
|
1478
|
+
search.on('end', (_: any) => { resolve(results) })
|
|
1479
1479
|
})
|
|
1480
1480
|
})
|
|
1481
1481
|
break
|
|
@@ -1506,7 +1506,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1506
1506
|
})
|
|
1507
1507
|
changes.push(change)
|
|
1508
1508
|
}
|
|
1509
|
-
client.modify(dn, changes, (err) => {
|
|
1509
|
+
client.modify(dn, changes, (err: any) => {
|
|
1510
1510
|
if (err) {
|
|
1511
1511
|
if (options.operation && options.operation === 'add') {
|
|
1512
1512
|
const msg = err.message.toLowerCase()
|
|
@@ -1526,7 +1526,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1526
1526
|
let newDN = options?.modification?.newDN
|
|
1527
1527
|
if (!newDN) return reject(new Error('modifyDN() missing newDN'))
|
|
1528
1528
|
if (Object.prototype.toString.call(newDN) === '[object LdapDn]') newDN = newDN.toString()
|
|
1529
|
-
client.modifyDN(dn, newDN, (err) => {
|
|
1529
|
+
client.modifyDN(dn, newDN, (err: any) => {
|
|
1530
1530
|
if (err) {
|
|
1531
1531
|
return reject(err)
|
|
1532
1532
|
}
|
|
@@ -1537,7 +1537,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1537
1537
|
|
|
1538
1538
|
case 'add':
|
|
1539
1539
|
result = await new Promise((resolve: any, reject: any) => {
|
|
1540
|
-
client.add(base, options, (err) => {
|
|
1540
|
+
client.add(base, options, (err: any) => {
|
|
1541
1541
|
if (err) {
|
|
1542
1542
|
return reject(err)
|
|
1543
1543
|
}
|
|
@@ -1548,7 +1548,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1548
1548
|
|
|
1549
1549
|
case 'del':
|
|
1550
1550
|
result = await new Promise((resolve: any, reject: any) => {
|
|
1551
|
-
client.del(base, (err) => {
|
|
1551
|
+
client.del(base, (err: any) => {
|
|
1552
1552
|
if (err) {
|
|
1553
1553
|
return reject(err)
|
|
1554
1554
|
}
|