samlify 2.11.0 → 2.13.0

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.
Files changed (83) hide show
  1. package/README.md +1 -1
  2. package/build/src/api.js +52 -3
  3. package/build/src/api.js.map +1 -1
  4. package/build/src/binding-post.js +236 -182
  5. package/build/src/binding-post.js.map +1 -1
  6. package/build/src/binding-redirect.js +303 -215
  7. package/build/src/binding-redirect.js.map +1 -1
  8. package/build/src/binding-simplesign.js +285 -137
  9. package/build/src/binding-simplesign.js.map +1 -1
  10. package/build/src/entity-idp.js +130 -47
  11. package/build/src/entity-idp.js.map +1 -1
  12. package/build/src/entity-sp.js +81 -39
  13. package/build/src/entity-sp.js.map +1 -1
  14. package/build/src/entity.js +100 -62
  15. package/build/src/entity.js.map +1 -1
  16. package/build/src/extractor.js +119 -155
  17. package/build/src/extractor.js.map +1 -1
  18. package/build/src/flow.js +100 -96
  19. package/build/src/flow.js.map +1 -1
  20. package/build/src/libsaml.js +318 -261
  21. package/build/src/libsaml.js.map +1 -1
  22. package/build/src/metadata-idp.js +60 -30
  23. package/build/src/metadata-idp.js.map +1 -1
  24. package/build/src/metadata-sp.js +51 -41
  25. package/build/src/metadata-sp.js.map +1 -1
  26. package/build/src/metadata.js +47 -43
  27. package/build/src/metadata.js.map +1 -1
  28. package/build/src/options.js +73 -0
  29. package/build/src/options.js.map +1 -0
  30. package/build/src/urn.js +28 -1
  31. package/build/src/urn.js.map +1 -1
  32. package/build/src/utility.js +165 -83
  33. package/build/src/utility.js.map +1 -1
  34. package/build/src/validator.js +27 -10
  35. package/build/src/validator.js.map +1 -1
  36. package/package.json +17 -7
  37. package/types/src/api.d.ts +33 -3
  38. package/types/src/binding-post.d.ts +67 -34
  39. package/types/src/binding-redirect.d.ts +58 -31
  40. package/types/src/binding-simplesign.d.ts +77 -21
  41. package/types/src/entity-idp.d.ts +40 -31
  42. package/types/src/entity-sp.d.ts +37 -27
  43. package/types/src/entity.d.ts +71 -77
  44. package/types/src/extractor.d.ts +31 -22
  45. package/types/src/flow.d.ts +24 -2
  46. package/types/src/libsaml.d.ts +172 -118
  47. package/types/src/metadata-idp.d.ts +27 -11
  48. package/types/src/metadata-sp.d.ts +29 -19
  49. package/types/src/metadata.d.ts +59 -34
  50. package/types/src/options.d.ts +37 -0
  51. package/types/src/types.d.ts +250 -24
  52. package/types/src/urn.d.ts +7 -0
  53. package/types/src/utility.d.ts +144 -89
  54. package/types/src/validator.d.ts +21 -0
  55. package/.circleci/config.yml +0 -98
  56. package/.editorconfig +0 -19
  57. package/.github/FUNDING.yml +0 -1
  58. package/.github/workflows/deploy-docs.yml +0 -56
  59. package/.pre-commit.sh +0 -15
  60. package/.snyk +0 -4
  61. package/Makefile +0 -25
  62. package/index.ts +0 -28
  63. package/src/api.ts +0 -36
  64. package/src/binding-post.ts +0 -336
  65. package/src/binding-redirect.ts +0 -335
  66. package/src/binding-simplesign.ts +0 -231
  67. package/src/entity-idp.ts +0 -145
  68. package/src/entity-sp.ts +0 -114
  69. package/src/entity.ts +0 -243
  70. package/src/extractor.ts +0 -399
  71. package/src/flow.ts +0 -469
  72. package/src/libsaml.ts +0 -777
  73. package/src/metadata-idp.ts +0 -146
  74. package/src/metadata-sp.ts +0 -203
  75. package/src/metadata.ts +0 -166
  76. package/src/types.ts +0 -127
  77. package/src/urn.ts +0 -210
  78. package/src/utility.ts +0 -231
  79. package/src/validator.ts +0 -44
  80. package/tsconfig.json +0 -41
  81. package/tslint.json +0 -35
  82. package/types.d.ts +0 -2
  83. package/vitest.config.ts +0 -12
@@ -1,336 +0,0 @@
1
- /**
2
- * @file binding-post.ts
3
- * @author tngan
4
- * @desc Binding-level API, declare the functions using POST binding
5
- */
6
-
7
- import { wording, namespace, StatusCode } from './urn';
8
- import { BindingContext } from './entity';
9
- import libsaml from './libsaml';
10
- import utility, { get } from './utility';
11
-
12
- const binding = wording.binding;
13
-
14
- /**
15
- * @desc Generate a base64 encoded login request
16
- * @param {string} referenceTagXPath reference uri
17
- * @param {object} entity object includes both idp and sp
18
- * @param {function} customTagReplacement used when developers have their own login response template
19
- */
20
- function base64LoginRequest(referenceTagXPath: string, entity: any, customTagReplacement?: (template: string) => BindingContext): BindingContext {
21
- const metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta };
22
- const spSetting = entity.sp.entitySetting;
23
- let id: string = '';
24
-
25
- if (metadata && metadata.idp && metadata.sp) {
26
- const base = metadata.idp.getSingleSignOnService(binding.post);
27
- let rawSamlRequest: string;
28
- if (spSetting.loginRequestTemplate && customTagReplacement) {
29
- const info = customTagReplacement(spSetting.loginRequestTemplate.context);
30
- id = get(info, 'id', null);
31
- rawSamlRequest = get(info, 'context', null);
32
- } else {
33
- const nameIDFormat = spSetting.nameIDFormat;
34
- const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
35
- id = spSetting.generateID();
36
- rawSamlRequest = libsaml.replaceTagsByValue(libsaml.defaultLoginRequestTemplate.context, {
37
- ID: id,
38
- Destination: base,
39
- Issuer: metadata.sp.getEntityID(),
40
- IssueInstant: new Date().toISOString(),
41
- AssertionConsumerServiceURL: metadata.sp.getAssertionConsumerService(binding.post),
42
- EntityID: metadata.sp.getEntityID(),
43
- AllowCreate: spSetting.allowCreate,
44
- NameIDFormat: selectedNameIDFormat
45
- } as any);
46
- }
47
- if (metadata.idp.isWantAuthnRequestsSigned()) {
48
- const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
49
- return {
50
- id,
51
- context: libsaml.constructSAMLSignature({
52
- referenceTagXPath,
53
- privateKey,
54
- privateKeyPass,
55
- signatureAlgorithm,
56
- transformationAlgorithms,
57
- rawSamlMessage: rawSamlRequest,
58
- signingCert: metadata.sp.getX509Certificate('signing'),
59
- signatureConfig: spSetting.signatureConfig || {
60
- prefix: 'ds',
61
- location: { reference: "/*[local-name(.)='AuthnRequest']/*[local-name(.)='Issuer']", action: 'after' },
62
- }
63
- }),
64
- };
65
- }
66
- // No need to embeded XML signature
67
- return {
68
- id,
69
- context: utility.base64Encode(rawSamlRequest),
70
- };
71
- }
72
- throw new Error('ERR_GENERATE_POST_LOGIN_REQUEST_MISSING_METADATA');
73
- }
74
- /**
75
- * @desc Generate a base64 encoded login response
76
- * @param {object} requestInfo corresponding request, used to obtain the id
77
- * @param {object} entity object includes both idp and sp
78
- * @param {object} user current logged user (e.g. req.user)
79
- * @param {function} customTagReplacement used when developers have their own login response template
80
- * @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
81
- */
82
- async function base64LoginResponse(requestInfo: any = {}, entity: any, user: any = {}, customTagReplacement?: (template: string) => BindingContext, encryptThenSign: boolean = false): Promise<BindingContext> {
83
- const idpSetting = entity.idp.entitySetting;
84
- const spSetting = entity.sp.entitySetting;
85
- const id = idpSetting.generateID();
86
- const metadata = {
87
- idp: entity.idp.entityMeta,
88
- sp: entity.sp.entityMeta,
89
- };
90
- const nameIDFormat = idpSetting.nameIDFormat;
91
- const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
92
- if (metadata && metadata.idp && metadata.sp) {
93
- const base = metadata.sp.getAssertionConsumerService(binding.post);
94
- let rawSamlResponse: string;
95
- const nowTime = new Date();
96
- const spEntityID = metadata.sp.getEntityID();
97
- const fiveMinutesLaterTime = new Date(nowTime.getTime());
98
- fiveMinutesLaterTime.setMinutes(fiveMinutesLaterTime.getMinutes() + 5);
99
- const fiveMinutesLater = fiveMinutesLaterTime.toISOString();
100
- const now = nowTime.toISOString();
101
- const acl = metadata.sp.getAssertionConsumerService(binding.post);
102
- const tvalue: any = {
103
- ID: id,
104
- AssertionID: idpSetting.generateID(),
105
- Destination: base,
106
- Audience: spEntityID,
107
- EntityID: spEntityID,
108
- SubjectRecipient: acl,
109
- Issuer: metadata.idp.getEntityID(),
110
- IssueInstant: now,
111
- AssertionConsumerServiceURL: acl,
112
- StatusCode: StatusCode.Success,
113
- // can be customized
114
- ConditionsNotBefore: now,
115
- ConditionsNotOnOrAfter: fiveMinutesLater,
116
- SubjectConfirmationDataNotOnOrAfter: fiveMinutesLater,
117
- NameIDFormat: selectedNameIDFormat,
118
- NameID: user.email || '',
119
- InResponseTo: get(requestInfo, 'extract.request.id', ''),
120
- AuthnStatement: '',
121
- AttributeStatement: '',
122
- };
123
- if (idpSetting.loginResponseTemplate && customTagReplacement) {
124
- const template = customTagReplacement(idpSetting.loginResponseTemplate.context);
125
- rawSamlResponse = get(template, 'context', null);
126
- } else {
127
- if (requestInfo !== null) {
128
- tvalue.InResponseTo = requestInfo.extract.request.id;
129
- }
130
- rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLoginResponseTemplate.context, tvalue);
131
- }
132
- const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm } = idpSetting;
133
- const config = {
134
- privateKey,
135
- privateKeyPass,
136
- signatureAlgorithm,
137
- signingCert: metadata.idp.getX509Certificate('signing'),
138
- isBase64Output: false,
139
- };
140
- // step: sign assertion ? -> encrypted ? -> sign message ?
141
- if (metadata.sp.isWantAssertionsSigned()) {
142
- // console.debug('sp wants assertion signed');
143
- rawSamlResponse = libsaml.constructSAMLSignature({
144
- ...config,
145
- rawSamlMessage: rawSamlResponse,
146
- transformationAlgorithms: spSetting.transformationAlgorithms,
147
- referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']",
148
- signatureConfig: {
149
- prefix: 'ds',
150
- location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
151
- },
152
- });
153
- }
154
-
155
- // console.debug('after assertion signed', rawSamlResponse);
156
-
157
- // SAML response must be signed sign message first, then encrypt
158
- if (!encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
159
- // console.debug('sign then encrypt and sign entire message');
160
- rawSamlResponse = libsaml.constructSAMLSignature({
161
- ...config,
162
- rawSamlMessage: rawSamlResponse,
163
- isMessageSigned: true,
164
- transformationAlgorithms: spSetting.transformationAlgorithms,
165
- signatureConfig: spSetting.signatureConfig || {
166
- prefix: 'ds',
167
- location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
168
- },
169
- });
170
- }
171
-
172
- // console.debug('after message signed', rawSamlResponse);
173
-
174
- if (idpSetting.isAssertionEncrypted) {
175
- // console.debug('idp is configured to do encryption');
176
- const context = await libsaml.encryptAssertion(entity.idp, entity.sp, rawSamlResponse);
177
- if (encryptThenSign) {
178
- //need to decode it
179
- rawSamlResponse = utility.base64Decode(context) as string;
180
- } else {
181
- return Promise.resolve({ id, context });
182
- }
183
- }
184
-
185
- //sign after encrypting
186
- if (encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
187
- rawSamlResponse = libsaml.constructSAMLSignature({
188
- ...config,
189
- rawSamlMessage: rawSamlResponse,
190
- isMessageSigned: true,
191
- transformationAlgorithms: spSetting.transformationAlgorithms,
192
- signatureConfig: spSetting.signatureConfig || {
193
- prefix: 'ds',
194
- location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
195
- },
196
- });
197
- }
198
-
199
- return Promise.resolve({
200
- id,
201
- context: utility.base64Encode(rawSamlResponse),
202
- });
203
-
204
- }
205
- throw new Error('ERR_GENERATE_POST_LOGIN_RESPONSE_MISSING_METADATA');
206
- }
207
- /**
208
- * @desc Generate a base64 encoded logout request
209
- * @param {object} user current logged user (e.g. req.user)
210
- * @param {string} referenceTagXPath reference uri
211
- * @param {object} entity object includes both idp and sp
212
- * @param {function} customTagReplacement used when developers have their own login response template
213
- * @return {string} base64 encoded request
214
- */
215
- function base64LogoutRequest(user, referenceTagXPath, entity, customTagReplacement?: (template: string) => BindingContext): BindingContext {
216
- const metadata = { init: entity.init.entityMeta, target: entity.target.entityMeta };
217
- const initSetting = entity.init.entitySetting;
218
- const nameIDFormat = initSetting.nameIDFormat;
219
- const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat; let id: string = '';
220
- if (metadata && metadata.init && metadata.target) {
221
- let rawSamlRequest: string;
222
- if (initSetting.logoutRequestTemplate && customTagReplacement) {
223
- const template = customTagReplacement(initSetting.logoutRequestTemplate.context);
224
- id = get(template, 'id', null);
225
- rawSamlRequest = get(template, 'context', null);
226
- } else {
227
- id = initSetting.generateID();
228
- const tvalue: any = {
229
- ID: id,
230
- Destination: metadata.target.getSingleLogoutService(binding.post),
231
- Issuer: metadata.init.getEntityID(),
232
- IssueInstant: new Date().toISOString(),
233
- EntityID: metadata.init.getEntityID(),
234
- NameIDFormat: selectedNameIDFormat,
235
- NameID: user.logoutNameID,
236
- };
237
- rawSamlRequest = libsaml.replaceTagsByValue(libsaml.defaultLogoutRequestTemplate.context, tvalue);
238
- }
239
- if (entity.target.entitySetting.wantLogoutRequestSigned) {
240
- // Need to embeded XML signature
241
- const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = initSetting;
242
- return {
243
- id,
244
- context: libsaml.constructSAMLSignature({
245
- referenceTagXPath,
246
- privateKey,
247
- privateKeyPass,
248
- signatureAlgorithm,
249
- transformationAlgorithms,
250
- rawSamlMessage: rawSamlRequest,
251
- signingCert: metadata.init.getX509Certificate('signing'),
252
- signatureConfig: initSetting.signatureConfig || {
253
- prefix: 'ds',
254
- location: { reference: "/*[local-name(.)='LogoutRequest']/*[local-name(.)='Issuer']", action: 'after' },
255
- }
256
- }),
257
- };
258
- }
259
- return {
260
- id,
261
- context: utility.base64Encode(rawSamlRequest),
262
- };
263
- }
264
- throw new Error('ERR_GENERATE_POST_LOGOUT_REQUEST_MISSING_METADATA');
265
- }
266
- /**
267
- * @desc Generate a base64 encoded logout response
268
- * @param {object} requestInfo corresponding request, used to obtain the id
269
- * @param {string} referenceTagXPath reference uri
270
- * @param {object} entity object includes both idp and sp
271
- * @param {function} customTagReplacement used when developers have their own login response template
272
- */
273
- function base64LogoutResponse(requestInfo: any, entity: any, customTagReplacement: (template: string) => BindingContext): BindingContext {
274
- const metadata = {
275
- init: entity.init.entityMeta,
276
- target: entity.target.entityMeta,
277
- };
278
- let id: string = '';
279
- const initSetting = entity.init.entitySetting;
280
- if (metadata && metadata.init && metadata.target) {
281
- let rawSamlResponse;
282
- if (initSetting.logoutResponseTemplate) {
283
- const template = customTagReplacement(initSetting.logoutResponseTemplate.context);
284
- id = template.id;
285
- rawSamlResponse = template.context;
286
- } else {
287
- id = initSetting.generateID();
288
- const tvalue: any = {
289
- ID: id,
290
- Destination: metadata.target.getSingleLogoutService(binding.post),
291
- EntityID: metadata.init.getEntityID(),
292
- Issuer: metadata.init.getEntityID(),
293
- IssueInstant: new Date().toISOString(),
294
- StatusCode: StatusCode.Success,
295
- InResponseTo: get(requestInfo, 'extract.request.id', null)
296
- };
297
- rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLogoutResponseTemplate.context, tvalue);
298
- }
299
- if (entity.target.entitySetting.wantLogoutResponseSigned) {
300
- const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = initSetting;
301
- return {
302
- id,
303
- context: libsaml.constructSAMLSignature({
304
- isMessageSigned: true,
305
- transformationAlgorithms: transformationAlgorithms,
306
- privateKey,
307
- privateKeyPass,
308
- signatureAlgorithm,
309
- rawSamlMessage: rawSamlResponse,
310
- signingCert: metadata.init.getX509Certificate('signing'),
311
- signatureConfig: {
312
- prefix: 'ds',
313
- location: {
314
- reference: "/*[local-name(.)='LogoutResponse']/*[local-name(.)='Issuer']",
315
- action: 'after'
316
- }
317
- }
318
- }),
319
- };
320
- }
321
- return {
322
- id,
323
- context: utility.base64Encode(rawSamlResponse),
324
- };
325
- }
326
- throw new Error('ERR_GENERATE_POST_LOGOUT_RESPONSE_MISSING_METADATA');
327
- }
328
-
329
- const postBinding = {
330
- base64LoginRequest,
331
- base64LoginResponse,
332
- base64LogoutRequest,
333
- base64LogoutResponse,
334
- };
335
-
336
- export default postBinding;
@@ -1,335 +0,0 @@
1
- /**
2
- * @file binding-redirect.ts
3
- * @author tngan
4
- * @desc Binding-level API, declare the functions using Redirect binding
5
- */
6
- import utility, { get } from './utility';
7
- import libsaml from './libsaml';
8
- import { BindingContext } from './entity';
9
- import { IdentityProvider as Idp } from './entity-idp';
10
- import { ServiceProvider as Sp } from './entity-sp';
11
- import * as url from 'url';
12
- import { wording, namespace } from './urn';
13
-
14
- const binding = wording.binding;
15
- const urlParams = wording.urlParams;
16
-
17
- export interface BuildRedirectConfig {
18
- baseUrl: string;
19
- type: string;
20
- isSigned: boolean;
21
- context: string;
22
- entitySetting: any;
23
- relayState?: string;
24
- }
25
-
26
- /**
27
- * @private
28
- * @desc Helper of generating URL param/value pair
29
- * @param {string} param key
30
- * @param {string} value value of key
31
- * @param {boolean} first determine whether the param is the starting one in order to add query header '?'
32
- * @return {string}
33
- */
34
- function pvPair(param: string, value: string, first?: boolean): string {
35
- return (first === true ? '?' : '&') + param + '=' + value;
36
- }
37
- /**
38
- * @private
39
- * @desc Refractored part of URL generation for login/logout request
40
- * @param {string} type
41
- * @param {boolean} isSigned
42
- * @param {string} rawSamlRequest
43
- * @param {object} entitySetting
44
- * @return {string}
45
- */
46
- function buildRedirectURL(opts: BuildRedirectConfig) {
47
- const {
48
- baseUrl,
49
- type,
50
- isSigned,
51
- context,
52
- entitySetting,
53
- } = opts;
54
- let { relayState = '' } = opts;
55
- const noParams = (url.parse(baseUrl).query || []).length === 0;
56
- const queryParam = libsaml.getQueryParamByType(type);
57
- // In general, this xmlstring is required to do deflate -> base64 -> urlencode
58
- const samlRequest = encodeURIComponent(utility.base64Encode(utility.deflateString(context)));
59
- if (relayState !== '') {
60
- relayState = pvPair(urlParams.relayState, encodeURIComponent(relayState));
61
- }
62
- if (isSigned) {
63
- const sigAlg = pvPair(urlParams.sigAlg, encodeURIComponent(entitySetting.requestSignatureAlgorithm));
64
- const octetString = samlRequest + relayState + sigAlg;
65
- return baseUrl
66
- + pvPair(queryParam, octetString, noParams)
67
- + pvPair(urlParams.signature, encodeURIComponent(
68
- libsaml.constructMessageSignature(
69
- queryParam + '=' + octetString,
70
- entitySetting.privateKey,
71
- entitySetting.privateKeyPass,
72
- undefined,
73
- entitySetting.requestSignatureAlgorithm
74
- ).toString()
75
- )
76
- );
77
- }
78
- return baseUrl + pvPair(queryParam, samlRequest + relayState, noParams);
79
- }
80
- /**
81
- * @desc Redirect URL for login request
82
- * @param {object} entity object includes both idp and sp
83
- * @param {function} customTagReplacement used when developers have their own login response template
84
- * @return {string} redirect URL
85
- */
86
- function loginRequestRedirectURL(entity: { idp: Idp, sp: Sp }, customTagReplacement?: (template: string) => BindingContext): BindingContext {
87
-
88
- const metadata: any = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta };
89
- const spSetting: any = entity.sp.entitySetting;
90
- let id: string = '';
91
-
92
- if (metadata && metadata.idp && metadata.sp) {
93
- const base = metadata.idp.getSingleSignOnService(binding.redirect);
94
- let rawSamlRequest: string;
95
- if (spSetting.loginRequestTemplate && customTagReplacement) {
96
- const info = customTagReplacement(spSetting.loginRequestTemplate);
97
- id = get(info, 'id', null);
98
- rawSamlRequest = get(info, 'context', null);
99
- // Support callback returning { context: string } or { context: { context: string } }
100
- if (typeof rawSamlRequest === 'object' && rawSamlRequest !== null && 'context' in rawSamlRequest) {
101
- rawSamlRequest = (rawSamlRequest as { context: string }).context;
102
- }
103
- } else {
104
- const nameIDFormat = spSetting.nameIDFormat;
105
- const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
106
- id = spSetting.generateID();
107
- rawSamlRequest = libsaml.replaceTagsByValue(libsaml.defaultLoginRequestTemplate.context, {
108
- ID: id,
109
- Destination: base,
110
- Issuer: metadata.sp.getEntityID(),
111
- IssueInstant: new Date().toISOString(),
112
- NameIDFormat: selectedNameIDFormat,
113
- AssertionConsumerServiceURL: metadata.sp.getAssertionConsumerService(binding.post),
114
- EntityID: metadata.sp.getEntityID(),
115
- AllowCreate: spSetting.allowCreate,
116
- } as any);
117
- }
118
- return {
119
- id,
120
- context: buildRedirectURL({
121
- context: rawSamlRequest,
122
- type: urlParams.samlRequest,
123
- isSigned: metadata.sp.isAuthnRequestSigned(),
124
- entitySetting: spSetting,
125
- baseUrl: base,
126
- relayState: spSetting.relayState,
127
- }),
128
- };
129
- }
130
- throw new Error('ERR_GENERATE_REDIRECT_LOGIN_REQUEST_MISSING_METADATA');
131
- }
132
-
133
- /**
134
- * @desc Redirect URL for login response
135
- * @param {object} requestInfo corresponding request, used to obtain the id
136
- * @param {object} entity object includes both idp and sp
137
- * @param {object} user current logged user (e.g. req.user)
138
- * @param {String} relayState the relaystate sent by sp corresponding request
139
- * @param {function} customTagReplacement used when developers have their own login response template
140
- */
141
- function loginResponseRedirectURL(requestInfo: any, entity: any, user: any = {}, relayState?: string, customTagReplacement?: (template: string) => BindingContext): BindingContext {
142
- const idpSetting = entity.idp.entitySetting;
143
- const spSetting = entity.sp.entitySetting;
144
- const metadata = {
145
- idp: entity.idp.entityMeta,
146
- sp: entity.sp.entityMeta,
147
- };
148
-
149
- let id: string = idpSetting.generateID();
150
- if (metadata && metadata.idp && metadata.sp) {
151
- const base = metadata.sp.getAssertionConsumerService(binding.redirect);
152
- let rawSamlResponse: string;
153
- //
154
- const nameIDFormat = idpSetting.nameIDFormat;
155
- const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
156
- const nowTime = new Date();
157
- // Five minutes later : nowtime + 5 * 60 * 1000 (in milliseconds)
158
- const fiveMinutesLaterTime = new Date(nowTime.getTime() + 300_000);
159
- const tvalue: any = {
160
- ID: id,
161
- AssertionID: idpSetting.generateID(),
162
- Destination: base,
163
- SubjectRecipient: base,
164
- Issuer: metadata.idp.getEntityID(),
165
- Audience: metadata.sp.getEntityID(),
166
- EntityID: metadata.sp.getEntityID(),
167
- IssueInstant: nowTime.toISOString(),
168
- AssertionConsumerServiceURL: base,
169
- StatusCode: namespace.statusCode.success,
170
- // can be customized
171
- ConditionsNotBefore: nowTime.toISOString(),
172
- ConditionsNotOnOrAfter: fiveMinutesLaterTime.toISOString(),
173
- SubjectConfirmationDataNotOnOrAfter: fiveMinutesLaterTime.toISOString(),
174
- NameIDFormat: selectedNameIDFormat,
175
- NameID: user.email || '',
176
- InResponseTo: get(requestInfo, 'extract.request.id', ''),
177
- AuthnStatement: '',
178
- AttributeStatement: '',
179
- };
180
-
181
- if (idpSetting.loginResponseTemplate && customTagReplacement) {
182
- const template = customTagReplacement(idpSetting.loginResponseTemplate.context);
183
- id = get(template, 'id', null);
184
- rawSamlResponse = get(template, 'context', null);
185
- } else {
186
-
187
- if (requestInfo !== null) {
188
- tvalue.InResponseTo = requestInfo.extract.request.id;
189
- }
190
- rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLoginResponseTemplate.context, tvalue);
191
- }
192
-
193
- const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm } = idpSetting;
194
- const config = {
195
- privateKey,
196
- privateKeyPass,
197
- signatureAlgorithm,
198
- signingCert: metadata.idp.getX509Certificate('signing'),
199
- isBase64Output: false,
200
- };
201
- // step: sign assertion ? -> encrypted ? -> sign message ?
202
- if (metadata.sp.isWantAssertionsSigned()) {
203
- rawSamlResponse = libsaml.constructSAMLSignature({
204
- ...config,
205
- rawSamlMessage: rawSamlResponse,
206
- transformationAlgorithms: spSetting.transformationAlgorithms,
207
- referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']",
208
- signatureConfig: {
209
- prefix: 'ds',
210
- location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
211
- },
212
- });
213
- }
214
-
215
- // Like in post binding, SAML response is always signed
216
- return {
217
- id,
218
- context: buildRedirectURL({
219
- baseUrl: base,
220
- type: urlParams.samlResponse,
221
- isSigned: true,
222
- context: rawSamlResponse,
223
- entitySetting: idpSetting,
224
- relayState,
225
- }),
226
- };
227
- }
228
- throw new Error('ERR_GENERATE_REDIRECT_LOGIN_RESPONSE_MISSING_METADATA');
229
- }
230
-
231
- /**
232
- * @desc Redirect URL for logout request
233
- * @param {object} user current logged user (e.g. req.user)
234
- * @param {object} entity object includes both idp and sp
235
- * @param {function} customTagReplacement used when developers have their own login response template
236
- * @return {string} redirect URL
237
- */
238
- function logoutRequestRedirectURL(user, entity, relayState?: string, customTagReplacement?: (template: string, tags: object) => BindingContext): BindingContext {
239
- const metadata = { init: entity.init.entityMeta, target: entity.target.entityMeta };
240
- const initSetting = entity.init.entitySetting;
241
- let id: string = initSetting.generateID();
242
- const nameIDFormat = initSetting.nameIDFormat;
243
- const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
244
-
245
- if (metadata && metadata.init && metadata.target) {
246
- const base = metadata.target.getSingleLogoutService(binding.redirect);
247
- let rawSamlRequest: string = '';
248
- const requiredTags = {
249
- ID: id,
250
- Destination: base,
251
- EntityID: metadata.init.getEntityID(),
252
- Issuer: metadata.init.getEntityID(),
253
- IssueInstant: new Date().toISOString(),
254
- NameIDFormat: selectedNameIDFormat,
255
- NameID: user.logoutNameID,
256
- SessionIndex: user.sessionIndex,
257
- };
258
- if (initSetting.logoutRequestTemplate && customTagReplacement) {
259
- const info = customTagReplacement(initSetting.logoutRequestTemplate, requiredTags);
260
- id = get(info, 'id', null);
261
- rawSamlRequest = get(info, 'context', null);
262
- } else {
263
- rawSamlRequest = libsaml.replaceTagsByValue(libsaml.defaultLogoutRequestTemplate.context, requiredTags as any);
264
- }
265
- return {
266
- id,
267
- context: buildRedirectURL({
268
- context: rawSamlRequest,
269
- relayState,
270
- type: urlParams.logoutRequest,
271
- isSigned: entity.target.entitySetting.wantLogoutRequestSigned,
272
- entitySetting: initSetting,
273
- baseUrl: base,
274
- }),
275
- };
276
- }
277
- throw new Error('ERR_GENERATE_REDIRECT_LOGOUT_REQUEST_MISSING_METADATA');
278
- }
279
- /**
280
- * @desc Redirect URL for logout response
281
- * @param {object} requescorresponding request, used to obtain the id
282
- * @param {object} entity object includes both idp and sp
283
- * @param {function} customTagReplacement used when developers have their own login response template
284
- */
285
- function logoutResponseRedirectURL(requestInfo: any, entity: any, relayState?: string, customTagReplacement?: (template: string) => BindingContext): BindingContext {
286
- const metadata = {
287
- init: entity.init.entityMeta,
288
- target: entity.target.entityMeta,
289
- };
290
- const initSetting = entity.init.entitySetting;
291
- let id: string = initSetting.generateID();
292
- if (metadata && metadata.init && metadata.target) {
293
- const base = metadata.target.getSingleLogoutService(binding.redirect);
294
- let rawSamlResponse: string;
295
- if (initSetting.logoutResponseTemplate && customTagReplacement) {
296
- const template = customTagReplacement(initSetting.logoutResponseTemplate);
297
- id = get(template, 'id', null);
298
- rawSamlResponse = get(template, 'context', null);
299
- } else {
300
- const tvalue: any = {
301
- ID: id,
302
- Destination: base,
303
- Issuer: metadata.init.getEntityID(),
304
- EntityID: metadata.init.getEntityID(),
305
- IssueInstant: new Date().toISOString(),
306
- StatusCode: namespace.statusCode.success,
307
- };
308
- if (requestInfo && requestInfo.extract && requestInfo.extract.request) {
309
- tvalue.InResponseTo = requestInfo.extract.request.id;
310
- }
311
- rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLogoutResponseTemplate.context, tvalue);
312
- }
313
- return {
314
- id,
315
- context: buildRedirectURL({
316
- baseUrl: base,
317
- type: urlParams.logoutResponse,
318
- isSigned: entity.target.entitySetting.wantLogoutResponseSigned,
319
- context: rawSamlResponse,
320
- entitySetting: initSetting,
321
- relayState,
322
- }),
323
- };
324
- }
325
- throw new Error('ERR_GENERATE_REDIRECT_LOGOUT_RESPONSE_MISSING_METADATA');
326
- }
327
-
328
- const redirectBinding = {
329
- loginRequestRedirectURL,
330
- loginResponseRedirectURL,
331
- logoutRequestRedirectURL,
332
- logoutResponseRedirectURL,
333
- };
334
-
335
- export default redirectBinding;