samlesa 2.17.0 → 2.17.1

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 (65) hide show
  1. package/build/index.js +2 -1
  2. package/build/src/binding-artifact.js +330 -146
  3. package/build/src/entity-sp.js +21 -94
  4. package/build/src/extractor.js +32 -0
  5. package/build/src/flow.js +23 -112
  6. package/build/src/libsaml.js +325 -127
  7. package/build/src/libsamlSoap.js +115 -0
  8. package/build/src/schemaValidator.js +1 -5
  9. package/build/src/soap.js +123 -3
  10. package/package.json +77 -75
  11. package/types/api.d.ts +15 -0
  12. package/types/api.d.ts.map +1 -0
  13. package/types/binding-post.d.ts +48 -0
  14. package/types/binding-post.d.ts.map +1 -0
  15. package/types/binding-redirect.d.ts +54 -0
  16. package/types/binding-redirect.d.ts.map +1 -0
  17. package/types/binding-simplesign.d.ts +41 -0
  18. package/types/binding-simplesign.d.ts.map +1 -0
  19. package/types/entity-idp.d.ts +38 -0
  20. package/types/entity-idp.d.ts.map +1 -0
  21. package/types/entity-sp.d.ts +38 -0
  22. package/types/entity-sp.d.ts.map +1 -0
  23. package/types/entity.d.ts +100 -0
  24. package/types/entity.d.ts.map +1 -0
  25. package/types/extractor.d.ts +26 -0
  26. package/types/extractor.d.ts.map +1 -0
  27. package/types/flow.d.ts +7 -0
  28. package/types/flow.d.ts.map +1 -0
  29. package/types/index.d.ts +2 -1
  30. package/types/index.d.ts.map +1 -1
  31. package/types/libsaml.d.ts +208 -0
  32. package/types/libsaml.d.ts.map +1 -0
  33. package/types/metadata-idp.d.ts +25 -0
  34. package/types/metadata-idp.d.ts.map +1 -0
  35. package/types/metadata-sp.d.ts +37 -0
  36. package/types/metadata-sp.d.ts.map +1 -0
  37. package/types/metadata.d.ts +58 -0
  38. package/types/metadata.d.ts.map +1 -0
  39. package/types/src/binding-artifact.d.ts +24 -29
  40. package/types/src/binding-artifact.d.ts.map +1 -1
  41. package/types/src/binding-post.d.ts.map +1 -1
  42. package/types/src/entity-sp.d.ts +13 -24
  43. package/types/src/entity-sp.d.ts.map +1 -1
  44. package/types/src/extractor.d.ts +22 -0
  45. package/types/src/extractor.d.ts.map +1 -1
  46. package/types/src/flow.d.ts +1 -0
  47. package/types/src/flow.d.ts.map +1 -1
  48. package/types/src/libsaml.d.ts +4 -3
  49. package/types/src/libsaml.d.ts.map +1 -1
  50. package/types/src/libsamlSoap.d.ts +7 -0
  51. package/types/src/libsamlSoap.d.ts.map +1 -0
  52. package/types/src/schemaValidator.d.ts.map +1 -1
  53. package/types/src/soap.d.ts +33 -0
  54. package/types/src/soap.d.ts.map +1 -1
  55. package/types/src/validator.d.ts.map +1 -1
  56. package/types/types.d.ts +128 -0
  57. package/types/types.d.ts.map +1 -0
  58. package/types/urn.d.ts +195 -0
  59. package/types/urn.d.ts.map +1 -0
  60. package/types/utility.d.ts +133 -0
  61. package/types/utility.d.ts.map +1 -0
  62. package/types/validator.d.ts +4 -0
  63. package/types/validator.d.ts.map +1 -0
  64. package/build/src/schema/XMLSchema.dtd +0 -402
  65. package/build/src/schema/datatypes.dtd +0 -203
@@ -4,7 +4,7 @@
4
4
  * @desc Declares the actions taken by service provider
5
5
  */
6
6
  import Entity from './entity.js';
7
- import * as crypto from "node:crypto";
7
+ import Artifact from './binding-artifact.js';
8
8
  import { namespace } from './urn.js';
9
9
  import redirectBinding from './binding-redirect.js';
10
10
  import postBinding from './binding-post.js';
@@ -61,12 +61,6 @@ export class ServiceProvider extends Entity {
61
61
  // Object context = {id, context, signature, sigAlg}
62
62
  context = simpleSignBinding.base64LoginRequest({ idp, sp: this }, customTagReplacement);
63
63
  break;
64
- case nsBinding.artifact:
65
- context = artifactSignBinding.base64LoginRequest("/*[local-name(.)='AuthnRequest']", {
66
- idp,
67
- sp: this
68
- }, customTagReplacement);
69
- break;
70
64
  default:
71
65
  // Will support artifact in the next release
72
66
  throw new Error('ERR_SP_LOGIN_REQUEST_UNDEFINED_BINDING');
@@ -78,39 +72,20 @@ export class ServiceProvider extends Entity {
78
72
  type: 'SAMLRequest',
79
73
  };
80
74
  }
81
- /**
82
- * @desc Generates the Art login request for developers to design their own method
83
- * @param {IdentityProvider} idp object of identity provider
84
- * @param {string} binding protocol binding
85
- * @param {function} customTagReplacement used when developers have their own login response template
86
- */
87
- createLoginRequestArt(idp, binding = 'redirect', customTagReplacement) {
75
+ async createLoginSoapRequest(idp, binding = 'artifact', config) {
88
76
  const nsBinding = namespace.binding;
89
77
  const protocol = nsBinding[binding];
90
78
  if (this.entityMeta.isAuthnRequestSigned() !== idp.entityMeta.isWantAuthnRequestsSigned()) {
91
79
  throw new Error('ERR_METADATA_CONFLICT_REQUEST_SIGNED_FLAG');
92
80
  }
93
81
  let context = null;
94
- switch (protocol) {
95
- case nsBinding.redirect:
96
- return redirectBinding.loginRequestRedirectURLArt({ idp, sp: this }, customTagReplacement);
97
- case nsBinding.post:
98
- context = postBinding.base64LoginRequest("/*[local-name(.)='AuthnRequest']", {
99
- idp,
100
- sp: this,
101
- soap: true
102
- }, customTagReplacement);
103
- break;
104
- default:
105
- // Will support artifact in the next release
106
- throw new Error('ERR_SP_LOGIN_REQUEST_UNDEFINED_BINDING');
107
- }
108
- return {
109
- ...context,
110
- relayState: this.entitySetting.relayState,
111
- entityEndpoint: idp.entityMeta.getSingleSignOnService(binding),
112
- type: 'SAMLRequest',
113
- };
82
+ context = await artifactSignBinding.soapLoginRequest("/*[local-name(.)='AuthnRequest']", {
83
+ idp,
84
+ sp: this,
85
+ inResponse: config?.inResponseTo,
86
+ relayState: config?.relayState,
87
+ }, config?.customTagReplacement);
88
+ return context;
114
89
  }
115
90
  /**
116
91
  * @desc Validation of the parsed the URL parameters
@@ -136,68 +111,20 @@ export class ServiceProvider extends Entity {
136
111
  * @param {string} binding protocol binding
137
112
  * @param {request} req request
138
113
  */
139
- parseLoginResponseArt(idp, binding, request) {
114
+ parseLoginRequestResolve(idp, xml) {
140
115
  const self = this;
141
- return flow({
142
- soap: true,
143
- from: idp,
144
- self: self,
145
- checkSignature: true, // saml response must have signature
146
- parserType: 'SAMLResponse',
147
- type: 'login',
148
- binding: binding,
149
- request: request
116
+ return Artifact.parseLoginRequestResolve({
117
+ idp: idp,
118
+ sp: self,
119
+ xml: xml
150
120
  });
151
121
  }
152
- /**
153
- * @desc generate Art id
154
- *
155
- * @param entityIDString
156
- */
157
- createArt(entityIDString, endpointIndex = 0) {
158
- let sourceEntityId = entityIDString ? entityIDString : this.entityMeta.getEntityID();
159
- // 1. 固定类型代码 (0x0004 - 2字节)
160
- const typeCode = Buffer.from([0x00, 0x04]);
161
- // 2. 端点索引 (2字节,大端序)
162
- if (endpointIndex < 0 || endpointIndex > 65535) {
163
- throw new Error('Endpoint index must be between 0 and 65535');
164
- }
165
- const endpointBuf = Buffer.alloc(2);
166
- endpointBuf.writeUInt16BE(endpointIndex);
167
- // 3. Source ID - 实体ID的SHA-1哈希 (20字节)
168
- const sourceId = crypto.createHash('sha1')
169
- .update(sourceEntityId)
170
- .digest();
171
- // 4. Message Handler - 20字节随机值
172
- const messageHandler = crypto.randomBytes(20);
173
- // 组合所有组件 (2+2+20+20 = 44字节)
174
- const artifact = Buffer.concat([typeCode, endpointBuf, sourceId, messageHandler]);
175
- // 返回Base64编码的Artifact
176
- return artifact.toString('base64');
177
- }
178
- /**
179
- * @desc generate Art id
180
- * @param artifact
181
- */
182
- parseArt(artifact) {
183
- // 解码 Base64
184
- const decoded = Buffer.from(artifact, 'base64');
185
- // 确保长度正确(SAML 工件固定为 44 字节)
186
- if (decoded.length !== 44) {
187
- throw new Error(`Invalid artifact length: ${decoded.length}, expected 44 bytes`);
188
- }
189
- // 读取前 4 字节(TypeCode + EndpointIndex)
190
- const typeCode = decoded.readUInt16BE(0);
191
- const endpointIndex = decoded.readUInt16BE(2);
192
- // 使用 Buffer.from() 替代 slice()
193
- const sourceId = Buffer.from(decoded.buffer, // 底层 ArrayBuffer
194
- decoded.byteOffset + 4, // 起始偏移量
195
- 20 // 长度
196
- ).toString('hex');
197
- const messageHandle = Buffer.from(decoded.buffer, // 底层 ArrayBuffer
198
- decoded.byteOffset + 24, // 起始偏移量
199
- 20 // 长度
200
- ).toString('hex');
201
- return { typeCode, endpointIndex, sourceId, messageHandle };
122
+ parseLoginResponseResolve(idp, art, request) {
123
+ const self = this;
124
+ return Artifact.parseLoginResponseResolve({
125
+ idp: idp,
126
+ sp: self,
127
+ art: art
128
+ });
202
129
  }
203
130
  }
@@ -54,6 +54,38 @@ export const loginRequestFields = [
54
54
  context: true
55
55
  }
56
56
  ];
57
+ export const artifactResolveFields = [
58
+ {
59
+ key: 'request',
60
+ localPath: ['ArtifactResolve'],
61
+ attributes: ['ID', 'IssueInstant', 'Version']
62
+ },
63
+ {
64
+ key: 'issuer', localPath: ['ArtifactResolve', 'Issuer'], attributes: []
65
+ },
66
+ {
67
+ key: 'Artifact', localPath: ['ArtifactResolve', 'Artifact'], attributes: []
68
+ },
69
+ {
70
+ key: 'signature', localPath: ['ArtifactResolve', 'Signature'], attributes: [], context: true
71
+ },
72
+ ];
73
+ export const artifactResponseFields = [
74
+ {
75
+ key: 'request',
76
+ localPath: ['Envelope', 'Body', 'ArtifactResolve'],
77
+ attributes: ['ID', 'IssueInstant', 'Version']
78
+ },
79
+ {
80
+ key: 'issuer', localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Issuer'], attributes: []
81
+ },
82
+ {
83
+ key: 'Artifact', localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Artifact'], attributes: []
84
+ },
85
+ {
86
+ key: 'signature', localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Signature'], attributes: [], context: true
87
+ },
88
+ ];
57
89
  // support two-tiers status code
58
90
  export const loginResponseStatusFields = [
59
91
  {
package/build/src/flow.js CHANGED
@@ -1,10 +1,6 @@
1
1
  import { base64Decode } from './utility.js';
2
2
  import { verifyTime } from './validator.js';
3
3
  import libsaml from './libsaml.js';
4
- import * as uuid from 'uuid';
5
- import { select } from 'xpath';
6
- import { DOMParser } from '@xmldom/xmldom';
7
- import { sendArtifactResolve } from "./soap.js";
8
4
  import { extract, loginRequestFields, loginResponseFields, loginResponseStatusFields, loginArtifactResponseStatusFields, logoutRequestFields, logoutResponseFields, logoutResponseStatusFields } from './extractor.js';
9
5
  import { BindingNamespace, ParserType, StatusCode, wording } from './urn.js';
10
6
  const bindDict = wording.binding;
@@ -126,58 +122,15 @@ async function redirectFlow(options) {
126
122
  }
127
123
  // proceed the post flow
128
124
  async function postFlow(options) {
129
- const { soap = false, request, from, self, parserType, checkSignature = true } = options;
125
+ const { request, from, self, parserType, checkSignature = true } = options;
130
126
  const { body } = request;
131
127
  const direction = libsaml.getQueryParamByType(parserType);
132
128
  let encodedRequest = '';
133
129
  let samlContent = '';
134
- if (!soap) {
135
- encodedRequest = body[direction];
136
- // @ts-ignore
137
- samlContent = String(base64Decode(encodedRequest));
138
- }
130
+ encodedRequest = body[direction];
131
+ // @ts-ignore
132
+ samlContent = String(base64Decode(encodedRequest));
139
133
  /** 增加判断是不是Soap 工件绑定*/
140
- if (soap) {
141
- const metadata = {
142
- idp: from.entityMeta,
143
- sp: self.entityMeta,
144
- };
145
- const spSetting = self.entitySetting;
146
- let ID = '_' + uuid.v4();
147
- let url = metadata.idp.getArtifactResolutionService(bindDict.soap);
148
- let samlSoapRaw = libsaml.replaceTagsByValue(libsaml.defaultArtifactResolveTemplate.context, {
149
- ID: ID,
150
- Destination: url,
151
- Issuer: metadata.sp.getEntityID(),
152
- IssueInstant: new Date().toISOString(),
153
- Art: request.Art
154
- });
155
- if (!metadata.idp.isWantAuthnRequestsSigned()) {
156
- samlContent = await sendArtifactResolve(url, samlSoapRaw);
157
- }
158
- if (metadata.idp.isWantAuthnRequestsSigned()) {
159
- const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
160
- let signatureSoap = libsaml.constructSAMLSignature({
161
- referenceTagXPath: "//*[local-name(.)='ArtifactResolve']",
162
- isMessageSigned: false,
163
- isBase64Output: false,
164
- transformationAlgorithms: transformationAlgorithms,
165
- privateKey,
166
- privateKeyPass,
167
- signatureAlgorithm,
168
- rawSamlMessage: samlSoapRaw,
169
- signingCert: metadata.sp.getX509Certificate('signing'),
170
- signatureConfig: {
171
- prefix: 'ds',
172
- location: {
173
- reference: "//*[local-name(.)='Issuer']",
174
- action: 'after'
175
- }
176
- }
177
- });
178
- samlContent = await sendArtifactResolve(url, signatureSoap);
179
- }
180
- }
181
134
  const verificationOptions = {
182
135
  metadata: from.entityMeta,
183
136
  signatureAlgorithm: from.entitySetting.requestSignatureAlgorithm,
@@ -186,7 +139,7 @@ async function postFlow(options) {
186
139
  let decryptRequired = from.entitySetting.isAssertionEncrypted;
187
140
  let extractorFields = [];
188
141
  // validate the xml first
189
- let res = await libsaml.isValidXml(samlContent, soap).catch((error) => {
142
+ let res = await libsaml.isValidXml(samlContent).catch((error) => {
190
143
  return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
191
144
  });
192
145
  if (res !== true) {
@@ -196,67 +149,25 @@ async function postFlow(options) {
196
149
  extractorFields = getDefaultExtractorFields(parserType, null);
197
150
  }
198
151
  // check status based on different scenarios
199
- await checkStatus(samlContent, parserType, soap);
152
+ await checkStatus(samlContent, parserType);
200
153
  /**检查签名顺序 */
201
- if (soap) {
202
- const [verified, verifiedAssertionNode, isDecryptRequired] = libsaml.verifySignatureSoap(samlContent, verificationOptions);
203
- decryptRequired = isDecryptRequired;
204
- if (!verified) {
205
- return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
206
- }
207
- if (!decryptRequired) {
208
- extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
209
- }
210
- if (parserType === 'SAMLResponse' && decryptRequired) {
211
- // 1. 解密断言
212
- const [decryptedSAML, decryptedAssertion] = await libsaml.decryptAssertionSoap(self, samlContent);
213
- // 2. 检查解密后的断言是否包含签名
214
- const assertionDoc = new DOMParser().parseFromString(decryptedAssertion, 'application/xml');
215
- // @ts-ignore
216
- const assertionSignatureNodes = select("./*[local-name()='Signature']", assertionDoc.documentElement);
217
- // 3. 如果存在签名则验证
218
- if (assertionSignatureNodes.length > 0) {
219
- // 3.1 创建新的验证选项(保持原配置)
220
- const assertionVerificationOptions = {
221
- ...verificationOptions,
222
- isAssertion: true // 添加标识表示正在验证断言
223
- };
224
- // 3.2 验证断言签名
225
- const [assertionVerified, result] = libsaml.verifySignatureSoap(decryptedAssertion, assertionVerificationOptions);
226
- if (!assertionVerified) {
227
- return Promise.reject('ERR_FAIL_TO_VERIFY_ASSERTION_SIGNATURE');
228
- }
229
- if (assertionVerified) {
230
- // @ts-ignore
231
- samlContent = result;
232
- extractorFields = getDefaultExtractorFields(parserType, result);
233
- }
234
- }
235
- else {
236
- samlContent = decryptedAssertion;
237
- extractorFields = getDefaultExtractorFields(parserType, decryptedAssertion);
238
- }
239
- }
154
+ const [verified, verifiedAssertionNode, isDecryptRequired, noSignature] = libsaml.verifySignature(samlContent, verificationOptions);
155
+ decryptRequired = isDecryptRequired;
156
+ if (isDecryptRequired && noSignature) {
157
+ const result = await libsaml.decryptAssertion(self, samlContent);
158
+ samlContent = result[0];
159
+ extractorFields = getDefaultExtractorFields(parserType, result[1]);
240
160
  }
241
- if (!soap) {
242
- const [verified, verifiedAssertionNode, isDecryptRequired, noSignature] = libsaml.verifySignature(samlContent, verificationOptions);
243
- decryptRequired = isDecryptRequired;
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) {
250
- return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
251
- }
252
- if (!decryptRequired) {
253
- extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
254
- }
255
- if (parserType === 'SAMLResponse' && decryptRequired && !noSignature) {
256
- const result = await libsaml.decryptAssertion(self, samlContent);
257
- samlContent = result[0];
258
- extractorFields = getDefaultExtractorFields(parserType, result[1]);
259
- }
161
+ if (!verified && !noSignature && !isDecryptRequired) {
162
+ return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
163
+ }
164
+ if (!isDecryptRequired) {
165
+ extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
166
+ }
167
+ if (parserType === 'SAMLResponse' && isDecryptRequired && !noSignature) {
168
+ const result = await libsaml.decryptAssertion(self, samlContent);
169
+ samlContent = result[0];
170
+ extractorFields = getDefaultExtractorFields(parserType, result[1]);
260
171
  }
261
172
  const parseResult = {
262
173
  samlContent: samlContent,
@@ -493,7 +404,7 @@ async function postSimpleSignFlow(options) {
493
404
  }
494
405
  return Promise.resolve(parseResult);
495
406
  }
496
- function checkStatus(content, parserType, soap) {
407
+ export function checkStatus(content, parserType, soap) {
497
408
  // only check response parser
498
409
  if (parserType !== urlParams.samlResponse && parserType !== urlParams.logoutResponse) {
499
410
  return Promise.resolve('SKIPPED');