scimgateway 6.2.0 → 6.2.1

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 CHANGED
@@ -1305,16 +1305,22 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1305
1305
 
1306
1306
  ## Change log
1307
1307
 
1308
+ ### v6.2.1
1309
+
1310
+ [Fixed]
1311
+
1312
+ - `HelperRest`: fixed some minor log cosmetics introduced in v6.2.0
1313
+
1308
1314
  ### v6.2.0
1309
1315
 
1310
1316
  [Fixed]
1311
1317
 
1312
- - `helper-rest` failed on Bun v1.3.14 due to stricter compliance with Fetch standards.
1318
+ - `HelperRest`: failed on Bun v1.3.14 due to stricter compliance with Fetch standards.
1313
1319
 
1314
1320
  [Improved]
1315
1321
 
1316
1322
  - New `plugin-generic` replacing previous `plugin-scim`. This new plugin use the endpointMapper for flexible attribute mapping and also supports the new mapper option `valueMap` (e.g., group filtering and mapping). The default configuration uses one-to-one SCIM mapping, with plugin-loki as the target SCIM endpoint.
1317
- - endpointMapper now supports the 'valueMap' option
1323
+ - endpointMapper now supports the `valueMap` option
1318
1324
 
1319
1325
  Example configuration:
1320
1326
 
@@ -180,10 +180,7 @@
180
180
  },
181
181
  "userName": {
182
182
  "mapTo": "userName",
183
- "type": "string",
184
- "xvalueMap": {
185
- "bjensen": "Xbjensen"
186
- }
183
+ "type": "string"
187
184
  },
188
185
  "active": {
189
186
  "mapTo": "active",
@@ -435,10 +435,9 @@ export class HelperRest {
435
435
  private async getServiceClient(baseEntity: string, connectionObj: Record<string, any>, method: string, path: string, opt?: any, ctx?: any) {
436
436
  const action = 'getServiceClient'
437
437
  if (typeof connectionObj !== 'object' || connectionObj === null) connectionObj = {}
438
- let urlObj: any
439
438
  if (!path) path = ''
440
439
  try {
441
- urlObj = new URL(path)
440
+ new URL(path)
442
441
  } catch (err) {
443
442
  //
444
443
  // path (no url) - default approach and client will be cached based on config
@@ -473,18 +472,12 @@ export class HelperRest {
473
472
  const err = new Error(`missing connection configuration: baseUrls`)
474
473
  throw err
475
474
  }
476
- urlObj = new URL(connectionObj.baseUrls[0])
477
475
  const param: any = {
478
476
  baseUrl: connectionObj.baseUrls[0],
479
477
  options: {
480
- json: true, // json-object response instead of string
481
478
  headers: {
482
479
  Accept: 'application/json',
483
480
  },
484
- host: urlObj.hostname,
485
- port: urlObj.port, // null if https and 443 defined in url
486
- protocol: urlObj.protocol, // http: or https:
487
- // 'method' and 'path' added at the end
488
481
  },
489
482
  }
490
483
 
@@ -567,16 +560,9 @@ export class HelperRest {
567
560
  }
568
561
  const cli: any = structuredClone(this._serviceClient[baseEntity]) // client ready
569
562
 
570
- // failover support
571
- path = this._serviceClient[baseEntity].baseUrl + path
572
- urlObj = new URL(path)
573
- cli.options.host = urlObj.hostname
574
- cli.options.port = urlObj.port
575
- cli.options.protocol = urlObj.protocol
576
-
577
- // adding none static
578
563
  cli.options.method = method
579
- cli.options.path = `${urlObj.pathname}${urlObj.search}`
564
+ cli.options.url = this._serviceClient[baseEntity].baseUrl + path // failover supported
565
+
580
566
  if (opt) {
581
567
  if (opt?.connection) delete opt.connection // only used for internal connection options
582
568
  cli.options = utils.extendObj(cli.options, opt) // merge with argument options
@@ -589,15 +575,11 @@ export class HelperRest {
589
575
  //
590
576
  this.scimgateway.logDebug(baseEntity, `${action}: Using raw client`)
591
577
  let options: any = {
592
- json: true,
593
578
  headers: {
594
579
  Accept: 'application/json',
595
580
  },
596
- host: urlObj.hostname,
597
- port: urlObj.port,
598
- protocol: urlObj.protocol,
599
581
  method: method,
600
- path: urlObj.pathname + urlObj.search,
582
+ url: path,
601
583
  }
602
584
 
603
585
  // proxy
@@ -646,12 +628,13 @@ export class HelperRest {
646
628
  **/
647
629
  private async doRequestHandler(baseEntity: string, method: string, path: string, body?: any, ctx?: any, opt?: any, retryCount?: number): Promise<any> {
648
630
  const connectionObj = this.config_entity[baseEntity]?.connection ?? {}
631
+ let options: Record<any, any> = {}
649
632
  let retryAfter = 0
650
633
  try {
651
634
  const controller = new AbortController()
652
635
  const signal = controller.signal
653
636
  const cli = await this.getServiceClient(baseEntity, connectionObj, method, path, opt, ctx)
654
- const options = cli.options
637
+ options = cli.options
655
638
  const timeout = setTimeout(() => controller.abort(), options.abortTimeout ? options.abortTimeout * 1000 : this.idleTimeout * 1000) // 120 seconds default abort timeout
656
639
  options.signal = signal
657
640
 
@@ -676,39 +659,33 @@ export class HelperRest {
676
659
  options.body = dataString
677
660
  } else if (options.headers) delete options.headers['Content-Type']
678
661
 
679
- let url = `${options.protocol}//${options.host}${options.port ? ':' + options.port : ''}${options.path}`
680
- if (this._serviceClient[baseEntity]?.nextLink[url]) {
662
+ if (this._serviceClient[baseEntity]?.nextLink[options.url]) {
681
663
  if (ctx?.paging?.startIndex && ctx.paging.startIndex > 1) {
682
- if (ctx.paging.startIndex === this._serviceClient[baseEntity]?.nextLink[url].startIndex) {
683
- url = this._serviceClient[baseEntity]?.nextLink[url]['@odata.nextLink']
664
+ if (ctx.paging.startIndex === this._serviceClient[baseEntity]?.nextLink[options.url].startIndex) {
665
+ options.url = this._serviceClient[baseEntity]?.nextLink[options.url]['@odata.nextLink']
684
666
  } else {
685
667
  if (!ctx) ctx = {}
686
668
  if (!ctx.paging) ctx.paging = {}
687
- if (this._serviceClient[baseEntity]?.nextLink[url].totalResults
688
- && ctx.paging.startIndex > this._serviceClient[baseEntity]?.nextLink[url].totalResults) {
689
- ctx.paging.totalResults = this._serviceClient[baseEntity]?.nextLink[url].totalResults
669
+ if (this._serviceClient[baseEntity]?.nextLink[options.url].totalResults
670
+ && ctx.paging.startIndex > this._serviceClient[baseEntity]?.nextLink[options.url].totalResults) {
671
+ ctx.paging.totalResults = this._serviceClient[baseEntity]?.nextLink[options.url].totalResults
690
672
  return { body: { value: [] } }
691
673
  } else {
692
674
  // reset the paging cursor - none expected startIndex sequence, using default none paged url
693
675
  ctx.paging.startIndex = 1 // caller should check and return this new startIndex in final response
694
- delete this._serviceClient[baseEntity].nextLink[url]
676
+ delete this._serviceClient[baseEntity].nextLink[options.url]
695
677
  }
696
678
  }
697
679
  }
698
680
  } else {
699
- if (ctx?.paging?.startIndex > 1 && !this._serviceClient[baseEntity]?.nextLink[url]) { // no previous paging and invalid startIndex
681
+ if (ctx?.paging?.startIndex > 1 && !this._serviceClient[baseEntity]?.nextLink[options.url]) { // no previous paging and invalid startIndex
700
682
  ctx.paging.totalResults = ctx.paging.startIndex - 1
701
683
  return { body: { value: [] } }
702
684
  }
703
685
  }
704
686
 
705
- // Bun v1.3.14 became stricter and more aligned with standards.
706
- delete options.host
707
- delete options.port
708
- delete options.protocol
709
-
710
687
  // execute request
711
- const f = await fetch(url, options)
688
+ const f = await fetch(options.url, options)
712
689
  if (!f.status) throw new Error('Response missing status code')
713
690
 
714
691
  const result: any = {
@@ -733,7 +710,7 @@ export class HelperRest {
733
710
  }
734
711
  throw new Error(JSON.stringify(result))
735
712
  }
736
- this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${options.protocol}//${options.host}${(options.port ? `:${options.port}` : '')}${options.path} Body = ${JSON.stringify(body)} Response = ${JSON.stringify(result)}`)
713
+ this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${options.url} Body = ${JSON.stringify(body)} Response = ${JSON.stringify(result)}`)
737
714
 
738
715
  // OData paging logic
739
716
  // client prerequisite for enabling doRequest() OData paging support (see plugin-entra-id):
@@ -766,7 +743,7 @@ export class HelperRest {
766
743
  ctx.paging.totalResults = totalResults
767
744
  }
768
745
  } else { // no more paging
769
- const linkBase = decodeURIComponent(url.substring(0, url.indexOf('$skiptoken') - 1))
746
+ const linkBase = decodeURIComponent(options.url?.substring(0, options.url?.indexOf('$skiptoken') - 1))
770
747
  if (ctx?.paging?.startIndex && ctx.paging.startIndex > 1 && this._serviceClient[baseEntity]?.nextLink[linkBase]) {
771
748
  if (!this._serviceClient[baseEntity]?.nextLink[linkBase].isCount) { // final no count page
772
749
  const itemsPerPage = result.body.value.length
@@ -821,8 +798,8 @@ export class HelperRest {
821
798
  }
822
799
  } else {
823
800
  if (statusCode === 404) { // not logged as error e.g. getUser-manager
824
- this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
825
- } else this.scimgateway.logError(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
801
+ this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${options.url} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
802
+ } else this.scimgateway.logError(baseEntity, `doRequest ${method} ${options.url} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
826
803
  if (statusCode === 401) delete this._serviceClient[baseEntity]
827
804
  throw err
828
805
  }
@@ -52,6 +52,14 @@ const config = scimgateway.getConfig()
52
52
  scimgateway.authPassThroughAllowed = false
53
53
  // end - mandatory plugin initialization
54
54
 
55
+ const isAllowlistingUser = config.map?.user
56
+ ? Object.values(config.map.user).some((item: any) => typeof item?.valueMap === 'object' && Object.keys(item.valueMap).length > 0)
57
+ : false
58
+
59
+ const isAllowlistingGroup = config.map?.group
60
+ ? Object.values(config.map.group).some((item: any) => typeof item.valueMap === 'object' && Object.keys(item.valueMap).length > 0)
61
+ : false
62
+
55
63
  // =================================================
56
64
  // getUsers
57
65
  // =================================================
@@ -114,16 +122,13 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
114
122
  totalResults: null,
115
123
  }
116
124
 
117
- const isAllowlisting = config.map?.user
118
- ? Object.values(config.map.user).some((item: any) => typeof item?.valueMap === 'object' && Object.keys(item.valueMap).length > 0)
119
- : false
120
- let currentStartIndex = isAllowlisting ? 1 : targetStartIndex
125
+ let currentStartIndex = isAllowlistingUser ? 1 : targetStartIndex
121
126
  let allValidResources: any[] = []
122
127
  let totalSkipped = 0
123
128
  let targetTotalResults: number | null = null
124
129
  let iteration = 0
125
130
  const maxIterations = 5 // Safety limit for look-ahead fetching
126
- const resourcesNeeded = isAllowlisting ? targetStartIndex + targetCount - 1 : targetCount
131
+ const resourcesNeeded = isAllowlistingUser ? targetStartIndex + targetCount - 1 : targetCount
127
132
 
128
133
  try {
129
134
  while (allValidResources.length < resourcesNeeded && iteration < maxIterations) {
@@ -166,8 +171,8 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
166
171
 
167
172
  if (targetTotalResults === null) {
168
173
  // Target endpoint returned full list
169
- ret.totalResults = isAllowlisting ? allValidResources.length : targetStartIndex - 1 + allValidResources.length
170
- ret.Resources = isAllowlisting ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
174
+ ret.totalResults = isAllowlistingUser ? allValidResources.length : targetStartIndex - 1 + allValidResources.length
175
+ ret.Resources = isAllowlistingUser ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
171
176
  return ret
172
177
  }
173
178
 
@@ -185,8 +190,8 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
185
190
  }
186
191
  if (ctx?.paging && Object.hasOwn(ctx.paging, 'totalResults')) targetTotalResults = ctx.paging.totalResults
187
192
 
188
- ret.totalResults = (targetTotalResults !== null && targetTotalResults > totalSkipped) ? targetTotalResults - totalSkipped : (isAllowlisting ? allValidResources.length : targetStartIndex - 1 + allValidResources.length)
189
- ret.Resources = isAllowlisting ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
193
+ ret.totalResults = (targetTotalResults !== null && targetTotalResults > totalSkipped) ? targetTotalResults - totalSkipped : (isAllowlistingUser ? allValidResources.length : targetStartIndex - 1 + allValidResources.length)
194
+ ret.Resources = isAllowlistingUser ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
190
195
  if (!ret.startIndex) ret.startIndex = targetStartIndex
191
196
 
192
197
  return ret
@@ -345,16 +350,13 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
345
350
  }
346
351
  */
347
352
 
348
- const isAllowlisting = config.map?.group
349
- ? Object.values(config.map.group).some((item: any) => typeof item.valueMap === 'object' && Object.keys(item.valueMap).length > 0)
350
- : false
351
- let currentStartIndex = isAllowlisting ? 1 : targetStartIndex
353
+ let currentStartIndex = isAllowlistingGroup ? 1 : targetStartIndex
352
354
  let allValidResources: any[] = []
353
355
  let totalSkipped = 0
354
356
  let targetTotalResults: number | null = null
355
357
  let iteration = 0
356
358
  const maxIterations = 5 // Safety limit for look-ahead fetching
357
- const resourcesNeeded = isAllowlisting ? targetStartIndex + targetCount - 1 : targetCount
359
+ const resourcesNeeded = isAllowlistingGroup ? targetStartIndex + targetCount - 1 : targetCount
358
360
 
359
361
  try {
360
362
  while (allValidResources.length < resourcesNeeded && iteration < maxIterations) {
@@ -399,8 +401,8 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
399
401
 
400
402
  if (targetTotalResults === null) {
401
403
  // Target endpoint returned full list
402
- ret.totalResults = isAllowlisting ? allValidResources.length : targetStartIndex - 1 + allValidResources.length
403
- ret.Resources = isAllowlisting ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
404
+ ret.totalResults = isAllowlistingGroup ? allValidResources.length : targetStartIndex - 1 + allValidResources.length
405
+ ret.Resources = isAllowlistingGroup ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
404
406
  return ret
405
407
  }
406
408
 
@@ -418,8 +420,8 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
418
420
  }
419
421
  if (ctx?.paging && Object.hasOwn(ctx.paging, 'totalResults')) targetTotalResults = ctx.paging.totalResults
420
422
 
421
- ret.totalResults = (targetTotalResults !== null && targetTotalResults > totalSkipped) ? targetTotalResults - totalSkipped : (isAllowlisting ? allValidResources.length : targetStartIndex - 1 + allValidResources.length)
422
- ret.Resources = isAllowlisting ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
423
+ ret.totalResults = (targetTotalResults !== null && targetTotalResults > totalSkipped) ? targetTotalResults - totalSkipped : (isAllowlistingGroup ? allValidResources.length : targetStartIndex - 1 + allValidResources.length)
424
+ ret.Resources = isAllowlistingGroup ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
423
425
  if (!ret.startIndex) ret.startIndex = targetStartIndex
424
426
 
425
427
  return ret
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scimgateway",
3
- "version": "6.2.0",
3
+ "version": "6.2.1",
4
4
  "type": "module",
5
5
  "description": "Using SCIM protocol as a gateway for user provisioning to other endpoints",
6
6
  "author": "Jarle Elshaug <jarle.elshaug@gmail.com> (https://elshaug.xyz)",