samlify 2.11.0 → 2.13.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 +1 -1
- package/build/src/api.js +52 -3
- package/build/src/api.js.map +1 -1
- package/build/src/binding-post.js +236 -182
- package/build/src/binding-post.js.map +1 -1
- package/build/src/binding-redirect.js +303 -215
- package/build/src/binding-redirect.js.map +1 -1
- package/build/src/binding-simplesign.js +285 -137
- package/build/src/binding-simplesign.js.map +1 -1
- package/build/src/entity-idp.js +130 -47
- package/build/src/entity-idp.js.map +1 -1
- package/build/src/entity-sp.js +81 -39
- package/build/src/entity-sp.js.map +1 -1
- package/build/src/entity.js +100 -62
- package/build/src/entity.js.map +1 -1
- package/build/src/extractor.js +119 -155
- package/build/src/extractor.js.map +1 -1
- package/build/src/flow.js +100 -96
- package/build/src/flow.js.map +1 -1
- package/build/src/libsaml.js +318 -261
- package/build/src/libsaml.js.map +1 -1
- package/build/src/metadata-idp.js +60 -30
- package/build/src/metadata-idp.js.map +1 -1
- package/build/src/metadata-sp.js +51 -41
- package/build/src/metadata-sp.js.map +1 -1
- package/build/src/metadata.js +47 -43
- package/build/src/metadata.js.map +1 -1
- package/build/src/options.js +73 -0
- package/build/src/options.js.map +1 -0
- package/build/src/urn.js +28 -1
- package/build/src/urn.js.map +1 -1
- package/build/src/utility.js +165 -83
- package/build/src/utility.js.map +1 -1
- package/build/src/validator.js +27 -10
- package/build/src/validator.js.map +1 -1
- package/package.json +17 -7
- package/types/src/api.d.ts +33 -3
- package/types/src/binding-post.d.ts +67 -34
- package/types/src/binding-redirect.d.ts +58 -31
- package/types/src/binding-simplesign.d.ts +77 -21
- package/types/src/entity-idp.d.ts +40 -31
- package/types/src/entity-sp.d.ts +37 -27
- package/types/src/entity.d.ts +71 -77
- package/types/src/extractor.d.ts +31 -22
- package/types/src/flow.d.ts +24 -2
- package/types/src/libsaml.d.ts +172 -118
- package/types/src/metadata-idp.d.ts +27 -11
- package/types/src/metadata-sp.d.ts +29 -19
- package/types/src/metadata.d.ts +59 -34
- package/types/src/options.d.ts +37 -0
- package/types/src/types.d.ts +250 -24
- package/types/src/urn.d.ts +7 -0
- package/types/src/utility.d.ts +144 -89
- package/types/src/validator.d.ts +21 -0
- package/.circleci/config.yml +0 -98
- package/.editorconfig +0 -19
- package/.github/FUNDING.yml +0 -1
- package/.github/workflows/deploy-docs.yml +0 -56
- package/.pre-commit.sh +0 -15
- package/.snyk +0 -4
- package/Makefile +0 -25
- package/index.ts +0 -28
- package/src/api.ts +0 -36
- package/src/binding-post.ts +0 -336
- package/src/binding-redirect.ts +0 -335
- package/src/binding-simplesign.ts +0 -231
- package/src/entity-idp.ts +0 -145
- package/src/entity-sp.ts +0 -114
- package/src/entity.ts +0 -243
- package/src/extractor.ts +0 -399
- package/src/flow.ts +0 -469
- package/src/libsaml.ts +0 -777
- package/src/metadata-idp.ts +0 -146
- package/src/metadata-sp.ts +0 -203
- package/src/metadata.ts +0 -166
- package/src/types.ts +0 -127
- package/src/urn.ts +0 -210
- package/src/utility.ts +0 -231
- package/src/validator.ts +0 -44
- package/tsconfig.json +0 -41
- package/tslint.json +0 -35
- package/types.d.ts +0 -2
- package/vitest.config.ts +0 -12
|
@@ -48,10 +48,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
48
48
|
};
|
|
49
49
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
50
|
/**
|
|
51
|
-
* @file binding-redirect.ts
|
|
52
|
-
* @author tngan
|
|
53
|
-
* @desc Binding-level API
|
|
54
|
-
|
|
51
|
+
* @file binding-redirect.ts
|
|
52
|
+
* @author tngan
|
|
53
|
+
* @desc Binding-level API for SAML HTTP-Redirect. Builds signed/unsigned
|
|
54
|
+
* redirect URLs for login/logout requests and responses.
|
|
55
|
+
*/
|
|
55
56
|
var utility_1 = __importStar(require("./utility"));
|
|
56
57
|
var libsaml_1 = __importDefault(require("./libsaml"));
|
|
57
58
|
var url = __importStar(require("url"));
|
|
@@ -59,31 +60,28 @@ var urn_1 = require("./urn");
|
|
|
59
60
|
var binding = urn_1.wording.binding;
|
|
60
61
|
var urlParams = urn_1.wording.urlParams;
|
|
61
62
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* @param
|
|
65
|
-
* @param
|
|
66
|
-
* @param
|
|
67
|
-
|
|
68
|
-
*/
|
|
63
|
+
* Build a `key=value` URL fragment prefixed with the correct separator.
|
|
64
|
+
*
|
|
65
|
+
* @param param key name
|
|
66
|
+
* @param value key value
|
|
67
|
+
* @param first when true, use `?` instead of `&`
|
|
68
|
+
*/
|
|
69
69
|
function pvPair(param, value, first) {
|
|
70
70
|
return (first === true ? '?' : '&') + param + '=' + value;
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* @param
|
|
77
|
-
* @
|
|
78
|
-
|
|
79
|
-
* @return {string}
|
|
80
|
-
*/
|
|
73
|
+
* Compose the final redirect URL, deflate/base64/urlencode the SAML message,
|
|
74
|
+
* optionally append the detached signature.
|
|
75
|
+
*
|
|
76
|
+
* @param opts redirect configuration
|
|
77
|
+
* @returns absolute redirect URL
|
|
78
|
+
*/
|
|
81
79
|
function buildRedirectURL(opts) {
|
|
82
80
|
var baseUrl = opts.baseUrl, type = opts.type, isSigned = opts.isSigned, context = opts.context, entitySetting = opts.entitySetting;
|
|
83
81
|
var _a = opts.relayState, relayState = _a === void 0 ? '' : _a;
|
|
84
82
|
var noParams = (url.parse(baseUrl).query || []).length === 0;
|
|
85
83
|
var queryParam = libsaml_1.default.getQueryParamByType(type);
|
|
86
|
-
//
|
|
84
|
+
// SAML redirect binding: deflate → base64 → URL-encode.
|
|
87
85
|
var samlRequest = encodeURIComponent(utility_1.default.base64Encode(utility_1.default.deflateString(context)));
|
|
88
86
|
if (relayState !== '') {
|
|
89
87
|
relayState = pvPair(urlParams.relayState, encodeURIComponent(relayState));
|
|
@@ -98,65 +96,106 @@ function buildRedirectURL(opts) {
|
|
|
98
96
|
return baseUrl + pvPair(queryParam, samlRequest + relayState, noParams);
|
|
99
97
|
}
|
|
100
98
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* @param
|
|
104
|
-
* @
|
|
105
|
-
|
|
106
|
-
|
|
99
|
+
* Build a redirect URL carrying a SAML AuthnRequest.
|
|
100
|
+
*
|
|
101
|
+
* @param entity `{ idp, sp }` handles
|
|
102
|
+
* @param customTagReplacement optional custom template transformer
|
|
103
|
+
* @param relayState per-request RelayState; falls back to `entitySetting.relayState`
|
|
104
|
+
* @param forceAuthn per-request `ForceAuthn` flag (saml-core §3.4.1)
|
|
105
|
+
* @param assertionConsumerServiceIndex per-request ACS index (saml-core §3.4.1).
|
|
106
|
+
* Mutually exclusive with `AssertionConsumerServiceURL` / `ProtocolBinding`;
|
|
107
|
+
* when supplied, both of those attributes are dropped from the rendered XML.
|
|
108
|
+
* @returns id + redirect URL wrapped in a {@link BindingContext}
|
|
109
|
+
*/
|
|
110
|
+
function loginRequestRedirectURL(entity, customTagReplacement, relayState, forceAuthn, assertionConsumerServiceIndex) {
|
|
111
|
+
var _a, _b;
|
|
107
112
|
var metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta };
|
|
108
113
|
var spSetting = entity.sp.entitySetting;
|
|
109
114
|
var id = '';
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
115
|
+
/* v8 ignore start */
|
|
116
|
+
if (!metadata.idp || !metadata.sp) {
|
|
117
|
+
throw new Error('ERR_GENERATE_REDIRECT_LOGIN_REQUEST_MISSING_METADATA');
|
|
118
|
+
}
|
|
119
|
+
/* v8 ignore stop */
|
|
120
|
+
var base = metadata.idp.getSingleSignOnService(binding.redirect);
|
|
121
|
+
// saml-bindings §3.4 / saml-metadata §2.4.3: the IdP must declare a
|
|
122
|
+
// <SingleSignOnService> entry with the HTTP-Redirect Binding URI.
|
|
123
|
+
// When that endpoint is absent, getSingleSignOnService returns the raw
|
|
124
|
+
// service map (an object) rather than a URL string — surface a clear
|
|
125
|
+
// error instead of letting it crash inside url.parse downstream.
|
|
126
|
+
if (typeof base !== 'string') {
|
|
127
|
+
throw new Error('ERR_NO_REDIRECT_SSO_ENDPOINT');
|
|
128
|
+
}
|
|
129
|
+
var rawSamlRequest;
|
|
130
|
+
if (customTagReplacement) {
|
|
131
|
+
// saml-bindings §3.4 — the AuthnRequest template is informative, not
|
|
132
|
+
// normative. Honour the callback regardless of whether the caller
|
|
133
|
+
// supplied a custom template (closes #549). Pass the user-supplied
|
|
134
|
+
// template when present; otherwise the library default.
|
|
135
|
+
var templateContext = (_b = (_a = spSetting.loginRequestTemplate) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : libsaml_1.default.defaultLoginRequestTemplate.context;
|
|
136
|
+
var info = customTagReplacement(templateContext);
|
|
137
|
+
id = (0, utility_1.get)(info, 'id');
|
|
138
|
+
rawSamlRequest = (0, utility_1.get)(info, 'context');
|
|
139
|
+
// Support callback returning { context: string } or { context: { context: string } }.
|
|
140
|
+
if (typeof rawSamlRequest === 'object' && rawSamlRequest !== null && 'context' in rawSamlRequest) {
|
|
141
|
+
rawSamlRequest = rawSamlRequest.context;
|
|
136
142
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
var nameIDFormat = spSetting.nameIDFormat;
|
|
146
|
+
var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
|
|
147
|
+
id = spSetting.generateID();
|
|
148
|
+
// saml-core §3.4.1 — `AssertionConsumerServiceIndex` is mutually
|
|
149
|
+
// exclusive with `AssertionConsumerServiceURL` / `ProtocolBinding`.
|
|
150
|
+
// When the caller supplies the index we set the URL+ProtocolBinding
|
|
151
|
+
// tags to undefined so `replaceTagsByValue` drops both attributes
|
|
152
|
+
// from the rendered XML (closes #437).
|
|
153
|
+
var useAcsIndex = assertionConsumerServiceIndex !== undefined;
|
|
154
|
+
var tags = {
|
|
155
|
+
ID: id,
|
|
156
|
+
Destination: base,
|
|
157
|
+
Issuer: metadata.sp.getEntityID(),
|
|
158
|
+
IssueInstant: new Date().toISOString(),
|
|
159
|
+
NameIDFormat: selectedNameIDFormat,
|
|
160
|
+
ProtocolBinding: useAcsIndex
|
|
161
|
+
? undefined
|
|
162
|
+
: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
|
|
163
|
+
AssertionConsumerServiceURL: useAcsIndex
|
|
164
|
+
? undefined
|
|
165
|
+
: metadata.sp.getAssertionConsumerService(binding.post),
|
|
166
|
+
AssertionConsumerServiceIndex: assertionConsumerServiceIndex,
|
|
167
|
+
EntityID: metadata.sp.getEntityID(),
|
|
168
|
+
AllowCreate: spSetting.allowCreate,
|
|
169
|
+
// saml-core §3.4.1 — `replaceTagsByValue` drops the attribute when
|
|
170
|
+
// `forceAuthn` is undefined, matching `use="optional"`.
|
|
171
|
+
ForceAuthn: forceAuthn,
|
|
147
172
|
};
|
|
173
|
+
rawSamlRequest = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLoginRequestTemplate.context, tags);
|
|
148
174
|
}
|
|
149
|
-
|
|
175
|
+
return {
|
|
176
|
+
id: id,
|
|
177
|
+
context: buildRedirectURL({
|
|
178
|
+
context: rawSamlRequest,
|
|
179
|
+
type: urlParams.samlRequest,
|
|
180
|
+
isSigned: metadata.sp.isAuthnRequestSigned(),
|
|
181
|
+
entitySetting: spSetting,
|
|
182
|
+
baseUrl: base,
|
|
183
|
+
relayState: relayState !== null && relayState !== void 0 ? relayState : spSetting.relayState,
|
|
184
|
+
}),
|
|
185
|
+
};
|
|
150
186
|
}
|
|
151
187
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* @param
|
|
155
|
-
* @param
|
|
156
|
-
* @param
|
|
157
|
-
* @param
|
|
158
|
-
|
|
188
|
+
* Build a redirect URL carrying a SAML login Response.
|
|
189
|
+
*
|
|
190
|
+
* @param requestInfo parsed request used to link `InResponseTo`
|
|
191
|
+
* @param entity `{ idp, sp }` handles
|
|
192
|
+
* @param user authenticated user
|
|
193
|
+
* @param relayState caller-supplied redirect URL
|
|
194
|
+
* @param customTagReplacement optional custom template transformer
|
|
195
|
+
* @returns id + redirect URL wrapped in a {@link BindingContext}
|
|
196
|
+
*/
|
|
159
197
|
function loginResponseRedirectURL(requestInfo, entity, user, relayState, customTagReplacement) {
|
|
198
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
160
199
|
if (user === void 0) { user = {}; }
|
|
161
200
|
var idpSetting = entity.idp.entitySetting;
|
|
162
201
|
var spSetting = entity.sp.entitySetting;
|
|
@@ -165,173 +204,222 @@ function loginResponseRedirectURL(requestInfo, entity, user, relayState, customT
|
|
|
165
204
|
sp: entity.sp.entityMeta,
|
|
166
205
|
};
|
|
167
206
|
var id = idpSetting.generateID();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
prefix: 'ds',
|
|
221
|
-
location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
|
|
222
|
-
} }));
|
|
207
|
+
/* v8 ignore start */
|
|
208
|
+
if (!metadata.idp || !metadata.sp) {
|
|
209
|
+
throw new Error('ERR_GENERATE_REDIRECT_LOGIN_RESPONSE_MISSING_METADATA');
|
|
210
|
+
}
|
|
211
|
+
/* v8 ignore stop */
|
|
212
|
+
var base = metadata.sp.getAssertionConsumerService(binding.redirect);
|
|
213
|
+
// saml-bindings §3.4 / saml-metadata §2.4.3: the SP must declare an
|
|
214
|
+
// <AssertionConsumerService> entry with the HTTP-Redirect Binding URI.
|
|
215
|
+
// When that endpoint is absent, getAssertionConsumerService returns
|
|
216
|
+
// undefined or the raw service list — reject with a clear error rather
|
|
217
|
+
// than crashing in url.parse.
|
|
218
|
+
if (typeof base !== 'string') {
|
|
219
|
+
throw new Error('ERR_NO_REDIRECT_SSO_ENDPOINT');
|
|
220
|
+
}
|
|
221
|
+
var rawSamlResponse;
|
|
222
|
+
var nameIDFormat = idpSetting.nameIDFormat;
|
|
223
|
+
var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
|
|
224
|
+
var nowTime = new Date();
|
|
225
|
+
var fiveMinutesLaterTime = new Date(nowTime.getTime() + 300000);
|
|
226
|
+
var tvalue = {
|
|
227
|
+
ID: id,
|
|
228
|
+
AssertionID: idpSetting.generateID(),
|
|
229
|
+
Destination: base,
|
|
230
|
+
SubjectRecipient: base,
|
|
231
|
+
Issuer: metadata.idp.getEntityID(),
|
|
232
|
+
Audience: metadata.sp.getEntityID(),
|
|
233
|
+
EntityID: metadata.sp.getEntityID(),
|
|
234
|
+
IssueInstant: nowTime.toISOString(),
|
|
235
|
+
AssertionConsumerServiceURL: base,
|
|
236
|
+
StatusCode: urn_1.namespace.statusCode.success,
|
|
237
|
+
ConditionsNotBefore: nowTime.toISOString(),
|
|
238
|
+
ConditionsNotOnOrAfter: fiveMinutesLaterTime.toISOString(),
|
|
239
|
+
SubjectConfirmationDataNotOnOrAfter: fiveMinutesLaterTime.toISOString(),
|
|
240
|
+
NameIDFormat: selectedNameIDFormat,
|
|
241
|
+
NameID: user.email || '',
|
|
242
|
+
InResponseTo: (0, utility_1.get)(requestInfo, 'extract.request.id', ''),
|
|
243
|
+
AuthnStatement: '',
|
|
244
|
+
AttributeStatement: '',
|
|
245
|
+
};
|
|
246
|
+
if (customTagReplacement) {
|
|
247
|
+
// saml-bindings §3.4 — honour the callback even when the caller did
|
|
248
|
+
// not override `loginResponseTemplate` (closes #549). Prefer the
|
|
249
|
+
// user-supplied template, then the tag-prefixed default (closes #388),
|
|
250
|
+
// and finally the library default.
|
|
251
|
+
var templateContext = (_e = (_b = (_a = idpSetting.loginResponseTemplate) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : (_d = (_c = idpSetting.tagPrefixedDefaults) === null || _c === void 0 ? void 0 : _c.loginResponseTemplate) === null || _d === void 0 ? void 0 : _d.context) !== null && _e !== void 0 ? _e : libsaml_1.default.defaultLoginResponseTemplate.context;
|
|
252
|
+
var template = customTagReplacement(templateContext);
|
|
253
|
+
id = (0, utility_1.get)(template, 'id');
|
|
254
|
+
rawSamlResponse = (0, utility_1.get)(template, 'context');
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
if (requestInfo !== null && ((_f = requestInfo.extract) === null || _f === void 0 ? void 0 : _f.request)) {
|
|
258
|
+
tvalue.InResponseTo = requestInfo.extract.request.id;
|
|
223
259
|
}
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
260
|
+
// saml-core §1.4: prefer the IdP-rewritten default when tagPrefix is
|
|
261
|
+
// overridden (closes #388); otherwise fall back to the library default.
|
|
262
|
+
var baseTemplate = (_j = (_h = (_g = idpSetting.tagPrefixedDefaults) === null || _g === void 0 ? void 0 : _g.loginResponseTemplate) === null || _h === void 0 ? void 0 : _h.context) !== null && _j !== void 0 ? _j : libsaml_1.default.defaultLoginResponseTemplate.context;
|
|
263
|
+
rawSamlResponse = libsaml_1.default.replaceTagsByValue(baseTemplate, tvalue);
|
|
264
|
+
}
|
|
265
|
+
var privateKey = idpSetting.privateKey, privateKeyPass = idpSetting.privateKeyPass, signatureAlgorithm = idpSetting.requestSignatureAlgorithm;
|
|
266
|
+
var config = {
|
|
267
|
+
privateKey: privateKey,
|
|
268
|
+
privateKeyPass: privateKeyPass,
|
|
269
|
+
signatureAlgorithm: signatureAlgorithm,
|
|
270
|
+
signingCert: metadata.idp.getX509Certificate('signing'),
|
|
271
|
+
isBase64Output: false,
|
|
272
|
+
};
|
|
273
|
+
if (metadata.sp.isWantAssertionsSigned()) {
|
|
274
|
+
rawSamlResponse = libsaml_1.default.constructSAMLSignature(__assign(__assign({}, config), { rawSamlMessage: rawSamlResponse, transformationAlgorithms: spSetting.transformationAlgorithms, referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']", signatureConfig: {
|
|
275
|
+
prefix: 'ds',
|
|
276
|
+
location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
|
|
277
|
+
} }));
|
|
236
278
|
}
|
|
237
|
-
|
|
279
|
+
// SAML response over redirect binding is always signed (see SAML core 3.4.4).
|
|
280
|
+
return {
|
|
281
|
+
id: id,
|
|
282
|
+
context: buildRedirectURL({
|
|
283
|
+
baseUrl: base,
|
|
284
|
+
type: urlParams.samlResponse,
|
|
285
|
+
isSigned: true,
|
|
286
|
+
context: rawSamlResponse,
|
|
287
|
+
entitySetting: idpSetting,
|
|
288
|
+
relayState: relayState,
|
|
289
|
+
}),
|
|
290
|
+
};
|
|
238
291
|
}
|
|
239
292
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* @param
|
|
243
|
-
* @param
|
|
244
|
-
* @
|
|
245
|
-
|
|
293
|
+
* Build a redirect URL carrying a SAML LogoutRequest.
|
|
294
|
+
*
|
|
295
|
+
* @param user currently authenticated user
|
|
296
|
+
* @param entity `{ init, target }` handles
|
|
297
|
+
* @param relayState caller-supplied redirect URL
|
|
298
|
+
* @param customTagReplacement optional custom template transformer
|
|
299
|
+
* @returns id + redirect URL wrapped in a {@link BindingContext}
|
|
300
|
+
*/
|
|
246
301
|
function logoutRequestRedirectURL(user, entity, relayState, customTagReplacement) {
|
|
302
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
247
303
|
var metadata = { init: entity.init.entityMeta, target: entity.target.entityMeta };
|
|
248
304
|
var initSetting = entity.init.entitySetting;
|
|
249
305
|
var id = initSetting.generateID();
|
|
250
306
|
var nameIDFormat = initSetting.nameIDFormat;
|
|
251
307
|
var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
308
|
+
/* v8 ignore start */
|
|
309
|
+
if (!metadata.init || !metadata.target) {
|
|
310
|
+
throw new Error('ERR_GENERATE_REDIRECT_LOGOUT_REQUEST_MISSING_METADATA');
|
|
311
|
+
}
|
|
312
|
+
/* v8 ignore stop */
|
|
313
|
+
var base = metadata.target.getSingleLogoutService(binding.redirect);
|
|
314
|
+
// saml-bindings §3.4 / saml-metadata §2.4.3: the target entity must declare
|
|
315
|
+
// a <SingleLogoutService> with the HTTP-Redirect Binding URI. Otherwise the
|
|
316
|
+
// service map leaks through and crashes inside url.parse downstream.
|
|
317
|
+
if (typeof base !== 'string') {
|
|
318
|
+
throw new Error('ERR_NO_REDIRECT_SLO_ENDPOINT');
|
|
319
|
+
}
|
|
320
|
+
var rawSamlRequest = '';
|
|
321
|
+
var requiredTags = {
|
|
322
|
+
ID: id,
|
|
323
|
+
Destination: base,
|
|
324
|
+
EntityID: metadata.init.getEntityID(),
|
|
325
|
+
Issuer: metadata.init.getEntityID(),
|
|
326
|
+
IssueInstant: new Date().toISOString(),
|
|
327
|
+
NameIDFormat: selectedNameIDFormat,
|
|
328
|
+
NameID: user.logoutNameID,
|
|
329
|
+
SessionIndex: user.sessionIndex,
|
|
330
|
+
};
|
|
331
|
+
if (customTagReplacement) {
|
|
332
|
+
// saml-bindings §3.4 — honour the callback even when the caller did
|
|
333
|
+
// not override `logoutRequestTemplate` (closes #549). Prefer the
|
|
334
|
+
// user-supplied template, then the tag-prefixed default (closes #388),
|
|
335
|
+
// and finally the library default.
|
|
336
|
+
var templateContext = (_e = (_b = (_a = initSetting.logoutRequestTemplate) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : (_d = (_c = initSetting.tagPrefixedDefaults) === null || _c === void 0 ? void 0 : _c.logoutRequestTemplate) === null || _d === void 0 ? void 0 : _d.context) !== null && _e !== void 0 ? _e : libsaml_1.default.defaultLogoutRequestTemplate.context;
|
|
337
|
+
var info = customTagReplacement(templateContext, requiredTags);
|
|
338
|
+
id = (0, utility_1.get)(info, 'id');
|
|
339
|
+
rawSamlRequest = (0, utility_1.get)(info, 'context');
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
var baseTemplate = (_h = (_g = (_f = initSetting.tagPrefixedDefaults) === null || _f === void 0 ? void 0 : _f.logoutRequestTemplate) === null || _g === void 0 ? void 0 : _g.context) !== null && _h !== void 0 ? _h : libsaml_1.default.defaultLogoutRequestTemplate.context;
|
|
343
|
+
rawSamlRequest = libsaml_1.default.replaceTagsByValue(baseTemplate, requiredTags);
|
|
284
344
|
}
|
|
285
|
-
|
|
345
|
+
return {
|
|
346
|
+
id: id,
|
|
347
|
+
context: buildRedirectURL({
|
|
348
|
+
context: rawSamlRequest,
|
|
349
|
+
relayState: relayState,
|
|
350
|
+
type: urlParams.logoutRequest,
|
|
351
|
+
isSigned: entity.target.entitySetting.wantLogoutRequestSigned,
|
|
352
|
+
entitySetting: initSetting,
|
|
353
|
+
baseUrl: base,
|
|
354
|
+
}),
|
|
355
|
+
};
|
|
286
356
|
}
|
|
287
357
|
/**
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* @param
|
|
291
|
-
* @param
|
|
292
|
-
|
|
358
|
+
* Build a redirect URL carrying a SAML LogoutResponse.
|
|
359
|
+
*
|
|
360
|
+
* @param requestInfo parsed request used to link `InResponseTo`
|
|
361
|
+
* @param entity `{ init, target }` handles
|
|
362
|
+
* @param relayState caller-supplied redirect URL
|
|
363
|
+
* @param customTagReplacement optional custom template transformer
|
|
364
|
+
* @returns id + redirect URL wrapped in a {@link BindingContext}
|
|
365
|
+
*/
|
|
293
366
|
function logoutResponseRedirectURL(requestInfo, entity, relayState, customTagReplacement) {
|
|
367
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
294
368
|
var metadata = {
|
|
295
369
|
init: entity.init.entityMeta,
|
|
296
370
|
target: entity.target.entityMeta,
|
|
297
371
|
};
|
|
298
372
|
var initSetting = entity.init.entitySetting;
|
|
299
373
|
var id = initSetting.generateID();
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}),
|
|
374
|
+
/* v8 ignore start */
|
|
375
|
+
if (!metadata.init || !metadata.target) {
|
|
376
|
+
throw new Error('ERR_GENERATE_REDIRECT_LOGOUT_RESPONSE_MISSING_METADATA');
|
|
377
|
+
}
|
|
378
|
+
/* v8 ignore stop */
|
|
379
|
+
var base = metadata.target.getSingleLogoutService(binding.redirect);
|
|
380
|
+
// saml-bindings §3.4 / saml-metadata §2.4.3: same constraint as the
|
|
381
|
+
// logout request path — the target must advertise a HTTP-Redirect SLO
|
|
382
|
+
// endpoint before we can build a URL.
|
|
383
|
+
if (typeof base !== 'string') {
|
|
384
|
+
throw new Error('ERR_NO_REDIRECT_SLO_ENDPOINT');
|
|
385
|
+
}
|
|
386
|
+
var rawSamlResponse;
|
|
387
|
+
if (customTagReplacement) {
|
|
388
|
+
// saml-bindings §3.4 — honour the callback even when the caller did
|
|
389
|
+
// not override `logoutResponseTemplate` (closes #549). Prefer the
|
|
390
|
+
// user-supplied template, then the tag-prefixed default (closes #388),
|
|
391
|
+
// and finally the library default.
|
|
392
|
+
var templateContext = (_e = (_b = (_a = initSetting.logoutResponseTemplate) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : (_d = (_c = initSetting.tagPrefixedDefaults) === null || _c === void 0 ? void 0 : _c.logoutResponseTemplate) === null || _d === void 0 ? void 0 : _d.context) !== null && _e !== void 0 ? _e : libsaml_1.default.defaultLogoutResponseTemplate.context;
|
|
393
|
+
var template = customTagReplacement(templateContext);
|
|
394
|
+
id = (0, utility_1.get)(template, 'id');
|
|
395
|
+
rawSamlResponse = (0, utility_1.get)(template, 'context');
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
var tvalue = {
|
|
399
|
+
ID: id,
|
|
400
|
+
Destination: base,
|
|
401
|
+
Issuer: metadata.init.getEntityID(),
|
|
402
|
+
EntityID: metadata.init.getEntityID(),
|
|
403
|
+
IssueInstant: new Date().toISOString(),
|
|
404
|
+
StatusCode: urn_1.namespace.statusCode.success,
|
|
332
405
|
};
|
|
406
|
+
if (requestInfo && requestInfo.extract && requestInfo.extract.request) {
|
|
407
|
+
tvalue.InResponseTo = requestInfo.extract.request.id;
|
|
408
|
+
}
|
|
409
|
+
var baseTemplate = (_h = (_g = (_f = initSetting.tagPrefixedDefaults) === null || _f === void 0 ? void 0 : _f.logoutResponseTemplate) === null || _g === void 0 ? void 0 : _g.context) !== null && _h !== void 0 ? _h : libsaml_1.default.defaultLogoutResponseTemplate.context;
|
|
410
|
+
rawSamlResponse = libsaml_1.default.replaceTagsByValue(baseTemplate, tvalue);
|
|
333
411
|
}
|
|
334
|
-
|
|
412
|
+
return {
|
|
413
|
+
id: id,
|
|
414
|
+
context: buildRedirectURL({
|
|
415
|
+
baseUrl: base,
|
|
416
|
+
type: urlParams.logoutResponse,
|
|
417
|
+
isSigned: entity.target.entitySetting.wantLogoutResponseSigned,
|
|
418
|
+
context: rawSamlResponse,
|
|
419
|
+
entitySetting: initSetting,
|
|
420
|
+
relayState: relayState,
|
|
421
|
+
}),
|
|
422
|
+
};
|
|
335
423
|
}
|
|
336
424
|
var redirectBinding = {
|
|
337
425
|
loginRequestRedirectURL: loginRequestRedirectURL,
|