feat(plugin-connections): CB-1360 CB-1359 ssh key for auth dialogs

This commit is contained in:
Naumov Alexey
2022-01-14 04:25:14 +03:00
parent 9a8c56c2b8
commit ed5a3ba5b9
10 changed files with 121 additions and 28 deletions
@@ -73,6 +73,7 @@ export const Textarea: TextareaType = observer(function Textarea({
style,
value: controlledValue,
state,
required,
children,
className,
fill,
@@ -103,7 +104,7 @@ export const Textarea: TextareaType = observer(function Textarea({
return styled(useStyles(baseFormControlStyles, styles, style))(
<field className={className} {...use({ tiny, small, medium, large, embedded })}>
<field-label as='label' title={rest.title}>{children}</field-label>
<field-label as='label' title={rest.title}>{children}{required && ' *'}</field-label>
<textarea
{...rest}
value={value ?? ''}
@@ -0,0 +1,9 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export const USER_NAME_PROPERTY_ID = 'userName';
@@ -19,3 +19,4 @@ export * from './isJDBCConnection';
export * from './NetworkHandlerResource';
export * from './useConnectionInfo';
export * from './useDBDriver';
export * from './USER_NAME_PROPERTY_ID';
@@ -6,14 +6,14 @@
* you may not use this file except in compliance with the License.
*/
import { observable, makeObservable } from 'mobx';
import { observable, makeObservable, computed } from 'mobx';
import { DBDriverResource, Connection, DatabaseAuthModelsResource, ConnectionInfoResource, DBDriver, ConnectionInitConfig } from '@cloudbeaver/core-connections';
import { DBDriverResource, Connection, DatabaseAuthModelsResource, ConnectionInfoResource, DBDriver, ConnectionInitConfig, USER_NAME_PROPERTY_ID } from '@cloudbeaver/core-connections';
import { injectable, IInitializableController, IDestructibleController } from '@cloudbeaver/core-di';
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications';
import { DatabaseAuthModel, DetailsError } from '@cloudbeaver/core-sdk';
import { DatabaseAuthModel, DetailsError, NetworkHandlerAuthType } from '@cloudbeaver/core-sdk';
import { getUniqueName } from '@cloudbeaver/core-utils';
import type { IConnectionAuthenticationConfig } from '@cloudbeaver/plugin-connections';
@@ -33,7 +33,7 @@ export interface IConnectionController {
@injectable()
export class ConnectionController
implements IInitializableController, IDestructibleController, IConnectionController {
implements IInitializableController, IDestructibleController, IConnectionController {
step = ConnectionStep.ConnectionTemplateSelect;
isLoading = true;
isConnecting = false;
@@ -67,6 +67,16 @@ implements IInitializableController, IDestructibleController, IConnectionControl
return this.dbDrivers.get(this.template.driverId);
}
get networkHandlers(): string[] {
if (!this.template) {
return [];
}
return this.template.networkHandlersConfig
.filter(handler => handler.enabled && !handler.savePassword)
.map(handler => handler.id)
}
constructor(
private dbDriverResource: DBDriverResource,
private connectionInfoResource: ConnectionInfoResource,
@@ -84,6 +94,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl
config: observable,
hasDetails: observable,
responseMessage: observable,
networkHandlers: computed
});
}
@@ -144,6 +155,29 @@ implements IInitializableController, IDestructibleController, IConnectionControl
saveCredentials: false,
};
if (this.template.authNeeded) {
const property = this.template.authProperties?.find(property => property.id === USER_NAME_PROPERTY_ID);
if (property?.value) {
this.config.credentials[USER_NAME_PROPERTY_ID] = property.value;
}
}
for (const id of this.networkHandlers) {
const handler = this.template.networkHandlersConfig.find(handler => handler.id === id);
if (handler && (handler.userName || handler.authType !== NetworkHandlerAuthType.Password)) {
this.config.networkHandlersConfig.push({
id: handler.id,
authType: handler.authType,
userName: handler.userName,
password: handler.password,
key: handler.key,
savePassword: handler.savePassword,
});
}
}
this.step = ConnectionStep.Connection;
if (!this.authModel) {
this.onConnect();
@@ -6,9 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { computed } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useMemo } from 'react';
import styled, { css } from 'reshadow';
import { useAdministrationSettings } from '@cloudbeaver/core-administration';
@@ -62,11 +60,6 @@ export const ConnectionDialog: DialogComponent<null, null> = observer(function C
subtitle = controller.template.name;
}
const networkHandlers = useMemo(() => computed(() => controller.template?.networkHandlersConfig
.filter(handler => handler.enabled && !handler.savePassword)
.map(handler => handler.id)
), [controller.template?.networkHandlersConfig]);
return styled(useStyles(styles))(
<CommonDialogWrapper
size='large'
@@ -102,7 +95,7 @@ export const ConnectionDialog: DialogComponent<null, null> = observer(function C
<ConnectionAuthenticationForm
config={controller.config}
authModelId={controller.authModel.id}
networkHandlers={networkHandlers.get()}
networkHandlers={controller.networkHandlers}
formId={controller.template?.id}
allowSaveCredentials={credentialsSavingEnabled}
disabled={controller.isConnecting}
@@ -30,7 +30,7 @@ export class TemplateConnectionsResource extends CachedDataResource<Connection[]
protected async loader(): Promise<Connection[]> {
const { connections } = await this.graphQLService.sdk.getTemplateConnections({
customIncludeNetworkHandlerCredentials: false,
customIncludeNetworkHandlerCredentials: true,
customIncludeOriginDetails: false,
includeAuthProperties: true,
includeOrigin: false,
@@ -9,11 +9,12 @@
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { BASE_CONTAINERS_STYLES, FieldCheckbox, GroupTitle, InputField, useMapResource } from '@cloudbeaver/core-blocks';
import { BASE_CONTAINERS_STYLES, Button, FieldCheckbox, GroupItem, GroupTitle, InputField, Textarea, UploadArea, useMapResource } from '@cloudbeaver/core-blocks';
import { NetworkHandlerResource } from '@cloudbeaver/core-connections';
import { useTranslate } from '@cloudbeaver/core-localization';
import type { NetworkHandlerConfigInput } from '@cloudbeaver/core-sdk';
import { NetworkHandlerAuthType, NetworkHandlerConfigInput } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
import { getTextFileReadingProcess } from '@cloudbeaver/core-utils';
interface Props {
id: string;
@@ -28,7 +29,8 @@ export const NetworkHandlerAuthForm = observer<Props>(function NetworkHandlerAut
if (!networkHandlersConfig.some(state => state.id === id)) {
networkHandlersConfig.push({
id: id,
id,
authType: NetworkHandlerAuthType.Password,
userName: '',
password: '',
savePassword: false,
@@ -37,6 +39,24 @@ export const NetworkHandlerAuthForm = observer<Props>(function NetworkHandlerAut
const state = networkHandlersConfig.find(state => state.id === id)!;
const handleKeyUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) {
throw new Error('File is not found');
}
const process = getTextFileReadingProcess(file);
const key = await process.promise;
if (key) {
state.key = key;
}
};
const keyAuth = state.authType === NetworkHandlerAuthType.PublicKey;
const passwordLabel = keyAuth ? 'Passphrase' : translate(`connections_network_handler_${id}_password`, 'connections_network_handler_default_password');
return styled(useStyles(BASE_CONTAINERS_STYLES))(
<>
<GroupTitle>{handler.data?.label || translate(`connections_network_handler_${id}_title`, 'connections_network_handler_default_title')}</GroupTitle>
@@ -56,8 +76,34 @@ export const NetworkHandlerAuthForm = observer<Props>(function NetworkHandlerAut
disabled={disabled}
mod='surface'
>
{translate(`connections_network_handler_${id}_password`, 'connections_network_handler_default_password')}
{passwordLabel}
</InputField>
{keyAuth && (
<>
<Textarea
name='key'
state={state}
disabled={disabled}
>
{translate('connections_network_handler_ssh_tunnel_private_key')}
</Textarea>
<GroupItem>
<UploadArea
accept='.txt, .ssh'
reset
disabled={disabled}
onChange={handleKeyUpload}
>
<Button
tag='div'
mod={['outlined']}
>
{translate('ui_file')}
</Button>
</UploadArea>
</GroupItem>
</>
)}
{allowSaveCredentials && (
<FieldCheckbox
id={id + ' savePassword'}
@@ -199,9 +199,11 @@ export class ConnectionSSHTabService extends Bootstrap {
);
if (handler.enabled !== initialConfig?.enabled
|| handler.authType !== initialConfig?.authType
|| handler.savePassword !== initialConfig?.savePassword
|| handler.userName !== initialConfig?.userName
|| passwordChanged
|| handler.key !== initialConfig?.key
|| handler.properties?.host !== initialConfig?.properties?.host
|| port !== formPort) {
return true;
@@ -73,9 +73,11 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
const enabled = state.enabled || false;
const keyAuth = state.authType === NetworkHandlerAuthType.PublicKey;
const passwordFilled = (initialConfig?.password === null && state.password !== '') || (state.password?.length || 0) > 0;
const testAvailable = keyAuth ? !!state.key?.length : passwordFilled;
const passwordLabel = keyAuth ? 'Passphrase' : translate('connections_network_handler_ssh_tunnel_password');
let passwordHint = '';
if (initialConfig?.password === '') {
if (initialConfig?.password === '' && initialConfig.authType === state.authType) {
passwordHint = '••••••';
}
@@ -125,6 +127,7 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
disabled={disabled || !enabled}
readOnly={readonly}
mod='surface'
required
small
>
{translate('connections_network_handler_ssh_tunnel_host')}
@@ -136,6 +139,7 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
disabled={disabled || !enabled}
readOnly={readonly}
mod='surface'
required
tiny
>
{translate('connections_network_handler_ssh_tunnel_port')}
@@ -156,15 +160,16 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
<InputField
type="password"
name="password"
placeholder={keyAuth ? undefined : passwordHint}
placeholder={passwordHint}
autoComplete='new-password'
state={state}
disabled={disabled || !enabled}
readOnly={readonly}
mod='surface'
required={!keyAuth}
tiny
>
{keyAuth ? 'Passphrase' : translate('connections_network_handler_ssh_tunnel_password')}
{passwordLabel}
</InputField>
{keyAuth && (
<>
@@ -173,6 +178,7 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
state={state}
disabled={disabled || !enabled}
readOnly={readonly}
required
medium
>
{translate('connections_network_handler_ssh_tunnel_private_key')}
@@ -201,14 +207,15 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
name="savePassword"
state={state}
disabled={disabled || !enabled || readonly}
>{translate('connections_connection_edit_save_credentials')}
>
{translate('connections_connection_edit_save_credentials')}
</FieldCheckbox>
)}
<GroupItem>
<Button
type='button'
mod={['unelevated']}
disabled={disabled || !enabled || (!keyAuth && !passwordFilled)}
disabled={disabled || !enabled || !testAvailable}
loader
onClick={testConnection}
>
@@ -8,17 +8,15 @@
import { observable, makeObservable } from 'mobx';
import { ConnectionInfoResource, ConnectionInitConfig, DBDriverResource } from '@cloudbeaver/core-connections';
import { ConnectionInfoResource, ConnectionInitConfig, DBDriverResource, USER_NAME_PROPERTY_ID } from '@cloudbeaver/core-connections';
import { injectable, IInitializableController, IDestructibleController } from '@cloudbeaver/core-di';
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications';
import { GQLErrorCatcher } from '@cloudbeaver/core-sdk';
import { GQLErrorCatcher, NetworkHandlerAuthType } from '@cloudbeaver/core-sdk';
import type { IConnectionAuthenticationConfig } from '../ConnectionAuthentication/IConnectionAuthenticationConfig';
const USER_NAME_PROPERTY_ID = 'userName';
@injectable()
export class DBAuthDialogController implements IInitializableController, IDestructibleController {
isAuthenticating = false;
@@ -121,11 +119,13 @@ export class DBAuthDialogController implements IInitializableController, IDestru
for (const id of this.networkHandlers) {
const handler = connection.networkHandlersConfig.find(handler => handler.id === id);
if (handler?.userName) {
if (handler && (handler.userName || handler.authType !== NetworkHandlerAuthType.Password)) {
this.config.networkHandlersConfig.push({
id: handler.id,
authType: handler.authType,
userName: handler.userName,
password: handler.password,
key: handler.key,
savePassword: handler.savePassword,
});
}