scimgateway 4.2.14 → 4.2.15

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 CHANGED
@@ -1170,6 +1170,14 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1170
1170
 
1171
1171
  ## Change log
1172
1172
 
1173
+ ### v4.2.15
1174
+
1175
+ [Added]
1176
+
1177
+ - Plugin can set error statusCode returned by scimgateway through error object key `err.name`. This can be done by adding suffix `#code` to err.name where code is HTTP status code e.g., `err.name += '#401'`. This can be useful for auth.PassThrough and other scenarios like createUser where user already exist (409) and modifyUser where user does not exist (404)
1178
+
1179
+ This change replace statusCode logic introduced in v4.2.11
1180
+
1173
1181
  ### v4.2.14
1174
1182
 
1175
1183
  [Fixed]
@@ -1192,6 +1200,8 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1192
1200
 
1193
1201
  [Added]
1194
1202
 
1203
+ Note, obsolete - see v4.2.15 comments
1204
+
1195
1205
  - 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.
1196
1206
 
1197
1207
  ### v4.2.10
@@ -267,7 +267,7 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
267
267
  } else return (null)
268
268
  } catch (err) {
269
269
  const newErr = new Error(`${action} error: ${err.message}`)
270
- if (err.message.includes('userPrincipalName already exists')) newErr.name = 'uniqueness' // maps to scimType error handling
270
+ if (err.message.includes('userPrincipalName already exists')) newErr.name += '#409' // customErrCode
271
271
  throw newErr
272
272
  }
273
273
  }
@@ -685,7 +685,7 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
685
685
  return null
686
686
  } catch (err) {
687
687
  const newErr = new Error(`${action} error: ${err.message}`)
688
- if (err.message.includes('already exist')) newErr.name = 'uniqueness'
688
+ if (err.message.includes('already exist')) newErr.name += '#409' // customErrCode
689
689
  throw newErr
690
690
  }
691
691
  }
@@ -322,7 +322,7 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
322
322
  return null
323
323
  } catch (err) {
324
324
  const newErr = new Error(`${action} error: ${err.message}`)
325
- if (newErr.message.includes('ENTRY_EXISTS')) newErr.name = 'uniqueness' // maps to scimType error handling
325
+ if (newErr.message.includes('ENTRY_EXISTS')) newErr.name += '#409' // customErrCode
326
326
  throw newErr
327
327
  }
328
328
  }
@@ -654,7 +654,7 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
654
654
  return null
655
655
  } catch (err) {
656
656
  const newErr = new Error(`${action} error: ${err.message}`)
657
- if (newErr.message.includes('ENTRY_EXISTS')) newErr.name = 'uniqueness' // maps to scimType error handling
657
+ if (newErr.message.includes('ENTRY_EXISTS')) newErr.name += '#409' // customErrCode
658
658
  throw newErr
659
659
  }
660
660
  }
@@ -228,7 +228,7 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
228
228
  } catch (err) {
229
229
  const newErr = new Error(`${action} error: ${err.message}`)
230
230
  if (err.message && err.message.startsWith('Duplicate key')) {
231
- newErr.name = 'uniqueness' // maps to scimType error handling
231
+ newErr.name += '#409' // customErrorCode
232
232
  }
233
233
  throw newErr
234
234
  }
@@ -472,7 +472,7 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
472
472
  } catch (err) {
473
473
  const newErr = new Error(`${action} error: ${err.message}`)
474
474
  if (err.message && err.message.startsWith('Duplicate key')) {
475
- newErr.name = 'uniqueness' // maps to scimType error handling
475
+ newErr.name += '#409' // customErrorCode
476
476
  }
477
477
  throw newErr
478
478
  }
@@ -319,7 +319,7 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
319
319
  } catch (err) {
320
320
  const newErr = new Error(`${action} error: ${err.message}`)
321
321
  if (err.message && err.message.includes('duplicate key')) {
322
- newErr.name = 'uniqueness' // maps to scimType error handling
322
+ newErr.name += '#409' // customErrorCode
323
323
  }
324
324
  throw newErr
325
325
  }
@@ -627,7 +627,7 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
627
627
  } catch (err) {
628
628
  const newErr = new Error(`${action} error: ${err.message}`)
629
629
  if (err.message && err.message.includes('duplicate key')) {
630
- newErr.name = 'uniqueness' // maps to scimType error handling
630
+ newErr.name += '#409' // customErrorCode
631
631
  }
632
632
  throw newErr
633
633
  }
@@ -330,35 +330,11 @@ const ScimGateway = function () {
330
330
  if (authType === 'Basic') [userName] = (Buffer.from(authToken, 'base64').toString() || '').split(':')
331
331
  if (!userName && authType === 'Bearer') userName = 'token'
332
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)
346
- if (Array.isArray(arrMatches) && arrMatches.length === 3) {
347
- pluginStatusCode = parseInt(arrMatches[2])
348
- }
349
- }
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
359
- }
333
+ if (ctx.response.status === 401 || ctx.response.status === 403) { // don't reveal original SCIM error message details related to access denied (e.g. using Auth PassThrough and customErrCode)
334
+ ctx.response.set('Content-Type', 'application/json; charset=utf-8')
335
+ ctx.response.body = { error: 'Access denied' }
336
+ res.body = ctx.response.body
360
337
  }
361
- // back to logResult...
362
338
  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
339
  } 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
340
  requestCounter += 1 // logged on exit (not win process termination)
@@ -634,7 +610,7 @@ const ScimGateway = function () {
634
610
  if (ipAllowListChecker(ctx.request.ipcli)) return resolve(next())
635
611
  logger.debug(`${gwName}[${pluginName}] client ip ${ctx.request.ipcli} not in ipAllowList`)
636
612
  ctx.status = 401
637
- ctx.body = 'Access denied'
613
+ ctx.body = { error: 'Access denied' }
638
614
  resolve(ctx)
639
615
  })
640
616
  }
@@ -806,9 +782,10 @@ const ScimGateway = function () {
806
782
  const tx = scimDef.Schemas.Resources.find(el => el.name === schemaName)
807
783
  if (!tx) {
808
784
  ctx.status = 404
809
- let err = new Error(`Schema '${schemaName}' not found`)
810
- err = jsonErr(config.scim.version, pluginName, ctx.status, err)
811
- ctx.body = err
785
+ const err = new Error(`Schema '${schemaName}' not found`)
786
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
787
+ if (customErrorCode) ctx.status = customErrorCode
788
+ ctx.body = e
812
789
  } else {
813
790
  ctx.body = tx
814
791
  }
@@ -855,9 +832,10 @@ const ScimGateway = function () {
855
832
 
856
833
  if (scimdata.Resources.length !== 1) {
857
834
  ctx.status = 404
858
- let err = new Error(`${handle.description} ${getObj.value} not found`)
859
- err = jsonErr(config.scim.version, pluginName, ctx.status, err)
860
- ctx.body = err
835
+ const err = new Error(`${handle.description} ${getObj.value} not found`)
836
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
837
+ if (customErrorCode) ctx.status = customErrorCode
838
+ ctx.body = e
861
839
  return
862
840
  }
863
841
 
@@ -898,7 +876,8 @@ const ScimGateway = function () {
898
876
  ctx.body = scimdata
899
877
  } catch (err) {
900
878
  ctx.status = 404
901
- const e = jsonErr(config.scim.version, pluginName, ctx.status, err)
879
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
880
+ if (customErrorCode) ctx.status = customErrorCode
902
881
  ctx.body = e
903
882
  }
904
883
  })
@@ -961,12 +940,10 @@ const ScimGateway = function () {
961
940
  // err.name = 'invalidFilter'
962
941
  }
963
942
  if (err) {
964
- let scimType
965
- if (err.name && isScimv2) {
966
- scimType = err.name
967
- ctx.status = 400
968
- } else ctx.status = 500
969
- const e = jsonErr(config.scim.version, pluginName, ctx.status, err, scimType)
943
+ if (isScimv2) ctx.status = 400
944
+ else ctx.status = 500
945
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
946
+ if (customErrorCode) ctx.status = customErrorCode
970
947
  ctx.body = e
971
948
  return
972
949
  }
@@ -1104,12 +1081,10 @@ const ScimGateway = function () {
1104
1081
 
1105
1082
  ctx.body = scimdata
1106
1083
  } catch (err) {
1107
- let scimType
1108
- if (err.name && isScimv2) {
1109
- scimType = err.name // e.g. invalidFilter
1110
- ctx.status = 400
1111
- } else ctx.status = 500
1112
- const e = jsonErr(config.scim.version, pluginName, ctx.status, err, scimType)
1084
+ if (isScimv2) ctx.status = 400
1085
+ else ctx.status = 500
1086
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1087
+ if (customErrorCode) ctx.status = customErrorCode
1113
1088
  ctx.body = e
1114
1089
  }
1115
1090
  })
@@ -1139,21 +1114,24 @@ const ScimGateway = function () {
1139
1114
  const strBody = JSON.stringify(jsonBody)
1140
1115
  if (strBody === '{}') {
1141
1116
  ctx.status = 500
1142
- let err = new Error('Not accepting empty or none JSON formatted POST requests')
1143
- err = jsonErr(config.scim.version, pluginName, ctx.status, err)
1144
- ctx.body = err
1117
+ const err = new Error('Not accepting empty or none JSON formatted POST requests')
1118
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1119
+ if (customErrorCode) ctx.status = customErrorCode
1120
+ ctx.body = e
1145
1121
  return
1146
1122
  } else if (handle.createMethod === 'createUser' && !jsonBody.userName && !jsonBody.externalId) {
1147
1123
  ctx.status = 500
1148
- let err = new Error('userName or externalId is mandatory')
1149
- err = jsonErr(config.scim.version, pluginName, ctx.status, err)
1150
- ctx.body = err
1124
+ const err = new Error('userName or externalId is mandatory')
1125
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1126
+ if (customErrorCode) ctx.status = customErrorCode
1127
+ ctx.body = e
1151
1128
  return
1152
1129
  } else if (handle.createMethod === 'createGroup' && !jsonBody.displayName && !jsonBody.externalId) {
1153
1130
  ctx.status = 500
1154
- let err = new Error('displayName or externalId is mandatory')
1155
- err = jsonErr(config.scim.version, pluginName, ctx.status, err)
1156
- ctx.body = err
1131
+ const err = new Error('displayName or externalId is mandatory')
1132
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1133
+ if (customErrorCode) ctx.status = customErrorCode
1134
+ ctx.body = e
1157
1135
  return
1158
1136
  }
1159
1137
 
@@ -1163,7 +1141,8 @@ const ScimGateway = function () {
1163
1141
  logger.debug(`${gwName}[${pluginName}] convertedBody=${JSON.stringify(scimdata)}`)
1164
1142
  if (err) {
1165
1143
  ctx.status = 500
1166
- const e = jsonErr(config.scim.version, pluginName, ctx.status, err)
1144
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1145
+ if (customErrorCode) ctx.status = customErrorCode
1167
1146
  ctx.body = e
1168
1147
  return
1169
1148
  }
@@ -1208,16 +1187,10 @@ const ScimGateway = function () {
1208
1187
  ctx.status = 201
1209
1188
  ctx.body = jsonBody
1210
1189
  } catch (err) {
1211
- let scimType
1212
- if (err.name) {
1213
- scimType = err.name
1214
- if (err.name === 'uniqueness') ctx.status = 409 // DuplicateKey
1215
- else {
1216
- if (isScimv2) ctx.status = 400
1217
- else ctx.status = 500
1218
- }
1219
- } else ctx.status = 500
1220
- const e = jsonErr(config.scim.version, pluginName, ctx.status, err, scimType)
1190
+ if (isScimv2) ctx.status = 400
1191
+ else ctx.status = 500
1192
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1193
+ if (customErrorCode) ctx.status = customErrorCode
1221
1194
  ctx.body = e
1222
1195
  }
1223
1196
  }) // post
@@ -1248,7 +1221,8 @@ const ScimGateway = function () {
1248
1221
  ctx.status = 204
1249
1222
  } catch (err) {
1250
1223
  ctx.status = 500
1251
- const e = jsonErr(config.scim.version, pluginName, ctx.status, err)
1224
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1225
+ if (customErrorCode) ctx.status = customErrorCode
1252
1226
  ctx.body = e
1253
1227
  }
1254
1228
  }) // delete
@@ -1289,9 +1263,10 @@ const ScimGateway = function () {
1289
1263
  const strBody = JSON.stringify(jsonBody)
1290
1264
  if (strBody === '{}') {
1291
1265
  ctx.status = 500
1292
- let err = new Error('Not accepting empty or none JSON formatted PATCH request')
1293
- err = jsonErr(config.scim.version, pluginName, ctx.status, err)
1294
- ctx.body = err
1266
+ const err = new Error('Not accepting empty or none JSON formatted PATCH request')
1267
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1268
+ if (customErrorCode) ctx.status = customErrorCode
1269
+ ctx.body = e
1295
1270
  } else {
1296
1271
  logger.debug(`${gwName}[${pluginName}] [Modify ${handle.description}] id=${id}`)
1297
1272
  let scimdata, err
@@ -1300,7 +1275,8 @@ const ScimGateway = function () {
1300
1275
  logger.debug(`${gwName}[${pluginName}] convertedBody=${JSON.stringify(scimdata)}`)
1301
1276
  if (err) {
1302
1277
  ctx.status = 500
1303
- const e = jsonErr(config.scim.version, pluginName, ctx.status, err)
1278
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1279
+ if (customErrorCode) ctx.status = customErrorCode
1304
1280
  ctx.body = e
1305
1281
  return
1306
1282
  }
@@ -1340,7 +1316,8 @@ const ScimGateway = function () {
1340
1316
  ctx.body = scimdata
1341
1317
  } catch (err) {
1342
1318
  ctx.status = 500
1343
- const e = jsonErr(config.scim.version, pluginName, ctx.status, err)
1319
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1320
+ if (customErrorCode) ctx.status = customErrorCode
1344
1321
  ctx.body = e
1345
1322
  }
1346
1323
  }
@@ -1360,9 +1337,10 @@ const ScimGateway = function () {
1360
1337
  const strBody = JSON.stringify(jsonBody)
1361
1338
  if (strBody === '{}') {
1362
1339
  ctx.status = 500
1363
- let err = new Error('Not accepting empty or none JSON formatted PUT requests')
1364
- err = jsonErr(config.scim.version, pluginName, ctx.status, err)
1365
- ctx.body = err
1340
+ const err = new Error('Not accepting empty or none JSON formatted PUT requests')
1341
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1342
+ if (customErrorCode) ctx.status = customErrorCode
1343
+ ctx.body = e
1366
1344
  } else {
1367
1345
  logger.debug(`${gwName}[${pluginName}] PUT ${ctx.originalUrl} body=${strBody}`)
1368
1346
  try {
@@ -1380,9 +1358,10 @@ const ScimGateway = function () {
1380
1358
 
1381
1359
  if (typeof (currentObj) !== 'object' || Object.keys(currentObj).length === 0) {
1382
1360
  ctx.status = 404
1383
- let err = new Error(`put using method ${handle.getMethod} error: ${handle.description.toLowerCase()} id=${id} does not exist`)
1384
- err = jsonErr(config.scim.version, pluginName, ctx.status, err)
1385
- ctx.body = err
1361
+ const err = new Error(`put using method ${handle.getMethod} error: ${handle.description.toLowerCase()} id=${id} does not exist`)
1362
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1363
+ if (customErrorCode) ctx.status = customErrorCode
1364
+ ctx.body = e
1386
1365
  return
1387
1366
  }
1388
1367
 
@@ -1575,7 +1554,8 @@ const ScimGateway = function () {
1575
1554
  ctx.body = scimdata
1576
1555
  } catch (err) {
1577
1556
  ctx.status = 500
1578
- const e = jsonErr(config.scim.version, pluginName, ctx.status, err)
1557
+ const [e, customErrorCode] = jsonErr(config.scim.version, pluginName, ctx.status, err)
1558
+ if (customErrorCode) ctx.status = customErrorCode
1579
1559
  ctx.body = e
1580
1560
  }
1581
1561
  }
@@ -2976,10 +2956,29 @@ const clearObjectValues = (o, parent) => {
2976
2956
  //
2977
2957
  // SCIM error formatting
2978
2958
  //
2979
- const jsonErr = (scimVersion, pluginName, htmlErrCode, err, scimType) => {
2959
+ const jsonErr = (scimVersion, pluginName, htmlErrCode, err) => {
2980
2960
  let errJson = {}
2961
+ let customErrCode = null
2962
+ let scimType = 'invalidSyntax'
2981
2963
  let msg = `scimgateway[${pluginName}] `
2982
- err.constructor === Error ? msg += err.message : msg += err
2964
+ if (err.constructor === Error) {
2965
+ if (err.name) { // customErrCode can be set by including suffix "#<number>" e.g., "<scimType>#404"
2966
+ const arr = err.name.split('#')
2967
+ if (arr.length > 1 && !isNaN(arr[arr.length - 1])) {
2968
+ customErrCode = arr[arr.length - 1]
2969
+ const code = parseInt(customErrCode)
2970
+ if (code < 300 && code > 199) customErrCode = null
2971
+ arr.splice(-1)
2972
+ err.name = arr.join('#') // back to original having customErrCode removed
2973
+ } else if (err.name === 'uniqueness') customErrCode = '409' // legacy support
2974
+ scimType = err.name
2975
+ if (scimType === 'Error') scimType = 'invalidSyntax' // default err.name used
2976
+ if (customErrCode === 409) scimType = 'uniqueness'
2977
+ }
2978
+ msg += err.message
2979
+ } else {
2980
+ msg += err
2981
+ }
2983
2982
 
2984
2983
  if (scimVersion !== '2.0' && scimVersion !== 2) { // v1.1
2985
2984
  errJson =
@@ -2987,7 +2986,7 @@ const jsonErr = (scimVersion, pluginName, htmlErrCode, err, scimType) => {
2987
2986
  Errors: [
2988
2987
  {
2989
2988
  description: msg,
2990
- code: htmlErrCode
2989
+ code: customErrCode || htmlErrCode
2991
2990
  }
2992
2991
  ]
2993
2992
  }
@@ -2997,10 +2996,12 @@ const jsonErr = (scimVersion, pluginName, htmlErrCode, err, scimType) => {
2997
2996
  schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
2998
2997
  scimType: scimType,
2999
2998
  detail: msg,
3000
- status: htmlErrCode
2999
+ status: customErrCode || htmlErrCode
3001
3000
  }
3002
3001
  }
3003
- return errJson
3002
+
3003
+ if (customErrCode) customErrCode = parseInt(customErrCode)
3004
+ return [errJson, customErrCode]
3004
3005
  }
3005
3006
 
3006
3007
  //
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scimgateway",
3
- "version": "4.2.14",
3
+ "version": "4.2.15",
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",