scimgateway 4.5.7 → 4.5.8
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 +43 -1
- package/config/plugin-ldap.json +15 -2
- package/lib/plugin-ldap.js +462 -73
- package/lib/scimgateway.js +49 -47
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Validated through IdP's:
|
|
|
16
16
|
|
|
17
17
|
Latest news:
|
|
18
18
|
|
|
19
|
-
- Supports stream publishing mode having [SCIM Stream](https://elshaug.xyz/docs/scim-stream) as a prerequisite. In this mode, standard incoming SCIM requests from your Identity Provider (IdP) or API are directed and published to the stream. Subsequently, one of the gateways subscribing to the channel utilized by the publisher will manage the SCIM request, and response back. Using SCIM Stream we have egress/outbound
|
|
19
|
+
- Supports stream publishing mode having [SCIM Stream](https://elshaug.xyz/docs/scim-stream) as a prerequisite. In this mode, standard incoming SCIM requests from your Identity Provider (IdP) or API are directed and published to the stream. Subsequently, one of the gateways subscribing to the channel utilized by the publisher will manage the SCIM request, and response back. Using SCIM Stream we have only egress/outbound traffic and get loadbalancing/failover by adding more gateways subscribing to the same channel.
|
|
20
20
|
- **BREAKING**: [SCIM Stream](https://elshaug.xyz/docs/scim-stream) is the modern way of user provisioning letting clients subscribe to messages instead of traditional IGA top-down provisioning. SCIM Gateway now offers enhanced functionality with support for message subscription and automated provisioning using SCIM Stream
|
|
21
21
|
- Authentication PassThrough letting plugin pass authentication directly to endpoint for avoid maintaining secrets at the gateway. Kubernetes health checks and shutdown handler support
|
|
22
22
|
- Supports OAuth Client Credentials authentication
|
|
@@ -1163,6 +1163,48 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1163
1163
|
|
|
1164
1164
|
## Change log
|
|
1165
1165
|
|
|
1166
|
+
### v4.5.8
|
|
1167
|
+
|
|
1168
|
+
[Fixed]
|
|
1169
|
+
|
|
1170
|
+
- plugin-ldap failed when using national special characters and some other LDAP special characters in DN
|
|
1171
|
+
|
|
1172
|
+
Note, plugin-ldap now has following new configuration:
|
|
1173
|
+
|
|
1174
|
+
"ldap": {
|
|
1175
|
+
"isOpenLdap": false,
|
|
1176
|
+
...
|
|
1177
|
+
"namingAttribute": {
|
|
1178
|
+
"user": [
|
|
1179
|
+
{
|
|
1180
|
+
"attribute": "CN",
|
|
1181
|
+
"mapTo": "userName"
|
|
1182
|
+
}
|
|
1183
|
+
],
|
|
1184
|
+
"group": [
|
|
1185
|
+
{
|
|
1186
|
+
"attribute": "CN",
|
|
1187
|
+
"mapTo": "displayName"
|
|
1188
|
+
}
|
|
1189
|
+
]
|
|
1190
|
+
},
|
|
1191
|
+
...
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
`isOpenLdap` true/false decides whether or not OpenLDAP Foundation protocol should be used for national characters and special characters in DN. For Active Directory, default isOpenLdap=false should be used.
|
|
1195
|
+
|
|
1196
|
+
`namingAttribute` can now be linked to scim `mapTo` attribute and is not hardcoded like it was in previous version.
|
|
1197
|
+
|
|
1198
|
+
Previous `userNamingAttr` and `groupNamingAttr` shown below, is now deprecated
|
|
1199
|
+
|
|
1200
|
+
"ldap": {
|
|
1201
|
+
...
|
|
1202
|
+
"userNamingAttr": "CN",
|
|
1203
|
+
"groupNamingAttr": "CN",
|
|
1204
|
+
...
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
|
|
1166
1208
|
### v4.5.7
|
|
1167
1209
|
|
|
1168
1210
|
[Fixed]
|
package/config/plugin-ldap.json
CHANGED
|
@@ -139,12 +139,25 @@
|
|
|
139
139
|
"username": "CN=Administrator,CN=Users,DC=test,DC=com",
|
|
140
140
|
"password": "password",
|
|
141
141
|
"ldap": {
|
|
142
|
+
"isOpenLdap": false,
|
|
142
143
|
"userBase": "CN=Users,DC=test,DC=com",
|
|
143
144
|
"groupBase": "OU=Groups,DC=test,DC=com",
|
|
144
145
|
"userFilter": null,
|
|
145
146
|
"groupFilter": null,
|
|
146
|
-
"
|
|
147
|
-
|
|
147
|
+
"namingAttribute": {
|
|
148
|
+
"user": [
|
|
149
|
+
{
|
|
150
|
+
"attribute": "CN",
|
|
151
|
+
"mapTo": "userName"
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"group": [
|
|
155
|
+
{
|
|
156
|
+
"attribute": "CN",
|
|
157
|
+
"mapTo": "displayName"
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
},
|
|
148
161
|
"userObjectClasses": [
|
|
149
162
|
"user",
|
|
150
163
|
"person",
|
package/lib/plugin-ldap.js
CHANGED
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
// ...
|
|
32
32
|
// }
|
|
33
33
|
//
|
|
34
|
+
// Configuration isOpenLdap true/false decides whether or not OpenLDAP Foundation protocol should
|
|
35
|
+
// be used for national characters and special characters in DN.
|
|
36
|
+
// For Active Directory, default isOpenLdap=false should be used
|
|
37
|
+
//
|
|
34
38
|
// Attributes according to map definition in the configuration file plugin-ldap.json:
|
|
35
39
|
//
|
|
36
40
|
// GlobalUser Template Scim Endpoint
|
|
@@ -70,6 +74,7 @@
|
|
|
70
74
|
'use strict'
|
|
71
75
|
|
|
72
76
|
const ldap = require('ldapjs')
|
|
77
|
+
const { BerReader } = require('@ldapjs/asn1')
|
|
73
78
|
|
|
74
79
|
// start - mandatory plugin initialization
|
|
75
80
|
let ScimGateway = null
|
|
@@ -86,33 +91,6 @@ config = scimgateway.processExtConfig(pluginName, config) // add any external co
|
|
|
86
91
|
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
|
|
87
92
|
// end - mandatory plugin initialization
|
|
88
93
|
|
|
89
|
-
if (!config.map || !config.map.user) {
|
|
90
|
-
scimgateway.logger.error(`${pluginName} map.user configuration is mandatory`)
|
|
91
|
-
process.exit(1)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
config.useSID_id = config.map.user.objectSid && config.map.user.objectSid.mapTo === 'id' // AD proprietary SID/GUID
|
|
95
|
-
config.useGUID_id = config.map.user.objectGUID && config.map.user.objectGUID.mapTo === 'id'
|
|
96
|
-
if (config.useSID_id && config.map.group) {
|
|
97
|
-
if (!config.map.group.objectSid || config.map.group.objectSid.mapTo !== 'id') {
|
|
98
|
-
scimgateway.logger.error(`${pluginName} missing configuration group.objectSid - user and group should be using the same attribute`)
|
|
99
|
-
process.exit(1)
|
|
100
|
-
}
|
|
101
|
-
} else if (config.useGUID_id && config.map.group) {
|
|
102
|
-
if (!config.map.group.objectGUID || config.map.group.objectGUID.mapTo !== 'id') {
|
|
103
|
-
scimgateway.logger.error(`${pluginName} missing configuration group.objectGUID - user and group should be using the same attribute`)
|
|
104
|
-
process.exit(1)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (config.map.user.userPrincipalName && config.map.user.userPrincipalName.mapDomain) { // support mapping different inbound/outbound upn domain names
|
|
108
|
-
if (config.map.user.userPrincipalName.mapDomain.inbound && config.map.user.userPrincipalName.mapDomain.outbound) {
|
|
109
|
-
const inbound = config.map.user.userPrincipalName.mapDomain.inbound
|
|
110
|
-
const outbound = config.map.user.userPrincipalName.mapDomain.outbound
|
|
111
|
-
config.map.user.userPrincipalName.mapDomain.inbound = inbound.startsWith('@') ? inbound : '@' + inbound // "@my-company.com"
|
|
112
|
-
config.map.user.userPrincipalName.mapDomain.outbound = outbound.startsWith('@') ? outbound : '@' + outbound // "@test.onmicrosoft.com
|
|
113
|
-
} else delete config.map.user.userPrincipalName.mapDomain
|
|
114
|
-
}
|
|
115
|
-
|
|
116
94
|
// =================================================
|
|
117
95
|
// getUsers
|
|
118
96
|
// =================================================
|
|
@@ -185,12 +163,12 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
185
163
|
attributes: attrs
|
|
186
164
|
}
|
|
187
165
|
} else { // search instead of lookup
|
|
166
|
+
const filter = createAndFilter(baseEntity, 'user', [{ attribute: userIdAttr, value: getObj.value }])
|
|
188
167
|
ldapOptions = {
|
|
189
|
-
filter
|
|
190
|
-
scope:
|
|
168
|
+
filter,
|
|
169
|
+
scope: 'sub',
|
|
191
170
|
attributes: attrs
|
|
192
171
|
}
|
|
193
|
-
if (config.entity[baseEntity].ldap.userFilter) ldapOptions.filter += config.entity[baseEntity].ldap.userFilter
|
|
194
172
|
}
|
|
195
173
|
}
|
|
196
174
|
} else if (getObj.operator === 'eq' && getObj.attribute === 'group.value') {
|
|
@@ -199,12 +177,14 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
199
177
|
} else {
|
|
200
178
|
// optional - simpel filtering
|
|
201
179
|
if (getObj.operator === 'eq') {
|
|
180
|
+
const [filterAttr, err] = scimgateway.endpointMapper('outbound', getObj.attribute, config.map.user)
|
|
181
|
+
if (err) throw new Error(`${action} error: ${err.message}`)
|
|
182
|
+
const filter = createAndFilter(baseEntity, 'user', [{ attribute: filterAttr, value: getObj.value }])
|
|
202
183
|
ldapOptions = {
|
|
203
|
-
filter
|
|
204
|
-
scope
|
|
184
|
+
filter,
|
|
185
|
+
scope,
|
|
205
186
|
attributes: attrs
|
|
206
187
|
}
|
|
207
|
-
if (config.entity[baseEntity].ldap.userFilter) ldapOptions.filter += config.entity[baseEntity].ldap.userFilter
|
|
208
188
|
} else {
|
|
209
189
|
throw new Error(`${action} error: not supporting simpel filtering: ${getObj.rawFilter}`)
|
|
210
190
|
}
|
|
@@ -214,12 +194,12 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
214
194
|
throw new Error(`${action} error: not supporting advanced filtering: ${getObj.rawFilter}`)
|
|
215
195
|
} else {
|
|
216
196
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all users to be returned - correspond to exploreUsers() in versions < 4.x.x
|
|
197
|
+
const filter = createAndFilter(baseEntity, 'user', [{ attribute: userIdAttr, value: '*' }])
|
|
217
198
|
ldapOptions = {
|
|
218
|
-
filter
|
|
219
|
-
scope
|
|
199
|
+
filter,
|
|
200
|
+
scope,
|
|
220
201
|
attributes: attrs
|
|
221
202
|
}
|
|
222
|
-
if (config.entity[baseEntity].ldap.userFilter) ldapOptions.filter += config.entity[baseEntity].ldap.userFilter
|
|
223
203
|
}
|
|
224
204
|
// end mandatory if-else logic
|
|
225
205
|
|
|
@@ -263,7 +243,7 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
263
243
|
}
|
|
264
244
|
|
|
265
245
|
const scimObj = scimgateway.endpointMapper('inbound', user, config.map.user)[0] // endpoint attribute naming => SCIM
|
|
266
|
-
if (!scimObj.groups) scimObj.groups = []
|
|
246
|
+
// if (!scimObj.groups) scimObj.groups = []
|
|
267
247
|
return scimObj
|
|
268
248
|
}))
|
|
269
249
|
} catch (err) {
|
|
@@ -317,8 +297,16 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
|
|
|
317
297
|
// endpointObj.objectClass is mandatory and must must match your ldap schema
|
|
318
298
|
endpointObj.objectClass = config.entity[baseEntity].ldap.userObjectClasses // Active Directory: ["user", "person", "organizationalPerson", "top"]
|
|
319
299
|
|
|
300
|
+
let base = ''
|
|
301
|
+
const [userNamingAttr, scimAttr] = getNamingAttribute(baseEntity, 'user') // ['CN', 'userName']
|
|
302
|
+
const arr = scimAttr.split('.')
|
|
303
|
+
if (arr.length < 2) {
|
|
304
|
+
base = `${userNamingAttr}=${userObj[scimAttr]},${userBase}`
|
|
305
|
+
} else {
|
|
306
|
+
base = `${userNamingAttr}=${userObj[arr[0]][arr[1]]},${userBase}`
|
|
307
|
+
}
|
|
308
|
+
|
|
320
309
|
const method = 'add'
|
|
321
|
-
const base = `${config.entity[baseEntity].ldap.userNamingAttr}=${userObj.userName},${userBase}`
|
|
322
310
|
const ldapOptions = endpointObj
|
|
323
311
|
|
|
324
312
|
try {
|
|
@@ -371,7 +359,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
371
359
|
delete attrObj.groups // make sure to be removed from attrObj
|
|
372
360
|
|
|
373
361
|
const [groupsAttr] = scimgateway.endpointMapper('outbound', 'groups.value', config.map.user)
|
|
374
|
-
const grp = { add: {
|
|
362
|
+
const grp = { add: {}, remove: {} }
|
|
375
363
|
grp.add[groupsAttr] = []
|
|
376
364
|
grp.remove[groupsAttr] = []
|
|
377
365
|
|
|
@@ -510,8 +498,8 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
510
498
|
totalResults: null
|
|
511
499
|
}
|
|
512
500
|
|
|
513
|
-
if (!config
|
|
514
|
-
scimgateway.logger.debug(`${pluginName}[${baseEntity}] "${action}"
|
|
501
|
+
if (!config?.map?.group || !config.entity[baseEntity]?.ldap?.groupBase) { // not using groups
|
|
502
|
+
scimgateway.logger.debug(`${pluginName}[${baseEntity}] "${action}" skip group handling - missing configuration endpoint.map.group or groupBase`)
|
|
515
503
|
return result
|
|
516
504
|
}
|
|
517
505
|
|
|
@@ -536,7 +524,7 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
536
524
|
// mandatory if-else logic - start
|
|
537
525
|
if (getObj.operator) {
|
|
538
526
|
if (getObj.operator === 'eq' && ['id', 'displayName', 'externalId'].includes(getObj.attribute)) {
|
|
539
|
-
|
|
527
|
+
// mandatory - unique filtering - single unique user to be returned - correspond to getUser() in versions < 4.x.x
|
|
540
528
|
if (getObj.attribute === 'id') { // lookup using dn or objectSid/objectGUID (Active Directory)
|
|
541
529
|
if (config.useSID_id) {
|
|
542
530
|
const sid = convertStringToSid(getObj.value) // sid using formatted string instead of default hex
|
|
@@ -566,12 +554,12 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
566
554
|
attributes: attrs
|
|
567
555
|
}
|
|
568
556
|
} else { // search instead of lookup
|
|
557
|
+
const filter = createAndFilter(baseEntity, 'group', [{ attribute: groupIdAttr, value: getObj.value }])
|
|
569
558
|
ldapOptions = {
|
|
570
|
-
filter
|
|
571
|
-
scope
|
|
559
|
+
filter,
|
|
560
|
+
scope,
|
|
572
561
|
attributes: attrs
|
|
573
562
|
}
|
|
574
|
-
if (config.entity[baseEntity].ldap.groupFilter) ldapOptions.filter += config.entity[baseEntity].ldap.groupFilter
|
|
575
563
|
}
|
|
576
564
|
}
|
|
577
565
|
} else if (getObj.operator === 'eq' && getObj.attribute === 'members.value') {
|
|
@@ -580,19 +568,30 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
580
568
|
ldapOptions = 'getMemberOfGroups'
|
|
581
569
|
} else {
|
|
582
570
|
// optional - simpel filtering
|
|
583
|
-
|
|
571
|
+
if (getObj.operator === 'eq') {
|
|
572
|
+
const [filterAttr, err] = scimgateway.endpointMapper('outbound', getObj.attribute, config.map.group)
|
|
573
|
+
if (err) throw new Error(`${action} error: ${err.message}`)
|
|
574
|
+
const filter = createAndFilter(baseEntity, 'group', [{ attribute: filterAttr, value: getObj.value }])
|
|
575
|
+
ldapOptions = {
|
|
576
|
+
filter,
|
|
577
|
+
scope,
|
|
578
|
+
attributes: attrs
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
throw new Error(`${action} error: not supporting simpel filtering: ${getObj.rawFilter}`)
|
|
582
|
+
}
|
|
584
583
|
}
|
|
585
584
|
} else if (getObj.rawFilter) {
|
|
586
585
|
// optional - advanced filtering having and/or/not - use getObj.rawFilter
|
|
587
586
|
throw new Error(`${action} error: not supporting advanced filtering: ${getObj.rawFilter}`)
|
|
588
587
|
} else {
|
|
589
|
-
|
|
588
|
+
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreGroups() in versions < 4.x.x
|
|
589
|
+
const filter = createAndFilter(baseEntity, 'group', [{ attribute: groupDisplayNameAttr, value: '*' }])
|
|
590
590
|
ldapOptions = {
|
|
591
|
-
filter
|
|
592
|
-
scope
|
|
591
|
+
filter,
|
|
592
|
+
scope,
|
|
593
593
|
attributes: attrs
|
|
594
594
|
}
|
|
595
|
-
if (config.entity[baseEntity].ldap.groupFilter) ldapOptions.filter += config.entity[baseEntity].ldap.groupFilter
|
|
596
595
|
}
|
|
597
596
|
// mandatory if-else logic - end
|
|
598
597
|
|
|
@@ -639,6 +638,7 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
|
|
|
639
638
|
scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" groupObj=${JSON.stringify(groupObj)}`)
|
|
640
639
|
|
|
641
640
|
if (!config.map.group) throw new Error(`${action} error: missing configuration endpoint.map.group`)
|
|
641
|
+
const groupBase = config.entity[baseEntity].ldap.groupBase
|
|
642
642
|
|
|
643
643
|
// convert SCIM attributes to endpoint attributes according to config.map
|
|
644
644
|
const [endpointObj] = scimgateway.endpointMapper('outbound', groupObj, config.map.group)
|
|
@@ -646,13 +646,23 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
|
|
|
646
646
|
// endpointObj.objectClass is mandatory and must must match your ldap schema
|
|
647
647
|
endpointObj.objectClass = config.entity[baseEntity].ldap.groupObjectClasses // Active Directory: ["group"]
|
|
648
648
|
|
|
649
|
+
let base = ''
|
|
650
|
+
const [groupNamingAttr, scimAttr] = getNamingAttribute(baseEntity, 'group') // ['CN', 'displayName']
|
|
651
|
+
const arr = scimAttr.split('.')
|
|
652
|
+
if (arr.length < 2) {
|
|
653
|
+
base = `${groupNamingAttr}=${groupObj[scimAttr]},${groupBase}`
|
|
654
|
+
} else {
|
|
655
|
+
base = `${groupNamingAttr}=${groupObj[arr[0]][arr[1]]},${groupBase}`
|
|
656
|
+
}
|
|
657
|
+
|
|
649
658
|
const method = 'add'
|
|
650
|
-
const base = `${config.entity[baseEntity].ldap.groupNamingAttr}=${groupObj.displayName},${config.entity[baseEntity].ldap.groupBase}`
|
|
651
659
|
const ldapOptions = endpointObj
|
|
652
660
|
|
|
653
661
|
try {
|
|
654
662
|
await doRequest(baseEntity, method, base, ldapOptions, ctx)
|
|
655
|
-
|
|
663
|
+
const res = await scimgateway.getGroups(baseEntity, { attribute: 'id', operator: 'eq', value: base }, [], ctx)
|
|
664
|
+
if (res && Array.isArray(res.Resources) && res.Resources.length === 1) return res.Resources[0]
|
|
665
|
+
else return null
|
|
656
666
|
} catch (err) {
|
|
657
667
|
const newErr = new Error(`${action} error: ${err.message}`)
|
|
658
668
|
if (newErr.message.includes('ENTRY_EXISTS')) newErr.name += '#409' // customErrCode
|
|
@@ -703,7 +713,7 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
703
713
|
const [memberAttr] = scimgateway.endpointMapper('outbound', 'members.value', config.map.group)
|
|
704
714
|
if (!memberAttr && attrObj.members) throw new Error(`${action} error: missing attribute mapping configuration for group members`)
|
|
705
715
|
|
|
706
|
-
const grp = { add: {
|
|
716
|
+
const grp = { add: {}, remove: {} }
|
|
707
717
|
grp.add[memberAttr] = []
|
|
708
718
|
grp.remove[memberAttr] = []
|
|
709
719
|
|
|
@@ -764,22 +774,82 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
764
774
|
const _serviceClient = {}
|
|
765
775
|
|
|
766
776
|
//
|
|
767
|
-
//
|
|
777
|
+
// createAndFilter creates AndFilter object to be used as filter instead of standard string filter
|
|
778
|
+
// Using AndFilter object for eliminating internal ldapjs escaping problems related to values with some
|
|
779
|
+
// combinations of parentheses e.g. ab(c)d
|
|
768
780
|
//
|
|
769
|
-
const
|
|
770
|
-
|
|
781
|
+
const createAndFilter = (baseEntity, type, arrObj) => {
|
|
782
|
+
const objFilters = []
|
|
783
|
+
|
|
784
|
+
// add arrObj
|
|
785
|
+
for (let i = 0; i < arrObj.length; i++) {
|
|
786
|
+
if (arrObj[i].value.indexOf('*') > -1) { // SubstringFilter or PresenceFilter
|
|
787
|
+
const arr = arrObj[i].value.split('*')
|
|
788
|
+
if (arr.length === 2 && !arr[0] && !arr[1]) { // cn=*
|
|
789
|
+
const f = new ldap.PresenceFilter({ attribute: arrObj[i].attribute })
|
|
790
|
+
objFilters.push(f)
|
|
791
|
+
} else { // cn=ab*cd*e
|
|
792
|
+
const fObj = {
|
|
793
|
+
attribute: arrObj[i].attribute
|
|
794
|
+
}
|
|
795
|
+
const arrAny = []
|
|
796
|
+
fObj.initial = arr[0]
|
|
797
|
+
if (!fObj.initial) delete fObj.initial
|
|
798
|
+
for (let i = 1; i < arr.length - 1; i++) {
|
|
799
|
+
arrAny.push(arr[i])
|
|
800
|
+
}
|
|
801
|
+
fObj.any = arrAny
|
|
802
|
+
if (arr[arr.length - 1]) {
|
|
803
|
+
fObj.final = arr[arr.length - 1]
|
|
804
|
+
}
|
|
805
|
+
const f = new ldap.SubstringFilter(fObj)
|
|
806
|
+
objFilters.push(f)
|
|
807
|
+
}
|
|
808
|
+
} else { // EqualityFilter cn=abc
|
|
809
|
+
const f = new ldap.EqualityFilter({ attribute: arrObj[i].attribute, value: arrObj[i].value })
|
|
810
|
+
objFilters.push(f)
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// add from configuration objectClass and userFiter/groupFilter
|
|
771
815
|
switch (type) {
|
|
772
816
|
case 'user':
|
|
773
817
|
for (let i = 0; i < config.entity[baseEntity].ldap.userObjectClasses.length; i++) {
|
|
774
|
-
|
|
818
|
+
const f = new ldap.EqualityFilter({ attribute: 'objectClass', value: config.entity[baseEntity].ldap.userObjectClasses[i] })
|
|
819
|
+
objFilters.push(f)
|
|
820
|
+
}
|
|
821
|
+
if (config.entity[baseEntity].ldap.userFilter) {
|
|
822
|
+
try {
|
|
823
|
+
const uf = ldap.parseFilter(config.entity[baseEntity].ldap.userFilter)
|
|
824
|
+
objFilters.push(uf)
|
|
825
|
+
} catch (err) {
|
|
826
|
+
throw new Error(`configuration ldap.userFilter: ${config.entity[baseEntity].ldap.userFilter} - parseFilter error: ${err.message}`)
|
|
827
|
+
}
|
|
775
828
|
}
|
|
776
829
|
break
|
|
777
830
|
case 'group':
|
|
778
831
|
for (let i = 0; i < config.entity[baseEntity].ldap.groupObjectClasses.length; i++) {
|
|
779
|
-
|
|
832
|
+
const f = new ldap.EqualityFilter({ attribute: 'objectClass', value: config.entity[baseEntity].ldap.groupObjectClasses[i] })
|
|
833
|
+
objFilters.push(f)
|
|
834
|
+
if (config.entity[baseEntity].ldap.groupFilter) {
|
|
835
|
+
try {
|
|
836
|
+
const gf = ldap.parseFilter(config.entity[baseEntity].ldap.groupFilter)
|
|
837
|
+
objFilters.push(gf)
|
|
838
|
+
} catch (err) {
|
|
839
|
+
throw new Error(`configuration ldap.groupFilter: ${config.entity[baseEntity].ldap.groupFilter} - parseFilter error: ${err.message}`)
|
|
840
|
+
}
|
|
841
|
+
}
|
|
780
842
|
}
|
|
781
843
|
break
|
|
782
844
|
}
|
|
845
|
+
|
|
846
|
+
// put all into AndFilter
|
|
847
|
+
const filter = new ldap.AndFilter({
|
|
848
|
+
filters: [
|
|
849
|
+
...objFilters
|
|
850
|
+
]
|
|
851
|
+
})
|
|
852
|
+
|
|
783
853
|
return filter
|
|
784
854
|
}
|
|
785
855
|
|
|
@@ -858,10 +928,10 @@ const convertSidToString = (buf) => {
|
|
|
858
928
|
for (i = 0, end = subAuthorityCount - 1, asc = end >= 0; asc ? i <= end : i >= end; asc ? i++ : i--) {
|
|
859
929
|
const subAuthOffset = i * 4
|
|
860
930
|
const tmp =
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
931
|
+
pad(buf[11 + subAuthOffset].toString(16)) +
|
|
932
|
+
pad(buf[10 + subAuthOffset].toString(16)) +
|
|
933
|
+
pad(buf[9 + subAuthOffset].toString(16)) +
|
|
934
|
+
pad(buf[8 + subAuthOffset].toString(16))
|
|
865
935
|
sidString += `-${parseInt(tmp, 16)}`
|
|
866
936
|
}
|
|
867
937
|
} catch (err) {
|
|
@@ -946,9 +1016,10 @@ const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
|
946
1016
|
const scope = 'sub'
|
|
947
1017
|
const base = config.entity[baseEntity].ldap.groupBase
|
|
948
1018
|
|
|
1019
|
+
const filter = createAndFilter(baseEntity, 'group', [{ attribute: memberAttr, value: idDn }])
|
|
949
1020
|
const ldapOptions = {
|
|
950
|
-
filter
|
|
951
|
-
scope
|
|
1021
|
+
filter,
|
|
1022
|
+
scope,
|
|
952
1023
|
attributes: attrs
|
|
953
1024
|
}
|
|
954
1025
|
|
|
@@ -967,6 +1038,194 @@ const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
|
967
1038
|
}
|
|
968
1039
|
}
|
|
969
1040
|
|
|
1041
|
+
//
|
|
1042
|
+
// ldapEscDn will escape DN according to the LDAP standard adjusted to ldapjs behavior
|
|
1043
|
+
// using OpenLDAP, DN must be escaped - national characters and special ldap characters
|
|
1044
|
+
// using Active Directory (none OpenLDAP), DN should not be escaped, but DN retrieved from AD is character escaped
|
|
1045
|
+
//
|
|
1046
|
+
const ldapEscDn = (isOpenLdap, str) => {
|
|
1047
|
+
if (!str) return str
|
|
1048
|
+
|
|
1049
|
+
if (!isOpenLdap && str.indexOf('\\') > 0) {
|
|
1050
|
+
const conv = str.replace(/\\([0-9A-Fa-f]{2})/g, (_, hex) => {
|
|
1051
|
+
const intAscii = parseInt(hex, 16)
|
|
1052
|
+
if (intAscii > 128) { // extended ascii - will be unescaped by decodeURIComponent
|
|
1053
|
+
return '%' + hex
|
|
1054
|
+
} else { // use character escape
|
|
1055
|
+
return '\\' + String.fromCharCode(intAscii)
|
|
1056
|
+
}
|
|
1057
|
+
})
|
|
1058
|
+
str = decodeURIComponent(conv)
|
|
1059
|
+
str = str.replace(/\\/g, '') // lower ascii may be character escaped e.g. 'cn=Kürt\, Lastname' - see below comma logic
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const arr = str.split(',')
|
|
1063
|
+
for (let i = 0; i < arr.length; i++) { // CN=Firstname, Lastname,OU=...
|
|
1064
|
+
if (!arr[i]) { // value having comma only
|
|
1065
|
+
if (arr[i - 1].charAt(arr[i - 1].length - 1) === '\\') {
|
|
1066
|
+
arr[i - 1] = arr[i - 1].substring(0, arr[i - 1].length - 1)
|
|
1067
|
+
}
|
|
1068
|
+
if (isOpenLdap) arr[i - 1] += '\\,'
|
|
1069
|
+
else arr[i - 1] += ','
|
|
1070
|
+
arr.splice(i, 1)
|
|
1071
|
+
i -= 1
|
|
1072
|
+
continue
|
|
1073
|
+
}
|
|
1074
|
+
const a = arr[i].split('=')
|
|
1075
|
+
if (a.length < 2 && i > 0) { // value having comma and content
|
|
1076
|
+
if (arr[i - 1].charAt(arr[i - 1].length - 1) === '\\') {
|
|
1077
|
+
arr[i - 1] = arr[i - 1].substring(0, arr[i - 1].length - 1)
|
|
1078
|
+
}
|
|
1079
|
+
if (isOpenLdap) arr[i - 1] += `\\,${ldapEsc(a[0])}`
|
|
1080
|
+
else arr[i - 1] += `,${a[0]}`
|
|
1081
|
+
arr.splice(i, 1)
|
|
1082
|
+
i -= 1
|
|
1083
|
+
continue
|
|
1084
|
+
} else {
|
|
1085
|
+
if (isOpenLdap) arr[i] = `${a[0]}=${ldapEsc(a[1])}`
|
|
1086
|
+
else arr[i] = `${a[0]}=${a[1]}`
|
|
1087
|
+
}
|
|
1088
|
+
if (i > 0) break // only escape logic on first, assume sub OU's are correct
|
|
1089
|
+
}
|
|
1090
|
+
if (isOpenLdap) {
|
|
1091
|
+
str = arr.join(',')
|
|
1092
|
+
return str
|
|
1093
|
+
}
|
|
1094
|
+
// Using dn object and BER encoding
|
|
1095
|
+
// e.g., Active Directory to avoid internal ldapjs OpenLDAP validating and string escaping logic
|
|
1096
|
+
const dn = new ldap.DN()
|
|
1097
|
+
for (let i = 0; i < arr.length; i++) {
|
|
1098
|
+
const a = arr[i].split('=') // cn=Kürt
|
|
1099
|
+
if (a.length === 2) {
|
|
1100
|
+
if (i === 0) {
|
|
1101
|
+
const ua = new Uint8Array(Buffer.from(a[1], 'utf-8'))
|
|
1102
|
+
const buf = Buffer.from(new Uint8Array([4, ua.length, ...ua]))
|
|
1103
|
+
const rdn = {}
|
|
1104
|
+
rdn[a[0]] = new BerReader(buf)
|
|
1105
|
+
dn.push(new ldap.RDN(rdn))
|
|
1106
|
+
// new BerReader(Buffer.from([0x04, 0x05, 0x4B, 0xc3, 0xbc, 0x72, 0x74])) // Kürt
|
|
1107
|
+
// the leading 04 is the tag for "octet string" and the following 05 is the length in bytes of the string.
|
|
1108
|
+
} else {
|
|
1109
|
+
const rdn = {}
|
|
1110
|
+
rdn[a[0]] = a[1]
|
|
1111
|
+
dn.push(new ldap.RDN(rdn))
|
|
1112
|
+
}
|
|
1113
|
+
} else {
|
|
1114
|
+
throw new Error('ldapEscDn() invalid DN: ' + str)
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return dn
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
//
|
|
1121
|
+
// ldapEsc will character escape str according to OpenLDAP DN standard
|
|
1122
|
+
// Hex encoded escaping (extended and unicode ascii) is not included because
|
|
1123
|
+
// automatically handled by ldapjs when not using BER encoded DN
|
|
1124
|
+
//
|
|
1125
|
+
const ldapEsc = (str) => {
|
|
1126
|
+
if (!str) return str
|
|
1127
|
+
let newStr = ''
|
|
1128
|
+
for (let i = 0; i < str.length; i++) {
|
|
1129
|
+
let c = str[i]
|
|
1130
|
+
let isEsc = false
|
|
1131
|
+
if (i > 0 && str[i - 1] === '\\') isEsc = true
|
|
1132
|
+
switch (c) {
|
|
1133
|
+
case ',':
|
|
1134
|
+
if (isEsc) c = ','
|
|
1135
|
+
else c = '\\,'
|
|
1136
|
+
break
|
|
1137
|
+
case ';':
|
|
1138
|
+
if (isEsc) c = ';'
|
|
1139
|
+
else c = '\\;'
|
|
1140
|
+
break
|
|
1141
|
+
case '+':
|
|
1142
|
+
if (isEsc) c = '+'
|
|
1143
|
+
else c = '\\+'
|
|
1144
|
+
break
|
|
1145
|
+
case '<':
|
|
1146
|
+
if (isEsc) c = '<'
|
|
1147
|
+
else c = '\\<'
|
|
1148
|
+
break
|
|
1149
|
+
case '>':
|
|
1150
|
+
if (isEsc) c = '>'
|
|
1151
|
+
else c = '\\>'
|
|
1152
|
+
break
|
|
1153
|
+
case '=':
|
|
1154
|
+
if (isEsc) c = '='
|
|
1155
|
+
else c = '\\='
|
|
1156
|
+
break
|
|
1157
|
+
case '"':
|
|
1158
|
+
if (isEsc) c = '"'
|
|
1159
|
+
else c = '\\"'
|
|
1160
|
+
break
|
|
1161
|
+
case '(':
|
|
1162
|
+
if (isEsc) c = '('
|
|
1163
|
+
else c = '\\('
|
|
1164
|
+
break
|
|
1165
|
+
case ')':
|
|
1166
|
+
if (isEsc) c = ')'
|
|
1167
|
+
else c = '\\)'
|
|
1168
|
+
break
|
|
1169
|
+
}
|
|
1170
|
+
newStr += c
|
|
1171
|
+
}
|
|
1172
|
+
return newStr
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
//
|
|
1176
|
+
// berDecodeDn decodes a BER string part of type DN
|
|
1177
|
+
// OU=#04057573657273,OU=abc,... => OU=users,OU=abc,...
|
|
1178
|
+
// only using BER on first part of dn
|
|
1179
|
+
// Having BER decoding for Active Directory, but not for OpenLDAP
|
|
1180
|
+
//
|
|
1181
|
+
const berDecodeDn = (dn) => {
|
|
1182
|
+
if (Object.prototype.toString.call(dn) !== '[object LdapDn]') return dn // OpenLDAP
|
|
1183
|
+
const str = dn.toString()
|
|
1184
|
+
if (str.indexOf('#') < 1) return str
|
|
1185
|
+
const arr = str.split('#')
|
|
1186
|
+
if (arr.length === 2) {
|
|
1187
|
+
const a = arr[1].split(',')
|
|
1188
|
+
if (a.length > 1) {
|
|
1189
|
+
const berStr = a[0].substring(4)
|
|
1190
|
+
let decoded = ''
|
|
1191
|
+
let c = ''
|
|
1192
|
+
if (berStr.length % 2 === 0) {
|
|
1193
|
+
for (let i = 0; i < berStr.length; i++) {
|
|
1194
|
+
c += berStr[i]
|
|
1195
|
+
if (c.length === 2) {
|
|
1196
|
+
const intAscii = parseInt(c, 16)
|
|
1197
|
+
decoded += String.fromCharCode(intAscii)
|
|
1198
|
+
c = ''
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (decoded.length > 0) {
|
|
1203
|
+
a.splice(0, 1) // remove element 0 from array
|
|
1204
|
+
return `${arr[0]}${decoded},${a.join(',')}` // OU=users,OU=abc
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return str
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const getNamingAttribute = (baseEntity, type) => {
|
|
1212
|
+
let arr
|
|
1213
|
+
switch (type) {
|
|
1214
|
+
case 'user':
|
|
1215
|
+
arr = config.entity[baseEntity]?.ldap?.namingAttribute?.user
|
|
1216
|
+
break
|
|
1217
|
+
case 'group':
|
|
1218
|
+
arr = config.entity[baseEntity]?.ldap?.namingAttribute?.group
|
|
1219
|
+
break
|
|
1220
|
+
default:
|
|
1221
|
+
throw new Error(`getNamingAttribute error: invalid type ${type}`)
|
|
1222
|
+
}
|
|
1223
|
+
if (!Array.isArray(arr) || arr.length !== 1) throw new Error(`configuration missing namingAttribute definition for ${type}`)
|
|
1224
|
+
const [endpointAttr] = scimgateway.endpointMapper('outbound', arr[0].mapTo, config.map[type])
|
|
1225
|
+
if (!endpointAttr) throw new Error(`namingAttribute mapTo:${arr[0].mapTo} cannot be found in the map ${type} configuration`)
|
|
1226
|
+
return [arr[0].attribute, arr[0].mapTo]
|
|
1227
|
+
}
|
|
1228
|
+
|
|
970
1229
|
//
|
|
971
1230
|
// getCtxAuth returns username/secret from ctx header when using Auth PassThrough
|
|
972
1231
|
//
|
|
@@ -1034,11 +1293,11 @@ const getServiceClient = async (baseEntity, ctx) => {
|
|
|
1034
1293
|
// "attributes": ["sAMAccountName","displayName","mail"]
|
|
1035
1294
|
// }
|
|
1036
1295
|
//
|
|
1037
|
-
const doRequest = async (baseEntity, method, base,
|
|
1296
|
+
const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
1038
1297
|
let result = null
|
|
1039
1298
|
let client = null
|
|
1299
|
+
base = ldapEscDn(config.entity[baseEntity].ldap.isOpenLdap, base)
|
|
1040
1300
|
|
|
1041
|
-
const options = scimgateway.copyObj(ldapOptions)
|
|
1042
1301
|
// support having different upn-domain on IdP and target
|
|
1043
1302
|
if (options.modification && options.modification.userPrincipalName && config.map.user.userPrincipalName && config.map.user.userPrincipalName.mapDomain) {
|
|
1044
1303
|
if (options.modification.userPrincipalName.endsWith(config.map.user.userPrincipalName.mapDomain.outbound)) {
|
|
@@ -1055,7 +1314,8 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1055
1314
|
options.paged = { pageSize: 200, pagePause: false } // parse entire directory calling 'page' method for each page
|
|
1056
1315
|
result = await new Promise((resolve, reject) => {
|
|
1057
1316
|
const results = []
|
|
1058
|
-
|
|
1317
|
+
|
|
1318
|
+
client.search(base, options, (err, search) => {
|
|
1059
1319
|
if (err) {
|
|
1060
1320
|
return reject(err)
|
|
1061
1321
|
}
|
|
@@ -1086,6 +1346,23 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1086
1346
|
scimgateway.logger.debug(`${pluginName}[${baseEntity}] outbound upnMapDomain ${old} => ${obj.userPrincipalName}`)
|
|
1087
1347
|
}
|
|
1088
1348
|
}
|
|
1349
|
+
|
|
1350
|
+
if (obj.dn && obj.dn.indexOf('\\') > 0) {
|
|
1351
|
+
// for OpenLDAP ensure dn is not hex escaped e.g.: cn=K\c3\bcrt => cn=Kürt
|
|
1352
|
+
// because dn may be be used as value in standard attributes like group memberOf
|
|
1353
|
+
obj.dn = obj.dn.replace(/\\\\/g, '__') // temp
|
|
1354
|
+
let conv = obj.dn.replace(/\\([0-9A-Fa-f]{2})/g, (_, hex) => {
|
|
1355
|
+
const intAscii = parseInt(hex, 16)
|
|
1356
|
+
if (intAscii > 128) { // extended ascii - will be unescaped by decodeURIComponent
|
|
1357
|
+
return '%' + hex
|
|
1358
|
+
} else { // use character escape
|
|
1359
|
+
return '\\' + String.fromCharCode(intAscii)
|
|
1360
|
+
}
|
|
1361
|
+
})
|
|
1362
|
+
conv = conv.replace(/__/g, '\\\\')
|
|
1363
|
+
obj.dn = decodeURIComponent(conv)
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1089
1366
|
results.push(obj)
|
|
1090
1367
|
})
|
|
1091
1368
|
|
|
@@ -1115,11 +1392,10 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1115
1392
|
if (mod.values.length > 1) { // delete before replace to keep inbound order
|
|
1116
1393
|
changes.push({
|
|
1117
1394
|
operation: 'delete',
|
|
1118
|
-
modification: {type: key, values: []}
|
|
1395
|
+
modification: { type: key, values: [] }
|
|
1119
1396
|
})
|
|
1120
1397
|
}
|
|
1121
|
-
}
|
|
1122
|
-
else {
|
|
1398
|
+
} else {
|
|
1123
1399
|
if (typeof options.modification[key] === 'string') mod.values = [options.modification[key]]
|
|
1124
1400
|
else mod.values = [options.modification[key].toString()]
|
|
1125
1401
|
}
|
|
@@ -1168,14 +1444,20 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1168
1444
|
}
|
|
1169
1445
|
client.unbind()
|
|
1170
1446
|
} catch (err) {
|
|
1171
|
-
|
|
1447
|
+
if (options.filter && typeof options.filter === 'object') {
|
|
1448
|
+
options.filter = options.filter.toString()
|
|
1449
|
+
}
|
|
1450
|
+
scimgateway.logger.error(`${pluginName}[${baseEntity}] doRequest method=${method} base=${berDecodeDn(base)} ldapOptions=${JSON.stringify(options)} Error Response = ${err.message}`)
|
|
1172
1451
|
if (client) {
|
|
1173
|
-
try { client.destroy() } catch (err) {}
|
|
1452
|
+
try { client.destroy() } catch (err) { }
|
|
1174
1453
|
}
|
|
1175
1454
|
throw err
|
|
1176
1455
|
}
|
|
1177
1456
|
|
|
1178
|
-
|
|
1457
|
+
if (options.filter && typeof options.filter === 'object') {
|
|
1458
|
+
options.filter = options.filter.toString()
|
|
1459
|
+
}
|
|
1460
|
+
scimgateway.logger.debug(`${pluginName}[${baseEntity}] doRequest method=${method} base=${berDecodeDn(base)} ldapOptions=${JSON.stringify(options)} Response=${JSON.stringify(result)}`)
|
|
1179
1461
|
return result
|
|
1180
1462
|
} // doRequest
|
|
1181
1463
|
|
|
@@ -1186,3 +1468,110 @@ process.on('SIGTERM', () => { // kill
|
|
|
1186
1468
|
})
|
|
1187
1469
|
process.on('SIGINT', () => { // Ctrl+C
|
|
1188
1470
|
})
|
|
1471
|
+
|
|
1472
|
+
//
|
|
1473
|
+
// startup initialization
|
|
1474
|
+
// at the end to ensure scimgatway logger have started
|
|
1475
|
+
//
|
|
1476
|
+
if (!config?.map?.user) {
|
|
1477
|
+
scimgateway.logger.error('configuration map.user is missing')
|
|
1478
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1479
|
+
} else {
|
|
1480
|
+
let idFound = false
|
|
1481
|
+
let userNameFound = false
|
|
1482
|
+
for (const key in config.map.user) {
|
|
1483
|
+
if (config.map.user[key].mapTo === 'id') idFound = true
|
|
1484
|
+
else if (['userName', 'externalId'].includes(config.map.user[key].mapTo)) userNameFound = true
|
|
1485
|
+
if (idFound && userNameFound) break
|
|
1486
|
+
}
|
|
1487
|
+
if (!idFound || !userNameFound) {
|
|
1488
|
+
scimgateway.logger.error('configuration map.user missing mapTo definition for mandatory id/userName')
|
|
1489
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (!config?.map?.group) {
|
|
1493
|
+
scimgateway.logger.info('configuration map.group is not defiend and groups will not be supported')
|
|
1494
|
+
} else {
|
|
1495
|
+
let idFound = false
|
|
1496
|
+
let displayNameFound = false
|
|
1497
|
+
for (const key in config.map.group) {
|
|
1498
|
+
if (config.map.group[key].mapTo === 'id') idFound = true
|
|
1499
|
+
else if (config.map.group[key].mapTo === 'displayName') displayNameFound = true
|
|
1500
|
+
if (idFound && displayNameFound) break
|
|
1501
|
+
}
|
|
1502
|
+
if ((!idFound || !displayNameFound) && (Object.keys(config.map.group).length > 0)) {
|
|
1503
|
+
scimgateway.logger.error('configuration map.group missing mapTo definition for mandatory id/displayName')
|
|
1504
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
for (const key in config.entity) {
|
|
1509
|
+
const userBase = config.entity[key]?.ldap?.userBase
|
|
1510
|
+
const groupBase = config.entity[key]?.ldap?.groupBase
|
|
1511
|
+
if (!userBase) {
|
|
1512
|
+
scimgateway.logger.error(`configuration missing mandatory endpoint.entity.${key}.ldap.userBase`)
|
|
1513
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1514
|
+
}
|
|
1515
|
+
if (!groupBase && config?.map?.group && Object.keys(config.map.group).length > 0) {
|
|
1516
|
+
scimgateway.logger.error(`configuration missing mandatory endpoint.entity.${key}.ldap.groupBase`)
|
|
1517
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1518
|
+
}
|
|
1519
|
+
let usrArr = config.entity[key]?.ldap?.namingAttribute?.user
|
|
1520
|
+
if (!usrArr || !Array.isArray(usrArr)) { // check for legacy
|
|
1521
|
+
const attr = config.entity[key]?.ldap?.userNamingAttr
|
|
1522
|
+
if (attr) {
|
|
1523
|
+
usrArr = [{ attribute: attr, mapTo: 'userName' }]
|
|
1524
|
+
if (!config.entity[key].ldap.namingAttribute) config.entity[key].ldap.namingAttribute = {}
|
|
1525
|
+
config.entity[key].ldap.namingAttribute.user = scimgateway.copyObj(usrArr)
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (!Array.isArray(usrArr) || usrArr.length !== 1) {
|
|
1529
|
+
scimgateway.logger.error(`configuration missing namingAttribute: endpoint.entity.${key}.ldap.namingAttribute.user`)
|
|
1530
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1531
|
+
}
|
|
1532
|
+
if (!usrArr[0].attribute || !usrArr[0].mapTo) {
|
|
1533
|
+
scimgateway.logger.error(`configuration missing attribute/mapTo: endpoint.entity.${key}.ldap.namingAttribute.user`)
|
|
1534
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1535
|
+
}
|
|
1536
|
+
let grpArr = config.entity[key]?.ldap?.namingAttribute?.group
|
|
1537
|
+
if (config?.map?.group && Object.keys(config.map.group).length > 0) {
|
|
1538
|
+
if (!grpArr || !Array.isArray(grpArr)) { // check for legacy
|
|
1539
|
+
const attr = config.entity[key]?.ldap?.groupNamingAttr
|
|
1540
|
+
if (attr) {
|
|
1541
|
+
grpArr = [{ attribute: attr, mapTo: 'displayName' }]
|
|
1542
|
+
if (!config.entity[key].ldap.namingAttribute) config.entity[key].ldap.namingAttribute = {}
|
|
1543
|
+
config.entity[key].ldap.namingAttribute.group = scimgateway.copyObj(grpArr)
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (!Array.isArray(grpArr) || grpArr.length !== 1) {
|
|
1547
|
+
scimgateway.logger.error(`configuration missing namingAttribute: endpoint.entity.${key}.ldap.namingAttribute.group`)
|
|
1548
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1549
|
+
}
|
|
1550
|
+
if (!grpArr[0].attribute || !grpArr[0].mapTo) {
|
|
1551
|
+
scimgateway.logger.error(`configuration missing attribute/mapTo: endpoint.entity.${key}.ldap.namingAttribute.group`)
|
|
1552
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
config.useSID_id = config.map.user.objectSid && config.map.user.objectSid.mapTo === 'id' // AD proprietary SID/GUID
|
|
1558
|
+
config.useGUID_id = config.map.user.objectGUID && config.map.user.objectGUID.mapTo === 'id'
|
|
1559
|
+
if (config.useSID_id && config.map.group) {
|
|
1560
|
+
if (!config.map.group.objectSid || config.map.group.objectSid.mapTo !== 'id') {
|
|
1561
|
+
scimgateway.logger.error('configuration missing group.objectSid - user and group should be using the same attribute')
|
|
1562
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1563
|
+
}
|
|
1564
|
+
} else if (config.useGUID_id && config.map.group) {
|
|
1565
|
+
if (!config.map.group.objectGUID || config.map.group.objectGUID.mapTo !== 'id') {
|
|
1566
|
+
scimgateway.logger.error('configuration missing group.objectGUID - user and group should be using the same attribute')
|
|
1567
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
if (config.map.user.userPrincipalName && config.map.user.userPrincipalName.mapDomain) { // support mapping different inbound/outbound upn domain names
|
|
1571
|
+
if (config.map.user.userPrincipalName.mapDomain.inbound && config.map.user.userPrincipalName.mapDomain.outbound) {
|
|
1572
|
+
const inbound = config.map.user.userPrincipalName.mapDomain.inbound
|
|
1573
|
+
const outbound = config.map.user.userPrincipalName.mapDomain.outbound
|
|
1574
|
+
config.map.user.userPrincipalName.mapDomain.inbound = inbound.startsWith('@') ? inbound : '@' + inbound // "@my-company.com"
|
|
1575
|
+
config.map.user.userPrincipalName.mapDomain.outbound = outbound.startsWith('@') ? outbound : '@' + outbound // "@test.onmicrosoft.com
|
|
1576
|
+
} else delete config.map.user.userPrincipalName.mapDomain
|
|
1577
|
+
}
|
package/lib/scimgateway.js
CHANGED
|
@@ -521,8 +521,8 @@ const ScimGateway = function () {
|
|
|
521
521
|
else expires = oAuthTokenExpire
|
|
522
522
|
config.auth.oauthTokenStore[authToken] = {
|
|
523
523
|
expireDate: Date.now() + expires * 1000,
|
|
524
|
-
readOnly
|
|
525
|
-
baseEntities
|
|
524
|
+
readOnly,
|
|
525
|
+
baseEntities
|
|
526
526
|
}
|
|
527
527
|
return resolve(true)
|
|
528
528
|
}
|
|
@@ -792,8 +792,8 @@ const ScimGateway = function () {
|
|
|
792
792
|
|
|
793
793
|
config.auth.oauthTokenStore[token] = { // update token store
|
|
794
794
|
expireDate: dtNow + expires * 1000, // 1 hour
|
|
795
|
-
readOnly
|
|
796
|
-
baseEntities
|
|
795
|
+
readOnly,
|
|
796
|
+
baseEntities
|
|
797
797
|
}
|
|
798
798
|
|
|
799
799
|
const tx = {
|
|
@@ -844,7 +844,10 @@ const ScimGateway = function () {
|
|
|
844
844
|
let u = ctx.request.originalUrl.substr(0, ctx.request.originalUrl.lastIndexOf('/'))
|
|
845
845
|
u = u.substr(u.lastIndexOf('/') + 1) // u = Users, Groups
|
|
846
846
|
const handle = handler[u]
|
|
847
|
-
|
|
847
|
+
let id = decodeURIComponent(ctx.params.id)
|
|
848
|
+
if (id && id.endsWith('.json')) {
|
|
849
|
+
id = decodeURIComponent(require('path').basename(id, '.json')) // supports <id>.json
|
|
850
|
+
}
|
|
848
851
|
|
|
849
852
|
const getObj = {
|
|
850
853
|
attribute: 'id',
|
|
@@ -863,7 +866,7 @@ const ScimGateway = function () {
|
|
|
863
866
|
handle: handle.getMethod,
|
|
864
867
|
baseEntity: ctx.params.baseEntity,
|
|
865
868
|
obj: ob,
|
|
866
|
-
attributes
|
|
869
|
+
attributes,
|
|
867
870
|
ctxPassThrough: ctx.passThrough
|
|
868
871
|
}
|
|
869
872
|
logger.debug(`${gwName}[${pluginName}] publishing "${handle.getMethod}" to SCIM Stream and awaiting result`)
|
|
@@ -909,7 +912,7 @@ const ScimGateway = function () {
|
|
|
909
912
|
handle: handler.groups.getMethod,
|
|
910
913
|
baseEntity: ctx.params.baseEntity,
|
|
911
914
|
obj: ob,
|
|
912
|
-
attributes
|
|
915
|
+
attributes,
|
|
913
916
|
ctxPassThrough: ctx.passThrough
|
|
914
917
|
}
|
|
915
918
|
logger.debug(`${gwName}[${pluginName}] publishing "${handler.groups.getMethod}" to SCIM Stream and awaiting result - groups to be included`)
|
|
@@ -987,7 +990,6 @@ const ScimGateway = function () {
|
|
|
987
990
|
getObj.value = decodeURIComponent(arrFilter.slice(2).join(' ').replace(/"/g, '')) // bjensen
|
|
988
991
|
}
|
|
989
992
|
}
|
|
990
|
-
|
|
991
993
|
let err
|
|
992
994
|
if (getObj.attribute) {
|
|
993
995
|
if (multiValueTypes.includes(getObj.attribute) || getObj.attribute === 'roles') {
|
|
@@ -1108,7 +1110,7 @@ const ScimGateway = function () {
|
|
|
1108
1110
|
handle: handle.getMethod,
|
|
1109
1111
|
baseEntity: ctx.params.baseEntity,
|
|
1110
1112
|
obj: ob,
|
|
1111
|
-
attributes
|
|
1113
|
+
attributes,
|
|
1112
1114
|
ctxPassThrough: ctx.passThrough
|
|
1113
1115
|
}
|
|
1114
1116
|
logger.debug(`${gwName}[${pluginName}] publishing "${handle.getMethod}" to SCIM Stream and awaiting result`)
|
|
@@ -1147,7 +1149,7 @@ const ScimGateway = function () {
|
|
|
1147
1149
|
handle: handler.groups.getMethod,
|
|
1148
1150
|
baseEntity: ctx.params.baseEntity,
|
|
1149
1151
|
obj: ob,
|
|
1150
|
-
attributes
|
|
1152
|
+
attributes,
|
|
1151
1153
|
ctxPassThrough: ctx.passThrough
|
|
1152
1154
|
}
|
|
1153
1155
|
logger.debug(`${gwName}[${pluginName}] publishing "${handler.groups.getMethod}" to SCIM Stream and awaiting result - groups to be included`)
|
|
@@ -1265,13 +1267,13 @@ const ScimGateway = function () {
|
|
|
1265
1267
|
res = await this.publish(streamObj)
|
|
1266
1268
|
} else {
|
|
1267
1269
|
if (scimdata.groups && Array.isArray(scimdata.groups) && handle.createMethod === 'createUser') {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
}
|
|
1273
|
-
delete scimdata.groups
|
|
1270
|
+
if (!config.scim.groupMemberOfUser) {
|
|
1271
|
+
for (let i = 0; i < scimdata.groups.length; i++) {
|
|
1272
|
+
if (!scimdata.groups[i].value) continue
|
|
1273
|
+
addGrps.push(decodeURIComponent(scimdata.groups[i].value))
|
|
1274
1274
|
}
|
|
1275
|
+
delete scimdata.groups
|
|
1276
|
+
}
|
|
1275
1277
|
}
|
|
1276
1278
|
logger.debug(`${gwName}[${pluginName}] calling "${handle.createMethod}" and awaiting result`)
|
|
1277
1279
|
res = await this[handle.createMethod](ctx.params.baseEntity, scimdata, ctx.passThrough)
|
|
@@ -1279,7 +1281,7 @@ const ScimGateway = function () {
|
|
|
1279
1281
|
for (const key in res) { // merge any result e.g: {'id': 'xxxx'}
|
|
1280
1282
|
jsonBody[key] = res[key]
|
|
1281
1283
|
}
|
|
1282
|
-
|
|
1284
|
+
|
|
1283
1285
|
if (!jsonBody.id) { // retrieve all attributes including id
|
|
1284
1286
|
let res
|
|
1285
1287
|
try {
|
|
@@ -1293,7 +1295,7 @@ const ScimGateway = function () {
|
|
|
1293
1295
|
handle: handle.getMethod,
|
|
1294
1296
|
baseEntity: ctx.params.baseEntity,
|
|
1295
1297
|
obj: ob,
|
|
1296
|
-
attributes
|
|
1298
|
+
attributes,
|
|
1297
1299
|
ctxPassThrough: ctx.passThrough
|
|
1298
1300
|
}
|
|
1299
1301
|
res = await this.publish(streamObj)
|
|
@@ -1310,7 +1312,7 @@ const ScimGateway = function () {
|
|
|
1310
1312
|
handle: handle.getMethod,
|
|
1311
1313
|
baseEntity: ctx.params.baseEntity,
|
|
1312
1314
|
obj: ob,
|
|
1313
|
-
attributes
|
|
1315
|
+
attributes,
|
|
1314
1316
|
ctxPassThrough: ctx.passThrough
|
|
1315
1317
|
}
|
|
1316
1318
|
res = await this.publish(streamObj)
|
|
@@ -1327,7 +1329,7 @@ const ScimGateway = function () {
|
|
|
1327
1329
|
}
|
|
1328
1330
|
|
|
1329
1331
|
if (addGrps.length > 0 && handle.createMethod === 'createUser') { // add group membership
|
|
1330
|
-
const addGroups = async (groupId) => {
|
|
1332
|
+
const addGroups = async (groupId) => {
|
|
1331
1333
|
if (config.stream.publisher.enabled) {
|
|
1332
1334
|
const streamObj = {
|
|
1333
1335
|
handle: handler.groups.modifyMethod,
|
|
@@ -1349,7 +1351,7 @@ const ScimGateway = function () {
|
|
|
1349
1351
|
}
|
|
1350
1352
|
jsonBody.groups = []
|
|
1351
1353
|
addGrps.forEach((el) => {
|
|
1352
|
-
jsonBody.groups.push({
|
|
1354
|
+
jsonBody.groups.push({ value: el, type: 'direct' })
|
|
1353
1355
|
})
|
|
1354
1356
|
}
|
|
1355
1357
|
|
|
@@ -1398,7 +1400,7 @@ const ScimGateway = function () {
|
|
|
1398
1400
|
const streamObj = {
|
|
1399
1401
|
handle: handle.deleteMethod,
|
|
1400
1402
|
baseEntity: ctx.params.baseEntity,
|
|
1401
|
-
id
|
|
1403
|
+
id,
|
|
1402
1404
|
ctxPassThrough: ctx.passThrough
|
|
1403
1405
|
}
|
|
1404
1406
|
logger.debug(`${gwName}[${pluginName}] publishing "${handle.deleteMethod}" to SCIM Stream and awaiting result`)
|
|
@@ -1479,7 +1481,7 @@ const ScimGateway = function () {
|
|
|
1479
1481
|
obj.value = decodeURIComponent(obj.value)
|
|
1480
1482
|
groups.push(obj)
|
|
1481
1483
|
}
|
|
1482
|
-
delete scimdata.groups
|
|
1484
|
+
delete scimdata.groups
|
|
1483
1485
|
}
|
|
1484
1486
|
}
|
|
1485
1487
|
try {
|
|
@@ -1487,7 +1489,7 @@ const ScimGateway = function () {
|
|
|
1487
1489
|
let streamObj = {
|
|
1488
1490
|
handle: handle.modifyMethod,
|
|
1489
1491
|
baseEntity: ctx.params.baseEntity,
|
|
1490
|
-
id
|
|
1492
|
+
id,
|
|
1491
1493
|
obj: scimdata,
|
|
1492
1494
|
ctxPassThrough: ctx.passThrough
|
|
1493
1495
|
}
|
|
@@ -1497,7 +1499,7 @@ const ScimGateway = function () {
|
|
|
1497
1499
|
handle: 'replaceUsrGrp',
|
|
1498
1500
|
baseEntity: ctx.params.baseEntity,
|
|
1499
1501
|
originalUrl: ctx.request.originalUrl,
|
|
1500
|
-
id
|
|
1502
|
+
id,
|
|
1501
1503
|
obj: scimdata,
|
|
1502
1504
|
ctxPassThrough: ctx.passThrough
|
|
1503
1505
|
}
|
|
@@ -1515,21 +1517,21 @@ const ScimGateway = function () {
|
|
|
1515
1517
|
}
|
|
1516
1518
|
|
|
1517
1519
|
if (groups.length > 0 && handle.modifyMethod === 'modifyUser') { // modify user includes groups, add/remove group membership
|
|
1518
|
-
const updateGroup = async (groupsObj) => {
|
|
1520
|
+
const updateGroup = async (groupsObj) => {
|
|
1519
1521
|
const groupId = groupsObj.value
|
|
1520
|
-
const memberObj = {
|
|
1521
|
-
if (groupsObj.operation) memberObj.operation= groupsObj.operation
|
|
1522
|
+
const memberObj = { value: id }
|
|
1523
|
+
if (groupsObj.operation) memberObj.operation = groupsObj.operation
|
|
1522
1524
|
if (config.stream.publisher.enabled) {
|
|
1523
1525
|
const streamObj = {
|
|
1524
1526
|
handle: handler.groups.modifyMethod,
|
|
1525
1527
|
baseEntity: ctx.params.baseEntity,
|
|
1526
1528
|
id: groupId,
|
|
1527
|
-
obj: { members: [
|
|
1529
|
+
obj: { members: [memberObj] },
|
|
1528
1530
|
ctxPassThrough: ctx.passThrough
|
|
1529
1531
|
}
|
|
1530
1532
|
return await this.publish(streamObj)
|
|
1531
1533
|
} else {
|
|
1532
|
-
return await this[handler.groups.modifyMethod](ctx.params.baseEntity, groupId, { members: [
|
|
1534
|
+
return await this[handler.groups.modifyMethod](ctx.params.baseEntity, groupId, { members: [memberObj] }, ctx.passThrough)
|
|
1533
1535
|
}
|
|
1534
1536
|
}
|
|
1535
1537
|
const res = await Promise.allSettled(groups.map((groupsObj) => updateGroup(groupsObj)))
|
|
@@ -1554,7 +1556,7 @@ const ScimGateway = function () {
|
|
|
1554
1556
|
handle: handle.getMethod,
|
|
1555
1557
|
baseEntity: ctx.params.baseEntity,
|
|
1556
1558
|
obj: ob,
|
|
1557
|
-
attributes
|
|
1559
|
+
attributes,
|
|
1558
1560
|
ctxPassThrough: ctx.passThrough
|
|
1559
1561
|
}
|
|
1560
1562
|
logger.debug(`${gwName}[${pluginName}] publishing "${handle.getMethod}" to SCIM Stream and awaiting result`)
|
|
@@ -1762,7 +1764,7 @@ const ScimGateway = function () {
|
|
|
1762
1764
|
const streamObj = {
|
|
1763
1765
|
handle: 'replaceUsrGrp',
|
|
1764
1766
|
baseEntity: ctx.params.baseEntity,
|
|
1765
|
-
originalUrl
|
|
1767
|
+
originalUrl,
|
|
1766
1768
|
id: ctx.params.id,
|
|
1767
1769
|
obj: ctx.request.body,
|
|
1768
1770
|
ctxPassThrough: ctx.passThrough
|
|
@@ -1810,12 +1812,12 @@ const ScimGateway = function () {
|
|
|
1810
1812
|
result = await this.postApi(ctx.params.baseEntity, apiObj, ctx.passThrough)
|
|
1811
1813
|
}
|
|
1812
1814
|
if (result) {
|
|
1813
|
-
if (typeof result === 'object') result = { result
|
|
1815
|
+
if (typeof result === 'object') result = { result }
|
|
1814
1816
|
else {
|
|
1815
1817
|
try {
|
|
1816
1818
|
result = { result: JSON.parse(result) }
|
|
1817
1819
|
} catch (err) {
|
|
1818
|
-
result = { result
|
|
1820
|
+
result = { result }
|
|
1819
1821
|
}
|
|
1820
1822
|
}
|
|
1821
1823
|
} else result = {}
|
|
@@ -1859,7 +1861,7 @@ const ScimGateway = function () {
|
|
|
1859
1861
|
const streamObj = {
|
|
1860
1862
|
handle: 'putApi',
|
|
1861
1863
|
baseEntity: ctx.params.baseEntity,
|
|
1862
|
-
id
|
|
1864
|
+
id,
|
|
1863
1865
|
obj: apiObj,
|
|
1864
1866
|
ctxPassThrough: ctx.passThrough
|
|
1865
1867
|
}
|
|
@@ -1870,12 +1872,12 @@ const ScimGateway = function () {
|
|
|
1870
1872
|
result = await this.putApi(ctx.params.baseEntity, id, apiObj, ctx.passThrough)
|
|
1871
1873
|
}
|
|
1872
1874
|
if (result) {
|
|
1873
|
-
if (typeof result === 'object') result = { result
|
|
1875
|
+
if (typeof result === 'object') result = { result }
|
|
1874
1876
|
else {
|
|
1875
1877
|
try {
|
|
1876
1878
|
result = { result: JSON.parse(result) }
|
|
1877
1879
|
} catch (err) {
|
|
1878
|
-
result = { result
|
|
1880
|
+
result = { result }
|
|
1879
1881
|
}
|
|
1880
1882
|
}
|
|
1881
1883
|
} else result = {}
|
|
@@ -1919,7 +1921,7 @@ const ScimGateway = function () {
|
|
|
1919
1921
|
const streamObj = {
|
|
1920
1922
|
handle: 'patchApi',
|
|
1921
1923
|
baseEntity: ctx.params.baseEntity,
|
|
1922
|
-
id
|
|
1924
|
+
id,
|
|
1923
1925
|
obj: apiObj,
|
|
1924
1926
|
ctxPassThrough: ctx.passThrough
|
|
1925
1927
|
}
|
|
@@ -1930,12 +1932,12 @@ const ScimGateway = function () {
|
|
|
1930
1932
|
result = await this.patchApi(ctx.params.baseEntity, id, apiObj, ctx.passThrough)
|
|
1931
1933
|
}
|
|
1932
1934
|
if (result) {
|
|
1933
|
-
if (typeof result === 'object') result = { result
|
|
1935
|
+
if (typeof result === 'object') result = { result }
|
|
1934
1936
|
else {
|
|
1935
1937
|
try {
|
|
1936
1938
|
result = { result: JSON.parse(result) }
|
|
1937
1939
|
} catch (err) {
|
|
1938
|
-
result = { result
|
|
1940
|
+
result = { result }
|
|
1939
1941
|
}
|
|
1940
1942
|
}
|
|
1941
1943
|
} else result = {}
|
|
@@ -1976,7 +1978,7 @@ const ScimGateway = function () {
|
|
|
1976
1978
|
const streamObj = {
|
|
1977
1979
|
handle: 'getApi',
|
|
1978
1980
|
baseEntity: ctx.params.baseEntity,
|
|
1979
|
-
id
|
|
1981
|
+
id,
|
|
1980
1982
|
query: ctx.query,
|
|
1981
1983
|
obj: apiObj,
|
|
1982
1984
|
ctxPassThrough: ctx.passThrough
|
|
@@ -1988,12 +1990,12 @@ const ScimGateway = function () {
|
|
|
1988
1990
|
result = await this.getApi(ctx.params.baseEntity, id, ctx.query, apiObj, ctx.passThrough)
|
|
1989
1991
|
}
|
|
1990
1992
|
if (result) {
|
|
1991
|
-
if (typeof result === 'object') result = { result
|
|
1993
|
+
if (typeof result === 'object') result = { result }
|
|
1992
1994
|
else {
|
|
1993
1995
|
try {
|
|
1994
1996
|
result = { result: JSON.parse(result) }
|
|
1995
1997
|
} catch (err) {
|
|
1996
|
-
result = { result
|
|
1998
|
+
result = { result }
|
|
1997
1999
|
}
|
|
1998
2000
|
}
|
|
1999
2001
|
} else result = {}
|
|
@@ -2026,7 +2028,7 @@ const ScimGateway = function () {
|
|
|
2026
2028
|
const streamObj = {
|
|
2027
2029
|
handle: 'deleteApi',
|
|
2028
2030
|
baseEntity: ctx.params.baseEntity,
|
|
2029
|
-
id
|
|
2031
|
+
id,
|
|
2030
2032
|
ctxPassThrough: ctx.passThrough
|
|
2031
2033
|
}
|
|
2032
2034
|
logger.debug(`${gwName}[${pluginName}] publishing "deleteApi" to SCIM Stream and awaiting result`)
|
|
@@ -2036,12 +2038,12 @@ const ScimGateway = function () {
|
|
|
2036
2038
|
result = await this.deleteApi(ctx.params.baseEntity, id, ctx.passThrough)
|
|
2037
2039
|
}
|
|
2038
2040
|
if (result) {
|
|
2039
|
-
if (typeof result === 'object') result = { result
|
|
2041
|
+
if (typeof result === 'object') result = { result }
|
|
2040
2042
|
else {
|
|
2041
2043
|
try {
|
|
2042
2044
|
result = { result: JSON.parse(result) }
|
|
2043
2045
|
} catch (err) {
|
|
2044
|
-
result = { result
|
|
2046
|
+
result = { result }
|
|
2045
2047
|
}
|
|
2046
2048
|
}
|
|
2047
2049
|
} else result = {}
|
|
@@ -3323,7 +3325,7 @@ const jsonErr = (scimVersion, pluginName, htmlErrCode, err) => {
|
|
|
3323
3325
|
errJson =
|
|
3324
3326
|
{
|
|
3325
3327
|
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
|
3326
|
-
scimType
|
|
3328
|
+
scimType,
|
|
3327
3329
|
detail: msg,
|
|
3328
3330
|
status: customErrCode || htmlErrCode
|
|
3329
3331
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.8",
|
|
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,7 +43,7 @@
|
|
|
43
43
|
"ldapjs": "^3.0.7",
|
|
44
44
|
"lokijs": "^1.5.12",
|
|
45
45
|
"mongodb": "^6.6.2",
|
|
46
|
-
"nats": "^2.
|
|
46
|
+
"nats": "^2.28.2",
|
|
47
47
|
"node-machine-id": "1.1.9",
|
|
48
48
|
"nodemailer": "^6.9.13",
|
|
49
49
|
"passport": "^0.7.0",
|