diff --git a/server/auth/types/saml/routes.ts b/server/auth/types/saml/routes.ts index 243e786f4..a25b44f40 100644 --- a/server/auth/types/saml/routes.ts +++ b/server/auth/types/saml/routes.ts @@ -43,6 +43,7 @@ export class SamlAuthRoutes { validate: validateNextUrl, }) ), + redirectHash: schema.string(), }), }, options: { @@ -64,6 +65,7 @@ export class SamlAuthRoutes { saml: { nextUrl: request.query.nextUrl, requestId: samlHeader.requestId, + redirectHash: request.query.redirectHash === 'true', }, }; this.sessionStorageFactory.asScoped(request).set(cookie); @@ -92,6 +94,7 @@ export class SamlAuthRoutes { async (context, request, response) => { let requestId: string = ''; let nextUrl: string = '/'; + let redirectHash: boolean = false; try { const cookie = await this.sessionStorageFactory.asScoped(request).get(); if (cookie) { @@ -99,6 +102,7 @@ export class SamlAuthRoutes { nextUrl = cookie.saml?.nextUrl || `${this.coreSetup.http.basePath.serverBasePath}/app/opensearch-dashboards`; + redirectHash = cookie.saml?.redirectHash || false; } if (!requestId) { return response.badRequest({ @@ -140,11 +144,19 @@ export class SamlAuthRoutes { expiryTime, }; this.sessionStorageFactory.asScoped(request).set(cookie); - return response.redirected({ - headers: { - location: nextUrl, - }, - }); + if (redirectHash) { + return response.redirected({ + headers: { + location: `${this.coreSetup.http.basePath.serverBasePath}/auth/saml/redirectUrlFragment?nextUrl=${escape(nextUrl)}`, + } + }) + } else { + return response.redirected({ + headers: { + location: nextUrl, + }, + }); + } } catch (error) { context.security_plugin.logger.error( `SAML SP initiated authentication workflow failed: ${error}` @@ -212,6 +224,113 @@ export class SamlAuthRoutes { } ); + this.coreSetup.http.resources.register( + { + path: '/auth/saml/captureUrlFragment', + validate: { + query: schema.object({ + nextUrl: schema.maybe( + schema.string({ + validate: validateNextUrl, + }) + ), + }), + }, + options: { + authRequired: false, + }, + }, + async (context, request, response) => { + this.sessionStorageFactory.asScoped(request).clear(); + const serverBasePath = this.coreSetup.http.basePath.serverBasePath; + return response.renderHtml({ + body: ` + + OSD SAML Capture + + + `, + }); + } + ); + + this.coreSetup.http.resources.register( + { + path: '/auth/saml/captureUrlFragment.js', + validate: false, + options: { + authRequired: false, + }, + }, + async (context, request, response) => { + this.sessionStorageFactory.asScoped(request).clear(); + return response.renderJs({ + body: `let samlHash=window.location.hash.toString(); + let redirectHash = false; + if (samlHash !== "") { + window.localStorage.removeItem('samlHash'); + window.localStorage.setItem('samlHash', samlHash); + redirectHash = true; + } + let params = new URLSearchParams(window.location.search); + let nextUrl = params.get("nextUrl"); + finalUrl = "login?nextUrl=" + encodeURIComponent(nextUrl); + finalUrl += "&redirectHash=" + encodeURIComponent(redirectHash); + window.location.replace(finalUrl); + + `, + }); + } + ); + + this.coreSetup.http.resources.register( + { + path: '/auth/saml/redirectUrlFragment', + validate: { + query: schema.object({ + nextUrl: schema.any(), + }), + }, + options: { + authRequired: true, + }, + }, + async (context, request, response) => { + const serverBasePath = this.coreSetup.http.basePath.serverBasePath; + return response.renderHtml({ + body: ` + + OSD SAML Success + + + `, + }); + } + ); + + this.coreSetup.http.resources.register( + { + path: '/auth/saml/redirectUrlFragment.js', + validate: false, + options: { + authRequired: true, + }, + }, + async (context, request, response) => { + return response.renderJs({ + body: `let samlHash=window.localStorage.getItem('samlHash'); + window.localStorage.removeItem('samlHash'); + let params = new URLSearchParams(window.location.search); + let nextUrl = params.get("nextUrl"); + finalUrl = nextUrl + samlHash; + window.location.replace(finalUrl); + `, + }); + } + ); + + + this.router.get( { path: API_AUTH_LOGOUT, diff --git a/server/auth/types/saml/saml_auth.ts b/server/auth/types/saml/saml_auth.ts index d9e61718b..ac898337f 100644 --- a/server/auth/types/saml/saml_auth.ts +++ b/server/auth/types/saml/saml_auth.ts @@ -58,13 +58,13 @@ export class SamlAuthentication extends AuthenticationType { return escape(path); } - private redirectToLoginUri(request: OpenSearchDashboardsRequest, toolkit: AuthToolkit) { + private redirectSAMlCapture = (request: OpenSearchDashboardsRequest, toolkit: AuthToolkit) => { const nextUrl = this.generateNextUrl(request); const clearOldVersionCookie = clearOldVersionCookieValue(this.config); return toolkit.redirected({ - location: `${this.coreSetup.http.basePath.serverBasePath}/auth/saml/login?nextUrl=${nextUrl}`, + location: `${this.coreSetup.http.basePath.serverBasePath}/auth/saml/captureUrlFragment?nextUrl=${nextUrl}`, 'set-cookie': clearOldVersionCookie, - }); + }) } private setupRoutes(): void { @@ -112,7 +112,7 @@ export class SamlAuthentication extends AuthenticationType { toolkit: AuthToolkit ): IOpenSearchDashboardsResponse | AuthResult { if (this.isPageRequest(request)) { - return this.redirectToLoginUri(request, toolkit); + return this.redirectSAMlCapture(request, toolkit); } else { return response.unauthorized(); } diff --git a/server/session/security_cookie.ts b/server/session/security_cookie.ts index 7cd172a90..50b880d9b 100644 --- a/server/session/security_cookie.ts +++ b/server/session/security_cookie.ts @@ -36,6 +36,7 @@ export interface SecuritySessionCookie { saml?: { requestId?: string; nextUrl?: string; + redirectHash?: boolean; }; }