samlesa 2.16.5 → 2.17.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.

Potentially problematic release.


This version of samlesa might be problematic. Click here for more details.

Files changed (59) hide show
  1. package/README.md +30 -50
  2. package/build/src/binding-post.js +45 -31
  3. package/build/src/binding-redirect.js +88 -3
  4. package/build/src/binding-simplesign.js +0 -1
  5. package/build/src/entity-idp.js +1 -5
  6. package/build/src/entity-sp.js +115 -23
  7. package/build/src/extractor.js +29 -4
  8. package/build/src/flow.js +36 -103
  9. package/build/src/libsaml.js +172 -162
  10. package/build/src/metadata-sp.js +2 -0
  11. package/build/src/metadata.js +0 -2
  12. package/build/src/schema/saml-schema-ecp-2.0.xsd +1 -1
  13. package/build/src/schema/saml-schema-metadata-2.0.xsd +3 -3
  14. package/build/src/schema/saml-schema-protocol-2.0.xsd +1 -1
  15. package/build/src/schema/{env.xsd → soap-envelope.xsd} +1 -33
  16. package/build/src/schema/xml.xsd +88 -0
  17. package/build/src/schemaValidator.js +29 -12
  18. package/build/src/utility.js +12 -7
  19. package/package.json +14 -20
  20. package/types/src/api.d.ts +3 -3
  21. package/types/src/api.d.ts.map +1 -1
  22. package/types/src/binding-post.d.ts +22 -22
  23. package/types/src/binding-post.d.ts.map +1 -1
  24. package/types/src/binding-redirect.d.ts +14 -1
  25. package/types/src/binding-redirect.d.ts.map +1 -1
  26. package/types/src/binding-simplesign.d.ts.map +1 -1
  27. package/types/src/entity-idp.d.ts +3 -4
  28. package/types/src/entity-idp.d.ts.map +1 -1
  29. package/types/src/entity-sp.d.ts +44 -21
  30. package/types/src/entity-sp.d.ts.map +1 -1
  31. package/types/src/entity.d.ts.map +1 -1
  32. package/types/src/extractor.d.ts +5 -0
  33. package/types/src/extractor.d.ts.map +1 -1
  34. package/types/src/flow.d.ts.map +1 -1
  35. package/types/src/libsaml.d.ts +15 -4
  36. package/types/src/libsaml.d.ts.map +1 -1
  37. package/types/src/metadata-sp.d.ts.map +1 -1
  38. package/types/src/metadata.d.ts.map +1 -1
  39. package/types/src/schemaValidator.d.ts +1 -1
  40. package/types/src/schemaValidator.d.ts.map +1 -1
  41. package/types/src/utility.d.ts.map +1 -1
  42. package/build/index.js.map +0 -1
  43. package/build/src/api.js.map +0 -1
  44. package/build/src/binding-post.js.map +0 -1
  45. package/build/src/binding-redirect.js.map +0 -1
  46. package/build/src/binding-simplesign.js.map +0 -1
  47. package/build/src/entity-idp.js.map +0 -1
  48. package/build/src/entity-sp.js.map +0 -1
  49. package/build/src/entity.js.map +0 -1
  50. package/build/src/extractor.js.map +0 -1
  51. package/build/src/flow.js.map +0 -1
  52. package/build/src/libsaml.js.map +0 -1
  53. package/build/src/metadata-idp.js.map +0 -1
  54. package/build/src/metadata-sp.js.map +0 -1
  55. package/build/src/metadata.js.map +0 -1
  56. package/build/src/types.js.map +0 -1
  57. package/build/src/urn.js.map +0 -1
  58. package/build/src/utility.js.map +0 -1
  59. package/build/src/validator.js.map +0 -1
@@ -68,6 +68,19 @@ export const loginResponseStatusFields = [
68
68
  }
69
69
  ];
70
70
  // support two-tiers status code
71
+ export const loginArtifactResponseStatusFields = [
72
+ {
73
+ key: 'top',
74
+ localPath: ['Envelope', 'Body', 'ArtifactResponse', 'Status', 'StatusCode'],
75
+ attributes: ['Value'],
76
+ },
77
+ {
78
+ key: 'second',
79
+ localPath: ['Envelope', 'Body', 'ArtifactResponse', 'Status', 'StatusCode', 'StatusCode'],
80
+ attributes: ['Value'],
81
+ }
82
+ ];
83
+ // support two-tiers status code
71
84
  export const logoutResponseStatusFields = [
72
85
  {
73
86
  key: 'top',
@@ -178,7 +191,7 @@ export const logoutResponseFields = [
178
191
  ];
179
192
  export function extract(context, fields) {
180
193
  const { dom } = getContext();
181
- const rootDoc = dom.parseFromString(context);
194
+ const rootDoc = dom.parseFromString(context, 'application/xml');
182
195
  return fields.reduce((result, field) => {
183
196
  // get essential fields
184
197
  const key = field.key;
@@ -194,7 +207,7 @@ export function extract(context, fields) {
194
207
  // if shortcut is used, then replace the doc
195
208
  // it's a design for overriding the doc used during runtime
196
209
  if (shortcut) {
197
- targetDoc = dom.parseFromString(shortcut);
210
+ targetDoc = dom.parseFromString(shortcut, 'application/xml');
198
211
  }
199
212
  // special case: multiple path
200
213
  /*
@@ -216,6 +229,7 @@ export function extract(context, fields) {
216
229
  .join(' | ');
217
230
  return {
218
231
  ...result,
232
+ // @ts-expect-error misssing Node properties are not needed
219
233
  [key]: uniq(select(multiXPaths, targetDoc).map((n) => n.nodeValue).filter(notEmpty))
220
234
  };
221
235
  }
@@ -236,8 +250,10 @@ export function extract(context, fields) {
236
250
  // find the index in localpath
237
251
  const indexPath = buildAttributeXPath(index);
238
252
  const fullLocalXPath = `${baseXPath}${indexPath}`;
253
+ // @ts-expect-error misssing Node properties are not needed
239
254
  const parentNodes = select(baseXPath, targetDoc);
240
255
  // [uid, mail, edupersonaffiliation], ready for aggregate
256
+ // @ts-expect-error misssing Node properties are not needed
241
257
  const parentAttributes = select(fullLocalXPath, targetDoc).map((n) => n.value);
242
258
  // [attribute, attributevalue]
243
259
  const childXPath = buildAbsoluteXPath([last(localPath)].concat(attributePath));
@@ -245,8 +261,9 @@ export function extract(context, fields) {
245
261
  const fullChildXPath = `${childXPath}${childAttributeXPath}`;
246
262
  // [ 'test', 'test@example.com', [ 'users', 'examplerole1' ] ]
247
263
  const childAttributes = parentNodes.map(node => {
248
- const nodeDoc = dom.parseFromString(node.toString());
264
+ const nodeDoc = dom.parseFromString(node.toString(), 'application/xml');
249
265
  if (attributes.length === 0) {
266
+ // @ts-ignore
250
267
  const childValues = select(fullChildXPath, nodeDoc).map((n) => n.nodeValue);
251
268
  if (childValues.length === 1) {
252
269
  return childValues[0];
@@ -254,6 +271,7 @@ export function extract(context, fields) {
254
271
  return childValues;
255
272
  }
256
273
  if (attributes.length > 0) {
274
+ // @ts-ignore
257
275
  const childValues = select(fullChildXPath, nodeDoc).map((n) => n.value);
258
276
  if (childValues.length === 1) {
259
277
  return childValues[0];
@@ -279,6 +297,7 @@ export function extract(context, fields) {
279
297
  }
280
298
  */
281
299
  if (isEntire) {
300
+ // @ts-expect-error misssing Node properties are not needed
282
301
  const node = select(baseXPath, targetDoc);
283
302
  let value = null;
284
303
  if (node.length === 1) {
@@ -301,10 +320,13 @@ export function extract(context, fields) {
301
320
  }
302
321
  */
303
322
  if (attributes.length > 1) {
323
+ // @ts-ignore
304
324
  const baseNode = select(baseXPath, targetDoc).map(n => n.toString());
305
325
  const childXPath = `${buildAbsoluteXPath([last(localPath)])}${attributeXPath}`;
306
326
  const attributeValues = baseNode.map((node) => {
307
- const nodeDoc = dom.parseFromString(node);
327
+ // @ts-ignore
328
+ const nodeDoc = dom.parseFromString(node, 'application/xml');
329
+ // @ts-ignore
308
330
  const values = select(childXPath, nodeDoc).reduce((r, n) => {
309
331
  r[camelCase(n.name, { locale: 'en-us' })] = n.value;
310
332
  return r;
@@ -326,6 +348,7 @@ export function extract(context, fields) {
326
348
  */
327
349
  if (attributes.length === 1) {
328
350
  const fullPath = `${baseXPath}${attributeXPath}`;
351
+ // @ts-ignore
329
352
  const attributeValues = select(fullPath, targetDoc).map((n) => n.value);
330
353
  return {
331
354
  ...result,
@@ -342,9 +365,11 @@ export function extract(context, fields) {
342
365
  */
343
366
  if (attributes.length === 0) {
344
367
  let attributeValue = null;
368
+ // @ts-expect-error misssing Node properties are not needed
345
369
  const node = select(baseXPath, targetDoc);
346
370
  if (node.length === 1) {
347
371
  const fullPath = `string(${baseXPath}${attributeXPath})`;
372
+ // @ts-expect-error misssing Node properties are not needed
348
373
  attributeValue = select(fullPath, targetDoc);
349
374
  }
350
375
  if (node.length > 1) {
package/build/src/flow.js CHANGED
@@ -5,8 +5,8 @@ import * as uuid from 'uuid';
5
5
  import { select } from 'xpath';
6
6
  import { DOMParser } from '@xmldom/xmldom';
7
7
  import { sendArtifactResolve } from "./soap.js";
8
- import { extract, loginRequestFields, loginResponseFields, logoutRequestFields, logoutResponseFields, logoutResponseStatusFields, loginResponseStatusFields } from './extractor.js';
9
- import { BindingNamespace, ParserType, wording, StatusCode } from './urn.js';
8
+ import { extract, loginRequestFields, loginResponseFields, loginResponseStatusFields, loginArtifactResponseStatusFields, logoutRequestFields, logoutResponseFields, logoutResponseStatusFields } from './extractor.js';
9
+ import { BindingNamespace, ParserType, StatusCode, wording } from './urn.js';
10
10
  const bindDict = wording.binding;
11
11
  const urlParams = wording.urlParams;
12
12
  // get the default extractor fields based on the parserType
@@ -131,7 +131,7 @@ async function postFlow(options) {
131
131
  const direction = libsaml.getQueryParamByType(parserType);
132
132
  let encodedRequest = '';
133
133
  let samlContent = '';
134
- if (soap === false) {
134
+ if (!soap) {
135
135
  encodedRequest = body[direction];
136
136
  // @ts-ignore
137
137
  samlContent = String(base64Decode(encodedRequest));
@@ -146,12 +146,15 @@ async function postFlow(options) {
146
146
  let ID = '_' + uuid.v4();
147
147
  let url = metadata.idp.getArtifactResolutionService(bindDict.soap);
148
148
  let samlSoapRaw = libsaml.replaceTagsByValue(libsaml.defaultArtifactResolveTemplate.context, {
149
- ID: request?.messageHandle,
149
+ ID: ID,
150
150
  Destination: url,
151
151
  Issuer: metadata.sp.getEntityID(),
152
152
  IssueInstant: new Date().toISOString(),
153
153
  Art: request.Art
154
154
  });
155
+ if (!metadata.idp.isWantAuthnRequestsSigned()) {
156
+ samlContent = await sendArtifactResolve(url, samlSoapRaw);
157
+ }
155
158
  if (metadata.idp.isWantAuthnRequestsSigned()) {
156
159
  const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
157
160
  let signatureSoap = libsaml.constructSAMLSignature({
@@ -172,14 +175,8 @@ async function postFlow(options) {
172
175
  }
173
176
  }
174
177
  });
175
- let data = await sendArtifactResolve(url, signatureSoap);
176
- /* console.log(signatureSoap)
177
- console.log("签过名的")*/
178
- console.log(data);
179
- console.log("keycloak数据----------------------");
180
- samlContent = data;
178
+ samlContent = await sendArtifactResolve(url, signatureSoap);
181
179
  }
182
- // No need to embeded XML signature
183
180
  }
184
181
  const verificationOptions = {
185
182
  metadata: from.entityMeta,
@@ -189,57 +186,33 @@ async function postFlow(options) {
189
186
  let decryptRequired = from.entitySetting.isAssertionEncrypted;
190
187
  let extractorFields = [];
191
188
  // validate the xml first
192
- /* let res = await libsaml.isValidXml(samlContent).catch((error)=>{
193
- console.log(error);
194
- console.log("验证和结果-----------------------")
195
- console.log("验证和结果-----------------------")
196
- console.log("验证和结果-----------------------")
197
- console.log("验证和结果-----------------------")
198
- console.log("验证和结果-----------------------")
199
- console.log("验证和结果-----------------------")
200
- console.log("验证和结果-----------------------")
201
- });
202
- console.log(res);
203
- console.log("验证和结果-----------------------")*/
189
+ let res = await libsaml.isValidXml(samlContent, soap).catch((error) => {
190
+ return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
191
+ });
192
+ if (res !== true) {
193
+ return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
194
+ }
204
195
  if (parserType !== urlParams.samlResponse) {
205
196
  extractorFields = getDefaultExtractorFields(parserType, null);
206
197
  }
207
198
  // check status based on different scenarios
208
- /* await checkStatus(samlContent, parserType);*/
199
+ await checkStatus(samlContent, parserType, soap);
209
200
  /**检查签名顺序 */
210
- /* if (
211
- checkSignature &&
212
- from.entitySetting.messageSigningOrder === MessageSignatureOrder.ETS
213
- ) {
214
- console.log("===============我走的这里=========================")
215
- const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
216
- console.log(verified);
217
- console.log("verified")
218
- decryptRequired = isDecryptRequired
219
- if (!verified) {
220
- return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
221
- }
222
- if (!decryptRequired) {
223
- extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
224
- }
225
- }*/
226
- if (soap === true) {
201
+ if (soap) {
227
202
  const [verified, verifiedAssertionNode, isDecryptRequired] = libsaml.verifySignatureSoap(samlContent, verificationOptions);
228
203
  decryptRequired = isDecryptRequired;
229
204
  if (!verified) {
230
205
  return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
231
206
  }
232
207
  if (!decryptRequired) {
233
- console.log("-------------------走到了这里----------------------");
234
208
  extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
235
209
  }
236
210
  if (parserType === 'SAMLResponse' && decryptRequired) {
237
211
  // 1. 解密断言
238
212
  const [decryptedSAML, decryptedAssertion] = await libsaml.decryptAssertionSoap(self, samlContent);
239
- console.log(decryptedAssertion);
240
- console.log("解密数据-----------------------------");
241
213
  // 2. 检查解密后的断言是否包含签名
242
- const assertionDoc = new DOMParser().parseFromString(decryptedAssertion, 'text/xml');
214
+ const assertionDoc = new DOMParser().parseFromString(decryptedAssertion, 'application/xml');
215
+ // @ts-ignore
243
216
  const assertionSignatureNodes = select("./*[local-name()='Signature']", assertionDoc.documentElement);
244
217
  // 3. 如果存在签名则验证
245
218
  if (assertionSignatureNodes.length > 0) {
@@ -250,11 +223,7 @@ async function postFlow(options) {
250
223
  };
251
224
  // 3.2 验证断言签名
252
225
  const [assertionVerified, result] = libsaml.verifySignatureSoap(decryptedAssertion, assertionVerificationOptions);
253
- console.log(assertionVerified);
254
- console.log(result);
255
- console.log("验证机结果--------------");
256
226
  if (!assertionVerified) {
257
- console.error("解密后的断言签名验证失败");
258
227
  return Promise.reject('ERR_FAIL_TO_VERIFY_ASSERTION_SIGNATURE');
259
228
  }
260
229
  if (assertionVerified) {
@@ -269,34 +238,26 @@ async function postFlow(options) {
269
238
  }
270
239
  }
271
240
  }
272
- if (soap === false) {
273
- const [verified, verifiedAssertionNode, isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
241
+ if (!soap) {
242
+ const [verified, verifiedAssertionNode, isDecryptRequired, noSignature] = libsaml.verifySignature(samlContent, verificationOptions);
274
243
  decryptRequired = isDecryptRequired;
275
- if (!verified) {
244
+ if (isDecryptRequired && noSignature) {
245
+ const result = await libsaml.decryptAssertion(self, samlContent);
246
+ samlContent = result[0];
247
+ extractorFields = getDefaultExtractorFields(parserType, result[1]);
248
+ }
249
+ if (!verified && !noSignature && !isDecryptRequired) {
276
250
  return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
277
251
  }
278
252
  if (!decryptRequired) {
279
253
  extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
280
254
  }
281
- if (parserType === 'SAMLResponse' && decryptRequired) {
255
+ if (parserType === 'SAMLResponse' && decryptRequired && !noSignature) {
282
256
  const result = await libsaml.decryptAssertion(self, samlContent);
283
257
  samlContent = result[0];
284
258
  extractorFields = getDefaultExtractorFields(parserType, result[1]);
285
259
  }
286
260
  }
287
- // verify the signatures (the response is signed then encrypted, then decrypt first then verify)
288
- /* if (
289
- checkSignature &&
290
- from.entitySetting.messageSigningOrder === MessageSignatureOrder.STE
291
- ) {
292
- const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
293
- decryptRequired = isDecryptRequired
294
- if (verified) {
295
- extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
296
- } else {
297
- return Promise.reject('ERR_FAIL_TO_VERIFY_STE_SIGNATURE');
298
- }
299
- }*/
300
261
  const parseResult = {
301
262
  samlContent: samlContent,
302
263
  extract: extract(samlContent, extractorFields),
@@ -307,10 +268,6 @@ async function postFlow(options) {
307
268
  const targetEntityMetadata = from.entityMeta;
308
269
  const issuer = targetEntityMetadata.getEntityID();
309
270
  const extractedProperties = parseResult.extract;
310
- console.log(extractedProperties);
311
- console.log(parseResult);
312
- console.log("解析结果----------------------------------");
313
- console.log("签发这-----------");
314
271
  // unmatched issuer
315
272
  if ((parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
316
273
  && extractedProperties
@@ -370,29 +327,13 @@ async function postArtifactFlow(options) {
370
327
  let decryptRequired = from.entitySetting.isAssertionEncrypted;
371
328
  let extractorFields = [];
372
329
  // validate the xml first
373
- let res = await libsaml.isValidXml(samlContent);
330
+ let res = await libsaml.isValidXml(samlContent, true);
374
331
  if (parserType !== urlParams.samlResponse) {
375
332
  extractorFields = getDefaultExtractorFields(parserType, null);
376
333
  }
377
334
  // check status based on different scenarios
378
335
  await checkStatus(samlContent, parserType);
379
336
  /**检查签名顺序 */
380
- /* if (
381
- checkSignature &&
382
- from.entitySetting.messageSigningOrder === MessageSignatureOrder.ETS
383
- ) {
384
- console.log("===============我走的这里=========================")
385
- const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
386
- console.log(verified);
387
- console.log("verified")
388
- decryptRequired = isDecryptRequired
389
- if (!verified) {
390
- return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
391
- }
392
- if (!decryptRequired) {
393
- extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
394
- }
395
- }*/
396
337
  const [verified, verifiedAssertionNode, isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
397
338
  decryptRequired = isDecryptRequired;
398
339
  if (!verified) {
@@ -406,19 +347,6 @@ async function postArtifactFlow(options) {
406
347
  samlContent = result[0];
407
348
  extractorFields = getDefaultExtractorFields(parserType, result[1]);
408
349
  }
409
- // verify the signatures (the response is signed then encrypted, then decrypt first then verify)
410
- /* if (
411
- checkSignature &&
412
- from.entitySetting.messageSigningOrder === MessageSignatureOrder.STE
413
- ) {
414
- const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
415
- decryptRequired = isDecryptRequired
416
- if (verified) {
417
- extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
418
- } else {
419
- return Promise.reject('ERR_FAIL_TO_VERIFY_STE_SIGNATURE');
420
- }
421
- }*/
422
350
  const parseResult = {
423
351
  samlContent: samlContent,
424
352
  extract: extract(samlContent, extractorFields),
@@ -488,7 +416,7 @@ async function postSimpleSignFlow(options) {
488
416
  const xmlString = String(base64Decode(encodedRequest));
489
417
  // validate the xml
490
418
  try {
491
- await libsaml.isValidXml(xmlString);
419
+ await libsaml.isValidXml(xmlString, false);
492
420
  }
493
421
  catch (e) {
494
422
  return Promise.reject('ERR_INVALID_XML');
@@ -565,14 +493,19 @@ async function postSimpleSignFlow(options) {
565
493
  }
566
494
  return Promise.resolve(parseResult);
567
495
  }
568
- function checkStatus(content, parserType) {
496
+ function checkStatus(content, parserType, soap) {
569
497
  // only check response parser
570
498
  if (parserType !== urlParams.samlResponse && parserType !== urlParams.logoutResponse) {
571
499
  return Promise.resolve('SKIPPED');
572
500
  }
573
- const fields = parserType === urlParams.samlResponse
501
+ let fields = parserType === urlParams.samlResponse
574
502
  ? loginResponseStatusFields
575
503
  : logoutResponseStatusFields;
504
+ if (soap === true) {
505
+ fields = parserType === urlParams.samlResponse
506
+ ? loginArtifactResponseStatusFields
507
+ : logoutResponseStatusFields;
508
+ }
576
509
  const { top, second } = extract(content, fields);
577
510
  // only resolve when top-tier status code is success
578
511
  if (top === StatusCode.Success) {