scimgateway 4.1.11 → 4.1.13
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 +23 -8
- package/lib/logger.js +41 -31
- package/lib/scimgateway.js +14 -12
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -17,12 +17,12 @@ Validated through IdP's:
|
|
|
17
17
|
Latest news:
|
|
18
18
|
|
|
19
19
|
- **BREAKING**: [SCIM Stream](https://elshaug.xyz/docs/scim-stream) is the modern way of user provisioning letting clients subscribe to messages instead of traditional IGA top-down provisioning. SCIM Stream includes **SCIM Stream Gateway**, the next generation SCIM Gateway that supports message subscription and automated provisioning
|
|
20
|
-
-
|
|
20
|
+
- Supports OAuth Client Credentials authentication
|
|
21
21
|
- Major version v4.0.0. getUsers() and getGroups() replacing some deprecated methods. No limitations on filtering/sorting. Admin user access can be linked to specific baseEntities. New MongoDB plugin
|
|
22
22
|
- ipAllowList for restricting access to allowlisted IP addresses or subnets e.g. Azure AD IP-range
|
|
23
23
|
- General LDAP plugin configured for Active Directory
|
|
24
24
|
- [PlugSSO](https://elshaug.xyz/docs/plugsso) using SCIM Gateway
|
|
25
|
-
-
|
|
25
|
+
- Each authentication configuration allowing more than one admin user including option for readOnly
|
|
26
26
|
- Codebase moved from callback of h... to the the promise(d) land of async/await
|
|
27
27
|
- Supports configuration by environments and external files
|
|
28
28
|
- Health monitoring through "/ping" URL, and option for error notifications by email
|
|
@@ -49,7 +49,7 @@ SCIM Gateway becomes a standalone SCIM endpoint
|
|
|
49
49
|
Demonstrates user provisioning towards document-oriented database
|
|
50
50
|
Using [LokiJS](https://github.com/techfort/LokiJS) for a fast, in-memory document-oriented database (much like MongoDB/PouchDB)
|
|
51
51
|
Default gives two predefined test users loaded using in-memory only (no persistence)
|
|
52
|
-
|
|
52
|
+
Configuration `{"persistence": true}` gives persistence file store (no test users)
|
|
53
53
|
Example of a fully functional SCIM Gateway plugin
|
|
54
54
|
|
|
55
55
|
* **MongoDB** (NoSQL Document-Oriented Database)
|
|
@@ -379,11 +379,11 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
379
379
|
|
|
380
380
|
Note, we should normally use certificate (https) for communicating with SCIM Gateway unless we install ScimGatway locally on the manager (e.g. on the CA Connector Server). When installed on the manager, we could use `http://localhost:port` or `http://127.0.0.1:port` which will not be passed down to the data link layer for transmission. We could then also set {"localhostonly": true}
|
|
381
381
|
|
|
382
|
-
- **ipAllowList** - Array of one or more IPv4/IPv6 subnets (CIDR) allowed for incoming traffic. E.g. using Azure AD as IdP, we would like to restrict access to IP addresses used by Azure AD. Azure IP-range can be downloaded from: [https://azureipranges.azurewebsites.net](https://azureipranges.azurewebsites.net), enter **AzureActiveDirectory** in the search list and select JSON download. Copy the "addressPrefixes" array content and paste into ipAllowList array. CIDR single IP-host syntax is a.b.c.d/32. Note, front-end HTTP proxy or a load balancer must include **X-Forwarded-For** header. Configuration example:
|
|
382
|
+
- **ipAllowList** - Array of one or more IPv4/IPv6 subnets (CIDR) allowed for incoming traffic. E.g. using Azure AD as IdP, we would like to restrict access to IP addresses used by Azure AD. Azure IP-range can be downloaded from: [https://azureipranges.azurewebsites.net](https://azureipranges.azurewebsites.net), enter **AzureActiveDirectory** in the search list and select JSON download. Copy the "addressPrefixes" array content and paste into ipAllowList array. CIDR single IP-host syntax is a.b.c.d/32. Note, front-end HTTP proxy or a load balancer must include client IP-address in the **X-Forwarded-For** header. Configuration example:
|
|
383
383
|
|
|
384
384
|
"ipAllowList": [
|
|
385
|
-
"13.
|
|
386
|
-
"13.66.
|
|
385
|
+
"13.64.151.161/32",
|
|
386
|
+
"13.66.141.64/27",
|
|
387
387
|
...
|
|
388
388
|
"2603:1056:2000::/48",
|
|
389
389
|
"2603:1057:2::/48"
|
|
@@ -1146,6 +1146,18 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1146
1146
|
|
|
1147
1147
|
## Change log
|
|
1148
1148
|
|
|
1149
|
+
### v4.1.13
|
|
1150
|
+
|
|
1151
|
+
[Fixed]
|
|
1152
|
+
|
|
1153
|
+
- Do not create logs directory or log-file when configuration `log.loglevel.file` not defined or set to `"off"`. This fix will allow SCIM Gateway to run on systems having read-only disk like Google Cloud App Engine Standard
|
|
1154
|
+
|
|
1155
|
+
### v4.1.12
|
|
1156
|
+
|
|
1157
|
+
[Added]
|
|
1158
|
+
|
|
1159
|
+
- Dependencies bump
|
|
1160
|
+
|
|
1149
1161
|
### v4.1.11
|
|
1150
1162
|
|
|
1151
1163
|
[Fixed]
|
|
@@ -1187,13 +1199,16 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1187
1199
|
- Symantec/Broadcom/CA ConnectorXpress configuration file `config\resources\Azure - ScimGateway.xml` for defining the Azure endpoint, have been updated with some new attributes according to plugin-azure-ad.json attribute mappings
|
|
1188
1200
|
|
|
1189
1201
|
### v4.1.6
|
|
1190
|
-
|
|
1202
|
+
|
|
1203
|
+
[Added]
|
|
1204
|
+
|
|
1191
1205
|
- Dependencies bump
|
|
1192
1206
|
|
|
1193
1207
|
### v4.1.5
|
|
1208
|
+
|
|
1194
1209
|
[Added]
|
|
1195
1210
|
|
|
1196
|
-
|
|
1211
|
+
SCIM Gateway related news:
|
|
1197
1212
|
|
|
1198
1213
|
- [SCIM Stream](https://elshaug.xyz/docs/scim-stream) is the modern way of user provisioning letting clients subscribe to messages instead of traditional IGA top-down provisioning. SCIM Stream includes **SCIM Stream Gateway**, the next generation SCIM Gateway that supports message subscription and automated provisioning
|
|
1199
1214
|
|
package/lib/logger.js
CHANGED
|
@@ -44,12 +44,12 @@ const Log = function (config, logfile, _this) { // { loglevel: { file: "debug",
|
|
|
44
44
|
|
|
45
45
|
const maskSecret = winston.format((info, opts) => {
|
|
46
46
|
// mask json secrets
|
|
47
|
-
|
|
47
|
+
let rePattern = new RegExp(reJson, 'i')
|
|
48
48
|
let msg = info.message
|
|
49
49
|
let endPos = msg.length - 1
|
|
50
50
|
let found = false
|
|
51
51
|
do {
|
|
52
|
-
|
|
52
|
+
const arrMatches = msg.substring(0, endPos).match(rePattern)
|
|
53
53
|
if (Array.isArray(arrMatches) && arrMatches.length === 3) {
|
|
54
54
|
arrMatches[2] = arrMatches[2].replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') // escaping special regexp characters
|
|
55
55
|
msg = msg.replace(new RegExp(arrMatches[2], 'g'), '********')
|
|
@@ -62,7 +62,7 @@ const Log = function (config, logfile, _this) { // { loglevel: { file: "debug",
|
|
|
62
62
|
rePattern = new RegExp(reXml, 'i')
|
|
63
63
|
endPos = msg.length - 1
|
|
64
64
|
do {
|
|
65
|
-
arrMatches = msg.substring(0, endPos).match(rePattern)
|
|
65
|
+
const arrMatches = msg.substring(0, endPos).match(rePattern)
|
|
66
66
|
if (Array.isArray(arrMatches) && arrMatches.length === 3) {
|
|
67
67
|
arrMatches[2] = arrMatches[2].replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
|
|
68
68
|
msg = msg.replace(new RegExp('>' + arrMatches[2] + '<', 'g'), '>********<')
|
|
@@ -98,38 +98,48 @@ const Log = function (config, logfile, _this) { // { loglevel: { file: "debug",
|
|
|
98
98
|
)
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
Log.prototype.logger =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
101
|
+
Log.prototype.logger = () => {
|
|
102
|
+
if (config.loglevel.file) {
|
|
103
|
+
return winston.createLogger({
|
|
104
|
+
format: winston.format.combine(
|
|
105
|
+
maskSecret()
|
|
106
|
+
),
|
|
107
|
+
transports: [
|
|
108
|
+
new winston.transports.Console({ // note, console logging is synchronous e.g. node.js halts when console window is scrolled
|
|
109
|
+
level: (config.loglevel.console && arrValidLevel.includes(config.loglevel.console.toLowerCase())) ? config.loglevel.console : 'debug',
|
|
110
|
+
handleExceptions: true,
|
|
111
|
+
stderrLevels: ['error'],
|
|
112
|
+
format: (config.colorize) ? consoleFormat : Log.prototype.unColorize()
|
|
113
|
+
}),
|
|
114
|
+
new winston.transports.File({
|
|
115
|
+
level: (config.loglevel.file && arrValidLevel.includes(config.loglevel.file.toLowerCase())) ? config.loglevel.file : 'debug',
|
|
116
|
+
filename: logfile,
|
|
117
|
+
handleExceptions: true,
|
|
118
|
+
format: fileFormat,
|
|
119
|
+
maxsize: 1024 * 1024 * 20, // 20 MB
|
|
120
|
+
maxFiles: 5
|
|
121
|
+
})
|
|
122
|
+
],
|
|
123
|
+
exitOnError: false
|
|
119
124
|
})
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
}
|
|
126
|
+
return winston.createLogger({
|
|
127
|
+
format: winston.format.combine(
|
|
128
|
+
maskSecret()
|
|
129
|
+
),
|
|
130
|
+
transports: [
|
|
131
|
+
new winston.transports.Console({
|
|
132
|
+
level: (config.loglevel.console && arrValidLevel.includes(config.loglevel.console.toLowerCase())) ? config.loglevel.console : 'debug',
|
|
133
|
+
handleExceptions: true,
|
|
134
|
+
stderrLevels: ['error'],
|
|
135
|
+
format: (config.colorize) ? consoleFormat : Log.prototype.unColorize()
|
|
136
|
+
})
|
|
137
|
+
],
|
|
138
|
+
exitOnError: false
|
|
129
139
|
})
|
|
130
140
|
}
|
|
131
141
|
|
|
132
|
-
const logger = Log.prototype.logger // fix multiple loggers using stream
|
|
142
|
+
const logger = Log.prototype.logger() // fix multiple loggers using stream
|
|
133
143
|
Log.prototype.stream = {
|
|
134
144
|
write: function (message, encoding) {
|
|
135
145
|
logger.info(message)
|
package/lib/scimgateway.js
CHANGED
|
@@ -46,8 +46,8 @@ const ScimGateway = function () {
|
|
|
46
46
|
const gwName = path.basename(__filename, '.js') // prefix of current file
|
|
47
47
|
const logDir = path.join(path.dirname(requester), '..', 'logs')
|
|
48
48
|
const Log = require('../lib/logger').Log
|
|
49
|
-
const log = new Log(utils.extendObj(utils.copyObj(config.log), { category: pluginName, colorize: process.stdout.isTTY || false, loglevel: { file: 'debug', console: 'debug' } }), path.join(`${logDir}`, `${pluginName}.log`))
|
|
50
|
-
const logger = log.logger
|
|
49
|
+
const log = new Log(utils.extendObj(utils.copyObj(config.log), { category: pluginName, colorize: process.stdout.isTTY || false, loglevel: { file: (!config.log.loglevel.file || config.log.loglevel.file === 'off') ? null : 'debug', console: 'debug' } }), path.join(`${logDir}`, `${pluginName}.log`))
|
|
50
|
+
const logger = log.logger()
|
|
51
51
|
this.logger = logger // exposed to plugin-code
|
|
52
52
|
this.notValidAttributes = notValidAttributes // exposed to plugin-code
|
|
53
53
|
let pwErrCount = 0
|
|
@@ -228,10 +228,12 @@ const ScimGateway = function () {
|
|
|
228
228
|
logger.error(`${gwName}[${pluginName}] stopping...\n`)
|
|
229
229
|
throw (new Error('Using exception to stop further asynchronous code execution (ensure synchronous logger flush to logfile and exit program), please ignore this one...'))
|
|
230
230
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
if (!fs.existsSync(configDir + '/wsdls')) fs.mkdirSync(configDir + '/wsdls')
|
|
234
|
+
if (!fs.existsSync(configDir + '/certs')) fs.mkdirSync(configDir + '/certs')
|
|
235
|
+
if (!fs.existsSync(configDir + '/schemas')) fs.mkdirSync(configDir + '/schemas')
|
|
236
|
+
} catch (err) {}
|
|
235
237
|
|
|
236
238
|
let isScimv2 = false
|
|
237
239
|
if (config.scim.version === '2.0' || config.scim.version === 2) {
|
|
@@ -450,9 +452,9 @@ const ScimGateway = function () {
|
|
|
450
452
|
if (tokenObj.readOnly === true && method !== 'GET') return reject(new Error('only allowing readOnly for this bearerOAuth according to bearerOAuth configuration readOnly=true'))
|
|
451
453
|
return resolve(true)
|
|
452
454
|
} else {
|
|
453
|
-
for (let i = 0; i < arr.length; i++) { // resolve if token memory store have been cleared
|
|
454
|
-
if (utils.getEncrypted(authToken, arr[i].client_secret) ===
|
|
455
|
-
arr[i].isTokenRequested = true // flagged as true to not allow repeated resolvements
|
|
455
|
+
for (let i = 0; i < arr.length; i++) { // resolve if token memory store have been cleared because of a gateway restart
|
|
456
|
+
if (utils.getEncrypted(authToken, arr[i].client_secret) === arr[i].client_secret && !arr[i].isTokenRequested) {
|
|
457
|
+
arr[i].isTokenRequested = true // flagged as true to not allow repeated resolvements because token will also be cleared when expired
|
|
456
458
|
const baseEntities = utils.copyObj(arr[i].baseEntities)
|
|
457
459
|
let expires
|
|
458
460
|
let readOnly = false
|
|
@@ -658,7 +660,7 @@ const ScimGateway = function () {
|
|
|
658
660
|
for (let i = 0; i < arr.length; i++) {
|
|
659
661
|
if (!arr[i].client_id || !arr[i].client_secret) continue
|
|
660
662
|
if (arr[i].client_id === jsonBody.client_id && arr[i].client_secret === jsonBody.client_secret) { // authentication OK
|
|
661
|
-
token = utils.getEncrypted(
|
|
663
|
+
token = utils.getEncrypted(jsonBody.client_secret, jsonBody.client_secret)
|
|
662
664
|
baseEntities = utils.copyObj(arr[i].baseEntities)
|
|
663
665
|
if (arr[i].readOnly && arr[i].readOnly === true) readOnly = true
|
|
664
666
|
if (arr[i].expires_in && !isNaN(arr[i].expires_in)) expires = arr[i].expires_in
|
|
@@ -1327,7 +1329,7 @@ const ScimGateway = function () {
|
|
|
1327
1329
|
else { // try to get current groups the standard way
|
|
1328
1330
|
let res
|
|
1329
1331
|
try {
|
|
1330
|
-
res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }, ['
|
|
1332
|
+
res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }, ['id', 'displayName'])
|
|
1331
1333
|
} catch (err) {} // method may be implemented but throwing error like groups not supported/implemented
|
|
1332
1334
|
currentGroups = []
|
|
1333
1335
|
if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length > 0) {
|
|
@@ -1421,7 +1423,7 @@ const ScimGateway = function () {
|
|
|
1421
1423
|
// include groups
|
|
1422
1424
|
if (handle.getMethod === handler.users.getMethod && typeof this[handler.groups.getMethod] === 'function') {
|
|
1423
1425
|
logger.debug(`${gwName}[${pluginName}] calling "${handler.groups.getMethod}" and awaiting result`)
|
|
1424
|
-
const res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: id }, ['
|
|
1426
|
+
const res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: id }, ['id', 'displayName'])
|
|
1425
1427
|
let grps = []
|
|
1426
1428
|
if (res && res.Resources && Array.isArray(res.Resources)) grps = res.Resources
|
|
1427
1429
|
else if (Array.isArray(res)) grps = res
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.13",
|
|
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",
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
"https-proxy-agent": "^5.0.1",
|
|
38
38
|
"is-in-subnet": "^4.0.1",
|
|
39
39
|
"jsonwebtoken": "^8.5.1",
|
|
40
|
-
"koa": "^2.
|
|
40
|
+
"koa": "^2.14.0",
|
|
41
41
|
"koa-bodyparser": "^4.3.0",
|
|
42
42
|
"koa-router": "^12.0.0",
|
|
43
43
|
"ldapjs": "^2.3.3",
|
|
44
44
|
"lokijs": "^1.5.12",
|
|
45
|
-
"mongodb": "^4.
|
|
45
|
+
"mongodb": "^4.12.1",
|
|
46
46
|
"node-machine-id": "1.1.9",
|
|
47
|
-
"nodemailer": "^6.
|
|
47
|
+
"nodemailer": "^6.8.0",
|
|
48
48
|
"passport": "^0.6.0",
|
|
49
49
|
"passport-azure-ad": "^4.3.4",
|
|
50
50
|
"soap": "^0.45.0",
|
|
51
|
-
"tedious": "^15.1.
|
|
51
|
+
"tedious": "^15.1.2",
|
|
52
52
|
"winston": "^3.8.2"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|