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.
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
package/README.md CHANGED
@@ -1,64 +1,44 @@
1
- # samlify · [![构建状态](https://img.shields.io/circleci/build/github/tngan/samlify?style=for-the-badge&logo=circleci)](https://app.circleci.com/pipelines/github/tngan/samlify) [![npm 版本](https://img.shields.io/npm/v/samlify.svg?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/samlify) [![下载量](https://img.shields.io/npm/dm/samlify.svg?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/samlify) [![覆盖率](https://img.shields.io/coveralls/tngan/samlify/master.svg?style=for-the-badge&logo=coveralls)](https://coveralls.io/github/tngan/samlify?branch=master)
2
-
3
- 高度可配置的 Node.js SAML 2.0 单点登录库
4
- Highly configurable Node.js SAML 2.0 library for Single Sign On
1
+ # samlify · [![构建状态](https://img.shields.io/circleci/build/github/tngan/samlify?style=for-the-badge&logo=circleci)](https://app.circleci.com/pipelines/github/tngan/samlify) [![npm 版本](https://img.shields.io/npm/v/samlify.svg?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/samlify) [![下载量](https://img.shields.io/npm/dm/samlify.svg?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/samlify) [![覆盖率](https://img.shields.io/coveralls/tngan/samlify/master.svg?style=for-the-badge&logo=coveralls)](https://coveralls.io/github/tngan/samlify?branch=master)
5
2
 
6
3
  ---
7
-
8
- ## 🔄 本仓库为 [samlify](https://github.com/tngan/samlify) 的改进分支版本,原作者[tngan](https://github.com/tngan)
9
-
10
- ### 主要改进 / Key Improvements
11
-
12
- - 📦 CJS模块打包转为 ESModule
13
-
14
- - ✅ 将依赖包 `@authenio/xml-encryption` 替换为 `xml-encryption` 并升级版本对 sha256/512 加密密钥 OAEP 摘要方法的支持
15
-
16
- - 🛠️ 修复加密断言验证签名函数 verifySignature 提取`Assertion` 字段的错误,增加对加密断言 `EncryptedAssertion` 字段提取逻辑
17
-
18
- - 📦 ServiceProvider实例化函数 attributeConsumingService字段参函数, 生成默认的 `AttributeConsumingService` 元素和属性值
19
-
20
- - 🗑️ 移除作为Idp使用 IdentityProvider 函数自定义函数模板loginResponseTemplate字段的支持,并改进了自定义函数替换。
21
- 改进createLoginResponse函数签名改为对象的传参方式
22
-
23
- - 🔒 默认签名算法升级为 SHA-256,Idp默认加密算法为 AES_256_GCM
24
-
25
- - ⬆️ 升级所有能够升级的依赖版本,移除 `node-rsa`/`node-forge` 模块儿,改用原生nodejs `crypto` 模块实现。
26
-
27
- - 🌐 将 `url` 库替换为 `URL` 原生 API
28
- - 改进了如果响应为的绑定`urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect`,某些情况下未能DEFLATE压缩导致不能提取xml的异常情况的处理
29
- - 现在如果遇到加密响应无需显示传递 `isAssertionEncrypted` 字段,也无需传递 `MessageSignatureOrder`
30
- 字段。因为我认为是否加密应该是可以自动判断的,MessageSignatureOrder我修改了判断逻辑并在Keycloak 验证可以通过。使用前你应该自行验证这其中的风险
31
- - 默认 elementsOrder 增加了 AttributeConsumingService 适配
32
- - 我已经使用 Burp SAML Raider测试了 八种XSW都能良好的应对,以及XXE。你应该自行验证
4
+ [English Version](#README.md) | [中文版本](#readmeCN.md)
5
+ ## 🔄 This repository is an improved fork of [samlify](https://github.com/tngan/samlify) by [tngan](https://github.com/tngan)
6
+
7
+ ### Key Improvements
8
+
9
+ - 📦 Converted from CJS to ESModule
10
+ - ✅ Replaced `@authenio/xml-encryption` with `xml-encryption` and added support for sha256/512 encryption key OAEP digest methods
11
+ - ✅ Upgraded `@xmldom/xmldom` to the latest version
12
+ - 🛠️ Fixed encrypted assertion signature verification by adding `EncryptedAssertion` field extraction logic
13
+ - 📦 Added default `AttributeConsumingService` element generation for ServiceProvider
14
+ - 📦 Added partial Artifact binding support
15
+ - 🗑️ Removed custom template support for IdentityProvider and improved parameter passing
16
+ - 🔒 Upgraded default signature algorithm to SHA-256 and default encryption to AES_256_GCM
17
+ - 🧪 Added built-in XML XSD validator
18
+ - 🐛 Improved handling of HTTP-Redirect binding without DEFLATE compression
19
+ - 🔓 Automatic detection of encrypted assertions without explicit flags
20
+ - 📝 Added AttributeConsumingService to default elementsOrder
21
+ - ✅ Tested against Burp SAML Raider (XSW and XXE attacks)
22
+ - Migrated tests to Vitest
33
23
 
34
24
  ---
35
25
 
36
- ## 欢迎 PR / Welcome PRs
26
+ ## Welcome PRs
37
27
 
38
- 欢迎贡献代码或提供与其他框架集成的用例
39
- Welcome contributions or integration examples with frameworks
28
+ Contributions are welcome! Please feel free to submit pull requests or provide integration examples with other frameworks.
40
29
 
41
30
  ---
42
31
 
43
- ## 安装 / Installation
44
- 您应该在使用的前提下首先设置验证其
45
- ```js
32
+ ## How to use?
46
33
 
47
- import * as validator from '@authenio/samlify-xsd-schema-validator';
48
- import * as Saml from "samlesa";
49
- import {Extractor,} from "samlesa";
50
- import validator from '@authenio/samlify-node-xmllint'
51
- // 设置模式验证器 / Set schema validator
52
- Saml.setSchemaValidator(validator);
34
+ Refer to the `type/flows.test.ts` test cases and the original documentation at [https://samlify.js.org](https://samlify.js.org). Note that some parameters have been changed in this fork.
53
35
 
36
+ ---
54
37
 
55
- ```
56
-
57
- ## 生成密钥
58
-
59
- 我们使用 openssl 生成密钥和证书用于测试。私钥可以使用密码保护,这是可选的。以下是生成私钥和自签名证书的命令。
38
+ ## Generating Keys
60
39
 
61
- > openssl genrsa -passout pass:foobar -out encryptKey.pem 4096
62
- > openssl req -new -x509 -key encryptKey.pem -out encryptionCert.cer -days 3650
40
+ Use OpenSSL to generate keys and certificates for testing. Private keys can be password-protected (optional). Here are the commands:
63
41
 
64
- #
42
+ ```bash
43
+ openssl genrsa -passout pass:foobar -out encryptKey.pem 4096
44
+ openssl req -new -x509 -key encryptKey.pem -out encryptionCert.cer -days 3650
@@ -1,18 +1,18 @@
1
1
  /**
2
- * @file binding-post.ts
3
- * @author tngan
4
- * @desc Binding-level API, declare the functions using POST binding
5
- */
2
+ * @file binding-post.ts
3
+ * @author tngan
4
+ * @desc Binding-level API, declare the functions using POST binding
5
+ */
6
6
  import { wording, StatusCode } from './urn.js';
7
7
  import libsaml from './libsaml.js';
8
8
  import utility, { get } from './utility.js';
9
9
  const binding = wording.binding;
10
10
  /**
11
- * @desc Generate a base64 encoded login request
12
- * @param {string} referenceTagXPath reference uri
13
- * @param {object} entity object includes both idp and sp
14
- * @param {function} customTagReplacement used when developers have their own login response template
15
- */
11
+ * @desc Generate a base64 encoded login request
12
+ * @param {string} referenceTagXPath reference uri
13
+ * @param {object} entity object includes both idp and sp
14
+ * @param {function} customTagReplacement used when developers have their own login response template
15
+ */
16
16
  function base64LoginRequest(referenceTagXPath, entity, customTagReplacement) {
17
17
  const metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta };
18
18
  const spSetting = entity.sp.entitySetting;
@@ -141,7 +141,6 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTa
141
141
  };
142
142
  // step: sign assertion ? -> encrypted ? -> sign message ?
143
143
  if (metadata.sp.isWantAssertionsSigned()) {
144
- // console.debug('sp wants assertion signed');
145
144
  rawSamlResponse = libsaml.constructSAMLSignature({
146
145
  ...config,
147
146
  rawSamlMessage: rawSamlResponse,
@@ -149,7 +148,10 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTa
149
148
  referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']",
150
149
  signatureConfig: {
151
150
  prefix: 'ds',
152
- location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
151
+ location: {
152
+ reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']",
153
+ action: 'after'
154
+ },
153
155
  },
154
156
  });
155
157
  }
@@ -168,7 +170,19 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTa
168
170
  },
169
171
  });
170
172
  }
171
- // console.debug('after message signed', rawSamlResponse);
173
+ /* if (spSetting.wantMessageSigned) {
174
+ // console.debug('sign then encrypt and sign entire message');
175
+ rawSamlResponse = libsaml.constructSAMLSignature({
176
+ ...config,
177
+ rawSamlMessage: rawSamlResponse,
178
+ isMessageSigned: true,
179
+ transformationAlgorithms: spSetting.transformationAlgorithms,
180
+ signatureConfig: spSetting.signatureConfig || {
181
+ prefix: 'ds',
182
+ location: {reference: "/!*[local-name(.)='Response']/!*[local-name(.)='Issuer']", action: 'after'},
183
+ },
184
+ });
185
+ }*/
172
186
  if (idpSetting.isAssertionEncrypted) {
173
187
  // console.debug('idp is configured to do encryption');
174
188
  const context = await libsaml.encryptAssertion(entity.idp, entity.sp, rawSamlResponse);
@@ -181,18 +195,18 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTa
181
195
  }
182
196
  }
183
197
  //sign after encrypting
184
- if (encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
185
- rawSamlResponse = libsaml.constructSAMLSignature({
198
+ /* if (encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
199
+ rawSamlResponse = libsaml.constructSAMLSignature({
186
200
  ...config,
187
201
  rawSamlMessage: rawSamlResponse,
188
202
  isMessageSigned: true,
189
203
  transformationAlgorithms: spSetting.transformationAlgorithms,
190
204
  signatureConfig: spSetting.signatureConfig || {
191
- prefix: 'ds',
192
- location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
205
+ prefix: 'ds',
206
+ location: {reference: "/!*[local-name(.)='Response']/!*[local-name(.)='Issuer']", action: 'after'},
193
207
  },
194
- });
195
- }
208
+ });
209
+ }*/
196
210
  return Promise.resolve({
197
211
  id,
198
212
  context: utility.base64Encode(rawSamlResponse),
@@ -201,13 +215,13 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTa
201
215
  throw new Error('ERR_GENERATE_POST_LOGIN_RESPONSE_MISSING_METADATA');
202
216
  }
203
217
  /**
204
- * @desc Generate a base64 encoded logout request
205
- * @param {object} user current logged user (e.g. req.user)
206
- * @param {string} referenceTagXPath reference uri
207
- * @param {object} entity object includes both idp and sp
208
- * @param {function} customTagReplacement used when developers have their own login response template
209
- * @return {string} base64 encoded request
210
- */
218
+ * @desc Generate a base64 encoded logout request
219
+ * @param {object} user current logged user (e.g. req.user)
220
+ * @param {string} referenceTagXPath reference uri
221
+ * @param {object} entity object includes both idp and sp
222
+ * @param {function} customTagReplacement used when developers have their own login response template
223
+ * @return {string} base64 encoded request
224
+ */
211
225
  function base64LogoutRequest(user, referenceTagXPath, entity, customTagReplacement) {
212
226
  const metadata = { init: entity.init.entityMeta, target: entity.target.entityMeta };
213
227
  const initSetting = entity.init.entitySetting;
@@ -262,12 +276,12 @@ function base64LogoutRequest(user, referenceTagXPath, entity, customTagReplaceme
262
276
  throw new Error('ERR_GENERATE_POST_LOGOUT_REQUEST_MISSING_METADATA');
263
277
  }
264
278
  /**
265
- * @desc Generate a base64 encoded logout response
266
- * @param {object} requestInfo corresponding request, used to obtain the id
267
- * @param {string} referenceTagXPath reference uri
268
- * @param {object} entity object includes both idp and sp
269
- * @param {function} customTagReplacement used when developers have their own login response template
270
- */
279
+ * @desc Generate a base64 encoded logout response
280
+ * @param {object} requestInfo corresponding request, used to obtain the id
281
+ * @param {string} referenceTagXPath reference uri
282
+ * @param {object} entity object includes both idp and sp
283
+ * @param {function} customTagReplacement used when developers have their own login response template
284
+ */
271
285
  function base64LogoutResponse(requestInfo, entity, customTagReplacement) {
272
286
  const metadata = {
273
287
  init: entity.init.entityMeta,
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import utility, { get } from './utility.js';
7
7
  import libsaml from './libsaml.js';
8
- import { wording, namespace } from './urn.js';
8
+ import { namespace, wording } from './urn.js';
9
9
  const binding = wording.binding;
10
10
  const urlParams = wording.urlParams;
11
11
  /**
@@ -59,8 +59,9 @@ function buildRedirectURL(opts) {
59
59
  * @param {function} customTagReplacement used when developers have their own login response template
60
60
  * @return {string} redirect URL
61
61
  */
62
+ // @ts-ignore
62
63
  function loginRequestRedirectURL(entity, customTagReplacement) {
63
- const metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta };
64
+ const metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta, soap: entity.soap ?? false };
64
65
  const spSetting = entity.sp.entitySetting;
65
66
  let id = '';
66
67
  if (metadata && metadata.idp && metadata.sp) {
@@ -100,6 +101,90 @@ function loginRequestRedirectURL(entity, customTagReplacement) {
100
101
  }
101
102
  throw new Error('ERR_GENERATE_REDIRECT_LOGIN_REQUEST_MISSING_METADATA');
102
103
  }
104
+ /**
105
+ * @desc Redirect URL for login request
106
+ * @param {object} entity object includes both idp and sp
107
+ * @param {function} customTagReplacement used when developers have their own login response template
108
+ * @return {string} redirect URL
109
+ */
110
+ // @ts-ignore
111
+ function loginRequestRedirectURLArt(entity, customTagReplacement) {
112
+ const metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta, inResponse: entity.inResponse ?? false };
113
+ const spSetting = entity.sp.entitySetting;
114
+ let id = '';
115
+ if (metadata && metadata.idp && metadata.sp) {
116
+ const base = metadata.idp.getSingleSignOnService(binding.redirect);
117
+ let rawSamlRequest;
118
+ if (spSetting.loginRequestTemplate && customTagReplacement) {
119
+ const info = customTagReplacement(spSetting.loginRequestTemplate);
120
+ id = get(info, 'id', null);
121
+ rawSamlRequest = get(info, 'context', null);
122
+ }
123
+ else {
124
+ const nameIDFormat = spSetting.nameIDFormat;
125
+ const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
126
+ id = spSetting.generateID();
127
+ rawSamlRequest = libsaml.replaceTagsByValue(libsaml.defaultLoginRequestTemplate.context, {
128
+ ID: id,
129
+ Destination: base,
130
+ Issuer: metadata.sp.getEntityID(),
131
+ IssueInstant: new Date().toISOString(),
132
+ NameIDFormat: selectedNameIDFormat,
133
+ AssertionConsumerServiceURL: metadata.sp.getAssertionConsumerService(binding.post),
134
+ EntityID: metadata.sp.getEntityID(),
135
+ AllowCreate: spSetting.allowCreate,
136
+ });
137
+ }
138
+ const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
139
+ if (metadata.idp.isWantAuthnRequestsSigned()) {
140
+ let signAuthnRequest = libsaml.constructSAMLSignature({
141
+ referenceTagXPath: "/*[local-name(.)='AuthnRequest']",
142
+ privateKey,
143
+ privateKeyPass,
144
+ signatureAlgorithm,
145
+ transformationAlgorithms,
146
+ isBase64Output: false,
147
+ rawSamlMessage: rawSamlRequest,
148
+ signingCert: metadata.sp.getX509Certificate('signing'),
149
+ signatureConfig: spSetting.signatureConfig || {
150
+ prefix: 'ds',
151
+ location: {
152
+ reference: "/*[local-name(.)='AuthnRequest']/*[local-name(.)='Issuer']",
153
+ action: 'after'
154
+ },
155
+ }
156
+ });
157
+ rawSamlRequest = signAuthnRequest;
158
+ }
159
+ /* console.log(metadata.idp)
160
+ console.log(entity.idp.getEntitySetting())*/
161
+ let soapTemplate = libsaml.replaceTagsByValue(libsaml.defaultArtAuthnRequestTemplate.context, {
162
+ ID: id,
163
+ IssueInstant: new Date().toISOString(),
164
+ InResponseTo: metadata.inResponse ?? "",
165
+ Issuer: metadata.sp.getEntityID(),
166
+ AuthnRequest: rawSamlRequest
167
+ });
168
+ let rootSignSoap = libsaml.constructSAMLSignature({
169
+ isMessageSigned: true,
170
+ isBase64Output: false,
171
+ privateKey,
172
+ privateKeyPass,
173
+ signatureAlgorithm,
174
+ transformationAlgorithms,
175
+ rawSamlMessage: soapTemplate,
176
+ signingCert: metadata.sp.getX509Certificate('signing'),
177
+ signatureConfig: {
178
+ prefix: 'ds',
179
+ location: { reference: "//*[local-name()='Header']", action: 'after' },
180
+ }
181
+ });
182
+ return {
183
+ authnRequest: rootSignSoap
184
+ };
185
+ }
186
+ throw new Error('ERR_GENERATE_REDIRECT_LOGIN_REQUEST_MISSING_METADATA');
187
+ }
103
188
  /**
104
189
  * @desc Redirect URL for login response
105
190
  * @param {object} requestInfo corresponding request, used to obtain the id
@@ -130,7 +215,6 @@ function loginResponseRedirectURL(requestInfo, entity, user = {}, relayState, cu
130
215
  // Five minutes later : nowtime + 5 * 60 * 1000 (in milliseconds)
131
216
  const fiveMinutesLaterTime = new Date(nowTime.getTime() + 300_000);
132
217
  const now = nowTime.toISOString();
133
- console.log(`现在是北京时间:${nowTime.toLocaleString()}`);
134
218
  const sessionIndex = 'session' + idpSetting.generateID(); // 这个是当前系统的会话索引,用于单点注销
135
219
  const tenHoursLaterTime = new Date(nowTime.getTime());
136
220
  tenHoursLaterTime.setHours(tenHoursLaterTime.getHours() + 10);
@@ -304,6 +388,7 @@ function logoutResponseRedirectURL(requestInfo, entity, relayState, customTagRep
304
388
  throw new Error('ERR_GENERATE_REDIRECT_LOGOUT_RESPONSE_MISSING_METADATA');
305
389
  }
306
390
  const redirectBinding = {
391
+ loginRequestRedirectURLArt,
307
392
  loginRequestRedirectURL,
308
393
  loginResponseRedirectURL,
309
394
  logoutRequestRedirectURL,
@@ -119,7 +119,6 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, relaySta
119
119
  // Five minutes later : nowtime + 5 * 60 * 1000 (in milliseconds)
120
120
  const fiveMinutesLaterTime = new Date(nowTime.getTime() + 300_000);
121
121
  const now = nowTime.toISOString();
122
- console.log(`现在是北京时间:${nowTime.toLocaleString()}`);
123
122
  const sessionIndex = 'session' + idpSetting.generateID(); // 这个是当前系统的会话索引,用于单点注销
124
123
  const tenHoursLaterTime = new Date(nowTime.getTime());
125
124
  tenHoursLaterTime.setHours(tenHoursLaterTime.getHours() + 10);
@@ -59,11 +59,7 @@ export class IdentityProvider extends Entity {
59
59
  sp,
60
60
  }, user, relayState, customTagReplacement, AttributeStatement);
61
61
  default:
62
- context = await postBinding.base64LoginResponse(requestInfo, {
63
- idp: this,
64
- sp,
65
- }, user, customTagReplacement, encryptThenSign, AttributeStatement);
66
- /* throw new Error('ERR_CREATE_RESPONSE_UNDEFINED_BINDING');*/
62
+ throw new Error('ERR_CREATE_RESPONSE_UNDEFINED_BINDING');
67
63
  }
68
64
  return {
69
65
  ...context,
@@ -1,9 +1,10 @@
1
1
  /**
2
- * @file entity-sp.ts
3
- * @author tngan
4
- * @desc Declares the actions taken by service provider
5
- */
2
+ * @file entity-sp.ts
3
+ * @author tngan
4
+ * @desc Declares the actions taken by service provider
5
+ */
6
6
  import Entity from './entity.js';
7
+ import * as crypto from "node:crypto";
7
8
  import { namespace } from './urn.js';
8
9
  import redirectBinding from './binding-redirect.js';
9
10
  import postBinding from './binding-post.js';
@@ -17,15 +18,15 @@ export default function (props) {
17
18
  return new ServiceProvider(props);
18
19
  }
19
20
  /**
20
- * @desc Service provider can be configured using either metadata importing or spSetting
21
- * @param {object} spSettingimport { FlowResult } from '../types/src/flow.d';
21
+ * @desc Service provider can be configured using either metadata importing or spSetting
22
+ * @param {object} spSettingimport { FlowResult } from '../types/src/flow.d';
22
23
 
23
- */
24
+ */
24
25
  export class ServiceProvider extends Entity {
25
26
  /**
26
- * @desc Inherited from Entity
27
- * @param {object} spSetting setting of service provider
28
- */
27
+ * @desc Inherited from Entity
28
+ * @param {object} spSetting setting of service provider
29
+ */
29
30
  constructor(spSetting) {
30
31
  const entitySetting = Object.assign({
31
32
  authnRequestsSigned: false,
@@ -35,11 +36,11 @@ export class ServiceProvider extends Entity {
35
36
  super(entitySetting, 'sp');
36
37
  }
37
38
  /**
38
- * @desc Generates the login request for developers to design their own method
39
- * @param {IdentityProvider} idp object of identity provider
40
- * @param {string} binding protocol binding
41
- * @param {function} customTagReplacement used when developers have their own login response template
42
- */
39
+ * @desc Generates the login request for developers to design their own method
40
+ * @param {IdentityProvider} idp object of identity provider
41
+ * @param {string} binding protocol binding
42
+ * @param {function} customTagReplacement used when developers have their own login response template
43
+ */
43
44
  createLoginRequest(idp, binding = 'redirect', customTagReplacement) {
44
45
  const nsBinding = namespace.binding;
45
46
  const protocol = nsBinding[binding];
@@ -51,14 +52,20 @@ export class ServiceProvider extends Entity {
51
52
  case nsBinding.redirect:
52
53
  return redirectBinding.loginRequestRedirectURL({ idp, sp: this }, customTagReplacement);
53
54
  case nsBinding.post:
54
- context = postBinding.base64LoginRequest("/*[local-name(.)='AuthnRequest']", { idp, sp: this }, customTagReplacement);
55
+ context = postBinding.base64LoginRequest("/*[local-name(.)='AuthnRequest']", {
56
+ idp,
57
+ sp: this
58
+ }, customTagReplacement);
55
59
  break;
56
60
  case nsBinding.simpleSign:
57
61
  // Object context = {id, context, signature, sigAlg}
58
62
  context = simpleSignBinding.base64LoginRequest({ idp, sp: this }, customTagReplacement);
59
63
  break;
60
64
  case nsBinding.artifact:
61
- context = artifactSignBinding.base64LoginRequest("/*[local-name(.)='AuthnRequest']", { idp, sp: this }, customTagReplacement);
65
+ context = artifactSignBinding.base64LoginRequest("/*[local-name(.)='AuthnRequest']", {
66
+ idp,
67
+ sp: this
68
+ }, customTagReplacement);
62
69
  break;
63
70
  default:
64
71
  // Will support artifact in the next release
@@ -72,11 +79,45 @@ export class ServiceProvider extends Entity {
72
79
  };
73
80
  }
74
81
  /**
75
- * @desc Validation of the parsed the URL parameters
76
- * @param {IdentityProvider} idp object of identity provider
77
- * @param {string} binding protocol binding
78
- * @param {request} req request
79
- */
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) {
88
+ const nsBinding = namespace.binding;
89
+ const protocol = nsBinding[binding];
90
+ if (this.entityMeta.isAuthnRequestSigned() !== idp.entityMeta.isWantAuthnRequestsSigned()) {
91
+ throw new Error('ERR_METADATA_CONFLICT_REQUEST_SIGNED_FLAG');
92
+ }
93
+ 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
+ };
114
+ }
115
+ /**
116
+ * @desc Validation of the parsed the URL parameters
117
+ * @param {IdentityProvider} idp object of identity provider
118
+ * @param {string} binding protocol binding
119
+ * @param {request} req request
120
+ */
80
121
  parseLoginResponse(idp, binding, request) {
81
122
  const self = this;
82
123
  return flow({
@@ -95,7 +136,7 @@ export class ServiceProvider extends Entity {
95
136
  * @param {string} binding protocol binding
96
137
  * @param {request} req request
97
138
  */
98
- artifactResolveResponse(idp, binding, request) {
139
+ parseLoginResponseArt(idp, binding, request) {
99
140
  const self = this;
100
141
  return flow({
101
142
  soap: true,
@@ -108,4 +149,55 @@ export class ServiceProvider extends Entity {
108
149
  request: request
109
150
  });
110
151
  }
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 };
202
+ }
111
203
  }