scimgateway 5.0.6 → 5.0.7
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 +111 -19
- package/config/docker/dbinit/init.sql +43 -0
- package/config/docker/docker-compose-mssql.yml +58 -0
- package/lib/helper-rest.ts +9 -4
- package/lib/plugin-mssql.ts +245 -155
- package/lib/scimgateway.ts +17 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,14 +13,14 @@ Validated through IdP's:
|
|
|
13
13
|
- Okta
|
|
14
14
|
- Omada
|
|
15
15
|
- SailPoint/IdentityNow
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
Latest news:
|
|
18
18
|
|
|
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.
|
|
19
|
+
- 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.
|
|
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
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** getUsers() and getGroups() replacing some deprecated methods. No limitations on filtering/sorting. Admin user access can be linked to specific baseEntities. New MongoDB plugin
|
|
23
|
+
- Major version **v4.0.0** 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
|
|
@@ -460,6 +460,21 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
460
460
|
- **email.emailOnError.cc** - Optional comma separated list of cc mail addresses
|
|
461
461
|
- **email.emailOnError.subject** - Optional mail subject, default `SCIM Gateway error message`
|
|
462
462
|
|
|
463
|
+
Configuration notes when using default configuration oauth and tenantIdGUID - Microsoft Exchange Online (ExO):
|
|
464
|
+
|
|
465
|
+
- Entra ID application must have application permissions "**Mail.Send**"
|
|
466
|
+
- To prevent the sending of emails from any defined mailboxes, an ExO **ApplicationAccessPolicy** must be defined through PowerShell.
|
|
467
|
+
|
|
468
|
+
First create a mail-enabled security-group that only includes those users (mailboxes) the application is allowed to send from
|
|
469
|
+
Note, "mail enabled security" group cannot be created from portal, only from admin or admin.exchange console
|
|
470
|
+
|
|
471
|
+
##Connect to Exchange
|
|
472
|
+
Install-Module -Name ExchangeOnlineManagement
|
|
473
|
+
Connect-ExchangeOnline
|
|
474
|
+
|
|
475
|
+
##Create ApplicationAccessPolicy
|
|
476
|
+
New-ApplicationAccessPolicy -AppId $AppClientID -PolicyScopeGroupId $MailEnabledSecurityGrpId -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
|
|
477
|
+
|
|
463
478
|
- **stream** - See [SCIM Stream](https://elshaug.xyz/docs/scim-stream) for configuration details
|
|
464
479
|
|
|
465
480
|
- **endpoint** - Contains endpoint specific configuration according to our **plugin code**.
|
|
@@ -589,8 +604,7 @@ docker-compose**
|
|
|
589
604
|
**Dockerfile** <== Main dockerfile
|
|
590
605
|
**DataDockerfile** <== Handles volume mapping
|
|
591
606
|
**docker-compose-debug.yml** <== Debugging
|
|
592
|
-
|
|
593
|
-
|
|
607
|
+
**docker-compose-mssql.yml** <== Example including MSSQL docker image
|
|
594
608
|
|
|
595
609
|
- Create a scimgateway user on your Linux VM.
|
|
596
610
|
|
|
@@ -1097,14 +1111,92 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1097
1111
|
|
|
1098
1112
|
## Change log
|
|
1099
1113
|
|
|
1100
|
-
### v5.0.
|
|
1114
|
+
### v5.0.7
|
|
1115
|
+
|
|
1116
|
+
[Improved]
|
|
1117
|
+
|
|
1118
|
+
- plugin-mssql all methods now implemented, also includes docker and dbinit configuration, **thanks to [@Peter Havekes](https://github.com/phavekes) and [@mrvanes](https://github.com/mrvanes)**
|
|
1119
|
+
|
|
1120
|
+
[Fixed]
|
|
1121
|
+
|
|
1122
|
+
- mail sending option introduced in v5.0.6 did not fully support national special charcters when using Microsoft Exchange Online and html formatted email
|
|
1123
|
+
|
|
1124
|
+
### v5.0.6
|
|
1101
1125
|
|
|
1102
1126
|
[Improved]
|
|
1103
1127
|
|
|
1104
1128
|
- new configuration option: `scimgateway.idleTimeout` default 120, sets the the number of seconds to wait before timing out a connection due to inactivity
|
|
1105
|
-
-
|
|
1106
|
-
-
|
|
1107
|
-
|
|
1129
|
+
- deprecated configuration option: `scimgateway.payloadSize` Bun using default maxRequestBodySize 128MB
|
|
1130
|
+
- new configuration option: `scimgateway.email` replacing legacy `scimgateway.emailOnError` (legacy still supported). Email now support oauth authentication
|
|
1131
|
+
|
|
1132
|
+
**old configuration:**
|
|
1133
|
+
|
|
1134
|
+
{
|
|
1135
|
+
"scimgateway": {
|
|
1136
|
+
...
|
|
1137
|
+
"emailOnError": {
|
|
1138
|
+
"smtp": {
|
|
1139
|
+
"enabled": false,
|
|
1140
|
+
"host": null,
|
|
1141
|
+
"port": 587,
|
|
1142
|
+
"proxy": null,
|
|
1143
|
+
"authenticate": true,
|
|
1144
|
+
"username": null,
|
|
1145
|
+
"password": null,
|
|
1146
|
+
"sendInterval": 15,
|
|
1147
|
+
"to": null,
|
|
1148
|
+
"cc": null
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
...
|
|
1152
|
+
},
|
|
1153
|
+
...
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
**new configuration:**
|
|
1158
|
+
Using Microsoft Exchange Online and oauth authencation which also is default and recommended by Microsoft
|
|
1159
|
+
For other mail servers and options like SMTP AUTH (basic/oauth), please see configuration description
|
|
1160
|
+
Plugin may also send mail using method scimgateway.sendMail()
|
|
1161
|
+
|
|
1162
|
+
{
|
|
1163
|
+
"scimgateway": {
|
|
1164
|
+
...
|
|
1165
|
+
"email": {
|
|
1166
|
+
"auth": {
|
|
1167
|
+
"type": "oauth",
|
|
1168
|
+
"options": {
|
|
1169
|
+
"tenantIdGUID": null,
|
|
1170
|
+
"clientId": null,
|
|
1171
|
+
"clientSecret": null
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
"emailOnError": {
|
|
1175
|
+
"enabled": false,
|
|
1176
|
+
"from": null,
|
|
1177
|
+
"to": null
|
|
1178
|
+
}
|
|
1179
|
+
},
|
|
1180
|
+
...
|
|
1181
|
+
},
|
|
1182
|
+
...
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
Configuration notes when using oauth and tenantIdGUID - Microsoft Exchange Online (ExO):
|
|
1186
|
+
|
|
1187
|
+
- Entra ID application must have application permissions "**Mail.Send**"
|
|
1188
|
+
- To prevent the sending of emails from any defined mailboxes, an ExO **ApplicationAccessPolicy** must be defined through PowerShell.
|
|
1189
|
+
|
|
1190
|
+
First create a mail-enabled security-group that only includes those users (mailboxes) the application is allowed to send from
|
|
1191
|
+
Note, "mail enabled security" group cannot be created from portal, only from admin or admin.exchange console
|
|
1192
|
+
|
|
1193
|
+
##Connect to Exchange
|
|
1194
|
+
Install-Module -Name ExchangeOnlineManagement
|
|
1195
|
+
Connect-ExchangeOnline
|
|
1196
|
+
|
|
1197
|
+
##Create ApplicationAccessPolicy
|
|
1198
|
+
New-ApplicationAccessPolicy -AppId $AppClientID -PolicyScopeGroupId $MailEnabledSecurityGrpId -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
|
|
1199
|
+
|
|
1108
1200
|
|
|
1109
1201
|
### v5.0.5
|
|
1110
1202
|
|
|
@@ -1573,7 +1665,7 @@ Note, obsolete - see v4.2.15 comments
|
|
|
1573
1665
|
"forceExitTimeout": 1000
|
|
1574
1666
|
}
|
|
1575
1667
|
|
|
1576
|
-
**Thanks to Kevin Osborn**
|
|
1668
|
+
**Thanks to [@Kevin Osborn](https://github.com/osbornk)**
|
|
1577
1669
|
|
|
1578
1670
|
### v4.1.15
|
|
1579
1671
|
|
|
@@ -1604,7 +1696,7 @@ Note, obsolete - see v4.2.15 comments
|
|
|
1604
1696
|
scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx)
|
|
1605
1697
|
// tip, see provided example plugins
|
|
1606
1698
|
|
|
1607
|
-
**Thanks to Kevin Osborn**
|
|
1699
|
+
**Thanks to [@Kevin Osborn](https://github.com/osbornk)**
|
|
1608
1700
|
|
|
1609
1701
|
### v4.1.14
|
|
1610
1702
|
|
|
@@ -1629,7 +1721,7 @@ Note, obsolete - see v4.2.15 comments
|
|
|
1629
1721
|
[Improved]
|
|
1630
1722
|
|
|
1631
1723
|
- new plugin configuration `payloadSize`. If not defined, default "1mb" will be used. There are cases which large groups could exceed default size and you may want to increase by setting your own size e.g. "5mb"
|
|
1632
|
-
**Thanks to Sam Murphy**
|
|
1724
|
+
**Thanks to [@Sam Murphy*](https://github.com/SamMurphyDev)**
|
|
1633
1725
|
|
|
1634
1726
|
[Fixed]
|
|
1635
1727
|
|
|
@@ -1775,7 +1867,7 @@ SCIM Gateway related news:
|
|
|
1775
1867
|
}
|
|
1776
1868
|
|
|
1777
1869
|
- postinstall copying example plugins may be skipped by setting the property `scimgateway_postinstall_skip = true` in `.npmrc` or by setting environment `SCIMGATEWAY_POSTINSTALL_SKIP = true`
|
|
1778
|
-
- Secrets now also support key-value storage. The key defined in plugin configuration have syntax `process.text.<path>` where `<path>` is the file which contains raw (UTF-8) character value. E.g. configuration `endpoint.password` could have value `process.text./var/run/vault/endpoint.password`, and the corresponding file contains the secret. **Thanks to Raymond
|
|
1870
|
+
- Secrets now also support key-value storage. The key defined in plugin configuration have syntax `process.text.<path>` where `<path>` is the file which contains raw (UTF-8) character value. E.g. configuration `endpoint.password` could have value `process.text./var/run/vault/endpoint.password`, and the corresponding file contains the secret. **Thanks to [@Raymond Augé](https://github.com/rotty3000)**
|
|
1779
1871
|
|
|
1780
1872
|
|
|
1781
1873
|
### v4.0.0
|
|
@@ -1785,7 +1877,7 @@ SCIM Gateway related news:
|
|
|
1785
1877
|
- New `getGroups()` replacing deprecated exploreGroups(), getGroup() and getGroupMembers()
|
|
1786
1878
|
- Fully filter and sort support
|
|
1787
1879
|
- Authentication configuration may now include a baseEntities array containing one or more `baseEntity` allowed for corresponding admin user
|
|
1788
|
-
- New plugin-mongodb, **
|
|
1880
|
+
- New plugin-mongodb, **Thanks to [@Filipe Ribeiro](https://github.com/fribeiro-keeps) and [@Miguel Ferreira](https://github.com/jmaferreira) (KEEP SOLUTIONS)**
|
|
1789
1881
|
|
|
1790
1882
|
Note, using this major version **require existing custom plugins to be upgraded**. If you do not want to upgrade your custom plugins, the old version have to be installed using: `npm install scimgateway@3.2.11`
|
|
1791
1883
|
|
|
@@ -1893,7 +1985,7 @@ We also need to add logic from existing getGroup() and getGroupMembers()
|
|
|
1893
1985
|
[Fixed]
|
|
1894
1986
|
|
|
1895
1987
|
- Return 500 on GET handler error instead of 404
|
|
1896
|
-
**Thanks to Nipun Dayanath**
|
|
1988
|
+
**Thanks to [@Nipun Dayanath](https://github.com/nipund)**
|
|
1897
1989
|
- createUser/createRole response now includes id retrieved by getUser/getRole instead of using posted userName/displayName value
|
|
1898
1990
|
|
|
1899
1991
|
### v3.2.6
|
|
@@ -2373,7 +2465,7 @@ Custom plugins needs some changes (please see included example plugins)
|
|
|
2373
2465
|
|
|
2374
2466
|
- Some minor compliance fixes
|
|
2375
2467
|
|
|
2376
|
-
**Thanks to ywchuang**
|
|
2468
|
+
**Thanks to [@ywchuang](https://github.com/ywchuang)**
|
|
2377
2469
|
|
|
2378
2470
|
### v1.0.4
|
|
2379
2471
|
[Improved]
|
|
@@ -2473,7 +2565,7 @@ With:
|
|
|
2473
2565
|
|
|
2474
2566
|
- Document updated on how to run SCIM Gateway as a Docker container
|
|
2475
2567
|
- `config\docker` includes docker configuration examples
|
|
2476
|
-
**Thanks to
|
|
2568
|
+
**Thanks to [@cwatsonc](https://github.com/cwatsonc) and [@visualjeff](https://github.com/visualjeff)**
|
|
2477
2569
|
|
|
2478
2570
|
|
|
2479
2571
|
### v0.4.5
|
|
@@ -2494,7 +2586,7 @@ With:
|
|
|
2494
2586
|
|
|
2495
2587
|
- NoSQL Document-Oriented Database plugin: `plugin-loki`
|
|
2496
2588
|
This plugin now replace previous `plugin-testmode`
|
|
2497
|
-
**Thanks to
|
|
2589
|
+
**Thanks to [@visualjeff](https://github.com/visualjeff)**
|
|
2498
2590
|
- Minor code/comment reorganizations in provided plugins
|
|
2499
2591
|
- Minor adjustments to multi-value logic introduced in v0.4.0
|
|
2500
2592
|
|
|
@@ -2513,7 +2605,7 @@ This plugin now replace previous `plugin-testmode`
|
|
|
2513
2605
|
|
|
2514
2606
|
- Mocha test scripts for automated testing of plugin-testmode
|
|
2515
2607
|
- Automated tests run on Travis-ci.org (click on build badge)
|
|
2516
|
-
- **Thanks to
|
|
2608
|
+
- **Thanks to [@visualjeff](https://github.com/visualjeff)**
|
|
2517
2609
|
|
|
2518
2610
|
|
|
2519
2611
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
USE [master];
|
|
2
|
+
GO
|
|
3
|
+
|
|
4
|
+
IF NOT EXISTS (SELECT * FROM sys.sql_logins WHERE name = 'scimgateway')
|
|
5
|
+
BEGIN
|
|
6
|
+
CREATE LOGIN [scimgateway] WITH PASSWORD = 'password', CHECK_POLICY = OFF;
|
|
7
|
+
ALTER SERVER ROLE [sysadmin] ADD MEMBER [scimgateway];
|
|
8
|
+
END
|
|
9
|
+
GO
|
|
10
|
+
|
|
11
|
+
IF DB_ID('scimgateway') IS NULL
|
|
12
|
+
BEGIN
|
|
13
|
+
CREATE DATABASE [scimgateway];
|
|
14
|
+
END
|
|
15
|
+
GO
|
|
16
|
+
|
|
17
|
+
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'User')
|
|
18
|
+
BEGIN
|
|
19
|
+
USE [scimgateway]
|
|
20
|
+
CREATE TABLE [User] (
|
|
21
|
+
[UserID] VARCHAR(50) NOT NULL,
|
|
22
|
+
[Enabled] VARCHAR(50) NULL,
|
|
23
|
+
[Password] VARCHAR(50) NULL,
|
|
24
|
+
[FirstName] VARCHAR(50) NULL,
|
|
25
|
+
[MiddleName] VARCHAR(50) NULL,
|
|
26
|
+
[LastName] VARCHAR(50) NULL,
|
|
27
|
+
[Email] VARCHAR(50) NULL,
|
|
28
|
+
[MobilePhone] VARCHAR(50) NULL,
|
|
29
|
+
CONSTRAINT [PK_User] PRIMARY KEY ([UserID])
|
|
30
|
+
);
|
|
31
|
+
END
|
|
32
|
+
GO
|
|
33
|
+
|
|
34
|
+
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Group')
|
|
35
|
+
BEGIN
|
|
36
|
+
USE [scimgateway]
|
|
37
|
+
CREATE TABLE [Group] (
|
|
38
|
+
[GroupID] VARCHAR(50) NOT NULL,
|
|
39
|
+
[Enabled] VARCHAR(50) NULL,
|
|
40
|
+
CONSTRAINT [PK_Group] PRIMARY KEY ([GroupID])
|
|
41
|
+
);
|
|
42
|
+
END
|
|
43
|
+
GO
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
version: '2'
|
|
2
|
+
services:
|
|
3
|
+
# scimgateway:
|
|
4
|
+
# build:
|
|
5
|
+
# context: .
|
|
6
|
+
# dockerfile: ./Dockerfile
|
|
7
|
+
# image: scimgateway:latest
|
|
8
|
+
# container_name: scimgateway
|
|
9
|
+
# depends_on:
|
|
10
|
+
# scimgateway-sqlserver:
|
|
11
|
+
# condition: service_healthy
|
|
12
|
+
# hostname:
|
|
13
|
+
# scimgateway
|
|
14
|
+
# volumes:
|
|
15
|
+
# - ./config:/home/scimgateway/config:rw
|
|
16
|
+
# - /var/lib/dbus:/var/lib/dbus:ro
|
|
17
|
+
# ports:
|
|
18
|
+
# - "8880:8880"
|
|
19
|
+
# # environment:
|
|
20
|
+
# # - NODE_ENV=production
|
|
21
|
+
# # - PORT=8880
|
|
22
|
+
# # - SEED=changeit
|
|
23
|
+
# restart: on-failure:3
|
|
24
|
+
|
|
25
|
+
scimgateway-sqlserver:
|
|
26
|
+
image: mcr.microsoft.com/mssql/server:2019-latest
|
|
27
|
+
hostname:
|
|
28
|
+
MySqlHost
|
|
29
|
+
environment:
|
|
30
|
+
- ACCEPT_EULA=Y
|
|
31
|
+
- SA_PASSWORD=p@ssw0rd!
|
|
32
|
+
- MSSQL_PID=Developer
|
|
33
|
+
ports:
|
|
34
|
+
- 1433:1433
|
|
35
|
+
volumes:
|
|
36
|
+
- ./sqlserver_data:/var/opt/mssql
|
|
37
|
+
user: root
|
|
38
|
+
restart: always
|
|
39
|
+
healthcheck:
|
|
40
|
+
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P \"p@ssw0rd!\" -Q 'SELECT 1' || exit 1"]
|
|
41
|
+
interval: 10s
|
|
42
|
+
retries: 10
|
|
43
|
+
start_period: 10s
|
|
44
|
+
timeout: 3s
|
|
45
|
+
|
|
46
|
+
scimgateway-sqlserver-configurator:
|
|
47
|
+
image: mcr.microsoft.com/mssql/server:2019-latest
|
|
48
|
+
volumes:
|
|
49
|
+
- ./dbinit:/docker-entrypoint-initdb.d
|
|
50
|
+
depends_on:
|
|
51
|
+
scimgateway-sqlserver:
|
|
52
|
+
condition: service_healthy
|
|
53
|
+
restart: no
|
|
54
|
+
command: >
|
|
55
|
+
bash -c '
|
|
56
|
+
/opt/mssql-tools18/bin/sqlcmd -C -S MySqlHost -U sa -P "p@ssw0rd!" -d master -i docker-entrypoint-initdb.d/init.sql;
|
|
57
|
+
echo "All done!";
|
|
58
|
+
'
|
package/lib/helper-rest.ts
CHANGED
|
@@ -297,7 +297,7 @@ export class HelperRest {
|
|
|
297
297
|
} catch (err: any) {
|
|
298
298
|
throw new Error(`tls configuration error: ${err.message}`)
|
|
299
299
|
}
|
|
300
|
-
if (Object.prototype.hasOwnProperty.call(connOpt
|
|
300
|
+
if (connOpt.tls && Object.prototype.hasOwnProperty.call(connOpt.tls, 'rejectUnauthorized')) {
|
|
301
301
|
if (connOpt.tls.rejectUnauthorized !== false && connOpt.tls.rejectUnauthorized !== true) {
|
|
302
302
|
delete connOpt.tls.rejectUnauthorized
|
|
303
303
|
}
|
|
@@ -405,13 +405,18 @@ export class HelperRest {
|
|
|
405
405
|
let dataString = ''
|
|
406
406
|
if (body) {
|
|
407
407
|
if (options.headers['Content-Type']) {
|
|
408
|
-
|
|
408
|
+
const type: string = options.headers['Content-Type'].toLowerCase().trim()
|
|
409
|
+
if (type.startsWith('application/x-www-form-urlencoded')) {
|
|
409
410
|
if (typeof body === 'string') dataString = body
|
|
410
411
|
else dataString = querystring.stringify(body) // JSON to query string syntax + URL encoded
|
|
411
|
-
} else
|
|
412
|
+
} else {
|
|
413
|
+
if (typeof body === 'string') dataString = body
|
|
414
|
+
else dataString = JSON.stringify(body)
|
|
415
|
+
}
|
|
412
416
|
} else {
|
|
413
417
|
options.headers['Content-Type'] = 'application/json; charset=utf-8'
|
|
414
|
-
dataString =
|
|
418
|
+
if (typeof body === 'string') dataString = body
|
|
419
|
+
else dataString = JSON.stringify(body)
|
|
415
420
|
}
|
|
416
421
|
options.headers['Content-Length'] = Buffer.byteLength(dataString, 'utf8')
|
|
417
422
|
options.body = dataString
|
package/lib/plugin-mssql.ts
CHANGED
|
@@ -6,16 +6,40 @@
|
|
|
6
6
|
// Purpose: SQL user-provisioning
|
|
7
7
|
//
|
|
8
8
|
// Prereq:
|
|
9
|
-
// TABLE [
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
9
|
+
// CREATE TABLE [User] (
|
|
10
|
+
// [UserID] VARCHAR(50) NOT NULL,
|
|
11
|
+
// [Enabled] VARCHAR(50) NULL,
|
|
12
|
+
// [Password] VARCHAR(50) NULL,
|
|
13
|
+
// [FirstName] VARCHAR(50) NULL,
|
|
14
|
+
// [MiddleName] VARCHAR(50) NULL,
|
|
15
|
+
// [LastName] VARCHAR(50) NULL,
|
|
16
|
+
// [Email] VARCHAR(50) NULL,
|
|
17
|
+
// [MobilePhone] VARCHAR(50) NULL,
|
|
18
|
+
// CONSTRAINT [PK_User]
|
|
19
|
+
// PRIMARY KEY ([UserID])
|
|
20
|
+
// );
|
|
21
|
+
//
|
|
22
|
+
// CREATE TABLE [Group] (
|
|
23
|
+
// [GroupID] VARCHAR(50) NOT NULL,
|
|
24
|
+
// [Enabled] VARCHAR(50) NULL,
|
|
25
|
+
// CONSTRAINT [PK_Group]
|
|
26
|
+
// PRIMARY KEY ([GroupID])
|
|
27
|
+
// );
|
|
28
|
+
//
|
|
29
|
+
// CREATE TABLE [Users2Group] (
|
|
30
|
+
// [GroupID] VARCHAR(50) NOT NULL,
|
|
31
|
+
// [UserID] VARCHAR(50) NOT NULL,
|
|
32
|
+
// CONSTRAINT [PK_Users2Group]
|
|
33
|
+
// PRIMARY KEY ([GroupID],[UserID]),
|
|
34
|
+
// CONSTRAINT [FK_U2G_Group]
|
|
35
|
+
// FOREIGN KEY ([GroupID])
|
|
36
|
+
// REFERENCES [Group]([GroupID])
|
|
37
|
+
// ON DELETE CASCADE,
|
|
38
|
+
// CONSTRAINT [FK_U2G_Users]
|
|
39
|
+
// FOREIGN KEY ([UserID])
|
|
40
|
+
// REFERENCES [User]([UserID])
|
|
41
|
+
// ON DELETE CASCADE
|
|
42
|
+
// );
|
|
19
43
|
//
|
|
20
44
|
// Supported attributes:
|
|
21
45
|
//
|
|
@@ -69,7 +93,7 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
69
93
|
if (getObj.operator) {
|
|
70
94
|
if (getObj.operator === 'eq' && ['id', 'userName', 'externalId'].includes(getObj.attribute)) {
|
|
71
95
|
// mandatory - unique filtering - single unique user to be returned - correspond to getUser() in versions < 4.x.x
|
|
72
|
-
sqlQuery = `select * from [
|
|
96
|
+
sqlQuery = `select * from [Users] where UserID = '${getObj.value}'`
|
|
73
97
|
} else if (getObj.operator === 'eq' && getObj.attribute === 'group.value') {
|
|
74
98
|
// optional - only used when groups are member of users, not default behavior - correspond to getGroupUsers() in versions < 4.x.x
|
|
75
99
|
throw new Error(`${action} error: not supporting groups member of user filtering: ${getObj.rawFilter}`)
|
|
@@ -82,64 +106,41 @@ scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx) => {
|
|
|
82
106
|
throw new Error(`${action} not error: supporting advanced filtering: ${getObj.rawFilter}`)
|
|
83
107
|
} else {
|
|
84
108
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all users to be returned - correspond to exploreUsers() in versions < 4.x.x
|
|
85
|
-
sqlQuery = 'select * from [
|
|
109
|
+
sqlQuery = 'select * from [Users]'
|
|
86
110
|
}
|
|
87
111
|
// mandatory if-else logic - end
|
|
88
112
|
|
|
89
113
|
if (!sqlQuery) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
90
114
|
|
|
91
115
|
try {
|
|
92
|
-
return await new Promise((resolve, reject) => {
|
|
116
|
+
return await new Promise(async (resolve, reject) => {
|
|
93
117
|
const ret: any = { // itemsPerPage will be set by scimgateway
|
|
94
118
|
Resources: [],
|
|
95
119
|
totalResults: null,
|
|
96
120
|
}
|
|
97
121
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
const users: any[] = await query(sqlQuery, ctx).catch((err: any) => {
|
|
123
|
+
const e = new Error(`${action} error: ${err.message}`)
|
|
124
|
+
return reject(e)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
for (const user of users) {
|
|
128
|
+
const scimUser = {
|
|
129
|
+
id: user.UserID.value ? user.UserID.value : undefined,
|
|
130
|
+
userName: user.UserID.value ? user.UserID.value : undefined,
|
|
131
|
+
active: user.Enabled.value === 'true' || false,
|
|
132
|
+
name: {
|
|
133
|
+
givenName: user.FirstName.value ? user.FirstName.value : undefined,
|
|
134
|
+
middleName: user.MiddleName.value ? user.MiddleName.value : undefined,
|
|
135
|
+
familyName: user.LastName.value ? user.LastName.value : undefined,
|
|
136
|
+
},
|
|
137
|
+
phoneNumbers: user.MobilePhone.value ? [{ type: 'work', value: user.MobilePhone.value }] : undefined,
|
|
138
|
+
emails: user.Email.value ? [{ type: 'work', value: user.Email.value }] : undefined,
|
|
114
139
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
connection.close()
|
|
118
|
-
const e = new Error(`exploreUsers MSSQL client request: ${sqlQuery} Error: ${err.message}`)
|
|
119
|
-
return reject(e)
|
|
120
|
-
}
|
|
140
|
+
ret.Resources.push(scimUser)
|
|
141
|
+
}
|
|
121
142
|
|
|
122
|
-
|
|
123
|
-
const scimUser = {
|
|
124
|
-
id: rows[row].UserID.value ? rows[row].UserID.value : undefined,
|
|
125
|
-
userName: rows[row].UserID.value ? rows[row].UserID.value : undefined,
|
|
126
|
-
active: rows[row].Enabled.value === 'true' || false,
|
|
127
|
-
name: {
|
|
128
|
-
givenName: rows[row].FirstName.value ? rows[row].FirstName.value : undefined,
|
|
129
|
-
middleName: rows[row].MiddleName.value ? rows[row].MiddleName.value : undefined,
|
|
130
|
-
familyName: rows[row].LastName.value ? rows[row].LastName.value : undefined,
|
|
131
|
-
},
|
|
132
|
-
phoneNumbers: rows[row].MobilePhone.value ? [{ type: 'work', value: rows[row].MobilePhone.value }] : undefined,
|
|
133
|
-
emails: rows[row].Email.value ? [{ type: 'work', value: rows[row].Email.value }] : undefined,
|
|
134
|
-
}
|
|
135
|
-
ret.Resources.push(scimUser)
|
|
136
|
-
}
|
|
137
|
-
connection.close()
|
|
138
|
-
resolve(ret) // all explored users
|
|
139
|
-
}) // request
|
|
140
|
-
connection.execSql(request)
|
|
141
|
-
}) // connection
|
|
142
|
-
connection.connect() // initialize the connection
|
|
143
|
+
resolve(ret) // all explored users
|
|
143
144
|
}) // Promise
|
|
144
145
|
} catch (err: any) {
|
|
145
146
|
throw new Error(`${action} error: ${err.message}`)
|
|
@@ -154,7 +155,7 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
|
|
|
154
155
|
scimgateway.logDebug(baseEntity, `handling "${action}" userObj=${JSON.stringify(userObj)}`)
|
|
155
156
|
|
|
156
157
|
try {
|
|
157
|
-
return await new Promise((resolve, reject) => {
|
|
158
|
+
return await new Promise(async (resolve, reject) => {
|
|
158
159
|
if (!userObj.name) userObj.name = {}
|
|
159
160
|
if (!userObj.emails) userObj.emails = { work: {} }
|
|
160
161
|
if (!userObj.phoneNumbers) userObj.phoneNumbers = { work: {} }
|
|
@@ -170,37 +171,15 @@ scimgateway.createUser = async (baseEntity, userObj, ctx) => {
|
|
|
170
171
|
Email: (userObj.emails.work.value) ? `'${userObj.emails.work.value}'` : null,
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
const
|
|
174
|
-
if (ctx?.request?.header?.authorization) { // Auth PassThrough (don't use configuration password)
|
|
175
|
-
if (!connectionCfg.authentication) connectionCfg.authentication = {}
|
|
176
|
-
if (!connectionCfg.authentication.type) connectionCfg.authentication.type = 'default'
|
|
177
|
-
if (!connectionCfg.authentication.options) connectionCfg.authentication.options = {}
|
|
178
|
-
const [username, password] = getCtxAuth(ctx)
|
|
179
|
-
connectionCfg.authentication.options.password = password
|
|
180
|
-
if (username) connectionCfg.authentication.options.userName = username
|
|
181
|
-
}
|
|
182
|
-
const connection = new Connection(connectionCfg)
|
|
183
|
-
|
|
184
|
-
connection.on('connect', function (err) {
|
|
185
|
-
if (err) {
|
|
186
|
-
const e = new Error(`createUser MSSQL client connect error: ${err.message}`)
|
|
187
|
-
return reject(e)
|
|
188
|
-
}
|
|
189
|
-
const sqlQuery = `insert into [User] (UserID, Enabled, Password, FirstName, MiddleName, LastName, Email, MobilePhone)
|
|
174
|
+
const sqlQuery = `insert into [Users] (UserID, Enabled, Password, FirstName, MiddleName, LastName, Email, MobilePhone)
|
|
190
175
|
values (${insert.UserID}, ${insert.Enabled}, ${insert.Password}, ${insert.FirstName}, ${insert.MiddleName}, ${insert.LastName}, ${insert.Email}, ${insert.MobilePhone})`
|
|
191
176
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
connection.close()
|
|
199
|
-
resolve(null)
|
|
200
|
-
}) // request
|
|
201
|
-
connection.execSql(request)
|
|
202
|
-
}) // connection
|
|
203
|
-
connection.connect() // initialize the connection
|
|
177
|
+
await query(sqlQuery, ctx).catch((err: any) => {
|
|
178
|
+
const e = new Error(`${action} error: ${err.message}`)
|
|
179
|
+
return reject(e)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
resolve(null)
|
|
204
183
|
}) // Promise
|
|
205
184
|
} catch (err: any) {
|
|
206
185
|
throw new Error(`${action} error: ${err.message}`)
|
|
@@ -215,36 +194,14 @@ scimgateway.deleteUser = async (baseEntity, id, ctx) => {
|
|
|
215
194
|
scimgateway.logDebug(baseEntity, `handling "${action}" id=${id}`)
|
|
216
195
|
|
|
217
196
|
try {
|
|
218
|
-
return await new Promise((resolve, reject) => {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (username) connectionCfg.authentication.options.userName = username
|
|
227
|
-
}
|
|
228
|
-
const connection = new Connection(connectionCfg)
|
|
229
|
-
|
|
230
|
-
connection.on('connect', function (err) {
|
|
231
|
-
if (err) {
|
|
232
|
-
const e = new Error(`deleteUser MSSQL client connect error: ${err.message}`)
|
|
233
|
-
return reject(e)
|
|
234
|
-
}
|
|
235
|
-
const sqlQuery = `delete from [User] where UserID = '${id}'`
|
|
236
|
-
const request = new Request(sqlQuery, function (err) {
|
|
237
|
-
if (err) {
|
|
238
|
-
connection.close()
|
|
239
|
-
const e = new Error(`deleteUser MSSQL client request: ${sqlQuery} error: ${err.message}`)
|
|
240
|
-
return reject(e)
|
|
241
|
-
}
|
|
242
|
-
connection.close()
|
|
243
|
-
resolve(null)
|
|
244
|
-
}) // request
|
|
245
|
-
connection.execSql(request)
|
|
246
|
-
}) // connection
|
|
247
|
-
connection.connect() // initialize the connection
|
|
197
|
+
return await new Promise(async (resolve, reject) => {
|
|
198
|
+
const sqlQuery = `delete from [Users] where UserID = '${id}'`
|
|
199
|
+
await query(sqlQuery, ctx).catch((err: any) => {
|
|
200
|
+
const e = new Error(`${action} error: ${err.message}`)
|
|
201
|
+
return reject(e)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
resolve(null)
|
|
248
205
|
}) // Promise
|
|
249
206
|
} catch (err: any) {
|
|
250
207
|
throw new Error(`${action} error: ${err.message}`)
|
|
@@ -259,7 +216,7 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
259
216
|
scimgateway.logDebug(baseEntity, `handling "${action}" id=${id} attrObj=${JSON.stringify(attrObj)}`)
|
|
260
217
|
|
|
261
218
|
try {
|
|
262
|
-
return await new Promise((resolve, reject) => {
|
|
219
|
+
return await new Promise(async (resolve, reject) => {
|
|
263
220
|
if (!attrObj.name) attrObj.name = {}
|
|
264
221
|
if (!attrObj.emails) attrObj.emails = { work: {} }
|
|
265
222
|
if (!attrObj.phoneNumbers) attrObj.phoneNumbers = { work: {} }
|
|
@@ -294,35 +251,13 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
294
251
|
|
|
295
252
|
sql = sql.substr(0, sql.length - 1) // remove trailing ","
|
|
296
253
|
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const [username, password] = getCtxAuth(ctx)
|
|
303
|
-
connectionCfg.authentication.options.password = password
|
|
304
|
-
if (username) connectionCfg.authentication.options.userName = username
|
|
305
|
-
}
|
|
306
|
-
const connection = new Connection(connectionCfg)
|
|
254
|
+
const sqlQuery = `update [Users] set ${sql} where UserID like '${id}'`
|
|
255
|
+
await query(sqlQuery, ctx).catch((err: any) => {
|
|
256
|
+
const e = new Error(`${action} error: ${err.message}`)
|
|
257
|
+
return reject(e)
|
|
258
|
+
})
|
|
307
259
|
|
|
308
|
-
|
|
309
|
-
if (err) {
|
|
310
|
-
const e = new Error(`modifyUser MSSQL client connect error: ${err.message}`)
|
|
311
|
-
return reject(e)
|
|
312
|
-
}
|
|
313
|
-
const sqlQuery = `update [User] set ${sql} where UserID like '${id}'`
|
|
314
|
-
const request = new Request(sqlQuery, function (err) {
|
|
315
|
-
if (err) {
|
|
316
|
-
connection.close()
|
|
317
|
-
const e = new Error(`modifyUser MSSQL client request: ${sqlQuery} error: ${err.message}`)
|
|
318
|
-
return reject(e)
|
|
319
|
-
}
|
|
320
|
-
connection.close()
|
|
321
|
-
resolve(null)
|
|
322
|
-
}) // request
|
|
323
|
-
connection.execSql(request)
|
|
324
|
-
}) // connection
|
|
325
|
-
connection.connect() // initialize the connection
|
|
260
|
+
resolve(null)
|
|
326
261
|
}) // Promise
|
|
327
262
|
} catch (err: any) {
|
|
328
263
|
throw new Error(`${action} error: ${err.message}`)
|
|
@@ -332,55 +267,174 @@ scimgateway.modifyUser = async (baseEntity, id, attrObj, ctx) => {
|
|
|
332
267
|
// =================================================
|
|
333
268
|
// getGroups
|
|
334
269
|
// =================================================
|
|
335
|
-
scimgateway.getGroups = async (baseEntity, getObj, attributes) => {
|
|
270
|
+
scimgateway.getGroups = async (baseEntity, getObj, attributes, ctx) => {
|
|
336
271
|
const action = 'getGroups'
|
|
337
272
|
scimgateway.logDebug(baseEntity, `handling "${action}" getObj=${getObj ? JSON.stringify(getObj) : ''} attributes=${attributes}`)
|
|
338
273
|
|
|
274
|
+
let sqlQuery
|
|
275
|
+
|
|
339
276
|
// mandatory if-else logic - start
|
|
340
277
|
if (getObj.operator) {
|
|
341
278
|
if (getObj.operator === 'eq' && ['id', 'displayName', 'externalId'].includes(getObj.attribute)) {
|
|
342
|
-
// mandatory - unique filtering - single unique user to be returned - correspond to
|
|
279
|
+
// mandatory - unique filtering - single unique user to be returned - correspond to getGroup() in versions < 4.x.x
|
|
280
|
+
sqlQuery = `select * from [Groups] where GroupID = '${getObj.value}'`
|
|
343
281
|
} else if (getObj.operator === 'eq' && getObj.attribute === 'members.value') {
|
|
344
282
|
// mandatory - return all groups the user 'id' (getObj.value) is member of - correspond to getGroupMembers() in versions < 4.x.x
|
|
345
|
-
|
|
283
|
+
sqlQuery = `select * from [Groups] join [Users2Group] on Groups.GroupID = Users2Group.GroupID where Users2Group.UserID = '${getObj.value}'`
|
|
346
284
|
} else {
|
|
347
285
|
// optional - simpel filtering
|
|
286
|
+
throw new Error(`${action} error: not supporting simple filtering: ${getObj.rawFilter}`)
|
|
348
287
|
}
|
|
349
288
|
} else if (getObj.rawFilter) {
|
|
350
289
|
// optional - advanced filtering having and/or/not - use getObj.rawFilter
|
|
290
|
+
throw new Error(`${action} not error: supporting advanced filtering: ${getObj.rawFilter}`)
|
|
351
291
|
} else {
|
|
352
292
|
// mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreGroups() in versions < 4.x.x
|
|
293
|
+
sqlQuery = 'select * from [Groups]'
|
|
353
294
|
}
|
|
354
295
|
// mandatory if-else logic - end
|
|
296
|
+
if (!sqlQuery) throw new Error(`${action} error: mandatory if-else logic not fully implemented`)
|
|
355
297
|
|
|
356
|
-
|
|
298
|
+
try {
|
|
299
|
+
return await new Promise(async (resolve, reject) => {
|
|
300
|
+
const ret: any = { // itemsPerPage will be set by scimgateway
|
|
301
|
+
Resources: [],
|
|
302
|
+
totalResults: null,
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const groups = await query(sqlQuery, ctx).catch((err: any) => {
|
|
306
|
+
const e = new Error(`${action} error: ${err.message}`)
|
|
307
|
+
return reject(e)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
for (const group of groups) {
|
|
311
|
+
const scimGroup: Record<string, any> = {
|
|
312
|
+
id: group.GroupID.value ? group.GroupID.value : undefined,
|
|
313
|
+
displayName: group.GroupID.value ? group.GroupID.value : undefined,
|
|
314
|
+
active: group.Enabled.value === 'true' || false,
|
|
315
|
+
members: [],
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const sqlQuery = `select UserID from [Users2Group] where GroupID = '${scimGroup.id}'`
|
|
319
|
+
const members = await query(sqlQuery, ctx).catch(e => console.warn(`${e}`))
|
|
320
|
+
for (const member of members) {
|
|
321
|
+
const scimMember = {
|
|
322
|
+
value: member.UserID.value,
|
|
323
|
+
display: member.UserID.value,
|
|
324
|
+
}
|
|
325
|
+
scimGroup.members.push(scimMember)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
ret.Resources.push(scimGroup)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
resolve(ret)
|
|
332
|
+
}) // Promise
|
|
333
|
+
} catch (err: any) {
|
|
334
|
+
throw new Error(`${action} error: ${err.message}`)
|
|
335
|
+
}
|
|
357
336
|
}
|
|
358
337
|
|
|
359
338
|
// =================================================
|
|
360
339
|
// createGroup
|
|
361
340
|
// =================================================
|
|
362
|
-
scimgateway.createGroup = async (baseEntity, groupObj) => {
|
|
341
|
+
scimgateway.createGroup = async (baseEntity, groupObj, ctx) => {
|
|
363
342
|
const action = 'createGroup'
|
|
364
343
|
scimgateway.logDebug(baseEntity, `handling "${action}" groupObj=${JSON.stringify(groupObj)}`)
|
|
365
|
-
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
return await new Promise(async (resolve, reject) => {
|
|
347
|
+
const insert = {
|
|
348
|
+
GroupID: `'${groupObj.displayName}'`,
|
|
349
|
+
Enabled: (groupObj.active) ? `'${groupObj.active}'` : '\'false\'',
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const sqlQuery = `insert into [Groups] (GroupID, Enabled) values (${insert.GroupID}, ${insert.Enabled})`
|
|
353
|
+
await query(sqlQuery, ctx).catch(e => console.warn(`${e}`))
|
|
354
|
+
|
|
355
|
+
for (const member of groupObj.members) {
|
|
356
|
+
const sqlQuery = `insert into [Users2Group] (UserID, GroupID) values ('${member.value}', ${insert.GroupID})`
|
|
357
|
+
await query(sqlQuery, ctx).catch((err: any) => {
|
|
358
|
+
const e = new Error(`${action} error: ${err.message}`)
|
|
359
|
+
return reject(e)
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
resolve(null)
|
|
364
|
+
}) // Promise
|
|
365
|
+
} catch (err: any) {
|
|
366
|
+
throw new Error(`${action} error: ${err.message}`)
|
|
367
|
+
}
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
// =================================================
|
|
369
371
|
// deleteGroup
|
|
370
372
|
// =================================================
|
|
371
|
-
scimgateway.deleteGroup = async (baseEntity, id) => {
|
|
373
|
+
scimgateway.deleteGroup = async (baseEntity, id, ctx) => {
|
|
372
374
|
const action = 'deleteGroup'
|
|
373
375
|
scimgateway.logDebug(baseEntity, `handling "${action}" id=${id}`)
|
|
374
|
-
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
return await new Promise(async (resolve, reject) => {
|
|
379
|
+
const sqlQuery = `delete from [Groups] where GroupID = '${id}'`
|
|
380
|
+
await query(sqlQuery, ctx).catch((err: any) => {
|
|
381
|
+
const e = new Error(`${action} error: ${err.message}`)
|
|
382
|
+
return reject(e)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
resolve(null)
|
|
386
|
+
}) // Promise
|
|
387
|
+
} catch (err: any) {
|
|
388
|
+
throw new Error(`${action} error: ${err.message}`)
|
|
389
|
+
}
|
|
375
390
|
}
|
|
376
391
|
|
|
377
392
|
// =================================================
|
|
378
393
|
// modifyGroup
|
|
379
394
|
// =================================================
|
|
380
|
-
scimgateway.modifyGroup = async (baseEntity, id, attrObj) => {
|
|
395
|
+
scimgateway.modifyGroup = async (baseEntity, id, attrObj, ctx) => {
|
|
381
396
|
const action = 'modifyGroup'
|
|
382
397
|
scimgateway.logDebug(baseEntity, `handling "${action}" id=${id} attrObj=${JSON.stringify(attrObj)}`)
|
|
383
|
-
|
|
398
|
+
|
|
399
|
+
let sql = ''
|
|
400
|
+
|
|
401
|
+
if (attrObj.active !== undefined) sql += `Enabled='${attrObj.active}',`
|
|
402
|
+
sql = sql.substr(0, sql.length - 1) // remove trailing ","
|
|
403
|
+
|
|
404
|
+
const queries = []
|
|
405
|
+
if (sql) {
|
|
406
|
+
queries.push(`update [Groups] set ${sql} where GroupID like '${id}'`)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// This BLINDLY inserts all user/groups and gracefully breaks on PK violation
|
|
410
|
+
// for each existing membership
|
|
411
|
+
if (Array.isArray(attrObj.members) && attrObj.members) {
|
|
412
|
+
attrObj.members.forEach((member) => {
|
|
413
|
+
if (member.operation == 'delete') {
|
|
414
|
+
queries.push(`delete from [Users2Group] where GroupID='${id}' and UserID='${member.value}'`)
|
|
415
|
+
} else {
|
|
416
|
+
queries.push(`insert into [Users2Group] (UserID, GroupID) values ('${member.value}','${id}')`)
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const sqlQuery = queries.join(';')
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
return await new Promise(async (resolve, reject) => {
|
|
425
|
+
if (sqlQuery) {
|
|
426
|
+
scimgateway.logDebug(baseEntity, `sqlQuery: ${sqlQuery}`)
|
|
427
|
+
await query(sqlQuery, ctx).catch((err: any) => {
|
|
428
|
+
const e = new Error(`${action} error: ${err.message}`)
|
|
429
|
+
return reject(e)
|
|
430
|
+
})
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
resolve(null)
|
|
434
|
+
}) // Promise
|
|
435
|
+
} catch (err: any) {
|
|
436
|
+
throw new Error(`${action} error: ${err.message}`)
|
|
437
|
+
}
|
|
384
438
|
}
|
|
385
439
|
|
|
386
440
|
// =================================================
|
|
@@ -399,6 +453,42 @@ const getCtxAuth = (ctx: undefined | Record<string, any>) => {
|
|
|
399
453
|
else return [undefined, authToken] // bearer auth
|
|
400
454
|
}
|
|
401
455
|
|
|
456
|
+
const connectionCfg = (ctx: undefined | Record<string, any>) => {
|
|
457
|
+
const connectionCfg = scimgateway.copyObj(config.connection)
|
|
458
|
+
if (ctx?.request?.header?.authorization) { // Auth PassThrough (don't use configuration password)
|
|
459
|
+
if (!connectionCfg.authentication) connectionCfg.authentication = {}
|
|
460
|
+
if (!connectionCfg.authentication.type) connectionCfg.authentication.type = 'default'
|
|
461
|
+
if (!connectionCfg.authentication.options) connectionCfg.authentication.options = {}
|
|
462
|
+
const [username, password] = getCtxAuth(ctx)
|
|
463
|
+
connectionCfg.authentication.options.password = password
|
|
464
|
+
if (username) connectionCfg.authentication.options.userName = username
|
|
465
|
+
}
|
|
466
|
+
return connectionCfg
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const query: (sql: string, ctx: any) => Promise<any> = (sql, ctx) => new Promise((resolve, reject) => {
|
|
470
|
+
const connection = new Connection(connectionCfg(ctx))
|
|
471
|
+
|
|
472
|
+
connection.connect((err) => {
|
|
473
|
+
if (err) {
|
|
474
|
+
const e = new Error(`MSSQL client connect error: ${err.message}`)
|
|
475
|
+
reject(e)
|
|
476
|
+
} else {
|
|
477
|
+
const request = new Request(sql, (err, rowCount, rows) => {
|
|
478
|
+
if (err) {
|
|
479
|
+
connection.close()
|
|
480
|
+
const e = new Error(`MSSQL client request: ${sql} Error: ${err.message}`)
|
|
481
|
+
reject(e)
|
|
482
|
+
} else {
|
|
483
|
+
connection.close()
|
|
484
|
+
resolve(rows)
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
connection.execSql(request)
|
|
488
|
+
}
|
|
489
|
+
})
|
|
490
|
+
})
|
|
491
|
+
|
|
402
492
|
//
|
|
403
493
|
// Cleanup on exit
|
|
404
494
|
//
|
package/lib/scimgateway.ts
CHANGED
|
@@ -2688,21 +2688,21 @@ export class ScimGateway {
|
|
|
2688
2688
|
* logDebug logs debug message
|
|
2689
2689
|
**/
|
|
2690
2690
|
logDebug(baseEntity: string | undefined, msg: string) {
|
|
2691
|
-
this.logger.debug(`${this.pluginName}[${baseEntity}]
|
|
2691
|
+
this.logger.debug(`${this.pluginName}[${baseEntity}] ${msg}`)
|
|
2692
2692
|
}
|
|
2693
2693
|
|
|
2694
2694
|
/**
|
|
2695
2695
|
* logInfo logs info message
|
|
2696
2696
|
**/
|
|
2697
2697
|
logInfo(baseEntity: string | undefined, msg: string) {
|
|
2698
|
-
this.logger.info(`${this.pluginName}[${baseEntity}]
|
|
2698
|
+
this.logger.info(`${this.pluginName}[${baseEntity}] ${msg}`)
|
|
2699
2699
|
}
|
|
2700
2700
|
|
|
2701
2701
|
/**
|
|
2702
2702
|
* logError logs error message
|
|
2703
2703
|
**/
|
|
2704
2704
|
logError(baseEntity: string | undefined, msg: string) {
|
|
2705
|
-
this.logger.error(`${this.pluginName}[${baseEntity}]
|
|
2705
|
+
this.logger.error(`${this.pluginName}[${baseEntity}] ${msg}`)
|
|
2706
2706
|
}
|
|
2707
2707
|
|
|
2708
2708
|
/**
|
|
@@ -2919,15 +2919,28 @@ export class ScimGateway {
|
|
|
2919
2919
|
if (!this.helperRest) this.helperRest = new HelperRest(this, { entity: { undefined: { connection: this.config.scimgateway.email } } })
|
|
2920
2920
|
if (this.config.scimgateway.email.auth?.options?.tenantIdGUID) {
|
|
2921
2921
|
// Graph API
|
|
2922
|
+
let content: string
|
|
2923
|
+
if (isHtml) { // ExO workaround for national special caracters in html content - require singleValueExtendedProperties being used
|
|
2924
|
+
var enc = new TextEncoder() // utf-8
|
|
2925
|
+
const buf = enc.encode(msgObj.content)
|
|
2926
|
+
content = new TextDecoder('windows-1252').decode(buf)
|
|
2927
|
+
} else content = msgObj.content
|
|
2928
|
+
|
|
2922
2929
|
const emailMessage: Record<string, any> = {
|
|
2923
2930
|
message: {
|
|
2924
2931
|
subject: msgObj.subject ? msgObj.subject : 'SCIM Gateway message',
|
|
2925
2932
|
body: {
|
|
2926
|
-
content
|
|
2933
|
+
content,
|
|
2927
2934
|
contentType: isHtml ? 'HTML' : 'Text',
|
|
2928
2935
|
},
|
|
2929
2936
|
toRecipients: [],
|
|
2930
2937
|
ccRecipients: [],
|
|
2938
|
+
singleValueExtendedProperties: [ // force using ExO header: Content-Type: text/plain; charset="utf-8"
|
|
2939
|
+
{
|
|
2940
|
+
id: 'Integer 0x3fde', // Content-Type header - can be verifed by checking raw mail
|
|
2941
|
+
value: '65001', // text/plain; charset="utf-8"
|
|
2942
|
+
},
|
|
2943
|
+
],
|
|
2931
2944
|
},
|
|
2932
2945
|
saveToSentItems: 'false',
|
|
2933
2946
|
}
|
package/package.json
CHANGED