scimgateway 5.3.2 → 5.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/lib/helper-rest.ts +1 -0
- package/lib/plugin-entra-id.ts +5 -7
- package/lib/plugin-loki.ts +16 -23
- package/lib/plugin-mongodb.ts +8 -9
- package/lib/plugin-soap.ts +3 -4
- package/lib/utils-scim.ts +61 -37
- package/lib/utils.ts +2 -1
- package/package.json +1 -1
- package/tsconfig.json +1 -1
package/README.md
CHANGED
|
@@ -1405,6 +1405,36 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1405
1405
|
|
|
1406
1406
|
## Change log
|
|
1407
1407
|
|
|
1408
|
+
### v5.3.4
|
|
1409
|
+
|
|
1410
|
+
[Fixed]
|
|
1411
|
+
|
|
1412
|
+
- PATCH operations (modifyUser/modifyGroup) that includes `null` values, will now be converted to empty string `""`
|
|
1413
|
+
|
|
1414
|
+
{
|
|
1415
|
+
"schemas": [
|
|
1416
|
+
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
|
|
1417
|
+
],
|
|
1418
|
+
"Operations": [{
|
|
1419
|
+
"op": "replace",
|
|
1420
|
+
"value": {
|
|
1421
|
+
"name": {
|
|
1422
|
+
"formatted": "Smith, John",
|
|
1423
|
+
"honorificPrefix": null
|
|
1424
|
+
}
|
|
1425
|
+
}}
|
|
1426
|
+
]
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
In the example above, following will be sent to plugin:
|
|
1430
|
+
{ "name": { "formatted": "Smith, John", "honorificPrefix": "" } }
|
|
1431
|
+
|
|
1432
|
+
### v5.3.3
|
|
1433
|
+
|
|
1434
|
+
[Fixed]
|
|
1435
|
+
|
|
1436
|
+
- helper-rest, SamlBearer token-request now includes `new_token=true` to avoid retrieving an existing token that is about to expire
|
|
1437
|
+
|
|
1408
1438
|
### v5.3.2
|
|
1409
1439
|
|
|
1410
1440
|
[Improved]
|
package/lib/helper-rest.ts
CHANGED
|
@@ -142,6 +142,7 @@ export class HelperRest {
|
|
|
142
142
|
grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
|
|
143
143
|
client_id: clientId,
|
|
144
144
|
company_id: companyId,
|
|
145
|
+
new_token: true,
|
|
145
146
|
assertion: await samlAssertion.run(context, cert, key, issuer, lifetime, clientId, nameId, userIdentifierFormat, tokenEndpoint, audience, delay),
|
|
146
147
|
}
|
|
147
148
|
break
|
package/lib/plugin-entra-id.ts
CHANGED
|
@@ -265,11 +265,8 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
265
265
|
if (parsedAttrObj instanceof Error) throw (parsedAttrObj) // error object
|
|
266
266
|
|
|
267
267
|
const objManager: Record<string, any> = {}
|
|
268
|
-
if (parsedAttrObj
|
|
269
|
-
objManager.manager =
|
|
270
|
-
delete parsedAttrObj.manager
|
|
271
|
-
} else if (parsedAttrObj.manager === null) { // delete manager
|
|
272
|
-
objManager.manager = null
|
|
268
|
+
if (Object.prototype.hasOwnProperty.call(parsedAttrObj, 'manager')) {
|
|
269
|
+
objManager.manager = parsedAttrObj.manager
|
|
273
270
|
delete parsedAttrObj.manager
|
|
274
271
|
}
|
|
275
272
|
|
|
@@ -309,6 +306,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
309
306
|
const manager = () => {
|
|
310
307
|
return new Promise((resolve, reject) => {
|
|
311
308
|
(async () => {
|
|
309
|
+
if (!Object.prototype.hasOwnProperty.call(objManager, 'manager')) return resolve(null)
|
|
312
310
|
let method: string | null = null
|
|
313
311
|
let path: string | null = null
|
|
314
312
|
let body: Record<string, any> | null = null
|
|
@@ -317,11 +315,11 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
317
315
|
method = 'PUT'
|
|
318
316
|
path = `/users/${id}/manager/$ref`
|
|
319
317
|
body = { '@odata.id': `${graphUrl}/users/${objManager.manager}` }
|
|
320
|
-
} else
|
|
318
|
+
} else { // delete manager (null/undefined/'')
|
|
321
319
|
method = 'DELETE'
|
|
322
320
|
path = `/users/${id}/manager/$ref`
|
|
323
321
|
body = null
|
|
324
|
-
}
|
|
322
|
+
}
|
|
325
323
|
try {
|
|
326
324
|
await helper.doRequest(baseEntity, method, path, body, ctx)
|
|
327
325
|
resolve(null)
|
package/lib/plugin-loki.ts
CHANGED
|
@@ -380,31 +380,24 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj) => {
|
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
} else {
|
|
383
|
-
// None multi value attribute
|
|
384
|
-
if (typeof (attrObj[key])
|
|
385
|
-
if (attrObj[key] === '' || attrObj[key] === null) delete userObj[key]
|
|
386
|
-
else userObj[key] = attrObj[key]
|
|
387
|
-
} else {
|
|
383
|
+
// None multi value attribute, blank will be deleted
|
|
384
|
+
if (typeof (attrObj[key]) === 'object' && attrObj[key] !== null) {
|
|
388
385
|
// name.familyName=Bianchi
|
|
389
386
|
if (!userObj[key]) userObj[key] = {} // e.g name object does not exist
|
|
390
|
-
for (const sub in attrObj[key]) {
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (Object.prototype.hasOwnProperty.call(attrObj[key][sub], 'value')
|
|
399
|
-
&& attrObj[key][sub].value === '') delete userObj[key][sub] // object having blank value attribute e.g. {"manager": {"value": "",...}}
|
|
400
|
-
else if (attrObj[key][sub] === '') delete userObj[key][sub]
|
|
401
|
-
else {
|
|
402
|
-
if (!userObj[key]) userObj[key] = {} // may have been deleted by length check below
|
|
403
|
-
userObj[key][sub] = attrObj[key][sub]
|
|
404
|
-
}
|
|
405
|
-
if (Object.keys(userObj[key]).length < 1) delete userObj[key]
|
|
387
|
+
for (const sub in attrObj[key]) {
|
|
388
|
+
if (!userObj[key]) userObj[key] = {}
|
|
389
|
+
if (Object.prototype.hasOwnProperty.call(attrObj[key][sub], 'value')
|
|
390
|
+
&& attrObj[key][sub].value === '') delete userObj[key][sub] // object having blank value attribute e.g. {"manager": {"value": "",...}}
|
|
391
|
+
else if (attrObj[key][sub] === '') delete userObj[key][sub]
|
|
392
|
+
else {
|
|
393
|
+
if (!userObj[key]) userObj[key] = {} // may have been deleted by length check below
|
|
394
|
+
userObj[key][sub] = attrObj[key][sub]
|
|
406
395
|
}
|
|
396
|
+
if (Object.keys(userObj[key]).length < 1) delete userObj[key]
|
|
407
397
|
}
|
|
398
|
+
} else {
|
|
399
|
+
if (attrObj[key] === '') delete userObj[key]
|
|
400
|
+
else userObj[key] = attrObj[key]
|
|
408
401
|
}
|
|
409
402
|
}
|
|
410
403
|
}
|
|
@@ -581,7 +574,7 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
581
574
|
if (!Array.isArray(attrObj.members)) {
|
|
582
575
|
throw new Error(`${action} error: ${JSON.stringify(attrObj)} - correct syntax is { "members": [...] }`)
|
|
583
576
|
}
|
|
584
|
-
|
|
577
|
+
for (const el of attrObj.members) {
|
|
585
578
|
if (el.operation && el.operation === 'delete') { // delete member from group
|
|
586
579
|
if (!el.value) groupObj.members = [] // members=[{"operation":"delete"}] => no value, delete all members
|
|
587
580
|
else {
|
|
@@ -601,7 +594,7 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
601
594
|
} else usersNotExist.push(el.value)
|
|
602
595
|
}
|
|
603
596
|
}
|
|
604
|
-
}
|
|
597
|
+
}
|
|
605
598
|
}
|
|
606
599
|
|
|
607
600
|
delete attrObj.members
|
package/lib/plugin-mongodb.ts
CHANGED
|
@@ -439,16 +439,12 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
439
439
|
if (attrObj[key] === '' || attrObj[key] === null) delete userObj[key]
|
|
440
440
|
else userObj[key] = attrObj[key]
|
|
441
441
|
} else {
|
|
442
|
+
// None multi value attribute, blank will be deleted
|
|
443
|
+
if (typeof (attrObj[key]) === 'object' && attrObj[key] !== null) {
|
|
442
444
|
// name.familyName=Bianchi
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
attrObj[key][sub].forEach((element) => {
|
|
447
|
-
const arrSub = element.split('.')
|
|
448
|
-
if (arrSub.length === 2) userObj[arrSub[0]][arrSub[1]] = '' // e.g. name.familyName
|
|
449
|
-
else userObj[element] = ''
|
|
450
|
-
})
|
|
451
|
-
} else {
|
|
445
|
+
if (!userObj[key]) userObj[key] = {} // e.g name object does not exist
|
|
446
|
+
for (const sub in attrObj[key]) {
|
|
447
|
+
if (!userObj[key]) userObj[key] = {}
|
|
452
448
|
if (Object.prototype.hasOwnProperty.call(attrObj[key][sub], 'value')
|
|
453
449
|
&& attrObj[key][sub].value === '') delete userObj[key][sub] // object having blank value attribute e.g. {"manager": {"value": "",...}}
|
|
454
450
|
else if (attrObj[key][sub] === '') delete userObj[key][sub]
|
|
@@ -458,6 +454,9 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
458
454
|
}
|
|
459
455
|
if (Object.keys(userObj[key]).length < 1) delete userObj[key]
|
|
460
456
|
}
|
|
457
|
+
} else {
|
|
458
|
+
if (attrObj[key] === '') delete userObj[key]
|
|
459
|
+
else userObj[key] = attrObj[key]
|
|
461
460
|
}
|
|
462
461
|
}
|
|
463
462
|
}
|
package/lib/plugin-soap.ts
CHANGED
|
@@ -436,7 +436,7 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
436
436
|
try {
|
|
437
437
|
const serviceClient = await getServiceClient(baseEntity, action, ctx)
|
|
438
438
|
|
|
439
|
-
attrObj.members
|
|
439
|
+
for (const el of attrObj.members) {
|
|
440
440
|
if (el.operation && el.operation === 'delete') { // delete member from group
|
|
441
441
|
const soapRequest = {
|
|
442
442
|
groupID: id,
|
|
@@ -451,7 +451,6 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
451
451
|
if (!result.return) {
|
|
452
452
|
throw new Error(`${config[action].service}-removeUserFromGroup : Got empty response on soap request: ${soapRequest}`)
|
|
453
453
|
}
|
|
454
|
-
return null
|
|
455
454
|
} else { // add member to group
|
|
456
455
|
const soapRequest = {
|
|
457
456
|
groupID: id,
|
|
@@ -466,9 +465,9 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
|
466
465
|
if (!result.return) {
|
|
467
466
|
throw new Error(`assignUserToGroup : Got empty response on soap request: ${soapRequest}`)
|
|
468
467
|
}
|
|
469
|
-
return null
|
|
470
468
|
}
|
|
471
|
-
}
|
|
469
|
+
}
|
|
470
|
+
return null
|
|
472
471
|
} catch (err: any) {
|
|
473
472
|
throw new Error(`${action} error: ${err.message}`)
|
|
474
473
|
}
|
package/lib/utils-scim.ts
CHANGED
|
@@ -87,37 +87,55 @@ export function convertedScim(obj: any, multiValueTypes: string[]): any {
|
|
|
87
87
|
})
|
|
88
88
|
delete scimdata[key]
|
|
89
89
|
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
90
|
+
} else if (key === 'active' && typeof scimdata[key] === 'string') {
|
|
91
|
+
const lcase = scimdata.active.toLowerCase()
|
|
92
|
+
if (lcase === 'true') scimdata.active = true
|
|
93
|
+
else if (lcase === 'false') scimdata.active = false
|
|
94
|
+
} else if (key === 'meta') { // cleared attributes e.g { meta: { attributes: [ 'name.givenName', 'title' ] } }
|
|
95
|
+
if (Array.isArray(scimdata.meta.attributes)) {
|
|
96
|
+
scimdata.meta.attributes.forEach((el: string) => {
|
|
97
|
+
let rootKey = ''
|
|
98
|
+
let subKey = ''
|
|
99
|
+
if (el.startsWith('urn:')) { // can't use dot.str on key having dot e.g. urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department
|
|
100
|
+
const i = el.lastIndexOf(':')
|
|
101
|
+
subKey = el.substring(i + 1)
|
|
102
|
+
if (subKey === 'User' || subKey === 'Group') rootKey = el
|
|
103
|
+
else rootKey = el.substring(0, i)
|
|
104
|
+
}
|
|
105
|
+
if (rootKey) {
|
|
106
|
+
if (!scimdata[rootKey]) scimdata[rootKey] = {}
|
|
107
|
+
dot.str(subKey, '', scimdata[rootKey])
|
|
108
|
+
} else {
|
|
109
|
+
dot.str(el, '', scimdata)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
delete scimdata.meta
|
|
114
|
+
} else { // replace any undefined/null with empty string
|
|
115
|
+
if (typeof scimdata[key] === 'object' && scimdata[key] !== null) {
|
|
116
|
+
for (const k in scimdata[key]) {
|
|
117
|
+
if (typeof scimdata[key][k] === 'object' && scimdata[key][k] !== null) {
|
|
118
|
+
for (const _k in scimdata[key][k]) {
|
|
119
|
+
if (scimdata[key][k][_k] === undefined || scimdata[key][k][_k] === null) {
|
|
120
|
+
scimdata[key][k][_k] = ''
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
if (scimdata[key][k] === undefined || scimdata[key][k] === null) {
|
|
125
|
+
scimdata[key][k] = ''
|
|
126
|
+
}
|
|
127
|
+
}
|
|
113
128
|
}
|
|
114
|
-
})
|
|
129
|
+
} else if (scimdata[key] === undefined || scimdata[key] === null) {
|
|
130
|
+
scimdata[key] = ''
|
|
131
|
+
}
|
|
115
132
|
}
|
|
116
|
-
delete scimdata.meta
|
|
117
133
|
}
|
|
134
|
+
|
|
118
135
|
for (const key in newMulti) {
|
|
119
136
|
dot.copy(key, key, newMulti, scimdata)
|
|
120
137
|
}
|
|
138
|
+
|
|
121
139
|
return [scimdata, err]
|
|
122
140
|
}
|
|
123
141
|
|
|
@@ -134,8 +152,8 @@ export function convertedScim(obj: any, multiValueTypes: string[]): any {
|
|
|
134
152
|
* {"name":{"givenName":"Rocky",formatted:""},"emails":{"work":{"value":"user@company.com","type":"work"}}}
|
|
135
153
|
*/
|
|
136
154
|
export function convertedScim20(obj: any, multiValueTypes: string[]): any {
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
if (!obj.Operations || !Array.isArray(obj.Operations)) return {}
|
|
156
|
+
let scimdata: { [key: string]: any } = { meta: { attributes: [] } } // meta is used for deleted attributes
|
|
139
157
|
const o: any = utils.copyObj(obj)
|
|
140
158
|
const arrPrimaryDone: any = []
|
|
141
159
|
const primaryOrgType: any = {}
|
|
@@ -289,10 +307,9 @@ export function convertedScim20(obj: any, multiValueTypes: string[]): any {
|
|
|
289
307
|
})
|
|
290
308
|
} else {
|
|
291
309
|
let value = element.value[key]
|
|
292
|
-
if (element
|
|
293
|
-
if (!scimdata.meta) scimdata.meta = {}
|
|
294
|
-
if (!scimdata.meta.attributes) scimdata.meta.attributes = []
|
|
310
|
+
if (element?.op === 'remove' || value === undefined || value === null) {
|
|
295
311
|
scimdata.meta.attributes.push(key)
|
|
312
|
+
continue
|
|
296
313
|
}
|
|
297
314
|
if (key.startsWith('urn:')) { // can't use dot.str on key having dot e.g. urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department
|
|
298
315
|
const i = key.lastIndexOf(':')
|
|
@@ -301,6 +318,17 @@ export function convertedScim20(obj: any, multiValueTypes: string[]): any {
|
|
|
301
318
|
if (k === 'User' || k === 'Group') rootKey = key
|
|
302
319
|
else rootKey = key.substring(0, i) // urn:ietf:params:scim:schemas:extension:enterprise:2.0:User
|
|
303
320
|
if (k === 'User' || k === 'Group') { // value is object
|
|
321
|
+
for (const _k in value) {
|
|
322
|
+
if (value[_k] === undefined || value[_k] === null) {
|
|
323
|
+
scimdata.meta.attributes.push(`${key}:${_k}`)
|
|
324
|
+
delete value[_k]
|
|
325
|
+
} else if (typeof value[_k] === 'object') { // manager.value
|
|
326
|
+
if (Object.prototype.hasOwnProperty.call(value[_k], 'value') && (value[_k].value === undefined || value[_k].value === null)) {
|
|
327
|
+
scimdata.meta.attributes.push(`${key}:${_k}.value`)
|
|
328
|
+
delete value[_k].value
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
304
332
|
const o: Record<string, any> = {}
|
|
305
333
|
o[rootKey] = value
|
|
306
334
|
scimdata = utils.extendObj(scimdata, o)
|
|
@@ -316,14 +344,10 @@ export function convertedScim20(obj: any, multiValueTypes: string[]): any {
|
|
|
316
344
|
} else {
|
|
317
345
|
if (typeof value === 'object') {
|
|
318
346
|
for (const k in element.value[key]) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (!scimdata.meta.attributes) scimdata.meta.attributes = []
|
|
347
|
+
value = element.value[key][k]
|
|
348
|
+
if ((element.op && element.op === 'remove') || value === null || value === undefined) {
|
|
322
349
|
scimdata.meta.attributes.push(`${key}.${k}`)
|
|
323
|
-
} else {
|
|
324
|
-
value = element.value[key][k]
|
|
325
|
-
dot.str(`${key}.${k}`, value, scimdata)
|
|
326
|
-
}
|
|
350
|
+
} else dot.str(`${key}.${k}`, value, scimdata)
|
|
327
351
|
}
|
|
328
352
|
} else dot.str(key, value, scimdata)
|
|
329
353
|
}
|
package/lib/utils.ts
CHANGED
|
@@ -201,7 +201,8 @@ export const copyObj = (o: any): any => { // deep copy/clone faster than JSON.pa
|
|
|
201
201
|
|
|
202
202
|
const _extendObj = (obj: Record<any, any>, src: Record<any, any>) => {
|
|
203
203
|
Object.keys(src).forEach((key) => {
|
|
204
|
-
if (typeof src[key] === 'object' && src[key]
|
|
204
|
+
if (typeof src[key] === 'object' && src[key] !== null) {
|
|
205
|
+
if (Object.keys(src[key]).length === 0) return
|
|
205
206
|
if (typeof obj[key] === 'undefined') obj[key] = src[key]
|
|
206
207
|
else if (Array.isArray(src[key])) {
|
|
207
208
|
if (!Array.isArray(obj[key])) obj[key] = src[key]
|
package/package.json
CHANGED