scimgateway 4.1.0 → 4.1.3
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 +75 -16
- package/config/plugin-api.json +2 -1
- package/config/plugin-azure-ad.json +2 -1
- package/config/plugin-forwardinc.json +2 -1
- package/config/plugin-ldap.json +4 -1
- package/config/plugin-loki.json +2 -1
- package/config/plugin-mongodb.json +2 -1
- package/config/plugin-mssql.json +2 -1
- package/config/plugin-saphana.json +2 -1
- package/config/plugin-scim.json +2 -1
- package/lib/plugin-ldap.js +11 -0
- package/lib/scimgateway.js +129 -35
- package/lib/utils.js +17 -0
- package/package.json +1 -1
- package/test/lib/plugin-loki.js +9 -3
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Latest news:
|
|
|
31
31
|
|
|
32
32
|
## Overview
|
|
33
33
|
|
|
34
|
-
With SCIM Gateway we
|
|
34
|
+
With SCIM Gateway we can manage users and groups by using REST based [SCIM](http://www.simplecloud.info/) 1.1 or 2.0 protocol. Gateway translates incoming SCIM requests and expose CRUD functionality (create, read, update and delete user/group) towards destinations using endpoint specific protocols. In other words, none SCIM-endpoints will become SCIM-endpoints. Gateway do not require SCIM to be used, it's also an API Gateway that could be used for other things than user provisioning.
|
|
35
35
|
|
|
36
36
|
SCIM Gateway is a standalone product, however this document shows how the gateway could be used by products like Symatec/Broadcom/CA Identity Manager.
|
|
37
37
|
|
|
@@ -208,7 +208,8 @@ Below shows an example of config\plugin-saphana.json
|
|
|
208
208
|
"scim": {
|
|
209
209
|
"version": "2.0",
|
|
210
210
|
"customSchema": null,
|
|
211
|
-
"skipTypeConvert" : false
|
|
211
|
+
"skipTypeConvert" : false,
|
|
212
|
+
"usePutSoftSync" : false
|
|
212
213
|
},
|
|
213
214
|
"log": {
|
|
214
215
|
"loglevel": {
|
|
@@ -326,6 +327,8 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
326
327
|
]
|
|
327
328
|
|
|
328
329
|
|
|
330
|
+
- **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
|
|
331
|
+
|
|
329
332
|
- **log.loglevel.file** - off, error, info, or debug. Output to plugin-logfile e.g. `logs\plugin-saphana.log`
|
|
330
333
|
|
|
331
334
|
- **log.loglevel.console** - off, error, info, or debug. Output to stdout and errors to stderr.
|
|
@@ -347,7 +350,7 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
347
350
|
|
|
348
351
|
- **auth.bearerOAuth** - Array of one or more Client Credentials OAuth configuration objects. **`client_id`** and **`client_secret`** are mandatory. client_secret value will become encrypted when gateway is started. OAuth token request url is **/oauth/token** e.g. http://localhost:8880/oauth/token
|
|
349
352
|
|
|
350
|
-
- **certificate** - If not using
|
|
353
|
+
- **certificate** - If not using TLS certificate, set "key", "cert" and "ca" to **null**. When using TLS, "key" and "cert" have to be defined with the filename corresponding to the primary-key and public-certificate. Both files must be located in the `<package-root>\config\certs` directory e.g:
|
|
351
354
|
|
|
352
355
|
"certificate": {
|
|
353
356
|
"key": "key.pem",
|
|
@@ -1139,28 +1142,84 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1139
1142
|
|
|
1140
1143
|
## Change log
|
|
1141
1144
|
|
|
1145
|
+
### v4.1.3
|
|
1146
|
+
[Fixed]
|
|
1147
|
+
|
|
1148
|
+
- createUser response did not include the id that was returned by plugin
|
|
1149
|
+
|
|
1150
|
+
[Added]
|
|
1151
|
+
|
|
1152
|
+
- PUT (Replace User) now includes group handling. Using configuration `scim.usePutSoftsync=true` will prevent removing any existing groups that are not included in body.groups
|
|
1153
|
+
|
|
1154
|
+
Example:
|
|
1155
|
+
|
|
1156
|
+
PUT /Users/bjensen
|
|
1157
|
+
{
|
|
1158
|
+
...
|
|
1159
|
+
"groups": [
|
|
1160
|
+
{"value":"Employees","display":"Employees"},
|
|
1161
|
+
{"value":"Admins","display":"Admins"}
|
|
1162
|
+
],
|
|
1163
|
+
...
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
### v4.1.2
|
|
1170
|
+
[Added]
|
|
1171
|
+
|
|
1172
|
+
- endpointMapper supporting one to many mappings using a comma separated list of attributes in the `mapTo`
|
|
1173
|
+
|
|
1174
|
+
Configuration example:
|
|
1175
|
+
|
|
1176
|
+
"map": {
|
|
1177
|
+
"user": {
|
|
1178
|
+
"PersonnelNumber": {
|
|
1179
|
+
"mapTo": "id,userName",
|
|
1180
|
+
"type": "string"
|
|
1181
|
+
},
|
|
1182
|
+
...
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
### v4.1.1
|
|
1188
|
+
[Added]
|
|
1189
|
+
|
|
1190
|
+
- plugin-ldap support userFilter/groupFilter configuration for restricting scope
|
|
1191
|
+
|
|
1192
|
+
Configuration example:
|
|
1193
|
+
|
|
1194
|
+
{
|
|
1195
|
+
...
|
|
1196
|
+
"userFilter": "(memberOf=CN=grp1,OU=Groups,DC=test,DC=com)(!(memberOf=CN=Domain Admins,CN=Users,DC=test,DC=com))",
|
|
1197
|
+
"groupFilter": "(!(cn=grp2))",
|
|
1198
|
+
...
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1142
1201
|
### v4.1.0
|
|
1143
1202
|
[Added]
|
|
1144
1203
|
|
|
1145
1204
|
- Supporting OAuth Client Credentials authentication
|
|
1146
1205
|
|
|
1147
|
-
Configuration example:
|
|
1206
|
+
Configuration example:
|
|
1148
1207
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1208
|
+
"bearerOAuth": [
|
|
1209
|
+
{
|
|
1210
|
+
"client_id": "my_client_id",
|
|
1211
|
+
"client_secret": "my_client_secret",
|
|
1212
|
+
"readOnly": false,
|
|
1213
|
+
"baseEntities": []
|
|
1214
|
+
}
|
|
1215
|
+
]
|
|
1157
1216
|
|
|
1158
1217
|
|
|
1159
|
-
In example above, client using SCIM Gateway must have OAuth configuration:
|
|
1218
|
+
In example above, client using SCIM Gateway must have OAuth configuration:
|
|
1160
1219
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1220
|
+
client_id = my_client_id
|
|
1221
|
+
client_secret = my_client_secret
|
|
1222
|
+
token request url = http(s)://<host>:<port>/oauth/token
|
|
1164
1223
|
|
|
1165
1224
|
|
|
1166
1225
|
### v4.0.1
|
package/config/plugin-api.json
CHANGED
package/config/plugin-ldap.json
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
"scim": {
|
|
6
6
|
"version": "2.0",
|
|
7
7
|
"customSchema": null,
|
|
8
|
-
"skipTypeConvert": false
|
|
8
|
+
"skipTypeConvert": false,
|
|
9
|
+
"usePutSoftSync": false
|
|
9
10
|
},
|
|
10
11
|
"log": {
|
|
11
12
|
"loglevel": {
|
|
@@ -94,6 +95,8 @@
|
|
|
94
95
|
"ldap": {
|
|
95
96
|
"userBase": "CN=Users,DC=test,DC=com",
|
|
96
97
|
"groupBase": "OU=Groups,DC=test,DC=com",
|
|
98
|
+
"userFilter": null,
|
|
99
|
+
"groupFilter": null,
|
|
97
100
|
"userNamingAttr": "CN",
|
|
98
101
|
"groupNamingAttr": "CN",
|
|
99
102
|
"userObjectClasses": [
|
package/config/plugin-loki.json
CHANGED
package/config/plugin-mssql.json
CHANGED
package/config/plugin-scim.json
CHANGED
package/lib/plugin-ldap.js
CHANGED
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
// "type": "string"
|
|
24
24
|
// }
|
|
25
25
|
//
|
|
26
|
+
// Additional user/group filtering for restricting scope may be configured in endpoint.entity.xxx.ldap e.g:
|
|
27
|
+
// {
|
|
28
|
+
// ...
|
|
29
|
+
// "userFilter": "(memberOf=CN=grp1,OU=Groups,DC=test,DC=com)(!(memberOf=CN=Domain Admins,CN=Users,DC=test,DC=com))",
|
|
30
|
+
// "groupFilter": "(!(cn=grp2))",
|
|
31
|
+
// ...
|
|
32
|
+
// }
|
|
26
33
|
//
|
|
27
34
|
// Attributes according to map definition in the configuration file plugin-ldap.json:
|
|
28
35
|
//
|
|
@@ -190,6 +197,7 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes) => {
|
|
|
190
197
|
scope: scope,
|
|
191
198
|
attributes: attrs
|
|
192
199
|
}
|
|
200
|
+
if (config.entity[baseEntity].ldap.userFilter) ldapOptions.filter += config.entity[baseEntity].ldap.userFilter
|
|
193
201
|
}
|
|
194
202
|
}
|
|
195
203
|
} else if (getObj.operator === 'eq' && getObj.attribute === 'group.value') {
|
|
@@ -209,6 +217,7 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes) => {
|
|
|
209
217
|
scope: scope,
|
|
210
218
|
attributes: attrs
|
|
211
219
|
}
|
|
220
|
+
if (config.entity[baseEntity].ldap.userFilter) ldapOptions.filter += config.entity[baseEntity].ldap.userFilter
|
|
212
221
|
}
|
|
213
222
|
// end mandatory if-else logic
|
|
214
223
|
|
|
@@ -558,6 +567,7 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes) => {
|
|
|
558
567
|
scope: scope,
|
|
559
568
|
attributes: attrs
|
|
560
569
|
}
|
|
570
|
+
if (config.entity[baseEntity].ldap.groupFilter) ldapOptions.filter += config.entity[baseEntity].ldap.groupFilter
|
|
561
571
|
}
|
|
562
572
|
}
|
|
563
573
|
} else if (getObj.operator === 'eq' && getObj.attribute === 'members.value') {
|
|
@@ -578,6 +588,7 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes) => {
|
|
|
578
588
|
scope: scope,
|
|
579
589
|
attributes: attrs
|
|
580
590
|
}
|
|
591
|
+
if (config.entity[baseEntity].ldap.groupFilter) ldapOptions.filter += config.entity[baseEntity].ldap.groupFilter
|
|
581
592
|
}
|
|
582
593
|
// mandatory if-else logic - end
|
|
583
594
|
|
package/lib/scimgateway.js
CHANGED
|
@@ -276,8 +276,8 @@ const ScimGateway = function () {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
this.testmodeusers = scimDef.TestmodeUsers.Resources //
|
|
280
|
-
this.testmodegroups = scimDef.TestmodeGroups.Resources //
|
|
279
|
+
this.testmodeusers = scimDef.TestmodeUsers.Resources // exposed and used by plugin-loki
|
|
280
|
+
this.testmodegroups = scimDef.TestmodeGroups.Resources // exposed and used by plugin-loki
|
|
281
281
|
|
|
282
282
|
// multiValueTypes array contains attributes that will be used by "type converted objects" logic
|
|
283
283
|
// groups, roles, and members are excluded
|
|
@@ -1083,33 +1083,36 @@ const ScimGateway = function () {
|
|
|
1083
1083
|
return
|
|
1084
1084
|
}
|
|
1085
1085
|
logger.debug(`${gwName}[${pluginName}] calling "${handle.createMethod}" and awaiting result`)
|
|
1086
|
+
delete jsonBody.id // in case included in request
|
|
1086
1087
|
try {
|
|
1087
1088
|
const res = await this[handle.createMethod](ctx.params.baseEntity, scimdata)
|
|
1088
|
-
for (const key in res) { // merge any result e.g:
|
|
1089
|
+
for (const key in res) { // merge any result e.g: {'id': 'xxxx'}
|
|
1089
1090
|
jsonBody[key] = res[key]
|
|
1090
1091
|
}
|
|
1091
1092
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
if (
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
if (
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1093
|
+
if (!jsonBody.id) { // retrieve id
|
|
1094
|
+
let obj
|
|
1095
|
+
try {
|
|
1096
|
+
if (handle.createMethod === 'createUser') {
|
|
1097
|
+
if (jsonBody.userName) {
|
|
1098
|
+
jsonBody.id = jsonBody.userName
|
|
1099
|
+
obj = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'userName', operator: 'eq', value: jsonBody.userName }, ['id', 'userName'])
|
|
1100
|
+
} else if (jsonBody.externalId) {
|
|
1101
|
+
jsonBody.id = jsonBody.externalId
|
|
1102
|
+
obj = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'externalId', operator: 'eq', value: jsonBody.externalId }, ['id', 'externalId'])
|
|
1103
|
+
}
|
|
1104
|
+
} else if (handle.createMethod === 'createGroup') {
|
|
1105
|
+
if (jsonBody.externalId) {
|
|
1106
|
+
jsonBody.id = jsonBody.externalId
|
|
1107
|
+
obj = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'externalId', operator: 'eq', value: jsonBody.externalId }, ['id', 'externalId'])
|
|
1108
|
+
} else if (jsonBody.displayName) {
|
|
1109
|
+
jsonBody.id = jsonBody.displayName
|
|
1110
|
+
obj = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'displayName', operator: 'eq', value: jsonBody.displayName }, ['id', 'displayName'])
|
|
1111
|
+
}
|
|
1109
1112
|
}
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
+
} catch (err) { }
|
|
1114
|
+
if (obj && obj.id) jsonBody.id = obj.id
|
|
1115
|
+
}
|
|
1113
1116
|
|
|
1114
1117
|
const location = `${ctx.origin}${ctx.path}/${jsonBody.id}`
|
|
1115
1118
|
if (!jsonBody.meta) jsonBody.meta = {}
|
|
@@ -1256,6 +1259,10 @@ const ScimGateway = function () {
|
|
|
1256
1259
|
// ==========================================
|
|
1257
1260
|
router.put([`/(|scim/)(!${undefined}|Users|Groups|servicePlans)/:id`,
|
|
1258
1261
|
`/:baseEntity/(|scim/)(!${undefined}|Users|Groups|servicePlans)/:id`], async (ctx) => {
|
|
1262
|
+
await replaceUsrGrp(ctx)
|
|
1263
|
+
})
|
|
1264
|
+
|
|
1265
|
+
const replaceUsrGrp = async (ctx, isExtCaller) => {
|
|
1259
1266
|
let u = ctx.originalUrl.substr(0, ctx.originalUrl.lastIndexOf('/'))
|
|
1260
1267
|
u = u.substr(u.lastIndexOf('/') + 1) // u = Users, Groups
|
|
1261
1268
|
const handle = handler[u]
|
|
@@ -1268,7 +1275,7 @@ const ScimGateway = function () {
|
|
|
1268
1275
|
err = jsonErr(config.scim.version, pluginName, ctx.status, err)
|
|
1269
1276
|
ctx.body = err
|
|
1270
1277
|
} else {
|
|
1271
|
-
logger.debug(`${gwName}[${pluginName}] [
|
|
1278
|
+
if (!isExtCaller) logger.debug(`${gwName}[${pluginName}] [Replace ${handle.description}] id=${id}`)
|
|
1272
1279
|
logger.debug(`${gwName}[${pluginName}] PUT ${ctx.originalUrl} body=${strBody}`)
|
|
1273
1280
|
try {
|
|
1274
1281
|
// get current object
|
|
@@ -1302,6 +1309,86 @@ const ScimGateway = function () {
|
|
|
1302
1309
|
logger.debug(`${gwName}[${pluginName}] calling "${handle.modifyMethod}" and awaiting result`)
|
|
1303
1310
|
await this[handle.modifyMethod](ctx.params.baseEntity, id, scimdata)
|
|
1304
1311
|
|
|
1312
|
+
// add/remove groups
|
|
1313
|
+
if (jsonBody.groups && Array.isArray(jsonBody.groups)) { // only if groups included, { "groups": [] } will remove all existing
|
|
1314
|
+
if (typeof this[handler.groups.getMethod] !== 'function' || typeof this[handler.groups.modifyMethod] !== 'function') {
|
|
1315
|
+
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')
|
|
1316
|
+
}
|
|
1317
|
+
let currentGroups
|
|
1318
|
+
if (currentObj.groups && Array.isArray(currentObj.groups)) currentGroups = currentObj.groups
|
|
1319
|
+
else { // try to get current groups the standard way
|
|
1320
|
+
let res
|
|
1321
|
+
try {
|
|
1322
|
+
res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }, ['members.value', 'id', 'displayName']) // await scimgateway.getUserGroups(baseEntity, userObj.id, 'members.value,displayName')
|
|
1323
|
+
} catch (err) {} // method may be implemented but throwing error like groups not supported/implemented
|
|
1324
|
+
currentGroups = []
|
|
1325
|
+
if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length > 0) {
|
|
1326
|
+
for (let i = 0; i < res.Resources.length; i++) {
|
|
1327
|
+
if (!res.Resources[i].id) continue
|
|
1328
|
+
const el = {}
|
|
1329
|
+
el.value = res.Resources[i].id
|
|
1330
|
+
if (res.Resources[i].displayName) el.display = res.Resources[i].displayName
|
|
1331
|
+
if (el.value) currentGroups.push(el) // { "value": "Admins", "display": "Admins"}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
const addGrps = []
|
|
1336
|
+
const removeGrps = []
|
|
1337
|
+
// add
|
|
1338
|
+
for (let i = 0; i < jsonBody.groups.length; i++) {
|
|
1339
|
+
let found = false
|
|
1340
|
+
for (let j = 0; j < currentGroups.length; j++) {
|
|
1341
|
+
if (jsonBody.groups[i].value === currentGroups[j].value) {
|
|
1342
|
+
found = true
|
|
1343
|
+
break
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (!found && jsonBody.groups[i].value) addGrps.push(jsonBody.groups[i].value)
|
|
1347
|
+
}
|
|
1348
|
+
// remove
|
|
1349
|
+
for (let i = 0; i < currentGroups.length; i++) {
|
|
1350
|
+
let found = false
|
|
1351
|
+
for (let j = 0; j < jsonBody.groups.length; j++) {
|
|
1352
|
+
if (currentGroups[i].value === jsonBody.groups[j].value) {
|
|
1353
|
+
found = true
|
|
1354
|
+
break
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (!found && currentGroups[i].value) removeGrps.push(currentGroups[i].value)
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
const addGroups = async (grp) => {
|
|
1361
|
+
const obj = { members: [{ value: id }] }
|
|
1362
|
+
return await this[handler.groups.modifyMethod](ctx.params.baseEntity, grp, obj)
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
const removeGroups = async (grp) => {
|
|
1366
|
+
const obj = { members: [{ operation: 'delete', value: id }] }
|
|
1367
|
+
return await this[handler.groups.modifyMethod](ctx.params.baseEntity, grp, obj)
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
let errRemove
|
|
1371
|
+
if (!config.scim.usePutSoftSync) { // default will remove any existing groups not included, usePutSoftSync=true prevents removing groups (only add groups)
|
|
1372
|
+
await Promise.all(removeGrps.map((grp) => removeGroups(grp)))
|
|
1373
|
+
.then()
|
|
1374
|
+
.catch((err) => {
|
|
1375
|
+
errRemove = err
|
|
1376
|
+
})
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
let errAdd
|
|
1380
|
+
await Promise.all(addGrps.map((grp) => addGroups(grp)))
|
|
1381
|
+
.then()
|
|
1382
|
+
.catch((err) => {
|
|
1383
|
+
errAdd = err
|
|
1384
|
+
})
|
|
1385
|
+
|
|
1386
|
+
let errMsg = ''
|
|
1387
|
+
if (errRemove) errMsg = `removeGroups error: ${errRemove.message}`
|
|
1388
|
+
if (errAdd) errMsg += `${errMsg ? ' ' : ''}addGroups error: ${errAdd.message}`
|
|
1389
|
+
if (errMsg) throw new Error(errMsg)
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1305
1392
|
// get updated object
|
|
1306
1393
|
logger.debug(`${gwName}[${pluginName}] calling "${handle.getMethod}" and awaiting result`)
|
|
1307
1394
|
res = await this[handle.getMethod](ctx.params.baseEntity, { attribute: 'id', operator: 'eq', value: id }, [])
|
|
@@ -1313,7 +1400,7 @@ const ScimGateway = function () {
|
|
|
1313
1400
|
else throw Error(`put using method ${handle.getMethod} got unexpected response: ${JSON.stringify(res)}`)
|
|
1314
1401
|
|
|
1315
1402
|
// include groups
|
|
1316
|
-
if (handle.getMethod === handler.users.getMethod) {
|
|
1403
|
+
if (handle.getMethod === handler.users.getMethod && typeof this[handler.groups.getMethod] === 'function') {
|
|
1317
1404
|
logger.debug(`${gwName}[${pluginName}] calling "${handler.groups.getMethod}" and awaiting result`)
|
|
1318
1405
|
const res = await this[handler.groups.getMethod](ctx.params.baseEntity, { attribute: 'members.value', operator: 'eq', value: id }, ['members.value', 'id', 'displayName'])
|
|
1319
1406
|
let grps = []
|
|
@@ -1346,7 +1433,8 @@ const ScimGateway = function () {
|
|
|
1346
1433
|
ctx.body = e
|
|
1347
1434
|
}
|
|
1348
1435
|
}
|
|
1349
|
-
}
|
|
1436
|
+
}
|
|
1437
|
+
this.replaceUsrGrp = replaceUsrGrp // exposed
|
|
1350
1438
|
|
|
1351
1439
|
// ==========================================
|
|
1352
1440
|
// API POST (no SCIM)
|
|
@@ -1782,7 +1870,7 @@ const ScimGateway = function () {
|
|
|
1782
1870
|
// exported methods
|
|
1783
1871
|
//
|
|
1784
1872
|
ScimGateway.prototype.endpointMap = endpointMap
|
|
1785
|
-
ScimGateway.prototype.countries =
|
|
1873
|
+
ScimGateway.prototype.countries = countries
|
|
1786
1874
|
|
|
1787
1875
|
ScimGateway.prototype.getPassword = (pwEntity, configFile) => {
|
|
1788
1876
|
return utils.getPassword(pwEntity, configFile) // utils.getPassword('scimgateway.password', './config/plugin-testmode.json');
|
|
@@ -1994,7 +2082,7 @@ ScimGateway.prototype.endpointMapper = function endpointMapper (direction, parse
|
|
|
1994
2082
|
}
|
|
1995
2083
|
}
|
|
1996
2084
|
for (const key2 in mapObj) {
|
|
1997
|
-
if (mapObj[key2].mapTo.
|
|
2085
|
+
if (mapObj[key2].mapTo.split(',').map(item => item.trim().toLowerCase()).includes(key.toLowerCase())) {
|
|
1998
2086
|
found = true
|
|
1999
2087
|
if (mapObj[key2].type === 'array' && arrIndex && arrIndex >= 0) {
|
|
2000
2088
|
dotNewObj[`${key2}.${arrIndex}`] = dotObj[keyOrg] // servicePlan.0.value => servicePlan.0 and groups[0].value => memberOf.0
|
|
@@ -2007,16 +2095,19 @@ ScimGateway.prototype.endpointMapper = function endpointMapper (direction, parse
|
|
|
2007
2095
|
}
|
|
2008
2096
|
} else { // string (get)
|
|
2009
2097
|
const resArr = []
|
|
2010
|
-
let strArr
|
|
2011
|
-
if (Array.isArray(str))
|
|
2012
|
-
|
|
2098
|
+
let strArr = []
|
|
2099
|
+
if (Array.isArray(str)) {
|
|
2100
|
+
for (let i = 0; i < str.length; i++) {
|
|
2101
|
+
strArr = strArr.concat(str[i].split(',').map(item => item.trim())) // supports "id,userName" e.g. {"mapTo": "id,userName"}
|
|
2102
|
+
}
|
|
2103
|
+
} else strArr = str.split(',').map(item => item.trim())
|
|
2013
2104
|
for (let i = 0; i < strArr.length; i++) {
|
|
2014
2105
|
const attr = strArr[i]
|
|
2015
2106
|
let found = false
|
|
2016
2107
|
for (const key in mapObj) {
|
|
2017
|
-
if (mapObj[key].mapTo
|
|
2108
|
+
if (mapObj[key].mapTo && mapObj[key].mapTo.split(',').map(item => item.trim()).includes(attr)) { // supports { "mapTo": "userName,id" }
|
|
2018
2109
|
found = true
|
|
2019
|
-
resArr.push(key)
|
|
2110
|
+
if (!resArr.includes(key)) resArr.push(key)
|
|
2020
2111
|
break
|
|
2021
2112
|
} else if (attr === 'roles' && mapObj[key].mapTo === 'roles.value') { // allow get using attribute roles - convert to correct roles.value
|
|
2022
2113
|
found = true
|
|
@@ -2098,7 +2189,10 @@ ScimGateway.prototype.endpointMapper = function endpointMapper (direction, parse
|
|
|
2098
2189
|
mapTo = mapTo.replace('.', '##') // only first occurence
|
|
2099
2190
|
noneCore = true
|
|
2100
2191
|
}
|
|
2101
|
-
|
|
2192
|
+
const arrMapTo = mapTo.split(',').map(item => item.trim()) // supports {"mapTo": "id,userName"}
|
|
2193
|
+
for (let i = 0; i < arrMapTo.length; i++) {
|
|
2194
|
+
dotNewObj[arrMapTo[i]] = dotObj[key] // {"active": {"mapTo": "accountEnabled"} => str.replace("accountEnabled", "active")
|
|
2195
|
+
}
|
|
2102
2196
|
}
|
|
2103
2197
|
let mapTo = mapObj[key].mapTo
|
|
2104
2198
|
if (mapTo.startsWith('urn:')) {
|
|
@@ -2623,7 +2717,7 @@ const clearObjectValues = (o, parent) => {
|
|
|
2623
2717
|
//
|
|
2624
2718
|
const jsonErr = (scimVersion, pluginName, htmlErrCode, err, scimType) => {
|
|
2625
2719
|
let errJson = {}
|
|
2626
|
-
let msg = `
|
|
2720
|
+
let msg = `scimgateway[${pluginName}] `
|
|
2627
2721
|
err.constructor === Error ? msg += err.message : msg += err
|
|
2628
2722
|
|
|
2629
2723
|
if (scimVersion !== '2.0' && scimVersion !== 2) { // v1.1
|
package/lib/utils.js
CHANGED
|
@@ -373,3 +373,20 @@ module.exports.getEncrypted = function (pw, seed) {
|
|
|
373
373
|
}
|
|
374
374
|
return undefined
|
|
375
375
|
}
|
|
376
|
+
|
|
377
|
+
// aadUnExtUpn convert Azure AD guest UPN to origin target UPN
|
|
378
|
+
// john.doe_company.com#EXT#@company.onmicrosoft.com => john.doe@company.com
|
|
379
|
+
module.exports.aadUnExtUpn = function (upn) {
|
|
380
|
+
const arr = upn.split('#EXT#')
|
|
381
|
+
if (arr.length === 1) return arr[0]
|
|
382
|
+
const extArr = arr[0].split('_')
|
|
383
|
+
if (extArr.length === 1) return extArr[0]
|
|
384
|
+
else {
|
|
385
|
+
upn = extArr[0]
|
|
386
|
+
for (let i = 1; i < extArr.length - 1; i++) {
|
|
387
|
+
upn += `_${extArr[i]}`
|
|
388
|
+
}
|
|
389
|
+
upn += `@${extArr[extArr.length - 1]}`
|
|
390
|
+
}
|
|
391
|
+
return upn
|
|
392
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.3",
|
|
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",
|
package/test/lib/plugin-loki.js
CHANGED
|
@@ -13,7 +13,6 @@ const options = {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
describe('plugin-loki tests', () => {
|
|
16
|
-
|
|
17
16
|
it('getUsers all test (1)', function (done) {
|
|
18
17
|
server_8880.get('/Users' +
|
|
19
18
|
'?startIndex=1&count=100')
|
|
@@ -362,7 +361,7 @@ describe('plugin-loki tests', () => {
|
|
|
362
361
|
*/
|
|
363
362
|
|
|
364
363
|
it('modifyUser test', (done) => {
|
|
365
|
-
|
|
364
|
+
let user = {
|
|
366
365
|
Operations: [
|
|
367
366
|
{
|
|
368
367
|
op: 'replace',
|
|
@@ -490,7 +489,11 @@ describe('plugin-loki tests', () => {
|
|
|
490
489
|
}],
|
|
491
490
|
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User': {
|
|
492
491
|
employeeNumber: '1111'
|
|
493
|
-
}
|
|
492
|
+
},
|
|
493
|
+
groups: [
|
|
494
|
+
{ value: 'Employees', display: 'Employees' },
|
|
495
|
+
{ value: 'Admins', display: 'Admins' }
|
|
496
|
+
]
|
|
494
497
|
}
|
|
495
498
|
|
|
496
499
|
server_8880.put('/Users/jgilber')
|
|
@@ -517,6 +520,9 @@ describe('plugin-loki tests', () => {
|
|
|
517
520
|
expect(user['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'].manager).to.equal(undefined) // deleted
|
|
518
521
|
expect(user['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'].test1).to.equal(undefined) // deleted
|
|
519
522
|
expect(user['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'].test2).to.equal(undefined) // deleted
|
|
523
|
+
expect(user.groups.length).to.equal(2)
|
|
524
|
+
expect(user.groups[0].value).to.equal('Admins')
|
|
525
|
+
expect(user.groups[1].value).to.equal('Employees')
|
|
520
526
|
done()
|
|
521
527
|
})
|
|
522
528
|
})
|