samlesa 3.4.2 → 3.4.3
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/build/src/binding-artifact.js +101 -129
- package/build/src/entity-idp.js +15 -1
- package/build/src/extractor.js +25 -5
- package/build/src/schemaValidator.js +73 -56
- package/build/src/urn.js +17 -1
- package/package.json +75 -75
- package/types/src/binding-artifact.d.ts +2 -10
- package/types/src/binding-artifact.d.ts.map +1 -1
- package/types/src/entity-idp.d.ts.map +1 -1
- package/types/src/extractor.d.ts +2 -1
- package/types/src/extractor.d.ts.map +1 -1
- package/types/src/schemaValidator.d.ts +18 -1
- package/types/src/schemaValidator.d.ts.map +1 -1
- package/types/src/urn.d.ts +9 -1
- package/types/src/urn.d.ts.map +1 -1
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { checkStatus } from "./flow.js";
|
|
7
7
|
import { ParserType, StatusCode, wording } from './urn.js';
|
|
8
|
+
import * as crypto from "node:crypto";
|
|
8
9
|
import libsaml from './libsaml.js';
|
|
9
10
|
import libsamlSoap from './libsamlSoap.js';
|
|
10
11
|
import utility, { get } from './utility.js';
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
13
|
import { randomUUID } from 'node:crypto';
|
|
14
|
+
import postBinding from './binding-post.js';
|
|
13
15
|
import { artifactResolveFields, extract, loginRequestFields, loginResponseFields, logoutRequestFields, logoutResponseFields } from "./extractor.js";
|
|
14
16
|
import { verifyTime } from "./validator.js";
|
|
15
17
|
import { sendArtifactResolve } from "./soap.js";
|
|
@@ -137,141 +139,111 @@ function soapLoginRequest(referenceTagXPath, entity, customTagReplacement) {
|
|
|
137
139
|
throw new Error('ERR_GENERATE_POST_LOGIN_REQUEST_MISSING_METADATA');
|
|
138
140
|
}
|
|
139
141
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* @param
|
|
143
|
-
* @param
|
|
144
|
-
* @
|
|
145
|
-
* @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
|
|
146
|
-
* @param AttributeStatement
|
|
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
147
|
*/
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
/**
|
|
149
|
+
* 生成符合 SAML 2.0 规范的 Artifact
|
|
150
|
+
* 结构: [TypeCode: 2 bytes] + [EndpointIndex: 2 bytes] + [SourceID: 20 bytes] + [MessageHandle: 20 bytes]
|
|
151
|
+
*/
|
|
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
|
+
async function soapLoginResponse(params) {
|
|
170
|
+
const { entity } = params;
|
|
152
171
|
const metadata = {
|
|
153
172
|
idp: entity.idp.entityMeta,
|
|
154
173
|
sp: entity.sp.entityMeta,
|
|
155
174
|
};
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (metadata && metadata.idp && metadata.sp) {
|
|
159
|
-
const base = metadata.sp.getAssertionConsumerService(binding.post);
|
|
160
|
-
let rawSamlResponse;
|
|
161
|
-
const nowTime = new Date();
|
|
162
|
-
const spEntityID = metadata.sp.getEntityID();
|
|
163
|
-
const oneMinutesLaterTime = new Date(nowTime.getTime());
|
|
164
|
-
oneMinutesLaterTime.setMinutes(oneMinutesLaterTime.getMinutes() + 5);
|
|
165
|
-
const OneMinutesLater = oneMinutesLaterTime.toISOString();
|
|
166
|
-
const now = nowTime.toISOString();
|
|
167
|
-
const acl = metadata.sp.getAssertionConsumerService(binding.post);
|
|
168
|
-
const sessionIndex = 'session' + idpSetting.generateID(); // 这个是当前系统的会话索引,用于单点注销
|
|
169
|
-
const tenHoursLaterTime = new Date(nowTime.getTime());
|
|
170
|
-
tenHoursLaterTime.setHours(tenHoursLaterTime.getHours() + 10);
|
|
171
|
-
const tenHoursLater = tenHoursLaterTime.toISOString();
|
|
172
|
-
const tvalue = {
|
|
173
|
-
ID: id,
|
|
174
|
-
AssertionID: idpSetting.generateID(),
|
|
175
|
-
Destination: base,
|
|
176
|
-
Audience: spEntityID,
|
|
177
|
-
EntityID: spEntityID,
|
|
178
|
-
SubjectRecipient: acl,
|
|
179
|
-
Issuer: metadata.idp.getEntityID(),
|
|
180
|
-
IssueInstant: now,
|
|
181
|
-
AssertionConsumerServiceURL: acl,
|
|
182
|
-
StatusCode: StatusCode.Success,
|
|
183
|
-
// can be customized
|
|
184
|
-
ConditionsNotBefore: now,
|
|
185
|
-
ConditionsNotOnOrAfter: OneMinutesLater,
|
|
186
|
-
SubjectConfirmationDataNotOnOrAfter: OneMinutesLater,
|
|
187
|
-
NameIDFormat: selectedNameIDFormat,
|
|
188
|
-
NameID: user?.NameID || '',
|
|
189
|
-
InResponseTo: get(requestInfo, 'extract.request.id', ''),
|
|
190
|
-
AuthnStatement: `<saml:AuthnStatement AuthnInstant="${now}" SessionNotOnOrAfter="${tenHoursLater}" SessionIndex="${sessionIndex}"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>`,
|
|
191
|
-
AttributeStatement: libsaml.attributeStatementBuilder(AttributeStatement),
|
|
192
|
-
};
|
|
193
|
-
if (idpSetting.loginResponseTemplate && customTagReplacement) {
|
|
194
|
-
const template = customTagReplacement(idpSetting.loginResponseTemplate.context);
|
|
195
|
-
rawSamlResponse = get(template, 'context', null);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
if (requestInfo !== null) {
|
|
199
|
-
tvalue.InResponseTo = requestInfo?.extract?.request?.id ?? '';
|
|
200
|
-
}
|
|
201
|
-
rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLoginResponseTemplate.context, tvalue);
|
|
202
|
-
}
|
|
203
|
-
const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm } = idpSetting;
|
|
204
|
-
const config = {
|
|
205
|
-
privateKey,
|
|
206
|
-
privateKeyPass,
|
|
207
|
-
signatureAlgorithm,
|
|
208
|
-
signingCert: metadata.idp.getX509Certificate('signing'),
|
|
209
|
-
isBase64Output: false,
|
|
210
|
-
};
|
|
211
|
-
// step: sign assertion ? -> encrypted ? -> sign message ?
|
|
212
|
-
if (metadata.sp.isWantAssertionsSigned()) {
|
|
213
|
-
rawSamlResponse = libsaml.constructSAMLSignature({
|
|
214
|
-
...config,
|
|
215
|
-
rawSamlMessage: rawSamlResponse,
|
|
216
|
-
transformationAlgorithms: spSetting.transformationAlgorithms,
|
|
217
|
-
referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']",
|
|
218
|
-
signatureConfig: {
|
|
219
|
-
prefix: 'ds',
|
|
220
|
-
location: {
|
|
221
|
-
reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']",
|
|
222
|
-
action: 'after'
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
// console.debug('after assertion signed', rawSamlResponse);
|
|
228
|
-
// SAML response must be signed sign message first, then encrypt
|
|
229
|
-
if (!encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
|
|
230
|
-
// console.debug('sign then encrypt and sign entire message');
|
|
231
|
-
// @ts-ignore
|
|
232
|
-
rawSamlResponse = libsaml.constructSAMLSignature({
|
|
233
|
-
...config,
|
|
234
|
-
rawSamlMessage: rawSamlResponse,
|
|
235
|
-
isMessageSigned: true,
|
|
236
|
-
transformationAlgorithms: spSetting.transformationAlgorithms,
|
|
237
|
-
signatureConfig: spSetting.signatureConfig || {
|
|
238
|
-
prefix: 'ds',
|
|
239
|
-
location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
// console.debug('after message signed', rawSamlResponse);
|
|
244
|
-
if (idpSetting.isAssertionEncrypted) {
|
|
245
|
-
// console.debug('idp is configured to do encryption');
|
|
246
|
-
const context = await libsaml.encryptAssertion(entity.idp, entity.sp, rawSamlResponse);
|
|
247
|
-
if (encryptThenSign) {
|
|
248
|
-
//need to decode it
|
|
249
|
-
rawSamlResponse = utility.base64Decode(context);
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
return Promise.resolve({ id, context });
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
//sign after encrypting
|
|
256
|
-
if (encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
|
|
257
|
-
// @ts-ignore
|
|
258
|
-
rawSamlResponse = libsaml.constructSAMLSignature({
|
|
259
|
-
...config,
|
|
260
|
-
rawSamlMessage: rawSamlResponse,
|
|
261
|
-
isMessageSigned: true,
|
|
262
|
-
transformationAlgorithms: spSetting.transformationAlgorithms,
|
|
263
|
-
signatureConfig: spSetting.signatureConfig || {
|
|
264
|
-
prefix: 'ds',
|
|
265
|
-
location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
return Promise.resolve({
|
|
270
|
-
id,
|
|
271
|
-
context: utility.base64Encode(rawSamlResponse),
|
|
272
|
-
});
|
|
175
|
+
if (!metadata.idp || !metadata.sp) {
|
|
176
|
+
throw new Error('ERR_GENERATE_ARTIFACT_MISSING_METADATA');
|
|
273
177
|
}
|
|
274
|
-
|
|
178
|
+
// 1. Generate the base SAML Response (Base64 encoded)
|
|
179
|
+
const samlResponseResult = await postBinding.base64LoginResponse(params);
|
|
180
|
+
const samlResponseXml = utility.base64Decode(samlResponseResult.context);
|
|
181
|
+
console.log(samlResponseXml);
|
|
182
|
+
console.log("我日你妈==");
|
|
183
|
+
// 2. Generate the SAML 2.0 Artifact ID
|
|
184
|
+
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)
|
|
193
|
+
const idpSetting = entity.idp.entitySetting;
|
|
194
|
+
const spSetting = entity.sp.entitySetting;
|
|
195
|
+
const config = {
|
|
196
|
+
privateKey: idpSetting.privateKey,
|
|
197
|
+
privateKeyPass: idpSetting.privateKeyPass,
|
|
198
|
+
signatureAlgorithm: idpSetting.requestSignatureAlgorithm,
|
|
199
|
+
signingCert: metadata.idp.getX509Certificate('signing'),
|
|
200
|
+
isBase64Output: false,
|
|
201
|
+
};
|
|
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
|
|
205
|
+
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>
|
|
213
|
+
<samlp:Status>
|
|
214
|
+
<samlp:StatusCode Value="${StatusCode.Success}"/>
|
|
215
|
+
</samlp:Status>
|
|
216
|
+
${samlResponseXml}
|
|
217
|
+
</samlp:ArtifactResponse>`;
|
|
218
|
+
const soapEnvelope = `
|
|
219
|
+
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
220
|
+
<soap:Header/>
|
|
221
|
+
<soap:Body>${soapBodyContent}</soap:Body>
|
|
222
|
+
</soap:Envelope>`;
|
|
223
|
+
// 6. Sign the SOAP Envelope
|
|
224
|
+
// Reference the Body element for signature
|
|
225
|
+
// @ts-ignore
|
|
226
|
+
// @ts-ignore
|
|
227
|
+
const signedSoapEnvelope = libsaml.constructSAMLSignature({
|
|
228
|
+
...config,
|
|
229
|
+
rawSamlMessage: soapEnvelope,
|
|
230
|
+
transformationAlgorithms: spSetting.transformationAlgorithms,
|
|
231
|
+
isMessageSigned: true,
|
|
232
|
+
referenceTagXPath: "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']",
|
|
233
|
+
signatureConfig: {
|
|
234
|
+
prefix: 'ds',
|
|
235
|
+
location: {
|
|
236
|
+
// Place signature inside the ArtifactResponse element, before the Status element
|
|
237
|
+
reference: "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']/*[local-name(.)='Status']",
|
|
238
|
+
action: 'before'
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
// 7. Return the Artifact ID and the Base64 encoded signed SOAP message
|
|
243
|
+
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
|
|
246
|
+
};
|
|
275
247
|
}
|
|
276
248
|
async function parseLoginRequestResolve(params) {
|
|
277
249
|
let { idp, sp, xml, } = params;
|
package/build/src/entity-idp.js
CHANGED
|
@@ -10,6 +10,7 @@ import { namespace } from './urn.js';
|
|
|
10
10
|
import postBinding from './binding-post.js';
|
|
11
11
|
import redirectBinding from './binding-redirect.js';
|
|
12
12
|
import simpleSignBinding from './binding-simplesign.js';
|
|
13
|
+
import artifactBinding from './binding-artifact.js';
|
|
13
14
|
import { flow } from './flow.js';
|
|
14
15
|
/**
|
|
15
16
|
* Identity provider can be configured using either metadata importing or idpSetting
|
|
@@ -36,7 +37,7 @@ export class IdentityProvider extends Entity {
|
|
|
36
37
|
* @param params
|
|
37
38
|
*/
|
|
38
39
|
async createLoginResponse(params) {
|
|
39
|
-
const bindType = params?.binding ?? 'post';
|
|
40
|
+
const bindType = (params?.binding ?? 'post');
|
|
40
41
|
const { sp, requestInfo = {}, user = {}, customTagReplacement, encryptThenSign = false, relayState = '', AttributeStatement = [], idpInit = false, } = params;
|
|
41
42
|
const protocol = namespace.binding[bindType];
|
|
42
43
|
// can support post, redirect and post simple sign bindings for login response
|
|
@@ -66,6 +67,19 @@ export class IdentityProvider extends Entity {
|
|
|
66
67
|
idp: this,
|
|
67
68
|
sp,
|
|
68
69
|
}, user, relayState, customTagReplacement, AttributeStatement);
|
|
70
|
+
case namespace.binding.artifact:
|
|
71
|
+
return artifactBinding.soapLoginResponse({
|
|
72
|
+
requestInfo,
|
|
73
|
+
entity: {
|
|
74
|
+
idp: this,
|
|
75
|
+
sp,
|
|
76
|
+
},
|
|
77
|
+
user,
|
|
78
|
+
customTagReplacement,
|
|
79
|
+
encryptThenSign,
|
|
80
|
+
AttributeStatement,
|
|
81
|
+
idpInit,
|
|
82
|
+
});
|
|
69
83
|
default:
|
|
70
84
|
throw new Error('ERR_CREATE_RESPONSE_UNDEFINED_BINDING');
|
|
71
85
|
}
|
package/build/src/extractor.js
CHANGED
|
@@ -117,10 +117,27 @@ export const loginRequestFields = [
|
|
|
117
117
|
{ key: 'signature', localPath: ['AuthnRequest', 'Signature'], attributes: [], context: true }
|
|
118
118
|
];
|
|
119
119
|
export const artifactResolveFields = [
|
|
120
|
-
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
{
|
|
121
|
+
key: 'request',
|
|
122
|
+
localPath: ['Envelope', 'Body', 'ArtifactResolve'],
|
|
123
|
+
attributes: ['ID', 'IssueInstant', 'Version', 'Destination']
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
key: 'issuer',
|
|
127
|
+
localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Issuer'],
|
|
128
|
+
attributes: []
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
key: 'artifact',
|
|
132
|
+
localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Artifact'],
|
|
133
|
+
attributes: []
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
key: 'signature',
|
|
137
|
+
localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Signature'],
|
|
138
|
+
attributes: [],
|
|
139
|
+
context: true
|
|
140
|
+
},
|
|
124
141
|
];
|
|
125
142
|
export const artifactResponseFields = [
|
|
126
143
|
{ key: 'request', localPath: ['Envelope', 'Body', 'ArtifactResolve'], attributes: ['ID', 'IssueInstant', 'Version'] },
|
|
@@ -1487,6 +1504,9 @@ export function extractSp(context) {
|
|
|
1487
1504
|
export function extractAuthRequest(context) {
|
|
1488
1505
|
return extract(context, loginRequestFields);
|
|
1489
1506
|
}
|
|
1490
|
-
export function extractResponse(context
|
|
1507
|
+
export function extractResponse(context) {
|
|
1491
1508
|
return extractSpToll(context, loginResponseFieldsFullList);
|
|
1492
1509
|
}
|
|
1510
|
+
export function extractArtifactResolve(context) {
|
|
1511
|
+
return extract(context, artifactResolveFields);
|
|
1512
|
+
}
|
|
@@ -5,7 +5,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { DOMParser } from '@xmldom/xmldom';
|
|
6
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
7
|
const __dirname = path.dirname(__filename);
|
|
8
|
-
|
|
8
|
+
// 定义各个场景所需的 schema 文件列表(保持不变)
|
|
9
|
+
const normalSchemas = [
|
|
9
10
|
'saml-schema-protocol-2.0.xsd',
|
|
10
11
|
'saml-schema-assertion-2.0.xsd',
|
|
11
12
|
'xmldsig-core-schema.xsd',
|
|
@@ -15,33 +16,31 @@ let normal = [
|
|
|
15
16
|
'saml-schema-ecp-2.0.xsd',
|
|
16
17
|
'saml-schema-dce-2.0.xsd'
|
|
17
18
|
];
|
|
18
|
-
|
|
19
|
+
const soapSchemas = [
|
|
19
20
|
'soap-envelope.xsd',
|
|
20
21
|
'xml.xsd',
|
|
21
|
-
// 2. SOAP核心模式(所有SOAP消息的基础)
|
|
22
|
-
// 3. XML签名模式(SAML签名的前置依赖)
|
|
23
22
|
'xmldsig-core-schema.xsd',
|
|
24
|
-
// 4. XML加密模式(SAML断言加密的前置依赖)
|
|
25
23
|
'xenc-schema.xsd',
|
|
26
24
|
'xenc-schema-11.xsd',
|
|
27
|
-
|
|
28
|
-
'saml-schema-assertion-2.0.xsd', // 断言定义
|
|
29
|
-
// 6. SAML协议模式(依赖断言模式)
|
|
25
|
+
'saml-schema-assertion-2.0.xsd',
|
|
30
26
|
'saml-schema-protocol-2.0.xsd',
|
|
31
|
-
|
|
32
|
-
'saml-schema-
|
|
33
|
-
'saml-schema-
|
|
34
|
-
'saml-schema-dce-2.0.xsd' // DCE扩展
|
|
27
|
+
'saml-schema-metadata-2.0.xsd',
|
|
28
|
+
'saml-schema-ecp-2.0.xsd',
|
|
29
|
+
'saml-schema-dce-2.0.xsd'
|
|
35
30
|
];
|
|
36
|
-
|
|
37
|
-
'saml-schema-metadata-2.0.xsd',
|
|
31
|
+
const metadataSchemas = [
|
|
32
|
+
'saml-schema-metadata-2.0.xsd',
|
|
38
33
|
'xml.xsd',
|
|
39
34
|
'saml-schema-assertion-2.0.xsd',
|
|
40
35
|
'xmldsig-core-schema.xsd',
|
|
41
36
|
'xenc-schema.xsd',
|
|
42
|
-
'xenc-schema-11.xsd'
|
|
37
|
+
'xenc-schema-11.xsd'
|
|
43
38
|
];
|
|
44
|
-
|
|
39
|
+
/**
|
|
40
|
+
* 检测 XML 字符串中是否存在 XXE 攻击指示器
|
|
41
|
+
* @param samlString 待检测的 XML 字符串
|
|
42
|
+
* @returns 如果存在可疑模式则返回匹配详情,否则返回 null
|
|
43
|
+
*/
|
|
45
44
|
function detectXXEIndicators(samlString) {
|
|
46
45
|
const xxePatterns = [
|
|
47
46
|
/<!DOCTYPE\s[^>]*>/i,
|
|
@@ -64,94 +63,112 @@ function detectXXEIndicators(samlString) {
|
|
|
64
63
|
});
|
|
65
64
|
return Object.keys(matches).length > 0 ? matches : null;
|
|
66
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* 加载指定的 schema 文件内容
|
|
68
|
+
* @param schemaNames 文件名数组
|
|
69
|
+
* @returns 包含 fileName 和 contents 的对象数组
|
|
70
|
+
*/
|
|
71
|
+
async function loadSchemas(schemaNames) {
|
|
72
|
+
const schemaPath = path.resolve(__dirname, 'schema');
|
|
73
|
+
return Promise.all(schemaNames.map(async (file) => ({
|
|
74
|
+
fileName: file,
|
|
75
|
+
contents: await fs.promises.readFile(`${schemaPath}/${file}`, 'utf-8')
|
|
76
|
+
})));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 验证 SAML 消息(普通或 SOAP)
|
|
80
|
+
* @param xml XML 字符串
|
|
81
|
+
* @param isSoap 是否为 SOAP 消息,默认 false
|
|
82
|
+
* @returns true 表示验证通过,否则抛出错误
|
|
83
|
+
* @throws 当检测到 XXE 或验证失败时抛出错误
|
|
84
|
+
*/
|
|
67
85
|
export const validate = async (xml, isSoap = false) => {
|
|
86
|
+
// 检测 XXE 攻击
|
|
68
87
|
const indicators = detectXXEIndicators(xml);
|
|
69
88
|
if (indicators) {
|
|
70
89
|
throw new Error('ERR_EXCEPTION_VALIDATE_XML');
|
|
71
90
|
}
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
fileName: file,
|
|
76
|
-
contents: await fs.promises.readFile(`${schemaPath}/${file}`, 'utf-8')
|
|
77
|
-
})));
|
|
91
|
+
// 根据类型选择对应的 schema 列表(避免全局变量并发问题)
|
|
92
|
+
const schemaList = isSoap ? soapSchemas : normalSchemas;
|
|
93
|
+
const schemas = await loadSchemas(schemaList);
|
|
78
94
|
try {
|
|
79
95
|
const validationResult = await validateXML({
|
|
80
|
-
xml: [
|
|
81
|
-
{
|
|
82
|
-
fileName: 'content.xml',
|
|
83
|
-
contents: xml,
|
|
84
|
-
},
|
|
85
|
-
],
|
|
96
|
+
xml: [{ fileName: 'content.xml', contents: xml }],
|
|
86
97
|
extension: 'schema',
|
|
87
|
-
schema: [
|
|
88
|
-
preload: [
|
|
98
|
+
schema: [schemas[0]], // 第一个 schema 作为主入口
|
|
99
|
+
preload: [schemas[0], ...schemas.slice(1)], // 其余作为预加载
|
|
89
100
|
});
|
|
90
101
|
if (validationResult.valid) {
|
|
91
102
|
return true;
|
|
92
103
|
}
|
|
104
|
+
// 验证失败,抛出错误对象
|
|
93
105
|
throw validationResult.errors;
|
|
94
106
|
}
|
|
95
107
|
catch (error) {
|
|
96
|
-
|
|
108
|
+
// 保留原始错误信息
|
|
109
|
+
throw error;
|
|
97
110
|
}
|
|
98
111
|
};
|
|
112
|
+
/**
|
|
113
|
+
* 验证 SAML 元数据,并可选择解析元数据类型
|
|
114
|
+
* @param xml XML 字符串
|
|
115
|
+
* @param isParse 是否解析并返回元数据类型,默认 false
|
|
116
|
+
* @returns 验证通过时:若 isParse 为 true 返回 { isValid: true, metadataType: string },否则返回 true;
|
|
117
|
+
* 验证失败时返回 Error 对象(保持原行为)
|
|
118
|
+
*/
|
|
99
119
|
export const validateMetadata = async (xml, isParse = false) => {
|
|
120
|
+
// 检测 XXE 攻击
|
|
100
121
|
const indicators = detectXXEIndicators(xml);
|
|
101
122
|
if (indicators) {
|
|
102
123
|
throw new Error('ERR_EXCEPTION_VALIDATE_XML');
|
|
103
124
|
}
|
|
104
|
-
schemas =
|
|
105
|
-
const schemaPath = path.resolve(__dirname, 'schema');
|
|
106
|
-
const [xmlParse, ...preload] = await Promise.all(schemas.map(async (file) => ({
|
|
107
|
-
fileName: file,
|
|
108
|
-
contents: await fs.promises.readFile(`${schemaPath}/${file}`, 'utf-8')
|
|
109
|
-
})));
|
|
125
|
+
const schemas = await loadSchemas(metadataSchemas);
|
|
110
126
|
try {
|
|
127
|
+
// @ts-ignore
|
|
111
128
|
const validationResult = await validateXML({
|
|
112
|
-
xml: [
|
|
113
|
-
{
|
|
114
|
-
fileName: 'content.xml',
|
|
115
|
-
contents: xml,
|
|
116
|
-
},
|
|
117
|
-
],
|
|
129
|
+
xml: [{ fileName: 'content.xml', contents: xml }],
|
|
118
130
|
extension: 'schema',
|
|
119
|
-
schema: [
|
|
120
|
-
preload: [
|
|
131
|
+
schema: [schemas[0]],
|
|
132
|
+
preload: [schemas[0], ...schemas.slice(1)],
|
|
121
133
|
});
|
|
122
134
|
if (validationResult.valid) {
|
|
123
135
|
if (isParse) {
|
|
124
|
-
// 解析 XML
|
|
136
|
+
// 解析 XML 并确定元数据类型
|
|
125
137
|
const parser = new DOMParser();
|
|
126
138
|
const xmlDoc = parser.parseFromString(xml, 'text/xml');
|
|
127
|
-
//
|
|
139
|
+
// 检查解析错误(防御性编程)
|
|
140
|
+
const parserError = xmlDoc.getElementsByTagName('parsererror');
|
|
141
|
+
if (parserError.length > 0) {
|
|
142
|
+
// 解析失败,视为无效 XML,返回错误对象(与原逻辑一致)
|
|
143
|
+
return new Error('XML parsing failed');
|
|
144
|
+
}
|
|
128
145
|
const idpDescriptor = xmlDoc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:metadata', 'IDPSSODescriptor');
|
|
129
146
|
const spDescriptor = xmlDoc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:metadata', 'SPSSODescriptor');
|
|
130
|
-
// 判断元数据类型
|
|
131
147
|
let metadataType;
|
|
132
148
|
if (idpDescriptor.length > 0 && spDescriptor.length > 0) {
|
|
133
|
-
metadataType = 'both';
|
|
149
|
+
metadataType = 'both';
|
|
134
150
|
}
|
|
135
151
|
else if (idpDescriptor.length > 0) {
|
|
136
|
-
metadataType = 'IdP';
|
|
152
|
+
metadataType = 'IdP';
|
|
137
153
|
}
|
|
138
154
|
else if (spDescriptor.length > 0) {
|
|
139
|
-
metadataType = 'SP';
|
|
155
|
+
metadataType = 'SP';
|
|
140
156
|
}
|
|
141
157
|
else {
|
|
142
|
-
metadataType = 'unknown';
|
|
158
|
+
metadataType = 'unknown';
|
|
143
159
|
}
|
|
144
|
-
// 返回验证结果和元数据类型
|
|
145
160
|
return {
|
|
146
161
|
isValid: true,
|
|
147
|
-
metadataType
|
|
162
|
+
metadataType
|
|
148
163
|
};
|
|
149
164
|
}
|
|
150
165
|
return true;
|
|
151
166
|
}
|
|
152
|
-
|
|
167
|
+
// 验证失败,返回错误对象(保持原行为)
|
|
168
|
+
return validationResult.errors;
|
|
153
169
|
}
|
|
154
170
|
catch (error) {
|
|
155
|
-
|
|
171
|
+
// 捕获其他异常(如文件读取失败)并返回错误对象
|
|
172
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
156
173
|
}
|
|
157
174
|
};
|
package/build/src/urn.js
CHANGED
|
@@ -10,6 +10,16 @@ export var BindingNamespace;
|
|
|
10
10
|
BindingNamespace["SimpleSign"] = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign";
|
|
11
11
|
BindingNamespace["Artifact"] = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact";
|
|
12
12
|
})(BindingNamespace || (BindingNamespace = {}));
|
|
13
|
+
export const NamespaceBindingMap = {
|
|
14
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': 'redirect',
|
|
15
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': 'post',
|
|
16
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign': 'simplesign',
|
|
17
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact': 'artifact'
|
|
18
|
+
};
|
|
19
|
+
// 可选:添加反向查找函数
|
|
20
|
+
function getBindingName(uri) {
|
|
21
|
+
return NamespaceBindingMap[uri];
|
|
22
|
+
}
|
|
13
23
|
export var MessageSignatureOrder;
|
|
14
24
|
(function (MessageSignatureOrder) {
|
|
15
25
|
MessageSignatureOrder["STE"] = "sign-then-encrypt";
|
|
@@ -51,6 +61,12 @@ const namespace = {
|
|
|
51
61
|
artifact: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact',
|
|
52
62
|
soap: 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP',
|
|
53
63
|
},
|
|
64
|
+
bindMap: {
|
|
65
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': 'redirect',
|
|
66
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': 'post',
|
|
67
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign': 'simplesign',
|
|
68
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact': 'artifact'
|
|
69
|
+
},
|
|
54
70
|
names: {
|
|
55
71
|
protocol: 'urn:oasis:names:tc:SAML:2.0:protocol',
|
|
56
72
|
assertion: 'urn:oasis:names:tc:SAML:2.0:assertion',
|
|
@@ -306,4 +322,4 @@ const elementsOrder = {
|
|
|
306
322
|
onelogin: ['KeyDescriptor', 'NameIDFormat', 'ArtifactResolutionService', 'SingleLogoutService', 'AssertionConsumerService', 'AttributeConsumingService'],
|
|
307
323
|
shibboleth: ['KeyDescriptor', 'ArtifactResolutionService', 'SingleLogoutService', 'NameIDFormat', 'AssertionConsumerService', 'AttributeConsumingService',],
|
|
308
324
|
};
|
|
309
|
-
export { namespace, tags, algorithms, wording, elementsOrder, messageConfigurations };
|
|
325
|
+
export { namespace, tags, algorithms, wording, elementsOrder, messageConfigurations, getBindingName };
|
package/package.json
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "samlesa",
|
|
3
|
-
"version": "3.4.
|
|
4
|
-
"description": "High-level API for Single Sign On (SAML 2.0) baseed on samlify ",
|
|
5
|
-
"main": "build/index.js",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"nodejs",
|
|
8
|
-
"saml2",
|
|
9
|
-
"sso",
|
|
10
|
-
"slo",
|
|
11
|
-
"metadata"
|
|
12
|
-
],
|
|
13
|
-
"type": "module",
|
|
14
|
-
"typings": "types/index.d.ts",
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc && copyfiles -u 1 src/schema/**/* build/src",
|
|
17
|
-
"docs": "docsify serve -o docs",
|
|
18
|
-
"lint": "tslint -p .",
|
|
19
|
-
"lint:fix": "tslint -p . --fix",
|
|
20
|
-
"test": "vitest",
|
|
21
|
-
"test:watch": "vitest --watch",
|
|
22
|
-
"test:coverage": "vitest run --coverage",
|
|
23
|
-
"hooks:postinstall": "mklink /J .git\\hooks\\pre-commit .pre-commit.sh || copy .pre-commit.sh .git\\hooks\\pre-commit"
|
|
24
|
-
},
|
|
25
|
-
"exports": {
|
|
26
|
-
".": {
|
|
27
|
-
"types": "./types/index.d.ts",
|
|
28
|
-
"import": "./build/index.js"
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
"files": [
|
|
32
|
-
"build",
|
|
33
|
-
"types"
|
|
34
|
-
],
|
|
35
|
-
"contributors": [
|
|
36
|
-
"Veclea <vemocle@gmail.com>"
|
|
37
|
-
],
|
|
38
|
-
"author": "Veclea",
|
|
39
|
-
"repository": {
|
|
40
|
-
"url": "https://github.com/Veclea/samlify.git",
|
|
41
|
-
"type": "git"
|
|
42
|
-
},
|
|
43
|
-
"license": "MIT",
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"@xmldom/xmldom": "^0.9.8",
|
|
46
|
-
"axios": "^1.13.
|
|
47
|
-
"camelcase": "^9.0.0",
|
|
48
|
-
"cross-env": "^10.1.0",
|
|
49
|
-
"iconv-lite": "^0.7.2",
|
|
50
|
-
"ts-node": "^10.9.2",
|
|
51
|
-
"vite-tsconfig-paths": "^6.1.1",
|
|
52
|
-
"xml": "^1.0.1",
|
|
53
|
-
"xml-crypto": "^6.1.2",
|
|
54
|
-
"xml-crypto-next": "^7.0.4",
|
|
55
|
-
"xml-encryption-next": "^4.6.0",
|
|
56
|
-
"xml-escape": "^1.1.0",
|
|
57
|
-
"xml2js": "^0.6.2",
|
|
58
|
-
"xmllint-wasm": "^5.
|
|
59
|
-
"xpath": "^0.0.34"
|
|
60
|
-
},
|
|
61
|
-
"devDependencies": {
|
|
62
|
-
"@types/node": "^25.
|
|
63
|
-
"@types/pako": "2.0.4",
|
|
64
|
-
"@types/uuid": "11.0.0",
|
|
65
|
-
"@vitest/coverage-istanbul": "^4.
|
|
66
|
-
"@vitest/coverage-v8": "4.
|
|
67
|
-
"copyfiles": "^2.4.1",
|
|
68
|
-
"coveralls": "^3.1.1",
|
|
69
|
-
"esbuild": "^0.27.
|
|
70
|
-
"jsdom": "^
|
|
71
|
-
"timekeeper": "^2.3.1",
|
|
72
|
-
"typescript": "
|
|
73
|
-
"vitest": "^4.
|
|
74
|
-
}
|
|
75
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "samlesa",
|
|
3
|
+
"version": "3.4.3",
|
|
4
|
+
"description": "High-level API for Single Sign On (SAML 2.0) baseed on samlify ",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"nodejs",
|
|
8
|
+
"saml2",
|
|
9
|
+
"sso",
|
|
10
|
+
"slo",
|
|
11
|
+
"metadata"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"typings": "types/index.d.ts",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc && copyfiles -u 1 src/schema/**/* build/src",
|
|
17
|
+
"docs": "docsify serve -o docs",
|
|
18
|
+
"lint": "tslint -p .",
|
|
19
|
+
"lint:fix": "tslint -p . --fix",
|
|
20
|
+
"test": "vitest",
|
|
21
|
+
"test:watch": "vitest --watch",
|
|
22
|
+
"test:coverage": "vitest run --coverage",
|
|
23
|
+
"hooks:postinstall": "mklink /J .git\\hooks\\pre-commit .pre-commit.sh || copy .pre-commit.sh .git\\hooks\\pre-commit"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./types/index.d.ts",
|
|
28
|
+
"import": "./build/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"build",
|
|
33
|
+
"types"
|
|
34
|
+
],
|
|
35
|
+
"contributors": [
|
|
36
|
+
"Veclea <vemocle@gmail.com>"
|
|
37
|
+
],
|
|
38
|
+
"author": "Veclea",
|
|
39
|
+
"repository": {
|
|
40
|
+
"url": "https://github.com/Veclea/samlify.git",
|
|
41
|
+
"type": "git"
|
|
42
|
+
},
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@xmldom/xmldom": "^0.9.8",
|
|
46
|
+
"axios": "^1.13.6",
|
|
47
|
+
"camelcase": "^9.0.0",
|
|
48
|
+
"cross-env": "^10.1.0",
|
|
49
|
+
"iconv-lite": "^0.7.2",
|
|
50
|
+
"ts-node": "^10.9.2",
|
|
51
|
+
"vite-tsconfig-paths": "^6.1.1",
|
|
52
|
+
"xml": "^1.0.1",
|
|
53
|
+
"xml-crypto": "^6.1.2",
|
|
54
|
+
"xml-crypto-next": "^7.0.4",
|
|
55
|
+
"xml-encryption-next": "^4.6.0",
|
|
56
|
+
"xml-escape": "^1.1.0",
|
|
57
|
+
"xml2js": "^0.6.2",
|
|
58
|
+
"xmllint-wasm": "^5.2.0",
|
|
59
|
+
"xpath": "^0.0.34"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^25.5.0",
|
|
63
|
+
"@types/pako": "2.0.4",
|
|
64
|
+
"@types/uuid": "11.0.0",
|
|
65
|
+
"@vitest/coverage-istanbul": "^4.1.2",
|
|
66
|
+
"@vitest/coverage-v8": "4.1.2",
|
|
67
|
+
"copyfiles": "^2.4.1",
|
|
68
|
+
"coveralls": "^3.1.1",
|
|
69
|
+
"esbuild": "^0.27.4",
|
|
70
|
+
"jsdom": "^29.0.1",
|
|
71
|
+
"timekeeper": "^2.3.1",
|
|
72
|
+
"typescript": "6.0.2",
|
|
73
|
+
"vitest": "^4.1.2"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BindingContext } from './entity.js';
|
|
2
2
|
import { IdentityProviderConstructor as IdentityProvider, ServiceProviderConstructor as ServiceProvider } from "./types.js";
|
|
3
|
+
import { Base64LoginResponseParams } from "./types.js";
|
|
3
4
|
/**
|
|
4
5
|
* @desc Generate a base64 encoded login request
|
|
5
6
|
* @param {string} referenceTagXPath reference uri
|
|
@@ -7,16 +8,7 @@ import { IdentityProviderConstructor as IdentityProvider, ServiceProviderConstru
|
|
|
7
8
|
* @param customTagReplacement
|
|
8
9
|
*/
|
|
9
10
|
declare function soapLoginRequest(referenceTagXPath: string, entity: any, customTagReplacement?: (template: string) => BindingContext): any;
|
|
10
|
-
|
|
11
|
-
* @desc Generate a base64 encoded login response
|
|
12
|
-
* @param {object} requestInfo corresponding request, used to obtain the id
|
|
13
|
-
* @param {object} entity object includes both idp and sp
|
|
14
|
-
* @param {object} user current logged user (e.g. req.user)
|
|
15
|
-
* @param {function} customTagReplacement used when developers have their own login response template
|
|
16
|
-
* @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
|
|
17
|
-
* @param AttributeStatement
|
|
18
|
-
*/
|
|
19
|
-
declare function soapLoginResponse(requestInfo: any | undefined, entity: any, user?: any, customTagReplacement?: (template: string) => BindingContext, encryptThenSign?: boolean, AttributeStatement?: never[]): Promise<BindingContext>;
|
|
11
|
+
declare function soapLoginResponse(params: Base64LoginResponseParams): Promise<BindingContext>;
|
|
20
12
|
declare function parseLoginRequestResolve(params: {
|
|
21
13
|
idp: IdentityProvider;
|
|
22
14
|
sp: ServiceProvider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding-artifact.d.ts","sourceRoot":"","sources":["../../src/binding-artifact.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"binding-artifact.d.ts","sourceRoot":"","sources":["../../src/binding-artifact.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAShD,OAAO,EACH,2BAA2B,IAAI,gBAAgB,EAC/C,0BAA0B,IAAI,eAAe,EAChD,MAAM,YAAY,CAAC;AAiBpB,OAAO,EAAC,yBAAyB,EAAC,MAAM,YAAY,CAAC;AAwBrD;;;;;GAKG;AACH,iBAAS,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,GAAG,GAAG,CAkGlI;AAqCD,iBAAe,iBAAiB,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0F3F;AAID,iBAAe,wBAAwB,CAAC,MAAM,EAAE;IAC5C,GAAG,EAAE,gBAAgB,CAAC;IACtB,EAAE,EAAE,eAAe,CAAC;IACpB,GAAG,EAAE,MAAM,CAAA;CACd;;;GAqDA;AAED,iBAAe,yBAAyB,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,gBAAgB,CAAC;IAAC,EAAE,EAAE,eAAe,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;;;GAgP3G;AAED,QAAA,MAAM,mBAAmB;;;;;CAOxB,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-idp.d.ts","sourceRoot":"","sources":["../../src/entity-idp.ts"],"names":[],"mappings":"AAYA,OAAO,MAAM,EAAE,EAAC,KAAK,gBAAgB,EAAC,MAAM,aAAa,CAAC;AAC1D,OAAO,EACH,0BAA0B,IAAI,eAAe,EAE7C,wBAAwB,EACxB,KAAK,wBAAwB,EAChC,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"entity-idp.d.ts","sourceRoot":"","sources":["../../src/entity-idp.ts"],"names":[],"mappings":"AAYA,OAAO,MAAM,EAAE,EAAC,KAAK,gBAAgB,EAAC,MAAM,aAAa,CAAC;AAC1D,OAAO,EACH,0BAA0B,IAAI,eAAe,EAE7C,wBAAwB,EACxB,KAAK,wBAAwB,EAChC,MAAM,YAAY,CAAC;AAOpB,OAAO,EAAO,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAEhD;;GAEG;AACH,MAAM,CAAC,OAAO,WAAW,KAAK,EAAE,wBAAwB,oBAEvD;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,MAAM;IAEhC,UAAU,EAAE,wBAAwB,CAAC;gBAEjC,UAAU,EAAE,wBAAwB;IAWhD;;;OAGG;IACU,mBAAmB,CAAC,MAAM,EAAE;QACrC,EAAE,EAAE,eAAe,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAClC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC3B,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,CAAC;QAC5D,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,kBAAkB,CAAC,EAAE,EAAE,CAAC;QACxB,OAAO,CAAC,EAAE,KAAK,CAAC;KAEnB;IAiED;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB;CAYhF"}
|
package/types/src/extractor.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export declare function extractSpData(context: string): any;
|
|
|
32
32
|
export declare function extractIdp(context: string): any;
|
|
33
33
|
export declare function extractSp(context: string): any;
|
|
34
34
|
export declare function extractAuthRequest(context: string): any;
|
|
35
|
-
export declare function extractResponse(context: string
|
|
35
|
+
export declare function extractResponse(context: string): any;
|
|
36
|
+
export declare function extractArtifactResolve(context: string): any;
|
|
36
37
|
export {};
|
|
37
38
|
//# sourceMappingURL=extractor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/extractor.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAEnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,MAAM,MAAM,eAAe,GAAG,cAAc,EAAE,CAAC;AA4B/C,eAAO,MAAM,kBAAkB,EAAE,eAsFhC,CAAC;AAKF,eAAO,MAAM,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/extractor.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAEnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,MAAM,MAAM,eAAe,GAAG,cAAc,EAAE,CAAC;AA4B/C,eAAO,MAAM,kBAAkB,EAAE,eAsFhC,CAAC;AAKF,eAAO,MAAM,qBAAqB,EAAE,eAsBnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAKpC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,eAGvC,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,eAG/C,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eAGxC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,KAAK,eAAe,CAWrE,CAAC;AAqMF,eAAO,MAAM,mBAAmB,EAAE,eAMjC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAIlC,CAAC;AAKF,eAAO,MAAM,iBAAiB,EAAE,eAiI/B,CAAC;AAOF,eAAO,MAAM,gBAAgB,EAAE,eAyL9B,CAAC;AAEF,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,OAiN/D;AASD,eAAO,MAAM,2BAA2B,EAAE,eAkZzC,CAAC;AAIF;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,OAkRrE;AAKD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,OAE5C;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,OAEzC;AAGD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,OAExC;AACD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,OAEjD;AACD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,OAE9C;AACD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,OAErD"}
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 验证 SAML 消息(普通或 SOAP)
|
|
3
|
+
* @param xml XML 字符串
|
|
4
|
+
* @param isSoap 是否为 SOAP 消息,默认 false
|
|
5
|
+
* @returns true 表示验证通过,否则抛出错误
|
|
6
|
+
* @throws 当检测到 XXE 或验证失败时抛出错误
|
|
7
|
+
*/
|
|
1
8
|
export declare const validate: (xml: string, isSoap?: boolean) => Promise<boolean>;
|
|
2
|
-
|
|
9
|
+
/**
|
|
10
|
+
* 验证 SAML 元数据,并可选择解析元数据类型
|
|
11
|
+
* @param xml XML 字符串
|
|
12
|
+
* @param isParse 是否解析并返回元数据类型,默认 false
|
|
13
|
+
* @returns 验证通过时:若 isParse 为 true 返回 { isValid: true, metadataType: string },否则返回 true;
|
|
14
|
+
* 验证失败时返回 Error 对象(保持原行为)
|
|
15
|
+
*/
|
|
16
|
+
export declare const validateMetadata: (xml: string, isParse?: boolean) => Promise<true | Error | readonly import("xmllint-wasm").XMLValidationError[] | {
|
|
17
|
+
isValid: boolean;
|
|
18
|
+
metadataType: string;
|
|
19
|
+
}>;
|
|
3
20
|
//# sourceMappingURL=schemaValidator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemaValidator.d.ts","sourceRoot":"","sources":["../../src/schemaValidator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schemaValidator.d.ts","sourceRoot":"","sources":["../../src/schemaValidator.ts"],"names":[],"mappings":"AAsFA;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAU,KAAK,MAAM,EAAE,SAAQ,OAAe,qBA4BlE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAU,KAAK,MAAM,EAAE,UAAS,OAAe;;;EA+D3E,CAAC"}
|
package/types/src/urn.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export declare enum BindingNamespace {
|
|
|
9
9
|
SimpleSign = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign",
|
|
10
10
|
Artifact = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
|
|
11
11
|
}
|
|
12
|
+
export declare const NamespaceBindingMap: Record<string, 'redirect' | 'post' | 'simplesign' | 'artifact'>;
|
|
13
|
+
declare function getBindingName(uri: string): 'redirect' | 'post' | 'simplesign' | 'artifact' | undefined;
|
|
12
14
|
export declare enum MessageSignatureOrder {
|
|
13
15
|
STE = "sign-then-encrypt",
|
|
14
16
|
ETS = "encrypt-then-sign"
|
|
@@ -46,6 +48,12 @@ declare const namespace: {
|
|
|
46
48
|
artifact: string;
|
|
47
49
|
soap: string;
|
|
48
50
|
};
|
|
51
|
+
bindMap: {
|
|
52
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': string;
|
|
53
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': string;
|
|
54
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign': string;
|
|
55
|
+
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact': string;
|
|
56
|
+
};
|
|
49
57
|
names: {
|
|
50
58
|
protocol: string;
|
|
51
59
|
assertion: string;
|
|
@@ -260,5 +268,5 @@ declare const elementsOrder: {
|
|
|
260
268
|
onelogin: string[];
|
|
261
269
|
shibboleth: string[];
|
|
262
270
|
};
|
|
263
|
-
export { namespace, tags, algorithms, wording, elementsOrder, messageConfigurations };
|
|
271
|
+
export { namespace, tags, algorithms, wording, elementsOrder, messageConfigurations, getBindingName };
|
|
264
272
|
//# sourceMappingURL=urn.d.ts.map
|
package/types/src/urn.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urn.d.ts","sourceRoot":"","sources":["../../src/urn.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oBAAY,gBAAgB;IAC1B,QAAQ,uDAAuD;IAC/D,IAAI,mDAAmD;IACvD,UAAU,8DAA8D;IACxE,QAAQ,uDAAuD;CAChE;AAED,oBAAY,qBAAqB;IAC/B,GAAG,sBAAsB;IACzB,GAAG,sBAAsB;CAC1B;AAED,oBAAY,UAAU;IAEpB,OAAO,+CAA+C;IACtD,SAAS,iDAAiD;IAC1D,SAAS,iDAAiD;IAC1D,eAAe,uDAAuD;IAEtE,UAAU,mDAAmD;IAC7D,sBAAsB,8DAA8D;IACpF,mBAAmB,2DAA2D;IAC9E,cAAc,sDAAsD;IACpE,cAAc,sDAAsD;IACpE,SAAS,iDAAiD;IAC1D,cAAc,sDAAsD;IACpE,aAAa,qDAAqD;IAClE,kBAAkB,0DAA0D;IAC5E,aAAa,qDAAqD;IAClE,kBAAkB,0DAA0D;IAC5E,wBAAwB,gEAAgE;IACxF,qBAAqB,6DAA6D;IAClF,oBAAoB,4DAA4D;IAChF,qBAAqB,6DAA6D;IAClF,gBAAgB,wDAAwD;IACxE,kBAAkB,0DAA0D;IAC5E,gBAAgB,wDAAwD;IACxE,kBAAkB,0DAA0D;CAC7E;AAED,QAAA,MAAM,SAAS
|
|
1
|
+
{"version":3,"file":"urn.d.ts","sourceRoot":"","sources":["../../src/urn.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oBAAY,gBAAgB;IAC1B,QAAQ,uDAAuD;IAC/D,IAAI,mDAAmD;IACvD,UAAU,8DAA8D;IACxE,QAAQ,uDAAuD;CAChE;AACD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,CAK/F,CAAC;AAGD,iBAAS,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAEjG;AAED,oBAAY,qBAAqB;IAC/B,GAAG,sBAAsB;IACzB,GAAG,sBAAsB;CAC1B;AAED,oBAAY,UAAU;IAEpB,OAAO,+CAA+C;IACtD,SAAS,iDAAiD;IAC1D,SAAS,iDAAiD;IAC1D,eAAe,uDAAuD;IAEtE,UAAU,mDAAmD;IAC7D,sBAAsB,8DAA8D;IACpF,mBAAmB,2DAA2D;IAC9E,cAAc,sDAAsD;IACpE,cAAc,sDAAsD;IACpE,SAAS,iDAAiD;IAC1D,cAAc,sDAAsD;IACpE,aAAa,qDAAqD;IAClE,kBAAkB,0DAA0D;IAC5E,aAAa,qDAAqD;IAClE,kBAAkB,0DAA0D;IAC5E,wBAAwB,gEAAgE;IACxF,qBAAqB,6DAA6D;IAClF,oBAAoB,4DAA4D;IAChF,qBAAqB,6DAA6D;IAClF,gBAAgB,wDAAwD;IACxE,kBAAkB,0DAA0D;IAC5E,gBAAgB,wDAAwD;IACxE,kBAAkB,0DAA0D;CAC7E;AAED,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoGd,CAAC;AAEF,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BT,CAAC;AAEF,QAAA,MAAM,qBAAqB;;;;;CAK1B,CAAC;AAEF,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAmFZ;;;WAGG;;;;;;;;;;;;;CAwBN,CAAC;AAaF,oBAAY,UAAU;IACpB,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,aAAa,kBAAkB;IAC/B,cAAc,mBAAmB;CAClC;AAED,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;CAyBZ,CAAC;AAIF,QAAA,MAAM,aAAa;;;;CAIlB,CAAC;AAEF,OAAO,EAAC,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAC,cAAc,EAAC,CAAC"}
|