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 +35 -68
- package/config/plugin-api.json +1 -1
- package/config/plugin-entra-id.json +3 -2
- package/config/plugin-ldap.json +1 -1
- package/config/plugin-loki.json +1 -1
- package/config/plugin-mongodb.json +1 -1
- package/config/plugin-mssql.json +1 -1
- package/config/plugin-saphana.json +1 -1
- package/config/plugin-scim.json +1 -1
- package/config/plugin-soap.json +1 -1
- package/lib/scimgateway.js +66 -50
- package/package.json +3 -3
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
|
|
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".
|
|
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
|
-
-
|
|
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
|
|
456
|
-
-
|
|
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
|
-
|
|
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
|
|
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]
|
package/config/plugin-api.json
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
"payloadSize": null,
|
|
6
6
|
"scim": {
|
|
7
7
|
"version": "2.0",
|
|
8
|
-
"customSchema": null,
|
|
9
8
|
"skipTypeConvert": false,
|
|
10
|
-
"
|
|
9
|
+
"skipMetaLocation": false,
|
|
10
|
+
"usePutSoftSync": false,
|
|
11
|
+
"usePutGroupMemberOfUser": false
|
|
11
12
|
},
|
|
12
13
|
"log": {
|
|
13
14
|
"loglevel": {
|
package/config/plugin-ldap.json
CHANGED
package/config/plugin-loki.json
CHANGED
package/config/plugin-mssql.json
CHANGED
package/config/plugin-scim.json
CHANGED
package/config/plugin-soap.json
CHANGED
package/lib/scimgateway.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
357
|
-
} else logger.info(`${gwName}[${pluginName}] ${ellapsed} ${ctx.request.
|
|
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
|
-
}
|
|
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.
|
|
629
|
-
logger.debug(`${gwName}[${pluginName}] client ip ${ctx.request.
|
|
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 (
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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 (
|
|
890
|
-
|
|
891
|
-
scimdata.meta =
|
|
892
|
-
|
|
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
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
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
|
-
|
|
1329
|
-
|
|
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
|
-
|
|
1557
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
46
|
+
"nats": "^2.19.0",
|
|
47
47
|
"node-machine-id": "1.1.9",
|
|
48
|
-
"nodemailer": "^6.9.
|
|
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",
|