samlesa 2.12.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/.editorconfig +19 -0
- package/.github/FUNDING.yml +1 -0
- package/.idea/compiler.xml +6 -0
- package/.idea/deployment.xml +14 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/samlify.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.pre-commit.sh +15 -0
- package/.snyk +8 -0
- package/.travis.yml +29 -0
- package/LICENSE +22 -0
- package/Makefile +25 -0
- package/README.md +84 -0
- package/build/.idea/workspace.xml +58 -0
- package/build/index.js +65 -0
- package/build/index.js.map +1 -0
- package/build/src/api.js +24 -0
- package/build/src/api.js.map +1 -0
- package/build/src/binding-post.js +369 -0
- package/build/src/binding-post.js.map +1 -0
- package/build/src/binding-redirect.js +333 -0
- package/build/src/binding-redirect.js.map +1 -0
- package/build/src/binding-simplesign.js +233 -0
- package/build/src/binding-simplesign.js.map +1 -0
- package/build/src/entity-idp.js +131 -0
- package/build/src/entity-idp.js.map +1 -0
- package/build/src/entity-sp.js +97 -0
- package/build/src/entity-sp.js.map +1 -0
- package/build/src/entity.js +236 -0
- package/build/src/entity.js.map +1 -0
- package/build/src/extractor.js +370 -0
- package/build/src/extractor.js.map +1 -0
- package/build/src/flow.js +320 -0
- package/build/src/flow.js.map +1 -0
- package/build/src/libsaml.js +642 -0
- package/build/src/libsaml.js.map +1 -0
- package/build/src/metadata-idp.js +128 -0
- package/build/src/metadata-idp.js.map +1 -0
- package/build/src/metadata-sp.js +232 -0
- package/build/src/metadata-sp.js.map +1 -0
- package/build/src/metadata.js +177 -0
- package/build/src/metadata.js.map +1 -0
- package/build/src/types.js +12 -0
- package/build/src/types.js.map +1 -0
- package/build/src/urn.js +213 -0
- package/build/src/urn.js.map +1 -0
- package/build/src/utility.js +249 -0
- package/build/src/utility.js.map +1 -0
- package/build/src/validator.js +27 -0
- package/build/src/validator.js.map +1 -0
- package/index.d.ts +10 -0
- package/index.js +19 -0
- package/index.js.map +1 -0
- package/index.ts +28 -0
- package/package.json +74 -0
- package/qodana.yaml +29 -0
- package/src/.idea/modules.xml +8 -0
- package/src/.idea/src.iml +12 -0
- package/src/.idea/vcs.xml +6 -0
- package/src/api.ts +36 -0
- package/src/binding-post.ts +338 -0
- package/src/binding-redirect.ts +331 -0
- package/src/binding-simplesign.ts +231 -0
- package/src/entity-idp.ts +145 -0
- package/src/entity-sp.ts +114 -0
- package/src/entity.ts +243 -0
- package/src/extractor.ts +392 -0
- package/src/flow.ts +467 -0
- package/src/libsaml.ts +786 -0
- package/src/metadata-idp.ts +146 -0
- package/src/metadata-sp.ts +268 -0
- package/src/metadata.ts +166 -0
- package/src/types.ts +153 -0
- package/src/urn.ts +211 -0
- package/src/utility.ts +248 -0
- package/src/validator.ts +44 -0
- package/tsconfig.json +38 -0
- package/tslint.json +35 -0
- package/types/index.d.ts +10 -0
- package/types/src/api.d.ts +13 -0
- package/types/src/binding-post.d.ts +46 -0
- package/types/src/binding-redirect.d.ts +52 -0
- package/types/src/binding-simplesign.d.ts +39 -0
- package/types/src/entity-idp.d.ts +42 -0
- package/types/src/entity-sp.d.ts +36 -0
- package/types/src/entity.d.ts +99 -0
- package/types/src/extractor.d.ts +25 -0
- package/types/src/flow.d.ts +6 -0
- package/types/src/libsaml.d.ts +210 -0
- package/types/src/metadata-idp.d.ts +24 -0
- package/types/src/metadata-sp.d.ts +36 -0
- package/types/src/metadata.d.ts +57 -0
- package/types/src/types.d.ts +127 -0
- package/types/src/urn.d.ts +194 -0
- package/types/src/utility.d.ts +134 -0
- package/types/src/validator.d.ts +3 -0
- package/types.d.ts +2 -0
package/src/flow.ts
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { inflateString, base64Decode } from './utility.js';
|
|
2
|
+
import { verifyTime } from './validator.js';
|
|
3
|
+
import libsaml from './libsaml.js';
|
|
4
|
+
import {
|
|
5
|
+
extract,
|
|
6
|
+
loginRequestFields,
|
|
7
|
+
loginResponseFields,
|
|
8
|
+
logoutRequestFields,
|
|
9
|
+
logoutResponseFields,
|
|
10
|
+
ExtractorFields,
|
|
11
|
+
logoutResponseStatusFields,
|
|
12
|
+
loginResponseStatusFields
|
|
13
|
+
} from './extractor.js';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
BindingNamespace,
|
|
17
|
+
ParserType,
|
|
18
|
+
wording,
|
|
19
|
+
MessageSignatureOrder,
|
|
20
|
+
StatusCode
|
|
21
|
+
} from './urn.js';
|
|
22
|
+
|
|
23
|
+
const bindDict = wording.binding;
|
|
24
|
+
const urlParams = wording.urlParams;
|
|
25
|
+
|
|
26
|
+
export interface FlowResult {
|
|
27
|
+
samlContent: string;
|
|
28
|
+
extract: any;
|
|
29
|
+
sigAlg?: string|null ;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// get the default extractor fields based on the parserType
|
|
33
|
+
function getDefaultExtractorFields(parserType: ParserType, assertion?: any): ExtractorFields {
|
|
34
|
+
switch (parserType) {
|
|
35
|
+
case ParserType.SAMLRequest:
|
|
36
|
+
return loginRequestFields;
|
|
37
|
+
case ParserType.SAMLResponse:
|
|
38
|
+
if (!assertion) {
|
|
39
|
+
// unexpected hit
|
|
40
|
+
throw new Error('ERR_EMPTY_ASSERTION');
|
|
41
|
+
}
|
|
42
|
+
return loginResponseFields(assertion);
|
|
43
|
+
case ParserType.LogoutRequest:
|
|
44
|
+
return logoutRequestFields;
|
|
45
|
+
case ParserType.LogoutResponse:
|
|
46
|
+
return logoutResponseFields;
|
|
47
|
+
default:
|
|
48
|
+
throw new Error('ERR_UNDEFINED_PARSERTYPE');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// proceed the redirect binding flow
|
|
53
|
+
async function redirectFlow(options): Promise<FlowResult> {
|
|
54
|
+
|
|
55
|
+
const { request, parserType, self, checkSignature = true, from } = options;
|
|
56
|
+
const { query, octetString } = request;
|
|
57
|
+
const { SigAlg: sigAlg, Signature: signature } = query;
|
|
58
|
+
|
|
59
|
+
const targetEntityMetadata = from.entityMeta;
|
|
60
|
+
|
|
61
|
+
// ?SAMLRequest= or ?SAMLResponse=
|
|
62
|
+
const direction = libsaml.getQueryParamByType(parserType);
|
|
63
|
+
const content = query[direction];
|
|
64
|
+
|
|
65
|
+
// query must contain the saml content
|
|
66
|
+
if (content === undefined) {
|
|
67
|
+
return Promise.reject('ERR_REDIRECT_FLOW_BAD_ARGS');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const xmlString = inflateString(decodeURIComponent(content));
|
|
71
|
+
|
|
72
|
+
// validate the xml
|
|
73
|
+
try {
|
|
74
|
+
await libsaml.isValidXml(xmlString);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return Promise.reject('ERR_INVALID_XML');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// check status based on different scenarios
|
|
80
|
+
await checkStatus(xmlString, parserType);
|
|
81
|
+
|
|
82
|
+
let assertion: string = '';
|
|
83
|
+
|
|
84
|
+
if (parserType === urlParams.samlResponse){
|
|
85
|
+
// Extract assertion shortcut
|
|
86
|
+
const verifiedDoc = extract(xmlString, [{
|
|
87
|
+
key: 'assertion',
|
|
88
|
+
localPath: ['~Response', 'Assertion'],
|
|
89
|
+
attributes: [],
|
|
90
|
+
context: true
|
|
91
|
+
}]);
|
|
92
|
+
if (verifiedDoc && verifiedDoc.assertion){
|
|
93
|
+
assertion = verifiedDoc.assertion as string;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const extractorFields = getDefaultExtractorFields(parserType, assertion.length > 0 ? assertion : null);
|
|
98
|
+
|
|
99
|
+
const parseResult: { samlContent: string, extract: any, sigAlg: (string | null) } = {
|
|
100
|
+
samlContent: xmlString,
|
|
101
|
+
sigAlg: null,
|
|
102
|
+
extract: extract(xmlString, extractorFields),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// see if signature check is required
|
|
106
|
+
// only verify message signature is enough
|
|
107
|
+
if (checkSignature) {
|
|
108
|
+
if (!signature || !sigAlg) {
|
|
109
|
+
return Promise.reject('ERR_MISSING_SIG_ALG');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// put the below two assignments into verifyMessageSignature function
|
|
113
|
+
const base64Signature = Buffer.from(decodeURIComponent(signature), 'base64');
|
|
114
|
+
const decodeSigAlg = decodeURIComponent(sigAlg);
|
|
115
|
+
|
|
116
|
+
const verified = libsaml.verifyMessageSignature(targetEntityMetadata, octetString, base64Signature, sigAlg);
|
|
117
|
+
|
|
118
|
+
if (!verified) {
|
|
119
|
+
// Fail to verify message signature
|
|
120
|
+
return Promise.reject('ERR_FAILED_MESSAGE_SIGNATURE_VERIFICATION');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
parseResult.sigAlg = decodeSigAlg;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Validation part: validate the context of response after signature is verified and decrypted (optional)
|
|
128
|
+
*/
|
|
129
|
+
const issuer = targetEntityMetadata.getEntityID();
|
|
130
|
+
const extractedProperties = parseResult.extract;
|
|
131
|
+
|
|
132
|
+
// unmatched issuer
|
|
133
|
+
if (
|
|
134
|
+
(parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
|
|
135
|
+
&& extractedProperties
|
|
136
|
+
&& extractedProperties.issuer !== issuer
|
|
137
|
+
) {
|
|
138
|
+
return Promise.reject('ERR_UNMATCH_ISSUER');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// invalid session time
|
|
142
|
+
// only run the verifyTime when `SessionNotOnOrAfter` exists
|
|
143
|
+
if (
|
|
144
|
+
parserType === 'SAMLResponse'
|
|
145
|
+
&& extractedProperties.sessionIndex.sessionNotOnOrAfter
|
|
146
|
+
&& !verifyTime(
|
|
147
|
+
undefined,
|
|
148
|
+
extractedProperties.sessionIndex.sessionNotOnOrAfter,
|
|
149
|
+
self.entitySetting.clockDrifts
|
|
150
|
+
)
|
|
151
|
+
) {
|
|
152
|
+
return Promise.reject('ERR_EXPIRED_SESSION');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// invalid time
|
|
156
|
+
// 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
|
157
|
+
if (
|
|
158
|
+
parserType === 'SAMLResponse'
|
|
159
|
+
&& extractedProperties.conditions
|
|
160
|
+
&& !verifyTime(
|
|
161
|
+
extractedProperties.conditions.notBefore,
|
|
162
|
+
extractedProperties.conditions.notOnOrAfter,
|
|
163
|
+
self.entitySetting.clockDrifts
|
|
164
|
+
)
|
|
165
|
+
) {
|
|
166
|
+
return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return Promise.resolve(parseResult);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// proceed the post flow
|
|
173
|
+
async function postFlow(options): Promise<FlowResult> {
|
|
174
|
+
|
|
175
|
+
const {
|
|
176
|
+
request,
|
|
177
|
+
from,
|
|
178
|
+
self,
|
|
179
|
+
parserType,
|
|
180
|
+
checkSignature = true
|
|
181
|
+
} = options;
|
|
182
|
+
|
|
183
|
+
const { body } = request;
|
|
184
|
+
|
|
185
|
+
const direction = libsaml.getQueryParamByType(parserType);
|
|
186
|
+
const encodedRequest = body[direction];
|
|
187
|
+
|
|
188
|
+
let samlContent = String(base64Decode(encodedRequest));
|
|
189
|
+
|
|
190
|
+
const verificationOptions = {
|
|
191
|
+
metadata: from.entityMeta,
|
|
192
|
+
signatureAlgorithm: from.entitySetting.requestSignatureAlgorithm,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const decryptRequired = from.entitySetting.isAssertionEncrypted;
|
|
196
|
+
let extractorFields: ExtractorFields = [];
|
|
197
|
+
|
|
198
|
+
// validate the xml first
|
|
199
|
+
await libsaml.isValidXml(samlContent);
|
|
200
|
+
|
|
201
|
+
if (parserType !== urlParams.samlResponse) {
|
|
202
|
+
extractorFields = getDefaultExtractorFields(parserType, null);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// check status based on different scenarios
|
|
206
|
+
await checkStatus(samlContent, parserType);
|
|
207
|
+
|
|
208
|
+
// verify the signatures (the response is encrypted then signed, then verify first then decrypt)
|
|
209
|
+
if (
|
|
210
|
+
checkSignature &&
|
|
211
|
+
from.entitySetting.messageSigningOrder === MessageSignatureOrder.ETS
|
|
212
|
+
) {
|
|
213
|
+
const [verified, verifiedAssertionNode] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
214
|
+
if (!verified) {
|
|
215
|
+
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
216
|
+
}
|
|
217
|
+
if (!decryptRequired) {
|
|
218
|
+
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (parserType === 'SAMLResponse' && decryptRequired) {
|
|
223
|
+
const result = await libsaml.decryptAssertion(self, samlContent);
|
|
224
|
+
samlContent = result[0];
|
|
225
|
+
extractorFields = getDefaultExtractorFields(parserType, result[1]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// verify the signatures (the response is signed then encrypted, then decrypt first then verify)
|
|
229
|
+
if (
|
|
230
|
+
checkSignature &&
|
|
231
|
+
from.entitySetting.messageSigningOrder === MessageSignatureOrder.STE
|
|
232
|
+
) {
|
|
233
|
+
const [verified, verifiedAssertionNode] = libsaml.verifySignature(samlContent, verificationOptions);
|
|
234
|
+
if (verified) {
|
|
235
|
+
extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode);
|
|
236
|
+
} else {
|
|
237
|
+
return Promise.reject('ERR_FAIL_TO_VERIFY_STE_SIGNATURE');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const parseResult = {
|
|
242
|
+
samlContent: samlContent,
|
|
243
|
+
extract: extract(samlContent, extractorFields),
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Validation part: validate the context of response after signature is verified and decrypted (optional)
|
|
248
|
+
*/
|
|
249
|
+
const targetEntityMetadata = from.entityMeta;
|
|
250
|
+
const issuer = targetEntityMetadata.getEntityID();
|
|
251
|
+
const extractedProperties = parseResult.extract;
|
|
252
|
+
|
|
253
|
+
// unmatched issuer
|
|
254
|
+
if (
|
|
255
|
+
(parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
|
|
256
|
+
&& extractedProperties
|
|
257
|
+
&& extractedProperties.issuer !== issuer
|
|
258
|
+
) {
|
|
259
|
+
return Promise.reject('ERR_UNMATCH_ISSUER');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// invalid session time
|
|
263
|
+
// only run the verifyTime when `SessionNotOnOrAfter` exists
|
|
264
|
+
if (
|
|
265
|
+
parserType === 'SAMLResponse'
|
|
266
|
+
&& extractedProperties.sessionIndex.sessionNotOnOrAfter
|
|
267
|
+
&& !verifyTime(
|
|
268
|
+
undefined,
|
|
269
|
+
extractedProperties.sessionIndex.sessionNotOnOrAfter,
|
|
270
|
+
self.entitySetting.clockDrifts
|
|
271
|
+
)
|
|
272
|
+
) {
|
|
273
|
+
return Promise.reject('ERR_EXPIRED_SESSION');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// invalid time
|
|
277
|
+
// 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
|
278
|
+
if (
|
|
279
|
+
parserType === 'SAMLResponse'
|
|
280
|
+
&& extractedProperties.conditions
|
|
281
|
+
&& !verifyTime(
|
|
282
|
+
extractedProperties.conditions.notBefore,
|
|
283
|
+
extractedProperties.conditions.notOnOrAfter,
|
|
284
|
+
self.entitySetting.clockDrifts
|
|
285
|
+
)
|
|
286
|
+
) {
|
|
287
|
+
return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return Promise.resolve(parseResult);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
// proceed the post simple sign binding flow
|
|
295
|
+
async function postSimpleSignFlow(options): Promise<FlowResult> {
|
|
296
|
+
|
|
297
|
+
const { request, parserType, self, checkSignature = true, from } = options;
|
|
298
|
+
|
|
299
|
+
const { body, octetString } = request;
|
|
300
|
+
|
|
301
|
+
const targetEntityMetadata = from.entityMeta;
|
|
302
|
+
|
|
303
|
+
// ?SAMLRequest= or ?SAMLResponse=
|
|
304
|
+
const direction = libsaml.getQueryParamByType(parserType);
|
|
305
|
+
const encodedRequest: string = body[direction];
|
|
306
|
+
const sigAlg: string = body['SigAlg'];
|
|
307
|
+
const signature: string = body['Signature'];
|
|
308
|
+
|
|
309
|
+
// query must contain the saml content
|
|
310
|
+
if (encodedRequest === undefined) {
|
|
311
|
+
return Promise.reject('ERR_SIMPLESIGN_FLOW_BAD_ARGS');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const xmlString = String(base64Decode(encodedRequest));
|
|
315
|
+
|
|
316
|
+
// validate the xml
|
|
317
|
+
try {
|
|
318
|
+
await libsaml.isValidXml(xmlString);
|
|
319
|
+
} catch (e) {
|
|
320
|
+
return Promise.reject('ERR_INVALID_XML');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// check status based on different scenarios
|
|
324
|
+
await checkStatus(xmlString, parserType);
|
|
325
|
+
|
|
326
|
+
let assertion: string = '';
|
|
327
|
+
|
|
328
|
+
if (parserType === urlParams.samlResponse){
|
|
329
|
+
// Extract assertion shortcut
|
|
330
|
+
const verifiedDoc = extract(xmlString, [{
|
|
331
|
+
key: 'assertion',
|
|
332
|
+
localPath: ['~Response', 'Assertion'],
|
|
333
|
+
attributes: [],
|
|
334
|
+
context: true
|
|
335
|
+
}]);
|
|
336
|
+
if (verifiedDoc && verifiedDoc.assertion){
|
|
337
|
+
assertion = verifiedDoc.assertion as string;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const extractorFields = getDefaultExtractorFields(parserType, assertion.length > 0 ? assertion : null);
|
|
342
|
+
|
|
343
|
+
const parseResult: { samlContent: string, extract: any, sigAlg: (string | null) } = {
|
|
344
|
+
samlContent: xmlString,
|
|
345
|
+
sigAlg: null,
|
|
346
|
+
extract: extract(xmlString, extractorFields),
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// see if signature check is required
|
|
350
|
+
// only verify message signature is enough
|
|
351
|
+
if (checkSignature) {
|
|
352
|
+
if (!signature || !sigAlg) {
|
|
353
|
+
return Promise.reject('ERR_MISSING_SIG_ALG');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// put the below two assignments into verifyMessageSignature function
|
|
357
|
+
const base64Signature = Buffer.from(signature, 'base64');
|
|
358
|
+
|
|
359
|
+
const verified = libsaml.verifyMessageSignature(targetEntityMetadata, octetString, base64Signature, sigAlg);
|
|
360
|
+
|
|
361
|
+
if (!verified) {
|
|
362
|
+
// Fail to verify message signature
|
|
363
|
+
return Promise.reject('ERR_FAILED_MESSAGE_SIGNATURE_VERIFICATION');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
parseResult.sigAlg = sigAlg;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Validation part: validate the context of response after signature is verified and decrypted (optional)
|
|
371
|
+
*/
|
|
372
|
+
const issuer = targetEntityMetadata.getEntityID();
|
|
373
|
+
const extractedProperties = parseResult.extract;
|
|
374
|
+
|
|
375
|
+
// unmatched issuer
|
|
376
|
+
if (
|
|
377
|
+
(parserType === 'LogoutResponse' || parserType === 'SAMLResponse')
|
|
378
|
+
&& extractedProperties
|
|
379
|
+
&& extractedProperties.issuer !== issuer
|
|
380
|
+
) {
|
|
381
|
+
return Promise.reject('ERR_UNMATCH_ISSUER');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// invalid session time
|
|
385
|
+
// only run the verifyTime when `SessionNotOnOrAfter` exists
|
|
386
|
+
if (
|
|
387
|
+
parserType === 'SAMLResponse'
|
|
388
|
+
&& extractedProperties.sessionIndex.sessionNotOnOrAfter
|
|
389
|
+
&& !verifyTime(
|
|
390
|
+
undefined,
|
|
391
|
+
extractedProperties.sessionIndex.sessionNotOnOrAfter,
|
|
392
|
+
self.entitySetting.clockDrifts
|
|
393
|
+
)
|
|
394
|
+
) {
|
|
395
|
+
return Promise.reject('ERR_EXPIRED_SESSION');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// invalid time
|
|
399
|
+
// 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
|
|
400
|
+
if (
|
|
401
|
+
parserType === 'SAMLResponse'
|
|
402
|
+
&& extractedProperties.conditions
|
|
403
|
+
&& !verifyTime(
|
|
404
|
+
extractedProperties.conditions.notBefore,
|
|
405
|
+
extractedProperties.conditions.notOnOrAfter,
|
|
406
|
+
self.entitySetting.clockDrifts
|
|
407
|
+
)
|
|
408
|
+
) {
|
|
409
|
+
return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return Promise.resolve(parseResult);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
function checkStatus(content: string, parserType: string): Promise<string> {
|
|
417
|
+
|
|
418
|
+
// only check response parser
|
|
419
|
+
if (parserType !== urlParams.samlResponse && parserType !== urlParams.logoutResponse) {
|
|
420
|
+
return Promise.resolve('SKIPPED');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const fields = parserType === urlParams.samlResponse
|
|
424
|
+
? loginResponseStatusFields
|
|
425
|
+
: logoutResponseStatusFields;
|
|
426
|
+
|
|
427
|
+
const {top, second} = extract(content, fields);
|
|
428
|
+
|
|
429
|
+
// only resolve when top-tier status code is success
|
|
430
|
+
if (top === StatusCode.Success) {
|
|
431
|
+
return Promise.resolve('OK');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (!top) {
|
|
435
|
+
throw new Error('ERR_UNDEFINED_STATUS');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// returns a detailed error for two-tier error code
|
|
439
|
+
throw new Error(`ERR_FAILED_STATUS with top tier code: ${top}, second tier code: ${second}`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export function flow(options): Promise<FlowResult> {
|
|
443
|
+
|
|
444
|
+
const binding = options.binding;
|
|
445
|
+
const parserType = options.parserType;
|
|
446
|
+
|
|
447
|
+
options.supportBindings = [BindingNamespace.Redirect, BindingNamespace.Post, BindingNamespace.SimpleSign];
|
|
448
|
+
// saml response allows POST, REDIRECT
|
|
449
|
+
if (parserType === ParserType.SAMLResponse) {
|
|
450
|
+
options.supportBindings = [BindingNamespace.Post, BindingNamespace.Redirect, BindingNamespace.SimpleSign];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (binding === bindDict.post) {
|
|
454
|
+
return postFlow(options);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (binding === bindDict.redirect) {
|
|
458
|
+
return redirectFlow(options);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (binding === bindDict.simpleSign) {
|
|
462
|
+
return postSimpleSignFlow(options);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return Promise.reject('ERR_UNEXPECTED_FLOW');
|
|
466
|
+
|
|
467
|
+
}
|