scimgateway 4.2.17 → 4.3.0

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.
@@ -1155,27 +1155,27 @@ const ScimGateway = function () {
1155
1155
  }
1156
1156
 
1157
1157
  if (!jsonBody.id) { // retrieve all attributes including id
1158
+ let res
1158
1159
  let obj
1159
1160
  try {
1160
1161
  if (handle.createMethod === 'createUser') {
1161
1162
  if (jsonBody.userName) {
1162
- jsonBody.id = jsonBody.userName
1163
- obj = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'userName', operator: 'eq', value: jsonBody.userName }, [], ctx.ctxCopy)
1163
+ res = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'userName', operator: 'eq', value: jsonBody.userName }, [], ctx.ctxCopy)
1164
1164
  } else if (jsonBody.externalId) {
1165
- jsonBody.id = jsonBody.externalId
1166
- obj = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'externalId', operator: 'eq', value: jsonBody.externalId }, [], ctx.ctxCopy)
1165
+ res = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'externalId', operator: 'eq', value: jsonBody.externalId }, [], ctx.ctxCopy)
1167
1166
  }
1168
1167
  } else if (handle.createMethod === 'createGroup') {
1169
1168
  if (jsonBody.externalId) {
1170
- jsonBody.id = jsonBody.externalId
1171
- obj = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'externalId', operator: 'eq', value: jsonBody.externalId }, [], ctx.ctxCopy)
1169
+ res = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'externalId', operator: 'eq', value: jsonBody.externalId }, [], ctx.ctxCopy)
1172
1170
  } else if (jsonBody.displayName) {
1173
- jsonBody.id = jsonBody.displayName
1174
- obj = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'displayName', operator: 'eq', value: jsonBody.displayName }, [], ctx.ctxCopy)
1171
+ res = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'displayName', operator: 'eq', value: jsonBody.displayName }, [], ctx.ctxCopy)
1175
1172
  }
1176
1173
  }
1177
1174
  } catch (err) { }
1178
- if (obj && obj.id) jsonBody = obj
1175
+ if (res.Resources && Array.isArray(res.Resources) && res.Resources.length === 1) {
1176
+ obj = res.Resources[0]
1177
+ }
1178
+ if (obj && obj.id) jsonBody = obj // id found, using returned object
1179
1179
  }
1180
1180
 
1181
1181
  const location = `${ctx.origin}${ctx.path}/${jsonBody.id}`
@@ -1365,31 +1365,22 @@ const ScimGateway = function () {
1365
1365
  return
1366
1366
  }
1367
1367
 
1368
- const clearedObj = clearObjectValues(currentObj)
1369
- delete clearedObj.active
1370
- delete clearedObj.password
1371
- delete clearedObj.meta
1372
-
1373
- // usePutSoftSync=true prevents removing existing roles (only add roles)
1374
- if (config.scim.usePutSoftSync) {
1375
- if (clearedObj.roles && Array.isArray(clearedObj.roles)) {
1376
- for (let i = 0; i < clearedObj.roles.length; i++) {
1377
- if (clearedObj.roles[i].operation && clearedObj.roles[i].operation === 'delete') {
1378
- clearedObj.roles.splice(i, 1) // delete
1368
+ let clearedObj = {}
1369
+ if (!config.scim.usePutSoftSync) { // usePutSoftSync=true keeps eksisting attributes (and groups) that are not included in PUT jsonBody
1370
+ clearedObj = clearObjectValues(currentObj)
1371
+ delete clearedObj.active
1372
+ delete clearedObj.password
1373
+ delete clearedObj.meta
1374
+ }
1375
+ if (config.scim.usePutGroupMemberOfUser) { // group member of user instead of default user member of group
1376
+ if (clearedObj.groups && Array.isArray(clearedObj.groups)) {
1377
+ for (let i = 0; i < clearedObj.groups.length; i++) {
1378
+ if (clearedObj.groups[i].operation && clearedObj.groups[i].operation === 'delete') {
1379
+ clearedObj.groups.splice(i, 1) // delete
1379
1380
  i -= 1
1380
1381
  }
1381
1382
  }
1382
1383
  }
1383
- if (config.scim.usePutGroupMemberOfUser) { // group member of user instead of default user member of group
1384
- if (clearedObj.groups && Array.isArray(clearedObj.groups)) {
1385
- for (let i = 0; i < clearedObj.groups.length; i++) {
1386
- if (clearedObj.groups[i].operation && clearedObj.groups[i].operation === 'delete') {
1387
- clearedObj.groups.splice(i, 1) // delete
1388
- i -= 1
1389
- }
1390
- }
1391
- }
1392
- }
1393
1384
  }
1394
1385
 
1395
1386
  // merge cleared object with the new
@@ -1479,25 +1470,18 @@ const ScimGateway = function () {
1479
1470
  return await this[handler.groups.modifyMethod](ctx.params.baseEntity, grp, obj, ctx.ctxCopy)
1480
1471
  }
1481
1472
 
1482
- let errRemove
1473
+ let errRemove = []
1483
1474
  if (!config.scim.usePutSoftSync) { // default will remove any existing groups not included, usePutSoftSync=true prevents removing existing groups (only add groups)
1484
- await Promise.all(removeGrps.map((grp) => removeGroups(grp)))
1485
- .then()
1486
- .catch((err) => {
1487
- errRemove = err
1488
- })
1475
+ const res = await Promise.allSettled(removeGrps.map((grp) => removeGroups(grp)))
1476
+ errRemove = res.filter(result => result.status === 'rejected').map(result => result.reason.message)
1489
1477
  }
1490
1478
 
1491
- let errAdd
1492
- await Promise.all(addGrps.map((grp) => addGroups(grp)))
1493
- .then()
1494
- .catch((err) => {
1495
- errAdd = err
1496
- })
1479
+ const res = await Promise.allSettled(addGrps.map((grp) => addGroups(grp)))
1480
+ const errAdd = res.filter(result => result.status === 'rejected').map(result => result.reason.message)
1497
1481
 
1498
1482
  let errMsg = ''
1499
- if (errRemove) errMsg = `removeGroups error: ${errRemove.message}`
1500
- if (errAdd) errMsg += `${errMsg ? ' ' : ''}addGroups error: ${errAdd.message}`
1483
+ if (errRemove.lenght > 0) errMsg = `removeGroups errors: ${errRemove.join(', ')}`
1484
+ if (errAdd.length > 0) errMsg += `${errMsg ? ' ' : ''}addGroups errors: ${errAdd.join(', ')}`
1501
1485
  if (errMsg) throw new Error(errMsg)
1502
1486
  }
1503
1487
  }
@@ -1787,49 +1771,53 @@ const ScimGateway = function () {
1787
1771
 
1788
1772
  logger.info('===================================================================')
1789
1773
 
1790
- if (config.localhostonly === true) {
1791
- logger.info(`${gwName}[${pluginName}] denying other clients than localhost (127.0.0.1)`)
1792
- if (config.certificate && config.certificate.key && config.certificate.cert) {
1774
+ if (!config.port) {
1775
+ logger.info(`${gwName}[${pluginName}] port deactivated, not allowing incoming traffic`)
1776
+ } else {
1777
+ if (config.localhostonly === true) {
1778
+ logger.info(`${gwName}[${pluginName}] denying other clients than localhost (127.0.0.1)`)
1779
+ if (config.certificate && config.certificate.key && config.certificate.cert) {
1793
1780
  // SSL
1794
- server = https.createServer({
1795
- key: fs.readFileSync(configDir + '/certs/' + config.certificate.key),
1796
- cert: fs.readFileSync(configDir + '/certs/' + config.certificate.cert)
1797
- }, app.callback()).listen(config.port, 'localhost')
1798
- logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on TLS port ${config.port}...\n`)
1799
- } else if (config.certificate && config.certificate.pfx && config.certificate.pfx.bundle) {
1781
+ server = https.createServer({
1782
+ key: fs.readFileSync(configDir + '/certs/' + config.certificate.key),
1783
+ cert: fs.readFileSync(configDir + '/certs/' + config.certificate.cert)
1784
+ }, app.callback()).listen(config.port, 'localhost')
1785
+ logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on TLS port ${config.port}...\n`)
1786
+ } else if (config.certificate && config.certificate.pfx && config.certificate.pfx.bundle) {
1800
1787
  // SSL using PFX / PKCS#12
1801
- server = https.createServer({
1802
- pfx: fs.readFileSync(configDir + '/certs/' + config.certificate.pfx.bundle),
1803
- passphrase: pwPfxPassword
1804
- }, app.callback()).listen(config.port, 'localhost')
1805
- logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on TLS port ${config.port}...\n`)
1806
- } else {
1788
+ server = https.createServer({
1789
+ pfx: fs.readFileSync(configDir + '/certs/' + config.certificate.pfx.bundle),
1790
+ passphrase: pwPfxPassword
1791
+ }, app.callback()).listen(config.port, 'localhost')
1792
+ logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on TLS port ${config.port}...\n`)
1793
+ } else {
1807
1794
  // none SSL
1808
- server = http.createServer(app.callback()).listen(config.port, 'localhost')
1809
- logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on port ${config.port}...\n`)
1810
- }
1811
- } else {
1812
- logger.info(`${gwName}[${pluginName}] accepting requests from all clients`)
1813
- if (config.certificate && config.certificate.key && config.certificate.cert) {
1795
+ server = http.createServer(app.callback()).listen(config.port, 'localhost')
1796
+ logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on port ${config.port}...\n`)
1797
+ }
1798
+ } else {
1799
+ logger.info(`${gwName}[${pluginName}] accepting requests from all clients`)
1800
+ if (config.certificate && config.certificate.key && config.certificate.cert) {
1814
1801
  // SSL self signed cert e.g: openssl req -nodes -newkey rsa:2048 -x509 -sha256 -days 3650 -keyout key.pem -out cert.pem -subj "/O=NodeJS/OU=Testing/CN=<FQDN>"
1815
1802
  // Note, self signed certificate (cert.pem) also needs to be imported at the CA Connector Server
1816
- server = https.createServer({
1817
- key: fs.readFileSync(configDir + '/certs/' + config.certificate.key),
1818
- cert: fs.readFileSync(configDir + '/certs/' + config.certificate.cert),
1819
- ca: (config.certificate.ca) ? fs.readFileSync(configDir + '/certs/' + config.certificate.ca) : null
1820
- }, app.callback()).listen(config.port)
1821
- logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on TLS port ${config.port}...\n`)
1822
- } else if (config.certificate && config.certificate.pfx && config.certificate.pfx.bundle) {
1803
+ server = https.createServer({
1804
+ key: fs.readFileSync(configDir + '/certs/' + config.certificate.key),
1805
+ cert: fs.readFileSync(configDir + '/certs/' + config.certificate.cert),
1806
+ ca: (config.certificate.ca) ? fs.readFileSync(configDir + '/certs/' + config.certificate.ca) : null
1807
+ }, app.callback()).listen(config.port)
1808
+ logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on TLS port ${config.port}...\n`)
1809
+ } else if (config.certificate && config.certificate.pfx && config.certificate.pfx.bundle) {
1823
1810
  // SSL using PFX / PKCS#12
1824
- server = https.createServer({
1825
- pfx: fs.readFileSync(configDir + '/certs/' + config.certificate.pfx.bundle),
1826
- passphrase: pwPfxPassword
1827
- }, app.callback()).listen(config.port)
1828
- logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on TLS port ${config.port}...\n`)
1829
- } else {
1811
+ server = https.createServer({
1812
+ pfx: fs.readFileSync(configDir + '/certs/' + config.certificate.pfx.bundle),
1813
+ passphrase: pwPfxPassword
1814
+ }, app.callback()).listen(config.port)
1815
+ logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on TLS port ${config.port}...\n`)
1816
+ } else {
1830
1817
  // none SSL
1831
- server = http.createServer(app.callback()).listen(config.port)
1832
- logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on port ${config.port}...\n`)
1818
+ server = http.createServer(app.callback()).listen(config.port)
1819
+ logger.info(`${gwName}[${pluginName}] now listening SCIM ${config.scim.version} on port ${config.port}...\n`)
1820
+ }
1833
1821
  }
1834
1822
  }
1835
1823
 
@@ -1929,11 +1917,13 @@ const ScimGateway = function () {
1929
1917
  }
1930
1918
  logger.info(`${gwName}[${pluginName}] pheww... ${requestCounter} requests have been processed in the period ${startTime} - ${utils.timestamp()}\n`)
1931
1919
  logger.close()
1932
- server.close(function () {
1933
- setTimeout(function () { // plugins may also use SIGTERM/SIGINT
1934
- process.exit(0)
1935
- }, 0.5 * 1000)
1936
- })
1920
+ if (server) {
1921
+ server.close(function () {
1922
+ setTimeout(function () { // plugins may also use SIGTERM/SIGINT
1923
+ process.exit(0)
1924
+ }, 0.5 * 1000)
1925
+ })
1926
+ }
1937
1927
  setTimeout(function () { // problem closing server connections in time due to keep-alive sessions (active browser connection?), now forcing exit
1938
1928
  process.exit(1)
1939
1929
  }, 2 * 1000)
package/lib/utils.js CHANGED
@@ -390,10 +390,10 @@ module.exports.fsExistsSync = function (f) {
390
390
  }
391
391
 
392
392
  // createRandomPassword creates a random password, syntax:
393
- // utils.createRandomPassword(12) => 12 characters, lower, upper and special
394
- // utils.createRandomPassword(12, utils.createRandomPassword.alphaLower)
393
+ // utils.createRandomPassword(12) => 12 characters: lower, upper, numeric and special
394
+ // utils.createRandomPassword(12, utils.createRandomPassword.alphaLower, utils.createRandomPassword.alphaUpper)
395
395
  // https://gist.github.com/6174/6062387
396
- module.exports.createRandomPassword = (function () {
396
+ module.exports.createRandomPassword = function (len, ...set) {
397
397
  const gen = (min, max) => max++ && [...Array(max - min)].map((s, i) => String.fromCharCode(min + i))
398
398
  const sets = {
399
399
  num: gen(48, 57),
@@ -405,5 +405,19 @@ module.exports.createRandomPassword = (function () {
405
405
  if (set.length < 1) { set = Object.values(sets).flat() }
406
406
  for (let i = 0; i < len; i++) { yield set[Math.random() * set.length | 0] }
407
407
  }
408
- return Object.assign((len, ...set) => [...iter(len, set.flat())].join(''), sets)
409
- }())
408
+ let res
409
+ if (len > 3 && set.length === 0) { // ensure all variants are included: lower, upper, numeric and special
410
+ res = Object.assign((len, set) => [...iter(len, set.flat())].join(''), sets)(len, set)
411
+ let pos = Math.random() * len | 0
412
+ if (pos > len - 4) pos = len - 4
413
+ res = res.split('')
414
+ res.splice(pos, 1, Object.assign((len, set) => [...iter(len, set.flat())].join(''), sets)(1, sets.num))
415
+ res.splice(pos + 1, 1, Object.assign((len, set) => [...iter(len, set.flat())].join(''), sets)(1, sets.alphaUpper))
416
+ res.splice(pos + 2, 1, Object.assign((len, set) => [...iter(len, set.flat())].join(''), sets)(1, sets.special))
417
+ res.splice(pos + 3, 1, Object.assign((len, set) => [...iter(len, set.flat())].join(''), sets)(1, sets.alphaLower))
418
+ res = res.join('')
419
+ } else {
420
+ res = Object.assign((len, set) => [...iter(len, set.flat())].join(''), sets)(len, set)
421
+ }
422
+ return res
423
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scimgateway",
3
- "version": "4.2.17",
3
+ "version": "4.3.0",
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",
@@ -22,11 +22,10 @@
22
22
  "scim",
23
23
  "gateway",
24
24
  "proxy",
25
- "azure",
26
- "identity",
27
- "manager",
25
+ "iga",
28
26
  "provisioning",
29
- "iga"
27
+ "azure",
28
+ "entra"
30
29
  ],
31
30
  "engines": {
32
31
  "node": ">=16.0.0"
@@ -41,15 +40,15 @@
41
40
  "koa": "^2.14.2",
42
41
  "koa-bodyparser": "^4.4.1",
43
42
  "koa-router": "^12.0.1",
44
- "ldapjs": "^3.0.5",
43
+ "ldapjs": "^3.0.7",
45
44
  "lokijs": "^1.5.12",
46
- "mongodb": "^6.1.0",
45
+ "mongodb": "^6.3.0",
47
46
  "node-machine-id": "1.1.9",
48
- "nodemailer": "^6.9.6",
49
- "passport": "^0.6.0",
47
+ "nodemailer": "^6.9.7",
48
+ "passport": "^0.7.0",
50
49
  "passport-azure-ad": "^4.3.5",
51
50
  "soap": "^1.0.0",
52
- "tedious": "^16.5.0",
51
+ "tedious": "^16.6.1",
53
52
  "winston": "^3.11.0"
54
53
  },
55
54
  "devDependencies": {