scimgateway 4.2.9 → 4.2.11
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 +12 -0
- package/lib/plugin-ldap.js +55 -37
- package/lib/scimgateway.js +22 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1169,6 +1169,18 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1169
1169
|
|
|
1170
1170
|
## Change log
|
|
1171
1171
|
|
|
1172
|
+
### v4.2.11
|
|
1173
|
+
|
|
1174
|
+
[Added]
|
|
1175
|
+
|
|
1176
|
+
- Plugin can set error statusCode returned by scimgateway through error message. Error message must then contain string `"statusCode":xxx` where xxx is HTTP status code e.g., 401. Plugin using REST will have statusCode automatically included in error message thrown by plugin. This could be useful for auth.PassThrough.
|
|
1177
|
+
|
|
1178
|
+
### v4.2.10
|
|
1179
|
+
|
|
1180
|
+
[Fixed]
|
|
1181
|
+
|
|
1182
|
+
- plugin-ldap broken after dependencies bump of ldapjs (from 2.x.x to 3.x.x) in version 4.2.7
|
|
1183
|
+
|
|
1172
1184
|
### v4.2.9
|
|
1173
1185
|
|
|
1174
1186
|
[Fixed]
|
package/lib/plugin-ldap.js
CHANGED
|
@@ -110,15 +110,11 @@ if (config.useSID_id && config.map.group) {
|
|
|
110
110
|
}
|
|
111
111
|
if (config.map.user.userPrincipalName && config.map.user.userPrincipalName.mapDomain) { // support mapping different inbound/outbound upn domain names
|
|
112
112
|
if (config.map.user.userPrincipalName.mapDomain.inbound && config.map.user.userPrincipalName.mapDomain.outbound) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
inbound = inbound.startsWith('@') ? inbound : '@' + inbound
|
|
116
|
-
outbound = outbound.startsWith('@') ? outbound : '@' + outbound
|
|
117
|
-
|
|
118
|
-
inbound: inbound, // "test.onmicrosoft.com
|
|
119
|
-
outbound: outbound // "my-company.com"
|
|
120
|
-
}
|
|
121
|
-
}
|
|
113
|
+
const inbound = config.map.user.userPrincipalName.mapDomain.inbound
|
|
114
|
+
const outbound = config.map.user.userPrincipalName.mapDomain.outbound
|
|
115
|
+
config.map.user.userPrincipalName.mapDomain.inbound = inbound.startsWith('@') ? inbound : '@' + inbound // "@my-company.com"
|
|
116
|
+
config.map.user.userPrincipalName.mapDomain.outbound = outbound.startsWith('@') ? outbound : '@' + outbound // "@test.onmicrosoft.com
|
|
117
|
+
} else delete config.map.user.userPrincipalName.mapDomain
|
|
122
118
|
}
|
|
123
119
|
|
|
124
120
|
// =================================================
|
|
@@ -1034,13 +1030,12 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1034
1030
|
let client = null
|
|
1035
1031
|
|
|
1036
1032
|
const options = scimgateway.copyObj(ldapOptions)
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
}
|
|
1033
|
+
// support having different upn-domain on IdP and target
|
|
1034
|
+
if (options.modification && options.modification.userPrincipalName && config.map.user.userPrincipalName && config.map.user.userPrincipalName.mapDomain) {
|
|
1035
|
+
if (options.modification.userPrincipalName.endsWith(config.map.user.userPrincipalName.mapDomain.outbound)) {
|
|
1036
|
+
const old = options.modification.userPrincipalName
|
|
1037
|
+
options.modification.userPrincipalName = options.modification.userPrincipalName.replace(config.map.user.userPrincipalName.mapDomain.outbound, config.map.user.userPrincipalName.mapDomain.inbound)
|
|
1038
|
+
scimgateway.logger.debug(`${pluginName}[${baseEntity}] inbound upnMapDomain ${old} => ${options.modification.userPrincipalName}`)
|
|
1044
1039
|
}
|
|
1045
1040
|
}
|
|
1046
1041
|
|
|
@@ -1055,37 +1050,45 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1055
1050
|
if (err) {
|
|
1056
1051
|
return reject(err)
|
|
1057
1052
|
}
|
|
1053
|
+
|
|
1058
1054
|
search.on('searchEntry', (entry) => {
|
|
1059
|
-
if (entry.attributes)
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1055
|
+
if (!entry.pojo || !entry.pojo.attributes) return
|
|
1056
|
+
const obj = { dn: entry.pojo.objectName }
|
|
1057
|
+
entry.pojo.attributes.map((el) => {
|
|
1058
|
+
if (el.values.length > 1) obj[el.type] = el.values
|
|
1059
|
+
else obj[el.type] = el.values[0]
|
|
1060
|
+
return null
|
|
1061
|
+
})
|
|
1062
|
+
// objectSid/objectGUID - assume Active Directory - can't use default utf-8 when attribute value is hex
|
|
1063
|
+
if (obj.objectSid) {
|
|
1064
|
+
const b = Buffer.from(obj.objectSid, 'utf-8')
|
|
1065
|
+
const sidStr = convertSidToString(b) // using string: S-1-5-21-2657077294-4200173015-2627628055-1255
|
|
1066
|
+
if (!sidStr) throw new Error(`doRequest() error: failed to convert SID ${b.toString('hex')} to string}`)
|
|
1067
|
+
obj.objectSid = sidStr
|
|
1068
|
+
}
|
|
1069
|
+
if (obj.objectGUID) {
|
|
1070
|
+
const b = Buffer.from(obj.objectGUID, 'utf-8')
|
|
1071
|
+
obj.objectGUID = b.toString('base64') // using base64: nitWLrhokUqKl1DywiavXg==
|
|
1072
|
+
}
|
|
1073
|
+
if (obj.userPrincipalName && config.map.user.userPrincipalName && config.map.user.userPrincipalName.mapDomain) {
|
|
1074
|
+
if (obj.userPrincipalName.endsWith(config.map.user.userPrincipalName.mapDomain.inbound)) {
|
|
1075
|
+
const old = obj.userPrincipalName
|
|
1076
|
+
obj.userPrincipalName = obj.userPrincipalName.replace(config.map.user.userPrincipalName.mapDomain.inbound, config.map.user.userPrincipalName.mapDomain.outbound)
|
|
1077
|
+
scimgateway.logger.debug(`${pluginName}[${baseEntity}] outbound upnMapDomain ${old} => ${obj.userPrincipalName}`)
|
|
1078
|
+
}
|
|
1078
1079
|
}
|
|
1079
|
-
results.push(
|
|
1080
|
+
results.push(obj)
|
|
1080
1081
|
})
|
|
1081
1082
|
|
|
1082
1083
|
search.on('page', (entry, cb) => {
|
|
1083
1084
|
// if (cb) cb() // pagePause = true gives callback
|
|
1084
1085
|
})
|
|
1086
|
+
|
|
1085
1087
|
search.on('error', (err) => {
|
|
1086
1088
|
if (err.message.includes('LdapErr: DSID-0C0909F2') || err.message.includes('NO_OBJECT')) return resolve([]) // object not found when using base <SID=...> or <GUID=...> ref. objectSid/objectGUID
|
|
1087
1089
|
reject(err)
|
|
1088
1090
|
})
|
|
1091
|
+
|
|
1089
1092
|
search.on('end', (_) => { resolve(results) })
|
|
1090
1093
|
})
|
|
1091
1094
|
})
|
|
@@ -1094,7 +1097,22 @@ const doRequest = async (baseEntity, method, base, ldapOptions, ctx) => {
|
|
|
1094
1097
|
case 'modify':
|
|
1095
1098
|
result = await new Promise((resolve, reject) => {
|
|
1096
1099
|
const dn = base
|
|
1097
|
-
|
|
1100
|
+
const changes = []
|
|
1101
|
+
for (const key in options.modification) {
|
|
1102
|
+
const mod = {}
|
|
1103
|
+
mod.type = key
|
|
1104
|
+
if (Array.isArray(options.modification[key])) mod.values = options.modification[key]
|
|
1105
|
+
else {
|
|
1106
|
+
if (typeof options.modification[key] === 'string') mod.values = [options.modification[key]]
|
|
1107
|
+
else mod.values = [options.modification[key].toString()]
|
|
1108
|
+
}
|
|
1109
|
+
const change = new ldap.Change({
|
|
1110
|
+
operation: options.operation || 'replace',
|
|
1111
|
+
modification: mod // { type: "givenName", values: ["Joe"] }
|
|
1112
|
+
})
|
|
1113
|
+
changes.push(change)
|
|
1114
|
+
}
|
|
1115
|
+
client.modify(dn, changes, (err) => {
|
|
1098
1116
|
if (err) {
|
|
1099
1117
|
if (options.operation && options.operation === 'add' && options.modification && options.modification.member) {
|
|
1100
1118
|
if (err.message.includes('ENTRY_EXISTS')) return resolve() // add already existing group to user
|
package/lib/scimgateway.js
CHANGED
|
@@ -318,22 +318,35 @@ const ScimGateway = function () {
|
|
|
318
318
|
if (!userName && authType === 'Bearer') userName = 'token'
|
|
319
319
|
if (ctx.request.url !== '/favicon.ico') {
|
|
320
320
|
if (ctx.response.status < 200 || ctx.response.status > 299) {
|
|
321
|
-
|
|
321
|
+
// statusCode check in logResult method...
|
|
322
|
+
// "statusCode":xxx in error messages let plugin set error statusCode returned by scimgateway
|
|
323
|
+
let pluginStatusCode = 0
|
|
324
|
+
const reJson = '^.*"(statusCode)" *: *([0-9][0-9][0-9]).*'
|
|
325
|
+
const rePattern = new RegExp(reJson, 'i')
|
|
322
326
|
if (res.body.detail) {
|
|
323
|
-
|
|
327
|
+
const arrMatches = res.body.detail.match(rePattern)
|
|
328
|
+
if (Array.isArray(arrMatches) && arrMatches.length === 3) {
|
|
329
|
+
pluginStatusCode = parseInt(arrMatches[2])
|
|
330
|
+
}
|
|
324
331
|
} else if (res.body.Errors) {
|
|
325
|
-
if (Array.isArray(res.body.Errors) && res.body.Errors[0].description && res.body.Errors[0].description
|
|
326
|
-
|
|
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
|
+
}
|
|
327
337
|
}
|
|
328
338
|
}
|
|
329
|
-
if (
|
|
330
|
-
ctx.response.
|
|
331
|
-
ctx.response.status = 401 // ctx.response.message becomes default 'Unauthorized'
|
|
332
|
-
ctx.response.body = { error: 'Access denied' }
|
|
339
|
+
if (pluginStatusCode > 0) {
|
|
340
|
+
ctx.response.status = pluginStatusCode // auto change ctx.response.message
|
|
333
341
|
res.statusCode = ctx.response.status
|
|
334
342
|
res.statusMessage = ctx.response.message
|
|
335
|
-
|
|
343
|
+
if (pluginStatusCode === 401 || pluginStatusCode === 403) { // don't reveal original SCIM error message details related to access denied (e.g. using Auth PassThrough)
|
|
344
|
+
ctx.response.set('Content-Type', 'application/json; charset=utf-8')
|
|
345
|
+
ctx.response.body = { error: 'Access denied' }
|
|
346
|
+
res.body = ctx.response.body
|
|
347
|
+
}
|
|
336
348
|
}
|
|
349
|
+
// back to logResult...
|
|
337
350
|
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' : ''}`)
|
|
338
351
|
} 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' : ''}`)
|
|
339
352
|
requestCounter += 1 // logged on exit (not win process termination)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.11",
|
|
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",
|