samlesa 3.4.3 → 3.5.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.
@@ -1,7 +1,8 @@
1
1
  /**
2
- * @file binding-post.ts
2
+ * @file binding-artifact.ts
3
3
  * @author tngan
4
- * @desc Binding-level API, declare the functions using POST binding
4
+ * @desc Binding-level API for SAML 2.0 Artifact Binding
5
+ * @see https://docs.oasis-open.org/security/saml/v2.0/saml-bind-2.0-os.pdf
5
6
  */
6
7
  import { checkStatus } from "./flow.js";
7
8
  import { ParserType, StatusCode, wording } from './urn.js';
@@ -9,23 +10,21 @@ import * as crypto from "node:crypto";
9
10
  import libsaml from './libsaml.js';
10
11
  import libsamlSoap from './libsamlSoap.js';
11
12
  import utility, { get } from './utility.js';
12
- import { fileURLToPath } from "node:url";
13
13
  import { randomUUID } from 'node:crypto';
14
14
  import postBinding from './binding-post.js';
15
15
  import { artifactResolveFields, extract, loginRequestFields, loginResponseFields, logoutRequestFields, logoutResponseFields } from "./extractor.js";
16
16
  import { verifyTime } from "./validator.js";
17
17
  import { sendArtifactResolve } from "./soap.js";
18
- import path from "node:path";
19
- const __filename = fileURLToPath(import.meta.url);
20
- const __dirname = path.dirname(__filename);
21
- // get the default extractor fields based on the parserType
18
+ const binding = wording.binding;
19
+ /**
20
+ * Get default extractor fields based on parser type
21
+ */
22
22
  function getDefaultExtractorFields(parserType, assertion) {
23
23
  switch (parserType) {
24
24
  case ParserType.SAMLRequest:
25
25
  return loginRequestFields;
26
26
  case ParserType.SAMLResponse:
27
27
  if (!assertion) {
28
- // unexpected hit
29
28
  throw new Error('ERR_EMPTY_ASSERTION');
30
29
  }
31
30
  return loginResponseFields(assertion);
@@ -37,25 +36,46 @@ function getDefaultExtractorFields(parserType, assertion) {
37
36
  throw new Error('ERR_UNDEFINED_PARSERTYPE');
38
37
  }
39
38
  }
40
- const binding = wording.binding;
41
39
  /**
42
- * @desc Generate a base64 encoded login request
43
- * @param {string} referenceTagXPath reference uri
44
- * @param {object} entity object includes both idp and sp
45
- * @param customTagReplacement
40
+ * Generate a SAML 2.0 compliant Artifact ID
41
+ * Format: [TypeCode: 2 bytes] + [EndpointIndex: 2 bytes] + [SourceID: 20 bytes] + [MessageHandle: 20 bytes]
42
+ * @param issuerId The entity ID of the issuing party (IdP)
43
+ * @param endpointIndex The index of the destination endpoint (default is 1 for Artifact Resolution Service)
44
+ * @returns The Base64 encoded Artifact ID string
45
+ */
46
+ export function generateArtifactId(issuerId, endpointIndex = 1) {
47
+ // SourceID: 20 bytes SHA-1 of issuer ID
48
+ const issuerHash = crypto.createHash('sha1').update(issuerId).digest();
49
+ const sourceId = issuerHash.subarray(0, 20);
50
+ // TypeCode: 0x0004 (SAML 2.0 Artifact Type)
51
+ const typeCode = Buffer.from([0x00, 0x04]);
52
+ // EndpointIndex: 2 bytes Big Endian
53
+ const indexBuffer = Buffer.alloc(2);
54
+ indexBuffer.writeUInt16BE(endpointIndex, 0);
55
+ // MessageHandle: 20 random bytes
56
+ const messageHandle = crypto.randomBytes(20);
57
+ // Concatenate: 2 + 2 + 20 + 20 = 44 bytes
58
+ const artifactBytes = Buffer.concat([typeCode, indexBuffer, sourceId, messageHandle]);
59
+ // Base64 encode
60
+ return artifactBytes.toString('base64');
61
+ }
62
+ /**
63
+ * @desc Generate a SOAP-encoded login request for Artifact binding
64
+ * @param {string} referenceTagXPath reference uri
65
+ * @param {object} entity object includes both idp and sp
66
+ * @param {function} customTagReplacement used when developers have their own login request template
67
+ * @returns {BindingContext}
46
68
  */
47
69
  function soapLoginRequest(referenceTagXPath, entity, customTagReplacement) {
48
70
  const metadata = {
49
71
  idp: entity.idp.entityMeta,
50
72
  sp: entity.sp.entityMeta,
51
- inResponse: entity?.inResponse,
52
- relayState: entity?.relayState
73
+ inResponse: entity.inResponse,
74
+ relayState: entity.relayState
53
75
  };
54
76
  const spSetting = entity.sp.entitySetting;
55
77
  let id = '';
56
- let id2 = spSetting.generateID();
57
- let soapTemplate = '';
58
- let Response = '';
78
+ let soapId = spSetting.generateID();
59
79
  if (metadata && metadata.idp && metadata.sp) {
60
80
  const base = metadata.idp.getSingleSignOnService(binding.post);
61
81
  let rawSamlRequest;
@@ -80,12 +100,13 @@ function soapLoginRequest(referenceTagXPath, entity, customTagReplacement) {
80
100
  });
81
101
  }
82
102
  const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
103
+ let signedAuthnRequest;
83
104
  if (metadata.idp.isWantAuthnRequestsSigned()) {
84
- Response = libsaml.constructSAMLSignature({
105
+ signedAuthnRequest = libsaml.constructSAMLSignature({
85
106
  referenceTagXPath,
86
- privateKey,
107
+ privateKey: privateKey,
87
108
  privateKeyPass,
88
- signatureAlgorithm,
109
+ signatureAlgorithm: signatureAlgorithm,
89
110
  transformationAlgorithms,
90
111
  rawSamlMessage: rawSamlRequest,
91
112
  isBase64Output: false,
@@ -93,35 +114,29 @@ function soapLoginRequest(referenceTagXPath, entity, customTagReplacement) {
93
114
  signatureConfig: spSetting.signatureConfig || {
94
115
  prefix: 'ds',
95
116
  location: {
96
- reference: "/*[local-name(.)='AuthnRequest']/!*[local-name(.)='Issuer']",
117
+ reference: "/*[local-name(.)='AuthnRequest']/*[local-name(.)='Issuer']",
97
118
  action: 'after'
98
119
  },
99
120
  }
100
121
  });
101
- soapTemplate = libsaml.replaceTagsByValue(libsaml.defaultArtAuthnRequestTemplate.context, {
102
- ID: id2,
103
- IssueInstant: new Date().toISOString(),
104
- InResponseTo: metadata.inResponse ?? "",
105
- Issuer: metadata.sp.getEntityID(),
106
- AuthnRequest: Response
107
- });
108
122
  }
109
123
  else {
110
- soapTemplate = libsaml.replaceTagsByValue(libsaml.defaultArtAuthnRequestTemplate.context, {
111
- ID: id2,
112
- IssueInstant: new Date().toISOString(),
113
- InResponseTo: metadata.inResponse ?? "",
114
- Issuer: metadata.sp.getEntityID(),
115
- AuthnRequest: rawSamlRequest
116
- });
124
+ signedAuthnRequest = rawSamlRequest;
117
125
  }
118
- /** 构建响应签名*/
119
- // No need to embeded XML signature
120
- return libsaml.constructSAMLSignature({
126
+ // Construct SOAP envelope with ArtifactResponse containing AuthnRequest
127
+ const soapTemplate = libsaml.replaceTagsByValue(libsaml.defaultArtAuthnRequestTemplate.context, {
128
+ ID: soapId,
129
+ IssueInstant: new Date().toISOString(),
130
+ InResponseTo: metadata.inResponse ?? "",
131
+ Issuer: metadata.sp.getEntityID(),
132
+ AuthnRequest: signedAuthnRequest
133
+ });
134
+ // Sign the SOAP envelope
135
+ const signedSoap = libsaml.constructSAMLSignature({
121
136
  referenceTagXPath: "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']",
122
- privateKey,
137
+ privateKey: privateKey,
123
138
  privateKeyPass,
124
- signatureAlgorithm,
139
+ signatureAlgorithm: signatureAlgorithm,
125
140
  transformationAlgorithms,
126
141
  rawSamlMessage: soapTemplate,
127
142
  isBase64Output: false,
@@ -135,37 +150,18 @@ function soapLoginRequest(referenceTagXPath, entity, customTagReplacement) {
135
150
  }
136
151
  },
137
152
  });
153
+ return {
154
+ id: soapId,
155
+ context: signedSoap,
156
+ };
138
157
  }
139
158
  throw new Error('ERR_GENERATE_POST_LOGIN_REQUEST_MISSING_METADATA');
140
159
  }
141
160
  /**
142
- * Generates a SAML 2.0 compliant Artifact ID.
143
- * Format: [4-byte source ID][2-byte endpoint index][20-byte sequence value]
144
- * @param issuerId The entity ID of the issuing party (IdP).
145
- * @param endpointIndex The index of the destination endpoint (default is '0004' for Artifact Resolution Service).
146
- * @returns The Base64 encoded Artifact ID string.
147
- */
148
- /**
149
- * 生成符合 SAML 2.0 规范的 Artifact
150
- * 结构: [TypeCode: 2 bytes] + [EndpointIndex: 2 bytes] + [SourceID: 20 bytes] + [MessageHandle: 20 bytes]
161
+ * @desc Generate a SOAP-encoded login response for Artifact binding
162
+ * @param {Base64LoginResponseParams} params parameters for generating login response
163
+ * @returns {BindingContext}
151
164
  */
152
- function generateArtifactId(issuerId, endpointIndex = 1) {
153
- // 1. SourceID: 20 bytes SHA-1
154
- const issuerHash = crypto.createHash('sha1').update(issuerId).digest();
155
- const sourceId = issuerHash.subarray(0, 20); // 必须 20 字节
156
- // 2. TypeCode: 0x0004
157
- const typeCode = Buffer.from([0x00, 0x04]);
158
- // 3. EndpointIndex: 2 bytes Big Endian
159
- const indexBuffer = Buffer.alloc(2);
160
- indexBuffer.writeUInt16BE(endpointIndex, 0);
161
- // 4. MessageHandle: 20 random bytes
162
- const messageHandle = crypto.randomBytes(20);
163
- // 5. Concat: 2 + 2 + 20 + 20 = 44 bytes
164
- const artifactBytes = Buffer.concat([typeCode, indexBuffer, sourceId, messageHandle]);
165
- // 6. Base64
166
- return artifactBytes.toString('base64');
167
- }
168
- // Initial response that sends only the artifact ID
169
165
  async function soapLoginResponse(params) {
170
166
  const { entity } = params;
171
167
  const metadata = {
@@ -175,21 +171,12 @@ async function soapLoginResponse(params) {
175
171
  if (!metadata.idp || !metadata.sp) {
176
172
  throw new Error('ERR_GENERATE_ARTIFACT_MISSING_METADATA');
177
173
  }
178
- // 1. Generate the base SAML Response (Base64 encoded)
174
+ // Generate the base SAML Response using POST binding logic
179
175
  const samlResponseResult = await postBinding.base64LoginResponse(params);
180
176
  const samlResponseXml = utility.base64Decode(samlResponseResult.context);
181
- console.log(samlResponseXml);
182
- console.log("我日你妈==");
183
- // 2. Generate the SAML 2.0 Artifact ID
177
+ // Generate the SAML 2.0 Artifact ID
184
178
  const artifactId = generateArtifactId(metadata.idp.getEntityID());
185
- // 3. Cache the SAML Response XML using the artifactId as the key
186
- // TODO: Implement your Redis caching logic here
187
- // Example:
188
- // const redisExpirySeconds = 300; // e.g., expire after 5 minutes
189
- // await redis.setex(`artifact_cache:${artifactId}`, redisExpirySeconds, samlResponseXml);
190
- // --- INSERT YOUR REDIS LOGIC HERE ---
191
- // Example: await cacheInRedis(artifactId, samlResponseXml);
192
- // 4. Prepare config for SOAP signing (reusing IDP settings)
179
+ // Prepare config for SOAP signing
193
180
  const idpSetting = entity.idp.entitySetting;
194
181
  const spSetting = entity.sp.entitySetting;
195
182
  const config = {
@@ -199,31 +186,26 @@ async function soapLoginResponse(params) {
199
186
  signingCert: metadata.idp.getX509Certificate('signing'),
200
187
  isBase64Output: false,
201
188
  };
202
- // 5. Construct the SOAP Envelope containing the Artifact ID
203
- // This is CORRECT - in the initial response we only send the artifact ID
204
- // The actual SAML response will be retrieved by the SP using the artifact resolution service
189
+ // Construct the SOAP Envelope containing the ArtifactResponse with SAML Response
205
190
  const soapBodyContent = `<samlp:ArtifactResponse
206
- xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
207
- xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
208
- ID="${samlResponseResult.id}_artifact_resp"
209
- InResponseTo="${params.requestInfo?.extract?.request?.id || ''}"
210
- Version="2.0"
211
- IssueInstant="${new Date().toISOString()}">
212
- <saml2:Issuer>${metadata.idp.getEntityID()}</saml2:Issuer>
191
+ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
192
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
193
+ ID="${samlResponseResult.id}_artifact_resp"
194
+ InResponseTo="${params.requestInfo?.extract?.request?.id || ''}"
195
+ Version="2.0"
196
+ IssueInstant="${new Date().toISOString()}">
197
+ <saml2:Issuer>${metadata.idp.getEntityID()}</saml2:Issuer>
213
198
  <samlp:Status>
214
199
  <samlp:StatusCode Value="${StatusCode.Success}"/>
215
200
  </samlp:Status>
216
- ${samlResponseXml}
201
+ ${samlResponseXml}
217
202
  </samlp:ArtifactResponse>`;
218
203
  const soapEnvelope = `
219
204
  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
220
205
  <soap:Header/>
221
206
  <soap:Body>${soapBodyContent}</soap:Body>
222
207
  </soap:Envelope>`;
223
- // 6. Sign the SOAP Envelope
224
- // Reference the Body element for signature
225
- // @ts-ignore
226
- // @ts-ignore
208
+ // Sign the SOAP Envelope
227
209
  const signedSoapEnvelope = libsaml.constructSAMLSignature({
228
210
  ...config,
229
211
  rawSamlMessage: soapEnvelope,
@@ -233,33 +215,40 @@ async function soapLoginResponse(params) {
233
215
  signatureConfig: {
234
216
  prefix: 'ds',
235
217
  location: {
236
- // Place signature inside the ArtifactResponse element, before the Status element
237
218
  reference: "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']/*[local-name(.)='Status']",
238
219
  action: 'before'
239
220
  },
240
221
  },
241
222
  });
242
- // 7. Return the Artifact ID and the Base64 encoded signed SOAP message
223
+ // Return the Artifact ID and the Base64 encoded signed SOAP message
243
224
  return {
244
- id: artifactId, // This is the key you'll need for resolving the artifact later
245
- context: utility.base64Encode(signedSoapEnvelope), // The SOAP message to send back
225
+ id: artifactId,
226
+ context: utility.base64Encode(signedSoapEnvelope),
246
227
  };
247
228
  }
229
+ /**
230
+ * @desc Parse and validate Artifact Resolve request
231
+ * @param {object} params
232
+ * @param {IdentityProvider} params.idp Identity Provider instance
233
+ * @param {ServiceProvider} params.sp Service Provider instance
234
+ * @param {string} params.xml SOAP request XML string
235
+ * @returns {Promise}
236
+ */
248
237
  async function parseLoginRequestResolve(params) {
249
- let { idp, sp, xml, } = params;
238
+ const { idp, sp, xml } = params;
250
239
  const verificationOptions = {
251
240
  metadata: idp.entityMeta,
252
241
  signatureAlgorithm: idp.entitySetting.requestSignatureAlgorithm,
253
242
  };
254
- let res = await libsaml.isValidXml(xml, true).catch((error) => {
243
+ // Validate XML
244
+ let res = await libsaml.isValidXml(xml, true).catch(() => {
255
245
  return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
256
246
  });
257
247
  if (res !== true) {
258
248
  return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
259
249
  }
260
- /** 首先先验证签名*/
261
- // @ts-ignore
262
- let [verify, xmlString, isEncrypted, noSignature] = await libsamlSoap.verifyAndDecryptSoapMessage(xml, verificationOptions);
250
+ // Verify and decrypt SOAP message
251
+ let [verify, xmlString] = await libsamlSoap.verifyAndDecryptSoapMessage(xml, verificationOptions);
263
252
  if (!verify) {
264
253
  return Promise.reject('ERR_FAIL_TO_VERIFY_SIGNATURE');
265
254
  }
@@ -267,25 +256,32 @@ async function parseLoginRequestResolve(params) {
267
256
  samlContent: xmlString,
268
257
  extract: extract(xmlString, artifactResolveFields),
269
258
  };
270
- /**
271
- * Validation part: validate the context of response after signature is verified and decrypted (optional)
272
- */
259
+ // Validation
273
260
  const targetEntityMetadata = sp.entityMeta;
274
261
  const issuer = targetEntityMetadata.getEntityID();
275
262
  const extractedProperties = parseResult.extract;
276
- // unmatched issuer
263
+ // Check issuer
277
264
  if (extractedProperties.issuer !== issuer) {
278
265
  return Promise.reject('ERR_UNMATCH_ISSUER');
279
266
  }
280
- // invalid session time
281
- // only run the verifyTime when `SessionNotOnOrAfter` exists
282
- if (!verifyTime(undefined, new Date(new Date(extractedProperties.request.issueInstant).getTime() + 5 * 60 * 1000).toISOString(), sp.entitySetting.clockDrifts)) {
267
+ // Check time validity (5 minutes from issue instant)
268
+ const issueInstant = new Date(extractedProperties.request.issueInstant);
269
+ const expiryTime = new Date(issueInstant.getTime() + 5 * 60 * 1000);
270
+ if (!verifyTime(undefined, expiryTime.toISOString(), sp.entitySetting.clockDrifts)) {
283
271
  return Promise.reject('ERR_EXPIRED_SESSION');
284
272
  }
285
273
  return Promise.resolve(parseResult);
286
274
  }
275
+ /**
276
+ * @desc Parse and validate Artifact Resolve response
277
+ * @param {object} params
278
+ * @param {IdentityProvider} params.idp Identity Provider instance
279
+ * @param {ServiceProvider} params.sp Service Provider instance
280
+ * @param {string} params.art Artifact string
281
+ * @returns {Promise}
282
+ */
287
283
  async function parseLoginResponseResolve(params) {
288
- let { idp, sp, art } = params;
284
+ const { idp, sp, art } = params;
289
285
  const metadata = {
290
286
  idp: idp.entityMeta,
291
287
  sp: sp.entityMeta,
@@ -294,14 +290,11 @@ async function parseLoginResponseResolve(params) {
294
290
  metadata: idp.entityMeta,
295
291
  signatureAlgorithm: idp.entitySetting.requestSignatureAlgorithm,
296
292
  };
297
- let parserType = 'SAMLResponse';
298
- /** 断言是否加密应根据响应里面的字段判断*/
299
- let decryptRequired = idp.entitySetting.isAssertionEncrypted;
300
- let extractorFields = [];
301
- let samlContent = '';
293
+ const parserType = ParserType.SAMLResponse;
302
294
  const spSetting = sp.entitySetting;
303
- let ID = '_' + randomUUID();
304
- let url = metadata.idp.getArtifactResolutionService('soap');
295
+ const ID = '_' + randomUUID();
296
+ const url = metadata.idp.getArtifactResolutionService('soap');
297
+ // Construct ArtifactResolve request
305
298
  let samlSoapRaw = libsaml.replaceTagsByValue(libsaml.defaultArtifactResolveTemplate.context, {
306
299
  ID: ID,
307
300
  Destination: url,
@@ -309,31 +302,36 @@ async function parseLoginResponseResolve(params) {
309
302
  IssueInstant: new Date().toISOString(),
310
303
  Art: art
311
304
  });
305
+ let samlContent;
306
+ // Send signed or unsigned ArtifactResolve based on IdP configuration
312
307
  if (!metadata.idp.isWantAuthnRequestsSigned()) {
313
308
  samlContent = await sendArtifactResolve(url, samlSoapRaw);
314
- // check status based on different scenarios
315
- // validate the xml
309
+ // Validate XML
316
310
  try {
317
311
  await libsaml.isValidXml(samlContent, true);
318
312
  }
319
- catch (e) {
313
+ catch {
320
314
  return Promise.reject('ERR_INVALID_XML');
321
315
  }
322
316
  await checkStatus(samlContent, parserType, true);
317
+ // Verify and decrypt SOAP response
318
+ const [verified, verifiedAssertionNode, isDecryptRequired] = await libsamlSoap.verifyAndDecryptSoapMessage(samlContent, verificationOptions);
319
+ if (!verified) {
320
+ return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
321
+ }
322
+ samlContent = verifiedAssertionNode;
323
323
  }
324
- if (metadata.idp.isWantAuthnRequestsSigned()) {
324
+ else {
325
325
  const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
326
- //@ts-ignore
327
- let signatureSoap = libsaml.constructSAMLSignature({
326
+ // Sign the ArtifactResolve request
327
+ const signatureSoap = libsaml.constructSAMLSignature({
328
328
  referenceTagXPath: "//*[local-name(.)='ArtifactResolve']",
329
329
  isMessageSigned: false,
330
330
  isBase64Output: false,
331
331
  transformationAlgorithms: transformationAlgorithms,
332
- //@ts-ignore
333
- privateKey,
332
+ privateKey: privateKey,
334
333
  privateKeyPass,
335
- //@ts-ignore
336
- signatureAlgorithm,
334
+ signatureAlgorithm: signatureAlgorithm,
337
335
  rawSamlMessage: samlSoapRaw,
338
336
  signingCert: metadata.sp.getX509Certificate('signing'),
339
337
  signatureConfig: {
@@ -345,26 +343,23 @@ async function parseLoginResponseResolve(params) {
345
343
  }
346
344
  });
347
345
  samlContent = await sendArtifactResolve(url, signatureSoap);
348
- // check status based on different scenarios
349
- // validate the xml
346
+ // Validate XML
350
347
  try {
351
348
  await libsaml.isValidXml(samlContent, true);
352
349
  }
353
- catch (e) {
350
+ catch {
354
351
  return Promise.reject('ERR_INVALID_XML');
355
352
  }
356
353
  await checkStatus(samlContent, parserType, true);
357
- const [verified1, verifiedAssertionNode1, isDecryptRequired1, noSignature1] = await libsamlSoap.verifyAndDecryptSoapMessage(samlContent, verificationOptions);
358
- /* decryptRequired = isDecryptRequired*/
354
+ // Verify and decrypt SOAP response
355
+ const [verified1, verifiedAssertionNode1, isDecryptRequired1] = await libsamlSoap.verifyAndDecryptSoapMessage(samlContent, verificationOptions);
359
356
  if (!verified1) {
360
357
  return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
361
358
  }
362
359
  samlContent = verifiedAssertionNode1;
363
- // 改进的postFlow函数中关于签名验证的部分
364
- const verificationResult = await libsaml.verifySignature(samlContent, verificationOptions, self);
365
- // 检查验证结果
360
+ // Verify SAML signature and decrypt if needed
361
+ const verificationResult = await libsaml.verifySignature(samlContent, verificationOptions, sp);
366
362
  if (!verificationResult.status) {
367
- // 如果验证失败,根据具体情况返回错误
368
363
  if (verificationResult.isMessageSigned && !verificationResult.MessageSignatureStatus) {
369
364
  return Promise.reject('ERR_FAIL_TO_VERIFY_MESSAGE_SIGNATURE');
370
365
  }
@@ -374,109 +369,51 @@ async function parseLoginResponseResolve(params) {
374
369
  if (verificationResult.encrypted && !verificationResult.decrypted) {
375
370
  return Promise.reject('ERR_FAIL_TO_DECRYPT_ASSERTION');
376
371
  }
377
- // 通用验证失败
378
372
  return Promise.reject('ERR_FAIL_TO_VERIFY_SIGNATURE_OR_DECRYPTION');
379
373
  }
380
- // 更新samlContent为验证后的版本(可能已解密)
381
374
  samlContent = verificationResult.samlContent;
382
- // 根据验证结果设置extractorFields
383
- if (verificationResult.assertionContent) {
384
- extractorFields = getDefaultExtractorFields(parserType, verificationResult.assertionContent);
385
- }
386
- else {
387
- // 如果没有断言内容(例如注销请求/响应),使用适当的处理方式
388
- extractorFields = getDefaultExtractorFields(parserType, null);
389
- }
390
- const parseResult = {
391
- samlContent: samlContent,
392
- extract: extract(samlContent, extractorFields),
393
- };
394
- /**
395
- * Validation part: validate the context of response after signature is verified and decrypted (optional)
396
- */
397
- const targetEntityMetadata = idp.entityMeta;
398
- const issuer = targetEntityMetadata.getEntityID();
399
- const extractedProperties = parseResult.extract;
400
- // unmatched issuer
401
- if ((parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
402
- && extractedProperties
403
- && extractedProperties.issuer !== issuer) {
404
- return Promise.reject('ERR_UNMATCH_ISSUER');
405
- }
406
- // invalid session time
407
- // only run the verifyTime when `SessionNotOnOrAfter` exists
408
- if (parserType === 'SAMLResponse'
409
- && extractedProperties.sessionIndex.sessionNotOnOrAfter
410
- && !verifyTime(undefined, extractedProperties.sessionIndex.sessionNotOnOrAfter, sp.entitySetting.clockDrifts)) {
411
- return Promise.reject('ERR_EXPIRED_SESSION');
412
- }
413
- // invalid time
414
- // 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
415
- if (parserType === 'SAMLResponse'
416
- && extractedProperties.conditions
417
- && !verifyTime(extractedProperties.conditions.notBefore, extractedProperties.conditions.notOnOrAfter, sp.entitySetting.clockDrifts)) {
418
- return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
419
- }
420
- return Promise.resolve(parseResult);
375
+ }
376
+ // Extract fields
377
+ let extractorFields;
378
+ if (parserType === 'SAMLResponse') {
379
+ extractorFields = getDefaultExtractorFields(parserType, samlContent);
380
+ }
381
+ else {
382
+ extractorFields = getDefaultExtractorFields(parserType, null);
421
383
  }
422
384
  const parseResult = {
423
385
  samlContent: samlContent,
424
386
  extract: extract(samlContent, extractorFields),
425
387
  };
426
- /**
427
- * Validation part: validate the context of response after signature is verified and decrypted (optional)
428
- */
388
+ // Validation
429
389
  const targetEntityMetadata = idp.entityMeta;
430
390
  const issuer = targetEntityMetadata.getEntityID();
431
391
  const extractedProperties = parseResult.extract;
432
- // unmatched issuer
433
- if ((parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
392
+ // Check issuer
393
+ if (parserType === ParserType.SAMLResponse
434
394
  && extractedProperties
435
395
  && extractedProperties.issuer !== issuer) {
436
396
  return Promise.reject('ERR_UNMATCH_ISSUER');
437
397
  }
438
- // invalid session time
439
- // only run the verifyTime when `SessionNotOnOrAfter` exists
398
+ // Check session time
440
399
  if (parserType === 'SAMLResponse'
441
400
  && extractedProperties.sessionIndex.sessionNotOnOrAfter
442
401
  && !verifyTime(undefined, extractedProperties.sessionIndex.sessionNotOnOrAfter, sp.entitySetting.clockDrifts)) {
443
402
  return Promise.reject('ERR_EXPIRED_SESSION');
444
403
  }
445
- // invalid time
446
- // 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
404
+ // Check conditions time
447
405
  if (parserType === 'SAMLResponse'
448
406
  && extractedProperties.conditions
449
407
  && !verifyTime(extractedProperties.conditions.notBefore, extractedProperties.conditions.notOnOrAfter, sp.entitySetting.clockDrifts)) {
450
408
  return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
451
409
  }
452
- //valid destination
453
- //There is no validation of the response here. The upper-layer application
454
- // should verify the result by itself to see if the destination is equal to the SP acs and
455
- // whether the response.id is used to prevent replay attacks.
456
- /*
457
- let destination = extractedProperties?.response?.destination
458
- let isExit = self.entitySetting?.assertionConsumerService?.filter((item) => {
459
- return item?.Location === destination
460
- })
461
- if (isExit?.length === 0) {
462
- return Promise.reject('ERR_Destination_URL');
463
- }
464
- if (parserType === 'SAMLResponse') {
465
- let destination = extractedProperties?.response?.destination
466
- let isExit = self.entitySetting?.assertionConsumerService?.filter((item: { Location: any; }) => {
467
- return item?.Location === destination
468
- })
469
- if (isExit?.length === 0) {
470
- return Promise.reject('ERR_Destination_URL');
471
- }
472
- }
473
- */
474
410
  return Promise.resolve(parseResult);
475
411
  }
476
- const artifactSignBinding = {
477
- parseLoginRequestResolve,
412
+ const artifactBinding = {
478
413
  soapLoginRequest,
479
- parseLoginResponseResolve,
480
414
  soapLoginResponse,
415
+ parseLoginRequestResolve,
416
+ parseLoginResponseResolve,
417
+ generateArtifactId,
481
418
  };
482
- export default artifactSignBinding;
419
+ export default artifactBinding;
@@ -68,7 +68,7 @@ export class IdentityProvider extends Entity {
68
68
  sp,
69
69
  }, user, relayState, customTagReplacement, AttributeStatement);
70
70
  case namespace.binding.artifact:
71
- return artifactBinding.soapLoginResponse({
71
+ context = await artifactBinding.soapLoginResponse({
72
72
  requestInfo,
73
73
  entity: {
74
74
  idp: this,
@@ -80,6 +80,7 @@ export class IdentityProvider extends Entity {
80
80
  AttributeStatement,
81
81
  idpInit,
82
82
  });
83
+ break;
83
84
  default:
84
85
  throw new Error('ERR_CREATE_RESPONSE_UNDEFINED_BINDING');
85
86
  }