scimgateway 4.1.3 → 4.1.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 +6 -0
- package/lib/plugin-loki.js +1 -1
- package/lib/plugin-mongodb.js +1 -1
- package/lib/scimgateway.js +49 -20
- package/lib/utils.js +0 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1142,6 +1142,12 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1142
1142
|
|
|
1143
1143
|
## Change log
|
|
1144
1144
|
|
|
1145
|
+
### v4.1.4
|
|
1146
|
+
[Fixed]
|
|
1147
|
+
|
|
1148
|
+
- TypeConvert logic for multivalue attribute `addresses` did not correctly catch duplicate entries
|
|
1149
|
+
- PUT (Replace User) configuration `scim.usePutSoftsync=true` will also prevent removing any existing roles that are not included in body.roles ref. v4.1.3
|
|
1150
|
+
|
|
1145
1151
|
### v4.1.3
|
|
1146
1152
|
[Fixed]
|
|
1147
1153
|
|
package/lib/plugin-loki.js
CHANGED
|
@@ -272,7 +272,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj) => {
|
|
|
272
272
|
} else { // add
|
|
273
273
|
if (!userObj[key]) userObj[key] = []
|
|
274
274
|
let exists
|
|
275
|
-
if (el.value) exists = userObj[key].find(e => e.value && e.value === el.value)
|
|
275
|
+
if (el.value) exists = userObj[key].find(e => e.value && e.value === el.value && e.type === el.type) // allowing same value on different type (type not mandatory)
|
|
276
276
|
if (!exists) userObj[key].push(el)
|
|
277
277
|
}
|
|
278
278
|
})
|
package/lib/plugin-mongodb.js
CHANGED
|
@@ -355,7 +355,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj) => {
|
|
|
355
355
|
} else { // add
|
|
356
356
|
if (!userObj[key]) userObj[key] = []
|
|
357
357
|
let exists
|
|
358
|
-
if (el.value) exists = userObj[key].find(e => e.value && e.value === el.value)
|
|
358
|
+
if (el.value) exists = userObj[key].find(e => e.value && e.value === el.value && e.type === el.type) // allowing same value on different type (type not mandatory)
|
|
359
359
|
if (!exists) userObj[key].push(el)
|
|
360
360
|
}
|
|
361
361
|
})
|
package/lib/scimgateway.js
CHANGED
|
@@ -1209,7 +1209,7 @@ const ScimGateway = function () {
|
|
|
1209
1209
|
} else {
|
|
1210
1210
|
logger.debug(`${gwName}[${pluginName}] [Modify ${handle.description}] id=${id}`)
|
|
1211
1211
|
let scimdata, err
|
|
1212
|
-
if (jsonBody.Operations) [scimdata, err] = convertedScim20(jsonBody) // v2.0
|
|
1212
|
+
if (jsonBody.Operations) [scimdata, err] = ScimGateway.prototype.convertedScim20(jsonBody) // v2.0
|
|
1213
1213
|
else [scimdata, err] = ScimGateway.prototype.convertedScim(jsonBody) // v1.1
|
|
1214
1214
|
logger.debug(`${gwName}[${pluginName}] convertedBody=${JSON.stringify(scimdata)}`)
|
|
1215
1215
|
if (err) {
|
|
@@ -1259,10 +1259,6 @@ const ScimGateway = function () {
|
|
|
1259
1259
|
// ==========================================
|
|
1260
1260
|
router.put([`/(|scim/)(!${undefined}|Users|Groups|servicePlans)/:id`,
|
|
1261
1261
|
`/:baseEntity/(|scim/)(!${undefined}|Users|Groups|servicePlans)/:id`], async (ctx) => {
|
|
1262
|
-
await replaceUsrGrp(ctx)
|
|
1263
|
-
})
|
|
1264
|
-
|
|
1265
|
-
const replaceUsrGrp = async (ctx, isExtCaller) => {
|
|
1266
1262
|
let u = ctx.originalUrl.substr(0, ctx.originalUrl.lastIndexOf('/'))
|
|
1267
1263
|
u = u.substr(u.lastIndexOf('/') + 1) // u = Users, Groups
|
|
1268
1264
|
const handle = handler[u]
|
|
@@ -1275,7 +1271,6 @@ const ScimGateway = function () {
|
|
|
1275
1271
|
err = jsonErr(config.scim.version, pluginName, ctx.status, err)
|
|
1276
1272
|
ctx.body = err
|
|
1277
1273
|
} else {
|
|
1278
|
-
if (!isExtCaller) logger.debug(`${gwName}[${pluginName}] [Replace ${handle.description}] id=${id}`)
|
|
1279
1274
|
logger.debug(`${gwName}[${pluginName}] PUT ${ctx.originalUrl} body=${strBody}`)
|
|
1280
1275
|
try {
|
|
1281
1276
|
// get current object
|
|
@@ -1293,6 +1288,18 @@ const ScimGateway = function () {
|
|
|
1293
1288
|
delete clearedObj.password
|
|
1294
1289
|
delete clearedObj.meta
|
|
1295
1290
|
|
|
1291
|
+
// usePutSoftSync=true prevents removing existing roles (only add roles)
|
|
1292
|
+
if (config.scim.usePutSoftSync) {
|
|
1293
|
+
if (clearedObj.roles && Array.isArray(clearedObj.roles)) {
|
|
1294
|
+
for (let i = 0; i < clearedObj.roles.length; i++) {
|
|
1295
|
+
if (clearedObj.roles[i].operation && clearedObj.roles[i].operation === 'delete') {
|
|
1296
|
+
clearedObj.roles.splice(i, 1) // delete
|
|
1297
|
+
i -= 1
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1296
1303
|
// merge cleared object with the new
|
|
1297
1304
|
const newObj = utils.extendObj(clearedObj, jsonBody)
|
|
1298
1305
|
delete newObj.id
|
|
@@ -1368,7 +1375,7 @@ const ScimGateway = function () {
|
|
|
1368
1375
|
}
|
|
1369
1376
|
|
|
1370
1377
|
let errRemove
|
|
1371
|
-
if (!config.scim.usePutSoftSync) { // default will remove any existing groups not included, usePutSoftSync=true prevents removing groups (only add groups)
|
|
1378
|
+
if (!config.scim.usePutSoftSync) { // default will remove any existing groups not included, usePutSoftSync=true prevents removing existing groups (only add groups)
|
|
1372
1379
|
await Promise.all(removeGrps.map((grp) => removeGroups(grp)))
|
|
1373
1380
|
.then()
|
|
1374
1381
|
.catch((err) => {
|
|
@@ -1433,8 +1440,7 @@ const ScimGateway = function () {
|
|
|
1433
1440
|
ctx.body = e
|
|
1434
1441
|
}
|
|
1435
1442
|
}
|
|
1436
|
-
}
|
|
1437
|
-
this.replaceUsrGrp = replaceUsrGrp // exposed
|
|
1443
|
+
})
|
|
1438
1444
|
|
|
1439
1445
|
// ==========================================
|
|
1440
1446
|
// API POST (no SCIM)
|
|
@@ -1807,13 +1813,27 @@ const ScimGateway = function () {
|
|
|
1807
1813
|
}
|
|
1808
1814
|
})
|
|
1809
1815
|
} else if (multiValueTypes.includes(key)) { // "type converted object" // groups, roles, member and scim.excludeTypeConvert are not included
|
|
1816
|
+
const tmpAddr = []
|
|
1810
1817
|
scimdata[key].forEach(function (element, index) {
|
|
1811
1818
|
if (!element.type) element.type = 'undefined' // "none-type"
|
|
1812
1819
|
if (element.operation && element.operation === 'delete') { // add as delete if same type not included as none delete
|
|
1813
1820
|
const arr = scimdata[key].filter(obj => obj.type && obj.type === element.type && !obj.operation)
|
|
1814
1821
|
if (arr.length < 1) {
|
|
1815
1822
|
if (!newMulti[key]) newMulti[key] = {}
|
|
1816
|
-
if (newMulti[key][element.type])
|
|
1823
|
+
if (newMulti[key][element.type]) {
|
|
1824
|
+
if (['addresses'].includes(key)) { // not checking type, but the others have to be unique
|
|
1825
|
+
for (const i in element) {
|
|
1826
|
+
if (i !== 'type') {
|
|
1827
|
+
if (tmpAddr.includes(i)) {
|
|
1828
|
+
err = new Error(`'type converted object' ${key} - includes more than one element having same ${i}, or ${i} is blank on more than one element - note, setting configuration scim.skipTypeConvert=true will disable this logic/check`)
|
|
1829
|
+
}
|
|
1830
|
+
tmpAddr.push(i)
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
} else {
|
|
1834
|
+
err = new Error(`'type converted object' ${key} - includes more than one element having same type, or type is blank on more than one element - note, setting configuration scim.skipTypeConvert=true will disable this logic/check`)
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1817
1837
|
newMulti[key][element.type] = {}
|
|
1818
1838
|
for (const i in element) {
|
|
1819
1839
|
newMulti[key][element.type][i] = element[i]
|
|
@@ -1822,7 +1842,20 @@ const ScimGateway = function () {
|
|
|
1822
1842
|
}
|
|
1823
1843
|
} else {
|
|
1824
1844
|
if (!newMulti[key]) newMulti[key] = {}
|
|
1825
|
-
if (newMulti[key][element.type])
|
|
1845
|
+
if (newMulti[key][element.type]) {
|
|
1846
|
+
if (['addresses'].includes(key)) { // not checking type, but the others have to be unique
|
|
1847
|
+
for (const i in element) {
|
|
1848
|
+
if (i !== 'type') {
|
|
1849
|
+
if (tmpAddr.includes(i)) {
|
|
1850
|
+
err = new Error(`'type converted object' ${key} - includes more than one element having same ${i}, or ${i} is blank on more than one element - note, setting configuration scim.skipTypeConvert=true will disable this logic/check`)
|
|
1851
|
+
}
|
|
1852
|
+
tmpAddr.push(i)
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
} else {
|
|
1856
|
+
err = new Error(`'type converted object' ${key} - includes more than one element having same type, or type is blank on more than one element - note, setting configuration scim.skipTypeConvert=true will disable this logic/check`)
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1826
1859
|
newMulti[key][element.type] = {}
|
|
1827
1860
|
for (const i in element) {
|
|
1828
1861
|
newMulti[key][element.type][i] = element[i]
|
|
@@ -2494,7 +2527,7 @@ const notValidAttributes = (obj, validScimAttr) => {
|
|
|
2494
2527
|
// "type converted object" and blank deleted values
|
|
2495
2528
|
// {"name":{"givenName":"Rocky",formatted:""},"emails":{"work":{"value":"user@company.com","type":"work"}}}
|
|
2496
2529
|
//
|
|
2497
|
-
|
|
2530
|
+
ScimGateway.prototype.convertedScim20 = function convertedScim20 (obj) {
|
|
2498
2531
|
let scimdata = {}
|
|
2499
2532
|
if (!obj.Operations || !Array.isArray(obj.Operations)) return scimdata
|
|
2500
2533
|
const o = utils.copyObj(obj)
|
|
@@ -2681,7 +2714,7 @@ const convertedScim20 = (obj) => {
|
|
|
2681
2714
|
// clearObjectValues returns a new object having values set to blank
|
|
2682
2715
|
// array values are kept, but includes {"operation" : "delete"} - scim 1.1 formatted
|
|
2683
2716
|
// boolean values e.g. {"active" : true} are kept "as is"
|
|
2684
|
-
// parent
|
|
2717
|
+
// parent used for internal recursive logic
|
|
2685
2718
|
const clearObjectValues = (o, parent) => {
|
|
2686
2719
|
if (!o) return {}
|
|
2687
2720
|
let v, key
|
|
@@ -2689,18 +2722,14 @@ const clearObjectValues = (o, parent) => {
|
|
|
2689
2722
|
for (key in o) {
|
|
2690
2723
|
v = o[key]
|
|
2691
2724
|
if (typeof v === 'object' && v !== null) {
|
|
2692
|
-
const objProp = Object.getPrototypeOf(v)
|
|
2725
|
+
const objProp = Object.getPrototypeOf(v)
|
|
2693
2726
|
if (objProp !== null && objProp !== Object.getPrototypeOf({}) && objProp !== Object.getPrototypeOf([])) {
|
|
2694
|
-
output[key] = Object.assign(Object.create(v), v)
|
|
2727
|
+
output[key] = Object.assign(Object.create(v), v)
|
|
2695
2728
|
} else {
|
|
2696
2729
|
output[key] = clearObjectValues(v, key)
|
|
2697
2730
|
}
|
|
2698
|
-
} else if (key === 'type') {
|
|
2699
|
-
output[key] = v
|
|
2700
|
-
output.operation = 'delete'
|
|
2701
|
-
if (output.value) output.value = ''
|
|
2702
2731
|
} else {
|
|
2703
|
-
if (parent && !isNaN(parent)
|
|
2732
|
+
if (parent && !isNaN(parent)) { // array
|
|
2704
2733
|
output.operation = 'delete'
|
|
2705
2734
|
output[key] = v
|
|
2706
2735
|
} else {
|
package/lib/utils.js
CHANGED
|
@@ -373,20 +373,3 @@ module.exports.getEncrypted = function (pw, seed) {
|
|
|
373
373
|
}
|
|
374
374
|
return undefined
|
|
375
375
|
}
|
|
376
|
-
|
|
377
|
-
// aadUnExtUpn convert Azure AD guest UPN to origin target UPN
|
|
378
|
-
// john.doe_company.com#EXT#@company.onmicrosoft.com => john.doe@company.com
|
|
379
|
-
module.exports.aadUnExtUpn = function (upn) {
|
|
380
|
-
const arr = upn.split('#EXT#')
|
|
381
|
-
if (arr.length === 1) return arr[0]
|
|
382
|
-
const extArr = arr[0].split('_')
|
|
383
|
-
if (extArr.length === 1) return extArr[0]
|
|
384
|
-
else {
|
|
385
|
-
upn = extArr[0]
|
|
386
|
-
for (let i = 1; i < extArr.length - 1; i++) {
|
|
387
|
-
upn += `_${extArr[i]}`
|
|
388
|
-
}
|
|
389
|
-
upn += `@${extArr[extArr.length - 1]}`
|
|
390
|
-
}
|
|
391
|
-
return upn
|
|
392
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.4",
|
|
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",
|