From eb9611ea28acee0a8fdef772ef8d1b31ac689e4c Mon Sep 17 00:00:00 2001 From: Eric Biagiotti Date: Fri, 17 May 2019 15:04:17 -0400 Subject: [PATCH] 15088: Improves federated linking logic and UI Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti --- src/models/link-account.ts | 4 +- src/routes/routes.ts | 5 +- src/store/auth/auth-action.ts | 4 +- .../link-account-panel-actions.ts | 24 ++++- .../link-account-panel-root.tsx | 98 +++++++++++++------ .../link-account-panel/link-account-panel.tsx | 1 + 6 files changed, 95 insertions(+), 41 deletions(-) diff --git a/src/models/link-account.ts b/src/models/link-account.ts index 1c2029cf..f5b60400 100644 --- a/src/models/link-account.ts +++ b/src/models/link-account.ts @@ -10,7 +10,9 @@ export enum LinkAccountStatus { export enum LinkAccountType { ADD_OTHER_LOGIN, - ACCESS_OTHER_ACCOUNT + ADD_LOCAL_TO_REMOTE, + ACCESS_OTHER_ACCOUNT, + ACCESS_OTHER_REMOTE_ACCOUNT } export interface AccountToLink { diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 76f5c32d..7e6897a8 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { matchPath } from 'react-router'; +import { matchPath, Router } from 'react-router'; import { ResourceKind, RESOURCE_UUID_PATTERN, extractUuidKind } from '~/models/resource'; import { getProjectUrl } from '~/models/project'; import { getCollectionUrl } from '~/models/collection'; @@ -127,6 +127,9 @@ export const matchKeepServicesRoute = (route: string) => export const matchTokenRoute = (route: string) => matchPath(route, { path: Routes.TOKEN }); +export const matchFedTokenRoute = (route: string) => + matchPath(route, {path: Routes.FED_LOGIN}); + export const matchUsersRoute = (route: string) => matchPath(route, { path: Routes.USERS }); diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts index 6ca71403..87eb3f75 100644 --- a/src/store/auth/auth-action.ts +++ b/src/store/auth/auth-action.ts @@ -13,7 +13,7 @@ import { Session } from "~/models/session"; import { getDiscoveryURL, Config } from '~/common/config'; import { initSessions } from "~/store/auth/auth-action-session"; import { cancelLinking } from '~/store/link-account-panel/link-account-panel-actions'; -import { matchTokenRoute } from '~/routes/routes'; +import { matchTokenRoute, matchFedTokenRoute } from '~/routes/routes'; import Axios from "axios"; import { AxiosError } from "axios"; @@ -54,7 +54,7 @@ export const initAuth = (config: Config) => (dispatch: Dispatch, getState: () => // Cancel any link account ops in progess unless the user has // just logged in or there has been a successful link operation const data = services.linkAccountService.getLinkOpStatus(); - if (!matchTokenRoute(location.pathname) && data === undefined) { + if (!matchTokenRoute(location.pathname) && (!matchFedTokenRoute(location.pathname)) && data === undefined) { dispatch(cancelLinking()); } diff --git a/src/store/link-account-panel/link-account-panel-actions.ts b/src/store/link-account-panel/link-account-panel-actions.ts index eec5bf3d..7a92f4aa 100644 --- a/src/store/link-account-panel/link-account-panel-actions.ts +++ b/src/store/link-account-panel/link-account-panel-actions.ts @@ -99,8 +99,17 @@ export const linkFailed = () => export const loadLinkAccountPanel = () => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + // If there are remote hosts, set the initial selected cluster by getting the first cluster that isn't the local cluster if (getState().linkAccountPanel.selectedCluster === undefined) { - dispatch(linkAccountPanelActions.SET_SELECTED_CLUSTER({ selectedCluster: getState().auth.localCluster })); + const localCluster = getState().auth.localCluster; + let selectedCluster = localCluster; + for (const key in getState().auth.remoteHosts) { + if (key !== localCluster) { + selectedCluster = key; + break; + } + } + dispatch(linkAccountPanelActions.SET_SELECTED_CLUSTER({ selectedCluster })); } // First check if an account link operation has completed @@ -124,7 +133,7 @@ export const loadLinkAccountPanel = () => dispatch(saveApiToken(curToken)); let params: any; - if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT) { + if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT || linkAccountData.type === LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT) { params = { originatingUser: OriginatingUser.USER_TO_LINK, targetUser: curUserResource, @@ -133,7 +142,7 @@ export const loadLinkAccountPanel = () => userToLinkToken: linkAccountData.token }; } - else if (linkAccountData.type === LinkAccountType.ADD_OTHER_LOGIN) { + else if (linkAccountData.type === LinkAccountType.ADD_OTHER_LOGIN || linkAccountData.type === LinkAccountType.ADD_LOCAL_TO_REMOTE) { params = { originatingUser: OriginatingUser.TARGET_USER, targetUser: savedUserResource, @@ -176,9 +185,14 @@ export const startLinking = (t: LinkAccountType) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const accountToLink = {type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken()} as AccountToLink; services.linkAccountService.saveAccountToLink(accountToLink); + const auth = getState().auth; - const selectedCluster = getState().linkAccountPanel.selectedCluster; - const homeCluster = selectedCluster ? selectedCluster : auth.homeCluster; + const isLocalUser = auth.user!.uuid.substring(0,5) === auth.localCluster; + let homeCluster = auth.localCluster; + if (isLocalUser && t === LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT) { + homeCluster = getState().linkAccountPanel.selectedCluster!; + } + dispatch(logout()); dispatch(login(auth.localCluster, homeCluster, auth.remoteHosts)); }; diff --git a/src/views/link-account-panel/link-account-panel-root.tsx b/src/views/link-account-panel/link-account-panel-root.tsx index 581e86dd..12dae426 100644 --- a/src/views/link-account-panel/link-account-panel-root.tsx +++ b/src/views/link-account-panel/link-account-panel-root.tsx @@ -33,6 +33,7 @@ export interface LinkAccountPanelRootDataProps { userToLink?: UserResource; remoteHosts: { [key: string]: string }; hasRemoteHosts: boolean; + localCluster: string; status : LinkAccountPanelStatus; error: LinkAccountPanelError; selectedCluster?: string; @@ -58,55 +59,88 @@ function displayUser(user: UserResource, showCreatedAt: boolean = false, showClu return disp; } +function isLocalUser(uuid: string, localCluster: string) { + return uuid.substring(0, 5) === localCluster; +} + type LinkAccountPanelRootProps = LinkAccountPanelRootDataProps & LinkAccountPanelRootActionProps & WithStyles; export const LinkAccountPanelRoot = withStyles(styles) ( ({classes, targetUser, userToLink, status, error, startLinking, cancelLinking, linkAccount, - remoteHosts, hasRemoteHosts, selectedCluster, setSelectedCluster}: LinkAccountPanelRootProps) => { + remoteHosts, hasRemoteHosts, selectedCluster, setSelectedCluster, localCluster}: LinkAccountPanelRootProps) => { return - { status === LinkAccountPanelStatus.INITIAL && targetUser && - - - - You are currently logged in as {displayUser(targetUser, true, hasRemoteHosts)} + { status === LinkAccountPanelStatus.INITIAL && targetUser &&
+ { isLocalUser(targetUser.uuid, localCluster) ? + + + You are currently logged in as {displayUser(targetUser, true)} + + + You can link Arvados accounts. After linking, either login will take you to the same account. + - - You can link Arvados accounts. After linking, either login will take you to the same account. - - {hasRemoteHosts && selectedCluster && - Please select the cluster that hosts the account you want to link with: - - } - - - - + + + + + + + - - + { hasRemoteHosts && selectedCluster && + + You can also link {displayUser(targetUser, false)} with an account from a remote cluster. + + + Please select the cluster that hosts the account you want to link with: + + + + + + } + : + + + + You are currently logged in as {displayUser(targetUser, true, true)} + + + This a remote account. You can link a local Arvados account to this one. After linking, you can access the local account's data by logging into the {localCluster} cluster with the {targetUser.email} account. + + + + - - } + } +
} { (status === LinkAccountPanelStatus.LINKING || status === LinkAccountPanelStatus.ERROR) && userToLink && targetUser && { status === LinkAccountPanelStatus.LINKING && - Clicking 'Link accounts' will link {displayUser(userToLink, true, hasRemoteHosts)} to {displayUser(targetUser, true, hasRemoteHosts)}. + Clicking 'Link accounts' will link {displayUser(userToLink, true, !isLocalUser(targetUser.uuid, localCluster))} to {displayUser(targetUser, true, !isLocalUser(targetUser.uuid, localCluster))}. - + { (isLocalUser(targetUser.uuid, localCluster)) && After linking, logging in as {displayUser(userToLink)} will log you into the same account as {displayUser(targetUser)}. - + } - Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(targetUser)}. + Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(targetUser)}. + { !isLocalUser(targetUser.uuid, localCluster) && + You can access {userToLink.email} data by logging into {localCluster} with the {targetUser.email} account. + } } { error === LinkAccountPanelError.NON_ADMIN && Cannot link admin account {displayUser(userToLink)} to non-admin account {displayUser(targetUser)}. diff --git a/src/views/link-account-panel/link-account-panel.tsx b/src/views/link-account-panel/link-account-panel.tsx index 06359111..3bdfbe43 100644 --- a/src/views/link-account-panel/link-account-panel.tsx +++ b/src/views/link-account-panel/link-account-panel.tsx @@ -18,6 +18,7 @@ const mapStateToProps = (state: RootState): LinkAccountPanelRootDataProps => { remoteHosts: state.auth.remoteHosts, hasRemoteHosts: Object.keys(state.auth.remoteHosts).length > 1, selectedCluster: state.linkAccountPanel.selectedCluster, + localCluster: state.auth.localCluster, targetUser: state.linkAccountPanel.targetUser, userToLink: state.linkAccountPanel.userToLink, status: state.linkAccountPanel.status, -- 2.30.2