scimgateway 4.4.2 → 4.4.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 CHANGED
@@ -207,8 +207,8 @@ Below shows an example of config\plugin-saphana.json
207
207
  "payloadSize": null,
208
208
  "scim": {
209
209
  "version": "2.0",
210
- "customSchema": null,
211
210
  "skipTypeConvert" : false,
211
+ "skipMetaLocation" false,
212
212
  "usePutSoftSync" : false,
213
213
  "usePutGroupMemberOfUser": false
214
214
  },
@@ -339,14 +339,11 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
339
339
 
340
340
  - **port** - Gateway will listen on this port number. Clients (e.g. Provisioning Server) will be using this port number for communicating with the gateway.
341
341
 
342
- - **localhostonly** - true or false. False means gateway accepts incoming requests from all clients. True means traffic from only localhost (127.0.0.1) is accepted (gateway must then be installed on the CA Connector Server).
342
+ - **localhostonly** - true or false. False means gateway accepts incoming requests from all clients. True means traffic from only localhost (127.0.0.1) is accepted.
343
343
 
344
344
  - **payloadSize** - if not defined, default "1mb" will be used. There are cases which large groups could exceed default size and you may want to increase by setting your own size
345
345
 
346
- - **scim.version** - "1.1" or "2.0". Default is "2.0". For Symantec/Broadcom/CA Identity Manager "1.1" should be used.
347
-
348
- - **scim.customSchema** - filename of JSON file located in `<package-root>\config\schemas` containing custom schema attributes, see configuration notes
349
- **additional information**: Schemas, ServiceProviderConfig and ResourceType can be customized if `lib/scimdef-v2.js (or scimdef-v1.js)` exists. Original scimdef-v2.js/scimdef-v1.js can be copied from node_modules/scimgateway/lib to your plugin/lib and customized.
346
+ - **scim.version** - "1.1" or "2.0". Default is "2.0".
350
347
 
351
348
  - **scim.skipTypeConvert** - true or false, default false. Multivalue attributes supporting types e.g. emails, phoneNumbers, ims, photos, addresses, entitlements and x509Certificates (but not roles, groups and members) will be become "type converted objects" when sent to modifyUser and createUser. This for simplicity of checking attributes included and also for the endpointMapper method (used by plugin-ldap and plugin-entra-id), e.g.:
352
349
 
@@ -364,6 +361,7 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
364
361
  {"value": "jsmith@hotmail.com"}
365
362
  ]
366
363
 
364
+ - **scim.skipMetaLocation** - true or false, default false. If set to true, `meta.location` which contains protocol and hostname from request-url, will be excluded from response e.g. `"{...,meta":{"location":"https://my-company.com/<...>"}}`. If using reverse proxy and not including headers `X-Forwarded-Proto` and `X-Forwarded-Host`, originator will be the proxy and we might not want to expose internal protocol and hostname being used by the proxy request.
367
365
 
368
366
  - **scim.usePutSoftSync** - true or false, default false. `PUT /Users/bjensen` will replace the user bjensen with body content. If set to `true`, only PUT body content will be replaced. Any additional existing user attributes and groups supported by plugin will remain as-is.
369
367
 
@@ -406,7 +404,7 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
406
404
 
407
405
  `<FQDN>` is Fully Qualified Domain Name of the host having SCIM Gateway installed
408
406
 
409
- Note, when using Broadcom/CA Provisioning, the "certificate authority - CA" also have to be imported on the Connector Server. For self-signed certificate CA and the certificate (public key) is the same.
407
+ Note, when using Symantec/Broadcom/CA Provisioning, the "certificate authority - CA" also have to be imported on the Connector Server. For self-signed certificate CA and the certificate (public key) is the same.
410
408
 
411
409
  PFX / PKCS#12 bundle can be used instead of key/cert/ca e.g:
412
410
 
@@ -450,10 +448,12 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
450
448
 
451
449
  #### Configuration notes
452
450
 
453
- - Setting environment variable `SEED` will override default password seeding logic.
451
+ - Custom Schemas, ServiceProviderConfig and ResourceType can be used if `./lib/scimdef-v2.js or scimdef-v1.js` exists. Original scimdef-v2.js/scimdef-v1.js can be copied from node_modules/scimgateway/lib to your plugin/lib and customized.
452
+ - Using reverse proxy and we want ipAllowList and correct meta.location response, following headers must be set by proxy: `X-Forwarded-For`, `X-Forwarded-Proto` and `X-Forwarded-Host`
453
+ - Setting environment variable `SEED` with some random characters will override default password seeding logic. This also allow copying configuration file with encrypted secrets from one machine to another.
454
454
  - All configuration can be set based on environment variables. Syntax will then be `"process.env.<ENVIRONMENT>"` where `<ENVIRONMENT>` is the environment variable used. E.g. scimgateway.port could have value "process.env.PORT", then using environment variable PORT.
455
- - All configuration can be set based on corresponding JSON-content (dot notation) in external file using plugin name as parent JSON object. Syntax will then be `"process.file.<path>"` where `<path>` is the file used. E.g. endpoint.password could have value "process.file./var/run/vault/secrets.json"
456
- - Also, individual secret file may be used for a plain text secret per file. Syntax will then be `"process.text.<path>"` where `<path>` is the file which contains raw (`UTF-8`) character value. E.g. endpoint.password could have value "process.text./var/run/vault/endpoint.password". This enables that the config file itself be loaded from a ConfigMap while specific values are mounted either from `secrets.json` style files as mentioned above OR from traditional secrets files mounted in the file system, one value per file.
455
+ - All configuration values can be moved to a single external file having JSON dot notation content with plugin name as parent JSON object. Syntax in original configuration file used by the gateway will then be `"process.file.<path>"` where `<path>` is the file used. E.g. key endpoint.password could have value "process.file./var/run/vault/secrets.json"
456
+ - All configuration values can be moved to multiple external files, each file containing one single value. Syntax in original configuration file used by the gateway will then be `"process.text.<path>"` where `<path>` is the file which contains raw (`UTF-8`) character value. E.g. key endpoint.password could have value "process.text./var/run/vault/endpoint.password".
457
457
 
458
458
  Example:
459
459
 
@@ -489,7 +489,11 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
489
489
  }
490
490
 
491
491
 
492
- secrets.json for plugin-soap - example (dot notation):
492
+ jwt.secret file content example:
493
+
494
+ thisIsSecret
495
+
496
+ secrets.json file content example for plugin-soap:
493
497
 
494
498
  {
495
499
  "plugin-soap.scimgateway.auth.basic[0].username": "gwadmin",
@@ -498,62 +502,6 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
498
502
  "plugin-soap.endpoint.password": "secret"
499
503
  }
500
504
 
501
- - Custom schema attributes can be added by plugin configuration `scim.customSchema` having value set to filename of a JSON schema-file located in `<package-root>/config/schemas` e.g:
502
-
503
- "scim": {
504
- "version": "2.0",
505
- "customSchema": "plugin-soap-schema.json"
506
- },
507
-
508
- JSON file have following syntax:
509
-
510
- [
511
- {
512
- "name": "User",
513
- "attributes": [...]
514
- },
515
- {
516
- "name": "Group",
517
- "attributes": [...]
518
- }
519
- ]
520
-
521
- Where array `attributes` contains custom attribute objects according to SCIM 1.1 or 2.0 spesification e.g:
522
-
523
- "attributes": [
524
- {
525
- "name": "musicPreference",
526
- "type": "string",
527
- "multiValued": false,
528
- "description": "Music Preferences",
529
- "readOnly": false,
530
- "required": false,
531
- "caseExact": false
532
- },
533
- {
534
- "name": "populations",
535
- "type": "complex",
536
- "multiValued": true,
537
- "multiValuedAttributeChildName": "population",
538
- "description": "Population array",
539
- "readOnly": false,
540
- "required": false,
541
- "caseExact": false,
542
- "subAttributes": [
543
- {
544
- "name": "value",
545
- "type": "string",
546
- "multiValued": false,
547
- "description": "Population value",
548
- "readOnly": false,
549
- "required": true,
550
- "caseExact": false
551
- }
552
- ]
553
- }
554
- ]
555
-
556
- Note, custom schema attributes will be merged into core:1.0/2.0 schema, and names must not conflict with standard SCIM attribute names.
557
505
 
558
506
 
559
507
  ## Manual startup
@@ -745,7 +693,7 @@ Some notes related to Entra ID:
745
693
 
746
694
  ## CA Identity Manager as IdP using SCIM Gateway
747
695
 
748
- Using Symantec/Broadcom/CA Identity Manger, plugin configuration file must include **SCIM Version "1.1"** (scimgateway.scim.version).
696
+ Using Symantec/Broadcom/CA Identity Manger, plugin configuration might have to use **SCIM Version "1.1"** (scimgateway.scim.version).
749
697
 
750
698
  In the Provisioning Manager we have to use
751
699
 
@@ -1199,6 +1147,25 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1199
1147
 
1200
1148
  ## Change log
1201
1149
 
1150
+ ### v4.4.4
1151
+
1152
+ [Added]
1153
+
1154
+ - New configuration: **scim.skipMetaLocation**
1155
+ true or false, default false. If set to true, `meta.location` which contains protocol and hostname from request-url, will be excluded from response e.g. `"{...,meta":{"location":"https://my-company.com/<...>"}}`. If using reverse proxy and not including headers `X-Forwarded-Proto` and `X-Forwarded-Host`, originator will be the proxy and we might not want to expose internal protocol and hostname being used by the proxy request.
1156
+
1157
+ Below is an example of nginx reverse proxy configuration supporting SCIM Gateway ipAllowList and correct meta.location response:
1158
+
1159
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
1160
+ proxy_set_header X-Forwarded-Proto $scheme;
1161
+ proxy_set_header X-Forwarded-Host $http_host;
1162
+
1163
+ ### v4.4.3
1164
+
1165
+ [Added]
1166
+
1167
+ - Dependencies bump
1168
+
1202
1169
  ### v4.4.2
1203
1170
 
1204
1171
  [Added]
@@ -5,8 +5,8 @@
5
5
  "payloadSize": null,
6
6
  "scim": {
7
7
  "version": "2.0",
8
- "customSchema": null,
9
8
  "skipTypeConvert": false,
9
+ "skipMetaLocation": false,
10
10
  "usePutSoftSync": false,
11
11
  "usePutGroupMemberOfUser": false
12
12
  },
@@ -5,9 +5,10 @@
5
5
  "payloadSize": null,
6
6
  "scim": {
7
7
  "version": "2.0",
8
- "customSchema": null,
9
8
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
9
+ "skipMetaLocation": false,
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -5,8 +5,8 @@
5
5
  "payloadSize": null,
6
6
  "scim": {
7
7
  "version": "2.0",
8
- "customSchema": null,
9
8
  "skipTypeConvert": false,
9
+ "skipMetaLocation": false,
10
10
  "usePutSoftSync": false,
11
11
  "usePutGroupMemberOfUser": false
12
12
  },
@@ -5,8 +5,8 @@
5
5
  "payloadSize": null,
6
6
  "scim": {
7
7
  "version": "2.0",
8
- "customSchema": null,
9
8
  "skipTypeConvert": false,
9
+ "skipMetaLocation": false,
10
10
  "usePutSoftSync": false,
11
11
  "usePutGroupMemberOfUser": false
12
12
  },
@@ -5,8 +5,8 @@
5
5
  "payloadSize": null,
6
6
  "scim": {
7
7
  "version": "2.0",
8
- "customSchema": null,
9
8
  "skipTypeConvert": false,
9
+ "skipMetaLocation": false,
10
10
  "usePutSoftSync": false,
11
11
  "usePutGroupMemberOfUser": false
12
12
  },
@@ -5,8 +5,8 @@
5
5
  "payloadSize": null,
6
6
  "scim": {
7
7
  "version": "2.0",
8
- "customSchema": null,
9
8
  "skipTypeConvert": false,
9
+ "skipMetaLocation": false,
10
10
  "usePutSoftSync": false,
11
11
  "usePutGroupMemberOfUser": false
12
12
  },
@@ -5,8 +5,8 @@
5
5
  "payloadSize": null,
6
6
  "scim": {
7
7
  "version": "2.0",
8
- "customSchema": null,
9
8
  "skipTypeConvert": false,
9
+ "skipMetaLocation": false,
10
10
  "usePutSoftSync": false,
11
11
  "usePutGroupMemberOfUser": false
12
12
  },
@@ -5,8 +5,8 @@
5
5
  "payloadSize": null,
6
6
  "scim": {
7
7
  "version": "2.0",
8
- "customSchema": null,
9
8
  "skipTypeConvert": false,
9
+ "skipMetaLocation": false,
10
10
  "usePutSoftSync": false,
11
11
  "usePutGroupMemberOfUser": false
12
12
  },
@@ -5,8 +5,8 @@
5
5
  "payloadSize": null,
6
6
  "scim": {
7
7
  "version": "2.0",
8
- "customSchema": null,
9
8
  "skipTypeConvert": false,
9
+ "skipMetaLocation": false,
10
10
  "usePutSoftSync": false,
11
11
  "usePutGroupMemberOfUser": false
12
12
  },
@@ -36,7 +36,8 @@ const ScimGateway = function () {
36
36
  const startTime = utils.timestamp()
37
37
  const stack = callsite()
38
38
  const requester = stack[1].getFileName()
39
- const pluginName = path.basename(requester, '.js')
39
+ let pluginName = path.basename(requester)
40
+ pluginName = pluginName.substring(0, pluginName.lastIndexOf('.')) || pluginName
40
41
  const pluginDir = path.dirname(requester)
41
42
  const configDir = path.join(pluginDir, '..', 'config')
42
43
  const configFile = path.join(`${configDir}`, `${pluginName}.json`) // config name prefix same as pluging name prefix
@@ -272,7 +273,7 @@ const ScimGateway = function () {
272
273
  else scimDef = require('../lib/scimdef-v1')
273
274
  }
274
275
 
275
- if (config.scim.customSchema) { // merge plugin custom schema extension into core schemas
276
+ if (config.scim.customSchema) { // legacy - merge plugin custom schema extension into core schemas
276
277
  let custom
277
278
  try {
278
279
  custom = JSON.parse(fs.readFileSync(`${configDir}/schemas/${config.scim.customSchema}`, 'utf8'))
@@ -353,8 +354,8 @@ const ScimGateway = function () {
353
354
  ctx.response.body = { error: 'Access denied' }
354
355
  res.body = ctx.response.body
355
356
  }
356
- logger.error(`${gwName}[${pluginName}] ${ellapsed} ${ctx.request.ipcli} ${userName} ${ctx.request.method} ${ctx.request.href} Inbound = ${JSON.stringify(ctx.request.body)} Outbound = ${JSON.stringify(res)}${(config.log.loglevel.file === 'debug' && ctx.request.url !== '/ping') ? '\n' : ''}`)
357
- } else logger.info(`${gwName}[${pluginName}] ${ellapsed} ${ctx.request.ipcli} ${userName} ${ctx.request.method} ${ctx.request.href} Inbound = ${JSON.stringify(ctx.request.body)} Outbound = ${JSON.stringify(res)}${(config.log.loglevel.file === 'debug' && ctx.request.url !== '/ping') ? '\n' : ''}`)
357
+ logger.error(`${gwName}[${pluginName}] ${ellapsed} ${ctx.request.ip} ${userName} ${ctx.request.method} ${ctx.request.href} Inbound = ${JSON.stringify(ctx.request.body)} Outbound = ${JSON.stringify(res)}${(config.log.loglevel.file === 'debug' && ctx.request.url !== '/ping') ? '\n' : ''}`)
358
+ } else logger.info(`${gwName}[${pluginName}] ${ellapsed} ${ctx.request.ip} ${userName} ${ctx.request.method} ${ctx.request.href} Inbound = ${JSON.stringify(ctx.request.body)} Outbound = ${JSON.stringify(res)}${(config.log.loglevel.file === 'debug' && ctx.request.url !== '/ping') ? '\n' : ''}`)
358
359
  requestCounter += 1 // logged on exit (not win process termination)
359
360
  if (ctx.response.body && typeof ctx.response.body === 'object' && ctx.response.status !== 401) ctx.set('Content-Type', 'application/scim+json; charset=utf-8')
360
361
  }
@@ -604,7 +605,7 @@ const ScimGateway = function () {
604
605
  })
605
606
  }
606
607
  }
607
- } // authentication
608
+ }
608
609
 
609
610
  const verifyContentType = (ctx, next) => {
610
611
  return new Promise((resolve) => {
@@ -622,18 +623,16 @@ const ScimGateway = function () {
622
623
 
623
624
  const ipAllowList = (ctx, next) => {
624
625
  return new Promise((resolve) => {
625
- ctx.request.ipcli = ctx.req.headers['x-forwarded-for'] || ctx.req.connection.remoteAddress
626
- ctx.request.ipcli = ctx.request.ipcli.split(',')[0] // used by logResult
627
626
  if (!ipAllowListChecker) return resolve(next())
628
- if (ipAllowListChecker(ctx.request.ipcli)) return resolve(next())
629
- logger.debug(`${gwName}[${pluginName}] client ip ${ctx.request.ipcli} not in ipAllowList`)
627
+ if (ipAllowListChecker(ctx.request.ip)) return resolve(next()) // if proxy, prereq: request includes header X-Forwarded-For and koa app.proxy=true
628
+ logger.debug(`${gwName}[${pluginName}] client ip ${ctx.request.ip} not in ipAllowList`)
630
629
  ctx.status = 401
631
630
  ctx.body = { error: 'Access denied' }
632
631
  resolve(ctx)
633
632
  })
634
633
  }
635
634
 
636
- const app = new Koa()
635
+ const app = new Koa({ proxy: true })
637
636
  const router = new Router()
638
637
 
639
638
  // Middleware run in the order they are defined and communicates through ctx
@@ -652,7 +651,7 @@ const ScimGateway = function () {
652
651
  app.use(router.allowedMethods())
653
652
 
654
653
  app.on('error', (err, ctx) => { // catching none try/catch in app middleware, also bodyparser and body not json
655
- logger.error(`${gwName}[${pluginName}] Koa method: ${ctx.method} url: ${ctx.origin + ctx.path} body: ${JSON.stringify(ctx.request.body)} error: ${err.message}`)
654
+ logger.error(`${gwName}[${pluginName}] Koa method: ${ctx.method} url: ${ctx.request.origin + ctx.path} body: ${JSON.stringify(ctx.request.body)} error: ${err.message}`)
656
655
  })
657
656
 
658
657
  router.get('/ping', async (ctx) => { // auth not required
@@ -663,9 +662,8 @@ const ScimGateway = function () {
663
662
 
664
663
  // Google App Engine B-class instance start/stop request
665
664
  router.get(['/_ah/start', '/_ah/stop'], async (ctx) => {
666
- const cli = ctx.request.ipcli
667
665
  const ver = process.env.GAE_VERSION
668
- if (!cli || cli !== '0.1.0.3' || !ver || !ctx.origin.includes(`.${ver}.`)) { // ctx.origin = http://<instance>.<version>.<project-id>.<region>.r.appspot.com
666
+ if (ctx.request.ip !== '0.1.0.3' || !ver || !ctx.request.origin.includes(`.${ver}.`)) { // ctx.request.origin = http://<instance>.<version>.<project-id>.<region>.r.appspot.com
669
667
  ctx.status = 403 // request not coming from GCP App Engine
670
668
  return
671
669
  }
@@ -679,14 +677,16 @@ const ScimGateway = function () {
679
677
  router.get(['/(|scim/)(ServiceProviderConfigs|ServiceProviderConfig)',
680
678
  '/:baseEntity/(|scim/)(ServiceProviderConfigs|ServiceProviderConfig)'], async (ctx) => {
681
679
  const tx = scimDef.ServiceProviderConfigs
682
- const location = ctx.origin + ctx.path
683
- if (tx.meta) tx.meta.location = location
684
- else {
685
- tx.meta = {}
686
- tx.meta.location = location
680
+ if (!config.scim.skipMetaLocation) {
681
+ const location = ctx.request.origin + ctx.path
682
+ if (tx.meta) tx.meta.location = location
683
+ else {
684
+ tx.meta = {}
685
+ tx.meta.location = location
686
+ }
687
687
  }
688
688
  ctx.body = tx
689
- logger.debug(`${gwName}[${pluginName}] GET ${ctx.originalUrl} Response = ${JSON.stringify(tx)}`)
689
+ logger.debug(`${gwName}[${pluginName}] GET ${ctx.request.originalUrl} Response = ${JSON.stringify(tx)}`)
690
690
  })
691
691
 
692
692
  // Initial connection, step #2: GET /Schemas
@@ -819,7 +819,7 @@ const ScimGateway = function () {
819
819
  `/:baseEntity/(|scim/)(!${undefined}|Users|Groups|servicePlans)/:id`], async (ctx) => {
820
820
  if (ctx.query.attributes) ctx.query.attributes = ctx.query.attributes.split(',').map(item => item.trim()).join()
821
821
  if (ctx.query.excludedAttributes) ctx.query.excludedAttributes = ctx.query.excludedAttributes.split(',').map(item => item.trim()).join()
822
- let u = ctx.originalUrl.substr(0, ctx.originalUrl.lastIndexOf('/'))
822
+ let u = ctx.request.originalUrl.substr(0, ctx.request.originalUrl.lastIndexOf('/'))
823
823
  u = u.substr(u.lastIndexOf('/') + 1) // u = Users, Groups
824
824
  const handle = handler[u]
825
825
  const id = decodeURIComponent(require('path').basename(ctx.params.id, '.json')) // supports <id>.json
@@ -882,14 +882,16 @@ const ScimGateway = function () {
882
882
  }
883
883
  }
884
884
  }
885
- const location = ctx.origin + ctx.path
886
885
  userObj = addPrimaryAttrs(userObj)
887
886
  scimdata = utils.stripObj(userObj, ctx.query.attributes, ctx.query.excludedAttributes)
888
887
  scimdata = addSchemas(scimdata, handle.description, isScimv2)
889
- if (scimdata.meta) scimdata.meta.location = location
890
- else {
891
- scimdata.meta = {}
892
- scimdata.meta.location = location
888
+ if (!config.scim.skipMetaLocation) {
889
+ const location = ctx.request.origin + ctx.path
890
+ if (scimdata.meta) scimdata.meta.location = location
891
+ else {
892
+ scimdata.meta = {}
893
+ scimdata.meta.location = location
894
+ }
893
895
  }
894
896
  ctx.body = scimdata
895
897
  } catch (err) {
@@ -908,7 +910,7 @@ const ScimGateway = function () {
908
910
  '/:baseEntity/(|scim/)(Users|Groups|servicePlans|AppRoles)'], async (ctx) => {
909
911
  if (ctx.query.attributes) ctx.query.attributes = ctx.query.attributes.split(',').map(item => item.trim()).join()
910
912
  if (ctx.query.excludedAttributes) ctx.query.excludedAttributes = ctx.query.excludedAttributes.split(',').map(item => item.trim()).join()
911
- let u = ctx.originalUrl.substr(ctx.originalUrl.lastIndexOf('/') + 1) // u = Users, Groups, servicePlans, ...
913
+ let u = ctx.request.originalUrl.substr(ctx.request.originalUrl.lastIndexOf('/') + 1) // u = Users, Groups, servicePlans, ...
912
914
  const ui = u.indexOf('?')
913
915
  if (ui > 0) u = u.substr(0, ui)
914
916
  const handle = handler[u]
@@ -1088,8 +1090,9 @@ const ScimGateway = function () {
1088
1090
  }
1089
1091
  }
1090
1092
 
1091
- let location = ctx.origin + ctx.path
1093
+ let location = ctx.request.origin + ctx.path
1092
1094
  if (ctx.query.attributes || (ctx.query.excludedAttributes && ctx.query.excludedAttributes.includes('meta'))) location = null
1095
+ if (config.scim.skipMetaLocation) location = null
1093
1096
  for (let i = 0; i < scimdata.Resources.length; i++) {
1094
1097
  scimdata.Resources[i] = addPrimaryAttrs(scimdata.Resources[i])
1095
1098
  scimdata.Resources[i] = utils.stripObj(scimdata.Resources[i], ctx.query.attributes, ctx.query.excludedAttributes)
@@ -1124,7 +1127,7 @@ const ScimGateway = function () {
1124
1127
  //
1125
1128
  router.post([`/(|scim/)(!${undefined}|Users|Groups)(|.json)(|.xml)`,
1126
1129
  `/:baseEntity/(|scim/)(!${undefined}|Users|Groups)(|.json)(|.xml)`], async (ctx) => {
1127
- let u = ctx.originalUrl.substr(ctx.originalUrl.lastIndexOf('/') + 1) // u = Users<.json|.xml>, Groups<.json|.xml>
1130
+ let u = ctx.request.originalUrl.substr(ctx.request.originalUrl.lastIndexOf('/') + 1) // u = Users<.json|.xml>, Groups<.json|.xml>
1128
1131
  u = u.split('?')[0] // Users?AzureAdScimPatch062020
1129
1132
  const handle = handler[u.split('.')[0]]
1130
1133
  logger.debug(`${gwName}[${pluginName}] [Create ${handle.description}]`)
@@ -1153,7 +1156,7 @@ const ScimGateway = function () {
1153
1156
  return
1154
1157
  }
1155
1158
 
1156
- logger.debug(`${gwName}[${pluginName}] POST ${ctx.originalUrl} body=${strBody}`)
1159
+ logger.debug(`${gwName}[${pluginName}] POST ${ctx.request.originalUrl} body=${strBody}`)
1157
1160
  jsonBody = JSON.parse(strBody) // using a copy
1158
1161
  const [scimdata, err] = ScimGateway.prototype.convertedScim(jsonBody)
1159
1162
  logger.debug(`${gwName}[${pluginName}] convertedBody=${JSON.stringify(scimdata)}`)
@@ -1196,12 +1199,14 @@ const ScimGateway = function () {
1196
1199
  if (obj && obj.id) jsonBody = obj // id found, using returned object
1197
1200
  }
1198
1201
 
1199
- const location = `${ctx.origin}${ctx.path}/${jsonBody.id}`
1200
- if (!jsonBody.meta) jsonBody.meta = {}
1201
- jsonBody.meta.location = location
1202
+ if (!config.scim.skipMetaLocation) {
1203
+ const location = `${ctx.request.origin}${ctx.path}/${jsonBody.id}`
1204
+ if (!jsonBody.meta) jsonBody.meta = {}
1205
+ jsonBody.meta.location = location
1206
+ ctx.set('Location', location)
1207
+ }
1202
1208
  delete jsonBody.password
1203
1209
  jsonBody = addPrimaryAttrs(jsonBody)
1204
- ctx.set('Location', location)
1205
1210
  ctx.status = 201
1206
1211
  ctx.body = jsonBody
1207
1212
  } catch (err) {
@@ -1227,7 +1232,7 @@ const ScimGateway = function () {
1227
1232
  //
1228
1233
  router.delete([`/(|scim/)(!${undefined}|Users|Groups)/:id`,
1229
1234
  `/:baseEntity/(|scim/)(!${undefined}|Users|Groups)/:id`], async (ctx) => {
1230
- let u = ctx.originalUrl.substr(0, ctx.originalUrl.lastIndexOf('/'))
1235
+ let u = ctx.request.originalUrl.substr(0, ctx.request.originalUrl.lastIndexOf('/'))
1231
1236
  u = u.substr(u.lastIndexOf('/') + 1) // u = Users, Groups
1232
1237
  const handle = handler[u]
1233
1238
  const id = ctx.params.id
@@ -1273,7 +1278,7 @@ const ScimGateway = function () {
1273
1278
  `/:baseEntity/(|scim/)(!${undefined}|Users|Groups|servicePlans)/:id`], async (ctx) => {
1274
1279
  if (ctx.query.attributes) ctx.query.attributes = ctx.query.attributes.split(',').map(item => item.trim()).join()
1275
1280
  if (ctx.query.excludedAttributes) ctx.query.excludedAttributes = ctx.query.excludedAttributes.split(',').map(item => item.trim()).join()
1276
- let u = ctx.originalUrl.substr(0, ctx.originalUrl.lastIndexOf('/'))
1281
+ let u = ctx.request.originalUrl.substr(0, ctx.request.originalUrl.lastIndexOf('/'))
1277
1282
  u = u.substr(u.lastIndexOf('/') + 1) // u = Users, Groups
1278
1283
  const handle = handler[u]
1279
1284
  const id = decodeURIComponent(ctx.params.id)
@@ -1325,8 +1330,10 @@ const ScimGateway = function () {
1325
1330
  return
1326
1331
  }
1327
1332
 
1328
- const location = ctx.origin + ctx.path
1329
- ctx.set('Location', location)
1333
+ if (!config.scim.skipMetaLocation) {
1334
+ const location = ctx.request.origin + ctx.path
1335
+ ctx.set('Location', location)
1336
+ }
1330
1337
  const userObj = addPrimaryAttrs(scimdata.Resources[0])
1331
1338
  scimdata = utils.stripObj(userObj, ctx.query.attributes, ctx.query.excludedAttributes)
1332
1339
  scimdata = addSchemas(scimdata, handle.description, isScimv2)
@@ -1351,7 +1358,7 @@ const ScimGateway = function () {
1351
1358
  })
1352
1359
 
1353
1360
  const replaceUsrGrp = async (ctx) => {
1354
- let u = ctx.originalUrl.substr(0, ctx.originalUrl.lastIndexOf('/'))
1361
+ let u = ctx.request.originalUrl.substr(0, ctx.request.originalUrl.lastIndexOf('/'))
1355
1362
  u = u.substr(u.lastIndexOf('/') + 1) // u = Users, Groups
1356
1363
  const handle = handler[u]
1357
1364
  const id = ctx.params.id
@@ -1364,7 +1371,7 @@ const ScimGateway = function () {
1364
1371
  if (customErrorCode) ctx.status = customErrorCode
1365
1372
  ctx.body = e
1366
1373
  } else {
1367
- logger.debug(`${gwName}[${pluginName}] PUT ${ctx.originalUrl} body=${strBody}`)
1374
+ logger.debug(`${gwName}[${pluginName}] PUT ${ctx.request.originalUrl} body=${strBody}`)
1368
1375
  try {
1369
1376
  // get current object
1370
1377
  logger.debug(`${gwName}[${pluginName}] calling "${handle.getMethod}" and awaiting result`)
@@ -1553,8 +1560,10 @@ const ScimGateway = function () {
1553
1560
  }
1554
1561
  }
1555
1562
 
1556
- const location = ctx.origin + ctx.path
1557
- ctx.set('Location', location)
1563
+ if (!config.scim.skipMetaLocation) {
1564
+ const location = ctx.request.origin + ctx.path
1565
+ ctx.set('Location', location)
1566
+ }
1558
1567
  scimdata = addSchemas(scimdata, handle.description, isScimv2)
1559
1568
  ctx.status = 200
1560
1569
  ctx.body = scimdata
@@ -1587,10 +1596,8 @@ const ScimGateway = function () {
1587
1596
  ctx.body = apiErr(pluginName, err)
1588
1597
  } else {
1589
1598
  logger.debug(`${gwName}[${pluginName}] calling "postApi" and awaiting result`)
1590
-
1591
1599
  try {
1592
1600
  let result = await this.postApi(ctx.params.baseEntity, apiObj, ctx.ctxCopy)
1593
- const location = ctx.origin + ctx.path
1594
1601
  if (result) {
1595
1602
  if (typeof result === 'object') result = { result: result }
1596
1603
  else {
@@ -1603,7 +1610,10 @@ const ScimGateway = function () {
1603
1610
  } else result = {}
1604
1611
  if (!result.meta) result.meta = {}
1605
1612
  result.meta.result = 'success'
1606
- result.meta.location = location
1613
+ if (!config.scim.skipMetaLocation) {
1614
+ const location = ctx.request.origin + ctx.path
1615
+ result.meta.location = location
1616
+ }
1607
1617
  ctx.status = 201
1608
1618
  ctx.body = result
1609
1619
  } catch (err) {
@@ -1636,7 +1646,6 @@ const ScimGateway = function () {
1636
1646
 
1637
1647
  try {
1638
1648
  let result = await this.putApi(ctx.params.baseEntity, id, apiObj, ctx.ctxCopy)
1639
- const location = ctx.origin + ctx.path
1640
1649
  if (result) {
1641
1650
  if (typeof result === 'object') result = { result: result }
1642
1651
  else {
@@ -1649,7 +1658,10 @@ const ScimGateway = function () {
1649
1658
  } else result = {}
1650
1659
  if (!result.meta) result.meta = {}
1651
1660
  result.meta.result = 'success'
1652
- result.meta.location = location
1661
+ if (!config.scim.skipMetaLocation) {
1662
+ const location = ctx.request.origin + ctx.path
1663
+ result.meta.location = location
1664
+ }
1653
1665
  ctx.status = 200
1654
1666
  ctx.body = result
1655
1667
  } catch (err) {
@@ -1682,7 +1694,6 @@ const ScimGateway = function () {
1682
1694
 
1683
1695
  try {
1684
1696
  let result = await this.patchApi(ctx.params.baseEntity, id, apiObj, ctx.ctxCopy)
1685
- const location = ctx.origin + ctx.path
1686
1697
  if (result) {
1687
1698
  if (typeof result === 'object') result = { result: result }
1688
1699
  else {
@@ -1695,7 +1706,10 @@ const ScimGateway = function () {
1695
1706
  } else result = {}
1696
1707
  if (!result.meta) result.meta = {}
1697
1708
  result.meta.result = 'success'
1698
- result.meta.location = location
1709
+ if (!config.scim.skipMetaLocation) {
1710
+ const location = ctx.request.origin + ctx.path
1711
+ result.meta.location = location
1712
+ }
1699
1713
  ctx.status = 200
1700
1714
  ctx.body = result
1701
1715
  } catch (err) {
@@ -1726,7 +1740,6 @@ const ScimGateway = function () {
1726
1740
 
1727
1741
  try {
1728
1742
  let result = await this.getApi(ctx.params.baseEntity, ctx.params.id, ctx.query, apiObj, ctx.ctxCopy)
1729
- const location = ctx.origin + ctx.path
1730
1743
  if (result) {
1731
1744
  if (typeof result === 'object') result = { result: result }
1732
1745
  else {
@@ -1739,7 +1752,10 @@ const ScimGateway = function () {
1739
1752
  } else result = {}
1740
1753
  if (!result.meta) result.meta = {}
1741
1754
  result.meta.result = 'success'
1742
- result.meta.location = location
1755
+ if (!config.scim.skipMetaLocation) {
1756
+ const location = ctx.request.origin + ctx.path
1757
+ result.meta.location = location
1758
+ }
1743
1759
  ctx.status = 200
1744
1760
  ctx.body = result
1745
1761
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scimgateway",
3
- "version": "4.4.2",
3
+ "version": "4.4.4",
4
4
  "description": "Using SCIM protocol as a gateway for user provisioning to other endpoints",
5
5
  "author": "Jarle Elshaug <jarle.elshaug@gmail.com> (https://elshaug.xyz)",
6
6
  "homepage": "https://elshaug.xyz",
@@ -43,9 +43,9 @@
43
43
  "ldapjs": "^3.0.7",
44
44
  "lokijs": "^1.5.12",
45
45
  "mongodb": "^6.3.0",
46
- "nats": "^2.18.0",
46
+ "nats": "^2.19.0",
47
47
  "node-machine-id": "1.1.9",
48
- "nodemailer": "^6.9.8",
48
+ "nodemailer": "^6.9.9",
49
49
  "passport": "^0.7.0",
50
50
  "passport-azure-ad": "^4.3.5",
51
51
  "tedious": "^16.6.1",