scimgateway 5.0.4 → 5.0.6
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 +74 -48
- package/config/plugin-api.json +12 -12
- package/config/plugin-entra-id.json +49 -12
- package/config/plugin-ldap.json +13 -13
- package/config/plugin-loki.json +12 -12
- package/config/plugin-mongodb.json +13 -13
- package/config/plugin-mssql.json +15 -15
- package/config/plugin-saphana.json +13 -13
- package/config/plugin-scim.json +12 -12
- package/config/plugin-soap.json +12 -12
- package/lib/helper-rest.ts +17 -16
- package/lib/logger.ts +1 -3
- package/lib/plugin-ldap.ts +1 -1
- package/lib/scimgateway.ts +267 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,11 +16,11 @@ Validated through IdP's:
|
|
|
16
16
|
|
|
17
17
|
Latest news:
|
|
18
18
|
|
|
19
|
-
- Major version **v5
|
|
19
|
+
- Major version **v5** marks a shift to native TypeScript support and prioritizes [Bun](https://bun.sh/) over Node.js. This upgrade requires some modifications to existing plugins.
|
|
20
20
|
- **BREAKING**: [SCIM Stream](https://elshaug.xyz/docs/scim-stream) is the modern way of user provisioning letting clients subscribe to messages instead of traditional IGA top-down provisioning. SCIM Gateway now offers enhanced functionality with support for message subscription and automated provisioning using SCIM Stream
|
|
21
|
-
- Authentication PassThrough letting plugin pass authentication directly to endpoint for avoid maintaining secrets at the gateway
|
|
21
|
+
- Authentication PassThrough letting plugin pass authentication directly to endpoint for avoid maintaining secrets at the gateway
|
|
22
22
|
- Supports OAuth Client Credentials authentication
|
|
23
|
-
- Major version **v4
|
|
23
|
+
- Major version **v4** getUsers() and getGroups() replacing some deprecated methods. No limitations on filtering/sorting. Admin user access can be linked to specific baseEntities. New MongoDB plugin
|
|
24
24
|
- ipAllowList for restricting access to allowlisted IP addresses or subnets e.g. Azure IP-range
|
|
25
25
|
- General LDAP plugin configured for Active Directory
|
|
26
26
|
- [PlugSSO](https://elshaug.xyz/docs/plugsso) using SCIM Gateway
|
|
@@ -157,7 +157,7 @@ If internet connection is blocked, we could install on another machine and copy
|
|
|
157
157
|
|
|
158
158
|
Not needed after a fresh install
|
|
159
159
|
|
|
160
|
-
The best and easiest way to upgrade is renaming existing scimgateway package folder, create a new one and do a fresh installation. After the installation we copy `index.ts, config and lib folder` (customized plugins) from previous installation to the new installation. You should also read the version history to see custom plugins needs to be updated.
|
|
160
|
+
The best and easiest way to upgrade is renaming existing scimgateway package folder, create a new one and do a fresh installation. After the installation we copy `index.ts, config and lib folder` (customized plugins) from previous installation to the new installation. You should also read the version history to see if custom plugins needs to be updated.
|
|
161
161
|
|
|
162
162
|
Alternatives are:
|
|
163
163
|
|
|
@@ -208,7 +208,6 @@ Below shows an example of config\plugin-saphana.json
|
|
|
208
208
|
"scimgateway": {
|
|
209
209
|
"port": 8884,
|
|
210
210
|
"localhostonly": false,
|
|
211
|
-
"payloadSize": null,
|
|
212
211
|
"scim": {
|
|
213
212
|
"version": "2.0",
|
|
214
213
|
"skipTypeConvert" : false,
|
|
@@ -281,18 +280,19 @@ Below shows an example of config\plugin-saphana.json
|
|
|
281
280
|
}
|
|
282
281
|
},
|
|
283
282
|
"ipAllowList": [],
|
|
284
|
-
"
|
|
285
|
-
"
|
|
283
|
+
"email": {
|
|
284
|
+
"auth": {
|
|
285
|
+
"type": "oauth",
|
|
286
|
+
"options": {
|
|
287
|
+
"tenantIdGUID": null,
|
|
288
|
+
"clientId": null,
|
|
289
|
+
"clientSecret": null
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
"emailOnError": {
|
|
286
293
|
"enabled": false,
|
|
287
|
-
"
|
|
288
|
-
"
|
|
289
|
-
"proxy": null,
|
|
290
|
-
"authenticate": true,
|
|
291
|
-
"username": null,
|
|
292
|
-
"password": null,
|
|
293
|
-
"sendInterval": 15,
|
|
294
|
-
"to": null,
|
|
295
|
-
"cc": null
|
|
294
|
+
"from": null,
|
|
295
|
+
"to": null
|
|
296
296
|
}
|
|
297
297
|
},
|
|
298
298
|
"stream": {
|
|
@@ -349,15 +349,15 @@ Definitions in `scimgateway` object have fixed attributes, but values can be mod
|
|
|
349
349
|
|
|
350
350
|
Definitions in `endpoint` object are customized according to our plugin code. Plugin typically need this information for communicating with endpoint
|
|
351
351
|
|
|
352
|
-
- **port** - Gateway will listen on this port number. Clients (e.g. Provisioning Server) will be using this port number for communicating with the gateway
|
|
352
|
+
- **port** - Gateway will listen on this port number. Clients (e.g. Provisioning Server) will be using this port number for communicating with the gateway
|
|
353
353
|
|
|
354
|
-
- **localhostonly** - true or false. False means gateway accepts incoming requests from all clients. True means traffic from only localhost (127.0.0.1) is accepted.
|
|
354
|
+
- **localhostonly** - true or false. False means gateway accepts incoming requests from all clients. True means traffic from only localhost (127.0.0.1) is accepted.
|
|
355
355
|
|
|
356
|
-
- **
|
|
356
|
+
- **idleTimeout** - default 120, sets the the number of seconds to wait before timing out a connection due to inactivity
|
|
357
357
|
|
|
358
|
-
- **scim.version** - "1.1" or "2.0". Default is "2.0".
|
|
358
|
+
- **scim.version** - "1.1" or "2.0". Default is "2.0".
|
|
359
359
|
|
|
360
|
-
- **scim.skipTypeConvert** - true or false, default false. Multivalue attributes supporting types e.g. emails, phoneNumbers, ims, photos, addresses, entitlements and x509Certificates (but not roles, groups and members) will be become "type converted objects" when sent to modifyUser and createUser. This for simplicity of checking attributes included and also for the endpointMapper method (used by plugin-ldap and plugin-entra-id), e.g.:
|
|
360
|
+
- **scim.skipTypeConvert** - true or false, default false. Multivalue attributes supporting types e.g. emails, phoneNumbers, ims, photos, addresses, entitlements and x509Certificates (but not roles, groups and members) will be become "type converted objects" when sent to modifyUser and createUser. This for simplicity of checking attributes included and also for the endpointMapper method (used by plugin-ldap and plugin-entra-id), e.g.:
|
|
361
361
|
|
|
362
362
|
"emails": {
|
|
363
363
|
"work": {"value": "jsmith@example.com", "type": "work"},
|
|
@@ -375,34 +375,34 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
375
375
|
|
|
376
376
|
- **scim.skipMetaLocation** - true or false, default false. If set to true, `meta.location` which contains protocol and hostname from request-url, will be excluded from response e.g. `"{...,meta":{"location":"https://my-company.com/<...>"}}`. If using reverse proxy and not including headers `X-Forwarded-Proto` and `X-Forwarded-Host`, originator will be the proxy and we might not want to expose internal protocol and hostname being used by the proxy request.
|
|
377
377
|
|
|
378
|
-
- **scim.
|
|
378
|
+
- **scim.groupMemberOfUser** - true or false, default false. If body contains groups and groupMemberOfUser=true, groups attribute will remain at user object (groups are member of user) instead of default user member of groups that will use modifyGroup method for maintaining group members.
|
|
379
379
|
|
|
380
380
|
- **scim.usePutSoftSync** - true or false, default false. `PUT /Users/bjensen` will replace the user bjensen with body content. If set to `true`, only PUT body content will be replaced. Any additional existing user attributes and groups supported by plugin will remain as-is.
|
|
381
381
|
|
|
382
|
-
- **log.loglevel.file** - off, error, info, or debug. Output to plugin-logfile e.g. `logs\plugin-saphana.log`
|
|
382
|
+
- **log.loglevel.file** - off, error, info, or debug. Output to plugin-logfile e.g. `logs\plugin-saphana.log`
|
|
383
383
|
|
|
384
|
-
- **log.loglevel.console** - off, error, info, or debug. Output to stdout and errors to stderr.
|
|
384
|
+
- **log.loglevel.console** - off, error, info, or debug. Output to stdout and errors to stderr.
|
|
385
385
|
|
|
386
|
-
- **log.customMasking** - array of attributes to be masked e.g. `"customMasking": ["SSN", "weight"]`. By default SCIM Gateway includes masking of some standard attributes like password.
|
|
386
|
+
- **log.customMasking** - array of attributes to be masked e.g. `"customMasking": ["SSN", "weight"]`. By default SCIM Gateway includes masking of some standard attributes like password.
|
|
387
387
|
|
|
388
388
|
- **auth** - Contains one or more authentication/authorization methods used by clients for accessing gateway - may also include:
|
|
389
389
|
- **auth.xx.readOnly** - true/false, true gives read only access - only allowing `GET` requests for corresponding admin user
|
|
390
390
|
- **auth.xx.baseEntities** - array containing one or more `baseEntity` allowed for this user e.g. ["client-a"] - empty array allowing all.
|
|
391
|
-
**Methods are disabled by setting corresponding admin user to null or remove methods not used**
|
|
391
|
+
**Methods are disabled by setting corresponding admin user to null or remove methods not used**
|
|
392
392
|
|
|
393
|
-
- **auth.basic** - Array of one ore more basic authentication objects - Basic Authentication with **username**/**password**. Note, we set a clear text password that will become encrypted when gateway is started.
|
|
393
|
+
- **auth.basic** - Array of one ore more basic authentication objects - Basic Authentication with **username**/**password**. Note, we set a clear text password that will become encrypted when gateway is started.
|
|
394
394
|
|
|
395
|
-
- **auth.bearerToken** - Array of one or more bearer token objects - Shared token/secret (supported by Entra ID). Clear text value will become encrypted when gateway is started.
|
|
395
|
+
- **auth.bearerToken** - Array of one or more bearer token objects - Shared token/secret (supported by Entra ID). Clear text value will become encrypted when gateway is started.
|
|
396
396
|
|
|
397
|
-
- **auth.bearerJwtAzure** - Array of one or more JWT used by Azure SyncFabric. **tenantIdGUID** must be set to Entra ID Tenant ID.
|
|
397
|
+
- **auth.bearerJwtAzure** - Array of one or more JWT used by Azure SyncFabric. **tenantIdGUID** must be set to Entra ID Tenant ID.
|
|
398
398
|
|
|
399
|
-
- **auth.bearerJwt** - Array of one or more standard JWT objects. Using **secret** or **publicKey** for signature verification. publicKey should be set to the filename of public key or certificate pem-file located in `<package-root>\config\certs` or absolute path being used. Clear text secret will become encrypted when gateway is started. **options.issuer** is mandatory. Other options may also be included according to jsonwebtoken npm package definition.
|
|
399
|
+
- **auth.bearerJwt** - Array of one or more standard JWT objects. Using **secret** or **publicKey** for signature verification. publicKey should be set to the filename of public key or certificate pem-file located in `<package-root>\config\certs` or absolute path being used. Clear text secret will become encrypted when gateway is started. **options.issuer** is mandatory. Other options may also be included according to jsonwebtoken npm package definition.
|
|
400
400
|
|
|
401
|
-
- **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
|
|
401
|
+
- **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
|
|
402
402
|
|
|
403
|
-
- **auth.passThrough** - Setting **auth.passThrough.enabled=true** will bypass SCIM Gateway authentication. Gateway will instead pass ctx containing authentication header to the plugin. Plugin could then use this information for endpoint authentication and we don't have any password/token stored at the gateway. Note, this also requires plugin binary having `scimgateway.authPassThroughAllowed = true` and endpoint logic for handling/passing ctx.request.header.authorization
|
|
403
|
+
- **auth.passThrough** - Setting **auth.passThrough.enabled=true** will bypass SCIM Gateway authentication. Gateway will instead pass ctx containing authentication header to the plugin. Plugin could then use this information for endpoint authentication and we don't have any password/token stored at the gateway. Note, this also requires plugin binary having `scimgateway.authPassThroughAllowed = true` and endpoint logic for handling/passing ctx.request.header.authorization
|
|
404
404
|
|
|
405
|
-
- **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 unless absolute path being defined e.g:
|
|
405
|
+
- **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 unless absolute path being defined e.g:
|
|
406
406
|
|
|
407
407
|
"certificate": {
|
|
408
408
|
"key": "key.pem",
|
|
@@ -436,23 +436,34 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
436
436
|
"2603:1056:2000::/48",
|
|
437
437
|
"2603:1057:2::/48"
|
|
438
438
|
]
|
|
439
|
-
|
|
440
|
-
- **
|
|
441
|
-
- **
|
|
442
|
-
- **
|
|
443
|
-
- **
|
|
444
|
-
- **
|
|
445
|
-
- **
|
|
446
|
-
- **
|
|
447
|
-
- **
|
|
448
|
-
- **
|
|
449
|
-
- **
|
|
450
|
-
- **
|
|
439
|
+
- **email** - Contains configuration for sending email from plugin or automated error notifications emailOnError. Note, for emailOnError only the first error will be sent until sendInterval have passed
|
|
440
|
+
- **email.host** - Mailserver e.g. "smtp.gmail.com" - mandatory when not using tenantIdGUID (Microsoft)
|
|
441
|
+
- **email.port** - Port used by mailserver e.g. 587, 25 or 465 - mandatory when not using tenantIdGUID (Microsoft)
|
|
442
|
+
- **email.auth** - Authentication configuration
|
|
443
|
+
- **email.auth.type** - `basic` or `oauth`
|
|
444
|
+
- **email.auth.options** - Authentication configuration options - note, different options for type basic and oauth
|
|
445
|
+
- **email.auth.options.username (basic)** - Mail account for authentication normally same as sender of the email, e.g. "user@gmail.com"
|
|
446
|
+
- **email.auth.options.password (basic)** - Mail account password
|
|
447
|
+
- **email.auth.options.tenantIdGUID (oauth)** - Entra ID tenant id, mandatory/recommended when using Microsoft Exchange Online
|
|
448
|
+
- **email.auth.options.tokenUrl (oauth)** - Token endpoint, mandatory when not using tenantIdGUID (Microsoft Exchange Online)
|
|
449
|
+
- **email.auth.options.clientId (oauth)** - Client ID
|
|
450
|
+
- **email.auth.options.clientSecret (oauth)** - Client Secret
|
|
451
|
+
- **email.proxy** - Proxy configuration if using mailproxy
|
|
452
|
+
- **email.proxy.host** - Proxy host e.g. `http://proxy-host:1234`
|
|
453
|
+
- **email.proxy.username** - username if authentication is required
|
|
454
|
+
- **email.proxy.password** - password if authentication is required
|
|
455
|
+
- **email.emailOnError** - Contains configuration for sending error notifications by email. Note, only the first error will be sent until sendInterval have passed
|
|
456
|
+
- **email.emailOnError.enabled** - true or false, value set to true will enable email notifications
|
|
457
|
+
- **email.emailOnError.sendInterval** - Default 15. Mail notifications on error are deferred until sendInterval **minutes** have passed since the last notification.
|
|
458
|
+
- **email.emailOnError.from** - Sender email addresses e.g: "noreply@example.com", note must correspond with email.auth.options being used and mailserver configuration
|
|
459
|
+
- **email.emailOnError.to** - Comma separated list of recipients email addresses e.g: "someone@example.com"
|
|
460
|
+
- **email.emailOnError.cc** - Optional comma separated list of cc mail addresses
|
|
461
|
+
- **email.emailOnError.subject** - Optional mail subject, default `SCIM Gateway error message`
|
|
451
462
|
|
|
452
463
|
- **stream** - See [SCIM Stream](https://elshaug.xyz/docs/scim-stream) for configuration details
|
|
453
464
|
|
|
454
|
-
- **endpoint** - Contains endpoint specific configuration according to our **plugin code**.
|
|
455
|
-
|
|
465
|
+
- **endpoint** - Contains endpoint specific configuration according to our **plugin code**.
|
|
466
|
+
|
|
456
467
|
#### Configuration notes
|
|
457
468
|
|
|
458
469
|
- Custom Schemas, ServiceProviderConfig and ResourceType can be used if `./lib/scimdef-v2.json or scimdef-v1.json` exists. Original scimdef-v2.json/scimdef-v1.json can be copied from node_modules/scimgateway/lib to your plugin/lib and customized.
|
|
@@ -1086,6 +1097,21 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1086
1097
|
|
|
1087
1098
|
## Change log
|
|
1088
1099
|
|
|
1100
|
+
### v5.0.6
|
|
1101
|
+
|
|
1102
|
+
[Improved]
|
|
1103
|
+
|
|
1104
|
+
- new configuration option: `scimgateway.idleTimeout` default 120, sets the the number of seconds to wait before timing out a connection due to inactivity
|
|
1105
|
+
- new configuration option: `scimgateway.email` replacing legacy `scimgateway.emailOnError` (legacy still supported). Email now support oauth authentication configuration which is default and recommended for Microsoft Exchange Online.
|
|
1106
|
+
- removed configuration option: `scimgateway.payloadSize` Bun using default maxRequestBodySize 128MB
|
|
1107
|
+
- plugin may send email using method scimgateway.sendMail()
|
|
1108
|
+
|
|
1109
|
+
### v5.0.5
|
|
1110
|
+
|
|
1111
|
+
[Fixed]
|
|
1112
|
+
|
|
1113
|
+
- plugin-ldap, dn special character not correct for ascii code 128(dec)/80(hex)
|
|
1114
|
+
|
|
1089
1115
|
### v5.0.4
|
|
1090
1116
|
|
|
1091
1117
|
[Improved]
|
|
@@ -1098,7 +1124,7 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1098
1124
|
|
|
1099
1125
|
- unauthorized connection when using configuration bearerJwtAzure
|
|
1100
1126
|
|
|
1101
|
-
[Improved]
|
|
1127
|
+
[Improved]
|
|
1102
1128
|
|
|
1103
1129
|
- minor type definition cosmetics
|
|
1104
1130
|
|
package/config/plugin-api.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"scimgateway": {
|
|
3
3
|
"port": 8890,
|
|
4
4
|
"localhostonly": false,
|
|
5
|
-
"payloadSize": null,
|
|
6
5
|
"scim": {
|
|
7
6
|
"version": "2.0",
|
|
8
7
|
"skipTypeConvert": false,
|
|
@@ -75,18 +74,19 @@
|
|
|
75
74
|
}
|
|
76
75
|
},
|
|
77
76
|
"ipAllowList": [],
|
|
78
|
-
"
|
|
79
|
-
"
|
|
77
|
+
"email": {
|
|
78
|
+
"auth": {
|
|
79
|
+
"type": "oauth",
|
|
80
|
+
"options": {
|
|
81
|
+
"tenantIdGUID": null,
|
|
82
|
+
"clientId": null,
|
|
83
|
+
"clientSecret": null
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"emailOnError": {
|
|
80
87
|
"enabled": false,
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"proxy": null,
|
|
84
|
-
"authenticate": true,
|
|
85
|
-
"username": null,
|
|
86
|
-
"password": null,
|
|
87
|
-
"sendInterval": 15,
|
|
88
|
-
"to": null,
|
|
89
|
-
"cc": null
|
|
88
|
+
"from": null,
|
|
89
|
+
"to": null
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"stream": {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"scimgateway": {
|
|
3
3
|
"port": 8881,
|
|
4
4
|
"localhostonly": false,
|
|
5
|
-
"payloadSize": null,
|
|
6
5
|
"scim": {
|
|
7
6
|
"version": "2.0",
|
|
8
7
|
"customSchema": null,
|
|
@@ -75,18 +74,56 @@
|
|
|
75
74
|
}
|
|
76
75
|
},
|
|
77
76
|
"ipAllowList": [],
|
|
78
|
-
"
|
|
79
|
-
"
|
|
77
|
+
"email": {
|
|
78
|
+
"auth": {
|
|
79
|
+
"type": "oauth",
|
|
80
|
+
"options": {
|
|
81
|
+
"tenantIdGUID": null,
|
|
82
|
+
"clientId": null,
|
|
83
|
+
"clientSecret": null
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"emailOnError": {
|
|
87
|
+
"enabled": false,
|
|
88
|
+
"from": null,
|
|
89
|
+
"to": null
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"stream": {
|
|
93
|
+
"baseUrls": [],
|
|
94
|
+
"certificate": {
|
|
95
|
+
"ca": null
|
|
96
|
+
},
|
|
97
|
+
"subscriber": {
|
|
80
98
|
"enabled": false,
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
"entity": {
|
|
100
|
+
"undefined": {
|
|
101
|
+
"nats": {
|
|
102
|
+
"tenant": null,
|
|
103
|
+
"subject": null,
|
|
104
|
+
"jwt": null,
|
|
105
|
+
"secret": null
|
|
106
|
+
},
|
|
107
|
+
"deleteUserOnLastGroupRoleRemoval": false,
|
|
108
|
+
"skipConvertRolesToGroups": false,
|
|
109
|
+
"generateUserPassword": false,
|
|
110
|
+
"modifyOnly": false,
|
|
111
|
+
"replaceDomains": []
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"publisher": {
|
|
116
|
+
"enabled": false,
|
|
117
|
+
"entity": {
|
|
118
|
+
"undefined": {
|
|
119
|
+
"nats": {
|
|
120
|
+
"tenant": null,
|
|
121
|
+
"subject": null,
|
|
122
|
+
"jwt": null,
|
|
123
|
+
"secret": null
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
90
127
|
}
|
|
91
128
|
}
|
|
92
129
|
},
|
package/config/plugin-ldap.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"scimgateway": {
|
|
3
3
|
"port": 8883,
|
|
4
4
|
"localhostonly": false,
|
|
5
|
-
"payloadSize": null,
|
|
6
5
|
"scim": {
|
|
7
6
|
"version": "2.0",
|
|
8
7
|
"skipTypeConvert": false,
|
|
@@ -75,18 +74,19 @@
|
|
|
75
74
|
}
|
|
76
75
|
},
|
|
77
76
|
"ipAllowList": [],
|
|
78
|
-
"
|
|
79
|
-
"
|
|
77
|
+
"email": {
|
|
78
|
+
"auth": {
|
|
79
|
+
"type": "oauth",
|
|
80
|
+
"options": {
|
|
81
|
+
"tenantIdGUID": null,
|
|
82
|
+
"clientId": null,
|
|
83
|
+
"clientSecret": null
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"emailOnError": {
|
|
80
87
|
"enabled": false,
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"proxy": null,
|
|
84
|
-
"authenticate": true,
|
|
85
|
-
"username": null,
|
|
86
|
-
"password": null,
|
|
87
|
-
"sendInterval": 15,
|
|
88
|
-
"to": null,
|
|
89
|
-
"cc": null
|
|
88
|
+
"from": null,
|
|
89
|
+
"to": null
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"stream": {
|
|
@@ -275,4 +275,4 @@
|
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
|
-
}
|
|
278
|
+
}
|
package/config/plugin-loki.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"scimgateway": {
|
|
3
3
|
"port": 8880,
|
|
4
4
|
"localhostonly": false,
|
|
5
|
-
"payloadSize": null,
|
|
6
5
|
"scim": {
|
|
7
6
|
"version": "2.0",
|
|
8
7
|
"skipTypeConvert": false,
|
|
@@ -75,18 +74,19 @@
|
|
|
75
74
|
}
|
|
76
75
|
},
|
|
77
76
|
"ipAllowList": [],
|
|
78
|
-
"
|
|
79
|
-
"
|
|
77
|
+
"email": {
|
|
78
|
+
"auth": {
|
|
79
|
+
"type": "oauth",
|
|
80
|
+
"options": {
|
|
81
|
+
"tenantIdGUID": null,
|
|
82
|
+
"clientId": null,
|
|
83
|
+
"clientSecret": null
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"emailOnError": {
|
|
80
87
|
"enabled": false,
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"proxy": null,
|
|
84
|
-
"authenticate": true,
|
|
85
|
-
"username": null,
|
|
86
|
-
"password": null,
|
|
87
|
-
"sendInterval": 15,
|
|
88
|
-
"to": null,
|
|
89
|
-
"cc": null
|
|
88
|
+
"from": null,
|
|
89
|
+
"to": null
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"stream": {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"scimgateway": {
|
|
3
3
|
"port": 8885,
|
|
4
4
|
"localhostonly": false,
|
|
5
|
-
"payloadSize": null,
|
|
6
5
|
"scim": {
|
|
7
6
|
"version": "2.0",
|
|
8
7
|
"skipTypeConvert": false,
|
|
@@ -81,18 +80,19 @@
|
|
|
81
80
|
}
|
|
82
81
|
},
|
|
83
82
|
"ipAllowList": [],
|
|
84
|
-
"
|
|
85
|
-
"
|
|
83
|
+
"email": {
|
|
84
|
+
"auth": {
|
|
85
|
+
"type": "oauth",
|
|
86
|
+
"options": {
|
|
87
|
+
"tenantIdGUID": null,
|
|
88
|
+
"clientId": null,
|
|
89
|
+
"clientSecret": null
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"emailOnError": {
|
|
86
93
|
"enabled": false,
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"proxy": null,
|
|
90
|
-
"authenticate": true,
|
|
91
|
-
"username": null,
|
|
92
|
-
"password": null,
|
|
93
|
-
"sendInterval": 15,
|
|
94
|
-
"to": null,
|
|
95
|
-
"cc": null
|
|
94
|
+
"from": null,
|
|
95
|
+
"to": null
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"stream": {
|
|
@@ -151,4 +151,4 @@
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
-
}
|
|
154
|
+
}
|
package/config/plugin-mssql.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"scimgateway": {
|
|
3
3
|
"port": 8888,
|
|
4
4
|
"localhostonly": false,
|
|
5
|
-
"payloadSize": null,
|
|
6
5
|
"scim": {
|
|
7
6
|
"version": "2.0",
|
|
8
7
|
"skipTypeConvert": false,
|
|
@@ -75,18 +74,19 @@
|
|
|
75
74
|
}
|
|
76
75
|
},
|
|
77
76
|
"ipAllowList": [],
|
|
78
|
-
"
|
|
79
|
-
"
|
|
77
|
+
"email": {
|
|
78
|
+
"auth": {
|
|
79
|
+
"type": "oauth",
|
|
80
|
+
"options": {
|
|
81
|
+
"tenantIdGUID": null,
|
|
82
|
+
"clientId": null,
|
|
83
|
+
"clientSecret": null
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"emailOnError": {
|
|
80
87
|
"enabled": false,
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"proxy": null,
|
|
84
|
-
"authenticate": true,
|
|
85
|
-
"username": null,
|
|
86
|
-
"password": null,
|
|
87
|
-
"sendInterval": 15,
|
|
88
|
-
"to": null,
|
|
89
|
-
"cc": null
|
|
88
|
+
"from": null,
|
|
89
|
+
"to": null
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"stream": {
|
|
@@ -133,18 +133,18 @@
|
|
|
133
133
|
"authentication": {
|
|
134
134
|
"type": "default",
|
|
135
135
|
"options": {
|
|
136
|
-
"userName": "
|
|
136
|
+
"userName": "scimgateway",
|
|
137
137
|
"password": "password"
|
|
138
138
|
}
|
|
139
139
|
},
|
|
140
140
|
"options": {
|
|
141
141
|
"instanceName": "",
|
|
142
142
|
"port": 1433,
|
|
143
|
-
"database": "
|
|
143
|
+
"database": "scimgateway",
|
|
144
144
|
"useColumnNames": true,
|
|
145
145
|
"rowCollectionOnRequestCompletion": true,
|
|
146
146
|
"encrypt": false
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
}
|
|
150
|
+
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"scimgateway": {
|
|
3
3
|
"port": 8884,
|
|
4
4
|
"localhostonly": false,
|
|
5
|
-
"payloadSize": null,
|
|
6
5
|
"scim": {
|
|
7
6
|
"version": "2.0",
|
|
8
7
|
"skipTypeConvert": false,
|
|
@@ -75,18 +74,19 @@
|
|
|
75
74
|
}
|
|
76
75
|
},
|
|
77
76
|
"ipAllowList": [],
|
|
78
|
-
"
|
|
79
|
-
"
|
|
77
|
+
"email": {
|
|
78
|
+
"auth": {
|
|
79
|
+
"type": "oauth",
|
|
80
|
+
"options": {
|
|
81
|
+
"tenantIdGUID": null,
|
|
82
|
+
"clientId": null,
|
|
83
|
+
"clientSecret": null
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"emailOnError": {
|
|
80
87
|
"enabled": false,
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"proxy": null,
|
|
84
|
-
"authenticate": true,
|
|
85
|
-
"username": null,
|
|
86
|
-
"password": null,
|
|
87
|
-
"sendInterval": 15,
|
|
88
|
-
"to": null,
|
|
89
|
-
"cc": null
|
|
88
|
+
"from": null,
|
|
89
|
+
"to": null
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"stream": {
|
|
@@ -134,4 +134,4 @@
|
|
|
134
134
|
"password": "password",
|
|
135
135
|
"saml_provider": "saml_provider_name"
|
|
136
136
|
}
|
|
137
|
-
}
|
|
137
|
+
}
|
package/config/plugin-scim.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"scimgateway": {
|
|
3
3
|
"port": 8886,
|
|
4
4
|
"localhostonly": false,
|
|
5
|
-
"payloadSize": null,
|
|
6
5
|
"scim": {
|
|
7
6
|
"version": "2.0",
|
|
8
7
|
"skipTypeConvert": false,
|
|
@@ -75,18 +74,19 @@
|
|
|
75
74
|
}
|
|
76
75
|
},
|
|
77
76
|
"ipAllowList": [],
|
|
78
|
-
"
|
|
79
|
-
"
|
|
77
|
+
"email": {
|
|
78
|
+
"auth": {
|
|
79
|
+
"type": "oauth",
|
|
80
|
+
"options": {
|
|
81
|
+
"tenantIdGUID": null,
|
|
82
|
+
"clientId": null,
|
|
83
|
+
"clientSecret": null
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"emailOnError": {
|
|
80
87
|
"enabled": false,
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"proxy": null,
|
|
84
|
-
"authenticate": true,
|
|
85
|
-
"username": null,
|
|
86
|
-
"password": null,
|
|
87
|
-
"sendInterval": 15,
|
|
88
|
-
"to": null,
|
|
89
|
-
"cc": null
|
|
88
|
+
"from": null,
|
|
89
|
+
"to": null
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"stream": {
|
package/config/plugin-soap.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"scimgateway": {
|
|
3
3
|
"port": 8882,
|
|
4
4
|
"localhostonly": false,
|
|
5
|
-
"payloadSize": null,
|
|
6
5
|
"scim": {
|
|
7
6
|
"version": "2.0",
|
|
8
7
|
"skipTypeConvert": false,
|
|
@@ -75,18 +74,19 @@
|
|
|
75
74
|
}
|
|
76
75
|
},
|
|
77
76
|
"ipAllowList": [],
|
|
78
|
-
"
|
|
79
|
-
"
|
|
77
|
+
"email": {
|
|
78
|
+
"auth": {
|
|
79
|
+
"type": "oauth",
|
|
80
|
+
"options": {
|
|
81
|
+
"tenantIdGUID": null,
|
|
82
|
+
"clientId": null,
|
|
83
|
+
"clientSecret": null
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"emailOnError": {
|
|
80
87
|
"enabled": false,
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"proxy": null,
|
|
84
|
-
"authenticate": true,
|
|
85
|
-
"username": null,
|
|
86
|
-
"password": null,
|
|
87
|
-
"sendInterval": 15,
|
|
88
|
-
"to": null,
|
|
89
|
-
"cc": null
|
|
88
|
+
"from": null,
|
|
89
|
+
"to": null
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"stream": {
|
package/lib/helper-rest.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Buffer } from 'node:buffer'
|
|
|
4
4
|
import fs from 'node:fs'
|
|
5
5
|
import querystring from 'querystring'
|
|
6
6
|
import * as utils from './utils.ts'
|
|
7
|
-
import
|
|
7
|
+
import ScimGateway from 'scimgateway'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* HelperRest includes function doRequest() for doing REST calls
|
|
@@ -14,12 +14,16 @@ export class HelperRest {
|
|
|
14
14
|
private _serviceClient: Record<string, any> = {}
|
|
15
15
|
private config_entity: any
|
|
16
16
|
private scimgateway: ScimGateway
|
|
17
|
+
private idleTimeout: number
|
|
17
18
|
private graphUrl = 'https://graph.microsoft.com/beta' // beta instead of 'v1.0' gives all user attributes when no $select
|
|
18
19
|
|
|
19
|
-
constructor(scimgateway: ScimGateway) {
|
|
20
|
+
constructor(scimgateway: ScimGateway, optionalEntities?: Record<string, any>) {
|
|
21
|
+
if (!(scimgateway instanceof ScimGateway)) throw new Error('HelperRest initialization error: argument scimgateway is not of type ScimGateway')
|
|
20
22
|
this.scimgateway = scimgateway
|
|
21
|
-
|
|
22
|
-
this.
|
|
23
|
+
this.idleTimeout = (scimgateway as any)?.config?.scimgateway.idleTimeout || 120
|
|
24
|
+
this.idleTimeout = this.idleTimeout - 1
|
|
25
|
+
if (optionalEntities && optionalEntities.entity) this.config_entity = utils.copyObj(optionalEntities.entity)
|
|
26
|
+
else this.config_entity = utils.copyObj(scimgateway.getConfig())?.entity
|
|
23
27
|
let entityFound = false
|
|
24
28
|
let connectionFound = false
|
|
25
29
|
for (const baseEntity in this.config_entity) {
|
|
@@ -31,15 +35,12 @@ export class HelperRest {
|
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
connectionFound = true
|
|
34
|
-
if (!this.config_entity[baseEntity].connection.baseUrls
|
|
35
|
-
|| !Array.isArray(this.config_entity[baseEntity].connection.baseUrls)
|
|
36
|
-
|| this.config_entity[baseEntity].connection.baseUrls.length < 1) {
|
|
37
|
-
throw new Error('HelperRest initialization error: missing configuration \'endpoint.entity.<name>.connection.baseUrls\'')
|
|
38
|
-
}
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
|
-
|
|
42
|
-
if (!
|
|
40
|
+
let errMsg = ''
|
|
41
|
+
if (!entityFound) errMsg = 'HelperRest initialization error: missing configuration \'endpoint.entity.<name>\''
|
|
42
|
+
else if (!connectionFound) errMsg = 'HelperRest initialization error: missing configuration \'endpoint.entity.<name>.connection\''
|
|
43
|
+
if (errMsg) this.scimgateway.logError('undefined', errMsg)
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
@@ -68,12 +69,12 @@ export class HelperRest {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
|
-
* getAccessToken returns oauth accesstoken
|
|
72
|
+
* getAccessToken returns oauth accesstoken object
|
|
72
73
|
* @param baseEntity
|
|
73
74
|
* @param ctx
|
|
74
|
-
* @returns oauth accesstoken
|
|
75
|
+
* @returns oauth accesstoken object
|
|
75
76
|
*/
|
|
76
|
-
|
|
77
|
+
public async getAccessToken(baseEntity: string, ctx?: Record<string, any> | undefined) { // public in case token is needed for other logic e.g. sending mail
|
|
77
78
|
await this.lock.acquire()
|
|
78
79
|
const clientIdentifier = this.getClientIdentifier(ctx)
|
|
79
80
|
const d = Math.floor(Date.now() / 1000) // seconds (unix time)
|
|
@@ -417,7 +418,7 @@ export class HelperRest {
|
|
|
417
418
|
} else delete options.headers['Content-Type']
|
|
418
419
|
const controller = new AbortController()
|
|
419
420
|
const signal = controller.signal
|
|
420
|
-
const timeout = setTimeout(() => controller.abort(), options.abortTimeout ? options.abortTimeout * 1000 :
|
|
421
|
+
const timeout = setTimeout(() => controller.abort(), options.abortTimeout ? options.abortTimeout * 1000 : this.idleTimeout * 1000) // 120 seconds default abort timeout
|
|
421
422
|
options.signal = signal
|
|
422
423
|
const url = `${options.protocol}//${options.host}${options.port ? ':' + options.port : ''}${options.path}`
|
|
423
424
|
// execute request
|
|
@@ -486,7 +487,7 @@ export class HelperRest {
|
|
|
486
487
|
if (!retryCount) retryCount = 0
|
|
487
488
|
let urlObj
|
|
488
489
|
try { urlObj = new URL(path) } catch (err) { void 0 }
|
|
489
|
-
if (!urlObj && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT' || retryAfter)) {
|
|
490
|
+
if (!urlObj && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ABORT_ERR' || err.code === 'ETIMEDOUT' || retryAfter)) {
|
|
490
491
|
if (retryAfter) {
|
|
491
492
|
this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} throttle/ratelimit error - awaiting ${retryAfter} seconds before automatic retry`)
|
|
492
493
|
await new Promise(resolve => setTimeout(function () {
|
package/lib/logger.ts
CHANGED
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
// Author: Jarle Elshaug
|
|
5
5
|
// ==============================================================
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
// import winston, { Logger } from 'winston'
|
|
9
|
-
import winston, { Logger } from 'winston'
|
|
7
|
+
import winston, { Logger } from 'winston' // level: silly=0, debug=1, verbose=2, info=3, warn=4, error=5
|
|
10
8
|
|
|
11
9
|
// Wrong time if not setting timestamp. Timezone did not work.
|
|
12
10
|
// moment-timezone is also an alternative to the timestamp() function.
|
package/lib/plugin-ldap.ts
CHANGED
|
@@ -1452,7 +1452,7 @@ const doRequest = async (baseEntity: string, method: string, base: any, options:
|
|
|
1452
1452
|
obj.dn = obj.dn.replace(/\\\\/g, '__') // temp
|
|
1453
1453
|
let conv = obj.dn.replace(/\\([0-9A-Fa-f]{2})/g, (_: any, hex: any) => {
|
|
1454
1454
|
const intAscii = parseInt(hex, 16)
|
|
1455
|
-
if (intAscii >
|
|
1455
|
+
if (intAscii > 127) { // extended ascii - will be unescaped by decodeURIComponent
|
|
1456
1456
|
return '%' + hex
|
|
1457
1457
|
} else { // use character escape
|
|
1458
1458
|
return '\\' + String.fromCharCode(intAscii)
|
package/lib/scimgateway.ts
CHANGED
|
@@ -27,6 +27,7 @@ import * as utils from './utils.ts'
|
|
|
27
27
|
import * as utilsScim from './utils-scim.ts'
|
|
28
28
|
import * as stream from './scim-stream.js'
|
|
29
29
|
export * from './helper-rest.ts'
|
|
30
|
+
import { HelperRest } from './helper-rest.ts'
|
|
30
31
|
|
|
31
32
|
export class ScimGateway {
|
|
32
33
|
private config: any
|
|
@@ -39,6 +40,8 @@ export class ScimGateway {
|
|
|
39
40
|
private getMemberOf: any
|
|
40
41
|
private getAppRoles: any
|
|
41
42
|
private pub: any
|
|
43
|
+
// @ts-expect-error: has no initializer
|
|
44
|
+
private helperRest: HelperRest
|
|
42
45
|
/** pluginName is the name of plugin e.g., plugin-loki */
|
|
43
46
|
readonly pluginName: string
|
|
44
47
|
/** configDir is full path to plugin ./config directory */
|
|
@@ -402,12 +405,50 @@ export class ScimGateway {
|
|
|
402
405
|
this.config.scimgateway.auth.oauthTokenStore = {}
|
|
403
406
|
if (!this.config.scimgateway.certificate) this.config.scimgateway.certificate = {}
|
|
404
407
|
if (!this.config.scimgateway.certificate.pfx) this.config.scimgateway.certificate.pfx = {}
|
|
405
|
-
|
|
406
|
-
if (!this.config.scimgateway.
|
|
408
|
+
|
|
409
|
+
if (!this.config.scimgateway.email) this.config.scimgateway.email = {}
|
|
410
|
+
if (!this.config.scimgateway.email.auth) this.config.scimgateway.email.auth = {}
|
|
411
|
+
if (!this.config.scimgateway.email.auth.options) this.config.scimgateway.email.auth.options = {}
|
|
412
|
+
if (!this.config.scimgateway.email.emailOnError) this.config.scimgateway.email.emailOnError = {}
|
|
413
|
+
|
|
407
414
|
if (!this.config.scimgateway.stream) this.config.scimgateway.stream = {}
|
|
408
415
|
if (!this.config.scimgateway.stream.subscriber) this.config.scimgateway.stream.subscriber = {}
|
|
409
416
|
if (!this.config.scimgateway.stream.publisher) this.config.scimgateway.stream.publisher = {}
|
|
410
417
|
|
|
418
|
+
// start - legacy support
|
|
419
|
+
if (!this.config.scimgateway.emailOnError) this.config.scimgateway.emailOnError = {}
|
|
420
|
+
if (!this.config.scimgateway.emailOnError.smtp) this.config.scimgateway.emailOnError.smtp = {}
|
|
421
|
+
if (this.config.scimgateway.emailOnError.smtp.host) {
|
|
422
|
+
this.config.scimgateway.email.host = this.config.scimgateway.emailOnError.smtp.host
|
|
423
|
+
}
|
|
424
|
+
if (this.config.scimgateway.emailOnError.smtp.port) {
|
|
425
|
+
this.config.scimgateway.email.port = this.config.scimgateway.emailOnError.smtp.port
|
|
426
|
+
}
|
|
427
|
+
if (this.config.scimgateway.emailOnError.smtp.proxy) {
|
|
428
|
+
this.config.scimgateway.email.proxy = this.config.scimgateway.emailOnError.smtp.proxy
|
|
429
|
+
}
|
|
430
|
+
if (this.config.scimgateway.emailOnError.smtp.username) {
|
|
431
|
+
this.config.scimgateway.email.emailOnError.from = this.config.scimgateway.emailOnError.smtp.username
|
|
432
|
+
this.config.scimgateway.email.auth.options.username = this.config.scimgateway.emailOnError.smtp.username
|
|
433
|
+
}
|
|
434
|
+
if (this.config.scimgateway.emailOnError.smtp.password) {
|
|
435
|
+
this.config.scimgateway.email.auth.options.password = this.config.scimgateway.emailOnError.smtp.password
|
|
436
|
+
this.config.scimgateway.email.auth.type = 'basic'
|
|
437
|
+
}
|
|
438
|
+
if (this.config.scimgateway.emailOnError.smtp.enabled) {
|
|
439
|
+
this.config.scimgateway.email.emailOnError.enabled = this.config.scimgateway.emailOnError.smtp.enabled
|
|
440
|
+
}
|
|
441
|
+
if (this.config.scimgateway.emailOnError.smtp.sendInterval) {
|
|
442
|
+
this.config.scimgateway.email.emailOnError.sendInterval = this.config.scimgateway.emailOnError.smtp.sendInterval
|
|
443
|
+
}
|
|
444
|
+
if (this.config.scimgateway.emailOnError.smtp.to) {
|
|
445
|
+
this.config.scimgateway.email.emailOnError.to = this.config.scimgateway.emailOnError.smtp.to
|
|
446
|
+
}
|
|
447
|
+
if (this.config.scimgateway.emailOnError.smtp.cc) {
|
|
448
|
+
this.config.scimgateway.email.emailOnError.cc = this.config.scimgateway.emailOnError.smtp.cc
|
|
449
|
+
}
|
|
450
|
+
// end - legacy support
|
|
451
|
+
|
|
411
452
|
if (this.config.scimgateway.ipAllowList && Array.isArray(this.config.scimgateway.ipAllowList) && this.config.scimgateway.ipAllowList.length > 0) {
|
|
412
453
|
ipAllowListChecker = createChecker(this.config.scimgateway.ipAllowList)
|
|
413
454
|
}
|
|
@@ -896,7 +937,7 @@ export class ScimGateway {
|
|
|
896
937
|
const baseEntity = ctx.routeObj.baseEntity
|
|
897
938
|
const id = decodeURIComponent(path.basename(ctx.routeObj.id || '', '.json')) // supports <id>.json
|
|
898
939
|
|
|
899
|
-
if (!id
|
|
940
|
+
if (!id) {
|
|
900
941
|
ctx.response.status = 500
|
|
901
942
|
const err = new Error('missing id')
|
|
902
943
|
const [e, customErrorCode] = utilsScim.jsonErr(this.config.scimgateway.scim.version, pluginName, ctx.response.status, err)
|
|
@@ -2119,7 +2160,7 @@ export class ScimGateway {
|
|
|
2119
2160
|
const attributes = ['id', 'displayName']
|
|
2120
2161
|
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling "${handler.groups.getMethod}" and awaiting result - groups to be included`)
|
|
2121
2162
|
res = await (this as any)[handler.groups.getMethod](baseEntity, ob, attributes, ctxPassThrough)
|
|
2122
|
-
} catch (err) {
|
|
2163
|
+
} catch (err) { void 0 }
|
|
2123
2164
|
if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length > 0) {
|
|
2124
2165
|
for (let i = 0; i < res.Resources.length; i++) {
|
|
2125
2166
|
if (!res.Resources[i].id) continue
|
|
@@ -2358,14 +2399,14 @@ export class ScimGateway {
|
|
|
2358
2399
|
hostname = 'localhost'
|
|
2359
2400
|
}
|
|
2360
2401
|
try {
|
|
2361
|
-
|
|
2402
|
+
// using fs.readFileSync() instead of Bun.file() for nodejs compability
|
|
2362
2403
|
if (this.config.scimgateway?.certificate?.key && this.config.scimgateway?.certificate?.cert) {
|
|
2363
|
-
|
|
2404
|
+
// TLS
|
|
2364
2405
|
tls.key = this.config.scimgateway.certificate.key ? fs.readFileSync(this.config.scimgateway.certificate.key) : undefined
|
|
2365
2406
|
tls.cert = this.config.scimgateway.certificate.cert ? fs.readFileSync(this.config.scimgateway.certificate.cert) : undefined
|
|
2366
|
-
|
|
2407
|
+
// loading tls.ca would require client certificates to be used
|
|
2367
2408
|
} else if (this.config.scimgateway?.certificate?.pfx && this.config.scimgateway?.certificate?.pfx?.bundle) {
|
|
2368
|
-
|
|
2409
|
+
// TLS PFX / PKCS#12
|
|
2369
2410
|
tls.pfx = this.config.scimgateway.certificate.pfx.bundle ? fs.readFileSync(this.config.scimgateway.certificate.pfx.bundle) : undefined
|
|
2370
2411
|
tls.passphrase = this.config.scimgateway.certificate.pfx.password ? utils.getSecret('scimgateway.certificate.pfx.password', this.configFile) : undefined
|
|
2371
2412
|
}
|
|
@@ -2439,6 +2480,7 @@ export class ScimGateway {
|
|
|
2439
2480
|
server = Bun.serve({
|
|
2440
2481
|
port: this.config.scimgateway.port,
|
|
2441
2482
|
reusePort: false,
|
|
2483
|
+
idleTimeout: this.config.scimgateway.idleTimeout || 120,
|
|
2442
2484
|
hostname, // hostname === 'localhost' ? hostname : undefined, // bun defaults to '0.0.0.0', but using '0.0.0.0.' or other ip like '127.0.0.1' becomes extremly slow - bun bug
|
|
2443
2485
|
tls,
|
|
2444
2486
|
async fetch(req, srv) {
|
|
@@ -2583,49 +2625,27 @@ export class ScimGateway {
|
|
|
2583
2625
|
logger.setLoglevelConsole(this.config?.scimgateway?.log?.loglevel?.console) // revert temporary info console loglevel, use config
|
|
2584
2626
|
|
|
2585
2627
|
logger.setEmailOnError(async (msg: string) => { // logger sending email on error
|
|
2586
|
-
if (!this.config.scimgateway.
|
|
2628
|
+
if (!(this.config.scimgateway.email.emailOnError.enabled === true) || isMailLock) return null // not sending mail
|
|
2587
2629
|
isMailLock = true
|
|
2588
2630
|
|
|
2589
2631
|
setTimeout(function () { // release lock after "sendInterval" minutes
|
|
2590
2632
|
isMailLock = false
|
|
2591
|
-
}, (this.config.scimgateway.emailOnError.
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
smtpConfig.auth = {}
|
|
2608
|
-
smtpConfig.auth.user = this.config.scimgateway.emailOnError.smtp.username
|
|
2609
|
-
smtpConfig.auth.pass = this.config.scimgateway.emailOnError.smtp.password
|
|
2610
|
-
}
|
|
2611
|
-
|
|
2612
|
-
const transporter = nodemailer.createTransport(smtpConfig)
|
|
2613
|
-
const mailOptions = {
|
|
2614
|
-
from: this.config.scimgateway.emailOnError.smtp.username, // sender address
|
|
2615
|
-
to: this.config.scimgateway.emailOnError.smtp.to, // list of receivers - comma separated
|
|
2616
|
-
cc: this.config.scimgateway.emailOnError.smtp.cc,
|
|
2617
|
-
subject: 'SCIM Gateway error message',
|
|
2618
|
-
html: bodyHtml, // 'text': bodyText
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
const smtp_to = this.config.scimgateway.emailOnError.smtp.to
|
|
2622
|
-
const smtp_cc = this.config.scimgateway.emailOnError.smtp.cc
|
|
2623
|
-
transporter.sendMail(mailOptions, function (err) {
|
|
2624
|
-
if (err != null) logger.error(`${gwName}[${pluginName}] mailOnError sending failed: ${err.message}`)
|
|
2625
|
-
else logger.debug(`${gwName}[${pluginName}] mailOnError sent to: ${smtp_to}${(smtp_cc) ? ',' + smtp_cc : ''}`)
|
|
2626
|
-
})
|
|
2627
|
-
return null
|
|
2628
|
-
}) // emailOnError
|
|
2633
|
+
}, (this.config.scimgateway.email.emailOnError.sendInterval || 15) * 1000 * 60)
|
|
2634
|
+
const msgHtml = `<html><body>
|
|
2635
|
+
<p>${msg}</p>
|
|
2636
|
+
<br>
|
|
2637
|
+
<p><strong>This is an automatically generated email - please do NOT reply to this email or forward to others</strong></p>
|
|
2638
|
+
</body></html>`
|
|
2639
|
+
|
|
2640
|
+
const msgObj = {
|
|
2641
|
+
from: this.config.scimgateway.email.emailOnError.from,
|
|
2642
|
+
to: this.config.scimgateway.email.emailOnError.to,
|
|
2643
|
+
cc: this.config.scimgateway.email.emailOnError.cc,
|
|
2644
|
+
subject: this.config.scimgateway.email.emailOnError.subject ? this.config.scimgateway.email.emailOnError.subject : 'SCIM Gateway error message',
|
|
2645
|
+
content: msgHtml,
|
|
2646
|
+
}
|
|
2647
|
+
this.sendMail(msgObj, true)
|
|
2648
|
+
})
|
|
2629
2649
|
|
|
2630
2650
|
const gracefulShutdown = async function () {
|
|
2631
2651
|
if (server) {
|
|
@@ -2642,6 +2662,7 @@ export class ScimGateway {
|
|
|
2642
2662
|
if (typeof Bun !== 'undefined') {
|
|
2643
2663
|
await Bun.sleep(400) // give in-flight requests a chance to complete, also plugins may use SIGTERM/SIGINT
|
|
2644
2664
|
server.stop()
|
|
2665
|
+
process.exit(0)
|
|
2645
2666
|
} else {
|
|
2646
2667
|
server.close(function () {
|
|
2647
2668
|
setTimeout(function () { // plugins may also use SIGTERM/SIGINT
|
|
@@ -2800,6 +2821,205 @@ export class ScimGateway {
|
|
|
2800
2821
|
*/
|
|
2801
2822
|
endpointMapper = utilsScim.endpointMapper
|
|
2802
2823
|
|
|
2824
|
+
/**
|
|
2825
|
+
* sendMail sends a mail using scimgateway.email configuraration
|
|
2826
|
+
* @param msgObj mail object
|
|
2827
|
+
* @param isHtml set to true if msgObj.content is HTML encoded, else false for plain text
|
|
2828
|
+
* @remarks
|
|
2829
|
+
* msgObj example:
|
|
2830
|
+
* ```
|
|
2831
|
+
* {
|
|
2832
|
+
* from: 'firstname.lastname@company.com',
|
|
2833
|
+
* to: 'servicedesk@company.com',
|
|
2834
|
+
* cc: 'operators@company.com',
|
|
2835
|
+
* subject: 'SCIM Gateway message',
|
|
2836
|
+
* content: '<html><body><p>Testing <b>HTML encoded</b> message</p></body></html>',
|
|
2837
|
+
* }
|
|
2838
|
+
* ```
|
|
2839
|
+
* email server and authentication being used is defiend in configuration file setting scimgateway.email
|
|
2840
|
+
* example below using **SMTP AUTH**
|
|
2841
|
+
* note, msgObj.from should normally correspond with configuration auth.options.username
|
|
2842
|
+
* ```
|
|
2843
|
+
* {
|
|
2844
|
+
* "scimgateway": {
|
|
2845
|
+
* "email": {
|
|
2846
|
+
* "host": "<host>", // smtp.gmail.com
|
|
2847
|
+
* "port": <port>, // 587
|
|
2848
|
+
* "auth": {
|
|
2849
|
+
* "type": "basic",
|
|
2850
|
+
* "options": {
|
|
2851
|
+
* "username": "<email address>",
|
|
2852
|
+
* "password": "<password>" // app password
|
|
2853
|
+
* }
|
|
2854
|
+
* },
|
|
2855
|
+
* "proxy": {
|
|
2856
|
+
* "host": null, // http://proxy-host:1234
|
|
2857
|
+
* "username": null,
|
|
2858
|
+
* "password": null
|
|
2859
|
+
* }
|
|
2860
|
+
* },
|
|
2861
|
+
* ...
|
|
2862
|
+
* }
|
|
2863
|
+
* }
|
|
2864
|
+
* ```
|
|
2865
|
+
* example below using recommended **OAuth**
|
|
2866
|
+
* note, Microsoft do not default support SMTP AUTH anymore and OAuth should be used
|
|
2867
|
+
* ```
|
|
2868
|
+
* {
|
|
2869
|
+
* "scimgateway": {
|
|
2870
|
+
* "email": {
|
|
2871
|
+
* "host": "<host>", // required when not using tenantIdGUID (Microsoft)
|
|
2872
|
+
* "port": <port>, // required when not using tenantIdGUID (Microsoft)
|
|
2873
|
+
* "auth": {
|
|
2874
|
+
* "type": "oauth",
|
|
2875
|
+
* "options": {
|
|
2876
|
+
* "tenantIdGUID": "<tenantId>", // used for Microsoft Exchange Online
|
|
2877
|
+
* "tokenUrl": "<tokenUrl>", // required when not using tenantIdGUID (Microsoft)
|
|
2878
|
+
* "clientId": "<clientId>",
|
|
2879
|
+
* "clientSecret": "<clientSecret>"
|
|
2880
|
+
* }
|
|
2881
|
+
* },
|
|
2882
|
+
* "proxy": {
|
|
2883
|
+
* "host": null, // http://proxy-host:1234
|
|
2884
|
+
* "username": null,
|
|
2885
|
+
* "password": null
|
|
2886
|
+
* }
|
|
2887
|
+
* },
|
|
2888
|
+
* ...
|
|
2889
|
+
* }
|
|
2890
|
+
* }
|
|
2891
|
+
* ```
|
|
2892
|
+
* Some notes when using OAuth and tenantIdGUID - Microsoft Exchange:
|
|
2893
|
+
* Entra ID application must have application permissions "**Mail.Send**"
|
|
2894
|
+
*
|
|
2895
|
+
* For not allowing send email from all mailboxes, ExO **ApplicationAccessPolicy** must be defined through PowerShell.
|
|
2896
|
+
* First create a mail-enabled security-group that only includes users (mailboxes) the app is allowed to send from
|
|
2897
|
+
* Note, "mail enabled security" cannot be created from portal, only from admin or admin.exchange console
|
|
2898
|
+
* ```
|
|
2899
|
+
* ##Connect to Exchange
|
|
2900
|
+
* Install-Module -Name ExchangeOnlineManagement
|
|
2901
|
+
* Connect-ExchangeOnline
|
|
2902
|
+
*
|
|
2903
|
+
* ##Create ApplicationAccessPolicy
|
|
2904
|
+
* New-ApplicationAccessPolicy -AppId $AppClientID -PolicyScopeGroupId $MailEnabledSecurityGrpId -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
|
|
2905
|
+
* ```
|
|
2906
|
+
**/
|
|
2907
|
+
async sendMail(msgObj: Record<string, any>, isHtml: boolean = false) {
|
|
2908
|
+
const gwName = this.gwName
|
|
2909
|
+
const pluginName = this.pluginName
|
|
2910
|
+
const logger = this.logger
|
|
2911
|
+
const authType = this.config.scimgateway?.email?.auth?.type ? this.config.scimgateway.email.auth.type.toLowerCase() : ''
|
|
2912
|
+
|
|
2913
|
+
if (typeof msgObj !== 'object' || !msgObj.from || !msgObj.to || !msgObj.content) {
|
|
2914
|
+
logger.error(`${gwName}[${pluginName}] sendMail failed: missing or invalid msgObj argument`)
|
|
2915
|
+
return
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
if (authType === 'oauth') {
|
|
2919
|
+
if (!this.helperRest) this.helperRest = new HelperRest(this, { entity: { undefined: { connection: this.config.scimgateway.email } } })
|
|
2920
|
+
if (this.config.scimgateway.email.auth?.options?.tenantIdGUID) {
|
|
2921
|
+
// Graph API
|
|
2922
|
+
const emailMessage: Record<string, any> = {
|
|
2923
|
+
message: {
|
|
2924
|
+
subject: msgObj.subject ? msgObj.subject : 'SCIM Gateway message',
|
|
2925
|
+
body: {
|
|
2926
|
+
content: msgObj.content,
|
|
2927
|
+
contentType: isHtml ? 'HTML' : 'Text',
|
|
2928
|
+
},
|
|
2929
|
+
toRecipients: [],
|
|
2930
|
+
ccRecipients: [],
|
|
2931
|
+
},
|
|
2932
|
+
saveToSentItems: 'false',
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
if (msgObj.to) {
|
|
2936
|
+
let arr = msgObj.to.split(',')
|
|
2937
|
+
for (let i = 0; i < arr.length; i++) {
|
|
2938
|
+
emailMessage.message.toRecipients.push({
|
|
2939
|
+
emailAddress: {
|
|
2940
|
+
address: arr[i],
|
|
2941
|
+
},
|
|
2942
|
+
})
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
if (msgObj.cc) {
|
|
2946
|
+
const arr = msgObj.cc.split(',')
|
|
2947
|
+
for (let i = 0; i < arr.length; i++) {
|
|
2948
|
+
emailMessage.message.ccRecipients.push({
|
|
2949
|
+
emailAddress: {
|
|
2950
|
+
address: arr[i],
|
|
2951
|
+
},
|
|
2952
|
+
})
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
if (emailMessage.message.toRecipients.length === 0) delete emailMessage.message.toRecipients
|
|
2956
|
+
if (emailMessage.message.ccRecipients.length === 0) delete emailMessage.message.ccRecipients
|
|
2957
|
+
|
|
2958
|
+
const path = `/users/${msgObj.from}/sendMail`
|
|
2959
|
+
try {
|
|
2960
|
+
await this.helperRest.doRequest('undefined', 'POST', path, emailMessage)
|
|
2961
|
+
logger.debug(`${gwName}[${pluginName}] sendMail subject '${emailMessage.message.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
|
|
2962
|
+
} catch (err: any) {
|
|
2963
|
+
logger.error(`${gwName}[${pluginName}] sendMail subject '${emailMessage.message.subject}' sending failed: ${err.message}`)
|
|
2964
|
+
}
|
|
2965
|
+
return
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
// nodemailer
|
|
2970
|
+
if (!this.config.scimgateway?.email?.host) {
|
|
2971
|
+
logger.error(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sending failed: some missing scimgateway.email configuration`)
|
|
2972
|
+
return
|
|
2973
|
+
}
|
|
2974
|
+
const smtpConfig: { [key: string]: any } = {
|
|
2975
|
+
host: this.config.scimgateway.email.host, // e.g. smtp.office365.com
|
|
2976
|
+
port: this.config.scimgateway.email.port || 587,
|
|
2977
|
+
proxy: this.config.scimgateway.email.proxy || null,
|
|
2978
|
+
secure: (this.config.scimgateway.email.port === 465), // false on 25/587
|
|
2979
|
+
tls: { ciphers: 'TLSv1.2' },
|
|
2980
|
+
}
|
|
2981
|
+
if (authType) {
|
|
2982
|
+
smtpConfig.auth = {}
|
|
2983
|
+
if (authType === 'basic') {
|
|
2984
|
+
smtpConfig.auth.user = this.config.scimgateway.email.auth.options.username
|
|
2985
|
+
smtpConfig.auth.pass = this.config.scimgateway.email.auth.options.password
|
|
2986
|
+
} else if (authType === 'oauth') {
|
|
2987
|
+
smtpConfig.auth.type = 'OAuth2'
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
const transporter = nodemailer.createTransport(smtpConfig)
|
|
2992
|
+
|
|
2993
|
+
const mailOptions: Record<string, any> = {
|
|
2994
|
+
from: msgObj.from, // sender address
|
|
2995
|
+
to: msgObj.to, // list of receivers - comma separated
|
|
2996
|
+
cc: msgObj.cc,
|
|
2997
|
+
subject: msgObj.subject ? msgObj.subject : 'SCIM Gateway message',
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
if (authType === 'oauth') {
|
|
3001
|
+
mailOptions.auth = {}
|
|
3002
|
+
mailOptions.auth.user = msgObj.from
|
|
3003
|
+
transporter.set('oauth2_provision_cb', async (user, renew, callback) => {
|
|
3004
|
+
const aObj = await this.helperRest.getAccessToken('undefined')
|
|
3005
|
+
const accessToken = aObj ? aObj?.access_token : null
|
|
3006
|
+
if (!accessToken) {
|
|
3007
|
+
return callback(new Error('missing access token'))
|
|
3008
|
+
} else {
|
|
3009
|
+
return callback(null, accessToken)
|
|
3010
|
+
}
|
|
3011
|
+
})
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
if (isHtml) mailOptions.html = msgObj.content
|
|
3015
|
+
else mailOptions.text = msgObj.content
|
|
3016
|
+
|
|
3017
|
+
transporter.sendMail(mailOptions, function (err) {
|
|
3018
|
+
if (err != null) logger.error(`${gwName}[${pluginName}] sendMail subject '${mailOptions.subject}' sending failed: ${err.message}`)
|
|
3019
|
+
else logger.debug(`${gwName}[${pluginName}] sendMail subject '${mailOptions.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
|
|
3020
|
+
})
|
|
3021
|
+
}
|
|
3022
|
+
|
|
2803
3023
|
// processConfig updates this.config and return found.<auth method>
|
|
2804
3024
|
// config external process.env/file/text replaced with actual values
|
|
2805
3025
|
// config encryption/decryption for keys named: 'password', 'secret', 'clientSecret', 'token', 'apikey'
|
package/package.json
CHANGED