Merge branch '21128-toolbar-context-menu'
[arvados-workbench2.git] / src / store / collections / collection-info-actions.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { ofType, unionize } from 'common/unionize';
6 import { Dispatch } from "redux";
7 import { RootState } from "store/store";
8 import { ServiceRepository } from "services/services";
9 import { dialogActions } from 'store/dialog/dialog-actions';
10 import { CollectionResource } from "models/collection";
11 import { SshKeyResource } from 'models/ssh-key';
12 import { User } from "models/user";
13 import { Session } from "models/session";
14 import { Config } from 'common/config';
15 import { createServices, setAuthorizationHeader } from "services/services";
16 import { getTokenV2 } from 'models/api-client-authorization';
17
18 export const COLLECTION_WEBDAV_S3_DIALOG_NAME = 'collectionWebdavS3Dialog';
19
20 export interface WebDavS3InfoDialogData {
21     uuid: string;
22     token: string;
23     downloadUrl: string;
24     collectionsUrl: string;
25     localCluster: string;
26     username: string;
27     activeTab: number;
28     collectionName: string;
29     setActiveTab: (event: any, tabNr: number) => void;
30 }
31
32 export const openWebDavS3InfoDialog = (uuid: string, activeTab?: number) =>
33     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
34         await dispatch<any>(getNewExtraToken(true));
35         dispatch(dialogActions.OPEN_DIALOG({
36             id: COLLECTION_WEBDAV_S3_DIALOG_NAME,
37             data: {
38                 title: 'Open with 3rd party client',
39                 token: getState().auth.extraApiToken || getState().auth.apiToken,
40                 downloadUrl: getState().auth.config.keepWebServiceUrl,
41                 collectionsUrl: getState().auth.config.keepWebInlineServiceUrl,
42                 localCluster: getState().auth.localCluster,
43                 username: getState().auth.user!.username,
44                 activeTab: activeTab || 0,
45                 collectionName: (getState().resources[uuid] as CollectionResource).name,
46                 setActiveTab: (event: any, tabNr: number) => dispatch<any>(openWebDavS3InfoDialog(uuid, tabNr)),
47                 uuid
48             }
49         }));
50     };
51
52 const authActions = unionize({
53     LOGIN: {},
54     LOGOUT: ofType<{ deleteLinkData: boolean, preservePath: boolean }>(),
55     SET_CONFIG: ofType<{ config: Config }>(),
56     SET_EXTRA_TOKEN: ofType<{ extraApiToken: string, extraApiTokenExpiration?: Date }>(),
57     RESET_EXTRA_TOKEN: {},
58     INIT_USER: ofType<{ user: User, token: string, tokenExpiration?: Date, tokenLocation?: string }>(),
59     USER_DETAILS_REQUEST: {},
60     USER_DETAILS_SUCCESS: ofType<User>(),
61     SET_SSH_KEYS: ofType<SshKeyResource[]>(),
62     ADD_SSH_KEY: ofType<SshKeyResource>(),
63     REMOVE_SSH_KEY: ofType<string>(),
64     SET_HOME_CLUSTER: ofType<string>(),
65     SET_SESSIONS: ofType<Session[]>(),
66     ADD_SESSION: ofType<Session>(),
67     REMOVE_SESSION: ofType<string>(),
68     UPDATE_SESSION: ofType<Session>(),
69     REMOTE_CLUSTER_CONFIG: ofType<{ config: Config }>(),
70 });
71
72 const getConfig = (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Config => {
73     const state = getState().auth;
74     return state.remoteHostsConfig[state.localCluster];
75 };
76
77 const getNewExtraToken =
78     (reuseStored: boolean = false) =>
79     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
80         const extraToken = getState().auth.extraApiToken;
81         if (reuseStored && extraToken !== undefined) {
82             const config = dispatch<any>(getConfig);
83             const svc = createServices(config, { progressFn: () => {}, errorFn: () => {} });
84             setAuthorizationHeader(svc, extraToken);
85             try {
86                 // Check the extra token's validity before using it. Refresh its
87                 // expiration date just in case it changed.
88                 const client = await svc.apiClientAuthorizationService.get('current');
89                 dispatch(
90                     authActions.SET_EXTRA_TOKEN({
91                         extraApiToken: extraToken,
92                         extraApiTokenExpiration: client.expiresAt ? new Date(client.expiresAt) : undefined,
93                     })
94                 );
95                 return extraToken;
96             } catch (e) {
97                 dispatch(authActions.RESET_EXTRA_TOKEN());
98             }
99         }
100         const user = getState().auth.user;
101         const loginCluster = getState().auth.config.clusterConfig.Login.LoginCluster;
102         if (user === undefined) {
103             return;
104         }
105         if (loginCluster !== '' && getState().auth.homeCluster !== loginCluster) {
106             return;
107         }
108         try {
109             // Do not show errors on the create call, cluster security configuration may not
110             // allow token creation and there's no way to know that from workbench2 side in advance.
111             const client = await services.apiClientAuthorizationService.create(undefined, false);
112             const newExtraToken = getTokenV2(client);
113             dispatch(
114                 authActions.SET_EXTRA_TOKEN({
115                     extraApiToken: newExtraToken,
116                     extraApiTokenExpiration: client.expiresAt ? new Date(client.expiresAt) : undefined,
117                 })
118             );
119             return newExtraToken;
120         } catch {
121             console.warn("Cannot create new tokens with the current token, probably because of cluster's security settings.");
122             return;
123         }
124     };