scimgateway 5.0.14 → 5.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -46
- package/lib/helper-rest.ts +12 -12
- package/lib/logger.ts +1 -1
- package/lib/scimgateway.ts +13 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Validated through IdP's:
|
|
|
16
16
|
|
|
17
17
|
Latest news:
|
|
18
18
|
|
|
19
|
-
- Email, onError and sendMail() supports
|
|
19
|
+
- Email, onError and sendMail() supports more secure RESTful OAuth for Microsoft Exchange Online (ExO) and Google Workspace Gmail, alongside traditional SMTP Auth for all mail systems. HelperRest supports a wide range of common authentication methods, including basicAuth, bearerAuth, tokenAuth, oauth, oauthSamlBearer, oauthJwtBearer and Auth PassTrough
|
|
20
20
|
- Major version **v5.0.0** marks a shift to native TypeScript support and prioritizes [Bun](https://bun.sh/) over Node.js. This upgrade requires some modifications to existing plugins.
|
|
21
21
|
- **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
|
|
22
22
|
- Authentication PassThrough letting plugin pass authentication directly to endpoint for avoid maintaining secrets at the gateway. E.g., using Entra ID application OAuth
|
|
@@ -437,7 +437,7 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
437
437
|
"2603:1056:2000::/48",
|
|
438
438
|
"2603:1057:2::/48"
|
|
439
439
|
]
|
|
440
|
-
- **email** -
|
|
440
|
+
- **email** - Sending email from plugin or automated error notifications emailOnError. For emailOnError only the first error will be sent until sendInterval have passed. Supporting both SMTP Auth and modern REST OAuth. For OAuth, currently Microsoft Exchange Online (ExO) and Google Workspace Gmail are supported - see configuration notes
|
|
441
441
|
- **email.auth** - Authentication configuration
|
|
442
442
|
- **email.auth.type** - `oauth` or `smtp`
|
|
443
443
|
- **email.auth.options** - Authentication options - note, different options for type oauth and smtp
|
|
@@ -461,45 +461,6 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
461
461
|
- **email.emailOnError.cc** - Optional comma separated list of cc mail addresses
|
|
462
462
|
- **email.emailOnError.subject** - Optional mail subject, default `SCIM Gateway error message`
|
|
463
463
|
|
|
464
|
-
**Configuration notes for Microsoft Exchange Online (ExO):**
|
|
465
|
-
|
|
466
|
-
- Entra ID application must have application permissions `Mail.Send`
|
|
467
|
-
- To prevent the sending of emails from any defined mailboxes, an ExO `ApplicationAccessPolicy` must be defined through PowerShell.
|
|
468
|
-
|
|
469
|
-
First create a mail-enabled security-group that only includes those users (mailboxes) the application is allowed to send from
|
|
470
|
-
Note, `mail enabled security group` cannot be created from portal, only from admin or admin.exchange console
|
|
471
|
-
|
|
472
|
-
##Connect to Exchange
|
|
473
|
-
Install-Module -Name ExchangeOnlineManagement
|
|
474
|
-
Connect-ExchangeOnline
|
|
475
|
-
|
|
476
|
-
##Create ApplicationAccessPolicy
|
|
477
|
-
New-ApplicationAccessPolicy -AppId <AppClientID> -PolicyScopeGroupId <MailEnabledSecurityGrpId> -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
|
|
478
|
-
|
|
479
|
-
**Configuration notes for Google Workspace Gmail:**
|
|
480
|
-
|
|
481
|
-
- https://console.cloud.google.com
|
|
482
|
-
- IAM & Admin > Service Accounts > Create Service Account
|
|
483
|
-
- Name=email-sender
|
|
484
|
-
- Create and Continue
|
|
485
|
-
- Grant this service account access to project - not needed
|
|
486
|
-
- Grant users access to this service - not needed
|
|
487
|
-
- IAM & Admin > Service Accounts > "email-sender" account > Keys
|
|
488
|
-
- Add Key > Create new key > JSON
|
|
489
|
-
- download json `serviceAccountKeyFile` file, refere to configuration `email.auth.options.serviceAccountKeyFile`
|
|
490
|
-
|
|
491
|
-
- https://admin.google.com
|
|
492
|
-
- Security > Access and data control > API controls
|
|
493
|
-
- Manage Domain Wide Delegation > Add new
|
|
494
|
-
- Client ID = id of service account created
|
|
495
|
-
- OAuth scope = https://www.googleapis.com/auth/gmail.send
|
|
496
|
-
|
|
497
|
-
- https://admin.google.com
|
|
498
|
-
- Billing > Subscriptions - verify Google Workspace license
|
|
499
|
-
- Directory > Users > "user"
|
|
500
|
-
- Licenses > Edit > enable Google Workspace license
|
|
501
|
-
`email.onerror.from` mail address must have Google Workspace Business license
|
|
502
|
-
|
|
503
464
|
- **stream** - See [SCIM Stream](https://elshaug.xyz/docs/scim-stream) for configuration details
|
|
504
465
|
|
|
505
466
|
- **endpoint** - Contains endpoint specific configuration according to customized **plugin code**.
|
|
@@ -561,6 +522,45 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
561
522
|
}
|
|
562
523
|
|
|
563
524
|
|
|
525
|
+
- Email, using Microsoft Exchange Online (ExO)
|
|
526
|
+
|
|
527
|
+
- Entra ID application must have application permissions `Mail.Send`
|
|
528
|
+
- To prevent the sending of emails from any defined mailboxes, an ExO `ApplicationAccessPolicy` must be defined through PowerShell.
|
|
529
|
+
|
|
530
|
+
First create a mail-enabled security-group that only includes those users (mailboxes) the application is allowed to send from
|
|
531
|
+
Note, `mail enabled security group` cannot be created from portal, only from admin or admin.exchange console
|
|
532
|
+
|
|
533
|
+
##Connect to Exchange
|
|
534
|
+
Install-Module -Name ExchangeOnlineManagement
|
|
535
|
+
Connect-ExchangeOnline
|
|
536
|
+
|
|
537
|
+
##Create ApplicationAccessPolicy
|
|
538
|
+
New-ApplicationAccessPolicy -AppId <AppClientID> -PolicyScopeGroupId <MailEnabledSecurityGrpId> -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
|
|
539
|
+
|
|
540
|
+
- Email, using Google Workspace Gmail
|
|
541
|
+
|
|
542
|
+
- https://console.cloud.google.com
|
|
543
|
+
- IAM & Admin > Service Accounts > Create Service Account
|
|
544
|
+
- Name=email-sender
|
|
545
|
+
- Create and Continue
|
|
546
|
+
- Grant this service account access to project - not needed
|
|
547
|
+
- Grant users access to this service - not needed
|
|
548
|
+
- IAM & Admin > Service Accounts > "email-sender" account > Keys
|
|
549
|
+
- Add Key > Create new key > JSON
|
|
550
|
+
- download json Service Account Key file, refere to configuration `email.auth.options.serviceAccountKeyFile`
|
|
551
|
+
|
|
552
|
+
- https://admin.google.com
|
|
553
|
+
- Security > Access and data control > API controls
|
|
554
|
+
- Manage Domain Wide Delegation > Add new
|
|
555
|
+
- Client ID = id of service account created
|
|
556
|
+
- OAuth scope = `https://www.googleapis.com/auth/gmail.send`
|
|
557
|
+
|
|
558
|
+
- https://admin.google.com
|
|
559
|
+
- Billing > Subscriptions - verify Google Workspace license
|
|
560
|
+
- Directory > Users > "user"
|
|
561
|
+
- Licenses > Edit > enable Google Workspace license
|
|
562
|
+
`email.emailOnError.from` mail address must have Google Workspace license
|
|
563
|
+
|
|
564
564
|
|
|
565
565
|
## Manual startup
|
|
566
566
|
|
|
@@ -1009,17 +1009,17 @@ For JavaScript coding editor you may use [Visual Studio Code](https://code.visua
|
|
|
1009
1009
|
|
|
1010
1010
|
Preparation:
|
|
1011
1011
|
|
|
1012
|
-
* Copy "best matching" example plugin e.g. `lib\plugin-mssql.ts` and `config\plugin-mssql.json` and rename both copies to your plugin name prefix e.g. plugin-mine.ts and plugin-mine.json
|
|
1012
|
+
* Copy "best matching" example plugin e.g. `lib\plugin-mssql.ts` and `config\plugin-mssql.json` and rename both copies to your plugin name prefix e.g. plugin-mine.ts and plugin-mine.json
|
|
1013
1013
|
* Edit plugin-mine.json and define a unique port number for the gateway setting
|
|
1014
1014
|
* Edit index.ts and include your plugin in the startup e.g. `const plugins = ['mine']');`
|
|
1015
|
-
* Start SCIM Gateway and verify
|
|
1015
|
+
* Start SCIM Gateway and verify using using your own SCIM API requests or your IdP/IGA system.
|
|
1016
1016
|
|
|
1017
1017
|
Now we are ready for custom coding by editing plugin-mine.ts
|
|
1018
|
-
Coding should be done step by step and each step should be verified and tested before starting the next
|
|
1018
|
+
Coding should be done step by step and each step should be verified and tested before starting the next
|
|
1019
1019
|
|
|
1020
|
-
1. **Turn off group functionality** - getGroups to return empty response
|
|
1020
|
+
1. **Turn off group functionality** - getGroups to return empty response (gateway automatically use getGroups for some of the methods if groups not included)
|
|
1021
1021
|
Please see plugin-saphana that do not use groups.
|
|
1022
|
-
2. **getUsers** (test provisioning retrieve accounts)
|
|
1022
|
+
2. **getUsers** (test provisioning retrieve all accounts and single account)
|
|
1023
1023
|
4. **createUser** (test provisioning new account)
|
|
1024
1024
|
5. **deleteUser** (test provisioning delete account)
|
|
1025
1025
|
6. **modifyUser** (test provisioning modify account)
|
|
@@ -1136,6 +1136,12 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1136
1136
|
|
|
1137
1137
|
## Change log
|
|
1138
1138
|
|
|
1139
|
+
### v5.0.15
|
|
1140
|
+
|
|
1141
|
+
[Improved]
|
|
1142
|
+
|
|
1143
|
+
- HelperRest, auth.type=oauthSamlAssertion and auth.type=oauthJwtAssertion have been updated to `oauthSamlBearer` and `oauthJwtBearer` for consistency
|
|
1144
|
+
|
|
1139
1145
|
### v5.0.14
|
|
1140
1146
|
|
|
1141
1147
|
[Improved]
|
package/lib/helper-rest.ts
CHANGED
|
@@ -37,7 +37,7 @@ export class HelperRest {
|
|
|
37
37
|
}
|
|
38
38
|
} else if (this.config_entity[baseEntity]?.connection?.auth?.options?.serviceAccountKeyFile) { // Google, setting baseUrls to googleapis
|
|
39
39
|
const type = this.config_entity[baseEntity]?.connection?.auth?.type
|
|
40
|
-
if (type === '
|
|
40
|
+
if (type === 'oauthJwtBearer' || type === 'oauth') { // includes oauth because of email.auth.type
|
|
41
41
|
this.config_entity[baseEntity].connection.baseUrls = [this.googleUrl]
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -99,7 +99,7 @@ export class HelperRest {
|
|
|
99
99
|
}
|
|
100
100
|
break
|
|
101
101
|
|
|
102
|
-
case '
|
|
102
|
+
case 'oauthSamlBearer':
|
|
103
103
|
tokenUrl = this.config_entity[baseEntity].connection.auth.options.tokenUrl
|
|
104
104
|
const context = null
|
|
105
105
|
const cert = fs.readFileSync(this.config_entity[baseEntity].connection.auth.options.certificate.cert).toString()
|
|
@@ -123,7 +123,7 @@ export class HelperRest {
|
|
|
123
123
|
}
|
|
124
124
|
break
|
|
125
125
|
|
|
126
|
-
case '
|
|
126
|
+
case 'oauthJwtBearer':
|
|
127
127
|
let privateKey = ''
|
|
128
128
|
let jwtAttr: Record<string, any> = {}
|
|
129
129
|
const serviceAccountKeyFile = this.config_entity[baseEntity]?.connection?.auth?.options?.serviceAccountKeyFile
|
|
@@ -284,7 +284,7 @@ export class HelperRest {
|
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
// Support no auth, header based auth (e.g., config {"options":{"headers":{"APIkey":"123"}}}),
|
|
287
|
-
// basicAuth, bearerAuth, oauth, tokenAuth,
|
|
287
|
+
// basicAuth, bearerAuth, oauth, tokenAuth, oauthSamlBearer, oauthJwtBearer and auth PassTrough using request header authorization
|
|
288
288
|
|
|
289
289
|
let orgConnection: any
|
|
290
290
|
if (opt?.connection) { // allow overriding/extending configuration connection by caller argument opt.connection
|
|
@@ -325,19 +325,19 @@ export class HelperRest {
|
|
|
325
325
|
}
|
|
326
326
|
param.options.headers['Authorization'] = 'Bearer ' + Buffer.from(this.config_entity[baseEntity].connection.auth.options.token).toString('base64')
|
|
327
327
|
break
|
|
328
|
-
case '
|
|
328
|
+
case 'oauthSamlBearer':
|
|
329
329
|
if (!this.config_entity[baseEntity]?.connection?.auth?.options?.clientId || !this.config_entity[baseEntity]?.connection?.auth?.options?.companyId
|
|
330
330
|
|| !this.config_entity[baseEntity]?.connection?.auth?.options?.certificate?.key) {
|
|
331
|
-
const err = new Error(`auth.type '
|
|
331
|
+
const err = new Error(`auth.type 'oauthSamlBearer' - missing configuration entity.${baseEntity}.connection.auth.options...`)
|
|
332
332
|
throw err
|
|
333
333
|
}
|
|
334
334
|
param.accessToken = await this.getAccessToken(baseEntity, ctx)
|
|
335
335
|
param.options.headers['Authorization'] = `Bearer ${param.accessToken.access_token}`
|
|
336
336
|
break
|
|
337
|
-
case '
|
|
337
|
+
case 'oauthJwtBearer':
|
|
338
338
|
if (this.config_entity[baseEntity]?.connection?.auth?.options?.serviceAccountKeyFile) { // Google Service Account
|
|
339
339
|
if (!this.config_entity[baseEntity]?.connection?.auth?.options?.scope || !this.config_entity[baseEntity]?.connection?.auth?.options?.subject) {
|
|
340
|
-
const err = new Error(`auth.type '
|
|
340
|
+
const err = new Error(`auth.type 'oauthJwtBearer' - using auth.options 'serviceAccountKeyFile' also requires mandatory configuration entity.${baseEntity}.connection.auth.options.scope/subject`)
|
|
341
341
|
throw err
|
|
342
342
|
}
|
|
343
343
|
} else if (!this.config_entity[baseEntity]?.connection?.auth?.options?.tokenUrl
|
|
@@ -347,7 +347,7 @@ export class HelperRest {
|
|
|
347
347
|
|| !this.config_entity[baseEntity]?.connection?.auth?.options?.audience
|
|
348
348
|
|| !this.config_entity[baseEntity]?.connection?.auth?.options?.certificate?.key
|
|
349
349
|
) {
|
|
350
|
-
const err = new Error(`auth.type '
|
|
350
|
+
const err = new Error(`auth.type 'oauthJwtBearer' - when not using auth.options 'serviceAccountKeyFile' which is related to Google, following auth.options is mandatory: tokenUrl, scope, subject, issuer, audience, certificate.key`)
|
|
351
351
|
throw err
|
|
352
352
|
}
|
|
353
353
|
|
|
@@ -653,7 +653,7 @@ export class HelperRest {
|
|
|
653
653
|
* ```
|
|
654
654
|
* type defines authentication being used
|
|
655
655
|
* if type not defined, no authentication used
|
|
656
|
-
* valid type is: `basic`, `oauth`, `token`, `bearer` or `
|
|
656
|
+
* valid type is: `basic`, `oauth`, `token`, `bearer` or `oauthSamlBearer`
|
|
657
657
|
*
|
|
658
658
|
* for each valid type there are different auth.options
|
|
659
659
|
*
|
|
@@ -699,7 +699,7 @@ export class HelperRest {
|
|
|
699
699
|
* }
|
|
700
700
|
* ```
|
|
701
701
|
*
|
|
702
|
-
* type=**"
|
|
702
|
+
* type=**"oauthSamlBearer"** having auth.options:
|
|
703
703
|
* ```
|
|
704
704
|
* {
|
|
705
705
|
* "options": {
|
|
@@ -715,7 +715,7 @@ export class HelperRest {
|
|
|
715
715
|
* }
|
|
716
716
|
* ```
|
|
717
717
|
*
|
|
718
|
-
* type=**"
|
|
718
|
+
* type=**"oauthJwtBearer"** having auth.options:
|
|
719
719
|
* ```
|
|
720
720
|
* // Google API - baseUrls automatically set to [https://www.googleapis.com]
|
|
721
721
|
* {
|
package/lib/logger.ts
CHANGED
package/lib/scimgateway.ts
CHANGED
|
@@ -2949,6 +2949,9 @@ export class ScimGateway {
|
|
|
2949
2949
|
isHtml = true
|
|
2950
2950
|
msgObj.content = `<html><body><pre style="font-family: monospace; white-space: pre-wrap;">${msgObj.content}</pre></body></html>`
|
|
2951
2951
|
}
|
|
2952
|
+
if (!msgObj.to) msgObj.to = ''
|
|
2953
|
+
if (!msgObj.cc) msgObj.cc = ''
|
|
2954
|
+
if (!msgObj.subject) msgObj.subject = 'SCIM Gateway message'
|
|
2952
2955
|
|
|
2953
2956
|
if (authType === 'oauth') {
|
|
2954
2957
|
if (!this.helperRest) this.helperRest = new HelperRest(this, { entity: { undefined: { connection: this.config.scimgateway.email } } })
|
|
@@ -2956,7 +2959,7 @@ export class ScimGateway {
|
|
|
2956
2959
|
// Microsoft Exchange Online (ExO) - using Graph API
|
|
2957
2960
|
const emailMessage: Record<string, any> = {
|
|
2958
2961
|
message: {
|
|
2959
|
-
subject: msgObj.subject
|
|
2962
|
+
subject: msgObj.subject,
|
|
2960
2963
|
body: {
|
|
2961
2964
|
content: msgObj.content,
|
|
2962
2965
|
contentType: isHtml ? 'HTML' : 'Text',
|
|
@@ -2993,16 +2996,13 @@ export class ScimGateway {
|
|
|
2993
2996
|
const path = `/users/${msgObj.from}/sendMail`
|
|
2994
2997
|
try {
|
|
2995
2998
|
await this.helperRest.doRequest('undefined', 'POST', path, emailMessage)
|
|
2996
|
-
logger.debug(`${gwName}[${pluginName}] sendMail subject '${
|
|
2999
|
+
logger.debug(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
|
|
2997
3000
|
} catch (err: any) {
|
|
2998
|
-
logger.error(`${gwName}[${pluginName}] sendMail subject '${
|
|
3001
|
+
logger.error(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sending failed: ${err.message}`)
|
|
2999
3002
|
}
|
|
3000
3003
|
return
|
|
3001
3004
|
} else if (this.config.scimgateway.email.auth?.options?.serviceAccountKeyFile) {
|
|
3002
3005
|
// Google Workspace Gmail
|
|
3003
|
-
if (!msgObj.to) msgObj.to = ''
|
|
3004
|
-
if (!msgObj.cc) msgObj.cc = ''
|
|
3005
|
-
|
|
3006
3006
|
let mimeMessage = `From: ${msgObj.from}
|
|
3007
3007
|
To: ${msgObj.to}
|
|
3008
3008
|
Cc: ${msgObj.cc}
|
|
@@ -3016,11 +3016,11 @@ Content-Transfer-Encoding: quoted-printable
|
|
|
3016
3016
|
const encodedMessage = btoa(mimeMessage)
|
|
3017
3017
|
const emailMessage = { raw: encodedMessage }
|
|
3018
3018
|
const path = `/gmail/v1/users/${msgObj.from}/messages/send`
|
|
3019
|
-
try { // using opt connection argument type=
|
|
3020
|
-
await this.helperRest.doRequest('undefined', 'POST', path, emailMessage, null, { connection: { auth: { type: '
|
|
3021
|
-
logger.debug(`${gwName}[${pluginName}] sendMail subject '${
|
|
3019
|
+
try { // using opt connection argument type=oauthJwtBearer and options scope/subject because we want to keep simplified email.auth.type=oauth and options serviceAccountKeyFile
|
|
3020
|
+
await this.helperRest.doRequest('undefined', 'POST', path, emailMessage, null, { connection: { auth: { type: 'oauthJwtBearer', options: { scope: 'https://www.googleapis.com/auth/gmail.send', subject: msgObj.from } } } })
|
|
3021
|
+
logger.debug(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
|
|
3022
3022
|
} catch (err: any) {
|
|
3023
|
-
logger.error(`${gwName}[${pluginName}] sendMail subject '${
|
|
3023
|
+
logger.error(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sending failed: ${err.message}`)
|
|
3024
3024
|
}
|
|
3025
3025
|
return
|
|
3026
3026
|
}
|
|
@@ -3057,15 +3057,15 @@ Content-Transfer-Encoding: quoted-printable
|
|
|
3057
3057
|
from: msgObj.from, // sender address
|
|
3058
3058
|
to: msgObj.to, // list of receivers - comma separated
|
|
3059
3059
|
cc: msgObj.cc,
|
|
3060
|
-
subject: msgObj.subject
|
|
3060
|
+
subject: msgObj.subject,
|
|
3061
3061
|
}
|
|
3062
3062
|
|
|
3063
3063
|
if (isHtml) mailOptions.html = msgObj.content
|
|
3064
3064
|
else mailOptions.text = msgObj.content
|
|
3065
3065
|
|
|
3066
3066
|
transporter.sendMail(mailOptions, function (err) {
|
|
3067
|
-
if (err != null) logger.error(`${gwName}[${pluginName}] sendMail subject '${
|
|
3068
|
-
else logger.debug(`${gwName}[${pluginName}] sendMail subject '${
|
|
3067
|
+
if (err != null) logger.error(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sending failed: ${err.message}`)
|
|
3068
|
+
else logger.debug(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
|
|
3069
3069
|
})
|
|
3070
3070
|
}
|
|
3071
3071
|
|
package/package.json
CHANGED