samlesa 2.17.0 → 2.17.2
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/index.js +3 -2
- package/build/src/binding-artifact.js +330 -146
- package/build/src/binding-post.js +2 -0
- package/build/src/entity-sp.js +21 -94
- package/build/src/extractor.js +32 -0
- package/build/src/flow.js +23 -112
- package/build/src/libsaml.js +325 -127
- package/build/src/libsamlSoap.js +115 -0
- package/build/src/metadata-idp.js +9 -9
- package/build/src/metadata-sp.js +6 -6
- package/build/src/schema/saml-schema-metadata-2.0.xsd +10 -9
- package/build/src/schemaValidator.js +43 -4
- package/build/src/soap.js +123 -3
- package/package.json +8 -5
- package/types/index.d.ts +3 -2
- package/types/index.d.ts.map +1 -1
- package/types/src/binding-artifact.d.ts +24 -29
- package/types/src/binding-artifact.d.ts.map +1 -1
- package/types/src/binding-post.d.ts.map +1 -1
- package/types/src/entity-sp.d.ts +13 -24
- package/types/src/entity-sp.d.ts.map +1 -1
- package/types/src/extractor.d.ts +22 -0
- package/types/src/extractor.d.ts.map +1 -1
- package/types/src/flow.d.ts +1 -0
- package/types/src/flow.d.ts.map +1 -1
- package/types/src/libsaml.d.ts +4 -3
- package/types/src/libsaml.d.ts.map +1 -1
- package/types/src/libsamlSoap.d.ts +7 -0
- package/types/src/libsamlSoap.d.ts.map +1 -0
- package/types/src/schemaValidator.d.ts +1 -0
- package/types/src/schemaValidator.d.ts.map +1 -1
- package/types/src/soap.d.ts +33 -0
- package/types/src/soap.d.ts.map +1 -1
- package/types/src/validator.d.ts.map +1 -1
- package/build/src/schema/XMLSchema.dtd +0 -402
- package/build/src/schema/datatypes.dtd +0 -203
package/build/index.js
CHANGED
|
@@ -9,11 +9,12 @@ export { default as SamlLib } from './src/libsaml.js';
|
|
|
9
9
|
// new name convention in version >= 3.0
|
|
10
10
|
import * as Constants from './src/urn.js';
|
|
11
11
|
import * as Extractor from './src/extractor.js';
|
|
12
|
-
import
|
|
12
|
+
import * as Soap from './src/soap.js';
|
|
13
|
+
import { validate, validateMetadata } from './src/schemaValidator.js';
|
|
13
14
|
// exposed methods for customizing samlify
|
|
14
15
|
import { setSchemaValidator, setDOMParserOptions } from './src/api.js';
|
|
15
16
|
export { Constants, Extractor,
|
|
16
17
|
// temp: resolve the conflict after version >= 3.0
|
|
17
18
|
IdentityProvider, IdentityProviderInstance, ServiceProvider, ServiceProviderInstance,
|
|
18
19
|
// set context
|
|
19
|
-
setSchemaValidator, setDOMParserOptions, validate };
|
|
20
|
+
setSchemaValidator, setDOMParserOptions, validate, validateMetadata, Soap };
|
|
@@ -3,20 +3,57 @@
|
|
|
3
3
|
* @author tngan
|
|
4
4
|
* @desc Binding-level API, declare the functions using POST binding
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { checkStatus } from "./flow.js";
|
|
7
|
+
import { ParserType, StatusCode, wording } from './urn.js';
|
|
7
8
|
import libsaml from './libsaml.js';
|
|
9
|
+
import libsamlSoap from './libsamlSoap.js';
|
|
8
10
|
import utility, { get } from './utility.js';
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import * as uuid from 'uuid';
|
|
13
|
+
import { artifactResolveFields, extract, loginRequestFields, loginResponseFields, logoutRequestFields, logoutResponseFields } from "./extractor.js";
|
|
14
|
+
import { verifyTime } from "./validator.js";
|
|
15
|
+
import { sendArtifactResolve } from "./soap.js";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
// get the default extractor fields based on the parserType
|
|
20
|
+
function getDefaultExtractorFields(parserType, assertion) {
|
|
21
|
+
switch (parserType) {
|
|
22
|
+
case ParserType.SAMLRequest:
|
|
23
|
+
return loginRequestFields;
|
|
24
|
+
case ParserType.SAMLResponse:
|
|
25
|
+
if (!assertion) {
|
|
26
|
+
// unexpected hit
|
|
27
|
+
throw new Error('ERR_EMPTY_ASSERTION');
|
|
28
|
+
}
|
|
29
|
+
return loginResponseFields(assertion);
|
|
30
|
+
case ParserType.LogoutRequest:
|
|
31
|
+
return logoutRequestFields;
|
|
32
|
+
case ParserType.LogoutResponse:
|
|
33
|
+
return logoutResponseFields;
|
|
34
|
+
default:
|
|
35
|
+
throw new Error('ERR_UNDEFINED_PARSERTYPE');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
9
38
|
const binding = wording.binding;
|
|
10
39
|
/**
|
|
11
40
|
* @desc Generate a base64 encoded login request
|
|
12
41
|
* @param {string} referenceTagXPath reference uri
|
|
13
42
|
* @param {object} entity object includes both idp and sp
|
|
14
|
-
* @param
|
|
43
|
+
* @param customTagReplacement
|
|
15
44
|
*/
|
|
16
|
-
function
|
|
17
|
-
const metadata = {
|
|
45
|
+
function soapLoginRequest(referenceTagXPath, entity, customTagReplacement) {
|
|
46
|
+
const metadata = {
|
|
47
|
+
idp: entity.idp.entityMeta,
|
|
48
|
+
sp: entity.sp.entityMeta,
|
|
49
|
+
inResponse: entity?.inResponse,
|
|
50
|
+
relayState: entity?.relayState
|
|
51
|
+
};
|
|
18
52
|
const spSetting = entity.sp.entitySetting;
|
|
19
53
|
let id = '';
|
|
54
|
+
let id2 = spSetting.generateID();
|
|
55
|
+
let soapTemplate = '';
|
|
56
|
+
let Response = '';
|
|
20
57
|
if (metadata && metadata.idp && metadata.sp) {
|
|
21
58
|
const base = metadata.idp.getSingleSignOnService(binding.post);
|
|
22
59
|
let rawSamlRequest;
|
|
@@ -40,30 +77,59 @@ function base64LoginRequest(referenceTagXPath, entity, customTagReplacement) {
|
|
|
40
77
|
NameIDFormat: selectedNameIDFormat
|
|
41
78
|
});
|
|
42
79
|
}
|
|
80
|
+
const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
|
|
43
81
|
if (metadata.idp.isWantAuthnRequestsSigned()) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
82
|
+
Response = libsaml.constructSAMLSignature({
|
|
83
|
+
referenceTagXPath,
|
|
84
|
+
privateKey,
|
|
85
|
+
privateKeyPass,
|
|
86
|
+
signatureAlgorithm,
|
|
87
|
+
transformationAlgorithms,
|
|
88
|
+
rawSamlMessage: rawSamlRequest,
|
|
89
|
+
isBase64Output: false,
|
|
90
|
+
signingCert: metadata.sp.getX509Certificate('signing'),
|
|
91
|
+
signatureConfig: spSetting.signatureConfig || {
|
|
92
|
+
prefix: 'ds',
|
|
93
|
+
location: { reference: "/*[local-name(.)='AuthnRequest']/!*[local-name(.)='Issuer']", action: 'after' },
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
soapTemplate = libsaml.replaceTagsByValue(libsaml.defaultArtAuthnRequestTemplate.context, {
|
|
97
|
+
ID: id2,
|
|
98
|
+
IssueInstant: new Date().toISOString(),
|
|
99
|
+
InResponseTo: metadata.inResponse ?? "",
|
|
100
|
+
Issuer: metadata.sp.getEntityID(),
|
|
101
|
+
AuthnRequest: Response
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
soapTemplate = libsaml.replaceTagsByValue(libsaml.defaultArtAuthnRequestTemplate.context, {
|
|
106
|
+
ID: id2,
|
|
107
|
+
IssueInstant: new Date().toISOString(),
|
|
108
|
+
InResponseTo: metadata.inResponse ?? "",
|
|
109
|
+
Issuer: metadata.sp.getEntityID(),
|
|
110
|
+
AuthnRequest: rawSamlRequest
|
|
111
|
+
});
|
|
61
112
|
}
|
|
113
|
+
/** 构建响应签名*/
|
|
62
114
|
// No need to embeded XML signature
|
|
63
|
-
return {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
115
|
+
return libsaml.constructSAMLSignature({
|
|
116
|
+
referenceTagXPath: "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']",
|
|
117
|
+
privateKey,
|
|
118
|
+
privateKeyPass,
|
|
119
|
+
signatureAlgorithm,
|
|
120
|
+
transformationAlgorithms,
|
|
121
|
+
rawSamlMessage: soapTemplate,
|
|
122
|
+
isBase64Output: false,
|
|
123
|
+
isMessageSigned: false,
|
|
124
|
+
signingCert: metadata.sp.getX509Certificate('signing'),
|
|
125
|
+
signatureConfig: {
|
|
126
|
+
prefix: 'ds',
|
|
127
|
+
location: {
|
|
128
|
+
reference: "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']/*[local-name(.)='Issuer']",
|
|
129
|
+
action: 'after'
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
});
|
|
67
133
|
}
|
|
68
134
|
throw new Error('ERR_GENERATE_POST_LOGIN_REQUEST_MISSING_METADATA');
|
|
69
135
|
}
|
|
@@ -76,7 +142,7 @@ function base64LoginRequest(referenceTagXPath, entity, customTagReplacement) {
|
|
|
76
142
|
* @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
|
|
77
143
|
* @param AttributeStatement
|
|
78
144
|
*/
|
|
79
|
-
async function
|
|
145
|
+
async function soapLoginResponse(requestInfo = {}, entity, user = {}, customTagReplacement, encryptThenSign = false, AttributeStatement = []) {
|
|
80
146
|
const idpSetting = entity.idp.entitySetting;
|
|
81
147
|
const spSetting = entity.sp.entitySetting;
|
|
82
148
|
const id = idpSetting.generateID();
|
|
@@ -141,7 +207,6 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTa
|
|
|
141
207
|
};
|
|
142
208
|
// step: sign assertion ? -> encrypted ? -> sign message ?
|
|
143
209
|
if (metadata.sp.isWantAssertionsSigned()) {
|
|
144
|
-
// console.debug('sp wants assertion signed');
|
|
145
210
|
rawSamlResponse = libsaml.constructSAMLSignature({
|
|
146
211
|
...config,
|
|
147
212
|
rawSamlMessage: rawSamlResponse,
|
|
@@ -149,7 +214,10 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTa
|
|
|
149
214
|
referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']",
|
|
150
215
|
signatureConfig: {
|
|
151
216
|
prefix: 'ds',
|
|
152
|
-
location: {
|
|
217
|
+
location: {
|
|
218
|
+
reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']",
|
|
219
|
+
action: 'after'
|
|
220
|
+
},
|
|
153
221
|
},
|
|
154
222
|
});
|
|
155
223
|
}
|
|
@@ -200,134 +268,250 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTa
|
|
|
200
268
|
}
|
|
201
269
|
throw new Error('ERR_GENERATE_POST_LOGIN_RESPONSE_MISSING_METADATA');
|
|
202
270
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const nameIDFormat = initSetting.nameIDFormat;
|
|
215
|
-
const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
|
|
216
|
-
let id = '';
|
|
217
|
-
if (metadata && metadata.init && metadata.target) {
|
|
218
|
-
let rawSamlRequest;
|
|
219
|
-
if (initSetting.logoutRequestTemplate && customTagReplacement) {
|
|
220
|
-
const template = customTagReplacement(initSetting.logoutRequestTemplate.context);
|
|
221
|
-
id = get(template, 'id', null);
|
|
222
|
-
rawSamlRequest = get(template, 'context', null);
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
id = initSetting.generateID();
|
|
226
|
-
const tvalue = {
|
|
227
|
-
ID: id,
|
|
228
|
-
Destination: metadata.target.getSingleLogoutService(binding.post),
|
|
229
|
-
Issuer: metadata.init.getEntityID(),
|
|
230
|
-
IssueInstant: new Date().toISOString(),
|
|
231
|
-
EntityID: metadata.init.getEntityID(),
|
|
232
|
-
NameIDFormat: selectedNameIDFormat,
|
|
233
|
-
NameID: user.NameID || '',
|
|
234
|
-
};
|
|
235
|
-
rawSamlRequest = libsaml.replaceTagsByValue(libsaml.defaultLogoutRequestTemplate.context, tvalue);
|
|
236
|
-
}
|
|
237
|
-
if (entity.target.entitySetting.wantLogoutRequestSigned) {
|
|
238
|
-
// Need to embeded XML signature
|
|
239
|
-
const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = initSetting;
|
|
240
|
-
return {
|
|
241
|
-
id,
|
|
242
|
-
context: libsaml.constructSAMLSignature({
|
|
243
|
-
referenceTagXPath,
|
|
244
|
-
privateKey,
|
|
245
|
-
privateKeyPass,
|
|
246
|
-
signatureAlgorithm,
|
|
247
|
-
transformationAlgorithms,
|
|
248
|
-
rawSamlMessage: rawSamlRequest,
|
|
249
|
-
signingCert: metadata.init.getX509Certificate('signing'),
|
|
250
|
-
signatureConfig: initSetting.signatureConfig || {
|
|
251
|
-
prefix: 'ds',
|
|
252
|
-
location: { reference: "/*[local-name(.)='LogoutRequest']/*[local-name(.)='Issuer']", action: 'after' },
|
|
253
|
-
}
|
|
254
|
-
}),
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
return {
|
|
258
|
-
id,
|
|
259
|
-
context: utility.base64Encode(rawSamlRequest),
|
|
260
|
-
};
|
|
271
|
+
async function parseLoginRequestResolve(params) {
|
|
272
|
+
let { idp, sp, xml, } = params;
|
|
273
|
+
const verificationOptions = {
|
|
274
|
+
metadata: idp.entityMeta,
|
|
275
|
+
signatureAlgorithm: idp.entitySetting.requestSignatureAlgorithm,
|
|
276
|
+
};
|
|
277
|
+
let res = await libsaml.isValidXml(xml, true).catch((error) => {
|
|
278
|
+
return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
|
|
279
|
+
});
|
|
280
|
+
if (res !== true) {
|
|
281
|
+
return Promise.reject('ERR_EXCEPTION_VALIDATE_XML');
|
|
261
282
|
}
|
|
262
|
-
|
|
283
|
+
/** 首先先验证签名*/
|
|
284
|
+
// @ts-ignore
|
|
285
|
+
let [verify, xmlString, isEncrypted, noSignature] = await libsamlSoap.verifyAndDecryptSoapMessage(xml, verificationOptions);
|
|
286
|
+
if (!verify) {
|
|
287
|
+
return Promise.reject('ERR_FAIL_TO_VERIFY_SIGNATURE');
|
|
288
|
+
}
|
|
289
|
+
const parseResult = {
|
|
290
|
+
samlContent: xmlString,
|
|
291
|
+
extract: extract(xmlString, artifactResolveFields),
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Validation part: validate the context of response after signature is verified and decrypted (optional)
|
|
295
|
+
*/
|
|
296
|
+
const targetEntityMetadata = sp.entityMeta;
|
|
297
|
+
const issuer = targetEntityMetadata.getEntityID();
|
|
298
|
+
const extractedProperties = parseResult.extract;
|
|
299
|
+
// unmatched issuer
|
|
300
|
+
if (extractedProperties.issuer !== issuer) {
|
|
301
|
+
return Promise.reject('ERR_UNMATCH_ISSUER');
|
|
302
|
+
}
|
|
303
|
+
// invalid session time
|
|
304
|
+
// only run the verifyTime when `SessionNotOnOrAfter` exists
|
|
305
|
+
if (!verifyTime(undefined, new Date(new Date(extractedProperties.request.issueInstant).getTime() + 5 * 60 * 1000).toISOString(), sp.entitySetting.clockDrifts)) {
|
|
306
|
+
return Promise.reject('ERR_EXPIRED_SESSION');
|
|
307
|
+
}
|
|
308
|
+
return Promise.resolve(parseResult);
|
|
263
309
|
}
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
*/
|
|
271
|
-
function base64LogoutResponse(requestInfo, entity, customTagReplacement) {
|
|
310
|
+
async function parseLoginResponseResolve(params) {
|
|
311
|
+
let { idp, sp, art } = params;
|
|
272
312
|
const metadata = {
|
|
273
|
-
|
|
274
|
-
|
|
313
|
+
idp: idp.entityMeta,
|
|
314
|
+
sp: sp.entityMeta,
|
|
275
315
|
};
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
316
|
+
const verificationOptions = {
|
|
317
|
+
metadata: idp.entityMeta,
|
|
318
|
+
signatureAlgorithm: idp.entitySetting.requestSignatureAlgorithm,
|
|
319
|
+
};
|
|
320
|
+
let parserType = 'SAMLResponse';
|
|
321
|
+
/** 断言是否加密应根据响应里面的字段判断*/
|
|
322
|
+
let decryptRequired = idp.entitySetting.isAssertionEncrypted;
|
|
323
|
+
let extractorFields = [];
|
|
324
|
+
let samlContent = '';
|
|
325
|
+
const spSetting = sp.entitySetting;
|
|
326
|
+
let ID = '_' + uuid.v4();
|
|
327
|
+
let url = metadata.idp.getArtifactResolutionService('soap');
|
|
328
|
+
let samlSoapRaw = libsaml.replaceTagsByValue(libsaml.defaultArtifactResolveTemplate.context, {
|
|
329
|
+
ID: ID,
|
|
330
|
+
Destination: url,
|
|
331
|
+
Issuer: metadata.sp.getEntityID(),
|
|
332
|
+
IssueInstant: new Date().toISOString(),
|
|
333
|
+
Art: art
|
|
334
|
+
});
|
|
335
|
+
if (!metadata.idp.isWantAuthnRequestsSigned()) {
|
|
336
|
+
samlContent = await sendArtifactResolve(url, samlSoapRaw);
|
|
337
|
+
// check status based on different scenarios
|
|
338
|
+
// validate the xml
|
|
339
|
+
try {
|
|
340
|
+
await libsaml.isValidXml(samlContent, true);
|
|
284
341
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const tvalue = {
|
|
288
|
-
ID: id,
|
|
289
|
-
Destination: metadata.target.getSingleLogoutService(binding.post),
|
|
290
|
-
EntityID: metadata.init.getEntityID(),
|
|
291
|
-
Issuer: metadata.init.getEntityID(),
|
|
292
|
-
IssueInstant: new Date().toISOString(),
|
|
293
|
-
StatusCode: StatusCode.Success,
|
|
294
|
-
InResponseTo: get(requestInfo, 'extract.request.id', '')
|
|
295
|
-
};
|
|
296
|
-
rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLogoutResponseTemplate.context, tvalue);
|
|
342
|
+
catch (e) {
|
|
343
|
+
return Promise.reject('ERR_INVALID_XML');
|
|
297
344
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
345
|
+
await checkStatus(samlContent, parserType, true);
|
|
346
|
+
}
|
|
347
|
+
if (metadata.idp.isWantAuthnRequestsSigned()) {
|
|
348
|
+
const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
|
|
349
|
+
//@ts-ignore
|
|
350
|
+
let signatureSoap = libsaml.constructSAMLSignature({
|
|
351
|
+
referenceTagXPath: "//*[local-name(.)='ArtifactResolve']",
|
|
352
|
+
isMessageSigned: false,
|
|
353
|
+
isBase64Output: false,
|
|
354
|
+
transformationAlgorithms: transformationAlgorithms,
|
|
355
|
+
//@ts-ignore
|
|
356
|
+
privateKey,
|
|
357
|
+
privateKeyPass,
|
|
358
|
+
//@ts-ignore
|
|
359
|
+
signatureAlgorithm,
|
|
360
|
+
rawSamlMessage: samlSoapRaw,
|
|
361
|
+
signingCert: metadata.sp.getX509Certificate('signing'),
|
|
362
|
+
signatureConfig: {
|
|
363
|
+
prefix: 'ds',
|
|
364
|
+
location: {
|
|
365
|
+
reference: "//*[local-name(.)='Issuer']",
|
|
366
|
+
action: 'after'
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
samlContent = await sendArtifactResolve(url, signatureSoap);
|
|
371
|
+
// check status based on different scenarios
|
|
372
|
+
// validate the xml
|
|
373
|
+
try {
|
|
374
|
+
await libsaml.isValidXml(samlContent, true);
|
|
319
375
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
376
|
+
catch (e) {
|
|
377
|
+
return Promise.reject('ERR_INVALID_XML');
|
|
378
|
+
}
|
|
379
|
+
await checkStatus(samlContent, parserType, true);
|
|
380
|
+
const [verified1, verifiedAssertionNode1, isDecryptRequired1, noSignature1] = await libsamlSoap.verifyAndDecryptSoapMessage(samlContent, verificationOptions);
|
|
381
|
+
/* decryptRequired = isDecryptRequired*/
|
|
382
|
+
if (!verified1) {
|
|
383
|
+
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
384
|
+
}
|
|
385
|
+
samlContent = verifiedAssertionNode1;
|
|
386
|
+
const [verified, verifiedAssertionNode, isDecryptRequired, noSignature] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
387
|
+
if (isDecryptRequired && noSignature) {
|
|
388
|
+
const result = await libsaml.decryptAssertion(sp, samlContent);
|
|
389
|
+
samlContent = result[0];
|
|
390
|
+
extractorFields = getDefaultExtractorFields(parserType, result[1]);
|
|
391
|
+
}
|
|
392
|
+
if (!verified && !noSignature && !isDecryptRequired) {
|
|
393
|
+
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
394
|
+
}
|
|
395
|
+
if (!isDecryptRequired) {
|
|
396
|
+
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
397
|
+
}
|
|
398
|
+
if (parserType === 'SAMLResponse' && isDecryptRequired && !noSignature) {
|
|
399
|
+
const result = await libsaml.decryptAssertion(sp, samlContent);
|
|
400
|
+
samlContent = result[0];
|
|
401
|
+
extractorFields = getDefaultExtractorFields(parserType, result[1]);
|
|
402
|
+
}
|
|
403
|
+
const parseResult = {
|
|
404
|
+
samlContent: samlContent,
|
|
405
|
+
extract: extract(samlContent, extractorFields),
|
|
323
406
|
};
|
|
407
|
+
/**
|
|
408
|
+
* Validation part: validate the context of response after signature is verified and decrypted (optional)
|
|
409
|
+
*/
|
|
410
|
+
const targetEntityMetadata = idp.entityMeta;
|
|
411
|
+
const issuer = targetEntityMetadata.getEntityID();
|
|
412
|
+
const extractedProperties = parseResult.extract;
|
|
413
|
+
// unmatched issuer
|
|
414
|
+
if ((parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
|
|
415
|
+
&& extractedProperties
|
|
416
|
+
&& extractedProperties.issuer !== issuer) {
|
|
417
|
+
return Promise.reject('ERR_UNMATCH_ISSUER');
|
|
418
|
+
}
|
|
419
|
+
// invalid session time
|
|
420
|
+
// only run the verifyTime when `SessionNotOnOrAfter` exists
|
|
421
|
+
if (parserType === 'SAMLResponse'
|
|
422
|
+
&& extractedProperties.sessionIndex.sessionNotOnOrAfter
|
|
423
|
+
&& !verifyTime(undefined, extractedProperties.sessionIndex.sessionNotOnOrAfter, sp.entitySetting.clockDrifts)) {
|
|
424
|
+
return Promise.reject('ERR_EXPIRED_SESSION');
|
|
425
|
+
}
|
|
426
|
+
// invalid time
|
|
427
|
+
// 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
|
428
|
+
if (parserType === 'SAMLResponse'
|
|
429
|
+
&& extractedProperties.conditions
|
|
430
|
+
&& !verifyTime(extractedProperties.conditions.notBefore, extractedProperties.conditions.notOnOrAfter, sp.entitySetting.clockDrifts)) {
|
|
431
|
+
return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
|
|
432
|
+
}
|
|
433
|
+
//valid destination
|
|
434
|
+
//There is no validation of the response here. The upper-layer application
|
|
435
|
+
// should verify the result by itself to see if the destination is equal to the SP acs and
|
|
436
|
+
// whether the response.id is used to prevent replay attacks.
|
|
437
|
+
/*
|
|
438
|
+
let destination = extractedProperties?.response?.destination
|
|
439
|
+
let isExit = self.entitySetting?.assertionConsumerService?.filter((item) => {
|
|
440
|
+
return item?.Location === destination
|
|
441
|
+
})
|
|
442
|
+
if (isExit?.length === 0) {
|
|
443
|
+
return Promise.reject('ERR_Destination_URL');
|
|
444
|
+
}
|
|
445
|
+
if (parserType === 'SAMLResponse') {
|
|
446
|
+
let destination = extractedProperties?.response?.destination
|
|
447
|
+
let isExit = self.entitySetting?.assertionConsumerService?.filter((item: { Location: any; }) => {
|
|
448
|
+
return item?.Location === destination
|
|
449
|
+
})
|
|
450
|
+
if (isExit?.length === 0) {
|
|
451
|
+
return Promise.reject('ERR_Destination_URL');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
*/
|
|
455
|
+
return Promise.resolve(parseResult);
|
|
456
|
+
}
|
|
457
|
+
const parseResult = {
|
|
458
|
+
samlContent: samlContent,
|
|
459
|
+
extract: extract(samlContent, extractorFields),
|
|
460
|
+
};
|
|
461
|
+
/**
|
|
462
|
+
* Validation part: validate the context of response after signature is verified and decrypted (optional)
|
|
463
|
+
*/
|
|
464
|
+
const targetEntityMetadata = idp.entityMeta;
|
|
465
|
+
const issuer = targetEntityMetadata.getEntityID();
|
|
466
|
+
const extractedProperties = parseResult.extract;
|
|
467
|
+
// unmatched issuer
|
|
468
|
+
if ((parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
|
|
469
|
+
&& extractedProperties
|
|
470
|
+
&& extractedProperties.issuer !== issuer) {
|
|
471
|
+
return Promise.reject('ERR_UNMATCH_ISSUER');
|
|
324
472
|
}
|
|
325
|
-
|
|
473
|
+
// invalid session time
|
|
474
|
+
// only run the verifyTime when `SessionNotOnOrAfter` exists
|
|
475
|
+
if (parserType === 'SAMLResponse'
|
|
476
|
+
&& extractedProperties.sessionIndex.sessionNotOnOrAfter
|
|
477
|
+
&& !verifyTime(undefined, extractedProperties.sessionIndex.sessionNotOnOrAfter, sp.entitySetting.clockDrifts)) {
|
|
478
|
+
return Promise.reject('ERR_EXPIRED_SESSION');
|
|
479
|
+
}
|
|
480
|
+
// invalid time
|
|
481
|
+
// 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
|
482
|
+
if (parserType === 'SAMLResponse'
|
|
483
|
+
&& extractedProperties.conditions
|
|
484
|
+
&& !verifyTime(extractedProperties.conditions.notBefore, extractedProperties.conditions.notOnOrAfter, sp.entitySetting.clockDrifts)) {
|
|
485
|
+
return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
|
|
486
|
+
}
|
|
487
|
+
//valid destination
|
|
488
|
+
//There is no validation of the response here. The upper-layer application
|
|
489
|
+
// should verify the result by itself to see if the destination is equal to the SP acs and
|
|
490
|
+
// whether the response.id is used to prevent replay attacks.
|
|
491
|
+
/*
|
|
492
|
+
let destination = extractedProperties?.response?.destination
|
|
493
|
+
let isExit = self.entitySetting?.assertionConsumerService?.filter((item) => {
|
|
494
|
+
return item?.Location === destination
|
|
495
|
+
})
|
|
496
|
+
if (isExit?.length === 0) {
|
|
497
|
+
return Promise.reject('ERR_Destination_URL');
|
|
498
|
+
}
|
|
499
|
+
if (parserType === 'SAMLResponse') {
|
|
500
|
+
let destination = extractedProperties?.response?.destination
|
|
501
|
+
let isExit = self.entitySetting?.assertionConsumerService?.filter((item: { Location: any; }) => {
|
|
502
|
+
return item?.Location === destination
|
|
503
|
+
})
|
|
504
|
+
if (isExit?.length === 0) {
|
|
505
|
+
return Promise.reject('ERR_Destination_URL');
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
*/
|
|
509
|
+
return Promise.resolve(parseResult);
|
|
326
510
|
}
|
|
327
511
|
const artifactSignBinding = {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
512
|
+
parseLoginRequestResolve,
|
|
513
|
+
soapLoginRequest,
|
|
514
|
+
parseLoginResponseResolve,
|
|
515
|
+
soapLoginResponse,
|
|
332
516
|
};
|
|
333
517
|
export default artifactSignBinding;
|
|
@@ -130,6 +130,8 @@ async function base64LoginResponse(requestInfo = {}, entity, user = {}, customTa
|
|
|
130
130
|
tvalue.InResponseTo = requestInfo?.extract?.request?.id ?? '';
|
|
131
131
|
}
|
|
132
132
|
rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLoginResponseTemplate.context, tvalue);
|
|
133
|
+
console.log(rawSamlResponse);
|
|
134
|
+
console.log("没有加密签名过的------------------------------------");
|
|
133
135
|
}
|
|
134
136
|
const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm } = idpSetting;
|
|
135
137
|
const config = {
|