scimgateway 4.5.7 → 4.5.9
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 +51 -3
- package/config/plugin-ldap.json +15 -2
- package/lib/plugin-api.js +2 -2
- package/lib/plugin-ldap.js +471 -75
- package/lib/scimgateway.js +49 -47
- package/lib/utils.js +1 -1
- package/package.json +7 -7
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
|
|
@@ -65,7 +65,7 @@ Can be used to chain several gateways
|
|
|
65
65
|
|
|
66
66
|
* **Soap** (SOAP Webservice)
|
|
67
67
|
Demonstrates user provisioning towards SOAP-Based endpoint
|
|
68
|
-
|
|
68
|
+
Example WSDLs are included
|
|
69
69
|
Using endpoint "Forwardinc" as an example (comes with Symantec/Broadcom/CA IM SDK - SDKWS)
|
|
70
70
|
Shows how to implement a highly configurable multi tenant or multi endpoint solution through `baseEntity` in URL
|
|
71
71
|
|
|
@@ -84,7 +84,7 @@ Includes Symantec/Broadcom/CA ConnectorXpress metafile for creating provisioning
|
|
|
84
84
|
* **LDAP** (Directory)
|
|
85
85
|
Fully functional LDAP plugin
|
|
86
86
|
Pre-configured for Microsoft Active Directory
|
|
87
|
-
Using endpointMapper (like plugin-entra-id) for attribute flexibility
|
|
87
|
+
Using endpointMapper (like plugin-entra-id) for attribute mapping flexibility
|
|
88
88
|
|
|
89
89
|
* **API** (REST Webservices)
|
|
90
90
|
Demonstrates API Gateway/plugin functionality using post/put/patch/get/delete
|
|
@@ -1163,6 +1163,54 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1163
1163
|
|
|
1164
1164
|
## Change log
|
|
1165
1165
|
|
|
1166
|
+
### v4.5.9
|
|
1167
|
+
|
|
1168
|
+
[Improved]
|
|
1169
|
+
|
|
1170
|
+
- Dependencies bump
|
|
1171
|
+
|
|
1172
|
+
### v4.5.8
|
|
1173
|
+
|
|
1174
|
+
[Fixed]
|
|
1175
|
+
|
|
1176
|
+
- plugin-ldap failed when using national special characters and some other LDAP special characters in DN
|
|
1177
|
+
|
|
1178
|
+
Note, plugin-ldap now has following new configuration:
|
|
1179
|
+
|
|
1180
|
+
"ldap": {
|
|
1181
|
+
"isOpenLdap": false,
|
|
1182
|
+
...
|
|
1183
|
+
"namingAttribute": {
|
|
1184
|
+
"user": [
|
|
1185
|
+
{
|
|
1186
|
+
"attribute": "CN",
|
|
1187
|
+
"mapTo": "userName"
|
|
1188
|
+
}
|
|
1189
|
+
],
|
|
1190
|
+
"group": [
|
|
1191
|
+
{
|
|
1192
|
+
"attribute": "CN",
|
|
1193
|
+
"mapTo": "displayName"
|
|
1194
|
+
}
|
|
1195
|
+
]
|
|
1196
|
+
},
|
|
1197
|
+
...
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
`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.
|
|
1201
|
+
|
|
1202
|
+
`namingAttribute` can now be linked to scim `mapTo` attribute and is not hardcoded like it was in previous version.
|
|
1203
|
+
|
|
1204
|
+
Previous `userNamingAttr` and `groupNamingAttr` shown below, is now deprecated
|
|
1205
|
+
|
|
1206
|
+
"ldap": {
|
|
1207
|
+
...
|
|
1208
|
+
"userNamingAttr": "CN",
|
|
1209
|
+
"groupNamingAttr": "CN",
|
|
1210
|
+
...
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
|
|
1166
1214
|
### v4.5.7
|
|
1167
1215
|
|
|
1168
1216
|
[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-api.js
CHANGED
|
@@ -283,8 +283,8 @@ const getAccessToken = async (baseEntity, ctx) => {
|
|
|
283
283
|
lock.release()
|
|
284
284
|
throw (err)
|
|
285
285
|
}
|
|
286
|
-
if (config.entity[baseEntity].tokenAuth) { //
|
|
287
|
-
if (jbody.
|
|
286
|
+
if (config.entity[baseEntity].tokenAuth) { // custom access_token
|
|
287
|
+
if (jbody.accessToken) jbody.access_token = jbody.accessToken
|
|
288
288
|
}
|
|
289
289
|
if (!jbody.access_token) {
|
|
290
290
|
const err = new Error(`[${action}] Error message: Retrieved invalid token response`)
|
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
|
|
|
@@ -229,8 +209,6 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
229
209
|
const users = await doRequest(baseEntity, method, base, ldapOptions, ctx) // ignoring SCIM paging startIndex/count - get all
|
|
230
210
|
result.totalResults = users.length
|
|
231
211
|
result.Resources = await Promise.all(users.map(async (user) => { // Promise.all because of async map
|
|
232
|
-
if (user.name) delete user.name // because mapper converts to SCIM name.xxx
|
|
233
|
-
|
|
234
212
|
// endpoint spesific attribute handling
|
|
235
213
|
// "active" must be handled separate
|
|
236
214
|
if (user.userAccountControl !== undefined) { // SCIM "active" - Active Directory
|
|
@@ -263,7 +241,7 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
263
241
|
}
|
|
264
242
|
|
|
265
243
|
const scimObj = scimgateway.endpointMapper('inbound', user, config.map.user)[0] // endpoint attribute naming => SCIM
|
|
266
|
-
if (!scimObj.groups) scimObj.groups = []
|
|
244
|
+
// if (!scimObj.groups) scimObj.groups = []
|
|
267
245
|
return scimObj
|
|
268
246
|
}))
|
|
269
247
|
} catch (err) {
|
|
@@ -317,8 +295,16 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
|
|
|
317
295
|
// endpointObj.objectClass is mandatory and must must match your ldap schema
|
|
318
296
|
endpointObj.objectClass = config.entity[baseEntity].ldap.userObjectClasses // Active Directory: ["user", "person", "organizationalPerson", "top"]
|
|
319
297
|
|
|
298
|
+
let base = ''
|
|
299
|
+
const [userNamingAttr, scimAttr] = getNamingAttribute(baseEntity, 'user') // ['CN', 'userName']
|
|
300
|
+
const arr = scimAttr.split('.')
|
|
301
|
+
if (arr.length < 2) {
|
|
302
|
+
base = `${userNamingAttr}=${userObj[scimAttr]},${userBase}`
|
|
303
|
+
} else {
|
|
304
|
+
base = `${userNamingAttr}=${userObj[arr[0]][arr[1]]},${userBase}`
|
|
305
|
+
}
|
|
306
|
+
|
|
320
307
|
const method = 'add'
|
|
321
|
-
const base = `${config.entity[baseEntity].ldap.userNamingAttr}=${userObj.userName},${userBase}`
|
|
322
308
|
const ldapOptions = endpointObj
|
|
323
309
|
|
|
324
310
|
try {
|
|
@@ -371,7 +357,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
371
357
|
delete attrObj.groups // make sure to be removed from attrObj
|
|
372
358
|
|
|
373
359
|
const [groupsAttr] = scimgateway.endpointMapper('outbound', 'groups.value', config.map.user)
|
|
374
|
-
const grp = { add: {
|
|
360
|
+
const grp = { add: {}, remove: {} }
|
|
375
361
|
grp.add[groupsAttr] = []
|
|
376
362
|
grp.remove[groupsAttr] = []
|
|
377
363
|
|
|
@@ -510,8 +496,8 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
510
496
|
totalResults: null
|
|
511
497
|
}
|
|
512
498
|
|
|
513
|
-
if (!config
|
|
514
|
-
scimgateway.logger.debug(`${pluginName}[${baseEntity}] "${action}"
|
|
499
|
+
if (!config?.map?.group || !config.entity[baseEntity]?.ldap?.groupBase) { // not using groups
|
|
500
|
+
scimgateway.logger.debug(`${pluginName}[${baseEntity}] "${action}" skip group handling - missing configuration endpoint.map.group or groupBase`)
|
|
515
501
|
return result
|
|
516
502
|
}
|
|
517
503
|
|
|
@@ -536,7 +522,7 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
536
522
|
// mandatory if-else logic - start
|
|
537
523
|
if (getObj.operator) {
|
|
538
524
|
if (getObj.operator === 'eq' && ['id', 'displayName', 'externalId'].includes(getObj.attribute)) {
|
|
539
|
-
|
|
525
|
+
// mandatory - unique filtering - single unique user to be returned - correspond to getUser() in versions < 4.x.x
|
|
540
526
|
if (getObj.attribute === 'id') { // lookup using dn or objectSid/objectGUID (Active Directory)
|
|
541
527
|
if (config.useSID_id) {
|
|
542
528
|
const sid = convertStringToSid(getObj.value) // sid using formatted string instead of default hex
|
|
@@ -566,12 +552,12 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
566
552
|
attributes: attrs
|
|
567
553
|
}
|
|
568
554
|
} else { // search instead of lookup
|
|
555
|
+
const filter = createAndFilter(baseEntity, 'group', [{ attribute: groupIdAttr, value: getObj.value }])
|
|
569
556
|
ldapOptions = {
|
|
570
|
-
filter
|
|
571
|
-
scope
|
|
557
|
+
filter,
|
|
558
|
+
scope,
|
|
572
559
|
attributes: attrs
|
|
573
560
|
}
|
|
574
|
-
if (config.entity[baseEntity].ldap.groupFilter) ldapOptions.filter += config.entity[baseEntity].ldap.groupFilter
|
|
575
561
|
}
|
|
576
562
|
}
|
|
577
563
|
} else if (getObj.operator === 'eq' && getObj.attribute === 'members.value') {
|
|
@@ -580,19 +566,30 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
580
566
|
ldapOptions = 'getMemberOfGroups'
|
|
581
567
|
} else {
|
|
582
568
|
// optional - simpel filtering
|
|
583
|
-
|
|
569
|
+
if (getObj.operator === 'eq') {
|
|
570
|
+
const [filterAttr, err] = scimgateway.endpointMapper('outbound', getObj.attribute, config.map.group)
|
|
571
|
+
if (err) throw new Error(`${action} error: ${err.message}`)
|
|
572
|
+
const filter = createAndFilter(baseEntity, 'group', [{ attribute: filterAttr, value: getObj.value }])
|
|
573
|
+
ldapOptions = {
|
|
574
|
+
filter,
|
|
575
|
+
scope,
|
|
576
|
+
attributes: attrs
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
throw new Error(`${action} error: not supporting simpel filtering: ${getObj.rawFilter}`)
|
|
580
|
+
}
|
|
584
581
|
}
|
|
585
582
|
} else if (getObj.rawFilter) {
|
|
586
583
|
// optional - advanced filtering having and/or/not - use getObj.rawFilter
|
|
587
584
|
throw new Error(`${action} error: not supporting advanced filtering: ${getObj.rawFilter}`)
|
|
588
585
|
} else {
|
|
589
|
-
|
|
586
|
+
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreGroups() in versions < 4.x.x
|
|
587
|
+
const filter = createAndFilter(baseEntity, 'group', [{ attribute: groupDisplayNameAttr, value: '*' }])
|
|
590
588
|
ldapOptions = {
|
|
591
|
-
filter
|
|
592
|
-
scope
|
|
589
|
+
filter,
|
|
590
|
+
scope,
|
|
593
591
|
attributes: attrs
|
|
594
592
|
}
|
|
595
|
-
if (config.entity[baseEntity].ldap.groupFilter) ldapOptions.filter += config.entity[baseEntity].ldap.groupFilter
|
|
596
593
|
}
|
|
597
594
|
// mandatory if-else logic - end
|
|
598
595
|
|
|
@@ -639,6 +636,7 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
|
|
|
639
636
|
scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" groupObj=${JSON.stringify(groupObj)}`)
|
|
640
637
|
|
|
641
638
|
if (!config.map.group) throw new Error(`${action} error: missing configuration endpoint.map.group`)
|
|
639
|
+
const groupBase = config.entity[baseEntity].ldap.groupBase
|
|
642
640
|
|
|
643
641
|
// convert SCIM attributes to endpoint attributes according to config.map
|
|
644
642
|
const [endpointObj] = scimgateway.endpointMapper('outbound', groupObj, config.map.group)
|
|
@@ -646,13 +644,23 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
|
|
|
646
644
|
// endpointObj.objectClass is mandatory and must must match your ldap schema
|
|
647
645
|
endpointObj.objectClass = config.entity[baseEntity].ldap.groupObjectClasses // Active Directory: ["group"]
|
|
648
646
|
|
|
647
|
+
let base = ''
|
|
648
|
+
const [groupNamingAttr, scimAttr] = getNamingAttribute(baseEntity, 'group') // ['CN', 'displayName']
|
|
649
|
+
const arr = scimAttr.split('.')
|
|
650
|
+
if (arr.length < 2) {
|
|
651
|
+
base = `${groupNamingAttr}=${groupObj[scimAttr]},${groupBase}`
|
|
652
|
+
} else {
|
|
653
|
+
base = `${groupNamingAttr}=${groupObj[arr[0]][arr[1]]},${groupBase}`
|
|
654
|
+
}
|
|
655
|
+
|
|
649
656
|
const method = 'add'
|
|
650
|
-
const base = `${config.entity[baseEntity].ldap.groupNamingAttr}=${groupObj.displayName},${config.entity[baseEntity].ldap.groupBase}`
|
|
651
657
|
const ldapOptions = endpointObj
|
|
652
658
|
|
|
653
659
|
try {
|
|
654
660
|
await doRequest(baseEntity, method, base, ldapOptions, ctx)
|
|
655
|
-
|
|
661
|
+
const res = await scimgateway.getGroups(baseEntity, { attribute: 'id', operator: 'eq', value: base }, [], ctx)
|
|
662
|
+
if (res && Array.isArray(res.Resources) && res.Resources.length === 1) return res.Resources[0]
|
|
663
|
+
else return null
|
|
656
664
|
} catch (err) {
|
|
657
665
|
const newErr = new Error(`${action} error: ${err.message}`)
|
|
658
666
|
if (newErr.message.includes('ENTRY_EXISTS')) newErr.name += '#409' // customErrCode
|
|
@@ -703,7 +711,7 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
703
711
|
const [memberAttr] = scimgateway.endpointMapper('outbound', 'members.value', config.map.group)
|
|
704
712
|
if (!memberAttr && attrObj.members) throw new Error(`${action} error: missing attribute mapping configuration for group members`)
|
|
705
713
|
|
|
706
|
-
const grp = { add: {
|
|
714
|
+
const grp = { add: {}, remove: {} }
|
|
707
715
|
grp.add[memberAttr] = []
|
|
708
716
|
grp.remove[memberAttr] = []
|
|
709
717
|
|
|
@@ -764,22 +772,82 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
764
772
|
const _serviceClient = {}
|
|
765
773
|
|
|
766
774
|
//
|
|
767
|
-
//
|
|
775
|
+
// createAndFilter creates AndFilter object to be used as filter instead of standard string filter
|
|
776
|
+
// Using AndFilter object for eliminating internal ldapjs escaping problems related to values with some
|
|
777
|
+
// combinations of parentheses e.g. ab(c)d
|
|
768
778
|
//
|
|
769
|
-
const
|
|
770
|
-
|
|
779
|
+
const createAndFilter = (baseEntity, type, arrObj) => {
|
|
780
|
+
const objFilters = []
|
|
781
|
+
|
|
782
|
+
// add arrObj
|
|
783
|
+
for (let i = 0; i < arrObj.length; i++) {
|
|
784
|
+
if (arrObj[i].value.indexOf('*') > -1) { // SubstringFilter or PresenceFilter
|
|
785
|
+
const arr = arrObj[i].value.split('*')
|
|
786
|
+
if (arr.length === 2 && !arr[0] && !arr[1]) { // cn=*
|
|
787
|
+
const f = new ldap.PresenceFilter({ attribute: arrObj[i].attribute })
|
|
788
|
+
objFilters.push(f)
|
|
789
|
+
} else { // cn=ab*cd*e
|
|
790
|
+
const fObj = {
|
|
791
|
+
attribute: arrObj[i].attribute
|
|
792
|
+
}
|
|
793
|
+
const arrAny = []
|
|
794
|
+
fObj.initial = arr[0]
|
|
795
|
+
if (!fObj.initial) delete fObj.initial
|
|
796
|
+
for (let i = 1; i < arr.length - 1; i++) {
|
|
797
|
+
arrAny.push(arr[i])
|
|
798
|
+
}
|
|
799
|
+
fObj.any = arrAny
|
|
800
|
+
if (arr[arr.length - 1]) {
|
|
801
|
+
fObj.final = arr[arr.length - 1]
|
|
802
|
+
}
|
|
803
|
+
const f = new ldap.SubstringFilter(fObj)
|
|
804
|
+
objFilters.push(f)
|
|
805
|
+
}
|
|
806
|
+
} else { // EqualityFilter cn=abc
|
|
807
|
+
const f = new ldap.EqualityFilter({ attribute: arrObj[i].attribute, value: arrObj[i].value })
|
|
808
|
+
objFilters.push(f)
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// add from configuration objectClass and userFiter/groupFilter
|
|
771
813
|
switch (type) {
|
|
772
814
|
case 'user':
|
|
773
815
|
for (let i = 0; i < config.entity[baseEntity].ldap.userObjectClasses.length; i++) {
|
|
774
|
-
|
|
816
|
+
const f = new ldap.EqualityFilter({ attribute: 'objectClass', value: config.entity[baseEntity].ldap.userObjectClasses[i] })
|
|
817
|
+
objFilters.push(f)
|
|
818
|
+
}
|
|
819
|
+
if (config.entity[baseEntity].ldap.userFilter) {
|
|
820
|
+
try {
|
|
821
|
+
const uf = ldap.parseFilter(config.entity[baseEntity].ldap.userFilter)
|
|
822
|
+
objFilters.push(uf)
|
|
823
|
+
} catch (err) {
|
|
824
|
+
throw new Error(`configuration ldap.userFilter: ${config.entity[baseEntity].ldap.userFilter} - parseFilter error: ${err.message}`)
|
|
825
|
+
}
|
|
775
826
|
}
|
|
776
827
|
break
|
|
777
828
|
case 'group':
|
|
778
829
|
for (let i = 0; i < config.entity[baseEntity].ldap.groupObjectClasses.length; i++) {
|
|
779
|
-
|
|
830
|
+
const f = new ldap.EqualityFilter({ attribute: 'objectClass', value: config.entity[baseEntity].ldap.groupObjectClasses[i] })
|
|
831
|
+
objFilters.push(f)
|
|
832
|
+
if (config.entity[baseEntity].ldap.groupFilter) {
|
|
833
|
+
try {
|
|
834
|
+
const gf = ldap.parseFilter(config.entity[baseEntity].ldap.groupFilter)
|
|
835
|
+
objFilters.push(gf)
|
|
836
|
+
} catch (err) {
|
|
837
|
+
throw new Error(`configuration ldap.groupFilter: ${config.entity[baseEntity].ldap.groupFilter} - parseFilter error: ${err.message}`)
|
|
838
|
+
}
|
|
839
|
+
}
|
|
780
840
|
}
|
|
781
841
|
break
|
|
782
842
|
}
|
|
843
|
+
|
|
844
|
+
// put all into AndFilter
|
|
845
|
+
const filter = new ldap.AndFilter({
|
|
846
|
+
filters: [
|
|
847
|
+
...objFilters
|
|
848
|
+
]
|
|
849
|
+
})
|
|
850
|
+
|
|
783
851
|
return filter
|
|
784
852
|
}
|
|
785
853
|
|
|
@@ -858,10 +926,10 @@ const convertSidToString = (buf) => {
|
|
|
858
926
|
for (i = 0, end = subAuthorityCount - 1, asc = end >= 0; asc ? i <= end : i >= end; asc ? i++ : i--) {
|
|
859
927
|
const subAuthOffset = i * 4
|
|
860
928
|
const tmp =
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
929
|
+
pad(buf[11 + subAuthOffset].toString(16)) +
|
|
930
|
+
pad(buf[10 + subAuthOffset].toString(16)) +
|
|
931
|
+
pad(buf[9 + subAuthOffset].toString(16)) +
|
|
932
|
+
pad(buf[8 + subAuthOffset].toString(16))
|
|
865
933
|
sidString += `-${parseInt(tmp, 16)}`
|
|
866
934
|
}
|
|
867
935
|
} catch (err) {
|
|
@@ -946,9 +1014,10 @@ const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
|
946
1014
|
const scope = 'sub'
|
|
947
1015
|
const base = config.entity[baseEntity].ldap.groupBase
|
|
948
1016
|
|
|
1017
|
+
const filter = createAndFilter(baseEntity, 'group', [{ attribute: memberAttr, value: idDn }])
|
|
949
1018
|
const ldapOptions = {
|
|
950
|
-
filter
|
|
951
|
-
scope
|
|
1019
|
+
filter,
|
|
1020
|
+
scope,
|
|
952
1021
|
attributes: attrs
|
|
953
1022
|
}
|
|
954
1023
|
|
|
@@ -967,6 +1036,192 @@ const getMemberOfGroups = async (baseEntity, id, ctx) => {
|
|
|
967
1036
|
}
|
|
968
1037
|
}
|
|
969
1038
|
|
|
1039
|
+
//
|
|
1040
|
+
// ldapEscDn will escape DN according to the LDAP standard adjusted to ldapjs behavior
|
|
1041
|
+
// using OpenLDAP, DN must be escaped - national characters and special ldap characters
|
|
1042
|
+
// using Active Directory (none OpenLDAP), DN should not be escaped, but DN retrieved from AD is character escaped
|
|
1043
|
+
//
|
|
1044
|
+
const ldapEscDn = (isOpenLdap, str) => {
|
|
1045
|
+
if (!str) return str
|
|
1046
|
+
|
|
1047
|
+
if (!isOpenLdap && str.indexOf('\\') > 0) {
|
|
1048
|
+
const conv = str.replace(/\\([0-9A-Fa-f]{2})/g, (_, hex) => {
|
|
1049
|
+
const intAscii = parseInt(hex, 16)
|
|
1050
|
+
if (intAscii > 128) { // extended ascii - will be unescaped by decodeURIComponent
|
|
1051
|
+
return '%' + hex
|
|
1052
|
+
} else { // use character escape
|
|
1053
|
+
return '\\' + String.fromCharCode(intAscii)
|
|
1054
|
+
}
|
|
1055
|
+
})
|
|
1056
|
+
str = decodeURIComponent(conv)
|
|
1057
|
+
str = str.replace(/\\/g, '') // lower ascii may be character escaped e.g. 'cn=Kürt\, Lastname' - see below comma logic
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const arr = str.split(',')
|
|
1061
|
+
for (let i = 0; i < arr.length; i++) { // CN=Firstname, Lastname,OU=...
|
|
1062
|
+
if (!arr[i]) { // value having comma only
|
|
1063
|
+
if (arr[i - 1].charAt(arr[i - 1].length - 1) === '\\') {
|
|
1064
|
+
arr[i - 1] = arr[i - 1].substring(0, arr[i - 1].length - 1)
|
|
1065
|
+
}
|
|
1066
|
+
if (isOpenLdap) arr[i - 1] += '\\,'
|
|
1067
|
+
else arr[i - 1] += ','
|
|
1068
|
+
arr.splice(i, 1)
|
|
1069
|
+
i -= 1
|
|
1070
|
+
continue
|
|
1071
|
+
}
|
|
1072
|
+
const a = arr[i].split('=')
|
|
1073
|
+
if (a.length < 2 && i > 0) { // value having comma and content
|
|
1074
|
+
if (arr[i - 1].charAt(arr[i - 1].length - 1) === '\\') {
|
|
1075
|
+
arr[i - 1] = arr[i - 1].substring(0, arr[i - 1].length - 1)
|
|
1076
|
+
}
|
|
1077
|
+
if (isOpenLdap) arr[i - 1] += `\\,${ldapEsc(a[0])}`
|
|
1078
|
+
else arr[i - 1] += `,${a[0]}`
|
|
1079
|
+
arr.splice(i, 1)
|
|
1080
|
+
i -= 1
|
|
1081
|
+
continue
|
|
1082
|
+
} else {
|
|
1083
|
+
if (isOpenLdap) arr[i] = `${a[0]}=${ldapEsc(a[1])}`
|
|
1084
|
+
else arr[i] = `${a[0]}=${a[1]}`
|
|
1085
|
+
}
|
|
1086
|
+
if (i > 0) break // only escape logic on first, assume sub OU's are correct
|
|
1087
|
+
}
|
|
1088
|
+
if (isOpenLdap) {
|
|
1089
|
+
str = arr.join(',')
|
|
1090
|
+
return str
|
|
1091
|
+
}
|
|
1092
|
+
// Using dn object and BER encoding
|
|
1093
|
+
// e.g., Active Directory to avoid internal ldapjs OpenLDAP validating and string escaping logic
|
|
1094
|
+
const dn = new ldap.DN()
|
|
1095
|
+
for (let i = 0; i < arr.length; i++) {
|
|
1096
|
+
const a = arr[i].split('=') // cn=Kürt
|
|
1097
|
+
if (a.length === 2) {
|
|
1098
|
+
if (i === 0) {
|
|
1099
|
+
const ua = new Uint8Array(Buffer.from(a[1], 'utf-8'))
|
|
1100
|
+
const buf = Buffer.from(new Uint8Array([4, ua.length, ...ua]))
|
|
1101
|
+
const rdn = {}
|
|
1102
|
+
rdn[a[0]] = new BerReader(buf)
|
|
1103
|
+
dn.push(new ldap.RDN(rdn))
|
|
1104
|
+
// new BerReader(Buffer.from([0x04, 0x05, 0x4B, 0xc3, 0xbc, 0x72, 0x74])) // Kürt
|
|
1105
|
+
// the leading 04 is the tag for "octet string" and the following 05 is the length in bytes of the string.
|
|
1106
|
+
} else {
|
|
1107
|
+
const rdn = {}
|
|
1108
|
+
rdn[a[0]] = a[1]
|
|
1109
|
+
dn.push(new ldap.RDN(rdn))
|
|
1110
|
+
}
|
|
1111
|
+
} else {
|
|
1112
|
+
throw new Error('ldapEscDn() invalid DN: ' + str)
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return dn
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
//
|
|
1119
|
+
// ldapEsc will character escape str according to OpenLDAP DN standard
|
|
1120
|
+
// Hex encoded escaping (extended and unicode ascii) is not included because
|
|
1121
|
+
// automatically handled by ldapjs when not using BER encoded DN
|
|
1122
|
+
//
|
|
1123
|
+
const ldapEsc = (str) => {
|
|
1124
|
+
if (!str) return str
|
|
1125
|
+
let newStr = ''
|
|
1126
|
+
for (let i = 0; i < str.length; i++) {
|
|
1127
|
+
let c = str[i]
|
|
1128
|
+
let isEsc = false
|
|
1129
|
+
if (i > 0 && str[i - 1] === '\\') isEsc = true
|
|
1130
|
+
switch (c) {
|
|
1131
|
+
case ',':
|
|
1132
|
+
if (isEsc) c = ','
|
|
1133
|
+
else c = '\\,'
|
|
1134
|
+
break
|
|
1135
|
+
case ';':
|
|
1136
|
+
if (isEsc) c = ';'
|
|
1137
|
+
else c = '\\;'
|
|
1138
|
+
break
|
|
1139
|
+
case '+':
|
|
1140
|
+
if (isEsc) c = '+'
|
|
1141
|
+
else c = '\\+'
|
|
1142
|
+
break
|
|
1143
|
+
case '<':
|
|
1144
|
+
if (isEsc) c = '<'
|
|
1145
|
+
else c = '\\<'
|
|
1146
|
+
break
|
|
1147
|
+
case '>':
|
|
1148
|
+
if (isEsc) c = '>'
|
|
1149
|
+
else c = '\\>'
|
|
1150
|
+
break
|
|
1151
|
+
case '=':
|
|
1152
|
+
if (isEsc) c = '='
|
|
1153
|
+
else c = '\\='
|
|
1154
|
+
break
|
|
1155
|
+
case '"':
|
|
1156
|
+
if (isEsc) c = '"'
|
|
1157
|
+
else c = '\\"'
|
|
1158
|
+
break
|
|
1159
|
+
case '(':
|
|
1160
|
+
if (isEsc) c = '('
|
|
1161
|
+
else c = '\\('
|
|
1162
|
+
break
|
|
1163
|
+
case ')':
|
|
1164
|
+
if (isEsc) c = ')'
|
|
1165
|
+
else c = '\\)'
|
|
1166
|
+
break
|
|
1167
|
+
}
|
|
1168
|
+
newStr += c
|
|
1169
|
+
}
|
|
1170
|
+
return newStr
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
//
|
|
1174
|
+
// berDecodeDn decodes a BER string part of type DN
|
|
1175
|
+
// OU=#04057573657273,OU=abc,... => OU=users,OU=abc,...
|
|
1176
|
+
// only using BER on first part of dn
|
|
1177
|
+
// Having BER decoding for Active Directory, but not for OpenLDAP
|
|
1178
|
+
//
|
|
1179
|
+
const berDecodeDn = (dn) => {
|
|
1180
|
+
if (Object.prototype.toString.call(dn) !== '[object LdapDn]') return dn // OpenLDAP
|
|
1181
|
+
const str = dn.toString()
|
|
1182
|
+
if (str.indexOf('#') < 1) return str
|
|
1183
|
+
const arr = str.split('#')
|
|
1184
|
+
if (arr.length === 2) {
|
|
1185
|
+
const a = arr[1].split(',')
|
|
1186
|
+
if (a.length > 1) {
|
|
1187
|
+
const berStr = a[0].substring(4)
|
|
1188
|
+
let decoded = ''
|
|
1189
|
+
let c = ''
|
|
1190
|
+
if (berStr.length % 2 === 0) {
|
|
1191
|
+
for (let i = 0; i < berStr.length; i++) {
|
|
1192
|
+
c += berStr[i]
|
|
1193
|
+
if (c.length === 2) {
|
|
1194
|
+
const intAscii = parseInt(c, 16)
|
|
1195
|
+
decoded += String.fromCharCode(intAscii)
|
|
1196
|
+
c = ''
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (decoded.length > 0) {
|
|
1201
|
+
a.splice(0, 1) // remove element 0 from array
|
|
1202
|
+
return `${arr[0]}${decoded},${a.join(',')}` // OU=users,OU=abc
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return str
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const getNamingAttribute = (baseEntity, type) => {
|
|
1210
|
+
let arr
|
|
1211
|
+
switch (type) {
|
|
1212
|
+
case 'user':
|
|
1213
|
+
arr = config.entity[baseEntity]?.ldap?.namingAttribute?.user
|
|
1214
|
+
break
|
|
1215
|
+
case 'group':
|
|
1216
|
+
arr = config.entity[baseEntity]?.ldap?.namingAttribute?.group
|
|
1217
|
+
break
|
|
1218
|
+
default:
|
|
1219
|
+
throw new Error(`getNamingAttribute error: invalid type ${type}`)
|
|
1220
|
+
}
|
|
1221
|
+
if (!Array.isArray(arr) || arr.length !== 1) throw new Error(`configuration missing namingAttribute definition for ${type}`)
|
|
1222
|
+
return [arr[0].attribute, arr[0].mapTo]
|
|
1223
|
+
}
|
|
1224
|
+
|
|
970
1225
|
//
|
|
971
1226
|
// getCtxAuth returns username/secret from ctx header when using Auth PassThrough
|
|
972
1227
|
//
|
|
@@ -1034,11 +1289,11 @@ const getServiceClient = async (baseEntity, ctx) => {
|
|
|
1034
1289
|
// "attributes": ["sAMAccountName","displayName","mail"]
|
|
1035
1290
|
// }
|
|
1036
1291
|
//
|
|
1037
|
-
const doRequest = async (baseEntity, method, base,
|
|
1292
|
+
const doRequest = async (baseEntity, method, base, options, ctx) => {
|
|
1038
1293
|
let result = null
|
|
1039
1294
|
let client = null
|
|
1295
|
+
base = ldapEscDn(config.entity[baseEntity].ldap.isOpenLdap, base)
|
|
1040
1296
|
|
|
1041
|
-
const options = scimgateway.copyObj(ldapOptions)
|
|
1042
1297
|
// support having different upn-domain on IdP and target
|
|
1043
1298
|
if (options.modification && options.modification.userPrincipalName && config.map.user.userPrincipalName && config.map.user.userPrincipalName.mapDomain) {
|
|
1044
1299
|
if (options.modification.userPrincipalName.endsWith(config.map.user.userPrincipalName.mapDomain.outbound)) {
|
|
@@ -1055,7 +1310,8 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1055
1310
|
options.paged = { pageSize: 200, pagePause: false } // parse entire directory calling 'page' method for each page
|
|
1056
1311
|
result = await new Promise((resolve, reject) => {
|
|
1057
1312
|
const results = []
|
|
1058
|
-
|
|
1313
|
+
|
|
1314
|
+
client.search(base, options, (err, search) => {
|
|
1059
1315
|
if (err) {
|
|
1060
1316
|
return reject(err)
|
|
1061
1317
|
}
|
|
@@ -1086,6 +1342,23 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1086
1342
|
scimgateway.logger.debug(`${pluginName}[${baseEntity}] outbound upnMapDomain ${old} => ${obj.userPrincipalName}`)
|
|
1087
1343
|
}
|
|
1088
1344
|
}
|
|
1345
|
+
|
|
1346
|
+
if (obj.dn && obj.dn.indexOf('\\') > 0) {
|
|
1347
|
+
// for OpenLDAP ensure dn is not hex escaped e.g.: cn=K\c3\bcrt => cn=Kürt
|
|
1348
|
+
// because dn may be be used as value in standard attributes like group memberOf
|
|
1349
|
+
obj.dn = obj.dn.replace(/\\\\/g, '__') // temp
|
|
1350
|
+
let conv = obj.dn.replace(/\\([0-9A-Fa-f]{2})/g, (_, hex) => {
|
|
1351
|
+
const intAscii = parseInt(hex, 16)
|
|
1352
|
+
if (intAscii > 128) { // extended ascii - will be unescaped by decodeURIComponent
|
|
1353
|
+
return '%' + hex
|
|
1354
|
+
} else { // use character escape
|
|
1355
|
+
return '\\' + String.fromCharCode(intAscii)
|
|
1356
|
+
}
|
|
1357
|
+
})
|
|
1358
|
+
conv = conv.replace(/__/g, '\\\\')
|
|
1359
|
+
obj.dn = decodeURIComponent(conv)
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1089
1362
|
results.push(obj)
|
|
1090
1363
|
})
|
|
1091
1364
|
|
|
@@ -1115,11 +1388,10 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1115
1388
|
if (mod.values.length > 1) { // delete before replace to keep inbound order
|
|
1116
1389
|
changes.push({
|
|
1117
1390
|
operation: 'delete',
|
|
1118
|
-
modification: {type: key, values: []}
|
|
1391
|
+
modification: { type: key, values: [] }
|
|
1119
1392
|
})
|
|
1120
1393
|
}
|
|
1121
|
-
}
|
|
1122
|
-
else {
|
|
1394
|
+
} else {
|
|
1123
1395
|
if (typeof options.modification[key] === 'string') mod.values = [options.modification[key]]
|
|
1124
1396
|
else mod.values = [options.modification[key].toString()]
|
|
1125
1397
|
}
|
|
@@ -1168,14 +1440,20 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1168
1440
|
}
|
|
1169
1441
|
client.unbind()
|
|
1170
1442
|
} catch (err) {
|
|
1171
|
-
|
|
1443
|
+
if (options.filter && typeof options.filter === 'object') {
|
|
1444
|
+
options.filter = options.filter.toString()
|
|
1445
|
+
}
|
|
1446
|
+
scimgateway.logger.error(`${pluginName}[${baseEntity}] doRequest method=${method} base=${berDecodeDn(base)} ldapOptions=${JSON.stringify(options)} Error Response = ${err.message}`)
|
|
1172
1447
|
if (client) {
|
|
1173
|
-
try { client.destroy() } catch (err) {}
|
|
1448
|
+
try { client.destroy() } catch (err) { }
|
|
1174
1449
|
}
|
|
1175
1450
|
throw err
|
|
1176
1451
|
}
|
|
1177
1452
|
|
|
1178
|
-
|
|
1453
|
+
if (options.filter && typeof options.filter === 'object') {
|
|
1454
|
+
options.filter = options.filter.toString()
|
|
1455
|
+
}
|
|
1456
|
+
scimgateway.logger.debug(`${pluginName}[${baseEntity}] doRequest method=${method} base=${berDecodeDn(base)} ldapOptions=${JSON.stringify(options)} Response=${JSON.stringify(result)}`)
|
|
1179
1457
|
return result
|
|
1180
1458
|
} // doRequest
|
|
1181
1459
|
|
|
@@ -1186,3 +1464,121 @@ process.on('SIGTERM', () => { // kill
|
|
|
1186
1464
|
})
|
|
1187
1465
|
process.on('SIGINT', () => { // Ctrl+C
|
|
1188
1466
|
})
|
|
1467
|
+
|
|
1468
|
+
//
|
|
1469
|
+
// startup initialization
|
|
1470
|
+
// at the end to ensure scimgatway logger have started
|
|
1471
|
+
//
|
|
1472
|
+
if (!config?.map?.user) {
|
|
1473
|
+
scimgateway.logger.error('configuration map.user is missing')
|
|
1474
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1475
|
+
} else {
|
|
1476
|
+
let idFound = false
|
|
1477
|
+
let userNameFound = false
|
|
1478
|
+
for (const key in config.map.user) {
|
|
1479
|
+
if (config.map.user[key].mapTo === 'id') idFound = true
|
|
1480
|
+
else if (['userName', 'externalId'].includes(config.map.user[key].mapTo)) userNameFound = true
|
|
1481
|
+
if (idFound && userNameFound) break
|
|
1482
|
+
}
|
|
1483
|
+
if (!idFound || !userNameFound) {
|
|
1484
|
+
scimgateway.logger.error('configuration map.user missing mapTo definition for mandatory id/userName')
|
|
1485
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
if (!config?.map?.group) {
|
|
1489
|
+
scimgateway.logger.info('configuration map.group is not defiend and groups will not be supported')
|
|
1490
|
+
} else {
|
|
1491
|
+
let idFound = false
|
|
1492
|
+
let displayNameFound = false
|
|
1493
|
+
for (const key in config.map.group) {
|
|
1494
|
+
if (config.map.group[key].mapTo === 'id') idFound = true
|
|
1495
|
+
else if (config.map.group[key].mapTo === 'displayName') displayNameFound = true
|
|
1496
|
+
if (idFound && displayNameFound) break
|
|
1497
|
+
}
|
|
1498
|
+
if ((!idFound || !displayNameFound) && (Object.keys(config.map.group).length > 0)) {
|
|
1499
|
+
scimgateway.logger.error('configuration map.group missing mapTo definition for mandatory id/displayName')
|
|
1500
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
for (const key in config.entity) {
|
|
1505
|
+
const userBase = config.entity[key]?.ldap?.userBase
|
|
1506
|
+
const groupBase = config.entity[key]?.ldap?.groupBase
|
|
1507
|
+
if (!userBase) {
|
|
1508
|
+
scimgateway.logger.error(`configuration missing mandatory endpoint.entity.${key}.ldap.userBase`)
|
|
1509
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1510
|
+
}
|
|
1511
|
+
if (!groupBase && config?.map?.group && Object.keys(config.map.group).length > 0) {
|
|
1512
|
+
scimgateway.logger.error(`configuration missing mandatory endpoint.entity.${key}.ldap.groupBase`)
|
|
1513
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1514
|
+
}
|
|
1515
|
+
let usrArr = config.entity[key]?.ldap?.namingAttribute?.user
|
|
1516
|
+
if (!usrArr || !Array.isArray(usrArr)) { // check for legacy
|
|
1517
|
+
const attr = config.entity[key]?.ldap?.userNamingAttr
|
|
1518
|
+
if (attr) {
|
|
1519
|
+
usrArr = [{ attribute: attr, mapTo: 'userName' }]
|
|
1520
|
+
if (!config.entity[key].ldap.namingAttribute) config.entity[key].ldap.namingAttribute = {}
|
|
1521
|
+
config.entity[key].ldap.namingAttribute.user = scimgateway.copyObj(usrArr)
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if (!Array.isArray(usrArr) || usrArr.length !== 1) {
|
|
1525
|
+
scimgateway.logger.error(`configuration missing namingAttribute: endpoint.entity.${key}.ldap.namingAttribute.user`)
|
|
1526
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1527
|
+
}
|
|
1528
|
+
if (!usrArr[0].attribute || !usrArr[0].mapTo) {
|
|
1529
|
+
scimgateway.logger.error(`configuration missing attribute/mapTo: endpoint.entity.${key}.ldap.namingAttribute.user`)
|
|
1530
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1531
|
+
}
|
|
1532
|
+
const [endpointAttr] = scimgateway.endpointMapper('outbound', usrArr[0].mapTo, config.map.user)
|
|
1533
|
+
if (!endpointAttr) {
|
|
1534
|
+
scimgateway.logger.error(`configuration namingAttribute mapTo:${usrArr[0].mapTo} cannot be found in the map user configuration`)
|
|
1535
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
let grpArr = config.entity[key]?.ldap?.namingAttribute?.group
|
|
1539
|
+
if (config?.map?.group && Object.keys(config.map.group).length > 0) {
|
|
1540
|
+
if (!grpArr || !Array.isArray(grpArr)) { // check for legacy
|
|
1541
|
+
const attr = config.entity[key]?.ldap?.groupNamingAttr
|
|
1542
|
+
if (attr) {
|
|
1543
|
+
grpArr = [{ attribute: attr, mapTo: 'displayName' }]
|
|
1544
|
+
if (!config.entity[key].ldap.namingAttribute) config.entity[key].ldap.namingAttribute = {}
|
|
1545
|
+
config.entity[key].ldap.namingAttribute.group = scimgateway.copyObj(grpArr)
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
if (!Array.isArray(grpArr) || grpArr.length !== 1) {
|
|
1549
|
+
scimgateway.logger.error(`configuration missing namingAttribute: endpoint.entity.${key}.ldap.namingAttribute.group`)
|
|
1550
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1551
|
+
}
|
|
1552
|
+
if (!grpArr[0].attribute || !grpArr[0].mapTo) {
|
|
1553
|
+
scimgateway.logger.error(`configuration missing attribute/mapTo: endpoint.entity.${key}.ldap.namingAttribute.group`)
|
|
1554
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1555
|
+
}
|
|
1556
|
+
const [endpointAttr] = scimgateway.endpointMapper('outbound', grpArr[0].mapTo, config.map.group)
|
|
1557
|
+
if (!endpointAttr) {
|
|
1558
|
+
scimgateway.logger.error(`configuration namingAttribute mapTo:${grpArr[0].mapTo} cannot be found in the map group configuration`)
|
|
1559
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
config.useSID_id = config.map.user.objectSid && config.map.user.objectSid.mapTo === 'id' // AD proprietary SID/GUID
|
|
1565
|
+
config.useGUID_id = config.map.user.objectGUID && config.map.user.objectGUID.mapTo === 'id'
|
|
1566
|
+
if (config.useSID_id && config.map.group) {
|
|
1567
|
+
if (!config.map.group.objectSid || config.map.group.objectSid.mapTo !== 'id') {
|
|
1568
|
+
scimgateway.logger.error('configuration missing group.objectSid - user and group should be using the same attribute')
|
|
1569
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1570
|
+
}
|
|
1571
|
+
} else if (config.useGUID_id && config.map.group) {
|
|
1572
|
+
if (!config.map.group.objectGUID || config.map.group.objectGUID.mapTo !== 'id') {
|
|
1573
|
+
scimgateway.logger.error('configuration missing group.objectGUID - user and group should be using the same attribute')
|
|
1574
|
+
throw new Error(`using exception to exit ${pluginName}, please ignore message...`)
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
if (config.map.user.userPrincipalName && config.map.user.userPrincipalName.mapDomain) { // support mapping different inbound/outbound upn domain names
|
|
1578
|
+
if (config.map.user.userPrincipalName.mapDomain.inbound && config.map.user.userPrincipalName.mapDomain.outbound) {
|
|
1579
|
+
const inbound = config.map.user.userPrincipalName.mapDomain.inbound
|
|
1580
|
+
const outbound = config.map.user.userPrincipalName.mapDomain.outbound
|
|
1581
|
+
config.map.user.userPrincipalName.mapDomain.inbound = inbound.startsWith('@') ? inbound : '@' + inbound // "@my-company.com"
|
|
1582
|
+
config.map.user.userPrincipalName.mapDomain.outbound = outbound.startsWith('@') ? outbound : '@' + outbound // "@test.onmicrosoft.com
|
|
1583
|
+
} else delete config.map.user.userPrincipalName.mapDomain
|
|
1584
|
+
}
|
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/lib/utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.9",
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"callsite": "^1.0.0",
|
|
35
35
|
"dot-object": "^2.1.5",
|
|
36
36
|
"fold-to-ascii": "^5.0.1",
|
|
37
|
-
"https-proxy-agent": "^7.0.
|
|
37
|
+
"https-proxy-agent": "^7.0.5",
|
|
38
38
|
"is-in-subnet": "^4.0.1",
|
|
39
39
|
"jsonwebtoken": "^9.0.2",
|
|
40
40
|
"koa": "^2.15.3",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"koa-router": "^12.0.1",
|
|
43
43
|
"ldapjs": "^3.0.7",
|
|
44
44
|
"lokijs": "^1.5.12",
|
|
45
|
-
"mongodb": "^6.
|
|
46
|
-
"nats": "^2.
|
|
45
|
+
"mongodb": "^6.9.0",
|
|
46
|
+
"nats": "^2.28.2",
|
|
47
47
|
"node-machine-id": "1.1.9",
|
|
48
|
-
"nodemailer": "^6.9.
|
|
48
|
+
"nodemailer": "^6.9.15",
|
|
49
49
|
"passport": "^0.7.0",
|
|
50
50
|
"passport-azure-ad": "^4.3.5",
|
|
51
|
-
"tedious": "^18.
|
|
52
|
-
"winston": "^3.
|
|
51
|
+
"tedious": "^18.6.1",
|
|
52
|
+
"winston": "^3.14.2"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"chai": "^4.2.0",
|