scimgateway 5.3.0 → 5.3.2
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 -9
- package/lib/helper-rest.ts +1 -1
- package/lib/logger.ts +34 -15
- package/lib/plugin-ldap.ts +4 -0
- package/lib/scimgateway.ts +11 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Validated through IdP's:
|
|
|
16
16
|
|
|
17
17
|
Latest news:
|
|
18
18
|
|
|
19
|
-
- [
|
|
19
|
+
- [Bulk Operations](https://datatracker.ietf.org/doc/html/rfc7644#section-3.7) now supported
|
|
20
20
|
- Remote real-time log subscription for monitoring and centralized logging
|
|
21
21
|
using browser and url: `https://<host>/logger`
|
|
22
22
|
`curl -N https://<host>/logger -u user:password`
|
|
@@ -159,7 +159,7 @@ If internet connection is blocked, we could install on another machine and copy
|
|
|
159
159
|
>Tip, take a look at bun test scripts located in `node_modules\scimgateway\test\lib`
|
|
160
160
|
|
|
161
161
|
> If using Node.js instead of Bun, scimgateway must be downloaded from github because Node.js does not support native typescript used by modules. Startup will then be:
|
|
162
|
-
node --experimental-strip-types c:\my-scimgateway\index.ts
|
|
162
|
+
`node --experimental-strip-types c:\my-scimgateway\index.ts`
|
|
163
163
|
|
|
164
164
|
#### Upgrade SCIM Gateway
|
|
165
165
|
|
|
@@ -1351,10 +1351,10 @@ Plugins should have following initialization:
|
|
|
1351
1351
|
// start - mandatory plugin initialization
|
|
1352
1352
|
const ScimGateway: typeof import('scimgateway').ScimGateway = await (async () => {
|
|
1353
1353
|
try {
|
|
1354
|
-
|
|
1354
|
+
return (await import('scimgateway')).ScimGateway
|
|
1355
1355
|
} catch (err) {
|
|
1356
|
-
|
|
1357
|
-
|
|
1356
|
+
const source = './scimgateway.ts'
|
|
1357
|
+
return (await import(source)).ScimGateway
|
|
1358
1358
|
}
|
|
1359
1359
|
})()
|
|
1360
1360
|
const scimgateway = new ScimGateway()
|
|
@@ -1368,10 +1368,10 @@ If using REST, we could also include the HelperRest:
|
|
|
1368
1368
|
...
|
|
1369
1369
|
const HelperRest: typeof import('scimgateway').HelperRest = await (async () => {
|
|
1370
1370
|
try {
|
|
1371
|
-
|
|
1371
|
+
return (await import('scimgateway')).HelperRest
|
|
1372
1372
|
} catch (err) {
|
|
1373
|
-
|
|
1374
|
-
|
|
1373
|
+
const source = './scimgateway.ts'
|
|
1374
|
+
return (await import(source)).HelperRest
|
|
1375
1375
|
}
|
|
1376
1376
|
})()
|
|
1377
1377
|
...
|
|
@@ -1405,11 +1405,25 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1405
1405
|
|
|
1406
1406
|
## Change log
|
|
1407
1407
|
|
|
1408
|
+
### v5.3.2
|
|
1409
|
+
|
|
1410
|
+
[Improved]
|
|
1411
|
+
|
|
1412
|
+
- helper-rest, retry on request error 504 Gateway Timeout
|
|
1413
|
+
- performance micro-optimization on log mask logic
|
|
1414
|
+
|
|
1415
|
+
### v5.3.1
|
|
1416
|
+
|
|
1417
|
+
[Fixed]
|
|
1418
|
+
|
|
1419
|
+
- Incorrect log masking of SCIM 2.0 PATCH Operations
|
|
1420
|
+
- plugin-ldap, create user/group having DN special character `#` failed on OpenLDAP
|
|
1421
|
+
|
|
1408
1422
|
### v5.3.0
|
|
1409
1423
|
|
|
1410
1424
|
[Improved]
|
|
1411
1425
|
|
|
1412
|
-
- [
|
|
1426
|
+
- [Bulk Operations](https://datatracker.ietf.org/doc/html/rfc7644#section-3.7) now supported
|
|
1413
1427
|
- Dependencies bump
|
|
1414
1428
|
|
|
1415
1429
|
### v5.2.5
|
package/lib/helper-rest.ts
CHANGED
|
@@ -699,7 +699,7 @@ export class HelperRest {
|
|
|
699
699
|
try { urlObj = new URL(path) } catch (err) { void 0 }
|
|
700
700
|
let isServiceClient = !urlObj && this._serviceClient[baseEntity] && !this.lock.isLocked() // !isLocked to avoid retry ongoing doRequest with failing getAccessToken()
|
|
701
701
|
let oAuthTokeErr = statusCode === 401 && this.config_entity[baseEntity].connection?.auth?.type && this.config_entity[baseEntity].connection.auth.type.startsWith('oauth')
|
|
702
|
-
if (isServiceClient && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ABORT_ERR' || err.code === 'ETIMEDOUT' || oAuthTokeErr || retryAfter)) {
|
|
702
|
+
if (isServiceClient && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ABORT_ERR' || err.code === 'ETIMEDOUT' || statusCode === 504 || oAuthTokeErr || retryAfter)) {
|
|
703
703
|
this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} Body = ${JSON.stringify(body)} Error Response = ${err.message}`)
|
|
704
704
|
if (retryAfter) {
|
|
705
705
|
this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} throttle/ratelimit error - awaiting ${retryAfter} seconds before automatic retry`)
|
package/lib/logger.ts
CHANGED
|
@@ -81,6 +81,7 @@ export class Logger {
|
|
|
81
81
|
private rotating = false
|
|
82
82
|
private buffer: string[] = []
|
|
83
83
|
private reJson: RegExp
|
|
84
|
+
private reJsonPathValue: RegExp
|
|
84
85
|
private reXml: RegExp
|
|
85
86
|
private callbacks: Set<(message: any) => Promise<void>> = new Set()
|
|
86
87
|
private LOG_DIR: string
|
|
@@ -128,20 +129,26 @@ export class Logger {
|
|
|
128
129
|
if (option.customMasking) this.customMasking = option.customMasking
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
let
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
132
|
+
let customMask = this.customMasking || []
|
|
133
|
+
if (!Array.isArray(customMask)) customMask = []
|
|
134
|
+
const jsonMaskKeys = ['password', 'access_token', 'client_secret', 'assertion', 'client_assertion']
|
|
135
|
+
const jsonJoinedKeys = jsonMaskKeys.concat(customMask).join('|')
|
|
136
|
+
const xmlMaskKeys = ['credentials', 'PasswordText', 'PasswordDigest', 'password']
|
|
137
|
+
const xmlJoinedKeys = xmlMaskKeys.concat(customMask).join('"?|') + '"?'
|
|
138
|
+
|
|
139
139
|
this.reJson = new RegExp(
|
|
140
|
-
`("(
|
|
140
|
+
`("(?:${jsonJoinedKeys})"\\s*:\\s*)"([^"]+)"`,
|
|
141
141
|
'gi',
|
|
142
142
|
)
|
|
143
|
+
|
|
144
|
+
// matches "path":"<maskKey>", then finds "value":"<value>" to mask it - SCIM 2.0 PATCH Operations
|
|
145
|
+
this.reJsonPathValue = new RegExp(
|
|
146
|
+
`("path"\\s*:\\s*"(?:${jsonJoinedKeys})"[^{}]*?"value"\\s*:\\s*")([^"]+)(")`,
|
|
147
|
+
'gi',
|
|
148
|
+
)
|
|
149
|
+
|
|
143
150
|
this.reXml = new RegExp(
|
|
144
|
-
`(<(?:\\w+:)?(
|
|
151
|
+
`(<(?:\\w+:)?(${xmlJoinedKeys})[^>]*>)([^<]+)(<\\/(:?\\w+:)?\\2>)`,
|
|
145
152
|
'gi',
|
|
146
153
|
)
|
|
147
154
|
|
|
@@ -164,18 +171,30 @@ export class Logger {
|
|
|
164
171
|
|
|
165
172
|
private maskSecret(msg: string): string {
|
|
166
173
|
if (!msg) return msg
|
|
174
|
+
|
|
167
175
|
// Mask JSON secrets
|
|
168
176
|
msg = msg.replace(
|
|
169
177
|
this.reJson,
|
|
170
178
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
171
|
-
(_, keyValuePair,
|
|
179
|
+
(_, keyValuePair, value) => `${keyValuePair}"******"`,
|
|
172
180
|
)
|
|
181
|
+
|
|
182
|
+
// Mask JSON path/value secrets (SCIM 2.0 PATCH Operations)
|
|
183
|
+
if (msg.includes('"path"')) {
|
|
184
|
+
msg = msg.replace(
|
|
185
|
+
this.reJsonPathValue,
|
|
186
|
+
(_, prefix, value, suffix) => `${prefix}******${suffix}`,
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
173
190
|
// Mask XML/Soap secrets
|
|
174
191
|
// console.log('XML matches found:', msg.match(this.reXml)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
192
|
+
if (msg.includes('<?xml')) {
|
|
193
|
+
msg = msg.replace(
|
|
194
|
+
this.reXml,
|
|
195
|
+
(_, startTag, tagName, value, endTag) => `${startTag}******${endTag}`,
|
|
196
|
+
)
|
|
197
|
+
}
|
|
179
198
|
|
|
180
199
|
return msg
|
|
181
200
|
}
|
package/lib/plugin-ldap.ts
CHANGED
package/lib/scimgateway.ts
CHANGED
|
@@ -570,10 +570,18 @@ export class ScimGateway {
|
|
|
570
570
|
if (authType === 'Basic') [userName] = (Buffer.from(authToken, 'base64').toString() || '').split(':')
|
|
571
571
|
if (!userName && authType === 'Bearer') userName = 'token'
|
|
572
572
|
let outbound = ctx.response.body
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
573
|
+
|
|
574
|
+
if (typeof outbound === 'string' && outbound.length > 1500 && outbound.includes('"Resources":')) {
|
|
575
|
+
try {
|
|
576
|
+
const o = JSON.parse(outbound)
|
|
577
|
+
if (o?.Resources?.length > 1) {
|
|
578
|
+
o.Resources = [o.Resources[0]]
|
|
579
|
+
o.Resources.push({ loggerComment: '===REST OF OBJECTS TRUNCATED BECAUSE OF LOG LENGTH===' })
|
|
580
|
+
outbound = JSON.stringify(o)
|
|
581
|
+
}
|
|
582
|
+
} catch (err) {}
|
|
576
583
|
}
|
|
584
|
+
|
|
577
585
|
if (ctx.response.status && (ctx.response.status < 200 || ctx.response.status > 299)) {
|
|
578
586
|
if (ctx.response.status === 404) logger.warn(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${userName} ${ctx.response.status} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}`)
|
|
579
587
|
else logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${userName} ${ctx.response.status} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}`)
|
package/package.json
CHANGED