scimgateway 4.2.2 → 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 +18 -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-loki.js +27 -18
- package/lib/plugin-mongodb.js +94 -37
- package/lib/plugin-mssql.js +58 -6
- package/lib/plugin-scim.js +55 -20
- package/lib/scimgateway.js +12 -26
- package/package.json +2 -2
package/lib/plugin-mssql.js
CHANGED
|
@@ -66,8 +66,10 @@ config = scimgateway.processExtConfig(pluginName, config) // add any external co
|
|
|
66
66
|
scimgateway.authPassThroughAllowed = false // true enables auth passThrough (no scimgateway authentication). scimgateway instead includes ctx (ctx.request.header) in plugin methods. Note, requires plugin-logic for handling/passing ctx.request.header.authorization to be used in endpoint communication
|
|
67
67
|
// mandatory plugin initialization - end
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
if (config?.connection?.authentication?.options?.password) {
|
|
70
|
+
const sqlPassword = scimgateway.getPassword('endpoint.connection.authentication.options.password', configFile)
|
|
71
|
+
config.connection.authentication.options.password = sqlPassword
|
|
72
|
+
}
|
|
71
73
|
|
|
72
74
|
// =================================================
|
|
73
75
|
// getUsers
|
|
@@ -119,7 +121,17 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
119
121
|
Resources: [],
|
|
120
122
|
totalResults: null
|
|
121
123
|
}
|
|
122
|
-
|
|
124
|
+
|
|
125
|
+
const connectionCfg = scimgateway.copyObj(config.connection)
|
|
126
|
+
if (ctx?.request?.header?.authorization) { // Auth PassThrough (don't use configuration password)
|
|
127
|
+
if (!connectionCfg.authentication) connectionCfg.authentication = {}
|
|
128
|
+
if (!connectionCfg.authentication.type) connectionCfg.authentication.type = 'default'
|
|
129
|
+
if (!connectionCfg.authentication.options) connectionCfg.authentication.options = {}
|
|
130
|
+
const [username, password] = getCtxAuth(ctx)
|
|
131
|
+
connectionCfg.authentication.options.password = password
|
|
132
|
+
if (username) connectionCfg.authentication.options.userName = username
|
|
133
|
+
}
|
|
134
|
+
const connection = new Connection(connectionCfg)
|
|
123
135
|
|
|
124
136
|
connection.on('connect', function (err) {
|
|
125
137
|
if (err) {
|
|
@@ -190,7 +202,16 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
|
|
|
190
202
|
Email: (userObj.emails.work.value) ? `'${userObj.emails.work.value}'` : null
|
|
191
203
|
}
|
|
192
204
|
|
|
193
|
-
const
|
|
205
|
+
const connectionCfg = scimgateway.copyObj(config.connection)
|
|
206
|
+
if (ctx?.request?.header?.authorization) { // Auth PassThrough (don't use configuration password)
|
|
207
|
+
if (!connectionCfg.authentication) connectionCfg.authentication = {}
|
|
208
|
+
if (!connectionCfg.authentication.type) connectionCfg.authentication.type = 'default'
|
|
209
|
+
if (!connectionCfg.authentication.options) connectionCfg.authentication.options = {}
|
|
210
|
+
const [username, password] = getCtxAuth(ctx)
|
|
211
|
+
connectionCfg.authentication.options.password = password
|
|
212
|
+
if (username) connectionCfg.authentication.options.userName = username
|
|
213
|
+
}
|
|
214
|
+
const connection = new Connection(connectionCfg)
|
|
194
215
|
|
|
195
216
|
connection.on('connect', function (err) {
|
|
196
217
|
if (err) {
|
|
@@ -227,7 +248,16 @@ scimgateway.deleteUser = async (baseEntity, id, ctx) => {
|
|
|
227
248
|
|
|
228
249
|
try {
|
|
229
250
|
return await new Promise((resolve, reject) => {
|
|
230
|
-
const
|
|
251
|
+
const connectionCfg = scimgateway.copyObj(config.connection)
|
|
252
|
+
if (ctx?.request?.header?.authorization) { // Auth PassThrough (don't use configuration password)
|
|
253
|
+
if (!connectionCfg.authentication) connectionCfg.authentication = {}
|
|
254
|
+
if (!connectionCfg.authentication.type) connectionCfg.authentication.type = 'default'
|
|
255
|
+
if (!connectionCfg.authentication.options) connectionCfg.authentication.options = {}
|
|
256
|
+
const [username, password] = getCtxAuth(ctx)
|
|
257
|
+
connectionCfg.authentication.options.password = password
|
|
258
|
+
if (username) connectionCfg.authentication.options.userName = username
|
|
259
|
+
}
|
|
260
|
+
const connection = new Connection(connectionCfg)
|
|
231
261
|
|
|
232
262
|
connection.on('connect', function (err) {
|
|
233
263
|
if (err) {
|
|
@@ -301,7 +331,17 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
301
331
|
}
|
|
302
332
|
|
|
303
333
|
sql = sql.substr(0, sql.length - 1) // remove trailing ","
|
|
304
|
-
|
|
334
|
+
|
|
335
|
+
const connectionCfg = scimgateway.copyObj(config.connection)
|
|
336
|
+
if (ctx?.request?.header?.authorization) { // Auth PassThrough (don't use configuration password)
|
|
337
|
+
if (!connectionCfg.authentication) connectionCfg.authentication = {}
|
|
338
|
+
if (!connectionCfg.authentication.type) connectionCfg.authentication.type = 'default'
|
|
339
|
+
if (!connectionCfg.authentication.options) connectionCfg.authentication.options = {}
|
|
340
|
+
const [username, password] = getCtxAuth(ctx)
|
|
341
|
+
connectionCfg.authentication.options.password = password
|
|
342
|
+
if (username) connectionCfg.authentication.options.userName = username
|
|
343
|
+
}
|
|
344
|
+
const connection = new Connection(connectionCfg)
|
|
305
345
|
|
|
306
346
|
connection.on('connect', function (err) {
|
|
307
347
|
if (err) {
|
|
@@ -397,6 +437,18 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
397
437
|
// helpers
|
|
398
438
|
// =================================================
|
|
399
439
|
|
|
440
|
+
//
|
|
441
|
+
// getCtxAuth returns username/secret from ctx header when using Auth PassThrough
|
|
442
|
+
//
|
|
443
|
+
const getCtxAuth = (ctx) => { // eslint-disable-line
|
|
444
|
+
if (!ctx?.request?.header?.authorization) return []
|
|
445
|
+
const [authType, authToken] = (ctx.request.header.authorization || '').split(' ') // [0] = 'Basic' or 'Bearer'
|
|
446
|
+
let username, password
|
|
447
|
+
if (authType === 'Basic') [username, password] = (Buffer.from(authToken, 'base64').toString() || '').split(':')
|
|
448
|
+
if (username) return [username, password] // basic auth
|
|
449
|
+
else return [undefined, authToken] // bearer auth
|
|
450
|
+
}
|
|
451
|
+
|
|
400
452
|
//
|
|
401
453
|
// Cleanup on exit
|
|
402
454
|
//
|
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/lib/scimgateway.js
CHANGED
|
@@ -2604,7 +2604,7 @@ const addSchemas = (data, type, isScimv2, location) => {
|
|
|
2604
2604
|
return data
|
|
2605
2605
|
}
|
|
2606
2606
|
|
|
2607
|
-
// addPrimaryAttrs cheks for primary attributes and add them as standalone attributes
|
|
2607
|
+
// addPrimaryAttrs cheks for primary attributes (only for roles) and add them as standalone attributes
|
|
2608
2608
|
// some IdP's may check for these e.g. Azure
|
|
2609
2609
|
// e.g. {roles: [{value: "val1", primary: "True"}]}
|
|
2610
2610
|
// gives:
|
|
@@ -2613,33 +2613,19 @@ const addSchemas = (data, type, isScimv2, location) => {
|
|
|
2613
2613
|
// roles[primary eq "True"].primary: "True"}]
|
|
2614
2614
|
// }
|
|
2615
2615
|
const addPrimaryAttrs = (obj) => {
|
|
2616
|
+
const key = 'roles'
|
|
2616
2617
|
if (!obj || typeof obj !== 'object') return obj
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
const o = utils.copyObj(obj)
|
|
2626
|
-
for (const key in o) {
|
|
2627
|
-
if (Array.isArray(o[key]) && key !== 'groups' && key !== 'members') {
|
|
2628
|
-
// check for primary attribute
|
|
2629
|
-
const index = o[key].findIndex(el => (el.primary === true || (typeof el.primary === 'string' && el.primary.toLowerCase() === 'true')))
|
|
2630
|
-
if (index >= 0) {
|
|
2631
|
-
const prim = o[key][index]
|
|
2632
|
-
for (const k in prim) {
|
|
2633
|
-
const primKey = `${key}[primary eq ${typeof prim.primary === 'string' ? `"${prim.primary}"` : prim.primary}].${k}` // roles[primary eq true].value / roles[primary eq "True"].value``
|
|
2634
|
-
o[primKey] = prim[k] // { roles[primary eq true].value : "some-value" }
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2618
|
+
if (!obj[key] || !Array.isArray(obj[key])) return obj
|
|
2619
|
+
const o = utils.copyObj(obj)
|
|
2620
|
+
const index = o[key].findIndex(el => (el.primary === true || (typeof el.primary === 'string' && el.primary.toLowerCase() === 'true')))
|
|
2621
|
+
if (index >= 0) {
|
|
2622
|
+
const prim = o[key][index]
|
|
2623
|
+
for (const k in prim) {
|
|
2624
|
+
const primKey = `${key}[primary eq ${typeof prim.primary === 'string' ? `"${prim.primary}"` : prim.primary}].${k}` // roles[primary eq true].value / roles[primary eq "True"].value``
|
|
2625
|
+
o[primKey] = prim[k] // { roles[primary eq true].value : "some-value" }
|
|
2638
2626
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
if (!Array.isArray(obj)) return arrRet[0]
|
|
2642
|
-
return arrRet
|
|
2627
|
+
}
|
|
2628
|
+
return o
|
|
2643
2629
|
}
|
|
2644
2630
|
|
|
2645
2631
|
//
|
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",
|