scimgateway 4.2.6 → 4.2.8

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
@@ -64,9 +64,10 @@ Can be used as SCIM version-gateway e.g. 1.1=>2.0 or 2.0=>1.1
64
64
  Can be used to chain several SCIM Gateway's
65
65
 
66
66
 
67
- * **Forwardinc** (SOAP Webservice)
68
- Demonstrates user provisioning towards SOAP-Based endpoint
69
- Using endpoint Forwardinc that comes with Broadcom/CA IM SDK (SDKWS) - [wiki.ca.com](https://docops.ca.com/ca-identity-manager/12-6-8/EN/programming/connector-programming-reference/sdk-sample-connectors/sdkws-sdk-web-services-connector/sdkws-sample-connector-build-requirements "wiki.ca.com")
67
+ * **Soap** (SOAP Webservice)
68
+ Demonstrates user provisioning towards SOAP-Based endpoint
69
+ Excample WSDLs are included
70
+ Using endpoint "Forwardinc" as an example (comes with Symantec/Broadcom/CA IM SDK - SDKWS)
70
71
  Shows how to implement a highly configurable multi tenant or multi endpoint solution through `baseEntity` in URL
71
72
 
72
73
  * **MSSQL** (MSSQL Database)
@@ -188,7 +189,7 @@ When maintaining a set of modifications it useful to disable the postinstall ope
188
189
  const loki = require('./lib/plugin-loki')
189
190
  // const mongodb = require('./lib/plugin-mongodb')
190
191
  // const scim = require('./lib/plugin-scim')
191
- // const forwardinc = require('./lib/plugin-forwardinc')
192
+ // const soap = require('./lib/plugin-soap')
192
193
  // const mssql = require('./lib/plugin-mssql')
193
194
  // const saphana = require('./lib/plugin-saphana') // prereq: npm install hdb
194
195
  // const azureAD = require('./lib/plugin-azure-ad')
@@ -212,7 +213,8 @@ Below shows an example of config\plugin-saphana.json
212
213
  "version": "2.0",
213
214
  "customSchema": null,
214
215
  "skipTypeConvert" : false,
215
- "usePutSoftSync" : false
216
+ "usePutSoftSync" : false,
217
+ "usePutGroupMemberOfUser": false
216
218
  },
217
219
  "log": {
218
220
  "loglevel": {
@@ -342,7 +344,9 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
342
344
  ]
343
345
 
344
346
 
345
- - **scim.usePutSoftSync** - true or false, default false. `PUT /Users/bjensen` will replace the user bjensen with body content. If body contains groups, usePutSoftsync=true will prevent removing any existing groups that are not included in body.groups
347
+ - **scim.usePutSoftSync** - true or false, default false. `PUT /Users/bjensen` will replace the user bjensen with body content. If body contains groups, usePutSoftsync=true will prevent removing any existing groups that are not included in body.groups
348
+
349
+ - **scim."usePutGroupMemberOfUser** - true or false, default false. `PUT /Users/<user>` will replace the user with body content. If body contains groups and usePutGroupMemberOfUser=true, groups will be set on user object (groups are member of user) instead of default user member of groups
346
350
 
347
351
  - **log.loglevel.file** - off, error, info, or debug. Output to plugin-logfile e.g. `logs\plugin-saphana.log`
348
352
 
@@ -462,20 +466,20 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
462
466
  }
463
467
 
464
468
 
465
- secrets.json for plugin-forwardinc - example (dot notation):
469
+ secrets.json for plugin-soap - example (dot notation):
466
470
 
467
471
  {
468
- "plugin-forwardinc.scimgateway.auth.basic[0].username": "gwadmin",
469
- "plugin-forwardinc.scimgateway.auth.basic[0].password": "password",
470
- "plugin-forwardinc.endpoint.username": "superuser",
471
- "plugin-forwardinc.endpoint.password": "secret"
472
+ "plugin-soap.scimgateway.auth.basic[0].username": "gwadmin",
473
+ "plugin-soap.scimgateway.auth.basic[0].password": "password",
474
+ "plugin-soap.endpoint.username": "superuser",
475
+ "plugin-soap.endpoint.password": "secret"
472
476
  }
473
477
 
474
478
  - Custom schema attributes can be added by plugin configuration `scim.customSchema` having value set to filename of a JSON schema-file located in `<package-root>/config/schemas` e.g:
475
479
 
476
480
  "scim": {
477
481
  "version": "2.0",
478
- "customSchema": "plugin-forwardinc-schema.json"
482
+ "customSchema": "plugin-soap-schema.json"
479
483
  },
480
484
 
481
485
  JSON file have following syntax:
@@ -748,7 +752,7 @@ Username, password and port must correspond with plugin configuration file. For
748
752
  http://localhost:8880/client-a
749
753
  http://localhost:8880/client-b
750
754
 
751
- Each baseEntity should then be defined in the plugin configuration file with custom attributes needed. Please see examples in plugin-forwardinc.json
755
+ Each baseEntity should then be defined in the plugin configuration file with custom attributes needed. Please see examples in plugin-soap.json
752
756
 
753
757
  IM 12.6 SP7 (and above) also supports pagination for SCIM endpoint (data transferred in bulks - endpoint explore of users). Loki plugin supports pagination. Other plugin may ignore this setting.
754
758
 
@@ -965,7 +969,7 @@ For JavaScript coding editor you may use [Visual Studio Code](https://code.visua
965
969
 
966
970
  Preparation:
967
971
 
968
- * Copy "best matching" example plugin e.g. `lib\plugin-mssql.js` and `config\plugin-mssql.json` and rename both copies to your plugin name prefix e.g. plugin-mine.js and plugin-mine.json (for SOAP Webservice endpoint we might use plugin-forwardinc as a template)
972
+ * Copy "best matching" example plugin e.g. `lib\plugin-mssql.js` and `config\plugin-mssql.json` and rename both copies to your plugin name prefix e.g. plugin-mine.js and plugin-mine.json (for SOAP Webservice endpoint we might use plugin-soap as a template)
969
973
  * Edit plugin-mine.json and define a unique port number for the gateway setting
970
974
  * Edit index.js and add a new line for starting your plugin e.g. `let mine = require('./lib/plugin-mine');`
971
975
  * Start SCIM Gateway and verify. If using CA Provisioning you could setup a SCIM endpoint using the port number you defined
@@ -988,7 +992,7 @@ Please see plugin-saphana that do not use groups.
988
992
 
989
993
  Template used by CA Provisioning role should only include endpoint supported attributes defined in our plugin. Template should therefore have no links to global user for none supported attributes (e.g. remove %UT% from "Job Title" if our endpoint/code do not support title)
990
994
 
991
- CA Provisioning using default SCIM endpoint do not support SCIM Enterprise User Schema Extension (having attributes like employeeNumber, costCenter, organization, division, department and manager). If we need these or other attributes not found in CA Provisioning, we could define our own by using the free-text "type" definition in the multivalue entitlements or roles attribute. In the template entitlements definition, we could for example define type=Company and set value to %UCOMP%. Please see plugin-forwardinc.js using Company as a multivalue "type" definition.
995
+ CA Provisioning using default SCIM endpoint do not support SCIM Enterprise User Schema Extension (having attributes like employeeNumber, costCenter, organization, division, department and manager). If we need these or other attributes not found in CA Provisioning, we could define our own by using the free-text "type" definition in the multivalue entitlements or roles attribute. In the template entitlements definition, we could for example define type=Company and set value to %UCOMP%. Please see plugin-soap.js using Company as a multivalue "type" definition.
992
996
 
993
997
  Using CA Connector Xpress we could create a new SCIM endpoint type based on the original SCIM. We could then add/remove attributes and change from default assign "user to groups" to assign "groups to user". There are also other predefined endpoints based on the original SCIM. You may take a look at "ServiceNow - WSL7" and "Zendesk - WSL7".
994
998
 
@@ -1165,6 +1169,25 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1165
1169
 
1166
1170
  ## Change log
1167
1171
 
1172
+ ### v4.2.8
1173
+
1174
+ [Fixed]
1175
+
1176
+ - PUT did not allow group name to be modified
1177
+
1178
+ ### v4.2.7
1179
+
1180
+ [Added]
1181
+
1182
+ - new plugin configuration **scim.usePutGroupMemberOfUser** can be set to true or false, default false. `PUT /Users/<user>` will replace user with body content. If body contains groups and usePutGroupMemberOfUser=true, groups will be set on user object (groups are member of user) instead of default user member of groups
1183
+ - plugin-forwardinc renamed to plugin-soap
1184
+ - Dependencies bump
1185
+
1186
+ [Fixed]
1187
+
1188
+ - plugin-azure-ad fixed some issues introduced in v4.2.4
1189
+ - plugin-mongodb fixed some issues introduced in v4.2.4
1190
+
1168
1191
  ### v4.2.6
1169
1192
 
1170
1193
  [Fixed]
@@ -7,7 +7,8 @@
7
7
  "version": "2.0",
8
8
  "customSchema": null,
9
9
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -7,7 +7,8 @@
7
7
  "version": "1.1",
8
8
  "customSchema": null,
9
9
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -7,7 +7,8 @@
7
7
  "version": "2.0",
8
8
  "customSchema": null,
9
9
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -7,7 +7,8 @@
7
7
  "version": "2.0",
8
8
  "customSchema": null,
9
9
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -7,7 +7,8 @@
7
7
  "version": "2.0",
8
8
  "customSchema": null,
9
9
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -7,7 +7,8 @@
7
7
  "version": "2.0",
8
8
  "customSchema": null,
9
9
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -7,7 +7,8 @@
7
7
  "version": "2.0",
8
8
  "customSchema": null,
9
9
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -7,7 +7,8 @@
7
7
  "version": "2.0",
8
8
  "customSchema": null,
9
9
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -7,7 +7,8 @@
7
7
  "version": "2.0",
8
8
  "customSchema": null,
9
9
  "skipTypeConvert": false,
10
- "usePutSoftSync": false
10
+ "usePutSoftSync": false,
11
+ "usePutGroupMemberOfUser": false
11
12
  },
12
13
  "log": {
13
14
  "loglevel": {
@@ -156,4 +157,4 @@
156
157
  }
157
158
  }
158
159
  }
159
- }
160
+ }
package/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  const loki = require('./lib/plugin-loki')
13
13
  // const mongodb = require('./lib/plugin-mongodb')
14
14
  // const scim = require('./lib/plugin-scim')
15
- // const forwardinc = require('./lib/plugin-forwardinc')
15
+ // const soap = require('./lib/plugin-soap')
16
16
  // const mssql = require('./lib/plugin-mssql')
17
17
  // const saphana = require('./lib/plugin-saphana') // prereq: npm install hdb --save
18
18
  // const azureAD = require('./lib/plugin-azure-ad')
@@ -578,7 +578,9 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
578
578
  } else if (getObj.operator === 'eq' && getObj.attribute === 'members.value') {
579
579
  // mandatory - return all groups the user 'id' (getObj.value) is member of - correspond to getGroupMembers() in versions < 4.x.x
580
580
  // Resources = [{ id: <id-group>> , displayName: <displayName-group>, members [{value: <id-user>}] }]
581
- path = `/users/${getObj.value}/memberOf/microsoft.graph.group?$select=id,displayName&$expand=members($select=id,displayName)`
581
+ // not using below expand because Azure returns only a maximum of 20 items for the expanded relationship
582
+ // path = `/users/${getObj.value}/memberOf/microsoft.graph.group?$select=id,displayName&$expand=members($select=id,displayName)`
583
+ path = `/users/${getObj.value}/memberOf/microsoft.graph.group?$select=id,displayName`
582
584
  } else {
583
585
  // optional - simpel filtering
584
586
  throw new Error(`${action} error: not supporting simpel filtering: ${getObj.rawFilter}`)
@@ -633,6 +635,10 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
633
635
  }
634
636
  })
635
637
  delete response.body.value[i].members
638
+ } else if (getObj.operator === 'eq' && getObj.attribute === 'members.value') { // Not using expand-members. Only includes current user as member, but should have requested all...
639
+ members = [{
640
+ value: getObj.value
641
+ }]
636
642
  }
637
643
 
638
644
  const [scimObj] = scimgateway.endpointMapper('inbound', response.body.value[i], config.map.group) // endpoint => SCIM/CustomSCIM attribute standard
@@ -1174,9 +1180,9 @@ const getAccessToken = async (baseEntity, ctx) => {
1174
1180
  const clientIdentifier = getClientIdentifier(ctx)
1175
1181
  const d = new Date() / 1000 // seconds (unix time)
1176
1182
  if (_serviceClient[baseEntity] && _serviceClient[baseEntity][clientIdentifier] && _serviceClient[baseEntity][clientIdentifier].accessToken &&
1177
- (_serviceClient[baseEntity].accessToken.validTo >= d + 30)) { // avoid simultaneously token requests
1183
+ (_serviceClient[baseEntity][clientIdentifier].accessToken.validTo >= d + 30)) { // avoid simultaneously token requests
1178
1184
  lock.release()
1179
- return _serviceClient[baseEntity].accessToken
1185
+ return _serviceClient[baseEntity][clientIdentifier].accessToken
1180
1186
  }
1181
1187
 
1182
1188
  const action = 'getAccessToken'
@@ -48,15 +48,16 @@ scimgateway.authPassThroughAllowed = false // true enables auth passThrough (no
48
48
 
49
49
  const validFilterOperators = ['eq', 'ne', 'aeq', 'dteq', 'gt', 'gte', 'lt', 'lte', 'between', 'jgt', 'jgte', 'jlt', 'jlte', 'jbetween', 'regex', 'in', 'nin', 'keyin', 'nkeyin', 'definedin', 'undefinedin', 'contains', 'containsAny', 'type', 'finite', 'size', 'len', 'exists']
50
50
 
51
- if (!config.entity) throw new Error('error: configuration entity is missing')
52
- if (!scimgateway.authPassThroughAllowed) { // not using Auth PassThrough, loading db handler at startup using username/password from config
53
- for (const baseEntity in config.entity) {
54
- loadHandler(baseEntity)
55
- }
56
- }
57
-
58
51
  async function loadHandler (baseEntity, ctx) {
59
52
  const action = 'loadHander'
53
+
54
+ const clientIdentifier = getClientIdentifier(ctx)
55
+ if (config.entity[baseEntity].isLoaded) { // loadHandler only once
56
+ if (!clientIdentifier) return clientIdentifier // not using Auth PassThrough
57
+ if (config.entity[baseEntity][clientIdentifier]) return clientIdentifier // authenticated
58
+ throw new Error('{"error":"Access denied","statusCode":401}') // string: "statusCode":401 ensure gateway returns 401
59
+ }
60
+
60
61
  if (!config.entity[baseEntity].baseUrl) { // mongodb://host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] - e.g: mongodb://localhost:27017/db?tls=true&tlsInsecure=true
61
62
  throw new Error(`${action} error: configuration entity.${baseEntity}.baseUrl is missing`)
62
63
  }
@@ -102,6 +103,9 @@ async function loadHandler (baseEntity, ctx) {
102
103
  groups.createIndex({ id: 1 }, { unique: true })
103
104
  }
104
105
  } catch (error) {
106
+ if (clientIdentifier && error.message.includes('Authentication')) {
107
+ throw new Error('{"error":"Access denied","statusCode":401}') // string: "statusCode":401 ensure gateway returns 401
108
+ }
105
109
  throw new Error(`${action} error: failed to connect to database '${client.s.options.dbName}' - ${error.message}`)
106
110
  }
107
111
 
@@ -147,11 +151,12 @@ async function loadHandler (baseEntity, ctx) {
147
151
  }
148
152
  }
149
153
  }
150
- const clientIdentifier = getClientIdentifier(ctx)
151
154
  if (!config.entity[baseEntity][clientIdentifier]) config.entity[baseEntity][clientIdentifier] = {}
152
155
  config.entity[baseEntity][clientIdentifier].collection = {}
153
156
  config.entity[baseEntity][clientIdentifier].collection.users = users
154
157
  config.entity[baseEntity][clientIdentifier].collection.groups = groups
158
+ config.entity[baseEntity].isLoaded = true
159
+ return clientIdentifier
155
160
  }
156
161
 
157
162
  // =================================================
@@ -173,6 +178,8 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
173
178
  const action = 'getUsers'
174
179
  scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" getObj=${getObj ? JSON.stringify(getObj) : ''} attributes=${attributes}`)
175
180
 
181
+ const clientIdentifier = await loadHandler(baseEntity, ctx) // includes Auth PassThrough logic and loaded only once
182
+
176
183
  if (getObj.operator) { // convert to plugin supported syntax
177
184
  switch (getObj.operator) {
178
185
  case 'co':
@@ -203,10 +210,6 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
203
210
  }
204
211
  }
205
212
 
206
- const clientIdentifier = getClientIdentifier(ctx)
207
- if (ctx && !config.entity[baseEntity][clientIdentifier]) { // first (or previous failed) PassThrough attempt - have to load connection
208
- await loadHandler(baseEntity, ctx)
209
- }
210
213
  const users = config.entity[baseEntity][clientIdentifier].collection.users
211
214
  let findObj
212
215
 
@@ -275,6 +278,8 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
275
278
  const action = 'createUser'
276
279
  scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" userObj=${JSON.stringify(userObj)}`)
277
280
 
281
+ const clientIdentifier = await loadHandler(baseEntity, ctx) // includes Auth PassThrough logic and loaded only once
282
+
278
283
  const notValid = scimgateway.notValidAttributes(userObj, validScimAttr) // We should check for unsupported endpoint attributes
279
284
  if (notValid) {
280
285
  throw new Error(`${action} error: unsupported scim attributes: ${notValid} (supporting only these attributes: ${validScimAttr.toString()})`)
@@ -308,7 +313,7 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
308
313
  userObj = encodeDotDate(userObj)
309
314
 
310
315
  try {
311
- const users = config.entity[baseEntity].collection.users
316
+ const users = config.entity[baseEntity][clientIdentifier].collection.users
312
317
  await users.insertOne(userObj)
313
318
  return null
314
319
  } catch (err) {
@@ -327,7 +332,9 @@ scimgateway.deleteUser = async (baseEntity, id, ctx) => {
327
332
  const action = 'deleteUser'
328
333
  scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" id=${id}`)
329
334
 
330
- const users = config.entity[baseEntity].collection.users
335
+ const clientIdentifier = await loadHandler(baseEntity, ctx) // includes Auth PassThrough logic and loaded only once
336
+
337
+ const users = config.entity[baseEntity][clientIdentifier].collection.users
331
338
  try {
332
339
  /*
333
340
  const now = Date.now()
@@ -354,6 +361,8 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
354
361
  const action = 'modifyUser'
355
362
  scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" id=${id} attrObj=${JSON.stringify(attrObj)}`)
356
363
 
364
+ const clientIdentifier = await loadHandler(baseEntity, ctx) // includes Auth PassThrough logic and loaded only once
365
+
357
366
  const notValid = scimgateway.notValidAttributes(attrObj, validScimAttr) // We should check for unsupported endpoint attributes
358
367
  if (notValid) {
359
368
  throw new Error(`${action} error: unsupported scim attributes: ${notValid} (supporting only these attributes: ${validScimAttr.toString()})`)
@@ -363,7 +372,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
363
372
  let res
364
373
 
365
374
  try {
366
- const users = config.entity[baseEntity].collection.users
375
+ const users = config.entity[baseEntity][clientIdentifier].collection.users
367
376
  res = await users.find({ id }, { projection: { _id: 0 } }).toArray()
368
377
  if (res.length === 0) throw new Error('user does not exist')
369
378
  if (res.length > 1) throw new Error('user is not unique, more than one have been found')
@@ -474,7 +483,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
474
483
  userObj = encodeDotDate(userObj)
475
484
 
476
485
  try {
477
- const users = config.entity[baseEntity].collection.users
486
+ const users = config.entity[baseEntity][clientIdentifier].collection.users
478
487
  await users.replaceOne({ id: id }, userObj)
479
488
  return null
480
489
  } catch (err) {
@@ -501,6 +510,8 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
501
510
  const action = 'getGroups'
502
511
  scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" getObj=${getObj ? JSON.stringify(getObj) : ''} attributes=${attributes}`)
503
512
 
513
+ const clientIdentifier = await loadHandler(baseEntity, ctx) // includes Auth PassThrough logic and loaded only once
514
+
504
515
  if (getObj.operator) { // convert to plugin supported syntax
505
516
  switch (getObj.operator) {
506
517
  case 'co':
@@ -574,7 +585,7 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
574
585
 
575
586
  try {
576
587
  const projection = attributes.length > 0 ? getProjectionFromAttributes(attributes) : { _id: 0 }
577
- const groups = config.entity[baseEntity].collection.groups
588
+ const groups = config.entity[baseEntity][clientIdentifier].collection.groups
578
589
  const groupsArr = await groups.find(findObj, { projection: projection }).sort({ _id: 1 }).skip(getObj.startIndex - 1).limit(getObj.count).toArray()
579
590
  const totalResults = await groups.countDocuments(findObj, { projection: projection })
580
591
  const arr = groupsArr.map((obj) => {
@@ -595,6 +606,8 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
595
606
  const action = 'createGroup'
596
607
  scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" groupObj=${JSON.stringify(groupObj)}`)
597
608
 
609
+ const clientIdentifier = await loadHandler(baseEntity, ctx) // includes Auth PassThrough logic and loaded only once
610
+
598
611
  if (!groupObj.meta) {
599
612
  const now = Date.now()
600
613
  groupObj.meta = {
@@ -608,7 +621,7 @@ scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
608
621
  groupObj = encodeDotDate(groupObj)
609
622
 
610
623
  try {
611
- const groups = config.entity[baseEntity].collection.groups
624
+ const groups = config.entity[baseEntity][clientIdentifier].collection.groups
612
625
  await groups.insertOne(groupObj)
613
626
  return null
614
627
  } catch (err) {
@@ -627,7 +640,9 @@ scimgateway.deleteGroup = async (baseEntity, id, ctx) => {
627
640
  const action = 'deleteGroup'
628
641
  scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" id=${id}`)
629
642
 
630
- const groups = config.entity[baseEntity].collection.groups
643
+ const clientIdentifier = await loadHandler(baseEntity, ctx) // includes Auth PassThrough logic and loaded only once
644
+
645
+ const groups = config.entity[baseEntity][clientIdentifier].collection.groups
631
646
  try {
632
647
  /*
633
648
  const now = Date.now()
@@ -654,8 +669,10 @@ scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
654
669
  const action = 'modifyGroup'
655
670
  scimgateway.logger.debug(`${pluginName}[${baseEntity}] handling "${action}" id=${id} attrObj=${JSON.stringify(attrObj)}`)
656
671
 
657
- const users = config.entity[baseEntity].collection.users
658
- const groups = config.entity[baseEntity].collection.groups
672
+ const clientIdentifier = await loadHandler(baseEntity, ctx) // includes Auth PassThrough logic and loaded only once
673
+
674
+ const users = config.entity[baseEntity][clientIdentifier].collection.users
675
+ const groups = config.entity[baseEntity][clientIdentifier].collection.groups
659
676
  let res
660
677
  let isModified = false
661
678
 
@@ -851,3 +868,11 @@ process.on('SIGINT', () => {
851
868
  }
852
869
  }
853
870
  })
871
+
872
+ // connect MongoDb and load users/groups
873
+ if (!config.entity) throw new Error('error: configuration entity is missing')
874
+ if (!scimgateway.authPassThroughAllowed) { // not using Auth PassThrough, loading db handler at startup using username/password from config
875
+ for (const baseEntity in config.entity) {
876
+ loadHandler(baseEntity)
877
+ }
878
+ }
@@ -1,5 +1,5 @@
1
1
  // =================================================================================
2
- // File: plugin-restful.js
2
+ // File: plugin-scim.js
3
3
  //
4
4
  // Author: Jarle Elshaug
5
5
  //
@@ -1,5 +1,5 @@
1
1
  // =================================================================================
2
- // File: plugin-forwardinc.js
2
+ // File: plugin-soap.js
3
3
  //
4
4
  // Author: Jarle Elshaug
5
5
  //
@@ -29,7 +29,7 @@ if (!fsExistsSync('../../lib')) fs.mkdirSync('../../lib')
29
29
 
30
30
  if (!fsExistsSync('../../config/plugin-loki.json')) fs.writeFileSync('../../config/plugin-loki.json', fs.readFileSync('./config/plugin-loki.json'))
31
31
  if (!fsExistsSync('../../config/plugin-scim.json')) fs.writeFileSync('../../config/plugin-scim.json', fs.readFileSync('./config/plugin-scim.json'))
32
- if (!fsExistsSync('../../config/plugin-forwardinc.json')) fs.writeFileSync('../../config/plugin-forwardinc.json', fs.readFileSync('./config/plugin-forwardinc.json'))
32
+ if (!fsExistsSync('../../config/plugin-soap.json')) fs.writeFileSync('../../config/plugin-soap.json', fs.readFileSync('./config/plugin-soap.json'))
33
33
  if (!fsExistsSync('../../config/plugin-mssql.json')) fs.writeFileSync('../../config/plugin-mssql.json', fs.readFileSync('./config/plugin-mssql.json'))
34
34
  if (!fsExistsSync('../../config/plugin-saphana.json')) fs.writeFileSync('../../config/plugin-saphana.json', fs.readFileSync('./config/plugin-saphana.json'))
35
35
  if (!fsExistsSync('../../config/plugin-api.json')) fs.writeFileSync('../../config/plugin-api.json', fs.readFileSync('./config/plugin-api.json'))
@@ -39,7 +39,7 @@ if (!fsExistsSync('../../config/plugin-mongodb.json')) fs.writeFileSync('../../c
39
39
 
40
40
  fs.writeFileSync('../../lib/plugin-loki.js', fs.readFileSync('./lib/plugin-loki.js'))
41
41
  fs.writeFileSync('../../lib/plugin-scim.js', fs.readFileSync('./lib/plugin-scim.js'))
42
- fs.writeFileSync('../../lib/plugin-forwardinc.js', fs.readFileSync('./lib/plugin-forwardinc.js'))
42
+ fs.writeFileSync('../../lib/plugin-soap.js', fs.readFileSync('./lib/plugin-soap.js'))
43
43
  fs.writeFileSync('../../lib/plugin-mssql.js', fs.readFileSync('./lib/plugin-mssql.js'))
44
44
  fs.writeFileSync('../../lib/plugin-saphana.js', fs.readFileSync('./lib/plugin-saphana.js'))
45
45
  fs.writeFileSync('../../lib/plugin-api.js', fs.readFileSync('./lib/plugin-api.js'))
@@ -1280,6 +1280,7 @@ const ScimGateway = function () {
1280
1280
  ctx.body = e
1281
1281
  return
1282
1282
  }
1283
+ delete scimdata.id
1283
1284
  logger.debug(`${gwName}[${pluginName}] calling "${handle.modifyMethod}" and awaiting result`)
1284
1285
  try {
1285
1286
  await this[handle.modifyMethod](ctx.params.baseEntity, id, scimdata, ctx.ctxCopy)
@@ -1290,22 +1291,26 @@ const ScimGateway = function () {
1290
1291
  }
1291
1292
  logger.debug(`${gwName}[${pluginName}] calling "${handle.getMethod}" and awaiting result`)
1292
1293
  const res = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'id', operator: 'eq', value: id }, ctx.query.attributes ? ctx.query.attributes.split(',').map(item => item.trim()) : [], ctx.ctxCopy)
1294
+
1293
1295
  scimdata = {
1294
- Resources: [],
1295
- totalResults: null
1296
+ Resources: []
1296
1297
  }
1297
1298
  if (res) {
1298
1299
  if (res.Resources && Array.isArray(res.Resources)) {
1299
1300
  scimdata.Resources = res.Resources
1300
- scimdata.totalResults = res.totalResults
1301
1301
  } else if (Array.isArray(res)) scimdata.Resources = res
1302
- else if (typeof (res) === 'object' && Object.keys(res).length > 0) scimdata.Resources[0] = res
1302
+ else if (typeof (res) === 'object') scimdata.Resources[0] = res
1303
+ else scimdata.Resources = []
1304
+ } else scimdata.Resources = []
1305
+ if (scimdata.Resources.length === 0 || scimdata.Resources.length > 1) {
1306
+ ctx.status = 204
1307
+ return
1303
1308
  }
1304
- if (scimdata.Resources.length !== 1) throw new Error(`using ${handle.getMethod} to retrive user ${id} after ${handle.modifyMethod} but response did not include user object`)
1309
+
1305
1310
  const location = ctx.origin + ctx.path
1306
1311
  ctx.set('Location', location)
1307
- scimdata.Resources[0] = addPrimaryAttrs(scimdata.Resources[0])
1308
- scimdata = utils.stripObj(scimdata.Resources[0], ctx.query.attributes, ctx.query.excludedAttributes)
1312
+ const userObj = addPrimaryAttrs(scimdata.Resources[0])
1313
+ scimdata = utils.stripObj(userObj, ctx.query.attributes, ctx.query.excludedAttributes)
1309
1314
  scimdata = addSchemas(scimdata, handle.description, isScimv2)
1310
1315
  ctx.status = 200
1311
1316
  ctx.body = scimdata
@@ -1362,17 +1367,27 @@ const ScimGateway = function () {
1362
1367
  }
1363
1368
  }
1364
1369
  }
1370
+ if (config.scim.usePutGroupMemberOfUser) { // group member of user instead of default user member of group
1371
+ if (clearedObj.groups && Array.isArray(clearedObj.groups)) {
1372
+ for (let i = 0; i < clearedObj.groups.length; i++) {
1373
+ if (clearedObj.groups[i].operation && clearedObj.groups[i].operation === 'delete') {
1374
+ clearedObj.groups.splice(i, 1) // delete
1375
+ i -= 1
1376
+ }
1377
+ }
1378
+ }
1379
+ }
1365
1380
  }
1366
1381
 
1367
1382
  // merge cleared object with the new
1368
1383
  const newObj = utils.extendObj(clearedObj, jsonBody)
1369
1384
  delete newObj.id
1370
- delete newObj.userName
1371
- delete newObj.externalId
1372
- delete newObj.groups // do not support "group member of users"
1373
1385
  delete newObj.schemas
1374
1386
  delete newObj.meta
1375
- if (handle.getMethod === handler.groups.getMethod) delete newObj.displayName
1387
+ if (!config.scim.usePutGroupMemberOfUser) delete newObj.groups
1388
+ // not allowing userName/displayName set to blank
1389
+ if (!newObj.userName) delete newObj.userName
1390
+ if (!newObj.displayName) delete newObj.displayName
1376
1391
 
1377
1392
  let [scimdata, err] = ScimGateway.prototype.convertedScim(newObj)
1378
1393
  if (err) throw err
@@ -1382,125 +1397,139 @@ const ScimGateway = function () {
1382
1397
  await this[handle.modifyMethod](ctx.params.baseEntity, id, scimdata, ctx.ctxCopy)
1383
1398
 
1384
1399
  // add/remove groups
1385
- if (jsonBody.groups && Array.isArray(jsonBody.groups)) { // only if groups included, { "groups": [] } will remove all existing
1386
- if (typeof this[handler.groups.getMethod] !== 'function' || typeof this[handler.groups.modifyMethod] !== 'function') {
1387
- throw new Error('replaceUser error: put operation can not be fully completed for the user`s groups, methods like getGroups() and modifyGroup() are not implemented')
1388
- }
1389
- let currentGroups
1390
- if (currentObj.groups && Array.isArray(currentObj.groups)) currentGroups = currentObj.groups
1391
- else { // try to get current groups the standard way
1392
- let res
1393
- try {
1394
- res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }, ['id', 'displayName'], ctx.ctxCopy)
1395
- } catch (err) {} // method may be implemented but throwing error like groups not supported/implemented
1396
- currentGroups = []
1397
- if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length > 0) {
1398
- for (let i = 0; i < res.Resources.length; i++) {
1399
- if (!res.Resources[i].id) continue
1400
- const el = {}
1401
- el.value = res.Resources[i].id
1402
- if (res.Resources[i].displayName) el.display = res.Resources[i].displayName
1403
- currentGroups.push(el) // { "value": "Admins", "display": "Admins"}
1404
- }
1400
+ if (!config.scim.usePutGroupMemberOfUser) { // default user member of group
1401
+ if (jsonBody.groups && Array.isArray(jsonBody.groups)) { // only if groups included, { "groups": [] } will remove all existing
1402
+ if (typeof this[handler.groups.getMethod] !== 'function' || typeof this[handler.groups.modifyMethod] !== 'function') {
1403
+ throw new Error('replaceUser error: put operation can not be fully completed for the user`s groups, methods like getGroups() and modifyGroup() are not implemented')
1405
1404
  }
1406
- }
1407
- currentGroups = currentGroups.map((el) => {
1408
- if (el.value) {
1409
- el.value = decodeURIComponent(el.value)
1405
+ let currentGroups
1406
+ if (currentObj.groups && Array.isArray(currentObj.groups)) currentGroups = currentObj.groups
1407
+ else { // try to get current groups the standard way
1408
+ let res
1409
+ try {
1410
+ res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }, ['id', 'displayName'], ctx.ctxCopy)
1411
+ } catch (err) {} // method may be implemented but throwing error like groups not supported/implemented
1412
+ currentGroups = []
1413
+ if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length > 0) {
1414
+ for (let i = 0; i < res.Resources.length; i++) {
1415
+ if (!res.Resources[i].id) continue
1416
+ const el = {}
1417
+ el.value = res.Resources[i].id
1418
+ if (res.Resources[i].displayName) el.display = res.Resources[i].displayName
1419
+ currentGroups.push(el) // { "value": "Admins", "display": "Admins"}
1420
+ }
1421
+ }
1410
1422
  }
1411
- return el
1412
- })
1423
+ currentGroups = currentGroups.map((el) => {
1424
+ if (el.value) {
1425
+ el.value = decodeURIComponent(el.value)
1426
+ }
1427
+ return el
1428
+ })
1413
1429
 
1414
- const addGrps = []
1415
- const removeGrps = []
1416
- // add
1417
- for (let i = 0; i < jsonBody.groups.length; i++) {
1418
- if (!jsonBody.groups[i].value) continue
1419
- jsonBody.groups[i].value = decodeURIComponent(jsonBody.groups[i].value)
1420
- let found = false
1421
- for (let j = 0; j < currentGroups.length; j++) {
1422
- if (jsonBody.groups[i].value === currentGroups[j].value) {
1423
- found = true
1424
- break
1430
+ const addGrps = []
1431
+ const removeGrps = []
1432
+ // add
1433
+ for (let i = 0; i < jsonBody.groups.length; i++) {
1434
+ if (!jsonBody.groups[i].value) continue
1435
+ jsonBody.groups[i].value = decodeURIComponent(jsonBody.groups[i].value)
1436
+ let found = false
1437
+ for (let j = 0; j < currentGroups.length; j++) {
1438
+ if (jsonBody.groups[i].value === currentGroups[j].value) {
1439
+ found = true
1440
+ break
1441
+ }
1425
1442
  }
1443
+ if (!found && jsonBody.groups[i].value) addGrps.push(jsonBody.groups[i].value)
1426
1444
  }
1427
- if (!found && jsonBody.groups[i].value) addGrps.push(jsonBody.groups[i].value)
1428
- }
1429
- // remove
1430
- for (let i = 0; i < currentGroups.length; i++) {
1431
- let found = false
1432
- for (let j = 0; j < jsonBody.groups.length; j++) {
1433
- if (!jsonBody.groups[j].value) continue
1434
- jsonBody.groups[j].value = decodeURIComponent(jsonBody.groups[j].value)
1435
- if (currentGroups[i].value === jsonBody.groups[j].value) {
1436
- found = true
1437
- break
1445
+ // remove
1446
+ for (let i = 0; i < currentGroups.length; i++) {
1447
+ let found = false
1448
+ for (let j = 0; j < jsonBody.groups.length; j++) {
1449
+ if (!jsonBody.groups[j].value) continue
1450
+ jsonBody.groups[j].value = decodeURIComponent(jsonBody.groups[j].value)
1451
+ if (currentGroups[i].value === jsonBody.groups[j].value) {
1452
+ found = true
1453
+ break
1454
+ }
1438
1455
  }
1456
+ if (!found && currentGroups[i].value) removeGrps.push(currentGroups[i].value)
1439
1457
  }
1440
- if (!found && currentGroups[i].value) removeGrps.push(currentGroups[i].value)
1441
- }
1442
1458
 
1443
- const addGroups = async (grp) => {
1444
- const obj = { members: [{ value: id }] }
1445
- return await this[handler.groups.modifyMethod](ctx.params.baseEntity, grp, obj, ctx.ctxCopy)
1446
- }
1459
+ const addGroups = async (grp) => {
1460
+ const obj = { members: [{ value: id }] }
1461
+ return await this[handler.groups.modifyMethod](ctx.params.baseEntity, grp, obj, ctx.ctxCopy)
1462
+ }
1447
1463
 
1448
- const removeGroups = async (grp) => {
1449
- const obj = { members: [{ operation: 'delete', value: id }] }
1450
- return await this[handler.groups.modifyMethod](ctx.params.baseEntity, grp, obj, ctx.ctxCopy)
1451
- }
1464
+ const removeGroups = async (grp) => {
1465
+ const obj = { members: [{ operation: 'delete', value: id }] }
1466
+ return await this[handler.groups.modifyMethod](ctx.params.baseEntity, grp, obj, ctx.ctxCopy)
1467
+ }
1468
+
1469
+ let errRemove
1470
+ if (!config.scim.usePutSoftSync) { // default will remove any existing groups not included, usePutSoftSync=true prevents removing existing groups (only add groups)
1471
+ await Promise.all(removeGrps.map((grp) => removeGroups(grp)))
1472
+ .then()
1473
+ .catch((err) => {
1474
+ errRemove = err
1475
+ })
1476
+ }
1452
1477
 
1453
- let errRemove
1454
- if (!config.scim.usePutSoftSync) { // default will remove any existing groups not included, usePutSoftSync=true prevents removing existing groups (only add groups)
1455
- await Promise.all(removeGrps.map((grp) => removeGroups(grp)))
1478
+ let errAdd
1479
+ await Promise.all(addGrps.map((grp) => addGroups(grp)))
1456
1480
  .then()
1457
1481
  .catch((err) => {
1458
- errRemove = err
1482
+ errAdd = err
1459
1483
  })
1460
- }
1461
-
1462
- let errAdd
1463
- await Promise.all(addGrps.map((grp) => addGroups(grp)))
1464
- .then()
1465
- .catch((err) => {
1466
- errAdd = err
1467
- })
1468
1484
 
1469
- let errMsg = ''
1470
- if (errRemove) errMsg = `removeGroups error: ${errRemove.message}`
1471
- if (errAdd) errMsg += `${errMsg ? ' ' : ''}addGroups error: ${errAdd.message}`
1472
- if (errMsg) throw new Error(errMsg)
1485
+ let errMsg = ''
1486
+ if (errRemove) errMsg = `removeGroups error: ${errRemove.message}`
1487
+ if (errAdd) errMsg += `${errMsg ? ' ' : ''}addGroups error: ${errAdd.message}`
1488
+ if (errMsg) throw new Error(errMsg)
1489
+ }
1473
1490
  }
1474
1491
 
1475
1492
  // get updated object
1476
1493
  logger.debug(`${gwName}[${pluginName}] calling "${handle.getMethod}" and awaiting result`)
1477
1494
  res = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'id', operator: 'eq', value: id }, [], ctx.ctxCopy)
1478
1495
 
1479
- scimdata = {}
1480
- if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length === 1) scimdata = res.Resources[0]
1481
- else if (Array.isArray(res) && res.length === 1) scimdata = res[0]
1482
- else if (res && typeof (res) === 'object' && Object.keys(res).length > 0) scimdata = res
1483
- else throw Error(`put using method ${handle.getMethod} got unexpected response: ${JSON.stringify(res)}`)
1496
+ scimdata = {
1497
+ Resources: []
1498
+ }
1499
+ if (res) {
1500
+ if (res.Resources && Array.isArray(res.Resources)) {
1501
+ scimdata.Resources = res.Resources
1502
+ } else if (Array.isArray(res)) scimdata.Resources = res
1503
+ else if (typeof (res) === 'object') scimdata.Resources[0] = res
1504
+ else scimdata.Resources = []
1505
+ } else scimdata.Resources = []
1506
+ if (scimdata.Resources.length === 0 || scimdata.Resources.length > 1) {
1507
+ ctx.status = 204
1508
+ return
1509
+ }
1510
+ scimdata = scimdata.Resources[0]
1484
1511
 
1485
1512
  // include groups
1486
- if (handle.getMethod === handler.users.getMethod && typeof this[handler.groups.getMethod] === 'function') {
1487
- logger.debug(`${gwName}[${pluginName}] calling "${handler.groups.getMethod}" and awaiting result`)
1488
- const res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: id }, ['id', 'displayName'], ctx.ctxCopy)
1489
- let grps = []
1490
- if (res && res.Resources && Array.isArray(res.Resources)) grps = res.Resources
1491
- else if (Array.isArray(res)) grps = res
1492
- else if (res && typeof (res) === 'object' && Object.keys(res).length > 0) grps = [res]
1493
- else throw Error(`put using method ${handler.groups.getMethod} got unexpected response: ${JSON.stringify(res)}`)
1494
-
1495
- if (grps.length > 0) {
1496
- scimdata.groups = []
1497
- for (let i = 0; i < grps.length; i++) {
1498
- const el = {}
1499
- el.value = grps[i].id
1500
- if (grps[i].displayName) el.display = grps[i].displayName
1501
- if (isScimv2) el.type = 'direct'
1502
- else el.type = { value: 'direct' }
1503
- if (el.value) scimdata.groups.push(el) // { "value": "Admins", "display": "Admins", "type": "direct"}
1513
+ if (!config.scim.usePutGroupMemberOfUser) { // default user member of group
1514
+ if (handle.getMethod === handler.users.getMethod && typeof this[handler.groups.getMethod] === 'function') {
1515
+ logger.debug(`${gwName}[${pluginName}] calling "${handler.groups.getMethod}" and awaiting result`)
1516
+ const res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: id }, ['id', 'displayName'], ctx.ctxCopy)
1517
+ let grps = []
1518
+ if (res && res.Resources && Array.isArray(res.Resources)) grps = res.Resources
1519
+ else if (Array.isArray(res)) grps = res
1520
+ else if (res && typeof (res) === 'object' && Object.keys(res).length > 0) grps = [res]
1521
+ else throw Error(`put using method ${handler.groups.getMethod} - got unexpected response: ${JSON.stringify(res)}`)
1522
+
1523
+ if (grps.length > 0) {
1524
+ scimdata.groups = []
1525
+ for (let i = 0; i < grps.length; i++) {
1526
+ const el = {}
1527
+ el.value = grps[i].id
1528
+ if (grps[i].displayName) el.display = grps[i].displayName
1529
+ if (isScimv2) el.type = 'direct'
1530
+ else el.type = { value: 'direct' }
1531
+ if (el.value) scimdata.groups.push(el) // { "value": "Admins", "display": "Admins", "type": "direct"}
1532
+ }
1504
1533
  }
1505
1534
  }
1506
1535
  }
package/package.json CHANGED
@@ -1,60 +1,60 @@
1
- {
2
- "name": "scimgateway",
3
- "version": "4.2.6",
4
- "description": "Using SCIM protocol as a gateway for user provisioning to other endpoints",
5
- "author": "Jarle Elshaug <jarle.elshaug@gmail.com> (https://elshaug.xyz)",
6
- "homepage": "https://elshaug.xyz",
7
- "license": "MIT",
8
- "main": "lib/scimgateway.js",
9
- "scripts": {
10
- "postinstall": "node lib/postinstall.js",
11
- "start": "node index.js",
12
- "test": "mocha -R spec ./test"
13
- },
14
- "bin": {
15
- "scimgateway": "./index.js"
16
- },
17
- "repository": {
18
- "type": "git",
19
- "url": "https://github.com/jelhub/scimgateway.git"
20
- },
21
- "keywords": [
22
- "scim",
23
- "gateway",
24
- "proxy",
25
- "azure",
26
- "identity",
27
- "manager",
28
- "provisioning",
29
- "iga"
30
- ],
31
- "engines": {
32
- "node": ">=14.0.0"
33
- },
34
- "dependencies": {
35
- "@godaddy/terminus": "^4.11.2",
36
- "callsite": "^1.0.0",
37
- "dot-object": "^2.1.4",
38
- "https-proxy-agent": "^5.0.1",
39
- "is-in-subnet": "^4.0.1",
40
- "jsonwebtoken": "^9.0.0",
41
- "koa": "^2.14.0",
42
- "koa-bodyparser": "^4.3.0",
43
- "koa-router": "^12.0.0",
44
- "ldapjs": "^2.3.3",
45
- "lokijs": "^1.5.12",
46
- "mongodb": "^4.12.1",
47
- "node-machine-id": "1.1.9",
48
- "nodemailer": "^6.8.0",
49
- "passport": "^0.6.0",
50
- "passport-azure-ad": "^4.3.4",
51
- "soap": "^0.45.0",
52
- "tedious": "^15.1.2",
53
- "winston": "^3.8.2"
54
- },
55
- "devDependencies": {
56
- "chai": "^4.2.0",
57
- "mocha": "^9.2.0",
58
- "supertest": "^4.0.2"
59
- }
60
- }
1
+ {
2
+ "name": "scimgateway",
3
+ "version": "4.2.8",
4
+ "description": "Using SCIM protocol as a gateway for user provisioning to other endpoints",
5
+ "author": "Jarle Elshaug <jarle.elshaug@gmail.com> (https://elshaug.xyz)",
6
+ "homepage": "https://elshaug.xyz",
7
+ "license": "MIT",
8
+ "main": "lib/scimgateway.js",
9
+ "scripts": {
10
+ "postinstall": "node lib/postinstall.js",
11
+ "start": "node index.js",
12
+ "test": "mocha -R spec ./test"
13
+ },
14
+ "bin": {
15
+ "scimgateway": "./index.js"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/jelhub/scimgateway.git"
20
+ },
21
+ "keywords": [
22
+ "scim",
23
+ "gateway",
24
+ "proxy",
25
+ "azure",
26
+ "identity",
27
+ "manager",
28
+ "provisioning",
29
+ "iga"
30
+ ],
31
+ "engines": {
32
+ "node": ">=14.0.0"
33
+ },
34
+ "dependencies": {
35
+ "@godaddy/terminus": "^4.12.0",
36
+ "callsite": "^1.0.0",
37
+ "dot-object": "^2.1.4",
38
+ "https-proxy-agent": "^5.0.1",
39
+ "is-in-subnet": "^4.0.1",
40
+ "jsonwebtoken": "^9.0.0",
41
+ "koa": "^2.14.2",
42
+ "koa-bodyparser": "^4.4.0",
43
+ "koa-router": "^12.0.0",
44
+ "ldapjs": "^3.0.2",
45
+ "lokijs": "^1.5.12",
46
+ "mongodb": "^5.6.0",
47
+ "node-machine-id": "1.1.9",
48
+ "nodemailer": "^6.9.3",
49
+ "passport": "^0.6.0",
50
+ "passport-azure-ad": "^4.3.5",
51
+ "soap": "^1.0.0",
52
+ "tedious": "^16.1.0",
53
+ "winston": "^3.9.0"
54
+ },
55
+ "devDependencies": {
56
+ "chai": "^4.2.0",
57
+ "mocha": "^9.2.0",
58
+ "supertest": "^6.3.3"
59
+ }
60
+ }