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.
- package/README.md +66 -50
- package/config/{plugin-azure-ad.json → plugin-entra-id.json} +10 -7
- package/config/plugin-loki.json +7 -3
- package/config/plugin-scim.json +9 -5
- package/index.js +1 -1
- package/lib/{plugin-azure-ad.js → plugin-entra-id.js} +243 -171
- package/lib/plugin-ldap.js +16 -7
- package/lib/plugin-loki.js +77 -38
- package/lib/plugin-scim.js +166 -12
- package/lib/postinstall.js +2 -2
- package/lib/scimgateway.js +75 -85
- package/lib/utils.js +19 -5
- package/package.json +9 -10
package/lib/scimgateway.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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.
|
|
1485
|
-
|
|
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
|
-
|
|
1492
|
-
|
|
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
|
|
1500
|
-
if (errAdd) errMsg += `${errMsg ? ' ' : ''}addGroups
|
|
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.
|
|
1791
|
-
logger.info(`${gwName}[${pluginName}]
|
|
1792
|
-
|
|
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
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
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
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
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
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
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
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
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
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
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
|
-
|
|
1832
|
-
|
|
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
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
26
|
-
"identity",
|
|
27
|
-
"manager",
|
|
25
|
+
"iga",
|
|
28
26
|
"provisioning",
|
|
29
|
-
"
|
|
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.
|
|
43
|
+
"ldapjs": "^3.0.7",
|
|
45
44
|
"lokijs": "^1.5.12",
|
|
46
|
-
"mongodb": "^6.
|
|
45
|
+
"mongodb": "^6.3.0",
|
|
47
46
|
"node-machine-id": "1.1.9",
|
|
48
|
-
"nodemailer": "^6.9.
|
|
49
|
-
"passport": "^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.
|
|
51
|
+
"tedious": "^16.6.1",
|
|
53
52
|
"winston": "^3.11.0"
|
|
54
53
|
},
|
|
55
54
|
"devDependencies": {
|