@@ -46,6 +46,7 @@ export class SamlAuthRoutes {
4646 validate : validateNextUrl ,
4747 } )
4848 ) ,
49+ redirectHash : schema . string ( ) ,
4950 } ) ,
5051 } ,
5152 options : {
@@ -67,6 +68,7 @@ export class SamlAuthRoutes {
6768 saml : {
6869 nextUrl : request . query . nextUrl ,
6970 requestId : samlHeader . requestId ,
71+ redirectHash : request . query . redirectHash === 'true' ,
7072 } ,
7173 } ;
7274 this . sessionStorageFactory . asScoped ( request ) . set ( cookie ) ;
@@ -95,13 +97,15 @@ export class SamlAuthRoutes {
9597 async ( context , request , response ) => {
9698 let requestId : string = '' ;
9799 let nextUrl : string = '/' ;
100+ let redirectHash : boolean = false ;
98101 try {
99102 const cookie = await this . sessionStorageFactory . asScoped ( request ) . get ( ) ;
100103 if ( cookie ) {
101104 requestId = cookie . saml ?. requestId || '' ;
102105 nextUrl =
103106 cookie . saml ?. nextUrl ||
104107 `${ this . coreSetup . http . basePath . serverBasePath } /app/opensearch-dashboards` ;
108+ redirectHash = cookie . saml ?. redirectHash || false ;
105109 }
106110 if ( ! requestId ) {
107111 return response . badRequest ( {
@@ -143,11 +147,21 @@ export class SamlAuthRoutes {
143147 expiryTime,
144148 } ;
145149 this . sessionStorageFactory . asScoped ( request ) . set ( cookie ) ;
146- return response . redirected ( {
147- headers : {
148- location : nextUrl ,
149- } ,
150- } ) ;
150+ if ( redirectHash ) {
151+ return response . redirected ( {
152+ headers : {
153+ location : `${
154+ this . coreSetup . http . basePath . serverBasePath
155+ } /auth/saml/redirectUrlFragment?nextUrl=${ escape ( nextUrl ) } `,
156+ } ,
157+ } ) ;
158+ } else {
159+ return response . redirected ( {
160+ headers : {
161+ location : nextUrl ,
162+ } ,
163+ } ) ;
164+ }
151165 } catch ( error ) {
152166 context . security_plugin . logger . error (
153167 `SAML SP initiated authentication workflow failed: ${ error } `
@@ -215,6 +229,119 @@ export class SamlAuthRoutes {
215229 }
216230 ) ;
217231
232+ // captureUrlFragment is the first route that will be invoked in the SP initiated login.
233+ // This route will execute the captureUrlFragment.js script.
234+ this . coreSetup . http . resources . register (
235+ {
236+ path : '/auth/saml/captureUrlFragment' ,
237+ validate : {
238+ query : schema . object ( {
239+ nextUrl : schema . maybe (
240+ schema . string ( {
241+ validate : validateNextUrl ,
242+ } )
243+ ) ,
244+ } ) ,
245+ } ,
246+ options : {
247+ authRequired : false ,
248+ } ,
249+ } ,
250+ async ( context , request , response ) => {
251+ this . sessionStorageFactory . asScoped ( request ) . clear ( ) ;
252+ const serverBasePath = this . coreSetup . http . basePath . serverBasePath ;
253+ return response . renderHtml ( {
254+ body : `
255+ <!DOCTYPE html>
256+ <title>OSD SAML Capture</title>
257+ <link rel="icon" href="data:,">
258+ <script src="${ serverBasePath } /auth/saml/captureUrlFragment.js"></script>
259+ ` ,
260+ } ) ;
261+ }
262+ ) ;
263+
264+ // This script will store the URL Hash in browser's local storage.
265+ this . coreSetup . http . resources . register (
266+ {
267+ path : '/auth/saml/captureUrlFragment.js' ,
268+ validate : false ,
269+ options : {
270+ authRequired : false ,
271+ } ,
272+ } ,
273+ async ( context , request , response ) => {
274+ this . sessionStorageFactory . asScoped ( request ) . clear ( ) ;
275+ return response . renderJs ( {
276+ body : `let samlHash=window.location.hash.toString();
277+ let redirectHash = false;
278+ if (samlHash !== "") {
279+ window.localStorage.removeItem('samlHash');
280+ window.localStorage.setItem('samlHash', samlHash);
281+ redirectHash = true;
282+ }
283+ let params = new URLSearchParams(window.location.search);
284+ let nextUrl = params.get("nextUrl");
285+ finalUrl = "login?nextUrl=" + encodeURIComponent(nextUrl);
286+ finalUrl += "&redirectHash=" + encodeURIComponent(redirectHash);
287+ window.location.replace(finalUrl);
288+
289+ ` ,
290+ } ) ;
291+ }
292+ ) ;
293+
294+ // Once the User is authenticated via the '_opendistro/_security/saml/acs' route,
295+ // the browser will be redirected to '/auth/saml/redirectUrlFragment' route,
296+ // which will execute the redirectUrlFragment.js.
297+ this . coreSetup . http . resources . register (
298+ {
299+ path : '/auth/saml/redirectUrlFragment' ,
300+ validate : {
301+ query : schema . object ( {
302+ nextUrl : schema . any ( ) ,
303+ } ) ,
304+ } ,
305+ options : {
306+ authRequired : true ,
307+ } ,
308+ } ,
309+ async ( context , request , response ) => {
310+ const serverBasePath = this . coreSetup . http . basePath . serverBasePath ;
311+ return response . renderHtml ( {
312+ body : `
313+ <!DOCTYPE html>
314+ <title>OSD SAML Success</title>
315+ <link rel="icon" href="data:,">
316+ <script src="${ serverBasePath } /auth/saml/redirectUrlFragment.js"></script>
317+ ` ,
318+ } ) ;
319+ }
320+ ) ;
321+
322+ // This script will pop the Hash from local storage if it exists.
323+ // And forward the browser to the next url.
324+ this . coreSetup . http . resources . register (
325+ {
326+ path : '/auth/saml/redirectUrlFragment.js' ,
327+ validate : false ,
328+ options : {
329+ authRequired : true ,
330+ } ,
331+ } ,
332+ async ( context , request , response ) => {
333+ return response . renderJs ( {
334+ body : `let samlHash=window.localStorage.getItem('samlHash');
335+ window.localStorage.removeItem('samlHash');
336+ let params = new URLSearchParams(window.location.search);
337+ let nextUrl = params.get("nextUrl");
338+ finalUrl = nextUrl + samlHash;
339+ window.location.replace(finalUrl);
340+ ` ,
341+ } ) ;
342+ }
343+ ) ;
344+
218345 this . router . get (
219346 {
220347 path : `/auth/logout` ,
0 commit comments