scimgateway 5.0.2 → 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 +37 -11
- 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/lib/scimgateway.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { createServer as httpCreateServer } from 'node:http'
|
|
14
14
|
import { createServer as httpsCreateServer } from 'node:https'
|
|
15
|
-
import { type IncomingMessage, type ServerResponse } from 'http'
|
|
15
|
+
import { type IncomingMessage, type ServerResponse } from 'node:http'
|
|
16
16
|
import { createChecker } from 'is-in-subnet'
|
|
17
17
|
import { BearerStrategy, type IBearerStrategyOptionWithRequest } from 'passport-azure-ad'
|
|
18
18
|
import { fileURLToPath } from 'url'
|
|
@@ -32,8 +32,8 @@ export class ScimGateway {
|
|
|
32
32
|
private config: any
|
|
33
33
|
private logger: any
|
|
34
34
|
private gwName: string
|
|
35
|
-
private scimDef: any
|
|
36
|
-
private countries: any
|
|
35
|
+
private scimDef: any
|
|
36
|
+
private countries: any
|
|
37
37
|
private multiValueTypes: any
|
|
38
38
|
private replaceUsrGrp: any
|
|
39
39
|
private getMemberOf: any
|
|
@@ -475,13 +475,13 @@ export class ScimGateway {
|
|
|
475
475
|
const schemas = ['User', 'Group']
|
|
476
476
|
let customMerged = false
|
|
477
477
|
for (let i = 0; i < schemas.length; i++) {
|
|
478
|
-
const schema = this.scimDef.Schemas.Resources.find(el => el.name === schemas[i])
|
|
479
|
-
const customSchema = custom.find(el => el.name === schemas[i])
|
|
478
|
+
const schema = this.scimDef.Schemas.Resources.find((el: Record<string, any>) => el.name === schemas[i])
|
|
479
|
+
const customSchema = custom.find((el: Record<string, any>) => el.name === schemas[i])
|
|
480
480
|
if (schema && customSchema && Array.isArray(customSchema.attributes)) {
|
|
481
481
|
const arr1 = schema.attributes // core:1.0/2.0 schema
|
|
482
482
|
const arr2 = customSchema.attributes
|
|
483
|
-
schema.attributes = arr2.filter((arr2Obj) => { // only merge attributes (objects) having unique name into core schema
|
|
484
|
-
if (!arr1.some(arr1Obj => arr1Obj.name === arr2Obj.name)) {
|
|
483
|
+
schema.attributes = arr2.filter((arr2Obj: Record<string, any>) => { // only merge attributes (objects) having unique name into core schema
|
|
484
|
+
if (!arr1.some((arr1Obj: Record<string, any>) => arr1Obj.name === arr2Obj.name)) {
|
|
485
485
|
customMerged = true
|
|
486
486
|
if (!isScimv2) arr2Obj.schema = 'urn:scim:schemas:core:1.0'
|
|
487
487
|
return arr2Obj
|
|
@@ -529,7 +529,7 @@ export class ScimGateway {
|
|
|
529
529
|
}
|
|
530
530
|
|
|
531
531
|
// start auth methods - used by auth
|
|
532
|
-
const basic = async (baseEntity, method, authType, authToken): Promise<boolean> => {
|
|
532
|
+
const basic = async (baseEntity: string, method: string, authType: string, authToken: string): Promise<boolean> => {
|
|
533
533
|
return await new Promise((resolve, reject) => { // basic auth
|
|
534
534
|
if (authType !== 'Basic') resolve(false)
|
|
535
535
|
if (!found.Basic) resolve(false)
|
|
@@ -555,7 +555,7 @@ export class ScimGateway {
|
|
|
555
555
|
})
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
-
const bearerToken = async (baseEntity, method, authType, authToken): Promise<boolean> => {
|
|
558
|
+
const bearerToken = async (baseEntity: string, method: string, authType: string, authToken: string): Promise<boolean> => {
|
|
559
559
|
return await new Promise((resolve, reject) => { // bearer token
|
|
560
560
|
if (authType !== 'Bearer' || !authToken) resolve(false)
|
|
561
561
|
if (!found.BearerToken) resolve(false)
|
|
@@ -576,14 +576,15 @@ export class ScimGateway {
|
|
|
576
576
|
})
|
|
577
577
|
}
|
|
578
578
|
|
|
579
|
-
const bearerJwtAzure = async (baseEntity,
|
|
579
|
+
const bearerJwtAzure = async (baseEntity: string, method: string, authType: string, authToken: string): Promise<boolean> => {
|
|
580
580
|
return await new Promise((resolve, reject) => {
|
|
581
581
|
if (authType !== 'Bearer' || !found.BearerJwtAzure) resolve(false) // no azure bearer token
|
|
582
|
-
const jtoken = jwt.decode(authToken, { complete: true })
|
|
582
|
+
const jtoken: any = jwt.decode(authToken, { complete: true })
|
|
583
583
|
if (jtoken == null) resolve(false)
|
|
584
584
|
else if (!jtoken.payload['iss']) resolve(false)
|
|
585
585
|
if (jtoken?.payload['iss'].indexOf('https://sts.windows.net') !== 0) resolve(false)
|
|
586
|
-
|
|
586
|
+
const req = { headers: { authorization: `${authType} ${authToken}` } } // Node.js http.createServer type IncomingMessage - header supported by passport
|
|
587
|
+
passport.authenticate('oauth-bearer', { session: false }, (err: any, user: any, info: any) => {
|
|
587
588
|
if (err) { return reject(err) }
|
|
588
589
|
if (user) { // authenticated OK
|
|
589
590
|
const arr = this.config.scimgateway.auth.bearerJwtAzure
|
|
@@ -595,16 +596,16 @@ export class ScimGateway {
|
|
|
595
596
|
if (!arr[i].baseEntities.includes(baseEntity)) return reject(new Error(`baseEntity=${baseEntity} not allowed for user ${arr[i].tenantIdGUID} according to bearerJwtAzure configuration baseEntitites=${arr[i].baseEntities}`))
|
|
596
597
|
}
|
|
597
598
|
}
|
|
598
|
-
if (arr[i].readOnly === true &&
|
|
599
|
+
if (arr[i].readOnly === true && method !== 'GET') return reject(new Error(`only allowing readOnly for user ${arr[i].tenantIdGUID} according to bearerJwtAzure configuration readOnly=true`))
|
|
599
600
|
}
|
|
600
601
|
}
|
|
601
602
|
resolve(true)
|
|
602
603
|
} else reject(new Error(`Azure JWT authorization failed: ${info}`))
|
|
603
|
-
})(
|
|
604
|
+
})(req)
|
|
604
605
|
})
|
|
605
606
|
}
|
|
606
607
|
|
|
607
|
-
const jwtVerify = async (baseEntity, method, el, authToken) => { // used by bearerJwt
|
|
608
|
+
const jwtVerify = async (baseEntity: string, method: string, el: Record<string, any>, authToken: string) => { // used by bearerJwt
|
|
608
609
|
return await new Promise((resolve) => {
|
|
609
610
|
jwt.verify(authToken, (el.secret) ? el.secret : el.publicKeyContent, el.options, (err) => {
|
|
610
611
|
if (err != null) resolve(false)
|
|
@@ -622,9 +623,9 @@ export class ScimGateway {
|
|
|
622
623
|
})
|
|
623
624
|
}
|
|
624
625
|
|
|
625
|
-
const bearerJwt = async (baseEntity, method, authType, authToken): Promise<boolean> => {
|
|
626
|
+
const bearerJwt = async (baseEntity: string, method: string, authType: string, authToken: string): Promise<boolean> => {
|
|
626
627
|
if (authType !== 'Bearer' || !found.BearerJwt) return false // no standard jwt bearer token
|
|
627
|
-
const jtoken = jwt.decode(authToken, { complete: true })
|
|
628
|
+
const jtoken: any = jwt.decode(authToken, { complete: true })
|
|
628
629
|
if (jtoken == null) return false
|
|
629
630
|
if (jtoken?.payload['iss'] && jtoken?.payload['iss'].indexOf('https://sts.windows.net') === 0) return false // azure - handled by bearerJwtAzure
|
|
630
631
|
const promises: any = []
|
|
@@ -639,7 +640,7 @@ export class ScimGateway {
|
|
|
639
640
|
throw new Error('JWT authentication failed')
|
|
640
641
|
}
|
|
641
642
|
|
|
642
|
-
const bearerOAuth = async (baseEntity, method, authType, authToken): Promise<boolean> => {
|
|
643
|
+
const bearerOAuth = async (baseEntity: string, method: string, authType: string, authToken: string): Promise<boolean> => {
|
|
643
644
|
return await new Promise((resolve, reject) => { // bearer token
|
|
644
645
|
if (authType !== 'Bearer' || !authToken) resolve(false)
|
|
645
646
|
if (!found.BearerOAuth || !authToken) resolve(false)
|
|
@@ -710,7 +711,7 @@ export class ScimGateway {
|
|
|
710
711
|
const arrResolve = await Promise.all([
|
|
711
712
|
basic(ctx.routeObj.baseEntity, ctx.request.method, authType, authToken),
|
|
712
713
|
bearerToken(ctx.routeObj.baseEntity, ctx.request.method, authType, authToken),
|
|
713
|
-
bearerJwtAzure(ctx.routeObj.baseEntity, ctx, authType, authToken),
|
|
714
|
+
bearerJwtAzure(ctx.routeObj.baseEntity, ctx.request.method, authType, authToken),
|
|
714
715
|
bearerJwt(ctx.routeObj.baseEntity, ctx.request.method, authType, authToken),
|
|
715
716
|
bearerOAuth(ctx.routeObj.baseEntity, ctx.request.method, authType, authToken),
|
|
716
717
|
authPassThrough(ctx.routeObj.baseEntity, ctx.request.method, authType, authToken),
|
|
@@ -727,13 +728,13 @@ export class ScimGateway {
|
|
|
727
728
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] request authToken = ${authToken}`)
|
|
728
729
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] request jwt.decode(authToken) = ${JSON.stringify(jwt.decode(authToken))}`)
|
|
729
730
|
}
|
|
730
|
-
if (authType === 'Bearer') ctx.response.headers
|
|
731
|
-
else if (found.Basic) ctx.response.headers
|
|
731
|
+
if (authType === 'Bearer') ctx.response.headers.set('WWW-Authenticate', 'Bearer realm=""')
|
|
732
|
+
else if (found.Basic) ctx.response.headers.set('WWW-Authenticate', 'Basic realm=""')
|
|
732
733
|
if (ctx.request.url !== '/favicon.ico') logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${err.message}`)
|
|
733
734
|
return false
|
|
734
735
|
} catch (err: any) {
|
|
735
|
-
if (authType === 'Bearer') ctx.response.headers
|
|
736
|
-
else ctx.response.headers
|
|
736
|
+
if (authType === 'Bearer') ctx.response.headers.set('WWW-Authenticate', 'Bearer realm=""')
|
|
737
|
+
else ctx.response.headers.set('WWW-Authenticate', 'Basic realm=""')
|
|
737
738
|
if (pwErrCount < 3) {
|
|
738
739
|
pwErrCount += 1
|
|
739
740
|
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ctx.request.url} ${err.message}`)
|
|
@@ -757,8 +758,8 @@ export class ScimGateway {
|
|
|
757
758
|
|
|
758
759
|
const getHandlerSchemas = async (ctx: Context) => {
|
|
759
760
|
let tx = this.scimDef.Schemas
|
|
760
|
-
tx = utilsScim.addResources(tx,
|
|
761
|
-
tx = utilsScim.addSchemas(tx,
|
|
761
|
+
tx = utilsScim.addResources(tx, undefined, undefined, undefined)
|
|
762
|
+
tx = utilsScim.addSchemas(tx, isScimv2, undefined, undefined)
|
|
762
763
|
ctx.response.body = JSON.stringify(tx)
|
|
763
764
|
}
|
|
764
765
|
|
|
@@ -881,7 +882,7 @@ export class ScimGateway {
|
|
|
881
882
|
refresh_token: token, // ignored by scimgateway, but maybe used by client
|
|
882
883
|
}
|
|
883
884
|
|
|
884
|
-
ctx.response.headers
|
|
885
|
+
ctx.response.headers.set('Cache-Control', 'no-store')
|
|
885
886
|
ctx.response.body = JSON.stringify(tx)
|
|
886
887
|
ctx.response.status = 200
|
|
887
888
|
}
|
|
@@ -903,8 +904,8 @@ export class ScimGateway {
|
|
|
903
904
|
ctx.response.body = JSON.stringify(e)
|
|
904
905
|
return
|
|
905
906
|
}
|
|
906
|
-
if (ctx.query.attributes) ctx.query.attributes = ctx.query.attributes.split(',').map(item => item.trim()).join()
|
|
907
|
-
if (ctx.query.excludedAttributes) ctx.query.excludedAttributes = ctx.query.excludedAttributes.split(',').map(item => item.trim()).join()
|
|
907
|
+
if (ctx.query.attributes) ctx.query.attributes = ctx.query.attributes.split(',').map((item: string) => item.trim()).join()
|
|
908
|
+
if (ctx.query.excludedAttributes) ctx.query.excludedAttributes = ctx.query.excludedAttributes.split(',').map((item: string) => item.trim()).join()
|
|
908
909
|
|
|
909
910
|
const getObj = {
|
|
910
911
|
attribute: 'id',
|
|
@@ -917,7 +918,7 @@ export class ScimGateway {
|
|
|
917
918
|
let res
|
|
918
919
|
try {
|
|
919
920
|
const ob = utils.copyObj(getObj)
|
|
920
|
-
const attributes = ctx.query.attributes ? ctx.query.attributes.split(',').map(item => item.trim()) : []
|
|
921
|
+
const attributes = ctx.query.attributes ? ctx.query.attributes.split(',').map((item: string) => item.trim()) : []
|
|
921
922
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
922
923
|
const streamObj = {
|
|
923
924
|
func: handle.getMethod,
|
|
@@ -930,7 +931,7 @@ export class ScimGateway {
|
|
|
930
931
|
res = await this.pub.publish(streamObj)
|
|
931
932
|
} else {
|
|
932
933
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling "${handle.getMethod}" and awaiting result`)
|
|
933
|
-
res = await this[handle.getMethod](baseEntity, ob, attributes, ctx.passThrough)
|
|
934
|
+
res = await (this as any)[handle.getMethod](baseEntity, ob, attributes, ctx.passThrough)
|
|
934
935
|
}
|
|
935
936
|
|
|
936
937
|
let scimdata: { [key: string]: any } = {
|
|
@@ -968,7 +969,7 @@ export class ScimGateway {
|
|
|
968
969
|
|
|
969
970
|
userObj = utilsScim.addPrimaryAttrs(userObj)
|
|
970
971
|
scimdata = utils.stripObj(userObj, ctx.query.attributes, ctx.query.excludedAttributes)
|
|
971
|
-
scimdata = utilsScim.addSchemas(scimdata, handle.description,
|
|
972
|
+
scimdata = utilsScim.addSchemas(scimdata, isScimv2, handle.description, undefined)
|
|
972
973
|
|
|
973
974
|
if (!this.config.scimgateway.scim.skipMetaLocation) {
|
|
974
975
|
const location = ctx.origin + ctx.path
|
|
@@ -994,8 +995,8 @@ export class ScimGateway {
|
|
|
994
995
|
const getHandler = async (ctx: Context) => {
|
|
995
996
|
const handle = handler[ctx.routeObj.handle]
|
|
996
997
|
const baseEntity = ctx.routeObj.baseEntity
|
|
997
|
-
if (ctx.query.attributes) ctx.query.attributes = ctx.query.attributes.split(',').map(item => item.trim()).join()
|
|
998
|
-
if (ctx.query.excludedAttributes) ctx.query.excludedAttributes = ctx.query.excludedAttributes.split(',').map(item => item.trim()).join()
|
|
998
|
+
if (ctx.query.attributes) ctx.query.attributes = ctx.query.attributes.split(',').map((item: string) => item.trim()).join()
|
|
999
|
+
if (ctx.query.excludedAttributes) ctx.query.excludedAttributes = ctx.query.excludedAttributes.split(',').map((item: string) => item.trim()).join()
|
|
999
1000
|
|
|
1000
1001
|
const getObj: any = {
|
|
1001
1002
|
attribute: undefined,
|
|
@@ -1125,7 +1126,7 @@ export class ScimGateway {
|
|
|
1125
1126
|
|
|
1126
1127
|
let res: any
|
|
1127
1128
|
const obj: any = utils.copyObj(getObj)
|
|
1128
|
-
const attributes = ctx.query.attributes ? ctx.query.attributes.split(',').map(item => item.trim()) : []
|
|
1129
|
+
const attributes = ctx.query.attributes ? ctx.query.attributes.split(',').map((item: string) => item.trim()) : []
|
|
1129
1130
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
1130
1131
|
const streamObj = {
|
|
1131
1132
|
func: handle.getMethod,
|
|
@@ -1158,11 +1159,11 @@ export class ScimGateway {
|
|
|
1158
1159
|
}
|
|
1159
1160
|
}
|
|
1160
1161
|
if (getObjArr.length > 0) {
|
|
1161
|
-
const getObj = async (o) => {
|
|
1162
|
-
return await this[handle.getMethod](baseEntity, o, attributes, ctx.passThrough)
|
|
1162
|
+
const getObj = async (o: Record<string, any>) => {
|
|
1163
|
+
return await (this as any)[handle.getMethod](baseEntity, o, attributes, ctx.passThrough)
|
|
1163
1164
|
}
|
|
1164
1165
|
const chunk = 5
|
|
1165
|
-
const chunkRes = []
|
|
1166
|
+
const chunkRes: Record<string, any>[] = []
|
|
1166
1167
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling "${handle.getMethod}" with chunks and awaiting result`)
|
|
1167
1168
|
do {
|
|
1168
1169
|
const arrChunk = getObjArr.splice(0, chunk)
|
|
@@ -1183,7 +1184,7 @@ export class ScimGateway {
|
|
|
1183
1184
|
|
|
1184
1185
|
if (!res) { // standard
|
|
1185
1186
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling "${handle.getMethod}" and awaiting result`)
|
|
1186
|
-
res = await this[handle.getMethod](baseEntity, obj, attributes, ctx.passThrough)
|
|
1187
|
+
res = await (this as any)[handle.getMethod](baseEntity, obj, attributes, ctx.passThrough)
|
|
1187
1188
|
}
|
|
1188
1189
|
// check for user attribute groups and include if needed
|
|
1189
1190
|
if (Array.isArray(res?.Resources)) {
|
|
@@ -1221,7 +1222,7 @@ export class ScimGateway {
|
|
|
1221
1222
|
scimdata.Resources[i] = utils.stripObj(scimdata.Resources[i], ctx.query.attributes, ctx.query.excludedAttributes)
|
|
1222
1223
|
}
|
|
1223
1224
|
scimdata = utilsScim.addResources(scimdata, ctx.query.startIndex, ctx.query.sortBy, ctx.query.sortOrder)
|
|
1224
|
-
scimdata = utilsScim.addSchemas(scimdata, handle.description,
|
|
1225
|
+
scimdata = utilsScim.addSchemas(scimdata, isScimv2, handle.description, location)
|
|
1225
1226
|
|
|
1226
1227
|
ctx.response.body = JSON.stringify(scimdata)
|
|
1227
1228
|
} catch (err: any) {
|
|
@@ -1312,7 +1313,7 @@ export class ScimGateway {
|
|
|
1312
1313
|
}
|
|
1313
1314
|
}
|
|
1314
1315
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling "${handle.createMethod}" and awaiting result`)
|
|
1315
|
-
res = await this[handle.createMethod](baseEntity, scimdata, ctx.passThrough)
|
|
1316
|
+
res = await (this as any)[handle.createMethod](baseEntity, scimdata, ctx.passThrough)
|
|
1316
1317
|
}
|
|
1317
1318
|
for (const key in res) { // merge any result e.g: {'id': 'xxxx'}
|
|
1318
1319
|
jsonBody[key] = res[key]
|
|
@@ -1323,7 +1324,7 @@ export class ScimGateway {
|
|
|
1323
1324
|
try {
|
|
1324
1325
|
if (handle.createMethod === 'createUser') {
|
|
1325
1326
|
let ob = {}
|
|
1326
|
-
const attributes = []
|
|
1327
|
+
const attributes: string[] = []
|
|
1327
1328
|
if (jsonBody.userName) ob = { attribute: 'userName', operator: 'eq', value: jsonBody.userName }
|
|
1328
1329
|
else if (jsonBody.externalId) ob = { attribute: 'externalId', operator: 'eq', value: jsonBody.externalId }
|
|
1329
1330
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
@@ -1336,11 +1337,11 @@ export class ScimGateway {
|
|
|
1336
1337
|
}
|
|
1337
1338
|
res = await this.pub.publish(streamObj)
|
|
1338
1339
|
} else {
|
|
1339
|
-
res = await this[handle.getMethod](baseEntity, ob, attributes, ctx.passThrough)
|
|
1340
|
+
res = await (this as any)[handle.getMethod](baseEntity, ob, attributes, ctx.passThrough)
|
|
1340
1341
|
}
|
|
1341
1342
|
} else if (handle.createMethod === 'createGroup') {
|
|
1342
1343
|
let ob = {}
|
|
1343
|
-
const attributes = []
|
|
1344
|
+
const attributes: string[] = []
|
|
1344
1345
|
if (jsonBody.externalId) ob = { attribute: 'externalId', operator: 'eq', value: jsonBody.externalId }
|
|
1345
1346
|
else if (jsonBody.displayName) ob = { attribute: 'displayName', operator: 'eq', value: jsonBody.displayName }
|
|
1346
1347
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
@@ -1353,7 +1354,7 @@ export class ScimGateway {
|
|
|
1353
1354
|
}
|
|
1354
1355
|
res = await this.pub.publish(streamObj)
|
|
1355
1356
|
} else {
|
|
1356
|
-
res = await this[handle.getMethod](baseEntity, ob, attributes, ctx.passThrough)
|
|
1357
|
+
res = await (this as any)[handle.getMethod](baseEntity, ob, attributes, ctx.passThrough)
|
|
1357
1358
|
}
|
|
1358
1359
|
}
|
|
1359
1360
|
} catch (err) { void 0 }
|
|
@@ -1365,7 +1366,7 @@ export class ScimGateway {
|
|
|
1365
1366
|
}
|
|
1366
1367
|
|
|
1367
1368
|
if (addGrps.length > 0 && handle.createMethod === 'createUser') { // add group membership
|
|
1368
|
-
const addGroups = async (groupId) => {
|
|
1369
|
+
const addGroups = async (groupId: string) => {
|
|
1369
1370
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
1370
1371
|
const streamObj = {
|
|
1371
1372
|
func: handler.groups.modifyMethod,
|
|
@@ -1376,10 +1377,10 @@ export class ScimGateway {
|
|
|
1376
1377
|
}
|
|
1377
1378
|
return await this.pub.publish(streamObj)
|
|
1378
1379
|
} else {
|
|
1379
|
-
return await this[handler.groups.modifyMethod](baseEntity, groupId, { members: [{ value: jsonBody.id }] }, ctx.passThrough)
|
|
1380
|
+
return await (this as any)[handler.groups.modifyMethod](baseEntity, groupId, { members: [{ value: jsonBody.id }] }, ctx.passThrough)
|
|
1380
1381
|
}
|
|
1381
1382
|
}
|
|
1382
|
-
const res = await Promise.allSettled(addGrps.map(groupId => addGroups(groupId)))
|
|
1383
|
+
const res = await Promise.allSettled(addGrps.map((groupId: string) => addGroups(groupId)))
|
|
1383
1384
|
const errAdd = res.filter(result => result.status === 'rejected').map(result => result.reason.message)
|
|
1384
1385
|
if (errAdd.length > 0) {
|
|
1385
1386
|
const errMsg = `user created, but there are group membership errors: ${errAdd.join(', ')}`
|
|
@@ -1395,11 +1396,11 @@ export class ScimGateway {
|
|
|
1395
1396
|
const location = ctx.origin + `${ctx.path}/${jsonBody.id}`
|
|
1396
1397
|
if (!jsonBody.meta) jsonBody.meta = {}
|
|
1397
1398
|
jsonBody.meta.location = location
|
|
1398
|
-
ctx.response.headers
|
|
1399
|
+
ctx.response.headers.set('Location', location)
|
|
1399
1400
|
}
|
|
1400
1401
|
delete jsonBody.password
|
|
1401
1402
|
jsonBody = utilsScim.addPrimaryAttrs(jsonBody)
|
|
1402
|
-
jsonBody = utilsScim.addSchemas(jsonBody, handle.description,
|
|
1403
|
+
jsonBody = utilsScim.addSchemas(jsonBody, isScimv2, handle.description, undefined)
|
|
1403
1404
|
ctx.response.status = 201
|
|
1404
1405
|
ctx.response.body = JSON.stringify(jsonBody)
|
|
1405
1406
|
} catch (err: any) {
|
|
@@ -1449,7 +1450,7 @@ export class ScimGateway {
|
|
|
1449
1450
|
const groups = await getMemberOf(baseEntity, id, handler.groups.getMethod, ctx.passThrough)
|
|
1450
1451
|
if (Array.isArray(groups) && groups.length > 0) {
|
|
1451
1452
|
const revokeGroupMember = async (grpId: string) => {
|
|
1452
|
-
return await this[handler.groups.modifyMethod](baseEntity, grpId, { members: [{ operation: 'delete', value: id }] }, ctx.passThrough)
|
|
1453
|
+
return await (this as any)[handler.groups.modifyMethod](baseEntity, grpId, { members: [{ operation: 'delete', value: id }] }, ctx.passThrough)
|
|
1453
1454
|
}
|
|
1454
1455
|
await Promise.allSettled(groups.map((grp: any) => {
|
|
1455
1456
|
if (grp.value) return revokeGroupMember(grp.value)
|
|
@@ -1458,7 +1459,7 @@ export class ScimGateway {
|
|
|
1458
1459
|
}
|
|
1459
1460
|
}
|
|
1460
1461
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling "${handle.deleteMethod}" and awaiting result`)
|
|
1461
|
-
await this[handle.deleteMethod](baseEntity, id, ctx.passThrough)
|
|
1462
|
+
await (this as any)[handle.deleteMethod](baseEntity, id, ctx.passThrough)
|
|
1462
1463
|
}
|
|
1463
1464
|
ctx.response.status = 204
|
|
1464
1465
|
} catch (err: any) {
|
|
@@ -1480,8 +1481,8 @@ export class ScimGateway {
|
|
|
1480
1481
|
// example: {"members":[{"value":"bjensen"}],"schemas":["urn:scim:schemas:core:1.0"]}
|
|
1481
1482
|
//
|
|
1482
1483
|
const patchHandler = async (ctx: Context) => {
|
|
1483
|
-
if (ctx.query.attributes) ctx.query.attributes = ctx.query.attributes.split(',').map(item => item.trim()).join()
|
|
1484
|
-
if (ctx.query.excludedAttributes) ctx.query.excludedAttributes = ctx.query.excludedAttributes.split(',').map(item => item.trim()).join()
|
|
1484
|
+
if (ctx.query.attributes) ctx.query.attributes = ctx.query.attributes.split(',').map((item: string) => item.trim()).join()
|
|
1485
|
+
if (ctx.query.excludedAttributes) ctx.query.excludedAttributes = ctx.query.excludedAttributes.split(',').map((item: any) => item.trim()).join()
|
|
1485
1486
|
const handle = handler[ctx.routeObj.handle]
|
|
1486
1487
|
const baseEntity = ctx.routeObj.baseEntity
|
|
1487
1488
|
const id = ctx.routeObj.id ? decodeURIComponent(ctx.routeObj.id) : ctx.routeObj.id
|
|
@@ -1545,12 +1546,12 @@ export class ScimGateway {
|
|
|
1545
1546
|
res = await replaceUsrGrp(ctx.routeObj.handle, baseEntity, id, scimdata, this.config.scimgateway.scim.usePutSoftSync, ctx.passThrough)
|
|
1546
1547
|
} else {
|
|
1547
1548
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling "${handle.modifyMethod}" and awaiting result`)
|
|
1548
|
-
res = await this[handle.modifyMethod](baseEntity, id, scimdata, ctx.passThrough)
|
|
1549
|
+
res = await (this as any)[handle.modifyMethod](baseEntity, id, scimdata, ctx.passThrough)
|
|
1549
1550
|
}
|
|
1550
1551
|
}
|
|
1551
1552
|
|
|
1552
1553
|
if (groups.length > 0 && handle.modifyMethod === 'modifyUser') { // modify user includes groups, add/remove group membership
|
|
1553
|
-
const updateGroup = async (groupsObj) => {
|
|
1554
|
+
const updateGroup = async (groupsObj: Record<string, any>) => {
|
|
1554
1555
|
const groupId = groupsObj.value
|
|
1555
1556
|
const memberObj: any = { value: id }
|
|
1556
1557
|
if (groupsObj.operation) memberObj.operation = groupsObj.operation
|
|
@@ -1564,10 +1565,10 @@ export class ScimGateway {
|
|
|
1564
1565
|
}
|
|
1565
1566
|
return await this.pub.publish(streamObj)
|
|
1566
1567
|
} else {
|
|
1567
|
-
return await this[handler.groups.modifyMethod](baseEntity, groupId, { members: [memberObj] }, ctx.passThrough)
|
|
1568
|
+
return await (this as any)[handler.groups.modifyMethod](baseEntity, groupId, { members: [memberObj] }, ctx.passThrough)
|
|
1568
1569
|
}
|
|
1569
1570
|
}
|
|
1570
|
-
const res = await Promise.allSettled(groups.map(groupsObj => updateGroup(groupsObj)))
|
|
1571
|
+
const res = await Promise.allSettled(groups.map((groupsObj: Record<string, any>) => updateGroup(groupsObj)))
|
|
1571
1572
|
const errRes = res.filter(result => result.status === 'rejected').map(result => result.reason.message)
|
|
1572
1573
|
if (errRes.length > 0) {
|
|
1573
1574
|
const errMsg = `modify user group membership error: ${errRes.join(', ')}`
|
|
@@ -1581,7 +1582,7 @@ export class ScimGateway {
|
|
|
1581
1582
|
return
|
|
1582
1583
|
}
|
|
1583
1584
|
const ob = { attribute: 'id', operator: 'eq', value: id }
|
|
1584
|
-
const attributes = ctx.query.attributes ? ctx.query.attributes.split(',').map(item => item.trim()) : []
|
|
1585
|
+
const attributes = ctx.query.attributes ? ctx.query.attributes.split(',').map((item: string) => item.trim()) : []
|
|
1585
1586
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
1586
1587
|
const streamObj = {
|
|
1587
1588
|
func: handle.getMethod,
|
|
@@ -1594,7 +1595,7 @@ export class ScimGateway {
|
|
|
1594
1595
|
res = await this.pub.publish(streamObj)
|
|
1595
1596
|
} else {
|
|
1596
1597
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling "${handle.getMethod}" and awaiting result`)
|
|
1597
|
-
res = await this[handle.getMethod](baseEntity, ob, attributes, ctx.passThrough)
|
|
1598
|
+
res = await (this as any)[handle.getMethod](baseEntity, ob, attributes, ctx.passThrough)
|
|
1598
1599
|
}
|
|
1599
1600
|
}
|
|
1600
1601
|
|
|
@@ -1614,11 +1615,11 @@ export class ScimGateway {
|
|
|
1614
1615
|
}
|
|
1615
1616
|
if (!this.config.scimgateway.scim.skipMetaLocation) {
|
|
1616
1617
|
const location = ctx.origin + ctx.path
|
|
1617
|
-
ctx.response.headers
|
|
1618
|
+
ctx.response.headers.set('Location', location)
|
|
1618
1619
|
}
|
|
1619
1620
|
const userObj = utilsScim.addPrimaryAttrs(scimdata.Resources[0])
|
|
1620
1621
|
scimdata = utils.stripObj(userObj, ctx.query.attributes, ctx.query.excludedAttributes)
|
|
1621
|
-
scimdata = utilsScim.addSchemas(scimdata, handle.description,
|
|
1622
|
+
scimdata = utilsScim.addSchemas(scimdata, isScimv2, handle.description, undefined)
|
|
1622
1623
|
ctx.response.status = 200
|
|
1623
1624
|
ctx.response.body = JSON.stringify(scimdata)
|
|
1624
1625
|
} catch (err: any) {
|
|
@@ -1640,7 +1641,7 @@ export class ScimGateway {
|
|
|
1640
1641
|
|
|
1641
1642
|
// get current object
|
|
1642
1643
|
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling "${handle.getMethod}" and awaiting result`)
|
|
1643
|
-
const res = await this[handle.getMethod](baseEntity, { attribute: 'id', operator: 'eq', value: id }, [], ctxPassThrough)
|
|
1644
|
+
const res = await (this as any)[handle.getMethod](baseEntity, { attribute: 'id', operator: 'eq', value: id }, [], ctxPassThrough)
|
|
1644
1645
|
logger.debug(`${gwName}[${pluginName}][${baseEntity}] "${handle.getMethod}" result: ${res ? JSON.stringify(res) : ''}`)
|
|
1645
1646
|
let currentObj
|
|
1646
1647
|
if (res && res.Resources && Array.isArray(res.Resources)) {
|
|
@@ -1683,13 +1684,13 @@ export class ScimGateway {
|
|
|
1683
1684
|
// update object
|
|
1684
1685
|
if (Object.keys(scimdata).length > 0) {
|
|
1685
1686
|
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling "${handle.modifyMethod}" and awaiting result`)
|
|
1686
|
-
await this[handle.modifyMethod](baseEntity, id, scimdata, ctxPassThrough)
|
|
1687
|
+
await (this as any)[handle.modifyMethod](baseEntity, id, scimdata, ctxPassThrough)
|
|
1687
1688
|
}
|
|
1688
1689
|
|
|
1689
1690
|
// add/remove groups
|
|
1690
1691
|
if (!this.config.scimgateway.scim.groupMemberOfUser) {
|
|
1691
1692
|
if (objGroups && Array.isArray(objGroups) && !(usePutSoftSync && objGroups.length < 1)) { // only if groups included, { "groups": [] } will remove all existing
|
|
1692
|
-
if (typeof this[handler.groups.getMethod] !== 'function' || typeof this[handler.groups.modifyMethod] !== 'function') {
|
|
1693
|
+
if (typeof (this as any)[handler.groups.getMethod] !== 'function' || typeof (this as any)[handler.groups.modifyMethod] !== 'function') {
|
|
1693
1694
|
throw new Error('replaceUser error: put operation can not be fully completed for the user`s groups, methods like getGroups() and modifyGroup() are not implemented')
|
|
1694
1695
|
}
|
|
1695
1696
|
let currentGroups
|
|
@@ -1697,7 +1698,7 @@ export class ScimGateway {
|
|
|
1697
1698
|
else { // try to get current groups the standard way
|
|
1698
1699
|
let res
|
|
1699
1700
|
try {
|
|
1700
|
-
res = await this[handler.groups.getMethod](baseEntity, { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }, ['id', 'displayName'], ctxPassThrough)
|
|
1701
|
+
res = await (this as any)[handler.groups.getMethod](baseEntity, { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }, ['id', 'displayName'], ctxPassThrough)
|
|
1701
1702
|
logger.debug(`${gwName}[${pluginName}][${baseEntity}] "${handler.groups.getMethod}" result: ${res ? JSON.stringify(res) : ''}`)
|
|
1702
1703
|
} catch (err) { void 0 } // method may be implemented but throwing error like groups not supported/implemented
|
|
1703
1704
|
currentGroups = []
|
|
@@ -1711,7 +1712,7 @@ export class ScimGateway {
|
|
|
1711
1712
|
}
|
|
1712
1713
|
}
|
|
1713
1714
|
}
|
|
1714
|
-
currentGroups = currentGroups.map((el) => {
|
|
1715
|
+
currentGroups = currentGroups.map((el: Record<string, any>) => {
|
|
1715
1716
|
if (el.value) {
|
|
1716
1717
|
el.value = decodeURIComponent(el.value)
|
|
1717
1718
|
}
|
|
@@ -1747,22 +1748,22 @@ export class ScimGateway {
|
|
|
1747
1748
|
if (!found && currentGroups[i].value) removeGrps.push(currentGroups[i].value)
|
|
1748
1749
|
}
|
|
1749
1750
|
|
|
1750
|
-
const assignGroupMember = async (grpId) => {
|
|
1751
|
-
return await this[handler.groups.modifyMethod](baseEntity, grpId, { members: [{ value: id }] }, ctxPassThrough)
|
|
1751
|
+
const assignGroupMember = async (grpId: string) => {
|
|
1752
|
+
return await (this as any)[handler.groups.modifyMethod](baseEntity, grpId, { members: [{ value: id }] }, ctxPassThrough)
|
|
1752
1753
|
}
|
|
1753
1754
|
|
|
1754
|
-
const revokeGroupMember = async (grpId) => {
|
|
1755
|
-
return await this[handler.groups.modifyMethod](baseEntity, grpId, { members: [{ operation: 'delete', value: id }] }, ctxPassThrough)
|
|
1755
|
+
const revokeGroupMember = async (grpId: string) => {
|
|
1756
|
+
return await (this as any)[handler.groups.modifyMethod](baseEntity, grpId, { members: [{ operation: 'delete', value: id }] }, ctxPassThrough)
|
|
1756
1757
|
}
|
|
1757
1758
|
|
|
1758
1759
|
let errRevoke: string[] = []
|
|
1759
1760
|
if (!usePutSoftSync) { // default will remove any existing groups not included, usePutSoftSync=true prevents removing existing groups (only add groups)
|
|
1760
1761
|
const res: { [key: string]: any } = await Promise.allSettled(removeGrps.map(async grpId => revokeGroupMember(grpId)))
|
|
1761
|
-
errRevoke = res.filter(result => result.status === 'rejected').map(result => result.reason.message)
|
|
1762
|
+
errRevoke = res.filter((result: Record<string, any>) => result.status === 'rejected').map((result: Record<string, any>) => result.reason.message)
|
|
1762
1763
|
}
|
|
1763
1764
|
|
|
1764
1765
|
const res: { [key: string]: any } = await Promise.allSettled(addGrps.map(async grpId => assignGroupMember(grpId)))
|
|
1765
|
-
const errAssign: string[] = res.filter(result => result.status === 'rejected').map(result => result.reason.message)
|
|
1766
|
+
const errAssign: string[] = res.filter((result: Record<string, any>) => result.status === 'rejected').map((result: Record<string, any>) => result.reason.message)
|
|
1766
1767
|
|
|
1767
1768
|
let errMsg = ''
|
|
1768
1769
|
if (errRevoke.length > 0) errMsg = `revokeGroupMember errors: ${errRevoke.join(', ')}`
|
|
@@ -1830,7 +1831,7 @@ export class ScimGateway {
|
|
|
1830
1831
|
return
|
|
1831
1832
|
}
|
|
1832
1833
|
try {
|
|
1833
|
-
let result
|
|
1834
|
+
let result: Record<string, any>
|
|
1834
1835
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
1835
1836
|
const streamObj = {
|
|
1836
1837
|
func: 'postApi',
|
|
@@ -1893,7 +1894,7 @@ export class ScimGateway {
|
|
|
1893
1894
|
}
|
|
1894
1895
|
|
|
1895
1896
|
try {
|
|
1896
|
-
let result
|
|
1897
|
+
let result: Record<string, any>
|
|
1897
1898
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
1898
1899
|
const streamObj = {
|
|
1899
1900
|
func: 'putApi',
|
|
@@ -1955,7 +1956,7 @@ export class ScimGateway {
|
|
|
1955
1956
|
return
|
|
1956
1957
|
} else {
|
|
1957
1958
|
try {
|
|
1958
|
-
let result
|
|
1959
|
+
let result: Record<string, any>
|
|
1959
1960
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
1960
1961
|
const streamObj = {
|
|
1961
1962
|
func: 'patchApi',
|
|
@@ -2063,7 +2064,7 @@ export class ScimGateway {
|
|
|
2063
2064
|
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [DELETE ${ctx.routeObj.handle} ] id=${id}`)
|
|
2064
2065
|
try {
|
|
2065
2066
|
if (!id || id.includes('/')) throw new Error('missing id')
|
|
2066
|
-
let result
|
|
2067
|
+
let result: Record<string, any>
|
|
2067
2068
|
if (this.config.scimgateway.stream.publisher.enabled) {
|
|
2068
2069
|
const streamObj = {
|
|
2069
2070
|
func: 'deleteApi',
|
|
@@ -2102,7 +2103,7 @@ export class ScimGateway {
|
|
|
2102
2103
|
//
|
|
2103
2104
|
// GET = /AppRoles
|
|
2104
2105
|
//
|
|
2105
|
-
this.getAppRoles = async (baseEntity) => {
|
|
2106
|
+
this.getAppRoles = async (baseEntity: string) => {
|
|
2106
2107
|
return await stream.getAppRoles(this, baseEntity)
|
|
2107
2108
|
}
|
|
2108
2109
|
|
|
@@ -2110,14 +2111,14 @@ export class ScimGateway {
|
|
|
2110
2111
|
const getMemberOf = async (baseEntity: string, id: string, getMethod: string, ctxPassThrough: any) => {
|
|
2111
2112
|
const groups: object[] = []
|
|
2112
2113
|
if (getMethod !== 'getGroups') return groups
|
|
2113
|
-
if (typeof this[handler.groups.getMethod] !== 'function') return groups // method not implemented
|
|
2114
|
+
if (typeof (this as any)[handler.groups.getMethod] !== 'function') return groups // method not implemented
|
|
2114
2115
|
if (this.config.scimgateway.scim.groupMemberOfUser) return groups // only support user member of group
|
|
2115
2116
|
let res: any
|
|
2116
2117
|
try {
|
|
2117
2118
|
const ob = { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }
|
|
2118
2119
|
const attributes = ['id', 'displayName']
|
|
2119
2120
|
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling "${handler.groups.getMethod}" and awaiting result - groups to be included`)
|
|
2120
|
-
res = await this[handler.groups.getMethod](baseEntity, ob, attributes, ctxPassThrough)
|
|
2121
|
+
res = await (this as any)[handler.groups.getMethod](baseEntity, ob, attributes, ctxPassThrough)
|
|
2121
2122
|
} catch (err) { } // ignore errors
|
|
2122
2123
|
if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length > 0) {
|
|
2123
2124
|
for (let i = 0; i < res.Resources.length; i++) {
|
|
@@ -2153,7 +2154,7 @@ export class ScimGateway {
|
|
|
2153
2154
|
body: any
|
|
2154
2155
|
}
|
|
2155
2156
|
response: {
|
|
2156
|
-
headers: HeadersInit
|
|
2157
|
+
headers: Headers // HeadersInit
|
|
2157
2158
|
status?: number
|
|
2158
2159
|
body?: string
|
|
2159
2160
|
}
|
|
@@ -2262,7 +2263,7 @@ export class ScimGateway {
|
|
|
2262
2263
|
},
|
|
2263
2264
|
response: {
|
|
2264
2265
|
status: undefined,
|
|
2265
|
-
headers:
|
|
2266
|
+
headers: new Headers(),
|
|
2266
2267
|
body: undefined,
|
|
2267
2268
|
},
|
|
2268
2269
|
routeObj: {
|
|
@@ -2277,8 +2278,8 @@ export class ScimGateway {
|
|
|
2277
2278
|
ip: getIpFromHeader(request.headers) || directIp,
|
|
2278
2279
|
origin: getOriginFromHeader(request.headers) || url.origin,
|
|
2279
2280
|
passThrough: (found.PassThrough && this.authPassThroughAllowed) ? { headers: request.headers } : undefined,
|
|
2280
|
-
|
|
2281
2281
|
}
|
|
2282
|
+
|
|
2282
2283
|
url.searchParams.forEach((value, key) => {
|
|
2283
2284
|
ctx.query[key] = value
|
|
2284
2285
|
})
|
|
@@ -2454,12 +2455,12 @@ export class ScimGateway {
|
|
|
2454
2455
|
// node --experimental-strip-types index.ts
|
|
2455
2456
|
|
|
2456
2457
|
// return body from req
|
|
2457
|
-
async function getRequestBody(req): Promise<Buffer> {
|
|
2458
|
+
async function getRequestBody(req: any): Promise<Buffer> {
|
|
2458
2459
|
return new Promise((resolve, reject) => {
|
|
2459
2460
|
const body: Uint8Array[] = []
|
|
2460
2461
|
req.on('data', (chunk: Uint8Array) => body.push(chunk)) // Explicitly typing chunk
|
|
2461
2462
|
req.on('end', () => resolve(Buffer.concat(body)))
|
|
2462
|
-
req.on('error', err => reject(err))
|
|
2463
|
+
req.on('error', (err: Error) => reject(err))
|
|
2463
2464
|
})
|
|
2464
2465
|
}
|
|
2465
2466
|
|
|
@@ -2774,7 +2775,7 @@ export class ScimGateway {
|
|
|
2774
2775
|
**/
|
|
2775
2776
|
getArrayObject(obj: any, element: string, type: string): any {
|
|
2776
2777
|
if (obj[element]) { // element is case sensitive
|
|
2777
|
-
return obj[element].find(function (el) {
|
|
2778
|
+
return obj[element].find(function (el: Record<string, any>) {
|
|
2778
2779
|
return (el.type && (el.type).toLowerCase() === type.toLowerCase())
|
|
2779
2780
|
})
|
|
2780
2781
|
}
|