scimgateway 4.2.3 → 4.2.5
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/.travis.yml +1 -1
- package/README.md +12 -0
- package/lib/plugin-api.js +55 -21
- package/lib/plugin-azure-ad.js +85 -45
- package/lib/plugin-forwardinc.js +35 -14
- package/lib/plugin-ldap.js +45 -29
- package/lib/plugin-mongodb.js +67 -19
- package/lib/plugin-mssql.js +58 -6
- package/lib/plugin-scim.js +55 -20
- package/package.json +2 -2
package/lib/plugin-scim.js
CHANGED
|
@@ -119,7 +119,7 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
try {
|
|
122
|
-
const response = await doRequest(baseEntity, method, path, body)
|
|
122
|
+
const response = await doRequest(baseEntity, method, path, body, ctx)
|
|
123
123
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
|
124
124
|
throw new Error(`${response.statusMessage} - ${JSON.stringify(response.body)}`)
|
|
125
125
|
} else if (!response.body) {
|
|
@@ -220,7 +220,7 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
try {
|
|
223
|
-
const response = await doRequest(baseEntity, method, path, body)
|
|
223
|
+
const response = await doRequest(baseEntity, method, path, body, ctx)
|
|
224
224
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
|
225
225
|
throw new Error(`${response.statusMessage} - ${JSON.stringify(response.body)}`)
|
|
226
226
|
}
|
|
@@ -330,7 +330,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
try {
|
|
333
|
-
const response = await doRequest(baseEntity, method, path, body)
|
|
333
|
+
const response = await doRequest(baseEntity, method, path, body, ctx)
|
|
334
334
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
|
335
335
|
throw new Error(`${response.statusMessage} - ${JSON.stringify(response.body)}`)
|
|
336
336
|
}
|
|
@@ -394,7 +394,7 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
try {
|
|
397
|
-
const response = await doRequest(baseEntity, method, path, body)
|
|
397
|
+
const response = await doRequest(baseEntity, method, path, body, ctx)
|
|
398
398
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
|
399
399
|
throw new Error(`${response.statusMessage} - ${JSON.stringify(response.body)}`)
|
|
400
400
|
} else if (!response.body) {
|
|
@@ -444,7 +444,7 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
|
|
|
444
444
|
const body = { displayName: groupObj.displayName }
|
|
445
445
|
|
|
446
446
|
try {
|
|
447
|
-
const response = await doRequest(baseEntity, method, path, body)
|
|
447
|
+
const response = await doRequest(baseEntity, method, path, body, ctx)
|
|
448
448
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
|
449
449
|
throw new Error(`${response.statusMessage} - ${JSON.stringify(response.body)}`)
|
|
450
450
|
}
|
|
@@ -466,7 +466,7 @@ scimgateway.deleteGroup = async (baseEntity, id, ctx) => {
|
|
|
466
466
|
const body = null
|
|
467
467
|
|
|
468
468
|
try {
|
|
469
|
-
const response = await doRequest(baseEntity, method, path, body)
|
|
469
|
+
const response = await doRequest(baseEntity, method, path, body, ctx)
|
|
470
470
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
|
471
471
|
throw new Error(`${response.statusMessage} - ${JSON.stringify(response.body)}`)
|
|
472
472
|
}
|
|
@@ -538,7 +538,7 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
538
538
|
const path = `/Groups/${id}`
|
|
539
539
|
|
|
540
540
|
try {
|
|
541
|
-
const response = await doRequest(baseEntity, method, path, body)
|
|
541
|
+
const response = await doRequest(baseEntity, method, path, body, ctx)
|
|
542
542
|
if (response.statusCode < 200 || response.statusCode > 299) {
|
|
543
543
|
throw new Error(`${response.statusMessage} - ${JSON.stringify(response.body)}`)
|
|
544
544
|
}
|
|
@@ -552,6 +552,24 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
552
552
|
// helpers
|
|
553
553
|
// =================================================
|
|
554
554
|
|
|
555
|
+
const getClientIdentifier = (ctx) => {
|
|
556
|
+
if (!ctx?.request?.header?.authorization) return undefined
|
|
557
|
+
const [user, secret] = getCtxAuth(ctx)
|
|
558
|
+
return `${encodeURIComponent(user)}_${encodeURIComponent(secret)}` // user_password or undefined_password
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
//
|
|
562
|
+
// getCtxAuth returns username/secret from ctx header when using Auth PassThrough
|
|
563
|
+
//
|
|
564
|
+
const getCtxAuth = (ctx) => { // eslint-disable-line
|
|
565
|
+
if (!ctx?.request?.header?.authorization) return []
|
|
566
|
+
const [authType, authToken] = (ctx.request.header.authorization || '').split(' ') // [0] = 'Basic' or 'Bearer'
|
|
567
|
+
let username, password
|
|
568
|
+
if (authType === 'Basic') [username, password] = (Buffer.from(authToken, 'base64').toString() || '').split(':')
|
|
569
|
+
if (username) return [username, password] // basic auth
|
|
570
|
+
else return [undefined, authToken] // bearer auth
|
|
571
|
+
}
|
|
572
|
+
|
|
555
573
|
//
|
|
556
574
|
// getServiceClient - returns options needed for connection parameters
|
|
557
575
|
//
|
|
@@ -561,7 +579,7 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
561
579
|
// path = url e.g. "http(s)://<host>:<port>/xxx/yyy", then using the url host/port/protocol
|
|
562
580
|
// opt (options) may be needed e.g {auth: {username: "username", password: "password"} }
|
|
563
581
|
//
|
|
564
|
-
const getServiceClient = async (baseEntity, method, path, opt) => {
|
|
582
|
+
const getServiceClient = async (baseEntity, method, path, opt, ctx) => {
|
|
565
583
|
const action = 'getServiceClient'
|
|
566
584
|
|
|
567
585
|
let urlObj
|
|
@@ -572,7 +590,8 @@ const getServiceClient = async (baseEntity, method, path, opt) => {
|
|
|
572
590
|
//
|
|
573
591
|
// path (no url) - default approach and client will be cached based on config
|
|
574
592
|
//
|
|
575
|
-
|
|
593
|
+
const clientIdentifier = getClientIdentifier(ctx)
|
|
594
|
+
if (_serviceClient[baseEntity] && _serviceClient[baseEntity][clientIdentifier]) { // serviceClient already exist
|
|
576
595
|
scimgateway.logger.debug(`${pluginName}[${baseEntity}] ${action}: Using existing client`)
|
|
577
596
|
} else {
|
|
578
597
|
scimgateway.logger.debug(`${pluginName}[${baseEntity}] ${action}: Client have to be created`)
|
|
@@ -589,7 +608,8 @@ const getServiceClient = async (baseEntity, method, path, opt) => {
|
|
|
589
608
|
json: true, // json-object response instead of string
|
|
590
609
|
headers: {
|
|
591
610
|
'Content-Type': 'application/json',
|
|
592
|
-
|
|
611
|
+
// Auth PassThrough or configuration, using ctx "AS-IS" header for PassThrough. For more advanced logic use getCtxAuth(ctx) - see examples in other plugins
|
|
612
|
+
Authorization: ctx?.request?.header?.authorization ? ctx.request.header.authorization : 'Basic ' + Buffer.from(`${config.entity[baseEntity].username}:${scimgateway.getPassword(`endpoint.entity.${baseEntity}.password`, configFile)}`).toString('base64')
|
|
593
613
|
},
|
|
594
614
|
host: urlObj.hostname,
|
|
595
615
|
port: urlObj.port, // null if https and 443 defined in url
|
|
@@ -609,13 +629,14 @@ const getServiceClient = async (baseEntity, method, path, opt) => {
|
|
|
609
629
|
}
|
|
610
630
|
|
|
611
631
|
if (!_serviceClient[baseEntity]) _serviceClient[baseEntity] = {}
|
|
612
|
-
_serviceClient[baseEntity] =
|
|
632
|
+
if (!_serviceClient[baseEntity][clientIdentifier]) _serviceClient[baseEntity][clientIdentifier] = {}
|
|
633
|
+
_serviceClient[baseEntity][clientIdentifier] = param // serviceClient created
|
|
613
634
|
}
|
|
614
635
|
|
|
615
|
-
const cli = scimgateway.copyObj(_serviceClient[baseEntity]) // client ready
|
|
636
|
+
const cli = scimgateway.copyObj(_serviceClient[baseEntity][clientIdentifier]) // client ready
|
|
616
637
|
|
|
617
638
|
// failover support
|
|
618
|
-
path = _serviceClient[baseEntity].baseUrl + path
|
|
639
|
+
path = _serviceClient[baseEntity][clientIdentifier].baseUrl + path
|
|
619
640
|
urlObj = new URL(path)
|
|
620
641
|
cli.options.host = urlObj.hostname
|
|
621
642
|
cli.options.port = urlObj.port
|
|
@@ -668,16 +689,16 @@ const getServiceClient = async (baseEntity, method, path, opt) => {
|
|
|
668
689
|
return cli // final client
|
|
669
690
|
}
|
|
670
691
|
|
|
671
|
-
const updateServiceClient = (baseEntity, obj) => {
|
|
672
|
-
if (_serviceClient[baseEntity]) _serviceClient[baseEntity] = scimgateway.extendObj(_serviceClient[baseEntity], obj) // merge with argument options
|
|
692
|
+
const updateServiceClient = (baseEntity, clientIdentifier, obj) => {
|
|
693
|
+
if (_serviceClient[baseEntity] && _serviceClient[baseEntity][clientIdentifier]) _serviceClient[baseEntity][clientIdentifier] = scimgateway.extendObj(_serviceClient[baseEntity][clientIdentifier], obj) // merge with argument options
|
|
673
694
|
}
|
|
674
695
|
|
|
675
696
|
//
|
|
676
697
|
// doRequest - execute REST service
|
|
677
698
|
//
|
|
678
|
-
const doRequest = async (baseEntity, method, path, body, opt, retryCount) => {
|
|
699
|
+
const doRequest = async (baseEntity, method, path, body, ctx, opt, retryCount) => {
|
|
679
700
|
try {
|
|
680
|
-
const cli = await getServiceClient(baseEntity, method, path, opt)
|
|
701
|
+
const cli = await getServiceClient(baseEntity, method, path, opt, ctx)
|
|
681
702
|
const options = cli.options
|
|
682
703
|
const result = await new Promise((resolve, reject) => {
|
|
683
704
|
let dataString = ''
|
|
@@ -732,15 +753,18 @@ const doRequest = async (baseEntity, method, path, body, opt, retryCount) => {
|
|
|
732
753
|
return result
|
|
733
754
|
} catch (err) { // includes failover/retry logic based on config baseUrls array
|
|
734
755
|
scimgateway.logger.error(`${pluginName}[${baseEntity}] doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
|
|
756
|
+
let statusCode
|
|
757
|
+
try { statusCode = JSON.parse(err.message).statusCode } catch (e) {}
|
|
758
|
+
const clientIdentifier = getClientIdentifier(ctx)
|
|
735
759
|
if (!retryCount) retryCount = 0
|
|
736
760
|
let urlObj
|
|
737
761
|
try { urlObj = new URL(path) } catch (err) {}
|
|
738
762
|
if (!urlObj && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND')) {
|
|
739
763
|
if (retryCount < config.entity[baseEntity].baseUrls.length) {
|
|
740
764
|
retryCount++
|
|
741
|
-
updateServiceClient(baseEntity, { baseUrl: config.entity[baseEntity].baseUrls[retryCount - 1] })
|
|
765
|
+
updateServiceClient(baseEntity, clientIdentifier, { baseUrl: config.entity[baseEntity].baseUrls[retryCount - 1] })
|
|
742
766
|
scimgateway.logger.debug(`${pluginName}[${baseEntity}] ${(config.entity[baseEntity].baseUrls.length > 1) ? 'failover ' : ''}retry[${retryCount}] using baseUrl = ${_serviceClient[baseEntity].baseUrl}`)
|
|
743
|
-
const ret = await doRequest(baseEntity, method, path, body, opt, retryCount) // retry
|
|
767
|
+
const ret = await doRequest(baseEntity, method, path, body, ctx, opt, retryCount) // retry
|
|
744
768
|
return ret // problem fixed
|
|
745
769
|
} else {
|
|
746
770
|
const newerr = new Error(err.message)
|
|
@@ -748,7 +772,18 @@ const doRequest = async (baseEntity, method, path, body, opt, retryCount) => {
|
|
|
748
772
|
newerr.message = newerr.message.replace('ENOTFOUND', 'UnableConnectingHost') // avoid returning ENOTFOUND error
|
|
749
773
|
throw newerr
|
|
750
774
|
}
|
|
751
|
-
} else
|
|
775
|
+
} else {
|
|
776
|
+
if (statusCode === 401) {
|
|
777
|
+
if (_serviceClient[baseEntity]) delete _serviceClient[baseEntity][clientIdentifier]
|
|
778
|
+
err.message = JSON.stringify( // don't reveal original message
|
|
779
|
+
{
|
|
780
|
+
statusCode: 401,
|
|
781
|
+
error: 'Access denied'
|
|
782
|
+
}
|
|
783
|
+
)
|
|
784
|
+
}
|
|
785
|
+
throw err // CA IM retries getUsers failure once (retry 6 times on ECONNREFUSED)
|
|
786
|
+
}
|
|
752
787
|
}
|
|
753
788
|
} // doRequest
|
|
754
789
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.5",
|
|
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",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"iga"
|
|
30
30
|
],
|
|
31
31
|
"engines": {
|
|
32
|
-
"node": ">=
|
|
32
|
+
"node": ">=14.0.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@godaddy/terminus": "^4.11.2",
|