samlesa 4.3.5 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -16
- package/build/src/artifact.js +55 -0
- package/build/src/binding-artifact.js +477 -363
- package/build/src/binding-post.js +7 -3
- package/build/src/entity-idp.js +51 -3
- package/build/src/entity-sp.js +35 -30
- package/build/src/extractor.js +21 -4
- package/build/src/libsamlSoap.js +88 -96
- package/build/src/soap.js +34 -105
- package/package.json +87 -87
- package/types/src/artifact.d.ts +14 -0
- package/types/src/artifact.d.ts.map +1 -0
- package/types/src/binding-artifact.d.ts +92 -58
- package/types/src/binding-artifact.d.ts.map +1 -1
- package/types/src/binding-post.d.ts +1 -1
- package/types/src/binding-post.d.ts.map +1 -1
- package/types/src/entity-idp.d.ts +42 -2
- package/types/src/entity-idp.d.ts.map +1 -1
- package/types/src/entity-sp.d.ts +16 -17
- package/types/src/entity-sp.d.ts.map +1 -1
- package/types/src/extractor.d.ts.map +1 -1
- package/types/src/flow.d.ts.map +1 -1
- package/types/src/libsamlSoap.d.ts +9 -2
- package/types/src/libsamlSoap.d.ts.map +1 -1
- package/types/src/soap.d.ts +5 -25
- package/types/src/soap.d.ts.map +1 -1
- package/types/src/types.d.ts +1 -0
- package/types/src/types.d.ts.map +1 -1
|
@@ -1,424 +1,538 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file binding-artifact.ts
|
|
3
|
-
* @
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* @desc Binding-level API for SAML 2.0 HTTP-Artifact Binding
|
|
4
|
+
*
|
|
5
|
+
* The Artifact binding has two distinct layers:
|
|
6
|
+
* 1. Front-channel delivery of `SAMLart`
|
|
7
|
+
* 2. Back-channel SOAP `ArtifactResolve` / `ArtifactResponse`
|
|
6
8
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import { ParserType, StatusCode, wording } from './urn.js';
|
|
9
|
-
import * as crypto from "node:crypto";
|
|
9
|
+
import { ParserType, StatusCode, namespace, wording } from './urn.js';
|
|
10
10
|
import libsaml from './libsaml.js';
|
|
11
11
|
import libsamlSoap from './libsamlSoap.js';
|
|
12
12
|
import utility, { get } from './utility.js';
|
|
13
|
-
import { randomUUID } from 'node:crypto';
|
|
14
13
|
import postBinding from './binding-post.js';
|
|
15
|
-
import { artifactResolveFields, extract, loginRequestFields,
|
|
16
|
-
import { verifyTime } from
|
|
17
|
-
import { sendArtifactResolve } from
|
|
14
|
+
import { artifactResolveFields, artifactResponseFields, extract, loginRequestFields, } from './extractor.js';
|
|
15
|
+
import { verifyTime } from './validator.js';
|
|
16
|
+
import { sendArtifactResolve } from './soap.js';
|
|
17
|
+
import { generateArtifactId as generateArtifactIdUtil, validateArtifact, } from './artifact.js';
|
|
18
18
|
import { applyAuthnRequestEnhancements } from './saml2-enhancements-integration.js';
|
|
19
|
+
import { flow } from './flow.js';
|
|
19
20
|
const binding = wording.binding;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
const SAML_ARTIFACT_PARAM = 'SAMLart';
|
|
22
|
+
function fail(code) {
|
|
23
|
+
throw code;
|
|
24
|
+
}
|
|
25
|
+
function normalizeBoolean(value) {
|
|
26
|
+
if (typeof value === 'boolean') {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === 'string') {
|
|
30
|
+
const lowered = value.trim().toLowerCase();
|
|
31
|
+
if (lowered === 'true') {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
if (lowered === 'false') {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function normalizeIndex(value, fallback) {
|
|
41
|
+
const parsed = Number.parseInt(String(value ?? fallback), 10);
|
|
42
|
+
return Number.isNaN(parsed) ? fallback : parsed;
|
|
43
|
+
}
|
|
44
|
+
function getServiceCandidates(entityMeta, key) {
|
|
45
|
+
const rawValue = entityMeta?.meta?.[key];
|
|
46
|
+
if (!rawValue) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const normalizeRecord = (item, order, bindingKey) => {
|
|
50
|
+
if (item == null) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (typeof item === 'string') {
|
|
54
|
+
return {
|
|
55
|
+
binding: String(bindingKey || ''),
|
|
56
|
+
location: item,
|
|
57
|
+
index: order,
|
|
58
|
+
isDefault: order === 0 ? true : null,
|
|
59
|
+
order,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const location = String(item.location ??
|
|
63
|
+
item.Location ??
|
|
64
|
+
item.responseLocation ??
|
|
65
|
+
item.ResponseLocation ??
|
|
66
|
+
'').trim();
|
|
67
|
+
if (!location) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
binding: String(item.binding ?? item.Binding ?? bindingKey ?? '').trim(),
|
|
72
|
+
location,
|
|
73
|
+
index: normalizeIndex(item.index ?? item.Index, order),
|
|
74
|
+
isDefault: normalizeBoolean(item.isDefault ?? item.IsDefault ?? item.default),
|
|
75
|
+
order,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
if (Array.isArray(rawValue)) {
|
|
79
|
+
return rawValue
|
|
80
|
+
.map((item, index) => normalizeRecord(item, index))
|
|
81
|
+
.filter((item) => item !== null);
|
|
82
|
+
}
|
|
83
|
+
if (typeof rawValue === 'object') {
|
|
84
|
+
const looksLikeSingleRecord = ('location' in rawValue ||
|
|
85
|
+
'Location' in rawValue ||
|
|
86
|
+
'binding' in rawValue ||
|
|
87
|
+
'Binding' in rawValue);
|
|
88
|
+
if (looksLikeSingleRecord) {
|
|
89
|
+
const record = normalizeRecord(rawValue, 0);
|
|
90
|
+
return record ? [record] : [];
|
|
91
|
+
}
|
|
92
|
+
return Object.entries(rawValue)
|
|
93
|
+
.map(([candidateBinding, candidateLocation], index) => normalizeRecord(candidateLocation, index, candidateBinding))
|
|
94
|
+
.filter((item) => item !== null);
|
|
95
|
+
}
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
function resolveBindingUri(bindingName) {
|
|
99
|
+
if (!bindingName) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const mapped = namespace.binding[bindingName];
|
|
103
|
+
if (typeof mapped === 'string') {
|
|
104
|
+
return mapped;
|
|
105
|
+
}
|
|
106
|
+
return bindingName;
|
|
107
|
+
}
|
|
108
|
+
function pickPreferredService(entityMeta, key, bindingName) {
|
|
109
|
+
const candidates = getServiceCandidates(entityMeta, key);
|
|
110
|
+
if (candidates.length === 0) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const expectedBinding = resolveBindingUri(bindingName);
|
|
114
|
+
const filtered = expectedBinding
|
|
115
|
+
? candidates.filter((item) => item.binding === expectedBinding)
|
|
116
|
+
: candidates;
|
|
117
|
+
const pool = filtered.length > 0 ? filtered : candidates;
|
|
118
|
+
return [...pool].sort((left, right) => {
|
|
119
|
+
const leftScore = left.isDefault === true ? 0 : (left.isDefault === null ? 1 : 2);
|
|
120
|
+
const rightScore = right.isDefault === true ? 0 : (right.isDefault === null ? 1 : 2);
|
|
121
|
+
if (leftScore !== rightScore) {
|
|
122
|
+
return leftScore - rightScore;
|
|
123
|
+
}
|
|
124
|
+
if (left.index !== right.index) {
|
|
125
|
+
return left.index - right.index;
|
|
126
|
+
}
|
|
127
|
+
return left.order - right.order;
|
|
128
|
+
})[0] ?? null;
|
|
129
|
+
}
|
|
130
|
+
function ensureServiceLocation(entityMeta, key, bindingName, errorCode) {
|
|
131
|
+
const expectedBinding = resolveBindingUri(bindingName);
|
|
132
|
+
const candidates = getServiceCandidates(entityMeta, key);
|
|
133
|
+
const filtered = expectedBinding
|
|
134
|
+
? candidates.filter((item) => item.binding === expectedBinding)
|
|
135
|
+
: candidates;
|
|
136
|
+
const candidate = filtered.length > 0
|
|
137
|
+
? [...filtered].sort((left, right) => {
|
|
138
|
+
const leftScore = left.isDefault === true ? 0 : (left.isDefault === null ? 1 : 2);
|
|
139
|
+
const rightScore = right.isDefault === true ? 0 : (right.isDefault === null ? 1 : 2);
|
|
140
|
+
if (leftScore !== rightScore) {
|
|
141
|
+
return leftScore - rightScore;
|
|
142
|
+
}
|
|
143
|
+
if (left.index !== right.index) {
|
|
144
|
+
return left.index - right.index;
|
|
30
145
|
}
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
default:
|
|
37
|
-
throw new Error('ERR_UNDEFINED_PARSERTYPE');
|
|
146
|
+
return left.order - right.order;
|
|
147
|
+
})[0]
|
|
148
|
+
: null;
|
|
149
|
+
if (!candidate?.location) {
|
|
150
|
+
fail(errorCode);
|
|
38
151
|
}
|
|
152
|
+
return candidate.location;
|
|
39
153
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
indexBuffer.writeUInt16BE(endpointIndex, 0);
|
|
56
|
-
// MessageHandle: 20 random bytes
|
|
57
|
-
const messageHandle = crypto.randomBytes(20);
|
|
58
|
-
// Concatenate: 2 + 2 + 20 + 20 = 44 bytes
|
|
59
|
-
const artifactBytes = Buffer.concat([typeCode, indexBuffer, sourceId, messageHandle]);
|
|
60
|
-
// Base64 encode
|
|
61
|
-
return artifactBytes.toString('base64');
|
|
154
|
+
function ensureValidDestination(entityMeta, key, destination, bindingName, errorCode) {
|
|
155
|
+
if (!destination) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const expectedBinding = resolveBindingUri(bindingName);
|
|
159
|
+
const candidates = getServiceCandidates(entityMeta, key);
|
|
160
|
+
const matched = candidates.some((item) => {
|
|
161
|
+
if (expectedBinding && item.binding && item.binding !== expectedBinding) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return item.location === destination;
|
|
165
|
+
});
|
|
166
|
+
if (!matched) {
|
|
167
|
+
fail(errorCode);
|
|
168
|
+
}
|
|
62
169
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
170
|
+
function getArtifactEndpointIndex(entityMeta) {
|
|
171
|
+
return pickPreferredService(entityMeta, 'artifactResolutionService', binding.soap)?.index ?? 0;
|
|
172
|
+
}
|
|
173
|
+
function getArtifactFromRequest(request) {
|
|
174
|
+
const artifact = request?.query?.[SAML_ARTIFACT_PARAM] ?? request?.body?.[SAML_ARTIFACT_PARAM];
|
|
175
|
+
if (typeof artifact !== 'string' || artifact.trim() === '') {
|
|
176
|
+
fail('ERR_MISSING_ARTIFACT');
|
|
177
|
+
}
|
|
178
|
+
const relayState = request?.query?.RelayState ?? request?.body?.RelayState ?? '';
|
|
179
|
+
return {
|
|
180
|
+
artifact,
|
|
181
|
+
relayState,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function ensureArtifactResolveIssueInstant(issueInstant, clockDrifts) {
|
|
185
|
+
if (!issueInstant) {
|
|
186
|
+
fail('ERR_INVALID_ISSUE_INSTANT');
|
|
187
|
+
}
|
|
188
|
+
const issuedAt = new Date(issueInstant);
|
|
189
|
+
if (Number.isNaN(issuedAt.getTime())) {
|
|
190
|
+
fail('ERR_INVALID_ISSUE_INSTANT');
|
|
191
|
+
}
|
|
192
|
+
const expiry = new Date(issuedAt.getTime() + 5 * 60 * 1000).toISOString();
|
|
193
|
+
if (!verifyTime(undefined, expiry, clockDrifts)) {
|
|
194
|
+
fail('ERR_EXPIRED_SESSION');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function buildRawLoginRequest(referenceTagXPath, entity, customTagReplacement) {
|
|
71
198
|
const metadata = {
|
|
72
199
|
idp: entity.idp.entityMeta,
|
|
73
200
|
sp: entity.sp.entityMeta,
|
|
74
|
-
inResponse: entity.inResponse,
|
|
75
|
-
relayState: entity.relayState
|
|
76
201
|
};
|
|
77
202
|
const spSetting = entity.sp.entitySetting;
|
|
78
203
|
let id = '';
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
NameIDFormat: selectedNameIDFormat
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
// 应用 AuthnRequest 增强功能
|
|
104
|
-
if (spSetting.authnRequestEnhancements) {
|
|
105
|
-
rawSamlRequest = applyAuthnRequestEnhancements(rawSamlRequest, spSetting.authnRequestEnhancements);
|
|
106
|
-
}
|
|
107
|
-
const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
|
|
108
|
-
let signedAuthnRequest;
|
|
109
|
-
if (metadata.idp.isWantAuthnRequestsSigned()) {
|
|
110
|
-
signedAuthnRequest = libsaml.constructSAMLSignature({
|
|
111
|
-
referenceTagXPath,
|
|
112
|
-
privateKey: privateKey,
|
|
113
|
-
privateKeyPass,
|
|
114
|
-
signatureAlgorithm: signatureAlgorithm,
|
|
115
|
-
transformationAlgorithms,
|
|
116
|
-
rawSamlMessage: rawSamlRequest,
|
|
117
|
-
isBase64Output: false,
|
|
118
|
-
signingCert: metadata.sp.getX509Certificate('signing'),
|
|
119
|
-
signatureConfig: spSetting.signatureConfig || {
|
|
120
|
-
prefix: 'ds',
|
|
121
|
-
location: {
|
|
122
|
-
reference: "/*[local-name(.)='AuthnRequest']/*[local-name(.)='Issuer']",
|
|
123
|
-
action: 'after'
|
|
124
|
-
},
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
signedAuthnRequest = rawSamlRequest;
|
|
130
|
-
}
|
|
131
|
-
// Construct SOAP envelope with ArtifactResponse containing AuthnRequest
|
|
132
|
-
const soapTemplate = libsaml.replaceTagsByValue(libsaml.defaultArtAuthnRequestTemplate.context, {
|
|
133
|
-
ID: soapId,
|
|
134
|
-
IssueInstant: new Date().toISOString(),
|
|
135
|
-
InResponseTo: metadata.inResponse ?? "",
|
|
204
|
+
if (!metadata.idp || !metadata.sp) {
|
|
205
|
+
fail('ERR_GENERATE_ARTIFACT_LOGIN_REQUEST_MISSING_METADATA');
|
|
206
|
+
}
|
|
207
|
+
const destination = ensureServiceLocation(metadata.idp, 'singleSignOnService', binding.artifact, 'ERR_MISSING_IDP_ARTIFACT_ENDPOINT');
|
|
208
|
+
const artifactAcs = pickPreferredService(metadata.sp, 'assertionConsumerService', binding.artifact)?.location;
|
|
209
|
+
if (!artifactAcs) {
|
|
210
|
+
fail('ERR_MISSING_SP_ARTIFACT_ACS');
|
|
211
|
+
}
|
|
212
|
+
let rawSamlRequest;
|
|
213
|
+
if (spSetting.loginRequestTemplate && customTagReplacement) {
|
|
214
|
+
const info = customTagReplacement(spSetting.loginRequestTemplate.context);
|
|
215
|
+
id = get(info, 'id', '');
|
|
216
|
+
rawSamlRequest = get(info, 'context', '');
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const nameIDFormat = spSetting.nameIDFormat;
|
|
220
|
+
const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
|
|
221
|
+
id = spSetting.generateID();
|
|
222
|
+
rawSamlRequest = libsaml.replaceTagsByValue(libsaml.defaultLoginRequestTemplate.context, {
|
|
223
|
+
ID: id,
|
|
224
|
+
Destination: destination,
|
|
136
225
|
Issuer: metadata.sp.getEntityID(),
|
|
137
|
-
|
|
226
|
+
IssueInstant: new Date().toISOString(),
|
|
227
|
+
AssertionConsumerServiceURL: artifactAcs,
|
|
228
|
+
EntityID: metadata.sp.getEntityID(),
|
|
229
|
+
AllowCreate: spSetting.allowCreate,
|
|
230
|
+
NameIDFormat: selectedNameIDFormat,
|
|
138
231
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
232
|
+
rawSamlRequest = rawSamlRequest.replace(`ProtocolBinding="${namespace.binding.post}"`, `ProtocolBinding="${namespace.binding.artifact}"`);
|
|
233
|
+
}
|
|
234
|
+
if (spSetting.authnRequestEnhancements) {
|
|
235
|
+
rawSamlRequest = applyAuthnRequestEnhancements(rawSamlRequest, spSetting.authnRequestEnhancements);
|
|
236
|
+
}
|
|
237
|
+
if (!metadata.idp.isWantAuthnRequestsSigned()) {
|
|
238
|
+
return {
|
|
239
|
+
id,
|
|
240
|
+
context: rawSamlRequest,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
id,
|
|
245
|
+
context: libsaml.constructSAMLSignature({
|
|
246
|
+
referenceTagXPath,
|
|
247
|
+
privateKey: spSetting.privateKey,
|
|
248
|
+
privateKeyPass: spSetting.privateKeyPass,
|
|
249
|
+
signatureAlgorithm: spSetting.requestSignatureAlgorithm,
|
|
250
|
+
transformationAlgorithms: spSetting.transformationAlgorithms,
|
|
251
|
+
rawSamlMessage: rawSamlRequest,
|
|
147
252
|
isBase64Output: false,
|
|
148
|
-
isMessageSigned: false,
|
|
149
253
|
signingCert: metadata.sp.getX509Certificate('signing'),
|
|
150
|
-
signatureConfig: {
|
|
254
|
+
signatureConfig: (spSetting.signatureConfig || {
|
|
151
255
|
prefix: 'ds',
|
|
152
256
|
location: {
|
|
153
|
-
reference: "/*[local-name(.)='
|
|
154
|
-
action: 'after'
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
id: soapId,
|
|
160
|
-
context: signedSoap,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
throw new Error('ERR_GENERATE_POST_LOGIN_REQUEST_MISSING_METADATA');
|
|
257
|
+
reference: "/*[local-name(.)='AuthnRequest']/*[local-name(.)='Issuer']",
|
|
258
|
+
action: 'after',
|
|
259
|
+
},
|
|
260
|
+
}),
|
|
261
|
+
}),
|
|
262
|
+
};
|
|
164
263
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
264
|
+
async function buildRawLoginResponse(params) {
|
|
265
|
+
if (!pickPreferredService(params.entity.sp.entityMeta, 'assertionConsumerService', binding.artifact)) {
|
|
266
|
+
fail('ERR_MISSING_SP_ARTIFACT_ACS');
|
|
267
|
+
}
|
|
268
|
+
const result = await postBinding.base64LoginResponse({
|
|
269
|
+
...params,
|
|
270
|
+
destinationBinding: binding.artifact,
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
id: result.id,
|
|
274
|
+
context: utility.base64Decode(result.context),
|
|
175
275
|
};
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
signatureAlgorithm: idpSetting.requestSignatureAlgorithm,
|
|
191
|
-
signingCert: metadata.idp.getX509Certificate('signing'),
|
|
276
|
+
}
|
|
277
|
+
function signSoapEnvelope(message, referenceTagXPath, signatureReference, signer) {
|
|
278
|
+
const signerSetting = signer.entitySetting;
|
|
279
|
+
const signingCert = signer.entityMeta.getX509Certificate('signing');
|
|
280
|
+
if (!signerSetting.privateKey || !signingCert) {
|
|
281
|
+
fail('ERR_MISSING_ARTIFACT_RESOLVE_CREDENTIALS');
|
|
282
|
+
}
|
|
283
|
+
return libsaml.constructSAMLSignature({
|
|
284
|
+
referenceTagXPath,
|
|
285
|
+
privateKey: signerSetting.privateKey,
|
|
286
|
+
privateKeyPass: signerSetting.privateKeyPass,
|
|
287
|
+
signatureAlgorithm: signerSetting.requestSignatureAlgorithm,
|
|
288
|
+
transformationAlgorithms: signerSetting.transformationAlgorithms,
|
|
289
|
+
rawSamlMessage: message,
|
|
192
290
|
isBase64Output: false,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const soapBodyContent = `<samlp:ArtifactResponse
|
|
196
|
-
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
|
|
197
|
-
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
|
198
|
-
ID="${samlResponseResult.id}_artifact_resp"
|
|
199
|
-
InResponseTo="${params.requestInfo?.extract?.request?.id || ''}"
|
|
200
|
-
Version="2.0"
|
|
201
|
-
IssueInstant="${new Date().toISOString()}">
|
|
202
|
-
<saml2:Issuer>${metadata.idp.getEntityID()}</saml2:Issuer>
|
|
203
|
-
<samlp:Status>
|
|
204
|
-
<samlp:StatusCode Value="${StatusCode.Success}"/>
|
|
205
|
-
</samlp:Status>
|
|
206
|
-
${samlResponseXml}
|
|
207
|
-
</samlp:ArtifactResponse>`;
|
|
208
|
-
const soapEnvelope = `
|
|
209
|
-
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
210
|
-
<soap:Header/>
|
|
211
|
-
<soap:Body>${soapBodyContent}</soap:Body>
|
|
212
|
-
</soap:Envelope>`;
|
|
213
|
-
// Sign the SOAP Envelope
|
|
214
|
-
const signedSoapEnvelope = libsaml.constructSAMLSignature({
|
|
215
|
-
...config,
|
|
216
|
-
rawSamlMessage: soapEnvelope,
|
|
217
|
-
transformationAlgorithms: spSetting.transformationAlgorithms,
|
|
218
|
-
isMessageSigned: true,
|
|
219
|
-
referenceTagXPath: "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']",
|
|
291
|
+
isMessageSigned: false,
|
|
292
|
+
signingCert,
|
|
220
293
|
signatureConfig: {
|
|
221
294
|
prefix: 'ds',
|
|
222
295
|
location: {
|
|
223
|
-
reference:
|
|
224
|
-
action: '
|
|
296
|
+
reference: signatureReference,
|
|
297
|
+
action: 'after',
|
|
225
298
|
},
|
|
226
299
|
},
|
|
227
300
|
});
|
|
228
|
-
|
|
301
|
+
}
|
|
302
|
+
function createArtifactResolveRequest(params) {
|
|
303
|
+
validateArtifact(params.artifact, params.responder.entityMeta.getEntityID());
|
|
304
|
+
const destination = ensureServiceLocation(params.responder.entityMeta, 'artifactResolutionService', binding.soap, 'ERR_MISSING_ARTIFACT_RESOLUTION_SERVICE');
|
|
305
|
+
const id = params.requester.entitySetting.generateID();
|
|
306
|
+
const soapResolve = libsaml.replaceTagsByValue(libsaml.defaultArtifactResolveTemplate.context, {
|
|
307
|
+
ID: id,
|
|
308
|
+
Destination: destination,
|
|
309
|
+
Issuer: params.requester.entityMeta.getEntityID(),
|
|
310
|
+
IssueInstant: new Date().toISOString(),
|
|
311
|
+
Art: params.artifact,
|
|
312
|
+
});
|
|
229
313
|
return {
|
|
230
|
-
id
|
|
231
|
-
|
|
314
|
+
id,
|
|
315
|
+
artifact: params.artifact,
|
|
316
|
+
entityEndpoint: destination,
|
|
317
|
+
type: 'ArtifactResolve',
|
|
318
|
+
context: signSoapEnvelope(soapResolve, "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResolve']", "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResolve']/*[local-name(.)='Issuer']", params.requester),
|
|
232
319
|
};
|
|
233
320
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
321
|
+
async function parseArtifactResolveRequest(params) {
|
|
322
|
+
const { requester, responder, xml } = params;
|
|
323
|
+
const validXml = await libsaml.isValidXml(xml, true).catch(() => false);
|
|
324
|
+
if (validXml !== true) {
|
|
325
|
+
fail('ERR_EXCEPTION_VALIDATE_XML');
|
|
326
|
+
}
|
|
327
|
+
const verifiedSoap = await libsamlSoap.verifyAndDecryptSoapMessage(xml, {
|
|
328
|
+
metadata: requester.entityMeta,
|
|
329
|
+
});
|
|
330
|
+
if (!verifiedSoap.verified || verifiedSoap.type !== 'ArtifactResolve') {
|
|
331
|
+
fail('ERR_FAIL_TO_VERIFY_SIGNATURE');
|
|
332
|
+
}
|
|
333
|
+
const extracted = extract(xml, artifactResolveFields);
|
|
334
|
+
if (extracted?.request?.version && extracted.request.version !== '2.0') {
|
|
335
|
+
fail('ERR_UNSUPPORTED_SAML_VERSION');
|
|
336
|
+
}
|
|
337
|
+
if (extracted?.issuer !== requester.entityMeta.getEntityID()) {
|
|
338
|
+
fail('ERR_UNMATCH_ISSUER');
|
|
339
|
+
}
|
|
340
|
+
ensureValidDestination(responder.entityMeta, 'artifactResolutionService', extracted?.request?.destination, binding.soap, 'ERR_INVALID_ARTIFACT_RESOLVE_DESTINATION');
|
|
341
|
+
ensureArtifactResolveIssueInstant(extracted?.request?.issueInstant, responder.entitySetting.clockDrifts);
|
|
342
|
+
const artifactData = validateArtifact(extracted?.artifact, responder.entityMeta.getEntityID());
|
|
343
|
+
return {
|
|
344
|
+
soapContent: xml,
|
|
345
|
+
samlContent: verifiedSoap.message,
|
|
346
|
+
extract: extracted,
|
|
347
|
+
artifact: extracted.artifact,
|
|
348
|
+
artifactData,
|
|
247
349
|
};
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
350
|
+
}
|
|
351
|
+
function createArtifactResolveResponse(params) {
|
|
352
|
+
const { requester, responder } = params;
|
|
353
|
+
const id = responder.entitySetting.generateID();
|
|
354
|
+
const destination = pickPreferredService(requester.entityMeta, 'artifactResolutionService', binding.soap)?.location || '';
|
|
355
|
+
const statusCode = params.statusCode || StatusCode.Success;
|
|
356
|
+
const template = `<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><samlp:ArtifactResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" InResponseTo="{InResponseTo}" Version="2.0" IssueInstant="{IssueInstant}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status>{SamlMessage}</samlp:ArtifactResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>`;
|
|
357
|
+
let soapResponse = libsaml.replaceTagsByValue(template, {
|
|
358
|
+
ID: id,
|
|
359
|
+
InResponseTo: params.inResponseTo,
|
|
360
|
+
IssueInstant: new Date().toISOString(),
|
|
361
|
+
Issuer: responder.entityMeta.getEntityID(),
|
|
362
|
+
StatusCode: statusCode,
|
|
363
|
+
SamlMessage: params.samlMessage || '',
|
|
251
364
|
});
|
|
252
|
-
if (
|
|
253
|
-
|
|
365
|
+
if (destination) {
|
|
366
|
+
soapResponse = soapResponse.replace(/(<samlp:ArtifactResponse\b[^>]*IssueInstant="[^"]+")/, `$1 Destination="${destination}"`);
|
|
254
367
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
368
|
+
return {
|
|
369
|
+
id,
|
|
370
|
+
context: signSoapEnvelope(soapResponse, "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']", "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']/*[local-name(.)='Issuer']", responder),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function parseArtifactResolveResponse(params) {
|
|
374
|
+
const validXml = await libsaml.isValidXml(params.xml, true).catch(() => false);
|
|
375
|
+
if (validXml !== true) {
|
|
376
|
+
fail('ERR_INVALID_XML');
|
|
259
377
|
}
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
378
|
+
const verifiedSoap = await libsamlSoap.verifyAndDecryptSoapMessage(params.xml, {
|
|
379
|
+
metadata: params.responder.entityMeta,
|
|
380
|
+
});
|
|
381
|
+
if (!verifiedSoap.verified || verifiedSoap.type !== 'ArtifactResponse') {
|
|
382
|
+
fail('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
383
|
+
}
|
|
384
|
+
const extracted = extract(params.xml, artifactResponseFields);
|
|
385
|
+
if (extracted?.response?.version && extracted.response.version !== '2.0') {
|
|
386
|
+
fail('ERR_UNSUPPORTED_SAML_VERSION');
|
|
387
|
+
}
|
|
388
|
+
if (extracted?.issuer !== params.responder.entityMeta.getEntityID()) {
|
|
389
|
+
fail('ERR_UNMATCH_ISSUER');
|
|
390
|
+
}
|
|
391
|
+
if (params.inResponseTo && extracted?.response?.inResponseTo !== params.inResponseTo) {
|
|
392
|
+
fail('ERR_UNMATCH_IN_RESPONSE_TO');
|
|
393
|
+
}
|
|
394
|
+
ensureValidDestination(params.requester.entityMeta, 'artifactResolutionService', extracted?.response?.destination, binding.soap, 'ERR_INVALID_ARTIFACT_RESPONSE_DESTINATION');
|
|
395
|
+
if (extracted?.status !== StatusCode.Success) {
|
|
396
|
+
fail('ERR_UNDEFINED_STATUS');
|
|
397
|
+
}
|
|
398
|
+
if (!verifiedSoap.resolvedMessage) {
|
|
399
|
+
fail('ERR_EMPTY_ARTIFACT_RESPONSE');
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
soapContent: params.xml,
|
|
403
|
+
samlContent: verifiedSoap.resolvedMessage,
|
|
404
|
+
extract: extracted,
|
|
263
405
|
};
|
|
264
|
-
// Validation
|
|
265
|
-
const targetEntityMetadata = sp.entityMeta;
|
|
266
|
-
const issuer = targetEntityMetadata.getEntityID();
|
|
267
|
-
const extractedProperties = parseResult.extract;
|
|
268
|
-
// Check issuer
|
|
269
|
-
if (extractedProperties.issuer !== issuer) {
|
|
270
|
-
return Promise.reject('ERR_UNMATCH_ISSUER');
|
|
271
|
-
}
|
|
272
|
-
// Check time validity (5 minutes from issue instant)
|
|
273
|
-
const issueInstant = new Date(extractedProperties.request.issueInstant);
|
|
274
|
-
const expiryTime = new Date(issueInstant.getTime() + 5 * 60 * 1000);
|
|
275
|
-
if (!verifyTime(undefined, expiryTime.toISOString(), sp.entitySetting.clockDrifts)) {
|
|
276
|
-
return Promise.reject('ERR_EXPIRED_SESSION');
|
|
277
|
-
}
|
|
278
|
-
return Promise.resolve(parseResult);
|
|
279
406
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
async function parseLoginResponseResolve(params) {
|
|
289
|
-
const { idp, sp, art } = params;
|
|
290
|
-
const metadata = {
|
|
291
|
-
idp: idp.entityMeta,
|
|
292
|
-
sp: sp.entityMeta,
|
|
407
|
+
function createLoginRequest(referenceTagXPath, entity, customTagReplacement) {
|
|
408
|
+
const request = buildRawLoginRequest(referenceTagXPath, entity, customTagReplacement);
|
|
409
|
+
const artifact = generateArtifactIdUtil(entity.sp.entityMeta.getEntityID(), getArtifactEndpointIndex(entity.sp.entityMeta));
|
|
410
|
+
return {
|
|
411
|
+
id: request.id,
|
|
412
|
+
artifact,
|
|
413
|
+
context: artifact,
|
|
414
|
+
samlContent: request.context,
|
|
293
415
|
};
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
416
|
+
}
|
|
417
|
+
async function createLoginResponse(params) {
|
|
418
|
+
const response = await buildRawLoginResponse(params);
|
|
419
|
+
const artifact = generateArtifactIdUtil(params.entity.idp.entityMeta.getEntityID(), getArtifactEndpointIndex(params.entity.idp.entityMeta));
|
|
420
|
+
return {
|
|
421
|
+
id: response.id,
|
|
422
|
+
artifact,
|
|
423
|
+
context: artifact,
|
|
424
|
+
samlContent: response.context,
|
|
297
425
|
};
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
IssueInstant: new Date().toISOString(),
|
|
308
|
-
Art: art
|
|
426
|
+
}
|
|
427
|
+
async function resolveArtifact(params) {
|
|
428
|
+
const resolveRequest = createArtifactResolveRequest(params);
|
|
429
|
+
const responseXml = await sendArtifactResolve(resolveRequest.entityEndpoint, resolveRequest.context);
|
|
430
|
+
const resolved = await parseArtifactResolveResponse({
|
|
431
|
+
requester: params.requester,
|
|
432
|
+
responder: params.responder,
|
|
433
|
+
xml: responseXml,
|
|
434
|
+
inResponseTo: resolveRequest.id,
|
|
309
435
|
});
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
if (!verified) {
|
|
325
|
-
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
326
|
-
}
|
|
327
|
-
samlContent = verifiedAssertionNode;
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm, transformationAlgorithms } = spSetting;
|
|
331
|
-
// Sign the ArtifactResolve request
|
|
332
|
-
const signatureSoap = libsaml.constructSAMLSignature({
|
|
333
|
-
referenceTagXPath: "//*[local-name(.)='ArtifactResolve']",
|
|
334
|
-
isMessageSigned: false,
|
|
335
|
-
isBase64Output: false,
|
|
336
|
-
transformationAlgorithms: transformationAlgorithms,
|
|
337
|
-
privateKey: privateKey,
|
|
338
|
-
privateKeyPass,
|
|
339
|
-
signatureAlgorithm: signatureAlgorithm,
|
|
340
|
-
rawSamlMessage: samlSoapRaw,
|
|
341
|
-
signingCert: metadata.sp.getX509Certificate('signing'),
|
|
342
|
-
signatureConfig: {
|
|
343
|
-
prefix: 'ds',
|
|
344
|
-
location: {
|
|
345
|
-
reference: "//*[local-name(.)='Issuer']",
|
|
346
|
-
action: 'after'
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
samlContent = await sendArtifactResolve(url, signatureSoap);
|
|
351
|
-
// Validate XML
|
|
352
|
-
try {
|
|
353
|
-
await libsaml.isValidXml(samlContent, true);
|
|
354
|
-
}
|
|
355
|
-
catch {
|
|
356
|
-
return Promise.reject('ERR_INVALID_XML');
|
|
357
|
-
}
|
|
358
|
-
await checkStatus(samlContent, parserType, true);
|
|
359
|
-
// Verify and decrypt SOAP response
|
|
360
|
-
const [verified1, verifiedAssertionNode1, isDecryptRequired1] = await libsamlSoap.verifyAndDecryptSoapMessage(samlContent, verificationOptions);
|
|
361
|
-
if (!verified1) {
|
|
362
|
-
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE');
|
|
363
|
-
}
|
|
364
|
-
samlContent = verifiedAssertionNode1;
|
|
365
|
-
// Verify SAML signature and decrypt if needed
|
|
366
|
-
const verificationResult = await libsaml.verifySignature(samlContent, verificationOptions, sp);
|
|
436
|
+
return {
|
|
437
|
+
resolveRequest,
|
|
438
|
+
resolved,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
async function parseResolvedLoginRequestXml(params) {
|
|
442
|
+
let samlContent = params.samlContent;
|
|
443
|
+
const verificationOptions = {
|
|
444
|
+
metadata: params.sp.entityMeta,
|
|
445
|
+
signatureAlgorithm: params.sp.entitySetting.requestSignatureAlgorithm,
|
|
446
|
+
};
|
|
447
|
+
const signatureLooksPresent = /<[^>]*:?Signature\b/.test(samlContent);
|
|
448
|
+
if (params.idp.entityMeta.isWantAuthnRequestsSigned() || signatureLooksPresent) {
|
|
449
|
+
const verificationResult = await libsaml.verifySignature(samlContent, verificationOptions, params.idp);
|
|
367
450
|
if (!verificationResult.status) {
|
|
368
451
|
if (verificationResult.isMessageSigned && !verificationResult.MessageSignatureStatus) {
|
|
369
|
-
|
|
452
|
+
fail('ERR_FAIL_TO_VERIFY_MESSAGE_SIGNATURE');
|
|
370
453
|
}
|
|
371
454
|
if (verificationResult.isAssertionSigned && !verificationResult.AssertionSignatureStatus) {
|
|
372
|
-
|
|
455
|
+
fail('ERR_FAIL_TO_VERIFY_ASSERTION_SIGNATURE');
|
|
373
456
|
}
|
|
374
|
-
|
|
375
|
-
return Promise.reject('ERR_FAIL_TO_DECRYPT_ASSERTION');
|
|
376
|
-
}
|
|
377
|
-
return Promise.reject('ERR_FAIL_TO_VERIFY_SIGNATURE_OR_DECRYPTION');
|
|
457
|
+
fail('ERR_FAIL_TO_VERIFY_SIGNATURE_OR_DECRYPTION');
|
|
378
458
|
}
|
|
379
459
|
samlContent = verificationResult.samlContent;
|
|
380
460
|
}
|
|
381
|
-
// Extract fields
|
|
382
|
-
let extractorFields;
|
|
383
|
-
if (parserType === 'SAMLResponse') {
|
|
384
|
-
extractorFields = getDefaultExtractorFields(parserType, samlContent);
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
extractorFields = getDefaultExtractorFields(parserType, null);
|
|
388
|
-
}
|
|
389
461
|
const parseResult = {
|
|
390
|
-
samlContent
|
|
391
|
-
extract: extract(samlContent,
|
|
462
|
+
samlContent,
|
|
463
|
+
extract: extract(samlContent, loginRequestFields),
|
|
464
|
+
};
|
|
465
|
+
if (parseResult.extract?.issuer !== params.sp.entityMeta.getEntityID()) {
|
|
466
|
+
fail('ERR_UNMATCH_ISSUER');
|
|
467
|
+
}
|
|
468
|
+
return parseResult;
|
|
469
|
+
}
|
|
470
|
+
async function parseLoginRequest(params) {
|
|
471
|
+
const { artifact, relayState } = getArtifactFromRequest(params.request);
|
|
472
|
+
validateArtifact(artifact, params.sp.entityMeta.getEntityID());
|
|
473
|
+
const { resolveRequest, resolved } = await resolveArtifact({
|
|
474
|
+
requester: params.idp,
|
|
475
|
+
responder: params.sp,
|
|
476
|
+
artifact,
|
|
477
|
+
});
|
|
478
|
+
const parseResult = await parseResolvedLoginRequestXml({
|
|
479
|
+
idp: params.idp,
|
|
480
|
+
sp: params.sp,
|
|
481
|
+
samlContent: resolved.samlContent,
|
|
482
|
+
});
|
|
483
|
+
ensureValidDestination(params.idp.entityMeta, 'singleSignOnService', parseResult?.extract?.request?.destination, binding.artifact, 'ERR_INVALID_DESTINATION');
|
|
484
|
+
ensureValidDestination(params.sp.entityMeta, 'assertionConsumerService', parseResult?.extract?.request?.assertionConsumerServiceUrl || parseResult?.extract?.request?.assertionConsumerServiceURL, undefined, 'ERR_INVALID_ASSERTION_CONSUMER_SERVICE');
|
|
485
|
+
return {
|
|
486
|
+
...parseResult,
|
|
487
|
+
artifact,
|
|
488
|
+
relayState,
|
|
489
|
+
artifactResolve: {
|
|
490
|
+
request: resolveRequest,
|
|
491
|
+
response: resolved.extract,
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
async function parseLoginResponse(params) {
|
|
496
|
+
const { artifact, relayState } = getArtifactFromRequest(params.request);
|
|
497
|
+
validateArtifact(artifact, params.idp.entityMeta.getEntityID());
|
|
498
|
+
const { resolveRequest, resolved } = await resolveArtifact({
|
|
499
|
+
requester: params.sp,
|
|
500
|
+
responder: params.idp,
|
|
501
|
+
artifact,
|
|
502
|
+
});
|
|
503
|
+
const parseResult = await flow({
|
|
504
|
+
from: params.idp,
|
|
505
|
+
self: params.sp,
|
|
506
|
+
checkSignature: true,
|
|
507
|
+
parserType: ParserType.SAMLResponse,
|
|
508
|
+
type: 'login',
|
|
509
|
+
binding: binding.post,
|
|
510
|
+
request: {
|
|
511
|
+
body: {
|
|
512
|
+
SAMLResponse: utility.base64Encode(resolved.samlContent),
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
return {
|
|
517
|
+
...parseResult,
|
|
518
|
+
artifact,
|
|
519
|
+
relayState,
|
|
520
|
+
artifactResolve: {
|
|
521
|
+
request: resolveRequest,
|
|
522
|
+
response: resolved.extract,
|
|
523
|
+
},
|
|
392
524
|
};
|
|
393
|
-
// Validation
|
|
394
|
-
const targetEntityMetadata = idp.entityMeta;
|
|
395
|
-
const issuer = targetEntityMetadata.getEntityID();
|
|
396
|
-
const extractedProperties = parseResult.extract;
|
|
397
|
-
// Check issuer
|
|
398
|
-
if (parserType === ParserType.SAMLResponse
|
|
399
|
-
&& extractedProperties
|
|
400
|
-
&& extractedProperties.issuer !== issuer) {
|
|
401
|
-
return Promise.reject('ERR_UNMATCH_ISSUER');
|
|
402
|
-
}
|
|
403
|
-
// Check session time
|
|
404
|
-
if (parserType === 'SAMLResponse'
|
|
405
|
-
&& extractedProperties.sessionIndex.sessionNotOnOrAfter
|
|
406
|
-
&& !verifyTime(undefined, extractedProperties.sessionIndex.sessionNotOnOrAfter, sp.entitySetting.clockDrifts)) {
|
|
407
|
-
return Promise.reject('ERR_EXPIRED_SESSION');
|
|
408
|
-
}
|
|
409
|
-
// Check conditions time
|
|
410
|
-
if (parserType === 'SAMLResponse'
|
|
411
|
-
&& extractedProperties.conditions
|
|
412
|
-
&& !verifyTime(extractedProperties.conditions.notBefore, extractedProperties.conditions.notOnOrAfter, sp.entitySetting.clockDrifts)) {
|
|
413
|
-
return Promise.reject('ERR_SUBJECT_UNCONFIRMED');
|
|
414
|
-
}
|
|
415
|
-
return Promise.resolve(parseResult);
|
|
416
525
|
}
|
|
526
|
+
export const generateArtifactId = generateArtifactIdUtil;
|
|
417
527
|
const artifactBinding = {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
528
|
+
createLoginRequest,
|
|
529
|
+
createLoginResponse,
|
|
530
|
+
parseLoginRequest,
|
|
531
|
+
parseLoginResponse,
|
|
532
|
+
createArtifactResolveRequest,
|
|
533
|
+
parseArtifactResolveRequest,
|
|
534
|
+
createArtifactResolveResponse,
|
|
535
|
+
parseArtifactResolveResponse,
|
|
422
536
|
generateArtifactId,
|
|
423
537
|
};
|
|
424
538
|
export default artifactBinding;
|