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 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
 
@@ -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
  })
@@ -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
  })
@@ -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]) 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`)
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]) 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`)
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
- const convertedScim20 = (obj) => {
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 only used for internal recursive logic
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) // e.g. HttpsProxyAgent {}
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) // e.g. { HttpsProxyAgent {...} }
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) && key === 'value') { // array
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",
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",