scimgateway 6.2.0 → 6.2.2
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 +1117 -3440
- package/config/plugin-entra-id.json +3 -3
- package/config/plugin-generic.json +1 -4
- package/lib/helper-rest.ts +23 -46
- package/lib/plugin-entra-id.ts +587 -187
- package/lib/plugin-generic.ts +31 -18
- package/lib/plugin-ldap.ts +15 -2
- package/lib/plugin-loki.ts +11 -0
- package/lib/plugin-mongodb.ts +11 -0
- package/lib/plugin-mssql.ts +11 -0
- package/lib/plugin-saphana.ts +11 -0
- package/lib/plugin-soap.ts +11 -0
- package/lib/scimgateway.ts +55 -37
- package/package.json +1 -1
package/lib/plugin-generic.ts
CHANGED
|
@@ -50,8 +50,17 @@ const scimgateway = new ScimGateway()
|
|
|
50
50
|
const helper = new HelperRest(scimgateway)
|
|
51
51
|
const config = scimgateway.getConfig()
|
|
52
52
|
scimgateway.authPassThroughAllowed = false
|
|
53
|
+
scimgateway.pluginAndOrFilterEnabled = false
|
|
53
54
|
// end - mandatory plugin initialization
|
|
54
55
|
|
|
56
|
+
const isAllowlistingUser = config.map?.user
|
|
57
|
+
? Object.values(config.map.user).some((item: any) => typeof item?.valueMap === 'object' && Object.keys(item.valueMap).length > 0)
|
|
58
|
+
: false
|
|
59
|
+
|
|
60
|
+
const isAllowlistingGroup = config.map?.group
|
|
61
|
+
? Object.values(config.map.group).some((item: any) => typeof item.valueMap === 'object' && Object.keys(item.valueMap).length > 0)
|
|
62
|
+
: false
|
|
63
|
+
|
|
55
64
|
// =================================================
|
|
56
65
|
// getUsers
|
|
57
66
|
// =================================================
|
|
@@ -85,6 +94,11 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
85
94
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all users to be returned - correspond to exploreUsers() in versions < 4.x.x
|
|
86
95
|
path = `/Users${(attrs.length > 0 ? '?attributes=' + attrs.join() : '')}`
|
|
87
96
|
}
|
|
97
|
+
if (getObj.and || getObj.or) {
|
|
98
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
99
|
+
// we could have this logic above, if not it must be defined here
|
|
100
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
101
|
+
}
|
|
88
102
|
// end mandatory if-else logic
|
|
89
103
|
|
|
90
104
|
if (!path) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
@@ -114,16 +128,13 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
114
128
|
totalResults: null,
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
|
|
118
|
-
? Object.values(config.map.user).some((item: any) => typeof item?.valueMap === 'object' && Object.keys(item.valueMap).length > 0)
|
|
119
|
-
: false
|
|
120
|
-
let currentStartIndex = isAllowlisting ? 1 : targetStartIndex
|
|
131
|
+
let currentStartIndex = isAllowlistingUser ? 1 : targetStartIndex
|
|
121
132
|
let allValidResources: any[] = []
|
|
122
133
|
let totalSkipped = 0
|
|
123
134
|
let targetTotalResults: number | null = null
|
|
124
135
|
let iteration = 0
|
|
125
136
|
const maxIterations = 5 // Safety limit for look-ahead fetching
|
|
126
|
-
const resourcesNeeded =
|
|
137
|
+
const resourcesNeeded = isAllowlistingUser ? targetStartIndex + targetCount - 1 : targetCount
|
|
127
138
|
|
|
128
139
|
try {
|
|
129
140
|
while (allValidResources.length < resourcesNeeded && iteration < maxIterations) {
|
|
@@ -166,8 +177,8 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
166
177
|
|
|
167
178
|
if (targetTotalResults === null) {
|
|
168
179
|
// Target endpoint returned full list
|
|
169
|
-
ret.totalResults =
|
|
170
|
-
ret.Resources =
|
|
180
|
+
ret.totalResults = isAllowlistingUser ? allValidResources.length : targetStartIndex - 1 + allValidResources.length
|
|
181
|
+
ret.Resources = isAllowlistingUser ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
|
|
171
182
|
return ret
|
|
172
183
|
}
|
|
173
184
|
|
|
@@ -185,8 +196,8 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
185
196
|
}
|
|
186
197
|
if (ctx?.paging && Object.hasOwn(ctx.paging, 'totalResults')) targetTotalResults = ctx.paging.totalResults
|
|
187
198
|
|
|
188
|
-
ret.totalResults = (targetTotalResults !== null && targetTotalResults > totalSkipped) ? targetTotalResults - totalSkipped : (
|
|
189
|
-
ret.Resources =
|
|
199
|
+
ret.totalResults = (targetTotalResults !== null && targetTotalResults > totalSkipped) ? targetTotalResults - totalSkipped : (isAllowlistingUser ? allValidResources.length : targetStartIndex - 1 + allValidResources.length)
|
|
200
|
+
ret.Resources = isAllowlistingUser ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
|
|
190
201
|
if (!ret.startIndex) ret.startIndex = targetStartIndex
|
|
191
202
|
|
|
192
203
|
return ret
|
|
@@ -321,6 +332,11 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
321
332
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreGroups() in versions < 4.x.x
|
|
322
333
|
path = `/Groups${(attrs.length > 0 ? '?attributes=' + attrs.join() : '')}`
|
|
323
334
|
}
|
|
335
|
+
if (getObj.and || getObj.or) {
|
|
336
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
337
|
+
// we could have this logic above, if not it must be defined here
|
|
338
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
339
|
+
}
|
|
324
340
|
// mandatory if-else logic - end
|
|
325
341
|
|
|
326
342
|
if (!path) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
@@ -345,16 +361,13 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
345
361
|
}
|
|
346
362
|
*/
|
|
347
363
|
|
|
348
|
-
|
|
349
|
-
? Object.values(config.map.group).some((item: any) => typeof item.valueMap === 'object' && Object.keys(item.valueMap).length > 0)
|
|
350
|
-
: false
|
|
351
|
-
let currentStartIndex = isAllowlisting ? 1 : targetStartIndex
|
|
364
|
+
let currentStartIndex = isAllowlistingGroup ? 1 : targetStartIndex
|
|
352
365
|
let allValidResources: any[] = []
|
|
353
366
|
let totalSkipped = 0
|
|
354
367
|
let targetTotalResults: number | null = null
|
|
355
368
|
let iteration = 0
|
|
356
369
|
const maxIterations = 5 // Safety limit for look-ahead fetching
|
|
357
|
-
const resourcesNeeded =
|
|
370
|
+
const resourcesNeeded = isAllowlistingGroup ? targetStartIndex + targetCount - 1 : targetCount
|
|
358
371
|
|
|
359
372
|
try {
|
|
360
373
|
while (allValidResources.length < resourcesNeeded && iteration < maxIterations) {
|
|
@@ -399,8 +412,8 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
399
412
|
|
|
400
413
|
if (targetTotalResults === null) {
|
|
401
414
|
// Target endpoint returned full list
|
|
402
|
-
ret.totalResults =
|
|
403
|
-
ret.Resources =
|
|
415
|
+
ret.totalResults = isAllowlistingGroup ? allValidResources.length : targetStartIndex - 1 + allValidResources.length
|
|
416
|
+
ret.Resources = isAllowlistingGroup ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
|
|
404
417
|
return ret
|
|
405
418
|
}
|
|
406
419
|
|
|
@@ -418,8 +431,8 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
418
431
|
}
|
|
419
432
|
if (ctx?.paging && Object.hasOwn(ctx.paging, 'totalResults')) targetTotalResults = ctx.paging.totalResults
|
|
420
433
|
|
|
421
|
-
ret.totalResults = (targetTotalResults !== null && targetTotalResults > totalSkipped) ? targetTotalResults - totalSkipped : (
|
|
422
|
-
ret.Resources =
|
|
434
|
+
ret.totalResults = (targetTotalResults !== null && targetTotalResults > totalSkipped) ? targetTotalResults - totalSkipped : (isAllowlistingGroup ? allValidResources.length : targetStartIndex - 1 + allValidResources.length)
|
|
435
|
+
ret.Resources = isAllowlistingGroup ? allValidResources.slice(targetStartIndex - 1, targetStartIndex - 1 + targetCount) : allValidResources.slice(0, targetCount)
|
|
423
436
|
if (!ret.startIndex) ret.startIndex = targetStartIndex
|
|
424
437
|
|
|
425
438
|
return ret
|
package/lib/plugin-ldap.ts
CHANGED
|
@@ -116,6 +116,7 @@ import { ScimGateway } from 'scimgateway'
|
|
|
116
116
|
const scimgateway = new ScimGateway()
|
|
117
117
|
const config = scimgateway.getConfig()
|
|
118
118
|
scimgateway.authPassThroughAllowed = false
|
|
119
|
+
scimgateway.pluginAndOrFilterEnabled = false
|
|
119
120
|
// end - mandatory plugin initialization
|
|
120
121
|
|
|
121
122
|
// =================================================
|
|
@@ -123,9 +124,10 @@ scimgateway.authPassThroughAllowed = false
|
|
|
123
124
|
// =================================================
|
|
124
125
|
scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
125
126
|
//
|
|
126
|
-
// "getObj" = { attribute: <>, operator: <>, value: <>, rawFilter: <>, startIndex: <>, count:
|
|
127
|
+
// "getObj" = { attribute: <>, operator: <>, value: <>, rawFilter: <>, startIndex: <>, count: <>, and/or: <getObj> }
|
|
127
128
|
// rawFilter is always included when filtering
|
|
128
129
|
// attribute, operator and value are included when requesting unique object or simpel filtering
|
|
130
|
+
// and/or will be included and the value set to corresponding getObj if the mandatory plugin initialization have 'scimgateway.pluginAndOrFilterEnabled = true' and the request query filter includes simple and/or logic
|
|
129
131
|
// See comments in the "mandatory if-else logic - start"
|
|
130
132
|
//
|
|
131
133
|
// "attributes" is array of attributes to be returned - if empty, all supported attributes should be returned
|
|
@@ -207,6 +209,11 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
207
209
|
attributes: attrs,
|
|
208
210
|
}
|
|
209
211
|
}
|
|
212
|
+
if (getObj.and || getObj.or) {
|
|
213
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
214
|
+
// we could have this logic above, if not it must be defined here
|
|
215
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
216
|
+
}
|
|
210
217
|
// end mandatory if-else logic
|
|
211
218
|
|
|
212
219
|
if (!ldapOptions) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
@@ -471,9 +478,10 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
471
478
|
// =================================================
|
|
472
479
|
scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
473
480
|
//
|
|
474
|
-
// "getObj" = { attribute: <>, operator: <>, value: <>, rawFilter: <>, startIndex: <>, count:
|
|
481
|
+
// "getObj" = { attribute: <>, operator: <>, value: <>, rawFilter: <>, startIndex: <>, count: <>, and/or: <getObj> }
|
|
475
482
|
// rawFilter is always included when filtering
|
|
476
483
|
// attribute, operator and value are included when requesting unique object or simpel filtering
|
|
484
|
+
// and/or will be included and the value set to corresponding getObj if the mandatory plugin initialization have 'scimgateway.pluginAndOrFilterEnabled = true' and the request query filter includes simple and/or logic
|
|
477
485
|
// See comments in the "mandatory if-else logic - start"
|
|
478
486
|
//
|
|
479
487
|
// "attributes" is array of attributes to be returned - if empty, all supported attributes should be returned
|
|
@@ -562,6 +570,11 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
562
570
|
attributes: attrs,
|
|
563
571
|
}
|
|
564
572
|
}
|
|
573
|
+
if (getObj.and || getObj.or) {
|
|
574
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
575
|
+
// we could have this logic above, if not it must be defined here
|
|
576
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
577
|
+
}
|
|
565
578
|
// mandatory if-else logic - end
|
|
566
579
|
|
|
567
580
|
if (!ldapOptions) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
package/lib/plugin-loki.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { ScimGateway } from 'scimgateway'
|
|
|
32
32
|
const scimgateway = new ScimGateway()
|
|
33
33
|
const config = scimgateway.getConfig()
|
|
34
34
|
scimgateway.authPassThroughAllowed = false
|
|
35
|
+
scimgateway.pluginAndOrFilterEnabled = false
|
|
35
36
|
// end - mandatory plugin initialization
|
|
36
37
|
|
|
37
38
|
const configDir = scimgateway.configDir
|
|
@@ -193,6 +194,11 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
193
194
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all users to be returned - correspond to exploreUsers() in versions < 4.x.x
|
|
194
195
|
usersArr = users.chain().data()
|
|
195
196
|
}
|
|
197
|
+
if (getObj.and || getObj.or) {
|
|
198
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
199
|
+
// we could have this logic above, if not it must be defined here
|
|
200
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
201
|
+
}
|
|
196
202
|
// mandatory if-else logic - end
|
|
197
203
|
|
|
198
204
|
if (!usersArr) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
@@ -363,6 +369,11 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
363
369
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreUsers() in versions < 4.x.x
|
|
364
370
|
groupsArr = groups.chain().data()
|
|
365
371
|
}
|
|
372
|
+
if (getObj.and || getObj.or) {
|
|
373
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
374
|
+
// we could have this logic above, if not it must be defined here
|
|
375
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
376
|
+
}
|
|
366
377
|
// mandatory if-else logic - end
|
|
367
378
|
|
|
368
379
|
if (!groupsArr) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
package/lib/plugin-mongodb.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { ScimGateway } from 'scimgateway'
|
|
|
29
29
|
const scimgateway = new ScimGateway()
|
|
30
30
|
const config = scimgateway.getConfig()
|
|
31
31
|
scimgateway.authPassThroughAllowed = false
|
|
32
|
+
scimgateway.pluginAndOrFilterEnabled = false
|
|
32
33
|
// end - mandatory plugin initialization
|
|
33
34
|
|
|
34
35
|
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']
|
|
@@ -224,6 +225,11 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
224
225
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all users to be returned - correspond to exploreUsers() in versions < 4.x.x
|
|
225
226
|
findObj = {}
|
|
226
227
|
}
|
|
228
|
+
if (getObj.and || getObj.or) {
|
|
229
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
230
|
+
// we could have this logic above, if not it must be defined here
|
|
231
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
232
|
+
}
|
|
227
233
|
// mandatory if-else logic - end
|
|
228
234
|
|
|
229
235
|
if (!findObj) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
@@ -448,6 +454,11 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
448
454
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreUsers() in versions < 4.x.x
|
|
449
455
|
findObj = {}
|
|
450
456
|
}
|
|
457
|
+
if (getObj.and || getObj.or) {
|
|
458
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
459
|
+
// we could have this logic above, if not it must be defined here
|
|
460
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
461
|
+
}
|
|
451
462
|
// mandatory if-else logic - end
|
|
452
463
|
|
|
453
464
|
if (!findObj) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
package/lib/plugin-mssql.ts
CHANGED
|
@@ -63,6 +63,7 @@ import { ScimGateway } from 'scimgateway'
|
|
|
63
63
|
const scimgateway = new ScimGateway()
|
|
64
64
|
const config = scimgateway.getConfig()
|
|
65
65
|
scimgateway.authPassThroughAllowed = false
|
|
66
|
+
scimgateway.pluginAndOrFilterEnabled = false
|
|
66
67
|
// end - mandatory plugin initialization
|
|
67
68
|
|
|
68
69
|
if (config?.connection?.authentication?.options?.password) {
|
|
@@ -97,6 +98,11 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
97
98
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all users to be returned - correspond to exploreUsers() in versions < 4.x.x
|
|
98
99
|
sqlQuery = 'select * from [Users]'
|
|
99
100
|
}
|
|
101
|
+
if (getObj.and || getObj.or) {
|
|
102
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
103
|
+
// we could have this logic above, if not it must be defined here
|
|
104
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
105
|
+
}
|
|
100
106
|
// mandatory if-else logic - end
|
|
101
107
|
|
|
102
108
|
if (!sqlQuery) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
@@ -269,6 +275,11 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
269
275
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreGroups() in versions < 4.x.x
|
|
270
276
|
sqlQuery = 'select * from [Groups]'
|
|
271
277
|
}
|
|
278
|
+
if (getObj.and || getObj.or) {
|
|
279
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
280
|
+
// we could have this logic above, if not it must be defined here
|
|
281
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
282
|
+
}
|
|
272
283
|
// mandatory if-else logic - end
|
|
273
284
|
if (!sqlQuery) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
274
285
|
|
package/lib/plugin-saphana.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { ScimGateway } from 'scimgateway'
|
|
|
24
24
|
const scimgateway = new ScimGateway()
|
|
25
25
|
const config = scimgateway.getConfig()
|
|
26
26
|
scimgateway.authPassThroughAllowed = false
|
|
27
|
+
scimgateway.pluginAndOrFilterEnabled = false
|
|
27
28
|
// end - mandatory plugin initialization
|
|
28
29
|
|
|
29
30
|
const endpointHost = config.host
|
|
@@ -66,6 +67,11 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
66
67
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all users to be returned - correspond to exploreUsers() in versions < 4.x.x
|
|
67
68
|
sqlQuery = 'select USER_NAME, USER_DEACTIVATED from SYS.USERS where IS_SAML_ENABLED like \'TRUE\''
|
|
68
69
|
}
|
|
70
|
+
if (getObj.and || getObj.or) {
|
|
71
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
72
|
+
// we could have this logic above, if not it must be defined here
|
|
73
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
74
|
+
}
|
|
69
75
|
// mandatory if-else logic - end
|
|
70
76
|
|
|
71
77
|
if (!sqlQuery) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
@@ -235,6 +241,11 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
235
241
|
} else {
|
|
236
242
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreGroups() in versions < 4.x.x
|
|
237
243
|
}
|
|
244
|
+
if (getObj.and || getObj.or) {
|
|
245
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
246
|
+
// we could have this logic above, if not it must be defined here
|
|
247
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
248
|
+
}
|
|
238
249
|
// mandatory if-else logic - end
|
|
239
250
|
|
|
240
251
|
return { Resources: [] } // groups not supported - returning empty Resources
|
package/lib/plugin-soap.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { ScimGateway } from 'scimgateway'
|
|
|
35
35
|
const scimgateway = new ScimGateway()
|
|
36
36
|
const config = scimgateway.getConfig()
|
|
37
37
|
scimgateway.authPassThroughAllowed = false
|
|
38
|
+
scimgateway.pluginAndOrFilterEnabled = false
|
|
38
39
|
// end - mandatory plugin initialization
|
|
39
40
|
|
|
40
41
|
const wsdlDir = path.join(`${scimgateway.configDir}`, 'wsdls')
|
|
@@ -74,6 +75,11 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
74
75
|
soapRequest = { sql: 'SELECT * FROM Users' }
|
|
75
76
|
soapAction = 'exploreUsers'
|
|
76
77
|
}
|
|
78
|
+
if (getObj.and || getObj.or) {
|
|
79
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
80
|
+
// we could have this logic above, if not it must be defined here
|
|
81
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
82
|
+
}
|
|
77
83
|
// mandatory if-else logic - end
|
|
78
84
|
|
|
79
85
|
if (!soapRequest) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
@@ -327,6 +333,11 @@ scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
327
333
|
soapRequest = { sql: 'SELECT * FROM Groups' }
|
|
328
334
|
soapAction = 'exploreGroups'
|
|
329
335
|
}
|
|
336
|
+
if (getObj.and || getObj.or) {
|
|
337
|
+
// plugin have enabled 'scimgateway.pluginAndOrFilterEnabled' and the query includes an additonal and/or getObj that must to be handled and combined with the initial getObj
|
|
338
|
+
// we could have this logic above, if not it must be defined here
|
|
339
|
+
throw new Error(`${action} error: logic for handling and/or filter is not implemented by plugin, not supporting: ${getObj.rawFilter}`)
|
|
340
|
+
}
|
|
330
341
|
// mandatory if-else logic - end
|
|
331
342
|
|
|
332
343
|
if (!soapRequest) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
package/lib/scimgateway.ts
CHANGED
|
@@ -56,6 +56,12 @@ export class ScimGateway {
|
|
|
56
56
|
* header in the communication with endpoint
|
|
57
57
|
*/
|
|
58
58
|
authPassThroughAllowed: boolean
|
|
59
|
+
/**
|
|
60
|
+
* pluginAndOrFilterEnabled can be set to 'true' by the plugin for letting the plugin handle query filtering that includes simple `and`/`or` logic instead of default handled by scimgateway
|
|
61
|
+
*
|
|
62
|
+
*/
|
|
63
|
+
pluginAndOrFilterEnabled: boolean
|
|
64
|
+
|
|
59
65
|
//
|
|
60
66
|
// plugin methods
|
|
61
67
|
//
|
|
@@ -364,6 +370,7 @@ export class ScimGateway {
|
|
|
364
370
|
this.configDir = configDir
|
|
365
371
|
this.configFile = configFile
|
|
366
372
|
this.authPassThroughAllowed = false // set to true by plugin if using Auth PassThrough
|
|
373
|
+
this.pluginAndOrFilterEnabled = false // set to true by plugin if plugin handle simple and/or query filter
|
|
367
374
|
|
|
368
375
|
let found: Record<string, any> = {}
|
|
369
376
|
let configErr: any
|
|
@@ -1538,8 +1545,10 @@ export class ScimGateway {
|
|
|
1538
1545
|
let isOrFilter = false
|
|
1539
1546
|
if (getObj.rawFilter) {
|
|
1540
1547
|
getObj.rawFilter = decodeURIComponent(getObj.rawFilter.trim())
|
|
1541
|
-
|
|
1542
|
-
|
|
1548
|
+
// Strip quoted literals (handling escaped quotes) to check for operators outside of values
|
|
1549
|
+
const filterWithoutQuotes = getObj.rawFilter.replace(/"(?:\\"|[^"])*"/g, '""')
|
|
1550
|
+
if (filterWithoutQuotes.includes(' and ')) isAndFilter = true
|
|
1551
|
+
if (filterWithoutQuotes.includes(' or ')) isOrFilter = true
|
|
1543
1552
|
}
|
|
1544
1553
|
if (ctx.query.filter) ctx.query.filter = decodeURIComponent(ctx.query.filter.trim())
|
|
1545
1554
|
else ctx.query.filter = ''
|
|
@@ -1553,7 +1562,7 @@ export class ScimGateway {
|
|
|
1553
1562
|
const value = arrFilter.slice(2).join(' ').replace(/"/g, '')
|
|
1554
1563
|
getObj.value = value
|
|
1555
1564
|
} else if (arrFilter[arrFilter.length - 1].endsWith(']')) { // emails[type eq "work"]
|
|
1556
|
-
const rePattern = /^(.*)\[(.*) (.*) (.*)\]$/
|
|
1565
|
+
const rePattern = /^(.*)\[(.*) (.*) (".*")\]$/
|
|
1557
1566
|
const arrMatches = ctx.query.filter.match(rePattern)
|
|
1558
1567
|
if (Array.isArray(arrMatches) && arrMatches.length === 5) {
|
|
1559
1568
|
getObj.attribute = `${arrMatches[1]}.${arrMatches[2]}` // emails.type
|
|
@@ -1703,7 +1712,7 @@ export class ScimGateway {
|
|
|
1703
1712
|
const splitBy = isAndFilter ? ' and ' : ' or '
|
|
1704
1713
|
const arr = obj.rawFilter.split(splitBy)
|
|
1705
1714
|
const originalGetObjArrLength = arr.length
|
|
1706
|
-
let getObjArr:
|
|
1715
|
+
let getObjArr: Record<string, any>[] = []
|
|
1707
1716
|
let complexAttr = ''
|
|
1708
1717
|
for (let i = 0; i < arr.length; i++) {
|
|
1709
1718
|
arr[i] = arr[i].replace(/\(/g, '').replace(/\)/g, '').trim()
|
|
@@ -1752,43 +1761,52 @@ export class ScimGateway {
|
|
|
1752
1761
|
}
|
|
1753
1762
|
|
|
1754
1763
|
if (getObjArr.length > 0) {
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
const
|
|
1764
|
-
|
|
1765
|
-
if (errors.length > 0) {
|
|
1766
|
-
const errMsg = `${handle.getMethod} chunks error: ${errors.join(', ')}`
|
|
1767
|
-
throw new Error(errMsg)
|
|
1768
|
-
}
|
|
1769
|
-
const arrArr = results.map(result => result?.value?.Resources)
|
|
1770
|
-
for (let i = 0; i < arrArr.length; i++) {
|
|
1771
|
-
Array.prototype.push.apply(chunkRes, arrArr[i])
|
|
1764
|
+
if (this.pluginAndOrFilterEnabled && getObjArr.length === 2) { // simple and/or logic handled by plugin
|
|
1765
|
+
const o = getObjArr[0]
|
|
1766
|
+
o.rawFilter = obj.rawFilter
|
|
1767
|
+
if (isAndFilter) o.and = getObjArr[1]
|
|
1768
|
+
else if (isOrFilter) o.or = getObjArr[1]
|
|
1769
|
+
logger.debug(`${gwName} calling ${handle.getMethod}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1770
|
+
res = await (this as any)[handle.getMethod](baseEntity, o, attributes, ctx.passThrough)
|
|
1771
|
+
} else { // and/or logic handled by scimgateway
|
|
1772
|
+
const getObj = async (o: Record<string, any>) => {
|
|
1773
|
+
return await (this as any)[handle.getMethod](baseEntity, o, attributes, ctx.passThrough)
|
|
1772
1774
|
}
|
|
1773
|
-
|
|
1775
|
+
const chunk = 5
|
|
1776
|
+
const chunkRes: Record<string, any>[] = []
|
|
1777
|
+
logger.debug(`${gwName} calling ${handle.getMethod} in chunks of ${chunk}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1778
|
+
do {
|
|
1779
|
+
const arrChunk = getObjArr.splice(0, chunk)
|
|
1780
|
+
const results = await Promise.allSettled(arrChunk.map(o => getObj(o))) as { status: 'fulfilled' | 'rejected', reason: any, value: any }[] // processing max chunk async
|
|
1781
|
+
const errors = results.filter(result => result.status === 'rejected').map(result => result.reason.message)
|
|
1782
|
+
if (errors.length > 0) {
|
|
1783
|
+
const errMsg = `${handle.getMethod} chunks error: ${errors.join(', ')}`
|
|
1784
|
+
throw new Error(errMsg)
|
|
1785
|
+
}
|
|
1786
|
+
const arrArr = results.map(result => result?.value?.Resources)
|
|
1787
|
+
for (let i = 0; i < arrArr.length; i++) {
|
|
1788
|
+
Array.prototype.push.apply(chunkRes, arrArr[i])
|
|
1789
|
+
}
|
|
1790
|
+
} while (getObjArr.length > 0)
|
|
1774
1791
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1792
|
+
if (isAndFilter) {
|
|
1793
|
+
const idCounts = new Map<string, number>()
|
|
1794
|
+
for (const item of chunkRes) {
|
|
1795
|
+
if (item.id) {
|
|
1796
|
+
idCounts.set(item.id, (idCounts.get(item.id) || 0) + 1)
|
|
1797
|
+
}
|
|
1780
1798
|
}
|
|
1799
|
+
const intersectionIds = new Set<string>()
|
|
1800
|
+
for (const [id, count] of idCounts.entries()) {
|
|
1801
|
+
if (count === originalGetObjArrLength) intersectionIds.add(id)
|
|
1802
|
+
}
|
|
1803
|
+
res = { Resources: Array.from(new Map(chunkRes.filter(item => intersectionIds.has(item.id)).map(item => [item.id, item])).values()) }
|
|
1804
|
+
} else if (isOrFilter) {
|
|
1805
|
+
const uniqueResources = Array.from(new Map(chunkRes.map(item =>
|
|
1806
|
+
[item.id, item])).values(),
|
|
1807
|
+
)
|
|
1808
|
+
res = { Resources: uniqueResources }
|
|
1781
1809
|
}
|
|
1782
|
-
const intersectionIds = new Set<string>()
|
|
1783
|
-
for (const [id, count] of idCounts.entries()) {
|
|
1784
|
-
if (count === originalGetObjArrLength) intersectionIds.add(id)
|
|
1785
|
-
}
|
|
1786
|
-
res = { Resources: Array.from(new Map(chunkRes.filter(item => intersectionIds.has(item.id)).map(item => [item.id, item])).values()) }
|
|
1787
|
-
} else if (isOrFilter) {
|
|
1788
|
-
const uniqueResources = Array.from(new Map(chunkRes.map(item =>
|
|
1789
|
-
[item.id, item])).values(),
|
|
1790
|
-
)
|
|
1791
|
-
res = { Resources: uniqueResources }
|
|
1792
1810
|
}
|
|
1793
1811
|
}
|
|
1794
1812
|
}
|
package/package.json
CHANGED