@boxyhq/saml-jackson 1.9.0 → 1.9.1
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/dist/controller/admin.d.ts +7 -3
- package/dist/controller/admin.js +17 -1
- package/dist/controller/admin.js.map +1 -1
- package/dist/controller/oauth.d.ts +3 -1
- package/dist/controller/oauth.js +321 -211
- package/dist/controller/oauth.js.map +1 -1
- package/dist/controller/saml-handler.d.ts +1 -0
- package/dist/controller/saml-handler.js +4 -2
- package/dist/controller/saml-handler.js.map +1 -1
- package/dist/controller/utils.d.ts +2 -1
- package/dist/controller/utils.js +1 -0
- package/dist/controller/utils.js.map +1 -1
- package/dist/directory-sync/DirectoryUsers.js +10 -9
- package/dist/directory-sync/DirectoryUsers.js.map +1 -1
- package/dist/directory-sync/types.d.ts +11 -0
- package/dist/directory-sync/utils.d.ts +6 -9
- package/dist/directory-sync/utils.js +35 -28
- package/dist/directory-sync/utils.js.map +1 -1
- package/dist/ee/branding/index.d.ts +15 -0
- package/dist/ee/branding/index.js +49 -0
- package/dist/ee/branding/index.js.map +1 -0
- package/dist/ee/federated-saml/app.d.ts +12 -5
- package/dist/ee/federated-saml/app.js +19 -12
- package/dist/ee/federated-saml/app.js.map +1 -1
- package/dist/ee/federated-saml/index.d.ts +3 -2
- package/dist/ee/federated-saml/index.js +2 -2
- package/dist/ee/federated-saml/index.js.map +1 -1
- package/dist/ee/federated-saml/sso.d.ts +4 -1
- package/dist/ee/federated-saml/sso.js +70 -45
- package/dist/ee/federated-saml/sso.js.map +1 -1
- package/dist/ee/federated-saml/types.d.ts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/saml/lib.d.ts +1 -0
- package/dist/saml/lib.js +3 -2
- package/dist/saml/lib.js.map +1 -1
- package/dist/saml-tracer/index.d.ts +14 -0
- package/dist/saml-tracer/index.js +87 -0
- package/dist/saml-tracer/index.js.map +1 -0
- package/dist/saml-tracer/types.d.ts +31 -0
- package/dist/saml-tracer/types.js +3 -0
- package/dist/saml-tracer/types.js.map +1 -0
- package/dist/typings.d.ts +12 -0
- package/dist/typings.js +1 -0
- package/dist/typings.js.map +1 -1
- package/package.json +6 -5
package/dist/controller/oauth.js
CHANGED
@@ -54,11 +54,12 @@ const lib_1 = require("../saml/lib");
|
|
54
54
|
const oidc_issuer_1 = require("./oauth/oidc-issuer");
|
55
55
|
const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
|
56
56
|
class OAuthController {
|
57
|
-
constructor({ connectionStore, sessionStore, codeStore, tokenStore, opts }) {
|
57
|
+
constructor({ connectionStore, sessionStore, codeStore, tokenStore, samlTracer, opts }) {
|
58
58
|
this.connectionStore = connectionStore;
|
59
59
|
this.sessionStore = sessionStore;
|
60
60
|
this.codeStore = codeStore;
|
61
61
|
this.tokenStore = tokenStore;
|
62
|
+
this.samlTracer = samlTracer;
|
62
63
|
this.opts = opts;
|
63
64
|
this.samlHandler = new saml_handler_1.SAMLHandler({
|
64
65
|
connection: connectionStore,
|
@@ -70,55 +71,25 @@ class OAuthController {
|
|
70
71
|
var _a;
|
71
72
|
return __awaiter(this, void 0, void 0, function* () {
|
72
73
|
const { response_type = 'code', client_id, redirect_uri, state, scope, nonce, code_challenge, code_challenge_method = '', idp_hint, forceAuthn = 'false', } = body;
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
let requestedTenant = tenant;
|
78
|
-
let requestedProduct = product;
|
79
|
-
metrics.increment('oauthAuthorize');
|
80
|
-
if (!redirect_uri) {
|
81
|
-
throw new error_1.JacksonError('Please specify a redirect URL.', 400);
|
82
|
-
}
|
74
|
+
let requestedTenant;
|
75
|
+
let requestedProduct;
|
76
|
+
let requestedScopes;
|
77
|
+
let requestedOIDCFlow;
|
83
78
|
let connection;
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
const
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
}
|
99
|
-
if ('connection' in response) {
|
100
|
-
connection = response.connection;
|
101
|
-
}
|
102
|
-
}
|
103
|
-
else if (client_id && client_id !== '' && client_id !== 'undefined' && client_id !== 'null') {
|
104
|
-
// if tenant and product are encoded in the client_id then we parse it and check for the relevant connection(s)
|
105
|
-
let sp = (0, utils_1.getEncodedTenantProduct)(client_id);
|
106
|
-
if (!sp && access_type) {
|
107
|
-
sp = (0, utils_1.getEncodedTenantProduct)(access_type);
|
108
|
-
}
|
109
|
-
if (!sp && resource) {
|
110
|
-
sp = (0, utils_1.getEncodedTenantProduct)(resource);
|
111
|
-
}
|
112
|
-
if (!sp && requestedScopes) {
|
113
|
-
const encodedParams = requestedScopes.find((scope) => scope.includes('=') && scope.includes('&')); // for now assume only one encoded param i.e. for tenant/product
|
114
|
-
if (encodedParams) {
|
115
|
-
sp = (0, utils_1.getEncodedTenantProduct)(encodedParams);
|
116
|
-
}
|
117
|
-
}
|
118
|
-
if (sp && sp.tenant && sp.product) {
|
119
|
-
const { tenant, product } = sp;
|
120
|
-
requestedTenant = tenant;
|
121
|
-
requestedProduct = product;
|
79
|
+
try {
|
80
|
+
const tenant = 'tenant' in body ? body.tenant : undefined;
|
81
|
+
const product = 'product' in body ? body.product : undefined;
|
82
|
+
const access_type = 'access_type' in body ? body.access_type : undefined;
|
83
|
+
const resource = 'resource' in body ? body.resource : undefined;
|
84
|
+
requestedTenant = tenant;
|
85
|
+
requestedProduct = product;
|
86
|
+
metrics.increment('oauthAuthorize');
|
87
|
+
if (!redirect_uri) {
|
88
|
+
throw new error_1.JacksonError('Please specify a redirect URL.', 400);
|
89
|
+
}
|
90
|
+
requestedScopes = (0, utils_1.getScopeValues)(scope);
|
91
|
+
requestedOIDCFlow = requestedScopes.includes('openid');
|
92
|
+
if (tenant && product) {
|
122
93
|
const response = yield this.samlHandler.resolveConnection({
|
123
94
|
tenant,
|
124
95
|
product,
|
@@ -135,47 +106,113 @@ class OAuthController {
|
|
135
106
|
connection = response.connection;
|
136
107
|
}
|
137
108
|
}
|
138
|
-
else {
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
109
|
+
else if (client_id && client_id !== '' && client_id !== 'undefined' && client_id !== 'null') {
|
110
|
+
// if tenant and product are encoded in the client_id then we parse it and check for the relevant connection(s)
|
111
|
+
let sp = (0, utils_1.getEncodedTenantProduct)(client_id);
|
112
|
+
if (!sp && access_type) {
|
113
|
+
sp = (0, utils_1.getEncodedTenantProduct)(access_type);
|
114
|
+
}
|
115
|
+
if (!sp && resource) {
|
116
|
+
sp = (0, utils_1.getEncodedTenantProduct)(resource);
|
117
|
+
}
|
118
|
+
if (!sp && requestedScopes) {
|
119
|
+
const encodedParams = requestedScopes.find((scope) => scope.includes('=') && scope.includes('&')); // for now assume only one encoded param i.e. for tenant/product
|
120
|
+
if (encodedParams) {
|
121
|
+
sp = (0, utils_1.getEncodedTenantProduct)(encodedParams);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
if (sp && sp.tenant && sp.product) {
|
125
|
+
const { tenant, product } = sp;
|
126
|
+
requestedTenant = tenant;
|
127
|
+
requestedProduct = product;
|
128
|
+
const response = yield this.samlHandler.resolveConnection({
|
129
|
+
tenant,
|
130
|
+
product,
|
131
|
+
idp_hint,
|
132
|
+
authFlow: 'oauth',
|
133
|
+
originalParams: Object.assign({}, body),
|
134
|
+
});
|
135
|
+
if ('redirectUrl' in response) {
|
136
|
+
return {
|
137
|
+
redirect_url: response.redirectUrl,
|
138
|
+
};
|
139
|
+
}
|
140
|
+
if ('connection' in response) {
|
141
|
+
connection = response.connection;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
else {
|
145
|
+
connection = yield this.connectionStore.get(client_id);
|
146
|
+
if (connection) {
|
147
|
+
requestedTenant = connection.tenant;
|
148
|
+
requestedProduct = connection.product;
|
149
|
+
}
|
143
150
|
}
|
144
151
|
}
|
152
|
+
else {
|
153
|
+
throw new error_1.JacksonError('You need to specify client_id or tenant & product', 403);
|
154
|
+
}
|
155
|
+
if (!connection) {
|
156
|
+
throw new error_1.JacksonError('IdP connection not found.', 403);
|
157
|
+
}
|
158
|
+
if (!allowed.redirect(redirect_uri, connection.redirectUrl)) {
|
159
|
+
throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
|
160
|
+
}
|
145
161
|
}
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
error_description: 'OAuth server not configured correctly for openid flow, check if JWT signing keys are loaded',
|
161
|
-
redirect_uri,
|
162
|
-
}),
|
163
|
-
};
|
164
|
-
}
|
165
|
-
if (!state) {
|
166
|
-
return {
|
167
|
-
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
168
|
-
error: 'invalid_request',
|
169
|
-
error_description: 'Please specify a state to safeguard against XSRF attacks',
|
170
|
-
redirect_uri,
|
171
|
-
}),
|
172
|
-
};
|
162
|
+
catch (err) {
|
163
|
+
const error_description = (0, utils_1.getErrorMessage)(err);
|
164
|
+
// Save the error trace
|
165
|
+
yield this.samlTracer.saveTrace({
|
166
|
+
error: error_description,
|
167
|
+
context: {
|
168
|
+
tenant: requestedTenant || '',
|
169
|
+
product: requestedProduct || '',
|
170
|
+
clientID: (connection === null || connection === void 0 ? void 0 : connection.clientID) || '',
|
171
|
+
requestedOIDCFlow,
|
172
|
+
redirectUri: redirect_uri,
|
173
|
+
},
|
174
|
+
});
|
175
|
+
throw err;
|
173
176
|
}
|
174
|
-
|
177
|
+
const isMissingJWTKeysForOIDCFlow = requestedOIDCFlow &&
|
178
|
+
(!((_a = this.opts.openid) === null || _a === void 0 ? void 0 : _a.jwtSigningKeys) || !(0, utils_1.isJWSKeyPairLoaded)(this.opts.openid.jwtSigningKeys));
|
179
|
+
const oAuthClientReqError = !state || response_type !== 'code';
|
180
|
+
const connectionIsSAML = 'idpMetadata' in connection && connection.idpMetadata !== undefined;
|
181
|
+
const connectionIsOIDC = 'oidcProvider' in connection && connection.oidcProvider !== undefined;
|
182
|
+
if (isMissingJWTKeysForOIDCFlow || oAuthClientReqError || (!connectionIsSAML && !connectionIsOIDC)) {
|
183
|
+
let error, error_description;
|
184
|
+
if (isMissingJWTKeysForOIDCFlow) {
|
185
|
+
error = 'server_error';
|
186
|
+
error_description =
|
187
|
+
'OAuth server not configured correctly for openid flow, check if JWT signing keys are loaded';
|
188
|
+
}
|
189
|
+
if (!state) {
|
190
|
+
error = 'invalid_request';
|
191
|
+
error_description = 'Please specify a state to safeguard against XSRF attacks';
|
192
|
+
}
|
193
|
+
if (response_type !== 'code') {
|
194
|
+
error = 'unsupported_response_type';
|
195
|
+
error_description = 'Only Authorization Code grant is supported';
|
196
|
+
}
|
197
|
+
if (!connectionIsSAML && !connectionIsOIDC) {
|
198
|
+
error = 'server_error';
|
199
|
+
error_description = 'Connection appears to be misconfigured';
|
200
|
+
}
|
201
|
+
// Save the error trace
|
202
|
+
const traceId = yield this.samlTracer.saveTrace({
|
203
|
+
error: error_description,
|
204
|
+
context: {
|
205
|
+
tenant: requestedTenant,
|
206
|
+
product: requestedProduct,
|
207
|
+
clientID: connection.clientID,
|
208
|
+
requestedOIDCFlow,
|
209
|
+
redirectUri: redirect_uri,
|
210
|
+
},
|
211
|
+
});
|
175
212
|
return {
|
176
213
|
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
177
|
-
error
|
178
|
-
error_description:
|
214
|
+
error,
|
215
|
+
error_description: traceId ? `${traceId}: ${error_description}` : error_description,
|
179
216
|
redirect_uri,
|
180
217
|
state,
|
181
218
|
}),
|
@@ -184,37 +221,47 @@ class OAuthController {
|
|
184
221
|
// Connection retrieved: Handover to IdP starts here
|
185
222
|
let ssoUrl;
|
186
223
|
let post = false;
|
187
|
-
const connectionIsSAML = 'idpMetadata' in connection && connection.idpMetadata !== undefined;
|
188
|
-
const connectionIsOIDC = 'oidcProvider' in connection && connection.oidcProvider !== undefined;
|
189
224
|
// Init sessionId
|
190
225
|
const sessionId = crypto_1.default.randomBytes(16).toString('hex');
|
191
226
|
const relayState = utils_1.relayStatePrefix + sessionId;
|
192
227
|
// SAML connection: SAML request will be constructed here
|
193
228
|
let samlReq;
|
194
|
-
if (
|
195
|
-
const { sso } = connection.idpMetadata;
|
196
|
-
if ('redirectUrl' in sso) {
|
197
|
-
// HTTP Redirect binding
|
198
|
-
ssoUrl = sso.redirectUrl;
|
199
|
-
}
|
200
|
-
else if ('postUrl' in sso) {
|
201
|
-
// HTTP-POST binding
|
202
|
-
ssoUrl = sso.postUrl;
|
203
|
-
post = true;
|
204
|
-
}
|
205
|
-
else {
|
206
|
-
// This code here is kept for backward compatibility. We now have validation while adding the SSO connection to ensure binding is present.
|
207
|
-
return {
|
208
|
-
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
209
|
-
error: 'invalid_request',
|
210
|
-
error_description: 'SAML binding could not be retrieved',
|
211
|
-
redirect_uri,
|
212
|
-
state,
|
213
|
-
}),
|
214
|
-
};
|
215
|
-
}
|
216
|
-
const cert = yield (0, x509_1.getDefaultCertificate)();
|
229
|
+
if (connectionIsSAML) {
|
217
230
|
try {
|
231
|
+
const { sso } = connection.idpMetadata;
|
232
|
+
if ('redirectUrl' in sso) {
|
233
|
+
// HTTP Redirect binding
|
234
|
+
ssoUrl = sso.redirectUrl;
|
235
|
+
}
|
236
|
+
else if ('postUrl' in sso) {
|
237
|
+
// HTTP-POST binding
|
238
|
+
ssoUrl = sso.postUrl;
|
239
|
+
post = true;
|
240
|
+
}
|
241
|
+
else {
|
242
|
+
// This code here is kept for backward compatibility. We now have validation while adding the SSO connection to ensure binding is present.
|
243
|
+
const error_description = 'SAML binding could not be retrieved';
|
244
|
+
// Save the error trace
|
245
|
+
const traceId = yield this.samlTracer.saveTrace({
|
246
|
+
error: error_description,
|
247
|
+
context: {
|
248
|
+
tenant: requestedTenant,
|
249
|
+
product: requestedProduct,
|
250
|
+
clientID: connection.clientID,
|
251
|
+
requestedOIDCFlow,
|
252
|
+
redirectUri: redirect_uri,
|
253
|
+
},
|
254
|
+
});
|
255
|
+
return {
|
256
|
+
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
257
|
+
error: 'invalid_request',
|
258
|
+
error_description: traceId ? `${traceId}: ${error_description}` : error_description,
|
259
|
+
redirect_uri,
|
260
|
+
state,
|
261
|
+
}),
|
262
|
+
};
|
263
|
+
}
|
264
|
+
const cert = yield (0, x509_1.getDefaultCertificate)();
|
218
265
|
samlReq = saml20_1.default.request({
|
219
266
|
ssoUrl,
|
220
267
|
entityID: this.opts.samlAudience,
|
@@ -228,10 +275,22 @@ class OAuthController {
|
|
228
275
|
});
|
229
276
|
}
|
230
277
|
catch (err) {
|
278
|
+
const error_description = (0, utils_1.getErrorMessage)(err);
|
279
|
+
// Save the error trace
|
280
|
+
const traceId = yield this.samlTracer.saveTrace({
|
281
|
+
error: error_description,
|
282
|
+
context: {
|
283
|
+
tenant: requestedTenant,
|
284
|
+
product: requestedProduct,
|
285
|
+
clientID: connection.clientID,
|
286
|
+
requestedOIDCFlow,
|
287
|
+
redirectUri: redirect_uri,
|
288
|
+
},
|
289
|
+
});
|
231
290
|
return {
|
232
291
|
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
233
292
|
error: 'server_error',
|
234
|
-
error_description:
|
293
|
+
error_description: traceId ? `${traceId}: ${error_description}` : error_description,
|
235
294
|
redirect_uri,
|
236
295
|
state,
|
237
296
|
}),
|
@@ -241,7 +300,7 @@ class OAuthController {
|
|
241
300
|
// OIDC Connection: Issuer discovery, openid-client init and extraction of authorization endpoint happens here
|
242
301
|
let oidcCodeVerifier;
|
243
302
|
let oidcNonce;
|
244
|
-
if (connectionIsOIDC
|
303
|
+
if (connectionIsOIDC) {
|
245
304
|
if (!this.opts.oidcPath) {
|
246
305
|
return {
|
247
306
|
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
@@ -347,25 +406,29 @@ class OAuthController {
|
|
347
406
|
authorize_form: authorizeForm,
|
348
407
|
};
|
349
408
|
}
|
350
|
-
|
409
|
+
if (connectionIsOIDC) {
|
351
410
|
return { redirect_url: ssoUrl };
|
352
411
|
}
|
353
|
-
|
354
|
-
return {
|
355
|
-
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
356
|
-
error: 'invalid_request',
|
357
|
-
error_description: 'Connection appears to be misconfigured',
|
358
|
-
redirect_uri,
|
359
|
-
state,
|
360
|
-
}),
|
361
|
-
};
|
362
|
-
}
|
412
|
+
throw 'Connection appears to be misconfigured';
|
363
413
|
}
|
364
414
|
catch (err) {
|
415
|
+
const error_description = (0, utils_1.getErrorMessage)(err);
|
416
|
+
// Save the error trace
|
417
|
+
const traceId = yield this.samlTracer.saveTrace({
|
418
|
+
error: error_description,
|
419
|
+
context: {
|
420
|
+
tenant: requestedTenant,
|
421
|
+
product: requestedProduct,
|
422
|
+
clientID: connection.clientID,
|
423
|
+
requestedOIDCFlow,
|
424
|
+
redirectUri: redirect_uri,
|
425
|
+
samlRequest: (samlReq === null || samlReq === void 0 ? void 0 : samlReq.request) || '',
|
426
|
+
},
|
427
|
+
});
|
365
428
|
return {
|
366
429
|
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
367
430
|
error: 'server_error',
|
368
|
-
error_description:
|
431
|
+
error_description: traceId ? `${traceId}: ${error_description}` : error_description,
|
369
432
|
redirect_uri,
|
370
433
|
state,
|
371
434
|
}),
|
@@ -376,112 +439,159 @@ class OAuthController {
|
|
376
439
|
samlResponse(body) {
|
377
440
|
var _a;
|
378
441
|
return __awaiter(this, void 0, void 0, function* () {
|
379
|
-
const { SAMLResponse, idp_hint, RelayState = '' } = body;
|
380
|
-
const isIdPFlow = !RelayState.startsWith(utils_1.relayStatePrefix);
|
381
|
-
// IdP is disabled so block the request
|
382
|
-
if (!this.opts.idpEnabled && isIdPFlow) {
|
383
|
-
// IdP login is disabled so block the request
|
384
|
-
throw new error_1.JacksonError('IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.', 403);
|
385
|
-
}
|
386
|
-
const sessionId = RelayState.replace(utils_1.relayStatePrefix, '');
|
387
|
-
const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
|
388
|
-
const issuer = saml20_1.default.parseIssuer(rawResponse);
|
389
|
-
if (!issuer) {
|
390
|
-
throw new error_1.JacksonError('Issuer not found.', 403);
|
391
|
-
}
|
392
|
-
const connections = yield this.connectionStore.getByIndex({
|
393
|
-
name: utils_1.IndexNames.EntityID,
|
394
|
-
value: issuer,
|
395
|
-
});
|
396
|
-
if (!connections || connections.length === 0) {
|
397
|
-
throw new error_1.JacksonError('SAML connection not found.', 403);
|
398
|
-
}
|
399
|
-
const session = sessionId ? yield this.sessionStore.get(sessionId) : null;
|
400
|
-
if (!isIdPFlow && !session) {
|
401
|
-
throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
|
402
|
-
}
|
403
|
-
const isSAMLFederated = session && 'samlFederated' in session;
|
404
|
-
const isSPFflow = !isIdPFlow && !isSAMLFederated;
|
405
442
|
let connection;
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
443
|
+
let rawResponse;
|
444
|
+
let sessionId;
|
445
|
+
let session;
|
446
|
+
let issuer;
|
447
|
+
let isIdPFlow;
|
448
|
+
let isSAMLFederated;
|
449
|
+
let validateOpts;
|
450
|
+
let redirect_uri;
|
451
|
+
const { SAMLResponse, idp_hint, RelayState = '' } = body;
|
452
|
+
try {
|
453
|
+
isIdPFlow = !RelayState.startsWith(utils_1.relayStatePrefix);
|
454
|
+
rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
|
455
|
+
issuer = saml20_1.default.parseIssuer(rawResponse);
|
456
|
+
if (!this.opts.idpEnabled && isIdPFlow) {
|
457
|
+
// IdP login is disabled so block the request
|
458
|
+
throw new error_1.JacksonError('IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.', 403);
|
459
|
+
}
|
460
|
+
sessionId = RelayState.replace(utils_1.relayStatePrefix, '');
|
461
|
+
if (!issuer) {
|
462
|
+
throw new error_1.JacksonError('Issuer not found.', 403);
|
463
|
+
}
|
464
|
+
const connections = yield this.connectionStore.getByIndex({
|
465
|
+
name: utils_1.IndexNames.EntityID,
|
466
|
+
value: issuer,
|
415
467
|
});
|
416
|
-
|
417
|
-
|
418
|
-
return {
|
419
|
-
app_select_form: response.postForm,
|
420
|
-
};
|
468
|
+
if (!connections || connections.length === 0) {
|
469
|
+
throw new error_1.JacksonError('SAML connection not found.', 403);
|
421
470
|
}
|
422
|
-
|
423
|
-
if (
|
424
|
-
|
471
|
+
session = sessionId ? yield this.sessionStore.get(sessionId) : null;
|
472
|
+
if (!isIdPFlow && !session) {
|
473
|
+
throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
|
425
474
|
}
|
475
|
+
isSAMLFederated = session && 'samlFederated' in session;
|
476
|
+
const isSPFflow = !isIdPFlow && !isSAMLFederated;
|
477
|
+
// IdP initiated SSO flow
|
478
|
+
if (isIdPFlow) {
|
479
|
+
const response = yield this.samlHandler.resolveConnection({
|
480
|
+
idp_hint,
|
481
|
+
authFlow: 'idp-initiated',
|
482
|
+
entityId: issuer,
|
483
|
+
originalParams: {
|
484
|
+
SAMLResponse,
|
485
|
+
},
|
486
|
+
});
|
487
|
+
// Redirect to the product selection page
|
488
|
+
if ('postForm' in response) {
|
489
|
+
return {
|
490
|
+
app_select_form: response.postForm,
|
491
|
+
};
|
492
|
+
}
|
493
|
+
// Found a connection
|
494
|
+
if ('connection' in response) {
|
495
|
+
connection = response.connection;
|
496
|
+
}
|
497
|
+
}
|
498
|
+
// SP initiated SSO flow
|
499
|
+
// Resolve if there are multiple matches for SP login
|
500
|
+
if (isSPFflow) {
|
501
|
+
connection = connections.filter((c) => {
|
502
|
+
return (c.clientID === session.requested.client_id ||
|
503
|
+
(c.tenant === session.requested.tenant && c.product === session.requested.product));
|
504
|
+
})[0];
|
505
|
+
}
|
506
|
+
if (!connection) {
|
507
|
+
connection = connections[0];
|
508
|
+
}
|
509
|
+
if (!connection) {
|
510
|
+
throw new error_1.JacksonError('SAML connection not found.', 403);
|
511
|
+
}
|
512
|
+
if (session &&
|
513
|
+
session.redirect_uri &&
|
514
|
+
!allowed.redirect(session.redirect_uri, connection.redirectUrl)) {
|
515
|
+
throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
|
516
|
+
}
|
517
|
+
const { privateKey } = yield (0, x509_1.getDefaultCertificate)();
|
518
|
+
validateOpts = {
|
519
|
+
thumbprint: `${connection === null || connection === void 0 ? void 0 : connection.idpMetadata.thumbprint}`,
|
520
|
+
audience: `${this.opts.samlAudience}`,
|
521
|
+
privateKey,
|
522
|
+
};
|
523
|
+
if (session && session.id) {
|
524
|
+
validateOpts['inResponseTo'] = session.id;
|
525
|
+
}
|
526
|
+
redirect_uri = (session && session.redirect_uri) || connection.defaultRedirectUrl;
|
426
527
|
}
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
|
445
|
-
}
|
446
|
-
const { privateKey } = yield (0, x509_1.getDefaultCertificate)();
|
447
|
-
const validateOpts = {
|
448
|
-
thumbprint: `${connection.idpMetadata.thumbprint}`,
|
449
|
-
audience: `${this.opts.samlAudience}`,
|
450
|
-
privateKey,
|
451
|
-
};
|
452
|
-
if (session && session.id) {
|
453
|
-
validateOpts['inResponseTo'] = session.id;
|
528
|
+
catch (err) {
|
529
|
+
// Save the error trace
|
530
|
+
yield this.samlTracer.saveTrace({
|
531
|
+
error: (0, utils_1.getErrorMessage)(err),
|
532
|
+
context: {
|
533
|
+
samlResponse: rawResponse,
|
534
|
+
tenant: (connection === null || connection === void 0 ? void 0 : connection.tenant) || '',
|
535
|
+
product: (connection === null || connection === void 0 ? void 0 : connection.product) || '',
|
536
|
+
clientID: (connection === null || connection === void 0 ? void 0 : connection.clientID) || '',
|
537
|
+
redirectUri: isIdPFlow ? connection === null || connection === void 0 ? void 0 : connection.defaultRedirectUrl : session === null || session === void 0 ? void 0 : session.redirect_uri,
|
538
|
+
issuer: issuer || '',
|
539
|
+
isSAMLFederated: !!isSAMLFederated,
|
540
|
+
isIdPFlow: !!isIdPFlow,
|
541
|
+
relayState: RelayState,
|
542
|
+
},
|
543
|
+
});
|
544
|
+
throw err; // Rethrow the error
|
454
545
|
}
|
455
|
-
|
456
|
-
let profile = null;
|
546
|
+
let profile;
|
457
547
|
try {
|
458
548
|
profile = yield (0, lib_1.extractSAMLResponseAttributes)(rawResponse, validateOpts);
|
549
|
+
// This is a federated SAML flow, let's create a new SAMLResponse and POST it to the SP
|
550
|
+
if (isSAMLFederated) {
|
551
|
+
const { responseForm } = yield this.samlHandler.createSAMLResponse({ profile, session });
|
552
|
+
yield this.sessionStore.delete(sessionId);
|
553
|
+
return { responseForm };
|
554
|
+
}
|
555
|
+
const code = yield this._buildAuthorizationCode(connection, profile, session, isIdPFlow);
|
556
|
+
const params = {
|
557
|
+
code,
|
558
|
+
};
|
559
|
+
if (session && session.state) {
|
560
|
+
params['state'] = session.state;
|
561
|
+
}
|
562
|
+
yield this.sessionStore.delete(sessionId);
|
563
|
+
return { redirect_url: redirect.success(redirect_uri, params) };
|
459
564
|
}
|
460
565
|
catch (err) {
|
566
|
+
const error_description = (0, utils_1.getErrorMessage)(err);
|
567
|
+
// Trace the error
|
568
|
+
const traceId = yield this.samlTracer.saveTrace({
|
569
|
+
error: error_description,
|
570
|
+
context: {
|
571
|
+
samlResponse: rawResponse,
|
572
|
+
tenant: connection.tenant,
|
573
|
+
product: connection.product,
|
574
|
+
clientID: connection.clientID,
|
575
|
+
redirectUri: isIdPFlow ? connection === null || connection === void 0 ? void 0 : connection.defaultRedirectUrl : session === null || session === void 0 ? void 0 : session.redirect_uri,
|
576
|
+
isSAMLFederated,
|
577
|
+
isIdPFlow,
|
578
|
+
relayState: RelayState,
|
579
|
+
issuer,
|
580
|
+
profile,
|
581
|
+
},
|
582
|
+
});
|
583
|
+
if (isSAMLFederated) {
|
584
|
+
throw err;
|
585
|
+
}
|
461
586
|
return {
|
462
587
|
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
463
588
|
error: 'access_denied',
|
464
|
-
error_description:
|
589
|
+
error_description: traceId ? `${traceId}: ${error_description}` : error_description,
|
465
590
|
redirect_uri,
|
466
591
|
state: (_a = session.requested) === null || _a === void 0 ? void 0 : _a.state,
|
467
592
|
}),
|
468
593
|
};
|
469
594
|
}
|
470
|
-
// This is a federated SAML flow, let's create a new SAMLResponse and POST it to the SP
|
471
|
-
if (isSAMLFederated) {
|
472
|
-
const { responseForm } = yield this.samlHandler.createSAMLResponse({ profile, session });
|
473
|
-
yield this.sessionStore.delete(sessionId);
|
474
|
-
return { responseForm };
|
475
|
-
}
|
476
|
-
const code = yield this._buildAuthorizationCode(connection, profile, session, isIdPFlow);
|
477
|
-
const params = {
|
478
|
-
code,
|
479
|
-
};
|
480
|
-
if (session && session.state) {
|
481
|
-
params['state'] = session.state;
|
482
|
-
}
|
483
|
-
yield this.sessionStore.delete(sessionId);
|
484
|
-
return { redirect_url: redirect.success(redirect_uri, params) };
|
485
595
|
});
|
486
596
|
}
|
487
597
|
oidcAuthzResponse(body) {
|