samlify 2.12.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 (84) hide show
  1. package/README.md +1 -1
  2. package/build/src/api.js +41 -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 +118 -151
  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 +315 -259
  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 +140 -85
  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 +16 -5
  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 +139 -90
  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/samlify-2.11.0.tgz +0 -0
  64. package/src/api.ts +0 -48
  65. package/src/binding-post.ts +0 -336
  66. package/src/binding-redirect.ts +0 -335
  67. package/src/binding-simplesign.ts +0 -231
  68. package/src/entity-idp.ts +0 -145
  69. package/src/entity-sp.ts +0 -114
  70. package/src/entity.ts +0 -243
  71. package/src/extractor.ts +0 -399
  72. package/src/flow.ts +0 -469
  73. package/src/libsaml.ts +0 -779
  74. package/src/metadata-idp.ts +0 -146
  75. package/src/metadata-sp.ts +0 -203
  76. package/src/metadata.ts +0 -166
  77. package/src/types.ts +0 -127
  78. package/src/urn.ts +0 -210
  79. package/src/utility.ts +0 -259
  80. package/src/validator.ts +0 -44
  81. package/tsconfig.json +0 -41
  82. package/tslint.json +0 -35
  83. package/types.d.ts +0 -2
  84. package/vitest.config.ts +0 -12
package/src/flow.ts DELETED
@@ -1,469 +0,0 @@
1
- import { inflateString, base64Decode } from './utility';
2
- import { verifyTime } from './validator';
3
- import libsaml from './libsaml';
4
- import {
5
- extract,
6
- loginRequestFields,
7
- loginResponseFields,
8
- logoutRequestFields,
9
- logoutResponseFields,
10
- ExtractorFields,
11
- logoutResponseStatusFields,
12
- loginResponseStatusFields
13
- } from './extractor';
14
-
15
- import {
16
- BindingNamespace,
17
- ParserType,
18
- wording,
19
- MessageSignatureOrder,
20
- StatusCode
21
- } from './urn';
22
-
23
- const bindDict = wording.binding;
24
- const urlParams = wording.urlParams;
25
-
26
- export interface FlowResult {
27
- samlContent: string;
28
- extract: any;
29
- sigAlg?: string|null ;
30
- }
31
-
32
- // get the default extractor fields based on the parserType
33
- function getDefaultExtractorFields(parserType: ParserType, assertion?: any): ExtractorFields {
34
- switch (parserType) {
35
- case ParserType.SAMLRequest:
36
- return loginRequestFields;
37
- case ParserType.SAMLResponse:
38
- if (!assertion) {
39
- // unexpected hit
40
- throw new Error('ERR_EMPTY_ASSERTION');
41
- }
42
- return loginResponseFields(assertion);
43
- case ParserType.LogoutRequest:
44
- return logoutRequestFields;
45
- case ParserType.LogoutResponse:
46
- return logoutResponseFields;
47
- default:
48
- throw new Error('ERR_UNDEFINED_PARSERTYPE');
49
- }
50
- }
51
-
52
- // proceed the redirect binding flow
53
- async function redirectFlow(options): Promise<FlowResult> {
54
-
55
- const { request, parserType, self, checkSignature = true, from } = options;
56
- const { query, octetString } = request;
57
- const { SigAlg: sigAlg, Signature: signature } = query;
58
-
59
- const targetEntityMetadata = from.entityMeta;
60
-
61
- // ?SAMLRequest= or ?SAMLResponse=
62
- const direction = libsaml.getQueryParamByType(parserType);
63
- const content = query[direction];
64
-
65
- // query must contain the saml content
66
- if (content === undefined) {
67
- return Promise.reject('ERR_REDIRECT_FLOW_BAD_ARGS');
68
- }
69
-
70
- const xmlString = inflateString(decodeURIComponent(content));
71
-
72
- // validate the xml
73
- try {
74
- await libsaml.isValidXml(xmlString);
75
- } catch (e) {
76
- return Promise.reject('ERR_INVALID_XML');
77
- }
78
-
79
- // check status based on different scenarios
80
- await checkStatus(xmlString, parserType);
81
-
82
- let assertion: string = '';
83
-
84
- if (parserType === urlParams.samlResponse){
85
- // Extract assertion shortcut
86
- const verifiedDoc = extract(xmlString, [{
87
- key: 'assertion',
88
- localPath: ['~Response', 'Assertion'],
89
- attributes: [],
90
- context: true
91
- }]);
92
- if (verifiedDoc && verifiedDoc.assertion){
93
- assertion = verifiedDoc.assertion as string;
94
- }
95
- }
96
-
97
- const extractorFields = getDefaultExtractorFields(parserType, assertion.length > 0 ? assertion : null);
98
-
99
- const parseResult: { samlContent: string, extract: any, sigAlg: (string | null) } = {
100
- samlContent: xmlString,
101
- sigAlg: null,
102
- extract: extract(xmlString, extractorFields),
103
- };
104
-
105
- // see if signature check is required
106
- // only verify message signature is enough
107
- if (checkSignature) {
108
- if (!signature || !sigAlg) {
109
- return Promise.reject('ERR_MISSING_SIG_ALG');
110
- }
111
-
112
- // put the below two assignments into verifyMessageSignature function
113
- const base64Signature = Buffer.from(decodeURIComponent(signature), 'base64');
114
- const decodeSigAlg = decodeURIComponent(sigAlg);
115
-
116
- const verified = libsaml.verifyMessageSignature(targetEntityMetadata, octetString, base64Signature, sigAlg);
117
-
118
- if (!verified) {
119
- // Fail to verify message signature
120
- return Promise.reject('ERR_FAILED_MESSAGE_SIGNATURE_VERIFICATION');
121
- }
122
-
123
- parseResult.sigAlg = decodeSigAlg;
124
- }
125
-
126
- /**
127
- * Validation part: validate the context of response after signature is verified and decrypted (optional)
128
- */
129
- const issuer = targetEntityMetadata.getEntityID();
130
- const extractedProperties = parseResult.extract;
131
-
132
- // unmatched issuer
133
- if (
134
- (parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
135
- && extractedProperties
136
- && extractedProperties.issuer !== issuer
137
- ) {
138
- return Promise.reject('ERR_UNMATCH_ISSUER');
139
- }
140
-
141
- // invalid session time
142
- // only run the verifyTime when `SessionNotOnOrAfter` exists
143
- if (
144
- parserType === 'SAMLResponse'
145
- && extractedProperties.sessionIndex.sessionNotOnOrAfter
146
- && !verifyTime(
147
- undefined,
148
- extractedProperties.sessionIndex.sessionNotOnOrAfter,
149
- self.entitySetting.clockDrifts
150
- )
151
- ) {
152
- return Promise.reject('ERR_EXPIRED_SESSION');
153
- }
154
-
155
- // invalid time
156
- // 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
157
- if (
158
- parserType === 'SAMLResponse'
159
- && extractedProperties.conditions
160
- && !verifyTime(
161
- extractedProperties.conditions.notBefore,
162
- extractedProperties.conditions.notOnOrAfter,
163
- self.entitySetting.clockDrifts
164
- )
165
- ) {
166
- return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
167
- }
168
-
169
- return Promise.resolve(parseResult);
170
- }
171
-
172
- // proceed the post flow
173
- async function postFlow(options): Promise<FlowResult> {
174
-
175
- const {
176
- request,
177
- from,
178
- self,
179
- parserType,
180
- checkSignature = true
181
- } = options;
182
-
183
- const { body } = request;
184
-
185
- const direction = libsaml.getQueryParamByType(parserType);
186
- const encodedRequest = body[direction];
187
-
188
- let samlContent = String(base64Decode(encodedRequest));
189
-
190
- const verificationOptions = {
191
- metadata: from.entityMeta,
192
- signatureAlgorithm: from.entitySetting.requestSignatureAlgorithm,
193
- };
194
-
195
- const decryptRequired = from.entitySetting.isAssertionEncrypted;
196
-
197
- let extractorFields: ExtractorFields = [];
198
-
199
- // validate the xml first
200
- await libsaml.isValidXml(samlContent);
201
-
202
- if (parserType !== urlParams.samlResponse) {
203
- extractorFields = getDefaultExtractorFields(parserType, null);
204
- }
205
-
206
- // check status based on different scenarios
207
- await checkStatus(samlContent, parserType);
208
-
209
- // verify the signatures (the response is encrypted then signed, then verify first then decrypt)
210
- if (
211
- checkSignature
212
- ) {
213
- // VerifiedAssertionNode is signed. Depending on use case, it may actually be a Response Node
214
- const [verified, verifiedAssertionNode] = libsaml.verifySignature(samlContent, verificationOptions);
215
-
216
- // First two cases are encrypted assertion cases
217
- // This case the verifiedAssertionNode is actually a response
218
- if (decryptRequired && verified && parserType === 'SAMLResponse' && verifiedAssertionNode) {
219
- // now it is extracted from solely signed contents
220
- const result = await libsaml.decryptAssertion(self, verifiedAssertionNode);
221
- samlContent = result[0];
222
- // extractor depends on signed content
223
- extractorFields = getDefaultExtractorFields(parserType, result[1]);
224
- } else if (decryptRequired && !verified) {
225
- // Encrypted Assertion, the assertion is signed
226
- const result = await libsaml.decryptAssertion(self, samlContent);
227
- const decryptedDoc = result[0];
228
- const [decryptedDocVerified, verifiedDecryptedAssertion] = libsaml.verifySignature(decryptedDoc, verificationOptions);
229
- if (decryptedDocVerified) {
230
- // extractor depends on signed content
231
- extractorFields = getDefaultExtractorFields(parserType, verifiedDecryptedAssertion);
232
- } else {
233
- return Promise.reject('FAILED_TO_VERIFY_SIGNATURE');
234
- }
235
- } else if (verified) {
236
- // extractor depends on signed content
237
- extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
238
- } else {
239
- return Promise.reject('FAILED_TO_VERIFY_SIGNATURE');
240
- }
241
- }
242
-
243
- const parseResult = {
244
- samlContent: samlContent,
245
- extract: extract(samlContent, extractorFields),
246
- };
247
-
248
- /**
249
- * Validation part: validate the context of response after signature is verified and decrypted (optional)
250
- */
251
- const targetEntityMetadata = from.entityMeta;
252
- const issuer = targetEntityMetadata.getEntityID();
253
- const extractedProperties = parseResult.extract;
254
-
255
- // unmatched issuer
256
- if (
257
- (parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
258
- && extractedProperties
259
- && extractedProperties.issuer !== issuer
260
- ) {
261
- return Promise.reject('ERR_UNMATCH_ISSUER');
262
- }
263
-
264
- // invalid session time
265
- // only run the verifyTime when `SessionNotOnOrAfter` exists
266
- if (
267
- parserType === 'SAMLResponse'
268
- && extractedProperties.sessionIndex.sessionNotOnOrAfter
269
- && !verifyTime(
270
- undefined,
271
- extractedProperties.sessionIndex.sessionNotOnOrAfter,
272
- self.entitySetting.clockDrifts
273
- )
274
- ) {
275
- return Promise.reject('ERR_EXPIRED_SESSION');
276
- }
277
-
278
- // invalid time
279
- // 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
280
- if (
281
- parserType === 'SAMLResponse'
282
- && extractedProperties.conditions
283
- && !verifyTime(
284
- extractedProperties.conditions.notBefore,
285
- extractedProperties.conditions.notOnOrAfter,
286
- self.entitySetting.clockDrifts
287
- )
288
- ) {
289
- return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
290
- }
291
-
292
- return Promise.resolve(parseResult);
293
- }
294
-
295
-
296
- // proceed the post simple sign binding flow
297
- async function postSimpleSignFlow(options): Promise<FlowResult> {
298
-
299
- const { request, parserType, self, checkSignature = true, from } = options;
300
-
301
- const { body, octetString } = request;
302
-
303
- const targetEntityMetadata = from.entityMeta;
304
-
305
- // ?SAMLRequest= or ?SAMLResponse=
306
- const direction = libsaml.getQueryParamByType(parserType);
307
- const encodedRequest: string = body[direction];
308
- const sigAlg: string = body['SigAlg'];
309
- const signature: string = body['Signature'];
310
-
311
- // query must contain the saml content
312
- if (encodedRequest === undefined) {
313
- return Promise.reject('ERR_SIMPLESIGN_FLOW_BAD_ARGS');
314
- }
315
-
316
- const xmlString = String(base64Decode(encodedRequest));
317
-
318
- // validate the xml
319
- try {
320
- await libsaml.isValidXml(xmlString);
321
- } catch (e) {
322
- return Promise.reject('ERR_INVALID_XML');
323
- }
324
-
325
- // check status based on different scenarios
326
- await checkStatus(xmlString, parserType);
327
-
328
- let assertion: string = '';
329
-
330
- if (parserType === urlParams.samlResponse){
331
- // Extract assertion shortcut
332
- const verifiedDoc = extract(xmlString, [{
333
- key: 'assertion',
334
- localPath: ['~Response', 'Assertion'],
335
- attributes: [],
336
- context: true
337
- }]);
338
- if (verifiedDoc && verifiedDoc.assertion){
339
- assertion = verifiedDoc.assertion as string;
340
- }
341
- }
342
-
343
- const extractorFields = getDefaultExtractorFields(parserType, assertion.length > 0 ? assertion : null);
344
-
345
- const parseResult: { samlContent: string, extract: any, sigAlg: (string | null) } = {
346
- samlContent: xmlString,
347
- sigAlg: null,
348
- extract: extract(xmlString, extractorFields),
349
- };
350
-
351
- // see if signature check is required
352
- // only verify message signature is enough
353
- if (checkSignature) {
354
- if (!signature || !sigAlg) {
355
- return Promise.reject('ERR_MISSING_SIG_ALG');
356
- }
357
-
358
- // put the below two assignments into verifyMessageSignature function
359
- const base64Signature = Buffer.from(signature, 'base64');
360
-
361
- const verified = libsaml.verifyMessageSignature(targetEntityMetadata, octetString, base64Signature, sigAlg);
362
-
363
- if (!verified) {
364
- // Fail to verify message signature
365
- return Promise.reject('ERR_FAILED_MESSAGE_SIGNATURE_VERIFICATION');
366
- }
367
-
368
- parseResult.sigAlg = sigAlg;
369
- }
370
-
371
- /**
372
- * Validation part: validate the context of response after signature is verified and decrypted (optional)
373
- */
374
- const issuer = targetEntityMetadata.getEntityID();
375
- const extractedProperties = parseResult.extract;
376
-
377
- // unmatched issuer
378
- if (
379
- (parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
380
- && extractedProperties
381
- && extractedProperties.issuer !== issuer
382
- ) {
383
- return Promise.reject('ERR_UNMATCH_ISSUER');
384
- }
385
-
386
- // invalid session time
387
- // only run the verifyTime when `SessionNotOnOrAfter` exists
388
- if (
389
- parserType === 'SAMLResponse'
390
- && extractedProperties.sessionIndex.sessionNotOnOrAfter
391
- && !verifyTime(
392
- undefined,
393
- extractedProperties.sessionIndex.sessionNotOnOrAfter,
394
- self.entitySetting.clockDrifts
395
- )
396
- ) {
397
- return Promise.reject('ERR_EXPIRED_SESSION');
398
- }
399
-
400
- // invalid time
401
- // 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
402
- if (
403
- parserType === 'SAMLResponse'
404
- && extractedProperties.conditions
405
- && !verifyTime(
406
- extractedProperties.conditions.notBefore,
407
- extractedProperties.conditions.notOnOrAfter,
408
- self.entitySetting.clockDrifts
409
- )
410
- ) {
411
- return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
412
- }
413
-
414
- return Promise.resolve(parseResult);
415
- }
416
-
417
-
418
- function checkStatus(content: string, parserType: string): Promise<string> {
419
-
420
- // only check response parser
421
- if (parserType !== urlParams.samlResponse && parserType !== urlParams.logoutResponse) {
422
- return Promise.resolve('SKIPPED');
423
- }
424
-
425
- const fields = parserType === urlParams.samlResponse
426
- ? loginResponseStatusFields
427
- : logoutResponseStatusFields;
428
-
429
- const {top, second} = extract(content, fields);
430
-
431
- // only resolve when top-tier status code is success
432
- if (top === StatusCode.Success) {
433
- return Promise.resolve('OK');
434
- }
435
-
436
- if (!top) {
437
- throw new Error('ERR_UNDEFINED_STATUS');
438
- }
439
-
440
- // returns a detailed error for two-tier error code
441
- throw new Error(`ERR_FAILED_STATUS with top tier code: ${top}, second tier code: ${second}`);
442
- }
443
-
444
- export function flow(options): Promise<FlowResult> {
445
-
446
- const binding = options.binding;
447
- const parserType = options.parserType;
448
-
449
- options.supportBindings = [BindingNamespace.Redirect, BindingNamespace.Post, BindingNamespace.SimpleSign];
450
- // saml response allows POST, REDIRECT
451
- if (parserType === ParserType.SAMLResponse) {
452
- options.supportBindings = [BindingNamespace.Post, BindingNamespace.Redirect, BindingNamespace.SimpleSign];
453
- }
454
-
455
- if (binding === bindDict.post) {
456
- return postFlow(options);
457
- }
458
-
459
- if (binding === bindDict.redirect) {
460
- return redirectFlow(options);
461
- }
462
-
463
- if (binding === bindDict.simpleSign) {
464
- return postSimpleSignFlow(options);
465
- }
466
-
467
- return Promise.reject('ERR_UNEXPECTED_FLOW');
468
-
469
- }