scimgateway 4.2.11 → 4.2.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 +14 -1
- package/lib/scimdef-v1.js +27 -11
- package/lib/scimdef-v2.js +17 -3
- package/lib/scimgateway.js +49 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ If internet connection is blocked, we could install on another machine and copy
|
|
|
124
124
|
|
|
125
125
|
node c:\my-scimgateway
|
|
126
126
|
|
|
127
|
-
Start a browser
|
|
127
|
+
Start a browser (note, Edge do not pop-up logon dialog box when using http)
|
|
128
128
|
|
|
129
129
|
http://localhost:8880/ping
|
|
130
130
|
=> Health check with a "hello" response
|
|
@@ -326,6 +326,7 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
326
326
|
- **scim.version** - "1.1" or "2.0". Default is "2.0". For Symantec/Broadcom/CA Identity Manager "1.1" should be used.
|
|
327
327
|
|
|
328
328
|
- **scim.customSchema** - filename of JSON file located in `<package-root>\config\schemas` containing custom schema attributes, see configuration notes
|
|
329
|
+
**additional information**: Schemas, ServiceProviderConfig and ResourceType can be customized if `lib/scimdef-v2.js (or scimdef-v1.js)` exists. Original scimdef-v2.js/scimdef-v1.js can be copied from node_modules/scimgateway/lib to your plugin/lib and customized.
|
|
329
330
|
|
|
330
331
|
- **scim.skipTypeConvert** - true or false, default false. Multivalue attributes supporting types e.g. emails, phoneNumbers, ims, photos, addresses, entitlements and x509Certificates (but not roles, groups and members) will be become "type converted objects" when sent to modifyUser and createUser. This for simplicity of checking attributes included and also for the endpointMapper method (used by plugin-ldap and plugin-azure-ad), e.g.:
|
|
331
332
|
|
|
@@ -1169,6 +1170,18 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1169
1170
|
|
|
1170
1171
|
## Change log
|
|
1171
1172
|
|
|
1173
|
+
### v4.2.13
|
|
1174
|
+
|
|
1175
|
+
[Fixed]
|
|
1176
|
+
|
|
1177
|
+
- `/ping` now excluded from info logs. If we want ping logging, use something else than lowercase e.g., `/Ping` or `/PING`
|
|
1178
|
+
|
|
1179
|
+
### v4.2.12
|
|
1180
|
+
|
|
1181
|
+
[Added]
|
|
1182
|
+
|
|
1183
|
+
- Schemas, ServiceProviderConfig and ResourceType can be customized if `lib/scimdef-v2.js (or scimdef-v1.js)` exists. Original scimdef-v2.js/scimdef-v1.js can be copied from node_modules/scimgateway/lib to your plugin/lib and customized.
|
|
1184
|
+
|
|
1172
1185
|
### v4.2.11
|
|
1173
1186
|
|
|
1174
1187
|
[Added]
|
package/lib/scimdef-v1.js
CHANGED
|
@@ -11,23 +11,39 @@ module.exports.ServiceProviderConfigs = {
|
|
|
11
11
|
"bulk": {
|
|
12
12
|
"supported": false,
|
|
13
13
|
"maxOperations": 10000,
|
|
14
|
-
"maxPayloadSize":
|
|
14
|
+
"maxPayloadSize": 1048576
|
|
15
15
|
},
|
|
16
16
|
"filter": {
|
|
17
17
|
"supported": true,
|
|
18
|
-
"maxResults":
|
|
18
|
+
"maxResults": 200
|
|
19
19
|
},
|
|
20
20
|
"changePassword": { "supported": true },
|
|
21
21
|
"sort": { "supported": false },
|
|
22
|
-
"etag": { "supported":
|
|
23
|
-
"authenticationSchemes": [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
"etag": { "supported": false },
|
|
23
|
+
"authenticationSchemes": [
|
|
24
|
+
{
|
|
25
|
+
"type": "httpbasic",
|
|
26
|
+
"name": "HTTP Basic",
|
|
27
|
+
"description": "Authentication scheme using the HTTP Basic Standard",
|
|
28
|
+
"specURI": "http://www.rfc-editor.org/info/rfc2617",
|
|
29
|
+
"documentationUri": "https://elshaug.xyz",
|
|
30
|
+
"primary": true
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "oauthbearertoken",
|
|
34
|
+
"name": "OAuth Bearer Token",
|
|
35
|
+
"description": "Authentication scheme using the OAuth Bearer Token Standard",
|
|
36
|
+
"specUri": "http://www.rfc-editor.org/info/rfc6750",
|
|
37
|
+
"documentationUri": "https://elshaug.xyz"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "oauth2",
|
|
41
|
+
"name": "OAuth v2.0",
|
|
42
|
+
"description": "Authentication Scheme using the OAuth Standard",
|
|
43
|
+
"specUri": "http://tools.ietf.org/html/rfc6749",
|
|
44
|
+
"documentationUri": "https://elshaug.xyz"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
31
47
|
"xmlDataFormat": { "supported": false }
|
|
32
48
|
}
|
|
33
49
|
|
package/lib/scimdef-v2.js
CHANGED
|
@@ -27,15 +27,29 @@ module.exports.ServiceProviderConfigs = {
|
|
|
27
27
|
"etag": {
|
|
28
28
|
"supported": false
|
|
29
29
|
},
|
|
30
|
-
"documentationUri": "
|
|
30
|
+
"documentationUri": "https://elshaug.xyz",
|
|
31
31
|
"authenticationSchemes": [
|
|
32
32
|
{
|
|
33
|
+
"type": "httpbasic",
|
|
33
34
|
"name": "HTTP Basic",
|
|
34
35
|
"description": "Authentication scheme using the HTTP Basic Standard",
|
|
35
36
|
"specURI": "http://www.rfc-editor.org/info/rfc2617",
|
|
36
|
-
"documentationUri": "
|
|
37
|
-
"type": "httpbasic",
|
|
37
|
+
"documentationUri": "https://elshaug.xyz",
|
|
38
38
|
"primary": true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "oauthbearertoken",
|
|
42
|
+
"name": "OAuth Bearer Token",
|
|
43
|
+
"description": "Authentication scheme using the OAuth Bearer Token Standard",
|
|
44
|
+
"specUri": "http://www.rfc-editor.org/info/rfc6750",
|
|
45
|
+
"documentationUri": "https://elshaug.xyz"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"type": "oauth2",
|
|
49
|
+
"name": "OAuth v2.0",
|
|
50
|
+
"description": "Authentication Scheme using the OAuth Standard",
|
|
51
|
+
"specUri": "http://tools.ietf.org/html/rfc6749",
|
|
52
|
+
"documentationUri": "https://elshaug.xyz"
|
|
39
53
|
}
|
|
40
54
|
],
|
|
41
55
|
"xmlDataFormat": { "supported": false }
|
package/lib/scimgateway.js
CHANGED
|
@@ -37,7 +37,8 @@ const ScimGateway = function () {
|
|
|
37
37
|
const stack = callsite()
|
|
38
38
|
const requester = stack[1].getFileName()
|
|
39
39
|
const pluginName = path.basename(requester, '.js')
|
|
40
|
-
const
|
|
40
|
+
const pluginDir = path.dirname(requester)
|
|
41
|
+
const configDir = path.join(pluginDir, '..', 'config')
|
|
41
42
|
const configFile = path.join(`${configDir}`, `${pluginName}.json`) // config name prefix same as pluging name prefix
|
|
42
43
|
let config = require(configFile).scimgateway
|
|
43
44
|
let extConfigErr
|
|
@@ -46,7 +47,7 @@ const ScimGateway = function () {
|
|
|
46
47
|
} catch (err) { extConfigErr = err }
|
|
47
48
|
|
|
48
49
|
const gwName = path.basename(__filename, '.js') // prefix of current file
|
|
49
|
-
const logDir = path.join(
|
|
50
|
+
const logDir = path.join(pluginDir, '..', 'logs')
|
|
50
51
|
const Log = require('../lib/logger').Log
|
|
51
52
|
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`))
|
|
52
53
|
const logger = log.logger()
|
|
@@ -246,10 +247,14 @@ const ScimGateway = function () {
|
|
|
246
247
|
let isScimv2 = false
|
|
247
248
|
if (config.scim.version === '2.0' || config.scim.version === 2) {
|
|
248
249
|
isScimv2 = true
|
|
249
|
-
scimDef = require('
|
|
250
|
-
|
|
250
|
+
if (fs.existsSync(pluginDir + '/scimdef-v2.js')) scimDef = require(pluginDir + '/scimdef-v2') // using custom
|
|
251
|
+
else scimDef = require('../lib/scimdef-v2')
|
|
252
|
+
} else {
|
|
253
|
+
if (fs.existsSync(pluginDir + '/scimdef-v1.js')) scimDef = require(pluginDir + '/scimdef-v1') // using custom
|
|
254
|
+
else scimDef = require('../lib/scimdef-v1')
|
|
255
|
+
}
|
|
251
256
|
|
|
252
|
-
if (config.scim.customSchema) { // merge plugin custom schema extension into core schemas
|
|
257
|
+
if (config.scim.customSchema) { // merge plugin custom schema extension into core schemas (better using above custom scimdef-v2.js in plugin lib folder)
|
|
253
258
|
let custom
|
|
254
259
|
try {
|
|
255
260
|
custom = JSON.parse(fs.readFileSync(`${configDir}/schemas/${config.scim.customSchema}`, 'utf8'))
|
|
@@ -285,8 +290,15 @@ const ScimGateway = function () {
|
|
|
285
290
|
}
|
|
286
291
|
}
|
|
287
292
|
|
|
288
|
-
|
|
289
|
-
this.
|
|
293
|
+
// exposed and used by plugin-loki
|
|
294
|
+
this.testmodeusers = []
|
|
295
|
+
this.testmodegroups = []
|
|
296
|
+
if (scimDef.TestmodeUsers && scimDef.TestmodeUsers.Resources) {
|
|
297
|
+
this.testmodeusers = scimDef.TestmodeUsers.Resources
|
|
298
|
+
}
|
|
299
|
+
if (scimDef.TestmodeGroups && scimDef.TestmodeGroups.Resources) {
|
|
300
|
+
this.testmodegroups = scimDef.TestmodeGroups.Resources
|
|
301
|
+
}
|
|
290
302
|
|
|
291
303
|
// multiValueTypes array contains attributes that will be used by "type converted objects" logic
|
|
292
304
|
// groups, roles, and members are excluded
|
|
@@ -305,7 +317,8 @@ const ScimGateway = function () {
|
|
|
305
317
|
|
|
306
318
|
const logResult = async (ctx, next) => {
|
|
307
319
|
const started = Date.now()
|
|
308
|
-
await next() // once all middleware
|
|
320
|
+
await next() // once all middleware completes, below continues
|
|
321
|
+
if (ctx.request.url === '/ping' || ctx.request.url === '/favicon.ico') return
|
|
309
322
|
const ellapsed = (Date.now() - started) + 'ms' // ctx.set('X-ResponseTime', ellapsed)
|
|
310
323
|
const res = {
|
|
311
324
|
statusCode: ctx.response.status,
|
|
@@ -316,41 +329,39 @@ const ScimGateway = function () {
|
|
|
316
329
|
const [authType, authToken] = (ctx.request.header.authorization || '').split(' ') // [0] = 'Basic' or 'Bearer'
|
|
317
330
|
if (authType === 'Basic') [userName] = (Buffer.from(authToken, 'base64').toString() || '').split(':')
|
|
318
331
|
if (!userName && authType === 'Bearer') userName = 'token'
|
|
319
|
-
if (ctx.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
332
|
+
if (ctx.response.status < 200 || ctx.response.status > 299) {
|
|
333
|
+
// statusCode check in logResult method...
|
|
334
|
+
// "statusCode":xxx in error messages let plugin set error statusCode returned by scimgateway
|
|
335
|
+
let pluginStatusCode = 0
|
|
336
|
+
const reJson = '^.*"(statusCode)" *: *([0-9][0-9][0-9]).*'
|
|
337
|
+
const rePattern = new RegExp(reJson, 'i')
|
|
338
|
+
if (res.body.detail) {
|
|
339
|
+
const arrMatches = res.body.detail.match(rePattern)
|
|
340
|
+
if (Array.isArray(arrMatches) && arrMatches.length === 3) {
|
|
341
|
+
pluginStatusCode = parseInt(arrMatches[2])
|
|
342
|
+
}
|
|
343
|
+
} else if (res.body.Errors) {
|
|
344
|
+
if (Array.isArray(res.body.Errors) && res.body.Errors[0].description && res.body.Errors[0].description) {
|
|
345
|
+
const arrMatches = res.body.Errors[0].description.match(rePattern)
|
|
328
346
|
if (Array.isArray(arrMatches) && arrMatches.length === 3) {
|
|
329
347
|
pluginStatusCode = parseInt(arrMatches[2])
|
|
330
348
|
}
|
|
331
|
-
} else if (res.body.Errors) {
|
|
332
|
-
if (Array.isArray(res.body.Errors) && res.body.Errors[0].description && res.body.Errors[0].description) {
|
|
333
|
-
const arrMatches = res.body.Errors[0].description.match(rePattern)
|
|
334
|
-
if (Array.isArray(arrMatches) && arrMatches.length === 3) {
|
|
335
|
-
pluginStatusCode = parseInt(arrMatches[2])
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
349
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
350
|
+
}
|
|
351
|
+
if (pluginStatusCode > 0) {
|
|
352
|
+
ctx.response.status = pluginStatusCode // auto change ctx.response.message
|
|
353
|
+
res.statusCode = ctx.response.status
|
|
354
|
+
res.statusMessage = ctx.response.message
|
|
355
|
+
if (pluginStatusCode === 401 || pluginStatusCode === 403) { // don't reveal original SCIM error message details related to access denied (e.g. using Auth PassThrough)
|
|
356
|
+
ctx.response.set('Content-Type', 'application/json; charset=utf-8')
|
|
357
|
+
ctx.response.body = { error: 'Access denied' }
|
|
358
|
+
res.body = ctx.response.body
|
|
348
359
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
360
|
+
}
|
|
361
|
+
// back to logResult...
|
|
362
|
+
logger.error(`${gwName}[${pluginName}] ${ellapsed} ${ctx.request.ipcli} ${userName} ${ctx.request.method} ${ctx.request.href} Inbound = ${JSON.stringify(ctx.request.body)} Outbound = ${JSON.stringify(res)}${(config.log.loglevel.file === 'debug' && ctx.request.url !== '/ping') ? '\n' : ''}`)
|
|
363
|
+
} else logger.info(`${gwName}[${pluginName}] ${ellapsed} ${ctx.request.ipcli} ${userName} ${ctx.request.method} ${ctx.request.href} Inbound = ${JSON.stringify(ctx.request.body)} Outbound = ${JSON.stringify(res)}${(config.log.loglevel.file === 'debug' && ctx.request.url !== '/ping') ? '\n' : ''}`)
|
|
364
|
+
requestCounter += 1 // logged on exit (not win process termination)
|
|
354
365
|
if (ctx.response.body && typeof ctx.response.body === 'object' && ctx.response.status !== 401) ctx.set('Content-Type', 'application/scim+json; charset=utf-8')
|
|
355
366
|
}
|
|
356
367
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.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",
|