From: Daniel Kutyła Date: Fri, 25 Sep 2020 15:39:25 +0000 (+0200) Subject: 16812: Added new mechanism to skip token in the url X-Git-Tag: 2.1.1~9^2~14 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/055723f58e163f0b5e49c1e8b92fd1bebe81873e 16812: Added new mechanism to skip token in the url Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła --- diff --git a/.env b/.env index fd91b99c..fd205836 100644 --- a/.env +++ b/.env @@ -3,5 +3,5 @@ # SPDX-License-Identifier: AGPL-3.0 REACT_APP_ARVADOS_CONFIG_URL=/config.json -REACT_APP_ARVADOS_API_HOST=c97qk.arvadosapi.com -HTTPS=true \ No newline at end of file +REACT_APP_ARVADOS_API_HOST=api.ardev.roche.com +HTTPS=false \ No newline at end of file diff --git a/src/common/redirect-to.ts b/src/common/redirect-to.ts new file mode 100644 index 00000000..54268c24 --- /dev/null +++ b/src/common/redirect-to.ts @@ -0,0 +1,26 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +const REDIRECT_TO_KEY = 'redirectTo'; + +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 = (token: string) => { + const { sessionStorage } = window; + + if (sessionStorage && sessionStorage.getItem(REDIRECT_TO_KEY)) { + const redirectUrl = sessionStorage.getItem(REDIRECT_TO_KEY); + sessionStorage.removeItem(REDIRECT_TO_KEY); + window.location.href = `${redirectUrl}?api_token=${token}`; + } +}; \ No newline at end of file diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx index b3f44824..2573d764 100644 --- a/src/components/icon/icon.tsx +++ b/src/components/icon/icon.tsx @@ -54,6 +54,7 @@ import Star from '@material-ui/icons/Star'; import StarBorder from '@material-ui/icons/StarBorder'; import Warning from '@material-ui/icons/Warning'; import VpnKey from '@material-ui/icons/VpnKey'; +import LinkOutlined from '@material-ui/icons/LinkOutlined'; // Import FontAwesome icons import { library } from '@fortawesome/fontawesome-svg-core'; @@ -128,3 +129,4 @@ export const UserPanelIcon: IconType = (props) => ; export const UsedByIcon: IconType = (props) => ; export const WorkflowIcon: IconType = (props) => ; export const WarningIcon: IconType = (props) => ; +export const Link: IconType = (props) => ; diff --git a/src/index.tsx b/src/index.tsx index 0a51ed3c..f87ff384 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -63,6 +63,7 @@ import { processResourceAdminActionSet } from '~/views-components/context-menu/a import { projectAdminActionSet } from '~/views-components/context-menu/action-sets/project-admin-action-set'; import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions"; import { openNotFoundDialog } from './store/not-found-panel/not-found-panel-action'; +import { storeRedirects } from './common/redirect-to'; console.log(`Starting arvados [${getBuildInfo()}]`); @@ -97,6 +98,8 @@ addMenuActionSet(ContextMenuKind.COLLECTION_ADMIN, collectionAdminActionSet); addMenuActionSet(ContextMenuKind.PROCESS_ADMIN, processResourceAdminActionSet); addMenuActionSet(ContextMenuKind.PROJECT_ADMIN, projectAdminActionSet); +storeRedirects(); + fetchConfig() .then(({ config, apiHost }) => { const history = createBrowserHistory(); diff --git a/src/store/store.ts b/src/store/store.ts index 030b6576..0bc351bb 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -6,6 +6,7 @@ import { createStore, applyMiddleware, compose, Middleware, combineReducers, Sto import { routerMiddleware, routerReducer } from "react-router-redux"; import thunkMiddleware from 'redux-thunk'; import { History } from "history"; +import { handleRedirects } from '../common/redirect-to'; import { authReducer } from "./auth/auth-reducer"; import { authMiddleware } from "./auth/auth-middleware"; @@ -130,6 +131,16 @@ export function configureStore(history: History, services: ServiceRepository): R const subprocessMiddleware = dataExplorerMiddleware( new SubprocessMiddlewareService(services, SUBPROCESS_PANEL_ID) ); + const redirectToMiddleware = (store: any) => (next: any) => (action: any) => { + const state = store.getState(); + + if (state.auth && state.auth.apiToken) { + const { apiToken } = state.auth; + handleRedirects(apiToken); + } + + return next(action); + }; const middlewares: Middleware[] = [ routerMiddleware(history), @@ -150,9 +161,9 @@ export function configureStore(history: History, services: ServiceRepository): R apiClientAuthorizationMiddlewareService, publicFavoritesMiddleware, collectionsContentAddress, - subprocessMiddleware + subprocessMiddleware, ]; - const enhancer = composeEnhancers(applyMiddleware(...middlewares)); + const enhancer = composeEnhancers(applyMiddleware(redirectToMiddleware, ...middlewares)); return createStore(rootReducer, enhancer); } diff --git a/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts b/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts index 2ded3736..f6899435 100644 --- a/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts @@ -7,7 +7,7 @@ import { RemoveIcon } from "~/components/icon/icon"; import { DownloadCollectionFileAction } from "../actions/download-collection-file-action"; import { openFileRemoveDialog } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions'; import { CollectionFileViewerAction } from '~/views-components/context-menu/actions/collection-file-viewer-action'; - +import { CollectionCopyToClipboardAction } from "../actions/collection-copy-to-clipboard-action"; export const readOnlyCollectionFilesItemActionSet: ContextMenuActionSet = [[ { @@ -17,6 +17,10 @@ export const readOnlyCollectionFilesItemActionSet: ContextMenuActionSet = [[ { component: CollectionFileViewerAction, execute: () => { return; }, + }, + { + component: CollectionCopyToClipboardAction, + execute: () => { return; }, } ]]; diff --git a/src/views-components/context-menu/actions/collection-copy-to-clipboard-action.tsx b/src/views-components/context-menu/actions/collection-copy-to-clipboard-action.tsx new file mode 100644 index 00000000..4ecdef70 --- /dev/null +++ b/src/views-components/context-menu/actions/collection-copy-to-clipboard-action.tsx @@ -0,0 +1,31 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { connect } from "react-redux"; +import { RootState } from "../../../store/store"; +import { getNodeValue } from "~/models/tree"; +import { CollectionFileType } from "~/models/collection-file"; +import { ContextMenuKind } from '~/views-components/context-menu/context-menu'; +import { CopyToClipboardAction } from "./copy-to-clipboard-action"; + +const mapStateToProps = (state: RootState) => { + const { resource } = state.contextMenu; + const currentCollectionUuid = state.collectionPanel.item ? state.collectionPanel.item.uuid : ''; + if (resource && resource.menuKind === ContextMenuKind.COLLECTION_FILES_ITEM) { + const file = getNodeValue(resource.uuid)(state.collectionPanelFiles); + if (file) { + return { + href: file.url, + download: file.type === CollectionFileType.DIRECTORY ? undefined : file.name, + kind: 'file', + currentCollectionUuid + }; + } + } else { + return ; + } + return ; +}; + +export const CollectionCopyToClipboardAction = connect(mapStateToProps)(CopyToClipboardAction); diff --git a/src/views-components/context-menu/actions/copy-to-clipboard-action.tsx b/src/views-components/context-menu/actions/copy-to-clipboard-action.tsx new file mode 100644 index 00000000..31ef4b97 --- /dev/null +++ b/src/views-components/context-menu/actions/copy-to-clipboard-action.tsx @@ -0,0 +1,33 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from "react"; +import * as copy from 'copy-to-clipboard'; +import { ListItemIcon, ListItemText, ListItem } from "@material-ui/core"; +import { Link } from "~/components/icon/icon"; +import { getClipboardUrl } from "./helpers"; + +export const CopyToClipboardAction = (props: { href?: any, download?: any, onClick?: () => void, kind?: string, currentCollectionUuid?: string; }) => { + const copyToClipboard = () => { + if (props.href) { + const clipboardUrl = getClipboardUrl(props.href); + copy(clipboardUrl); + } + + if (props.onClick) { + props.onClick(); + } + }; + + return props.href + ? + + + + + Copy to clipboard + + + : null; +}; \ No newline at end of file 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 20dcece4..a2c32bea 100644 --- a/src/views-components/context-menu/actions/file-viewer-action.tsx +++ b/src/views-components/context-menu/actions/file-viewer-action.tsx @@ -5,6 +5,7 @@ import * as React from "react"; import { ListItemIcon, ListItemText, ListItem } from "@material-ui/core"; import { OpenIcon } from "~/components/icon/icon"; +import { sanitizeToken } from "./helpers"; export const FileViewerAction = (props: { href?: any, download?: any, onClick?: () => void, kind?: string, currentCollectionUuid?: string; }) => { const fileProps = props.download ? { download: props.download } : {}; @@ -12,7 +13,7 @@ export const FileViewerAction = (props: { href?: any, download?: any, onClick?: return props.href ? diff --git a/src/views-components/context-menu/actions/helpers.tsx b/src/views-components/context-menu/actions/helpers.tsx new file mode 100644 index 00000000..a8cc24b3 --- /dev/null +++ b/src/views-components/context-menu/actions/helpers.tsx @@ -0,0 +1,16 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +export const sanitizeToken = (href: string, tokenAsQueryParam: boolean = true): string => { + const [prefix, suffix] = href.split('/t='); + const [token, ...rest] = suffix.split('/'); + + return `${[prefix, ...rest].join('/')}${tokenAsQueryParam ? `?api_token=${token}` : ''}`; +}; + +export const getClipboardUrl = (href: string): string => { + const { origin } = window.location; + + return `${origin}?redirectTo=${sanitizeToken(href, false)}`; +}; \ No newline at end of file diff --git a/src/views-components/main-content-bar/main-content-bar.tsx b/src/views-components/main-content-bar/main-content-bar.tsx index c8565d62..d5c316f6 100644 --- a/src/views-components/main-content-bar/main-content-bar.tsx +++ b/src/views-components/main-content-bar/main-content-bar.tsx @@ -48,8 +48,8 @@ export const MainContentBar = withStyles(styles)( (props: MainContentBarProps & WithStyles & any) => - - + +