scimgateway 5.0.8 → 5.0.9
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 +6 -0
- package/bun.lockb +0 -0
- package/lib/helper-rest.ts +69 -13
- package/lib/logger.ts +1 -1
- package/lib/samlAssertion.ts +220 -0
- package/lib/scimgateway.ts +2 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1111,6 +1111,12 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1111
1111
|
|
|
1112
1112
|
## Change log
|
|
1113
1113
|
|
|
1114
|
+
### v5.0.9
|
|
1115
|
+
|
|
1116
|
+
[Improved]
|
|
1117
|
+
|
|
1118
|
+
- HelperRest doRequest() now support configuration auth type `oauthSamlAssertion` for OAuth SAML token assertion. Please see code editor method IntelliSense for details
|
|
1119
|
+
|
|
1114
1120
|
### v5.0.8
|
|
1115
1121
|
|
|
1116
1122
|
[Fixed]
|
package/bun.lockb
CHANGED
|
Binary file
|
package/lib/helper-rest.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { HttpsProxyAgent } from 'https-proxy-agent'
|
|
2
2
|
import { URL } from 'url'
|
|
3
3
|
import { Buffer } from 'node:buffer'
|
|
4
|
+
import { samlAssertion } from './samlAssertion.ts' // prereq: saml
|
|
4
5
|
import fs from 'node:fs'
|
|
5
6
|
import querystring from 'querystring'
|
|
6
7
|
import * as utils from './utils.ts'
|
|
7
|
-
// import type { ScimGateway } from 'scimgateway' // comment out for supporting Node.js, using type any and no IntelliSense
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* HelperRest includes function doRequest() for
|
|
10
|
+
* HelperRest includes function doRequest() for executing REST calls
|
|
11
11
|
*/
|
|
12
12
|
export class HelperRest {
|
|
13
13
|
private lock = new utils.Lock()
|
|
@@ -118,6 +118,31 @@ export class HelperRest {
|
|
|
118
118
|
}
|
|
119
119
|
break
|
|
120
120
|
|
|
121
|
+
case 'oauthSamlAssertion':
|
|
122
|
+
tokenUrl = this.config_entity[baseEntity].connection.auth.options.tokenUrl
|
|
123
|
+
const context = null
|
|
124
|
+
const cert = fs.readFileSync(this.config_entity[baseEntity].connection.auth.options.certificate.cert).toString()
|
|
125
|
+
const key = fs.readFileSync(this.config_entity[baseEntity].connection.auth.options.certificate.key).toString()
|
|
126
|
+
|
|
127
|
+
const issuer = `scimgateway/${this.scimgateway.pluginName}`
|
|
128
|
+
const lifetime = 3600
|
|
129
|
+
const clientId = this.config_entity[baseEntity].connection.auth.options.clientId
|
|
130
|
+
const nameId = this.config_entity[baseEntity].connection.auth.options.userId
|
|
131
|
+
const userIdentifierFormat = 'userName'
|
|
132
|
+
const tokenEndpoint = tokenUrl
|
|
133
|
+
const audience = `scimgateway/${this.scimgateway.pluginName}`
|
|
134
|
+
const delay = 1
|
|
135
|
+
|
|
136
|
+
const assertion = await samlAssertion.run(context, cert, key, issuer, lifetime, clientId, nameId, userIdentifierFormat, tokenEndpoint, audience, delay)
|
|
137
|
+
form = {
|
|
138
|
+
token_url: tokenUrl,
|
|
139
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
|
|
140
|
+
client_id: clientId,
|
|
141
|
+
company_id: this.config_entity[baseEntity].connection.auth.options.companyId,
|
|
142
|
+
assertion: assertion,
|
|
143
|
+
}
|
|
144
|
+
break
|
|
145
|
+
|
|
121
146
|
default:
|
|
122
147
|
this.lock.release()
|
|
123
148
|
throw new Error(`getAccessToken() none supported entity.${baseEntity}.connection.auth.type: '${this.config_entity[baseEntity]?.connection?.auth?.type}'`)
|
|
@@ -176,7 +201,7 @@ export class HelperRest {
|
|
|
176
201
|
* @param baseEntity baseEntity
|
|
177
202
|
* @param method GET/PATCH/PUT/DELETE
|
|
178
203
|
* @param path e.g., /Users having baseUrl from configuration added, or full url e.g. https://mycompany.com/Users
|
|
179
|
-
* @param opt optional, connection
|
|
204
|
+
* @param opt optional, connection options
|
|
180
205
|
* @param ctx optional, ctx included if using Auth PassThrough
|
|
181
206
|
* @returns client.options needed for connect
|
|
182
207
|
*/
|
|
@@ -255,7 +280,7 @@ export class HelperRest {
|
|
|
255
280
|
const err = new Error(`auth type 'oauth' - missing configuration entity.${baseEntity}.connection.auth.options.clientId/clientSecret`)
|
|
256
281
|
throw err
|
|
257
282
|
}
|
|
258
|
-
param.accessToken = await this.getAccessToken(baseEntity, ctx)
|
|
283
|
+
param.accessToken = await this.getAccessToken(baseEntity, ctx)
|
|
259
284
|
param.options.headers['Authorization'] = `Bearer ${param.accessToken.access_token}`
|
|
260
285
|
break
|
|
261
286
|
case 'token':
|
|
@@ -263,7 +288,7 @@ export class HelperRest {
|
|
|
263
288
|
const err = new Error(`missing configuration entity.${baseEntity}.connection.auth.options.tokenUrl/password`)
|
|
264
289
|
throw err
|
|
265
290
|
}
|
|
266
|
-
param.accessToken = await this.getAccessToken(baseEntity, ctx)
|
|
291
|
+
param.accessToken = await this.getAccessToken(baseEntity, ctx)
|
|
267
292
|
param.options.headers['Authorization'] = `Bearer ${param.accessToken.access_token}`
|
|
268
293
|
break
|
|
269
294
|
case 'bearer':
|
|
@@ -273,6 +298,15 @@ export class HelperRest {
|
|
|
273
298
|
}
|
|
274
299
|
param.options.headers['Authorization'] = 'Bearer ' + Buffer.from(this.config_entity[baseEntity].connection.auth.options.token).toString('base64')
|
|
275
300
|
break
|
|
301
|
+
case 'oauthSamlAssertion':
|
|
302
|
+
if (!this.config_entity[baseEntity]?.connection?.auth?.options?.clientId || !this.config_entity[baseEntity]?.connection?.auth?.options?.companyId
|
|
303
|
+
|| !this.config_entity[baseEntity]?.connection?.auth?.options?.certificate?.key) {
|
|
304
|
+
const err = new Error(`auth type 'oauthSamlAssertion' - missing configuration entity.${baseEntity}.connection.auth.options...`)
|
|
305
|
+
throw err
|
|
306
|
+
}
|
|
307
|
+
param.accessToken = await this.getAccessToken(baseEntity, ctx)
|
|
308
|
+
param.options.headers['Authorization'] = `Bearer ${param.accessToken.access_token}`
|
|
309
|
+
break
|
|
276
310
|
default:
|
|
277
311
|
// no auth
|
|
278
312
|
}
|
|
@@ -426,6 +460,7 @@ export class HelperRest {
|
|
|
426
460
|
const timeout = setTimeout(() => controller.abort(), options.abortTimeout ? options.abortTimeout * 1000 : this.idleTimeout * 1000) // 120 seconds default abort timeout
|
|
427
461
|
options.signal = signal
|
|
428
462
|
const url = `${options.protocol}//${options.host}${options.port ? ':' + options.port : ''}${options.path}`
|
|
463
|
+
if (path.includes(')?$') && !options.headers['Accept-Encoding']) options.headers['Accept-Encoding'] = 'identity' // workaround for bun fetch error: "Decompression error: ShortRead" - have seen this error using OData with "<some-path>('xxx')?$expand=" or "<some-path>('xxx')?$select=" ref: https://github.com/oven-sh/bun/issues/8017
|
|
429
464
|
// execute request
|
|
430
465
|
const f = await fetch(url, options)
|
|
431
466
|
clearTimeout(timeout)
|
|
@@ -534,15 +569,20 @@ export class HelperRest {
|
|
|
534
569
|
* "entity": {
|
|
535
570
|
* "undefined": {
|
|
536
571
|
* "connection": {
|
|
537
|
-
* "baseUrls": [
|
|
572
|
+
* "baseUrls": [ // ignored when using option tenantIdGUID
|
|
538
573
|
* "<baseUrl>", // "https://host1.company.com:8880",
|
|
539
574
|
* "<baseUrl2>" // optional using several baseUrls for failover
|
|
540
575
|
* ],
|
|
541
576
|
* "auth": {
|
|
542
|
-
* "type": "<type>"
|
|
577
|
+
* "type": "<type>",
|
|
543
578
|
* "options": { <auth.options> }
|
|
544
579
|
* },
|
|
545
580
|
* "options": { <connection.options> }
|
|
581
|
+
* "proxy": {
|
|
582
|
+
* "host": "<host>", // http://proxy-host:1234
|
|
583
|
+
* "username": "<username>", // username if authentication is required
|
|
584
|
+
* "password": "<password>" // password if authentication is required
|
|
585
|
+
* }
|
|
546
586
|
* }
|
|
547
587
|
* }
|
|
548
588
|
* }
|
|
@@ -551,11 +591,11 @@ export class HelperRest {
|
|
|
551
591
|
* ```
|
|
552
592
|
* type defines authentication being used
|
|
553
593
|
* if type not defined, no authentication used
|
|
554
|
-
* valid type is: `basic`, `oauth`, `token` or `
|
|
594
|
+
* valid type is: `basic`, `oauth`, `token`, `bearer` or `oauthSamlAssertion`
|
|
555
595
|
*
|
|
556
596
|
* for each valid type there are different auth.options
|
|
557
597
|
*
|
|
558
|
-
* type=**basic
|
|
598
|
+
* type=**"basic"** having auth.options:
|
|
559
599
|
* ```
|
|
560
600
|
* {
|
|
561
601
|
* "options": {
|
|
@@ -565,11 +605,11 @@ export class HelperRest {
|
|
|
565
605
|
* }
|
|
566
606
|
* ```
|
|
567
607
|
*
|
|
568
|
-
* type=**oauth
|
|
608
|
+
* type=**"oauth"** having auth.options:
|
|
569
609
|
* ```
|
|
570
610
|
* {
|
|
571
611
|
* "options": {
|
|
572
|
-
* "tenantIdGUID": "<Entra ID tenantIdGUID", //
|
|
612
|
+
* "tenantIdGUID": "<Entra ID tenantIdGUID", // simplified configuration for using Microsoft Graph API
|
|
573
613
|
* "tokenUrl": "<tokenUrl>", // not used when tenantIdGUID defined
|
|
574
614
|
* "clientId": "<clientId",
|
|
575
615
|
* "clientSecret": "<clientSecret>"
|
|
@@ -577,7 +617,7 @@ export class HelperRest {
|
|
|
577
617
|
* }
|
|
578
618
|
* ```
|
|
579
619
|
*
|
|
580
|
-
* type=**token
|
|
620
|
+
* type=**"token"** having auth.options:
|
|
581
621
|
* ```
|
|
582
622
|
* {
|
|
583
623
|
* "options": {
|
|
@@ -588,7 +628,7 @@ export class HelperRest {
|
|
|
588
628
|
* }
|
|
589
629
|
* ```
|
|
590
630
|
*
|
|
591
|
-
* type=**bearer
|
|
631
|
+
* type=**"bearer"** having auth.options:
|
|
592
632
|
* ```
|
|
593
633
|
* {
|
|
594
634
|
* "options": {
|
|
@@ -597,6 +637,22 @@ export class HelperRest {
|
|
|
597
637
|
* }
|
|
598
638
|
* ```
|
|
599
639
|
*
|
|
640
|
+
* type=**"oauthSamlAssertion"** having auth.options:
|
|
641
|
+
* ```
|
|
642
|
+
* {
|
|
643
|
+
* "options": {
|
|
644
|
+
* "tokenUrl": "<tokenUrl>",
|
|
645
|
+
* "clientId": "<clientId>",
|
|
646
|
+
* "companyId": "<companyId>",
|
|
647
|
+
* "userId": "<userId>",
|
|
648
|
+
* "certificate": {
|
|
649
|
+
* "key": "<key-file-name>", // location: config/certs
|
|
650
|
+
* "cert": "<cert-file-name>", // location: config/certs
|
|
651
|
+
* }
|
|
652
|
+
* }
|
|
653
|
+
* }
|
|
654
|
+
* ```
|
|
655
|
+
*
|
|
600
656
|
* **connection.options** can be set according to web-standard fetch client options
|
|
601
657
|
* examples:
|
|
602
658
|
* ```
|
package/lib/logger.ts
CHANGED
|
@@ -96,7 +96,7 @@ export class Log {
|
|
|
96
96
|
customMaskXml = customMasking.join('"?|')
|
|
97
97
|
customMaskXml = '|' + customMaskXml + '"?'
|
|
98
98
|
}
|
|
99
|
-
this.reJson = `^.*"(password|access_token|client_secret${customMaskJson})" ?: ?"([^"]+)".*`
|
|
99
|
+
this.reJson = `^.*"(password|access_token|client_secret|assertion${customMaskJson})" ?: ?"([^"]+)".*`
|
|
100
100
|
this.reXml = `^.*(credentials"?|PasswordText"?|PasswordDigest"?|password"?${customMaskXml})>([^<]+)</.*`
|
|
101
101
|
|
|
102
102
|
const trans: any = [
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Purpose: create SAML token assertion that can be used by OAuth token request having grant type saml2-bearer
|
|
3
|
+
// Based on: https://github.com/edersouza38/insomnia-plugin-sfsf-samlassertion
|
|
4
|
+
//
|
|
5
|
+
// MIT License
|
|
6
|
+
//
|
|
7
|
+
// Copyright (c) 2023 edersouza38
|
|
8
|
+
//
|
|
9
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
// in the Software without restriction, including without limitation the rights
|
|
12
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
// furnished to do so, subject to the following conditions:
|
|
15
|
+
//
|
|
16
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
// copies or substantial portions of the Software.
|
|
18
|
+
//
|
|
19
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
// SOFTWARE.
|
|
26
|
+
//
|
|
27
|
+
|
|
28
|
+
// @ts-expect-error type declaration file not found
|
|
29
|
+
import { Saml20 as saml } from 'saml'
|
|
30
|
+
import crypto from 'node:crypto'
|
|
31
|
+
|
|
32
|
+
export const samlAssertionUtils = {
|
|
33
|
+
formatPrivateKey: function (input: string) {
|
|
34
|
+
// Validate PEM keys:
|
|
35
|
+
const keyArmor = /-----(BEGIN |END )(.*?) KEY-----/g
|
|
36
|
+
let v: any = [...input.matchAll(keyArmor)]
|
|
37
|
+
if (v.length > 0) {
|
|
38
|
+
if (v.length !== 2 || v[0][2] !== v[1][2] || v[0][2] !== 'PRIVATE') {
|
|
39
|
+
throw new Error('Invalid PEM private key. Make sure that the armoring is consistent and the PEM key is from the type "PRIVATE".')
|
|
40
|
+
}
|
|
41
|
+
return input.replace(/\r?\n|\r/g, '')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Verify whether key was generated directly in SFSF:
|
|
45
|
+
const d = Buffer.from(input, 'base64').toString('utf-8')
|
|
46
|
+
v = d.split('###')
|
|
47
|
+
if (v.length === 2) {
|
|
48
|
+
input = v[0]
|
|
49
|
+
}
|
|
50
|
+
return `-----BEGIN PRIVATE KEY-----${input}-----END PRIVATE KEY-----`
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
formatCertificate: function (input: string) {
|
|
54
|
+
// Validate PEM keys:
|
|
55
|
+
const keyArmor = /-----(BEGIN |END )(.*?)-----/g
|
|
56
|
+
let v: any = [...input.matchAll(keyArmor)]
|
|
57
|
+
if (v.length > 0) {
|
|
58
|
+
if (v.length !== 2 || v[0][2] !== v[1][2]) {
|
|
59
|
+
throw new Error('Invalid PEM certificate. Make sure that the armoring is consistent.')
|
|
60
|
+
}
|
|
61
|
+
return input.replace(/\r?\n|\r/g, '')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Verify whether key was generated directly in SFSF:
|
|
65
|
+
const d = Buffer.from(input, 'base64').toString('utf-8')
|
|
66
|
+
v = d.split('###')
|
|
67
|
+
if (v.length === 2) {
|
|
68
|
+
input = v[0]
|
|
69
|
+
}
|
|
70
|
+
return `-----BEGIN CERTIFICATE-----${input}-----END CERTIFICATE-----`
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
delay: function (time: number) {
|
|
74
|
+
return new Promise(resolve => setTimeout(resolve, time))
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
userIdentifierFormat: {
|
|
78
|
+
userId: 'userId',
|
|
79
|
+
userName: 'userName',
|
|
80
|
+
eMail: 'e-Mail',
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const samlAssertion = {
|
|
85
|
+
name: 'samlAssertionSFSF',
|
|
86
|
+
displayName: 'SAML Assertion - SFSF',
|
|
87
|
+
description: 'Create a SAML Assertion for SFSF OAuth2SAMLAssertion flow.',
|
|
88
|
+
args: [
|
|
89
|
+
{
|
|
90
|
+
displayName: 'X.509 Certificate',
|
|
91
|
+
description: 'X.509 Certificate used to identify the SAML IdP',
|
|
92
|
+
type: 'string',
|
|
93
|
+
placeholder: '-----BEGIN CERTIFICATE-----',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
displayName: 'Private Key',
|
|
97
|
+
description: 'Private Key used to sign the SAML Assertion',
|
|
98
|
+
type: 'string',
|
|
99
|
+
placeholder: '-----BEGIN PRIVATE KEY-----',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
displayName: 'SAML Issuer',
|
|
103
|
+
description: 'Name of the IdP issuing the SAML Assertion',
|
|
104
|
+
type: 'string',
|
|
105
|
+
defaultValue: 'local.insomnia.com',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
displayName: 'Lifetime in seconds',
|
|
109
|
+
description: 'Lifetime of the SAML Assertion in seconds',
|
|
110
|
+
type: 'number',
|
|
111
|
+
defaultValue: 600,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
displayName: 'Client Id',
|
|
115
|
+
description: 'Registered Client Id in SFSF',
|
|
116
|
+
type: 'string',
|
|
117
|
+
placeholder: 'OWE1Yzg0NTMyOGJlY2M4NWRiZGFiMGE3MTI5MA',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
displayName: 'User Identifier',
|
|
121
|
+
description: 'User Identifier',
|
|
122
|
+
type: 'string',
|
|
123
|
+
placeholder: 'Username',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
displayName: 'User Identifier Format',
|
|
127
|
+
description: 'User Identifier Format',
|
|
128
|
+
type: 'enum',
|
|
129
|
+
placeholder: 'User Identifier Format',
|
|
130
|
+
defaultValue: samlAssertionUtils.userIdentifierFormat.userId,
|
|
131
|
+
options: [
|
|
132
|
+
{
|
|
133
|
+
displayName: 'User ID',
|
|
134
|
+
value: samlAssertionUtils.userIdentifierFormat.userId,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
displayName: 'Username',
|
|
138
|
+
value: samlAssertionUtils.userIdentifierFormat.userName,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
displayName: 'E-Mail',
|
|
142
|
+
value: samlAssertionUtils.userIdentifierFormat.eMail,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
displayName: 'OAuth Token Endpoint',
|
|
148
|
+
description: 'SFSF OAuth Token Endpoint',
|
|
149
|
+
type: 'string',
|
|
150
|
+
placeholder: 'Username',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
displayName: 'Audience',
|
|
154
|
+
description: 'Audience of the SAML Assertion',
|
|
155
|
+
type: 'string',
|
|
156
|
+
defaultValue: 'www.successfactors.com',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
displayName: 'Delay (Seconds)',
|
|
160
|
+
description: 'Useful when the request is reaching the endpoint before the "validNotBefore" date from SAML assertion.',
|
|
161
|
+
type: 'number',
|
|
162
|
+
defaultValue: 0,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
|
|
166
|
+
async run(
|
|
167
|
+
context: any,
|
|
168
|
+
cert: any,
|
|
169
|
+
key: any,
|
|
170
|
+
issuer: any,
|
|
171
|
+
lifetime: any,
|
|
172
|
+
clientId: any,
|
|
173
|
+
nameId: any,
|
|
174
|
+
userIdentifierFormat: any,
|
|
175
|
+
tokenEndpoint: any,
|
|
176
|
+
audience: any,
|
|
177
|
+
delay: any,
|
|
178
|
+
) {
|
|
179
|
+
const samlAttributes: Record<string, any> = {
|
|
180
|
+
api_key: clientId,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let nameIdentifierFormat
|
|
184
|
+
= 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
|
|
185
|
+
|
|
186
|
+
switch (userIdentifierFormat) {
|
|
187
|
+
case samlAssertionUtils.userIdentifierFormat.eMail:
|
|
188
|
+
nameIdentifierFormat
|
|
189
|
+
= 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
|
|
190
|
+
break
|
|
191
|
+
case samlAssertionUtils.userIdentifierFormat.userName:
|
|
192
|
+
samlAttributes.use_username = 'true'
|
|
193
|
+
break
|
|
194
|
+
default:
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
const options = {
|
|
198
|
+
cert: samlAssertionUtils.formatCertificate(cert),
|
|
199
|
+
key: samlAssertionUtils.formatPrivateKey(key),
|
|
200
|
+
issuer: issuer,
|
|
201
|
+
lifetimeInSeconds: lifetime,
|
|
202
|
+
audiences: audience,
|
|
203
|
+
attributes: samlAttributes,
|
|
204
|
+
nameIdentifier: nameId,
|
|
205
|
+
nameIdentifierFormat: nameIdentifierFormat,
|
|
206
|
+
recipient: tokenEndpoint,
|
|
207
|
+
sessionIndex: '_' + crypto.randomUUID(),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const assertionBuff = Buffer.from(saml.create(options))
|
|
211
|
+
const assertion = assertionBuff.toString('base64')
|
|
212
|
+
|
|
213
|
+
if (delay > 0) {
|
|
214
|
+
await samlAssertionUtils.delay(delay * 1000)
|
|
215
|
+
}
|
|
216
|
+
return assertion
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export default samlAssertion
|
package/lib/scimgateway.ts
CHANGED
|
@@ -2382,9 +2382,9 @@ export class ScimGateway {
|
|
|
2382
2382
|
controller.close()
|
|
2383
2383
|
},
|
|
2384
2384
|
})
|
|
2385
|
-
response = new Response(body ? stream :
|
|
2385
|
+
response = new Response(body ? stream : body, { status: ctx.response.status, headers: ctx.response.headers })
|
|
2386
2386
|
} else {
|
|
2387
|
-
response = new Response(body
|
|
2387
|
+
response = new Response(body, { status: ctx.response.status, headers: ctx.response.headers })
|
|
2388
2388
|
}
|
|
2389
2389
|
logResult(ctx)
|
|
2390
2390
|
return response
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Using SCIM protocol as a gateway for user provisioning to other endpoints",
|
|
6
6
|
"author": "Jarle Elshaug <jarle.elshaug@gmail.com> (https://elshaug.xyz)",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"nodemailer": "^6.9.13",
|
|
53
53
|
"passport": "^0.7.0",
|
|
54
54
|
"passport-azure-ad": "^4.3.5",
|
|
55
|
+
"saml": "^3.0.1",
|
|
55
56
|
"winston": "^3.13.0"
|
|
56
57
|
},
|
|
57
58
|
"peerDependencies": {
|