mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
feat(plugin-connections): CB-1360 CB-1359 ssh key for auth dialogs
This commit is contained in:
@@ -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';
|
||||
|
||||
+38
-4
@@ -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();
|
||||
|
||||
+1
-8
@@ -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,
|
||||
|
||||
+50
-4
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user