From ae94f4d8463ff6350329e802cb902c8dad96a710 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Thu, 15 Oct 2020 17:21:27 -0400 Subject: [PATCH] 16812: Handoff token using query param Need to pass the token to keep-web without it being "sticky" in the URL bar. Using a query param accomplishes this, because keep-web knows to strip the api_token query parameter and respond with redirect and a cookie which the browser can use to fetch the file safely. Also distinguish between KeepWebService (now the download service) and KeepWebInlineService (the one that will serve content that can be displayed inline in the browser if it is safe to do so). Arvados-DCO-1.1-Signed-off-by: Peter Amstutz --- src/common/config.ts | 3 +++ src/common/redirect-to.test.ts | 12 ++++++------ src/common/redirect-to.ts | 11 +++++++---- src/store/auth/auth-action-session.ts | 1 + src/store/store.ts | 2 +- .../actions/collection-file-viewer-action.tsx | 6 +++--- .../context-menu/actions/download-action.tsx | 4 ++-- .../actions/download-collection-file-action.tsx | 4 ++-- .../context-menu/actions/file-viewer-action.tsx | 12 ++++++------ src/views-components/context-menu/actions/helpers.ts | 6 ++++-- 10 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/common/config.ts b/src/common/config.ts index afbeb5ae..146ca90a 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -89,6 +89,7 @@ export interface ClusterConfigJSON { export class Config { baseUrl: string; keepWebServiceUrl: string; + keepWebInlineServiceUrl: string; remoteHosts: { [key: string]: string }; @@ -114,6 +115,7 @@ export const buildConfig = (clusterConfig: ClusterConfigJSON): Config => { config.workbench2Url = clusterConfigJSON.Services.Workbench2.ExternalURL; config.workbenchUrl = clusterConfigJSON.Services.Workbench1.ExternalURL; config.keepWebServiceUrl = clusterConfigJSON.Services.WebDAVDownload.ExternalURL; + config.keepWebInlineServiceUrl = clusterConfigJSON.Services.WebDAV.ExternalURL; config.loginCluster = clusterConfigJSON.Login.LoginCluster; config.clusterConfig = clusterConfigJSON; config.apiRevision = 0; @@ -249,6 +251,7 @@ export const mockClusterConfigJSON = (config: Partial): Clust export const mockConfig = (config: Partial): Config => ({ baseUrl: "", keepWebServiceUrl: "", + keepWebInlineServiceUrl: "", remoteHosts: {}, rootUrl: "", uuidPrefix: "", diff --git a/src/common/redirect-to.test.ts b/src/common/redirect-to.test.ts index 177e1656..c7d3a84e 100644 --- a/src/common/redirect-to.test.ts +++ b/src/common/redirect-to.test.ts @@ -19,9 +19,9 @@ describe('redirect-to', () => { port: '80', protocol: 'http', search: '', - reload: () => {}, - replace: () => {}, - assign: () => {}, + reload: () => { }, + replace: () => { }, + assign: () => { }, ancestorOrigins: [], href: '', }; @@ -72,10 +72,10 @@ describe('redirect-to', () => { it('should redirect to page when it is present in session storage', () => { // when - handleRedirects(config); + handleRedirects("abcxyz", config); // then - expect(window.location.href).toBe(`${config.keepWebServiceUrl}${redirectTo}`); + expect(window.location.href).toBe(`${config.keepWebServiceUrl}${redirectTo}?api_token=abcxyz`); }); }); -}); \ No newline at end of file +}); diff --git a/src/common/redirect-to.ts b/src/common/redirect-to.ts index 86fac71c..7cb0d580 100644 --- a/src/common/redirect-to.ts +++ b/src/common/redirect-to.ts @@ -10,20 +10,23 @@ export const storeRedirects = () => { if (window.location.href.indexOf(REDIRECT_TO_KEY) > -1) { const { location: { href }, sessionStorage } = window; const redirectUrl = href.split(`${REDIRECT_TO_KEY}=`)[1]; - + if (sessionStorage) { sessionStorage.setItem(REDIRECT_TO_KEY, redirectUrl); } } }; -export const handleRedirects = (config: Config) => { +export const handleRedirects = (token: string, config: Config) => { const { sessionStorage } = window; const { keepWebServiceUrl } = config; if (sessionStorage && sessionStorage.getItem(REDIRECT_TO_KEY)) { const redirectUrl = sessionStorage.getItem(REDIRECT_TO_KEY); sessionStorage.removeItem(REDIRECT_TO_KEY); - window.location.href = `${keepWebServiceUrl}${redirectUrl}`; + if (redirectUrl) { + const sep = redirectUrl.indexOf("?") > -1 ? "&" : "?"; + window.location.href = `${keepWebServiceUrl}${redirectUrl}${sep}api_token=${token}`; + } } -}; \ No newline at end of file +}; diff --git a/src/store/auth/auth-action-session.ts b/src/store/auth/auth-action-session.ts index ed2e18b2..a02f922d 100644 --- a/src/store/auth/auth-action-session.ts +++ b/src/store/auth/auth-action-session.ts @@ -27,6 +27,7 @@ const getClusterConfig = async (origin: string, apiClient: AxiosInstance): Promi configFromDD = { baseUrl: normalizeURLPath(dd.baseUrl), keepWebServiceUrl: dd.keepWebServiceUrl, + keepWebInlineServiceUrl: dd.keepWebInlineServiceUrl, remoteHosts: dd.remoteHosts, rootUrl: dd.rootUrl, uuidPrefix: dd.uuidPrefix, diff --git a/src/store/store.ts b/src/store/store.ts index a6e0cbbe..7beb099c 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -136,7 +136,7 @@ export function configureStore(history: History, services: ServiceRepository, co const state = store.getState(); if (state.auth && state.auth.apiToken) { - handleRedirects(config); + handleRedirects(state.auth.apiToken, config); } return next(action); diff --git a/src/views-components/context-menu/actions/collection-file-viewer-action.tsx b/src/views-components/context-menu/actions/collection-file-viewer-action.tsx index cdc0c829..f75da238 100644 --- a/src/views-components/context-menu/actions/collection-file-viewer-action.tsx +++ b/src/views-components/context-menu/actions/collection-file-viewer-action.tsx @@ -15,15 +15,15 @@ const mapStateToProps = (state: RootState) => { const file = getNodeValue(resource.uuid)(state.collectionPanelFiles); if (file) { return { - href: file.url, + href: file.url.replace(state.auth.config.keepWebServiceUrl, state.auth.config.keepWebInlineServiceUrl), kind: 'file', currentCollectionUuid }; } } else { - return ; + return; } - return ; + return; }; export const CollectionFileViewerAction = connect(mapStateToProps)(FileViewerAction); diff --git a/src/views-components/context-menu/actions/download-action.tsx b/src/views-components/context-menu/actions/download-action.tsx index 7468954f..224d4308 100644 --- a/src/views-components/context-menu/actions/download-action.tsx +++ b/src/views-components/context-menu/actions/download-action.tsx @@ -47,7 +47,7 @@ export const DownloadAction = (props: { href?: any, download?: any, onClick?: () return props.href || props.kind === 'files' ? props.kind === 'files' ? createZip(props.href, props.download) : undefined}> @@ -61,4 +61,4 @@ export const DownloadAction = (props: { href?: any, download?: any, onClick?: () : null; -}; \ No newline at end of file +}; diff --git a/src/views-components/context-menu/actions/download-collection-file-action.tsx b/src/views-components/context-menu/actions/download-collection-file-action.tsx index 9332d170..437b22ed 100644 --- a/src/views-components/context-menu/actions/download-collection-file-action.tsx +++ b/src/views-components/context-menu/actions/download-collection-file-action.tsx @@ -17,7 +17,7 @@ const mapStateToProps = (state: RootState) => { const file = getNodeValue(resource.uuid)(state.collectionPanelFiles); if (file) { return { - href: sanitizeToken(file.url, false), + href: sanitizeToken(file.url, true), kind: 'file', currentCollectionUuid }; @@ -25,7 +25,7 @@ const mapStateToProps = (state: RootState) => { } else { const files = filterCollectionFilesBySelection(state.collectionPanelFiles, true); return { - href: files.map(file => sanitizeToken(file.url, false)), + href: files.map(file => sanitizeToken(file.url, true)), kind: 'files', currentCollectionUuid }; diff --git a/src/views-components/context-menu/actions/file-viewer-action.tsx b/src/views-components/context-menu/actions/file-viewer-action.tsx index e58ea6a7..9af2ef92 100644 --- a/src/views-components/context-menu/actions/file-viewer-action.tsx +++ b/src/views-components/context-menu/actions/file-viewer-action.tsx @@ -12,17 +12,17 @@ export const FileViewerAction = (props: { href?: any, download?: any, onClick?: return props.href ? - - - + + + Open in new tab - + : null; -}; \ No newline at end of file +}; diff --git a/src/views-components/context-menu/actions/helpers.ts b/src/views-components/context-menu/actions/helpers.ts index 38368606..419c796d 100644 --- a/src/views-components/context-menu/actions/helpers.ts +++ b/src/views-components/context-menu/actions/helpers.ts @@ -6,7 +6,9 @@ export const sanitizeToken = (href: string, tokenAsQueryParam: boolean = true): const [prefix, suffix] = href.split('/t='); const [token, ...rest] = suffix.split('/'); - return `${[prefix, ...rest].join('/')}${tokenAsQueryParam ? `?api_token=${token}` : ''}`; + const sep = href.indexOf("?") > -1 ? "&" : "?"; + + return `${[prefix, ...rest].join('/')}${tokenAsQueryParam ? `${sep}api_token=${token}` : ''}`; }; export const getClipboardUrl = (href: string): string => { @@ -14,4 +16,4 @@ export const getClipboardUrl = (href: string): string => { const url = sanitizeToken(href, false); return `${origin}?redirectTo=${url}`; -}; \ No newline at end of file +}; -- 2.30.2