Skip to content

Commit

Permalink
Preserve URL Hash for SAML based login (#1039)
Browse files Browse the repository at this point in the history
* Preserve URL HASH after user logs via SAML IDP

Co-authored-by: Darshit Chanpura <35282393+DarshitChanpura@users.noreply.github.com>
(cherry picked from commit a9d10d8)
  • Loading branch information
devardee authored and github-actions[bot] committed Nov 16, 2022
1 parent 4ea6df2 commit 3e82b0d
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 10 deletions.
137 changes: 132 additions & 5 deletions server/auth/types/saml/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class SamlAuthRoutes {
validate: validateNextUrl,
})
),
redirectHash: schema.string(),
}),
},
options: {
Expand All @@ -67,6 +68,7 @@ export class SamlAuthRoutes {
saml: {
nextUrl: request.query.nextUrl,
requestId: samlHeader.requestId,
redirectHash: request.query.redirectHash === 'true',
},
};
this.sessionStorageFactory.asScoped(request).set(cookie);
Expand Down Expand Up @@ -95,13 +97,15 @@ 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) {
requestId = cookie.saml?.requestId || '';
nextUrl =
cookie.saml?.nextUrl ||
`${this.coreSetup.http.basePath.serverBasePath}/app/opensearch-dashboards`;
redirectHash = cookie.saml?.redirectHash || false;
}
if (!requestId) {
return response.badRequest({
Expand Down Expand Up @@ -143,11 +147,21 @@ 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}`
Expand Down Expand Up @@ -215,6 +229,119 @@ export class SamlAuthRoutes {
}
);

// captureUrlFragment is the first route that will be invoked in the SP initiated login.
// This route will execute the captureUrlFragment.js script.
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: `
<!DOCTYPE html>
<title>OSD SAML Capture</title>
<link rel="icon" href="data:,">
<script src="${serverBasePath}/auth/saml/captureUrlFragment.js"></script>
`,
});
}
);

// This script will store the URL Hash in browser's local storage.
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);
`,
});
}
);

// Once the User is authenticated via the '_opendistro/_security/saml/acs' route,
// the browser will be redirected to '/auth/saml/redirectUrlFragment' route,
// which will execute the redirectUrlFragment.js.
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: `
<!DOCTYPE html>
<title>OSD SAML Success</title>
<link rel="icon" href="data:,">
<script src="${serverBasePath}/auth/saml/redirectUrlFragment.js"></script>
`,
});
}
);

// This script will pop the Hash from local storage if it exists.
// And forward the browser to the next url.
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: `/auth/logout`,
Expand Down
10 changes: 5 additions & 5 deletions server/auth/types/saml/saml_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,18 @@ export class SamlAuthentication extends AuthenticationType {
private generateNextUrl(request: OpenSearchDashboardsRequest): string {
const path =
this.coreSetup.http.basePath.serverBasePath +
(request.url.path || '/app/opensearch-dashboards');
(request.url.pathname || '/app/opensearch-dashboards');
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 {
const samlAuthRoutes = new SamlAuthRoutes(
Expand Down Expand Up @@ -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();
}
Expand Down
1 change: 1 addition & 0 deletions server/session/security_cookie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface SecuritySessionCookie {
saml?: {
requestId?: string;
nextUrl?: string;
redirectHash?: boolean;
};
}

Expand Down

0 comments on commit 3e82b0d

Please sign in to comment.