scimgateway 5.0.2 → 5.0.4
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 +43 -11
- package/bun.lockb +0 -0
- package/index.ts +5 -5
- package/lib/helper-rest.ts +7 -7
- package/lib/plugin-ldap.ts +35 -34
- package/lib/scimgateway.ts +86 -85
- package/lib/utils-scim.ts +35 -35
- package/lib/utils.ts +25 -25
- package/package.json +8 -4
- package/tsconfig.json +33 -34
package/README.md
CHANGED
|
@@ -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,6 +1086,23 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1071
1086
|
|
|
1072
1087
|
## Change log
|
|
1073
1088
|
|
|
1089
|
+
### v5.0.4
|
|
1090
|
+
|
|
1091
|
+
[Improved]
|
|
1092
|
+
|
|
1093
|
+
- minor type definition cosmetics
|
|
1094
|
+
|
|
1095
|
+
### v5.0.3
|
|
1096
|
+
|
|
1097
|
+
[Fixed]
|
|
1098
|
+
|
|
1099
|
+
- unauthorized connection when using configuration bearerJwtAzure
|
|
1100
|
+
|
|
1101
|
+
[Improved]
|
|
1102
|
+
|
|
1103
|
+
- minor type definition cosmetics
|
|
1104
|
+
|
|
1105
|
+
|
|
1074
1106
|
### v5.0.2
|
|
1075
1107
|
|
|
1076
1108
|
[Improved]
|
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
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
'use strict'
|
|
83
83
|
|
|
84
84
|
import ldap from 'ldapjs'
|
|
85
|
+
// @ts-expect-error missing type definitions
|
|
85
86
|
import { BerReader } from '@ldapjs/asn1'
|
|
86
87
|
|
|
87
88
|
// for supporting nodejs running scimgateway package directly, using dynamic import instead of: import { ScimGateway } from 'scimgateway'
|
|
@@ -219,7 +220,7 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
219
220
|
try {
|
|
220
221
|
const users: any = await doRequest(baseEntity, method, base, ldapOptions, ctx) // ignoring SCIM paging startIndex/count - get all
|
|
221
222
|
result.totalResults = users.length
|
|
222
|
-
result.Resources = await Promise.all(users.map(async (user) => { // Promise.all because of async map
|
|
223
|
+
result.Resources = await Promise.all(users.map(async (user: any) => { // Promise.all because of async map
|
|
223
224
|
// endpoint spesific attribute handling
|
|
224
225
|
// "active" must be handled separate
|
|
225
226
|
if (user.userAccountControl !== undefined) { // SCIM "active" - Active Directory
|
|
@@ -368,7 +369,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
368
369
|
delete attrObj.groups // make sure to be removed from attrObj
|
|
369
370
|
|
|
370
371
|
const [groupsAttr] = scimgateway.endpointMapper('outbound', 'groups.value', config.map.user)
|
|
371
|
-
const grp = { add: {}, remove: {} }
|
|
372
|
+
const grp: any = { add: {}, remove: {} }
|
|
372
373
|
grp.add[groupsAttr] = []
|
|
373
374
|
grp.remove[groupsAttr] = []
|
|
374
375
|
|
|
@@ -484,7 +485,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
484
485
|
// clean up zoombie group members and use the new user DN incase not handled by ldap server
|
|
485
486
|
const [memberAttr] = scimgateway.endpointMapper('outbound', 'members.value', config.map.group)
|
|
486
487
|
if (memberAttr) {
|
|
487
|
-
const grp = { add: {}, remove: {} }
|
|
488
|
+
const grp: any = { add: {}, remove: {} }
|
|
488
489
|
grp.add[memberAttr] = []
|
|
489
490
|
grp.remove[memberAttr] = []
|
|
490
491
|
let r
|
|
@@ -644,7 +645,7 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
644
645
|
if (ldapOptions === 'getMemberOfGroups') result.Resources = await getMemberOfGroups(baseEntity, getObj.value, ctx)
|
|
645
646
|
else {
|
|
646
647
|
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
|
|
648
|
+
result.Resources = await Promise.all(groups.map(async (group: any) => { // Promise.all because of async map
|
|
648
649
|
if (config.useSID_id || config.useGUID_id) {
|
|
649
650
|
if (group.member) {
|
|
650
651
|
const arr: string[] = []
|
|
@@ -756,7 +757,7 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
756
757
|
const [memberAttr] = scimgateway.endpointMapper('outbound', 'members.value', config.map.group)
|
|
757
758
|
if (!memberAttr && attrObj.members) throw new Error(`${action} error: missing attribute mapping configuration for group members`)
|
|
758
759
|
|
|
759
|
-
const grp = { add: {}, remove: {} }
|
|
760
|
+
const grp: any = { add: {}, remove: {} }
|
|
760
761
|
grp.add[memberAttr] = []
|
|
761
762
|
grp.remove[memberAttr] = []
|
|
762
763
|
|
|
@@ -821,14 +822,14 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
821
822
|
// helpers
|
|
822
823
|
// =================================================
|
|
823
824
|
|
|
824
|
-
const _serviceClient = {}
|
|
825
|
+
const _serviceClient: Record<string, any> = {}
|
|
825
826
|
|
|
826
827
|
//
|
|
827
828
|
// createAndFilter creates AndFilter object to be used as filter instead of standard string filter
|
|
828
829
|
// Using AndFilter object for eliminating internal ldapjs escaping problems related to values with some
|
|
829
830
|
// combinations of parentheses e.g. ab(c)d
|
|
830
831
|
//
|
|
831
|
-
const createAndFilter = (baseEntity, type, arrObj) => {
|
|
832
|
+
const createAndFilter = (baseEntity: string, type: string, arrObj: any) => {
|
|
832
833
|
const objFilters: ldap.PresenceFilter[] | ldap.SubstringFilter[] = []
|
|
833
834
|
|
|
834
835
|
// add arrObj
|
|
@@ -906,7 +907,7 @@ const createAndFilter = (baseEntity, type, arrObj) => {
|
|
|
906
907
|
//
|
|
907
908
|
// dnToSidGuid is used for Active Directory to return objectGUID based on dn
|
|
908
909
|
//
|
|
909
|
-
const dnToSidGuid = async (baseEntity, dn, ctx): Promise<string> => {
|
|
910
|
+
const dnToSidGuid = async (baseEntity: string, dn: any, ctx: any): Promise<string> => {
|
|
910
911
|
const method = 'search'
|
|
911
912
|
const ldapOptions: any = {}
|
|
912
913
|
if (config.useSID_id) ldapOptions.attributes = ['objectSid']
|
|
@@ -928,7 +929,7 @@ const dnToSidGuid = async (baseEntity, dn, ctx): Promise<string> => {
|
|
|
928
929
|
//
|
|
929
930
|
// guidToDn is used for Active Directory to return dn based on objectGUID
|
|
930
931
|
//
|
|
931
|
-
const sidGuidToDn = async (baseEntity, id, ctx): Promise<string> => {
|
|
932
|
+
const sidGuidToDn = async (baseEntity: string, id: string, ctx: any): Promise<string> => {
|
|
932
933
|
const method = 'search'
|
|
933
934
|
const ldapOptions = {
|
|
934
935
|
attributes: ['dn'],
|
|
@@ -959,8 +960,8 @@ const sidGuidToDn = async (baseEntity, id, ctx): Promise<string> => {
|
|
|
959
960
|
// output: S-1-5-21-2657077294-4200173015-2627628055-1146
|
|
960
961
|
// ref: https://gist.github.com/Krizzzn/0ae47f280cca9749c67759a9adedc015
|
|
961
962
|
//
|
|
962
|
-
const pad = function (s) { if (s.length < 2) { return `0${s}` } else { return s } }
|
|
963
|
-
const convertSidToString = (buf) => {
|
|
963
|
+
const pad = function (s: any) { if (s.length < 2) { return `0${s}` } else { return s } }
|
|
964
|
+
const convertSidToString = (buf: any) => {
|
|
964
965
|
let asc: any, end: any
|
|
965
966
|
let i: number
|
|
966
967
|
if (buf == null) { return null }
|
|
@@ -997,7 +998,7 @@ const convertSidToString = (buf) => {
|
|
|
997
998
|
// output: 010500000000000515000000a065cf7e784b9b5fe77c8770091c0100
|
|
998
999
|
// ref: https://devblogs.microsoft.com/oldnewthing/20040315-00/?p=40253
|
|
999
1000
|
//
|
|
1000
|
-
const convertStringToSid = (sidStr) => {
|
|
1001
|
+
const convertStringToSid = (sidStr: string) => {
|
|
1001
1002
|
const arr = sidStr.split('-')
|
|
1002
1003
|
if (arr.length !== 8) return null
|
|
1003
1004
|
try {
|
|
@@ -1025,7 +1026,7 @@ const convertStringToSid = (sidStr) => {
|
|
|
1025
1026
|
// getMemberOfGroups returns all groups the user is member of
|
|
1026
1027
|
// [{ id: <id-group>> , displayName: <displayName-group>, members [{value: <id-user>}] }]
|
|
1027
1028
|
//
|
|
1028
|
-
const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
1029
|
+
const getMemberOfGroups = async (baseEntity: string, id: string, ctx: any) => {
|
|
1029
1030
|
const action = 'getMemberOfGroups'
|
|
1030
1031
|
if (!config.map.group) throw new Error('missing configuration endpoint.map.group') // not using groups
|
|
1031
1032
|
|
|
@@ -1075,7 +1076,7 @@ const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
|
1075
1076
|
|
|
1076
1077
|
try {
|
|
1077
1078
|
const groups: any = await doRequest(baseEntity, method, base, ldapOptions, ctx)
|
|
1078
|
-
return groups.map((grp) => {
|
|
1079
|
+
return groups.map((grp: any) => {
|
|
1079
1080
|
return { // { id: <id-group>> , displayName: <displayName-group>, members [{value: <id-user>}] }
|
|
1080
1081
|
id: encodeURIComponent(grp[attrs[0]]), // not mandatory, but included anyhow
|
|
1081
1082
|
displayName: grp[attrs[1]], // displayName is mandatory
|
|
@@ -1093,7 +1094,7 @@ const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
|
1093
1094
|
// using OpenLDAP, DN must be escaped - national characters and special ldap characters
|
|
1094
1095
|
// using Active Directory (none OpenLDAP), DN should not be escaped, but DN retrieved from AD is character escaped
|
|
1095
1096
|
//
|
|
1096
|
-
const ldapEscDn = (isOpenLdap, str) => {
|
|
1097
|
+
const ldapEscDn = (isOpenLdap: any, str: string) => {
|
|
1097
1098
|
if (typeof str !== 'string' || str.length < 1) return str
|
|
1098
1099
|
|
|
1099
1100
|
if (!isOpenLdap && str.indexOf('\\') > 0) {
|
|
@@ -1150,13 +1151,13 @@ const ldapEscDn = (isOpenLdap, str) => {
|
|
|
1150
1151
|
if (i === 0) {
|
|
1151
1152
|
const ua = new Uint8Array(Buffer.from(a[1], 'utf-8'))
|
|
1152
1153
|
const buf = Buffer.from(new Uint8Array([4, ua.length, ...ua]))
|
|
1153
|
-
const rdn = {}
|
|
1154
|
+
const rdn: any = {}
|
|
1154
1155
|
rdn[a[0]] = new BerReader(buf)
|
|
1155
1156
|
dn.push(new ldap.RDN(rdn))
|
|
1156
1157
|
// new BerReader(Buffer.from([0x04, 0x05, 0x4B, 0xc3, 0xbc, 0x72, 0x74])) // Kürt
|
|
1157
1158
|
// the leading 04 is the tag for "octet string" and the following 05 is the length in bytes of the string.
|
|
1158
1159
|
} else {
|
|
1159
|
-
const rdn = {}
|
|
1160
|
+
const rdn: any = {}
|
|
1160
1161
|
rdn[a[0]] = a[1]
|
|
1161
1162
|
dn.push(new ldap.RDN(rdn))
|
|
1162
1163
|
}
|
|
@@ -1172,7 +1173,7 @@ const ldapEscDn = (isOpenLdap, str) => {
|
|
|
1172
1173
|
// Hex encoded escaping (extended and unicode ascii) is not included because
|
|
1173
1174
|
// automatically handled by ldapjs when not using BER encoded DN
|
|
1174
1175
|
//
|
|
1175
|
-
const ldapEsc = (str) => {
|
|
1176
|
+
const ldapEsc = (str: any) => {
|
|
1176
1177
|
if (!str) return str
|
|
1177
1178
|
let newStr = ''
|
|
1178
1179
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -1228,7 +1229,7 @@ const ldapEsc = (str) => {
|
|
|
1228
1229
|
// only using BER on first part of dn
|
|
1229
1230
|
// Having BER decoding for Active Directory, but not for OpenLDAP
|
|
1230
1231
|
//
|
|
1231
|
-
const berDecodeDn = (dn) => {
|
|
1232
|
+
const berDecodeDn = (dn: any) => {
|
|
1232
1233
|
if (Object.prototype.toString.call(dn) !== '[object LdapDn]') return dn // OpenLDAP
|
|
1233
1234
|
const str = dn.toString()
|
|
1234
1235
|
if (str.indexOf('#') < 1) return str
|
|
@@ -1262,7 +1263,7 @@ const berDecodeDn = (dn) => {
|
|
|
1262
1263
|
return str
|
|
1263
1264
|
}
|
|
1264
1265
|
|
|
1265
|
-
const getNamingAttribute = (baseEntity, type) => {
|
|
1266
|
+
const getNamingAttribute = (baseEntity: string, type: string) => {
|
|
1266
1267
|
let arr
|
|
1267
1268
|
switch (type) {
|
|
1268
1269
|
case 'user':
|
|
@@ -1278,7 +1279,7 @@ const getNamingAttribute = (baseEntity, type) => {
|
|
|
1278
1279
|
return [arr[0].attribute, arr[0].mapTo]
|
|
1279
1280
|
}
|
|
1280
1281
|
|
|
1281
|
-
const checkIfNewDN = (baseEntity, base, type, obj, endpointObj) => {
|
|
1282
|
+
const checkIfNewDN = (baseEntity: string, base: any, type: string, obj: any, endpointObj: any) => {
|
|
1282
1283
|
if (typeof obj !== 'object' || Object.keys(obj).length < 1) return ''
|
|
1283
1284
|
if (typeof endpointObj !== 'object' || Object.keys(endpointObj).length < 1) return ''
|
|
1284
1285
|
|
|
@@ -1326,7 +1327,7 @@ const checkIfNewDN = (baseEntity, base, type, obj, endpointObj) => {
|
|
|
1326
1327
|
//
|
|
1327
1328
|
// getCtxAuth returns username/secret from ctx header when using Auth PassThrough
|
|
1328
1329
|
//
|
|
1329
|
-
const getCtxAuth = (ctx) => {
|
|
1330
|
+
const getCtxAuth = (ctx: any) => {
|
|
1330
1331
|
if (!ctx?.request?.header?.authorization) return []
|
|
1331
1332
|
const [authType, authToken] = (ctx.request.header.authorization || '').split(' ') // [0] = 'Basic' or 'Bearer'
|
|
1332
1333
|
let username, password
|
|
@@ -1338,7 +1339,7 @@ const getCtxAuth = (ctx) => {
|
|
|
1338
1339
|
//
|
|
1339
1340
|
// getServiceClient returns LDAP client used by doRequest
|
|
1340
1341
|
//
|
|
1341
|
-
const getServiceClient = async (baseEntity, ctx) => {
|
|
1342
|
+
const getServiceClient = async (baseEntity: string, ctx: any) => {
|
|
1342
1343
|
const action = 'getServiceClient'
|
|
1343
1344
|
// TODO if (!config.entity[baseEntity].passwordDecrypted) config.entity[baseEntity].passwordDecrypted = scimgateway.getPassword(`endpoint.entity.${baseEntity}.password`, configFile)
|
|
1344
1345
|
if (!config.entity[baseEntity].baseUrl) config.entity[baseEntity].baseUrl = config.entity[baseEntity].baseUrls[0] // failover logic also updates baseUrl
|
|
@@ -1390,7 +1391,7 @@ const getServiceClient = async (baseEntity, ctx) => {
|
|
|
1390
1391
|
// "attributes": ["sAMAccountName","displayName","mail"]
|
|
1391
1392
|
// }
|
|
1392
1393
|
//
|
|
1393
|
-
const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
1394
|
+
const doRequest = async (baseEntity: string, method: string, base: any, options: any, ctx: any) => {
|
|
1394
1395
|
if (!config.entity[baseEntity]) throw new Error(`unsupported baseEntity: ${baseEntity}`)
|
|
1395
1396
|
let result: any = null
|
|
1396
1397
|
let client: any = null
|
|
@@ -1413,15 +1414,15 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1413
1414
|
result = await new Promise((resolve, reject) => {
|
|
1414
1415
|
const results: any = []
|
|
1415
1416
|
|
|
1416
|
-
client.search(base, options, (err, search) => {
|
|
1417
|
+
client.search(base, options, (err: any, search: any) => {
|
|
1417
1418
|
if (err) {
|
|
1418
1419
|
return reject(err)
|
|
1419
1420
|
}
|
|
1420
1421
|
|
|
1421
|
-
search.on('searchEntry', (entry) => {
|
|
1422
|
+
search.on('searchEntry', (entry: any) => {
|
|
1422
1423
|
if (!entry.pojo || !entry.pojo.attributes) return
|
|
1423
1424
|
const obj: any = { dn: entry.pojo.objectName }
|
|
1424
|
-
entry.pojo.attributes.map((el) => {
|
|
1425
|
+
entry.pojo.attributes.map((el: any) => {
|
|
1425
1426
|
if (el.values.length > 1) obj[el.type] = el.values
|
|
1426
1427
|
else obj[el.type] = el.values[0]
|
|
1427
1428
|
return null
|
|
@@ -1449,7 +1450,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1449
1450
|
// for OpenLDAP ensure dn is not hex escaped e.g.: cn=K\c3\bcrt => cn=Kürt
|
|
1450
1451
|
// because dn may be be used as value in standard attributes like group memberOf
|
|
1451
1452
|
obj.dn = obj.dn.replace(/\\\\/g, '__') // temp
|
|
1452
|
-
let conv = obj.dn.replace(/\\([0-9A-Fa-f]{2})/g, (_, hex) => {
|
|
1453
|
+
let conv = obj.dn.replace(/\\([0-9A-Fa-f]{2})/g, (_: any, hex: any) => {
|
|
1453
1454
|
const intAscii = parseInt(hex, 16)
|
|
1454
1455
|
if (intAscii > 128) { // extended ascii - will be unescaped by decodeURIComponent
|
|
1455
1456
|
return '%' + hex
|
|
@@ -1470,12 +1471,12 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1470
1471
|
})
|
|
1471
1472
|
*/
|
|
1472
1473
|
|
|
1473
|
-
search.on('error', (err) => {
|
|
1474
|
+
search.on('error', (err: any) => {
|
|
1474
1475
|
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
1476
|
reject(err)
|
|
1476
1477
|
})
|
|
1477
1478
|
|
|
1478
|
-
search.on('end', (_) => { resolve(results) })
|
|
1479
|
+
search.on('end', (_: any) => { resolve(results) })
|
|
1479
1480
|
})
|
|
1480
1481
|
})
|
|
1481
1482
|
break
|
|
@@ -1506,7 +1507,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1506
1507
|
})
|
|
1507
1508
|
changes.push(change)
|
|
1508
1509
|
}
|
|
1509
|
-
client.modify(dn, changes, (err) => {
|
|
1510
|
+
client.modify(dn, changes, (err: any) => {
|
|
1510
1511
|
if (err) {
|
|
1511
1512
|
if (options.operation && options.operation === 'add') {
|
|
1512
1513
|
const msg = err.message.toLowerCase()
|
|
@@ -1526,7 +1527,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1526
1527
|
let newDN = options?.modification?.newDN
|
|
1527
1528
|
if (!newDN) return reject(new Error('modifyDN() missing newDN'))
|
|
1528
1529
|
if (Object.prototype.toString.call(newDN) === '[object LdapDn]') newDN = newDN.toString()
|
|
1529
|
-
client.modifyDN(dn, newDN, (err) => {
|
|
1530
|
+
client.modifyDN(dn, newDN, (err: any) => {
|
|
1530
1531
|
if (err) {
|
|
1531
1532
|
return reject(err)
|
|
1532
1533
|
}
|
|
@@ -1537,7 +1538,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1537
1538
|
|
|
1538
1539
|
case 'add':
|
|
1539
1540
|
result = await new Promise((resolve: any, reject: any) => {
|
|
1540
|
-
client.add(base, options, (err) => {
|
|
1541
|
+
client.add(base, options, (err: any) => {
|
|
1541
1542
|
if (err) {
|
|
1542
1543
|
return reject(err)
|
|
1543
1544
|
}
|
|
@@ -1548,7 +1549,7 @@ const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
|
1548
1549
|
|
|
1549
1550
|
case 'del':
|
|
1550
1551
|
result = await new Promise((resolve: any, reject: any) => {
|
|
1551
|
-
client.del(base, (err) => {
|
|
1552
|
+
client.del(base, (err: any) => {
|
|
1552
1553
|
if (err) {
|
|
1553
1554
|
return reject(err)
|
|
1554
1555
|
}
|