From ccf093578f01b3d8ac0c421f85063939b3cd271d Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Fri, 10 May 2019 13:33:14 -0400 Subject: [PATCH] 15064: Fetch discovery documents for all remotes Start working on rendering iframe to login to remotes Arvados-DCO-1.1-Signed-off-by: Peter Amstutz --- src/common/config.ts | 3 +- src/index.tsx | 4 ++- src/routes/routes.ts | 1 + src/store/auth/auth-action-session.ts | 2 +- src/store/auth/auth-action.ts | 10 +++++-- src/store/auth/auth-reducer.ts | 11 ++++++- src/views-components/api-token/api-token.tsx | 8 +++-- src/views/workbench/fed-login.tsx | 31 ++++++++++++++++++++ src/views/workbench/workbench.tsx | 2 ++ 9 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 src/views/workbench/fed-login.tsx diff --git a/src/common/config.ts b/src/common/config.ts index 3961d5aa..7abff5da 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -51,6 +51,7 @@ export interface Config { version: string; websocketUrl: string; workbenchUrl: string; + workbench2Url?: string; vocabularyUrl: string; fileViewersConfigUrl: string; } @@ -136,4 +137,4 @@ const getDefaultConfig = (): ConfigJSON => ({ }); export const DISCOVERY_URL = 'discovery/v1/apis/arvados/v1/rest'; -const getDiscoveryURL = (apiHost: string) => `${window.location.protocol}//${apiHost}/${DISCOVERY_URL}`; +export const getDiscoveryURL = (apiHost: string) => `${window.location.protocol}//${apiHost}/${DISCOVERY_URL}`; diff --git a/src/index.tsx b/src/index.tsx index 9c7b39aa..ee174b2c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -113,7 +113,8 @@ fetchConfig() store.dispatch(loadVocabulary); store.dispatch(loadFileViewersConfig); - const TokenComponent = (props: any) => ; + const TokenComponent = (props: any) => ; + const FedTokenComponent = (props: any) => ; const MainPanelComponent = (props: any) => ; const App = () => @@ -123,6 +124,7 @@ fetchConfig() + diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 3fd6670d..bd47ca2f 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -10,6 +10,7 @@ import { getCollectionUrl } from '~/models/collection'; export const Routes = { ROOT: '/', TOKEN: '/token', + FED_LOGIN: '/fedtoken', PROJECTS: `/projects/:id(${RESOURCE_UUID_PATTERN})`, COLLECTIONS: `/collections/:id(${RESOURCE_UUID_PATTERN})`, PROCESSES: `/processes/:id(${RESOURCE_UUID_PATTERN})`, diff --git a/src/store/auth/auth-action-session.ts b/src/store/auth/auth-action-session.ts index 5bb192b8..b889e9cf 100644 --- a/src/store/auth/auth-action-session.ts +++ b/src/store/auth/auth-action-session.ts @@ -68,7 +68,7 @@ const getTokenUuid = async (baseUrl: string, token: string): Promise => return resp.data.items[0].uuid; }; -const getSaltedToken = (clusterId: string, tokenUuid: string, token: string) => { +export const getSaltedToken = (clusterId: string, tokenUuid: string, token: string) => { const shaObj = new jsSHA("SHA-1", "TEXT"); let secret = token; if (token.startsWith("v2/")) { diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts index baf80595..09d922e0 100644 --- a/src/store/auth/auth-action.ts +++ b/src/store/auth/auth-action.ts @@ -10,8 +10,9 @@ import { ServiceRepository } from "~/services/services"; import { SshKeyResource } from '~/models/ssh-key'; import { User } from "~/models/user"; import { Session } from "~/models/session"; -import { Config } from '~/common/config'; +import { getDiscoveryURL, Config } from '~/common/config'; import { initSessions } from "~/store/auth/auth-action-session"; +import Axios from "axios"; export const authActions = unionize({ SAVE_API_TOKEN: ofType(), @@ -28,7 +29,8 @@ export const authActions = unionize({ SET_SESSIONS: ofType(), ADD_SESSION: ofType(), REMOVE_SESSION: ofType(), - UPDATE_SESSION: ofType() + UPDATE_SESSION: ofType(), + REMOTE_CLUSTER_CONFIG: ofType<{ config: Config }>(), }); function setAuthorizationHeader(services: ServiceRepository, token: string) { @@ -57,6 +59,10 @@ export const initAuth = (config: Config) => (dispatch: Dispatch, getState: () => dispatch(getUserDetails()).then((user: User) => { dispatch(authActions.INIT({ user, token })); }); + Object.keys(config.remoteHosts).map((k) => { + Axios.get(getDiscoveryURL(config.remoteHosts[k])) + .then(response => dispatch(authActions.REMOTE_CLUSTER_CONFIG({ config: response.data }))); + }); } }; diff --git a/src/store/auth/auth-reducer.ts b/src/store/auth/auth-reducer.ts index 03357526..e44c81e3 100644 --- a/src/store/auth/auth-reducer.ts +++ b/src/store/auth/auth-reducer.ts @@ -7,6 +7,7 @@ import { User } from "~/models/user"; import { ServiceRepository } from "~/services/services"; import { SshKeyResource } from '~/models/ssh-key'; import { Session } from "~/models/session"; +import { Config } from '~/common/config'; export interface AuthState { user?: User; @@ -16,6 +17,7 @@ export interface AuthState { localCluster: string; homeCluster: string; remoteHosts: { [key: string]: string }; + remoteHostsConfig: { [key: string]: Config }; } const initialState: AuthState = { @@ -25,7 +27,8 @@ const initialState: AuthState = { sessions: [], localCluster: "", homeCluster: "", - remoteHosts: {} + remoteHosts: {}, + remoteHostsConfig: {} }; export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => { @@ -41,6 +44,12 @@ export const authReducer = (services: ServiceRepository) => (state = initialStat homeCluster: config.uuidPrefix }; }, + REMOTE_CLUSTER_CONFIG: ({ config }) => { + return { + ...state, + remoteHostsConfig: { ...state.remoteHostsConfig, [config.uuidPrefix]: config }, + }; + }, INIT: ({ user, token }) => { return { ...state, user, apiToken: token, homeCluster: user.uuid.substr(0, 5) }; }, diff --git a/src/views-components/api-token/api-token.tsx b/src/views-components/api-token/api-token.tsx index 43c55a92..b78e7192 100644 --- a/src/views-components/api-token/api-token.tsx +++ b/src/views-components/api-token/api-token.tsx @@ -16,6 +16,7 @@ import { initSessions } from "~/store/auth/auth-action-session"; interface ApiTokenProps { authService: AuthService; config: Config; + loadMainApp: boolean; } export const ApiToken = connect()( @@ -23,15 +24,18 @@ export const ApiToken = connect()( componentDidMount() { const search = this.props.location ? this.props.location.search : ""; const apiToken = getUrlParameter(search, 'api_token'); + const loadMainApp = this.props.loadMainApp; this.props.dispatch(saveApiToken(apiToken)); this.props.dispatch(getUserDetails()).then((user: User) => { this.props.dispatch(initSessions(this.props.authService, this.props.config, user)); }).finally(() => { - this.props.dispatch(navigateToRootProject); + if (loadMainApp) { + this.props.dispatch(navigateToRootProject); + } }); } render() { - return
; + return
; } } ); diff --git a/src/views/workbench/fed-login.tsx b/src/views/workbench/fed-login.tsx new file mode 100644 index 00000000..0e9b5308 --- /dev/null +++ b/src/views/workbench/fed-login.tsx @@ -0,0 +1,31 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import { connect } from 'react-redux'; +import { RootState } from '~/store/store'; +import { AuthState } from '~/store/auth/auth-reducer'; +import { getSaltedToken } from '~/store/auth/auth-action-session'; + +export interface FedLoginProps { + auth: AuthState; +} + +const mapStateToProps = ({ auth }: RootState) => ({ auth }); + +export const FedLogin = connect(mapStateToProps)( + class extends React.Component { + render() { + const auth = this.props.auth; + const remoteHostsConfig = auth.remoteHostsConfig; + if (!auth.user || !auth.user.uuid.startsWith(auth.homeCluster)) { + return <>; + } + return
+ {Object.keys(remoteHostsConfig) + .filter((k) => k !== auth.homeCluster) + .map((k) =>