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;
};
}