samlesa 2.16.6 → 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.
- package/README.md +30 -50
- package/build/src/binding-post.js +45 -31
- package/build/src/binding-redirect.js +0 -10
- package/build/src/binding-simplesign.js +0 -1
- package/build/src/entity-idp.js +1 -5
- package/build/src/entity-sp.js +0 -2
- package/build/src/extractor.js +16 -4
- package/build/src/flow.js +16 -69
- package/build/src/libsaml.js +166 -160
- package/build/src/schema/xml.xsd +88 -88
- package/build/src/schemaValidator.js +9 -13
- package/build/src/utility.js +12 -7
- package/package.json +14 -20
- package/types/src/api.d.ts +3 -3
- package/types/src/api.d.ts.map +1 -1
- package/types/src/binding-post.d.ts +22 -22
- package/types/src/binding-post.d.ts.map +1 -1
- package/types/src/binding-redirect.d.ts.map +1 -1
- package/types/src/binding-simplesign.d.ts.map +1 -1
- package/types/src/entity-idp.d.ts +3 -4
- package/types/src/entity-idp.d.ts.map +1 -1
- package/types/src/entity-sp.d.ts.map +1 -1
- package/types/src/entity.d.ts.map +1 -1
- package/types/src/extractor.d.ts.map +1 -1
- package/types/src/flow.d.ts.map +1 -1
- package/types/src/libsaml.d.ts +12 -4
- package/types/src/libsaml.d.ts.map +1 -1
- package/types/src/schemaValidator.d.ts +1 -1
- package/types/src/schemaValidator.d.ts.map +1 -1
- package/types/src/utility.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1,64 +1,44 @@
|
|
|
1
|
-
# samlify
|
|
2
|
-
|
|
3
|
-
高度可配置的 Node.js SAML 2.0 单点登录库
|
|
4
|
-
Highly configurable Node.js SAML 2.0 library for Single Sign On
|
|
1
|
+
# samlify · [](https://app.circleci.com/pipelines/github/tngan/samlify) [](https://www.npmjs.com/package/samlify) [](https://www.npmjs.com/package/samlify) [](https://coveralls.io/github/tngan/samlify?branch=master)
|
|
5
2
|
|
|
6
3
|
---
|
|
7
|
-
|
|
8
|
-
## 🔄
|
|
9
|
-
|
|
10
|
-
###
|
|
11
|
-
|
|
12
|
-
- 📦
|
|
13
|
-
|
|
14
|
-
- ✅
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
44
|
-
您应该在使用的前提下首先设置验证其
|
|
45
|
-
```js
|
|
32
|
+
## How to use?
|
|
46
33
|
|
|
47
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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,
|
|
@@ -135,8 +135,6 @@ function loginRequestRedirectURLArt(entity, customTagReplacement) {
|
|
|
135
135
|
AllowCreate: spSetting.allowCreate,
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
|
-
console.log(rawSamlRequest);
|
|
139
|
-
console.log("-----------------这是原始请求模板-------------------");
|
|
140
138
|
const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
|
|
141
139
|
if (metadata.idp.isWantAuthnRequestsSigned()) {
|
|
142
140
|
let signAuthnRequest = libsaml.constructSAMLSignature({
|
|
@@ -156,8 +154,6 @@ function loginRequestRedirectURLArt(entity, customTagReplacement) {
|
|
|
156
154
|
},
|
|
157
155
|
}
|
|
158
156
|
});
|
|
159
|
-
console.log(signAuthnRequest);
|
|
160
|
-
console.log("签名后的模板");
|
|
161
157
|
rawSamlRequest = signAuthnRequest;
|
|
162
158
|
}
|
|
163
159
|
/* console.log(metadata.idp)
|
|
@@ -169,9 +165,6 @@ function loginRequestRedirectURLArt(entity, customTagReplacement) {
|
|
|
169
165
|
Issuer: metadata.sp.getEntityID(),
|
|
170
166
|
AuthnRequest: rawSamlRequest
|
|
171
167
|
});
|
|
172
|
-
console.log(soapTemplate);
|
|
173
|
-
console.log("======================最后结果========================");
|
|
174
|
-
console.log("======================开始签名根节点========================");
|
|
175
168
|
let rootSignSoap = libsaml.constructSAMLSignature({
|
|
176
169
|
isMessageSigned: true,
|
|
177
170
|
isBase64Output: false,
|
|
@@ -186,8 +179,6 @@ function loginRequestRedirectURLArt(entity, customTagReplacement) {
|
|
|
186
179
|
location: { reference: "//*[local-name()='Header']", action: 'after' },
|
|
187
180
|
}
|
|
188
181
|
});
|
|
189
|
-
console.log(rootSignSoap);
|
|
190
|
-
console.log("======================已经签名========================");
|
|
191
182
|
return {
|
|
192
183
|
authnRequest: rootSignSoap
|
|
193
184
|
};
|
|
@@ -224,7 +215,6 @@ function loginResponseRedirectURL(requestInfo, entity, user = {}, relayState, cu
|
|
|
224
215
|
// Five minutes later : nowtime + 5 * 60 * 1000 (in milliseconds)
|
|
225
216
|
const fiveMinutesLaterTime = new Date(nowTime.getTime() + 300_000);
|
|
226
217
|
const now = nowTime.toISOString();
|
|
227
|
-
console.log(`现在是北京时间:${nowTime.toLocaleString()}`);
|
|
228
218
|
const sessionIndex = 'session' + idpSetting.generateID(); // 这个是当前系统的会话索引,用于单点注销
|
|
229
219
|
const tenHoursLaterTime = new Date(nowTime.getTime());
|
|
230
220
|
tenHoursLaterTime.setHours(tenHoursLaterTime.getHours() + 10);
|
|
@@ -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);
|
package/build/src/entity-idp.js
CHANGED
|
@@ -59,11 +59,7 @@ export class IdentityProvider extends Entity {
|
|
|
59
59
|
sp,
|
|
60
60
|
}, user, relayState, customTagReplacement, AttributeStatement);
|
|
61
61
|
default:
|
|
62
|
-
|
|
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,
|
package/build/src/entity-sp.js
CHANGED
|
@@ -156,8 +156,6 @@ export class ServiceProvider extends Entity {
|
|
|
156
156
|
*/
|
|
157
157
|
createArt(entityIDString, endpointIndex = 0) {
|
|
158
158
|
let sourceEntityId = entityIDString ? entityIDString : this.entityMeta.getEntityID();
|
|
159
|
-
console.log(sourceEntityId);
|
|
160
|
-
console.log("0000000000000000000000000000000000000000");
|
|
161
159
|
// 1. 固定类型代码 (0x0004 - 2字节)
|
|
162
160
|
const typeCode = Buffer.from([0x00, 0x04]);
|
|
163
161
|
// 2. 端点索引 (2字节,大端序)
|
package/build/src/extractor.js
CHANGED
|
@@ -191,7 +191,7 @@ export const logoutResponseFields = [
|
|
|
191
191
|
];
|
|
192
192
|
export function extract(context, fields) {
|
|
193
193
|
const { dom } = getContext();
|
|
194
|
-
const rootDoc = dom.parseFromString(context);
|
|
194
|
+
const rootDoc = dom.parseFromString(context, 'application/xml');
|
|
195
195
|
return fields.reduce((result, field) => {
|
|
196
196
|
// get essential fields
|
|
197
197
|
const key = field.key;
|
|
@@ -207,7 +207,7 @@ export function extract(context, fields) {
|
|
|
207
207
|
// if shortcut is used, then replace the doc
|
|
208
208
|
// it's a design for overriding the doc used during runtime
|
|
209
209
|
if (shortcut) {
|
|
210
|
-
targetDoc = dom.parseFromString(shortcut);
|
|
210
|
+
targetDoc = dom.parseFromString(shortcut, 'application/xml');
|
|
211
211
|
}
|
|
212
212
|
// special case: multiple path
|
|
213
213
|
/*
|
|
@@ -229,6 +229,7 @@ export function extract(context, fields) {
|
|
|
229
229
|
.join(' | ');
|
|
230
230
|
return {
|
|
231
231
|
...result,
|
|
232
|
+
// @ts-expect-error misssing Node properties are not needed
|
|
232
233
|
[key]: uniq(select(multiXPaths, targetDoc).map((n) => n.nodeValue).filter(notEmpty))
|
|
233
234
|
};
|
|
234
235
|
}
|
|
@@ -249,8 +250,10 @@ export function extract(context, fields) {
|
|
|
249
250
|
// find the index in localpath
|
|
250
251
|
const indexPath = buildAttributeXPath(index);
|
|
251
252
|
const fullLocalXPath = `${baseXPath}${indexPath}`;
|
|
253
|
+
// @ts-expect-error misssing Node properties are not needed
|
|
252
254
|
const parentNodes = select(baseXPath, targetDoc);
|
|
253
255
|
// [uid, mail, edupersonaffiliation], ready for aggregate
|
|
256
|
+
// @ts-expect-error misssing Node properties are not needed
|
|
254
257
|
const parentAttributes = select(fullLocalXPath, targetDoc).map((n) => n.value);
|
|
255
258
|
// [attribute, attributevalue]
|
|
256
259
|
const childXPath = buildAbsoluteXPath([last(localPath)].concat(attributePath));
|
|
@@ -258,8 +261,9 @@ export function extract(context, fields) {
|
|
|
258
261
|
const fullChildXPath = `${childXPath}${childAttributeXPath}`;
|
|
259
262
|
// [ 'test', 'test@example.com', [ 'users', 'examplerole1' ] ]
|
|
260
263
|
const childAttributes = parentNodes.map(node => {
|
|
261
|
-
const nodeDoc = dom.parseFromString(node.toString());
|
|
264
|
+
const nodeDoc = dom.parseFromString(node.toString(), 'application/xml');
|
|
262
265
|
if (attributes.length === 0) {
|
|
266
|
+
// @ts-ignore
|
|
263
267
|
const childValues = select(fullChildXPath, nodeDoc).map((n) => n.nodeValue);
|
|
264
268
|
if (childValues.length === 1) {
|
|
265
269
|
return childValues[0];
|
|
@@ -267,6 +271,7 @@ export function extract(context, fields) {
|
|
|
267
271
|
return childValues;
|
|
268
272
|
}
|
|
269
273
|
if (attributes.length > 0) {
|
|
274
|
+
// @ts-ignore
|
|
270
275
|
const childValues = select(fullChildXPath, nodeDoc).map((n) => n.value);
|
|
271
276
|
if (childValues.length === 1) {
|
|
272
277
|
return childValues[0];
|
|
@@ -292,6 +297,7 @@ export function extract(context, fields) {
|
|
|
292
297
|
}
|
|
293
298
|
*/
|
|
294
299
|
if (isEntire) {
|
|
300
|
+
// @ts-expect-error misssing Node properties are not needed
|
|
295
301
|
const node = select(baseXPath, targetDoc);
|
|
296
302
|
let value = null;
|
|
297
303
|
if (node.length === 1) {
|
|
@@ -314,10 +320,13 @@ export function extract(context, fields) {
|
|
|
314
320
|
}
|
|
315
321
|
*/
|
|
316
322
|
if (attributes.length > 1) {
|
|
323
|
+
// @ts-ignore
|
|
317
324
|
const baseNode = select(baseXPath, targetDoc).map(n => n.toString());
|
|
318
325
|
const childXPath = `${buildAbsoluteXPath([last(localPath)])}${attributeXPath}`;
|
|
319
326
|
const attributeValues = baseNode.map((node) => {
|
|
320
|
-
|
|
327
|
+
// @ts-ignore
|
|
328
|
+
const nodeDoc = dom.parseFromString(node, 'application/xml');
|
|
329
|
+
// @ts-ignore
|
|
321
330
|
const values = select(childXPath, nodeDoc).reduce((r, n) => {
|
|
322
331
|
r[camelCase(n.name, { locale: 'en-us' })] = n.value;
|
|
323
332
|
return r;
|
|
@@ -339,6 +348,7 @@ export function extract(context, fields) {
|
|
|
339
348
|
*/
|
|
340
349
|
if (attributes.length === 1) {
|
|
341
350
|
const fullPath = `${baseXPath}${attributeXPath}`;
|
|
351
|
+
// @ts-ignore
|
|
342
352
|
const attributeValues = select(fullPath, targetDoc).map((n) => n.value);
|
|
343
353
|
return {
|
|
344
354
|
...result,
|
|
@@ -355,9 +365,11 @@ export function extract(context, fields) {
|
|
|
355
365
|
*/
|
|
356
366
|
if (attributes.length === 0) {
|
|
357
367
|
let attributeValue = null;
|
|
368
|
+
// @ts-expect-error misssing Node properties are not needed
|
|
358
369
|
const node = select(baseXPath, targetDoc);
|
|
359
370
|
if (node.length === 1) {
|
|
360
371
|
const fullPath = `string(${baseXPath}${attributeXPath})`;
|
|
372
|
+
// @ts-expect-error misssing Node properties are not needed
|
|
361
373
|
attributeValue = select(fullPath, targetDoc);
|
|
362
374
|
}
|
|
363
375
|
if (node.length > 1) {
|
package/build/src/flow.js
CHANGED
|
@@ -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
|
|
134
|
+
if (!soap) {
|
|
135
135
|
encodedRequest = body[direction];
|
|
136
136
|
// @ts-ignore
|
|
137
137
|
samlContent = String(base64Decode(encodedRequest));
|
|
@@ -186,7 +186,7 @@ async function postFlow(options) {
|
|
|
186
186
|
let decryptRequired = from.entitySetting.isAssertionEncrypted;
|
|
187
187
|
let extractorFields = [];
|
|
188
188
|
// validate the xml first
|
|
189
|
-
let res = await libsaml.isValidXml(samlContent).catch((error) => {
|
|
189
|
+
let res = await libsaml.isValidXml(samlContent, soap).catch((error) => {
|
|
190
190
|
return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
|
|
191
191
|
});
|
|
192
192
|
if (res !== true) {
|
|
@@ -198,23 +198,7 @@ async function postFlow(options) {
|
|
|
198
198
|
// check status based on different scenarios
|
|
199
199
|
await checkStatus(samlContent, parserType, soap);
|
|
200
200
|
/**检查签名顺序 */
|
|
201
|
-
|
|
202
|
-
checkSignature &&
|
|
203
|
-
from.entitySetting.messageSigningOrder === MessageSignatureOrder.ETS
|
|
204
|
-
) {
|
|
205
|
-
console.log("===============我走的这里=========================")
|
|
206
|
-
const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
207
|
-
console.log(verified);
|
|
208
|
-
console.log("verified")
|
|
209
|
-
decryptRequired = isDecryptRequired
|
|
210
|
-
if (!verified) {
|
|
211
|
-
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
212
|
-
}
|
|
213
|
-
if (!decryptRequired) {
|
|
214
|
-
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
215
|
-
}
|
|
216
|
-
}*/
|
|
217
|
-
if (soap === true) {
|
|
201
|
+
if (soap) {
|
|
218
202
|
const [verified, verifiedAssertionNode, isDecryptRequired] = libsaml.verifySignatureSoap(samlContent, verificationOptions);
|
|
219
203
|
decryptRequired = isDecryptRequired;
|
|
220
204
|
if (!verified) {
|
|
@@ -227,7 +211,8 @@ async function postFlow(options) {
|
|
|
227
211
|
// 1. 解密断言
|
|
228
212
|
const [decryptedSAML, decryptedAssertion] = await libsaml.decryptAssertionSoap(self, samlContent);
|
|
229
213
|
// 2. 检查解密后的断言是否包含签名
|
|
230
|
-
const assertionDoc = new DOMParser().parseFromString(decryptedAssertion, '
|
|
214
|
+
const assertionDoc = new DOMParser().parseFromString(decryptedAssertion, 'application/xml');
|
|
215
|
+
// @ts-ignore
|
|
231
216
|
const assertionSignatureNodes = select("./*[local-name()='Signature']", assertionDoc.documentElement);
|
|
232
217
|
// 3. 如果存在签名则验证
|
|
233
218
|
if (assertionSignatureNodes.length > 0) {
|
|
@@ -239,7 +224,6 @@ async function postFlow(options) {
|
|
|
239
224
|
// 3.2 验证断言签名
|
|
240
225
|
const [assertionVerified, result] = libsaml.verifySignatureSoap(decryptedAssertion, assertionVerificationOptions);
|
|
241
226
|
if (!assertionVerified) {
|
|
242
|
-
console.error("解密后的断言签名验证失败");
|
|
243
227
|
return Promise.reject('ERR_FAIL_TO_VERIFY_ASSERTION_SIGNATURE');
|
|
244
228
|
}
|
|
245
229
|
if (assertionVerified) {
|
|
@@ -254,34 +238,26 @@ async function postFlow(options) {
|
|
|
254
238
|
}
|
|
255
239
|
}
|
|
256
240
|
}
|
|
257
|
-
if (soap
|
|
258
|
-
const [verified, verifiedAssertionNode, isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
241
|
+
if (!soap) {
|
|
242
|
+
const [verified, verifiedAssertionNode, isDecryptRequired, noSignature] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
259
243
|
decryptRequired = isDecryptRequired;
|
|
260
|
-
if (
|
|
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) {
|
|
261
250
|
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
262
251
|
}
|
|
263
252
|
if (!decryptRequired) {
|
|
264
253
|
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
265
254
|
}
|
|
266
|
-
if (parserType === 'SAMLResponse' && decryptRequired) {
|
|
255
|
+
if (parserType === 'SAMLResponse' && decryptRequired && !noSignature) {
|
|
267
256
|
const result = await libsaml.decryptAssertion(self, samlContent);
|
|
268
257
|
samlContent = result[0];
|
|
269
258
|
extractorFields = getDefaultExtractorFields(parserType, result[1]);
|
|
270
259
|
}
|
|
271
260
|
}
|
|
272
|
-
// verify the signatures (the response is signed then encrypted, then decrypt first then verify)
|
|
273
|
-
/* if (
|
|
274
|
-
checkSignature &&
|
|
275
|
-
from.entitySetting.messageSigningOrder === MessageSignatureOrder.STE
|
|
276
|
-
) {
|
|
277
|
-
const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
278
|
-
decryptRequired = isDecryptRequired
|
|
279
|
-
if (verified) {
|
|
280
|
-
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
281
|
-
} else {
|
|
282
|
-
return Promise.reject('ERR_FAIL_TO_VERIFY_STE_SIGNATURE');
|
|
283
|
-
}
|
|
284
|
-
}*/
|
|
285
261
|
const parseResult = {
|
|
286
262
|
samlContent: samlContent,
|
|
287
263
|
extract: extract(samlContent, extractorFields),
|
|
@@ -351,29 +327,13 @@ async function postArtifactFlow(options) {
|
|
|
351
327
|
let decryptRequired = from.entitySetting.isAssertionEncrypted;
|
|
352
328
|
let extractorFields = [];
|
|
353
329
|
// validate the xml first
|
|
354
|
-
let res = await libsaml.isValidXml(samlContent);
|
|
330
|
+
let res = await libsaml.isValidXml(samlContent, true);
|
|
355
331
|
if (parserType !== urlParams.samlResponse) {
|
|
356
332
|
extractorFields = getDefaultExtractorFields(parserType, null);
|
|
357
333
|
}
|
|
358
334
|
// check status based on different scenarios
|
|
359
335
|
await checkStatus(samlContent, parserType);
|
|
360
336
|
/**检查签名顺序 */
|
|
361
|
-
/* if (
|
|
362
|
-
checkSignature &&
|
|
363
|
-
from.entitySetting.messageSigningOrder === MessageSignatureOrder.ETS
|
|
364
|
-
) {
|
|
365
|
-
console.log("===============我走的这里=========================")
|
|
366
|
-
const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
367
|
-
console.log(verified);
|
|
368
|
-
console.log("verified")
|
|
369
|
-
decryptRequired = isDecryptRequired
|
|
370
|
-
if (!verified) {
|
|
371
|
-
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
372
|
-
}
|
|
373
|
-
if (!decryptRequired) {
|
|
374
|
-
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
375
|
-
}
|
|
376
|
-
}*/
|
|
377
337
|
const [verified, verifiedAssertionNode, isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
378
338
|
decryptRequired = isDecryptRequired;
|
|
379
339
|
if (!verified) {
|
|
@@ -387,19 +347,6 @@ async function postArtifactFlow(options) {
|
|
|
387
347
|
samlContent = result[0];
|
|
388
348
|
extractorFields = getDefaultExtractorFields(parserType, result[1]);
|
|
389
349
|
}
|
|
390
|
-
// verify the signatures (the response is signed then encrypted, then decrypt first then verify)
|
|
391
|
-
/* if (
|
|
392
|
-
checkSignature &&
|
|
393
|
-
from.entitySetting.messageSigningOrder === MessageSignatureOrder.STE
|
|
394
|
-
) {
|
|
395
|
-
const [verified, verifiedAssertionNode,isDecryptRequired] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
396
|
-
decryptRequired = isDecryptRequired
|
|
397
|
-
if (verified) {
|
|
398
|
-
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
399
|
-
} else {
|
|
400
|
-
return Promise.reject('ERR_FAIL_TO_VERIFY_STE_SIGNATURE');
|
|
401
|
-
}
|
|
402
|
-
}*/
|
|
403
350
|
const parseResult = {
|
|
404
351
|
samlContent: samlContent,
|
|
405
352
|
extract: extract(samlContent, extractorFields),
|
|
@@ -469,7 +416,7 @@ async function postSimpleSignFlow(options) {
|
|
|
469
416
|
const xmlString = String(base64Decode(encodedRequest));
|
|
470
417
|
// validate the xml
|
|
471
418
|
try {
|
|
472
|
-
await libsaml.isValidXml(xmlString);
|
|
419
|
+
await libsaml.isValidXml(xmlString, false);
|
|
473
420
|
}
|
|
474
421
|
catch (e) {
|
|
475
422
|
return Promise.reject('ERR_INVALID_XML');
|