feat(core-connections): CB-937 require credentials if we need to during test connection

This commit is contained in:
Naumov Alexey
2021-08-11 18:05:38 +03:00
parent f1c9d8e936
commit e5ba6f2625
28 changed files with 438 additions and 176 deletions
@@ -55,11 +55,18 @@ export class ConnectionAuthService {
}
const sshConfig = connection.networkHandlersConfig.find(state => state.id === SSH_TUNNEL_ID);
const sshAuthRequired = sshConfig?.enabled && !sshConfig?.savePassword;
const networkHandlers: string[] = [];
const isSSHAuthNeeded = sshConfig?.enabled && !sshConfig?.savePassword;
if (sshAuthRequired) {
networkHandlers.push(SSH_TUNNEL_ID);
}
if (connection.authNeeded || isSSHAuthNeeded) {
await this.commonDialogService.open(DatabaseAuthDialog, connectionId);
if (connection.authNeeded || sshAuthRequired) {
await this.commonDialogService.open(DatabaseAuthDialog, {
connectionId,
networkHandlers,
});
} else {
await this.connectionInfoResource.init({ id: connectionId });
}
@@ -0,0 +1,79 @@
/*
* 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.
*/
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { SubmittingForm, useFocus, Button } from '@cloudbeaver/core-blocks';
import { CommonDialogWrapper, DialogComponentProps } from '@cloudbeaver/core-dialogs';
import { useTranslate } from '@cloudbeaver/core-localization';
import type { ConnectionConfig } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
import { useDBDriver } from '../useDBDriver';
import { ConnectionCredentialsForm } from './ConnectionCredentialsForm';
const styles = css`
CommonDialogWrapper {
min-height: 400px;
min-width: 500px;
}
SubmittingForm {
overflow: auto;
margin: auto;
flex: 1;
display: flex;
flex-direction: column;
}
ConnectionCredentialsForm {
align-content: center;
}
Button {
margin-left: auto;
}
`;
interface Payload {
config: ConnectionConfig;
authModelId: string | null;
networkHandlers?: string[];
driverId?: string;
}
export const ConnectionCredentialsDialog = observer(function ConnectionCredentialsDialog({
payload,
rejectDialog,
resolveDialog,
}: DialogComponentProps<Payload>) {
const translate = useTranslate();
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
const { driver } = useDBDriver(payload.driverId || '');
return styled(useStyles(styles))(
<CommonDialogWrapper
title={translate('connections_connection_credentials_provisioning')}
subTitle={translate('connections_connection_credentials_provisioning_description')}
icon={driver?.icon}
footer={(
<Button mod={['unelevated']} onClick={() => resolveDialog()}>
{translate('ui_apply')}
</Button>
)}
onReject={rejectDialog}
>
<SubmittingForm ref={focusedRef} onSubmit={() => resolveDialog()}>
<ConnectionCredentialsForm
config={payload.config}
authModelId={payload.authModelId}
networkHandlers={payload.networkHandlers}
formId={payload.config.connectionId || payload.driverId}
/>
</SubmittingForm>
</CommonDialogWrapper>
);
});
@@ -0,0 +1,74 @@
/*
* 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.
*/
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { BASE_CONTAINERS_STYLES, Container, FieldCheckboxNew, Group, GroupTitle, ObjectPropertyInfoFormNew, TextPlaceholder, useMapResource } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles } from '@cloudbeaver/core-theming';
import { DatabaseAuthModelsResource } from '../DatabaseAuthModelsResource';
import type { IConnectionAuthCredentialsConfig } from './IConnectionAuthCredentialsConfig';
import { NetworkHandlers } from './NetworkHandlers';
interface Props {
config: Partial<IConnectionAuthCredentialsConfig>;
authModelId: string | null;
networkHandlers?: string[];
formId?: string;
allowSaveCredentials?: boolean;
disabled?: boolean;
className?: string;
}
export const ConnectionCredentialsForm: React.FC<Props> = observer(function ConnectionCredentialsForm({
config, networkHandlers, authModelId, formId, allowSaveCredentials, disabled, className,
}) {
const translate = useTranslate();
const { data: authModel } = useMapResource(DatabaseAuthModelsResource, authModelId);
return styled(useStyles(BASE_CONTAINERS_STYLES))(
<Container className={className}>
{authModel && (
<Group gap small>
{authModel.properties ? (
<>
{!!networkHandlers?.length && <GroupTitle>{translate('connections_database_authentication')}</GroupTitle>}
<ObjectPropertyInfoFormNew
autofillToken={`section-${formId || ''} section-auth`}
properties={authModel.properties}
state={config.credentials}
disabled={disabled}
/>
{allowSaveCredentials && (
<FieldCheckboxNew
id={formId || 'DBAuthSaveCredentials'}
name="saveCredentials"
label={translate('connections_connection_edit_save_credentials')}
disabled={disabled}
state={config}
/>
)}
</>
) : (
<TextPlaceholder>Authentication data is not avaliable</TextPlaceholder>
)}
</Group>
)}
{networkHandlers && config.networkHandlersConfig && (
<NetworkHandlers
networkHandlers={networkHandlers}
networkHandlersConfig={config.networkHandlersConfig}
allowSaveCredentials={allowSaveCredentials}
disabled={disabled}
/>
)}
</Container>
);
});
@@ -8,8 +8,8 @@
import type { NetworkHandlerConfigInput } from '@cloudbeaver/core-sdk';
import type { ConnectionInitConfig } from '../ConnectionInfoResource';
export interface IFormInitConfig extends Omit<Required<ConnectionInitConfig>, 'id'> {
networkCredentials: NetworkHandlerConfigInput[];
export interface IConnectionAuthCredentialsConfig {
credentials: any;
networkHandlersConfig: NetworkHandlerConfigInput[];
saveCredentials: boolean | undefined;
}
@@ -11,39 +11,36 @@ import styled from 'reshadow';
import { BASE_CONTAINERS_STYLES, FieldCheckboxNew, GroupTitle, InputFieldNew, useMapResource } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import type { NetworkHandlerConfigInput } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
import { NetworkHandlerResource } from '../NetworkHandlerResource';
import type { IFormInitConfig } from './IFormInitConfig';
interface Props {
config: IFormInitConfig;
sshHandlerId: string;
allowPasswordSave: boolean;
disabled: boolean;
className?: string;
id: string;
networkHandlersConfig: NetworkHandlerConfigInput[];
allowSaveCredentials?: boolean;
disabled?: boolean;
}
export const SSHAuthForm: React.FC<Props> = observer(function SSHAuthForm({
config, sshHandlerId, allowPasswordSave, disabled, className,
}) {
export const NetworkHandlerAuthForm: React.FC<Props> = observer(function NetworkHandlerAuthForm({ id, networkHandlersConfig, allowSaveCredentials, disabled }) {
const translate = useTranslate();
const handler = useMapResource(NetworkHandlerResource, sshHandlerId);
const handler = useMapResource(NetworkHandlerResource, id);
if (!config.networkCredentials.some(state => state.id === sshHandlerId)) {
config.networkCredentials.push({
id: sshHandlerId,
if (!networkHandlersConfig.some(state => state.id === id)) {
networkHandlersConfig!.push({
id: id,
userName: '',
password: '',
savePassword: false,
});
}
const state = config.networkCredentials.find(state => state.id === sshHandlerId)!;
const state = networkHandlersConfig.find(state => state.id === id)!;
return styled(useStyles(BASE_CONTAINERS_STYLES))(
<>
<GroupTitle>{translate(handler.data?.label || 'connections_network_handler_ssh_tunnel_title')}</GroupTitle>
<GroupTitle>{handler.data?.label || translate(`connections_network_handler_${id}_title`, 'connections_network_handler_default_title')}</GroupTitle>
<InputFieldNew
type="text"
name="userName"
@@ -51,7 +48,7 @@ export const SSHAuthForm: React.FC<Props> = observer(function SSHAuthForm({
disabled={disabled}
mod='surface'
>
{translate('connections_network_handler_ssh_tunnel_user')}
{translate(`connections_network_handler_${id}_user`, 'connections_network_handler_default_user')}
</InputFieldNew>
<InputFieldNew
type="password"
@@ -60,11 +57,11 @@ export const SSHAuthForm: React.FC<Props> = observer(function SSHAuthForm({
disabled={disabled}
mod='surface'
>
{translate('connections_network_handler_ssh_tunnel_password')}
{translate(`connections_network_handler_${id}_password`, 'connections_network_handler_default_password')}
</InputFieldNew>
{allowPasswordSave && (
{allowSaveCredentials && (
<FieldCheckboxNew
id={sshHandlerId + ' savePassword'}
id={id + ' savePassword'}
name="savePassword"
state={state}
label={translate('connections_connection_edit_save_credentials')}
@@ -0,0 +1,46 @@
/*
* 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.
*/
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { BASE_CONTAINERS_STYLES, Group } from '@cloudbeaver/core-blocks';
import type { NetworkHandlerConfigInput } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
import { NetworkHandlerAuthForm } from './NetworkHandlerAuthForm';
interface Props {
networkHandlers: string[];
networkHandlersConfig: NetworkHandlerConfigInput[];
disabled?: boolean;
allowSaveCredentials?: boolean;
}
export const NetworkHandlers: React.FC<Props> = observer(function NetworkHandlers({ networkHandlers, networkHandlersConfig, allowSaveCredentials, disabled }) {
const styles = useStyles(BASE_CONTAINERS_STYLES);
if (!networkHandlers.length) {
return null;
}
return styled(styles)(
<Group gap small>
{networkHandlers.map(handler => (
<NetworkHandlerAuthForm
key={handler}
id={handler}
networkHandlersConfig={networkHandlersConfig}
allowSaveCredentials={allowSaveCredentials}
disabled={disabled}
/>
))}
</Group>
);
});
@@ -12,9 +12,9 @@ import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
import type { MetadataValueGetter } from '@cloudbeaver/core-utils';
import { ConnectionsResource } from '../../Administration/ConnectionsResource';
import { connectionConfigContext } from '../connectionConfigContext';
import { ConnectionFormService } from '../ConnectionFormService';
import { connectionFormStateContext } from '../connectionFormStateContext';
import { connectionConfigContext } from '../Contexts/connectionConfigContext';
import { connectionFormStateContext } from '../Contexts/connectionFormStateContext';
import type { IConnectionFormProps, IConnectionFormState, IConnectionFormSubmitData } from '../IConnectionFormProps';
import { ConnectionAccess } from './ConnectionAccess';
import type { IConnectionAccessTabState } from './IConnectionAccessTabState';
@@ -16,8 +16,8 @@ import { useTranslate } from '@cloudbeaver/core-localization';
import type { ConnectionConfig } from '@cloudbeaver/core-sdk';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import { connectionConfigContext } from './connectionConfigContext';
import { ConnectionFormService } from './ConnectionFormService';
import { connectionConfigContext } from './Contexts/connectionConfigContext';
import type { IConnectionFormState } from './IConnectionFormProps';
const tabsStyles = css`
@@ -8,10 +8,14 @@
import { PlaceholderContainer, TabsContainer } from '@cloudbeaver/core-blocks';
import { injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { ENotificationType, NotificationService } from '@cloudbeaver/core-events';
import { ExecutorHandlersCollection, ExecutorInterrupter, IExecutorHandler, IExecutorHandlersCollection } from '@cloudbeaver/core-executor';
import { ConnectionCredentialsDialog } from '../ConnectionCredentials/ConnectionCredentialsDialog';
import { ConnectionFormBaseActions } from './ConnectionFormBaseActions';
import { connectionConfigContext } from './Contexts/connectionConfigContext';
import { connectionCredentialsStateContext } from './Contexts/connectionCredentialsStateContext';
import type { IConnectionFormProps, IConnectionFormState, IConnectionFormFillConfigData, IConnectionFormSubmitData } from './IConnectionFormProps';
export interface IConnectionFormValidation {
@@ -42,7 +46,8 @@ export class ConnectionFormService {
readonly formStateTask: IExecutorHandlersCollection<IConnectionFormState>;
constructor(
private readonly notificationService: NotificationService
private readonly notificationService: NotificationService,
private readonly commonDialogService: CommonDialogService,
) {
this.tabsContainer = new TabsContainer();
this.actionsContainer = new PlaceholderContainer();
@@ -60,6 +65,7 @@ export class ConnectionFormService {
this.formStateTask
.before<IConnectionFormSubmitData>(this.prepareConfigTask, state => ({ state, submitType: 'submit' }));
this.prepareConfigTask.addPostHandler(this.ensureCredentialsExisting);
this.formSubmittingTask.addPostHandler(this.showSubmittingStatusMessage);
this.formValidationTask.addPostHandler(this.ensureValidation);
@@ -115,6 +121,35 @@ export class ConnectionFormService {
}
};
private ensureCredentialsExisting: IExecutorHandler<IConnectionFormSubmitData> = async (data, contexts) => {
const credentialsState = contexts.getContext(connectionCredentialsStateContext);
if (data.submitType !== 'test' || (!credentialsState.authModelId && !credentialsState.networkHandlers.length)) {
return;
}
const config = contexts.getContext(connectionConfigContext);
if (config.credentials === undefined && credentialsState.authModelId) {
config.credentials = {};
}
if (config.networkHandlersConfig === undefined && credentialsState.networkHandlers.length > 0) {
config.networkHandlersConfig = [];
}
const result = await this.commonDialogService.open(ConnectionCredentialsDialog, {
config,
authModelId: credentialsState.authModelId,
networkHandlers: credentialsState.networkHandlers,
driverId: config.driverId,
});
if (result === DialogueStateResult.Rejected) {
ExecutorInterrupter.interrupt(contexts);
}
};
private ensureValidation: IExecutorHandler<IConnectionFormSubmitData> = (data, contexts) => {
const validation = contexts.getContext(this.connectionValidationContext);
@@ -16,7 +16,7 @@ import type { DatabaseConnection } from '../Administration/ConnectionsResource';
import { EConnectionFeature } from '../EConnectionFeature';
import { connectionFormConfigureContext } from './connectionFormConfigureContext';
import type { ConnectionFormService } from './ConnectionFormService';
import { connectionFormStateContext, IConnectionFormStateInfo } from './connectionFormStateContext';
import { connectionFormStateContext, IConnectionFormStateInfo } from './Contexts/connectionFormStateContext';
import type { IConnectionFormState, ConnectionFormMode, ConnectionFormType, IConnectionFormSubmitData } from './IConnectionFormProps';
export class ConnectionFormState implements IConnectionFormState {
@@ -0,0 +1,30 @@
/*
* 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.
*/
interface IConnectionCredentialsState {
authModelId: string | null;
networkHandlers: string[];
}
interface IConnectionCredentialsStateContext extends IConnectionCredentialsState {
requireAuthModel: (id: string) => void;
requireNetworkHandler: (id: string) => void;
}
export function connectionCredentialsStateContext(): IConnectionCredentialsStateContext {
return {
authModelId: null,
networkHandlers: [],
requireAuthModel(id) {
this.authModelId = id;
},
requireNetworkHandler(id) {
this.networkHandlers.push(id);
},
};
}
@@ -11,9 +11,9 @@ import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { isObjectPropertyInfoStateEqual } from '@cloudbeaver/core-sdk';
import { DBDriverResource } from '../../DBDriverResource';
import { connectionConfigContext } from '../connectionConfigContext';
import { ConnectionFormService } from '../ConnectionFormService';
import { connectionFormStateContext } from '../connectionFormStateContext';
import { connectionConfigContext } from '../Contexts/connectionConfigContext';
import { connectionFormStateContext } from '../Contexts/connectionFormStateContext';
import type { IConnectionFormFillConfigData, IConnectionFormSubmitData, IConnectionFormState } from '../IConnectionFormProps';
import { DriverProperties } from './DriverProperties';
@@ -11,7 +11,7 @@ import type { CachedMapResource, ConnectionConfig, GetConnectionsQueryVariables
import type { MetadataMap } from '@cloudbeaver/core-utils';
import type { DatabaseConnection } from '../Administration/ConnectionsResource';
import type { IConnectionFormStateInfo } from './connectionFormStateContext';
import type { IConnectionFormStateInfo } from './Contexts/connectionFormStateContext';
export type ConnectionFormMode = 'edit' | 'create';
export type ConnectionFormType = 'admin' | 'public';
@@ -17,13 +17,16 @@ import { DatabaseAuthModelsResource } from '../../DatabaseAuthModelsResource';
import { DBDriverResource } from '../../DBDriverResource';
import { getUniqueConnectionName } from '../../getUniqueConnectionName';
import { isJDBCConnection } from '../../isJDBCConnection';
import { connectionConfigContext } from '../connectionConfigContext';
import { connectionFormConfigureContext } from '../connectionFormConfigureContext';
import { ConnectionFormService } from '../ConnectionFormService';
import { connectionFormStateContext } from '../connectionFormStateContext';
import { connectionConfigContext } from '../Contexts/connectionConfigContext';
import { connectionCredentialsStateContext } from '../Contexts/connectionCredentialsStateContext';
import { connectionFormStateContext } from '../Contexts/connectionFormStateContext';
import type { IConnectionFormSubmitData, IConnectionFormFillConfigData, IConnectionFormState } from '../IConnectionFormProps';
import { Options } from './Options';
const USER_NAME_AUTH_PROPERTY_ID = 'userName';
@injectable()
export class ConnectionOptionsTabService extends Bootstrap {
constructor(
@@ -124,7 +127,7 @@ export class ConnectionOptionsTabService extends Bootstrap {
}
}
private validate(
private async validate(
{
state,
}: IConnectionFormSubmitData,
@@ -135,6 +138,15 @@ export class ConnectionOptionsTabService extends Bootstrap {
if (!state.config.name?.length) {
validation.error("Field 'name' can't be empty");
}
if (state.config.authModelId && state.config.saveCredentials) {
const authProperties = await this.getConnectionAuthModelProperties(state.config.authModelId, state.info);
const userNameProperty = authProperties.find(property => property.id === USER_NAME_AUTH_PROPERTY_ID);
if (userNameProperty && (!state.config.credentials || !state.config.credentials[USER_NAME_AUTH_PROPERTY_ID])) {
validation.error(`Field '${userNameProperty.displayName || 'User name'}' can't be empty`);
}
}
}
private fillConfig(
@@ -199,6 +211,7 @@ export class ConnectionOptionsTabService extends Bootstrap {
contexts: IExecutionContextProvider<IConnectionFormSubmitData>
) {
const config = contexts.getContext(connectionConfigContext);
const credentialsState = contexts.getContext(connectionCredentialsStateContext);
const driver = await this.dbDriverResource.load(state.config.driverId!, ['includeProviderProperties']);
@@ -240,7 +253,15 @@ export class ConnectionOptionsTabService extends Bootstrap {
const properties = await this.getConnectionAuthModelProperties(config.authModelId, state.info);
if (this.isCredentialsChanged(properties, state.config.credentials)) {
config.credentials = state.config.credentials;
config.credentials = { ...state.config.credentials };
}
if (
config.authModelId
&& !state.config?.saveCredentials
&& (!state.config.credentials || hasMissingCredentials(properties, state.config.credentials))
) {
credentialsState.requireAuthModel(config.authModelId);
}
}
@@ -332,3 +353,8 @@ export class ConnectionOptionsTabService extends Bootstrap {
return properties;
}
}
function hasMissingCredentials(authProperties: ObjectPropertyInfo[], credentials: Record<string, any>) {
return authProperties.some(property => property.id === USER_NAME_AUTH_PROPERTY_ID)
&& !credentials[USER_NAME_AUTH_PROPERTY_ID];
}
@@ -13,7 +13,7 @@ import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { isLocalConnection } from '../../Administration/ConnectionsResource';
import { connectionFormConfigureContext } from '../connectionFormConfigureContext';
import { ConnectionFormService } from '../ConnectionFormService';
import { connectionFormStateContext } from '../connectionFormStateContext';
import { connectionFormStateContext } from '../Contexts/connectionFormStateContext';
import type { IConnectionFormState } from '../IConnectionFormProps';
import { ConnectionFormAuthenticationAction } from './ConnectionFormAuthenticationAction';
import { OriginInfo } from './OriginInfo';
@@ -13,10 +13,11 @@ import type { NetworkHandlerConfigInput } from '@cloudbeaver/core-sdk';
import type { DatabaseConnection } from '../../Administration/ConnectionsResource';
import { DBDriverResource } from '../../DBDriverResource';
import { SSH_TUNNEL_ID } from '../../NetworkHandlerResource';
import { connectionConfigContext } from '../connectionConfigContext';
import { connectionFormConfigureContext } from '../connectionFormConfigureContext';
import { ConnectionFormService } from '../ConnectionFormService';
import { connectionFormStateContext } from '../connectionFormStateContext';
import { connectionConfigContext } from '../Contexts/connectionConfigContext';
import { connectionCredentialsStateContext } from '../Contexts/connectionCredentialsStateContext';
import { connectionFormStateContext } from '../Contexts/connectionFormStateContext';
import type { IConnectionFormFillConfigData, IConnectionFormState, IConnectionFormSubmitData } from '../IConnectionFormProps';
import { SSH } from './SSH';
import { SSHTab } from './SSHTab';
@@ -118,13 +119,16 @@ export class ConnectionSSHTabService extends Bootstrap {
}
for (const handler of config.networkHandlersConfig) {
if (handler.enabled && handler.savePassword && this.isChanged(handler, info)) {
if (!handler.userName?.length) {
validation.error("Field SSH 'User' can't be empty");
}
if (!handler.password?.length) {
validation.error("Field SSH 'Password' can't be empty");
if (handler.enabled && this.isChanged(handler, info)) {
if (handler.savePassword) {
if (!handler.userName?.length) {
validation.error("Field SSH 'User' can't be empty");
}
if (!handler.password?.length) {
validation.error("Field SSH 'Password' can't be empty");
}
}
if (!handler.properties?.host?.length) {
validation.error("Field SSH 'Host' can't be empty");
}
@@ -144,22 +148,31 @@ export class ConnectionSSHTabService extends Bootstrap {
contexts: IExecutionContextProvider<IConnectionFormSubmitData>
) {
const config = contexts.getContext(connectionConfigContext);
const credentialsState = contexts.getContext(connectionCredentialsStateContext);
if (!state.config.networkHandlersConfig || state.config.networkHandlersConfig.length === 0) {
return;
}
const configs: NetworkHandlerConfigInput[] = [];
let sshHandler: NetworkHandlerConfigInput | undefined;
for (const handler of state.config.networkHandlersConfig) {
if (this.isChanged(handler, state.info)) {
configs.push(handler);
configs.push({ ...handler });
}
if (handler.id === SSH_TUNNEL_ID) {
sshHandler = handler;
}
}
if (configs.length > 0) {
config.networkHandlersConfig = configs;
}
if (sshHandler?.enabled && !sshHandler.savePassword && hasMissingCredentials(sshHandler)) {
credentialsState.requireNetworkHandler(SSH_TUNNEL_ID);
}
}
private formState(
@@ -198,3 +211,7 @@ export class ConnectionSSHTabService extends Bootstrap {
return false;
}
}
function hasMissingCredentials(handler: NetworkHandlerConfigInput): boolean {
return !handler.userName;
}
@@ -14,16 +14,16 @@ import { NotificationService } from '@cloudbeaver/core-events';
import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications';
import { GQLErrorCatcher } from '@cloudbeaver/core-sdk';
import type { IConnectionAuthCredentialsConfig } from '../ConnectionCredentials/IConnectionAuthCredentialsConfig';
import { ConnectionInfoResource, ConnectionInitConfig } from '../ConnectionInfoResource';
import { DBDriverResource } from '../DBDriverResource';
import type { IFormInitConfig } from './IFormInitConfig';
@injectable()
export class DBAuthDialogController implements IInitializableController, IDestructibleController {
isAuthenticating = false;
config: IFormInitConfig = {
config: IConnectionAuthCredentialsConfig = {
credentials: {},
networkCredentials: [],
networkHandlersConfig: [],
saveCredentials: false,
};
@@ -45,7 +45,7 @@ export class DBAuthDialogController implements IInitializableController, IDestru
});
}
init(connectionId: string, onClose: () => void) {
init(connectionId: string, onClose: () => void): void {
this.connectionId = connectionId;
this.close = onClose;
this.loadAuthModel();
@@ -56,7 +56,7 @@ export class DBAuthDialogController implements IInitializableController, IDestru
this.isDistructed = true;
}
login = async () => {
login = async (): Promise<void> => {
if (this.isAuthenticating) {
return;
}
@@ -74,7 +74,7 @@ export class DBAuthDialogController implements IInitializableController, IDestru
}
};
showDetails = () => {
showDetails = (): void => {
if (this.error.exception) {
this.commonDialogService.open(ErrorDetailsDialog, this.error.exception);
}
@@ -90,8 +90,8 @@ export class DBAuthDialogController implements IInitializableController, IDestru
config.saveCredentials = this.config.saveCredentials;
}
if (this.config.networkCredentials.length > 0) {
config.networkCredentials = this.config.networkCredentials;
if (this.config.networkHandlersConfig.length > 0) {
config.networkCredentials = this.config.networkHandlersConfig;
}
return config;
@@ -14,26 +14,18 @@ import {
SubmittingForm,
Loader,
useFocus,
BASE_CONTAINERS_STYLES,
Container,
Group,
FieldCheckboxNew,
ObjectPropertyInfoFormNew,
GroupTitle,
ErrorMessage,
TextPlaceholder,
} from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import { CommonDialogWrapper, DialogComponentProps } from '@cloudbeaver/core-dialogs';
import { useTranslate } from '@cloudbeaver/core-localization';
import { composes, useStyles } from '@cloudbeaver/core-theming';
import { SSH_TUNNEL_ID } from '../NetworkHandlerResource';
import { ConnectionCredentialsForm } from '../ConnectionCredentials/ConnectionCredentialsForm';
import { useConnectionInfo } from '../useConnectionInfo';
import { useDBDriver } from '../useDBDriver';
import { DBAuthDialogController } from './DBAuthDialogController';
import { DBAuthDialogFooter } from './DBAuthDialogFooter';
import { SSHAuthForm } from './SSHAuthForm';
const styles = composes(
css`
@@ -53,7 +45,7 @@ const styles = composes(
display: flex;
flex-direction: column;
}
Container {
ConnectionCredentialsForm {
align-content: center;
}
ErrorMessage {
@@ -61,31 +53,33 @@ const styles = composes(
}
`);
interface Payload {
connectionId: string;
networkHandlers: string[];
}
export const DatabaseAuthDialog = observer(function DatabaseAuthDialog({
payload,
options,
rejectDialog,
}: DialogComponentProps<string>) {
const connection = useConnectionInfo(payload);
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
const { driver } = useDBDriver(connection.connectionInfo?.driverId || '');
const controller = useController(DBAuthDialogController, payload, rejectDialog);
}: DialogComponentProps<Payload>) {
const translate = useTranslate();
const connection = useConnectionInfo(payload.connectionId);
const controller = useController(DBAuthDialogController, payload.connectionId, rejectDialog);
const { driver } = useDBDriver(connection.connectionInfo?.driverId || '');
const { credentialsSavingEnabled } = useAdministrationSettings();
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
const sshConfig = connection.connectionInfo?.networkHandlersConfig.find(
handler => handler.id === SSH_TUNNEL_ID
);
let authModelId: string | null = null;
const isAuthNeeded = connection.connectionInfo?.authNeeded;
const isSSHAuthNeeded = sshConfig?.enabled && !sshConfig.savePassword;
if (connection.connectionInfo?.authNeeded) {
authModelId = connection.connectionInfo?.authModel || driver?.defaultAuthModel || null;
}
const title = translate('connections_database_authentication');
return styled(useStyles(styles, BASE_CONTAINERS_STYLES))(
return styled(useStyles(styles))(
<CommonDialogWrapper
title={title}
title={translate('connections_database_authentication')}
subTitle={connection.connectionInfo?.name}
icon={driver?.icon}
footer={(
@@ -108,48 +102,14 @@ export const DatabaseAuthDialog = observer(function DatabaseAuthDialog({
? <Loader />
: (
<SubmittingForm ref={focusedRef} onSubmit={controller.login}>
<Container>
{isAuthNeeded && (
<Group gap small>
{connection.connectionInfo?.authProperties ? (
<>
{isSSHAuthNeeded && sshConfig && <GroupTitle>{title}</GroupTitle>}
<ObjectPropertyInfoFormNew
autofillToken={`section-${connection.connectionInfo?.id || ''} section-auth`}
properties={connection.connectionInfo.authProperties}
state={controller.config.credentials}
disabled={controller.isAuthenticating}
/>
{credentialsSavingEnabled && (
<FieldCheckboxNew
id={connection.connectionInfo?.id || 'DBAuthSaveCredentials'}
name="saveCredentials"
label={translate('connections_connection_edit_save_credentials')}
disabled={controller.isAuthenticating}
state={controller.config}
/>
)}
</>
) : (
<TextPlaceholder>Authentication data is not avaliable</TextPlaceholder>
)}
</Group>
)}
{isSSHAuthNeeded && (
<Group gap small>
{sshConfig ? (
<SSHAuthForm
config={controller.config}
sshHandlerId={sshConfig.id}
allowPasswordSave={credentialsSavingEnabled}
disabled={controller.isAuthenticating}
/>
) : (
<TextPlaceholder>SSH Authentication data is not avaliable</TextPlaceholder>
)}
</Group>
)}
</Container>
<ConnectionCredentialsForm
config={controller.config}
authModelId={authModelId}
networkHandlers={payload.networkHandlers}
formId={payload.connectionId}
allowSaveCredentials={credentialsSavingEnabled}
disabled={controller.isAuthenticating}
/>
</SubmittingForm>
)}
</CommonDialogWrapper>
@@ -16,15 +16,17 @@ export * from './ConnectionForm/DriverProperties/ConnectionDriverPropertiesTabSe
export * from './ConnectionForm/SSH/ConnectionSSHTabService';
export * from './ConnectionForm/OriginInfo/ConnectionOriginInfoTabService';
export * from './ConnectionForm/ConnectionAccess/ConnectionAccessTabService';
export * from './ConnectionForm/connectionConfigContext';
export * from './ConnectionForm/Contexts/connectionConfigContext';
export * from './ConnectionForm/ConnectionFormBaseActions';
export * from './ConnectionForm/connectionFormConfigureContext';
export * from './ConnectionForm/ConnectionForm';
export * from './ConnectionForm/ConnectionFormService';
export * from './ConnectionForm/ConnectionFormState';
export * from './ConnectionForm/connectionFormStateContext';
export * from './ConnectionForm/Contexts/connectionFormStateContext';
export * from './ConnectionForm/IConnectionFormProps';
export * from './ConnectionForm/useConnectionFormState';
export * from './ConnectionCredentials/IConnectionAuthCredentialsConfig';
export * from './ConnectionCredentials/ConnectionCredentialsForm';
export * from './extensions/IConnectionProvider';
export * from './extensions/IConnectionSetter';
@@ -35,8 +37,6 @@ export * from './EConnectionFeature';
export * from './ContainerResource';
export * from './ConnectionsLocaleService';
export * from './DatabaseAuthModelsResource';
export * from './DatabaseAuthDialog/IFormInitConfig';
export * from './DatabaseAuthDialog/SSHAuthForm';
export * from './DBDriverResource';
export * from './NetworkHandlerResource';
export * from './useConnectionInfo';
@@ -22,6 +22,8 @@ export default [
['connections_connection_create_custom', 'Custom'],
['connections_connection_create_search_database', 'Search'],
['connections_connection_edit_save_credentials', 'Save credentials'],
['connections_connection_credentials_provisioning', 'Credentials provisioning'],
['connections_connection_credentials_provisioning_description', 'You need to provide additional credentials to test this connection'],
['connections_connection_edit_authentication', 'Authentication'],
['connections_connection_edit_settings', 'Settings'],
['connections_connection_edit_access', 'Access'],
@@ -55,6 +57,9 @@ export default [
['connections_network_handler_test', 'Test Tunnel'],
['connections_network_handler_test_fail', 'Tunnel test failed'],
['connections_network_handler_test_success', 'Tunnel test success'],
['connections_network_handler_default_title', 'Network handler'],
['connections_network_handler_default_user', 'User'],
['connections_network_handler_default_password', 'Password'],
['connections_network_handler_ssh_tunnel_title', 'SSH Tunnel'],
['connections_network_handler_ssh_tunnel_enable', 'Use SSH Tunnel'],
['connections_network_handler_ssh_tunnel_host', 'Host'],
@@ -19,6 +19,8 @@ export default [
['connections_connection_create_custom', 'Настроить'],
['connections_connection_create_search_database', 'Найти'],
['connections_connection_edit_save_credentials', ' Запомнить данные'],
['connections_connection_credentials_provisioning', 'Предоставление данных доступа'],
['connections_connection_credentials_provisioning_description', 'Чтобы проверить подключение, необходимо предоставить дополнительные данные доступа'],
['connections_connection_edit_authentication', 'Авторизация'],
['connections_connection_edit_settings', 'Настройки'],
['connections_connection_edit_access', 'Доступ'],
@@ -52,6 +54,9 @@ export default [
['connections_network_handler_test', 'Проверить подключение'],
['connections_network_handler_test_fail', 'Не удалось установить соединение'],
['connections_network_handler_test_success', 'Соединение установлено'],
['connections_network_handler_default_title', 'Сетевой обработчик'],
['connections_network_handler_default_user', 'Пользователь'],
['connections_network_handler_default_password', 'Пароль'],
['connections_network_handler_ssh_tunnel_title', 'SSH Тунель'],
['connections_network_handler_ssh_tunnel_enable', 'Использовать SSH Тунель'],
['connections_network_handler_ssh_tunnel_host', 'Хост'],
@@ -13,6 +13,7 @@ export const defaultENLocale = [
['ui_processing_ok', 'Ok'],
['ui_processing_create', 'Create'],
['ui_processing_save', 'Save'],
['ui_apply', 'Apply'],
['ui_error_message', 'Error:'],
['ui_error_close', 'Close'],
['ui_close', 'Close'],
@@ -11,6 +11,7 @@ export const defaultRULocale = [
['ui_processing_ok', 'Принять'],
['ui_processing_create', 'Создать'],
['ui_processing_save', 'Сохранить'],
['ui_apply', 'Применить'],
['ui_error_message', 'Ошибка:'],
['ui_error_close', 'Закрыть'],
['ui_close', 'Закрыть'],
+1 -1
View File
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
type Cookies = Record<string, string>;
type Cookies = Partial<Record<string, string>>;
export function getCookies(): Cookies {
if (!document.cookie) {
@@ -9,7 +9,7 @@
import { observable, makeObservable } from 'mobx';
import { DBDriverResource, Connection, DatabaseAuthModelsResource, ConnectionInfoResource, DBDriver, ConnectionInitConfig, getUniqueConnectionName } from '@cloudbeaver/core-connections';
import type { IFormInitConfig } from '@cloudbeaver/core-connections';
import type { IConnectionAuthCredentialsConfig } from '@cloudbeaver/core-connections';
import { injectable, IInitializableController, IDestructibleController } from '@cloudbeaver/core-di';
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
@@ -25,7 +25,7 @@ export enum ConnectionStep {
export interface IConnectionController {
template: Connection | null;
config: IFormInitConfig;
config: IConnectionAuthCredentialsConfig;
isConnecting: boolean;
onConnect: () => void;
}
@@ -38,9 +38,9 @@ implements IInitializableController, IDestructibleController, IConnectionControl
isConnecting = false;
template: Connection | null = null;
authModel?: DatabaseAuthModel;
config: IFormInitConfig = {
config: IConnectionAuthCredentialsConfig = {
credentials: {},
networkCredentials: [],
networkHandlersConfig: [],
saveCredentials: false,
};
@@ -95,7 +95,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl
this.isDistructed = true;
}
onStep = (step: ConnectionStep) => {
onStep = (step: ConnectionStep): void => {
this.step = step;
this.clearError();
@@ -104,7 +104,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl
}
};
onConnect = async () => {
onConnect = async (): Promise<void> => {
if (!this.template) {
return;
}
@@ -132,14 +132,14 @@ implements IInitializableController, IDestructibleController, IConnectionControl
}
};
onTemplateSelect = async (templateId: string) => {
onTemplateSelect = async (templateId: string): Promise<void> => {
this.template = this.templateConnections.find(template => template.id === templateId)!;
await this.loadAuthModel();
this.clearError();
this.config = {
credentials: {},
networkCredentials: [],
networkHandlersConfig: [],
saveCredentials: false,
};
@@ -149,7 +149,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl
}
};
onShowDetails = () => {
onShowDetails = (): void => {
if (this.exception) {
this.commonDialogService.open(ErrorDetailsDialog, this.exception);
}
@@ -165,8 +165,8 @@ implements IInitializableController, IDestructibleController, IConnectionControl
config.saveCredentials = this.config.saveCredentials;
}
if (this.config.networkCredentials.length > 0) {
config.networkCredentials = this.config.networkCredentials;
if (this.config.networkHandlersConfig.length > 0) {
config.networkCredentials = this.config.networkHandlersConfig;
}
return config;
@@ -15,14 +15,8 @@ import {
SubmittingForm,
Loader,
useFocus,
Container,
Group,
FieldCheckboxNew,
BASE_CONTAINERS_STYLES,
ObjectPropertyInfoFormNew,
GroupTitle
} from '@cloudbeaver/core-blocks';
import { SSH_TUNNEL_ID, SSHAuthForm } from '@cloudbeaver/core-connections';
import { SSH_TUNNEL_ID, ConnectionCredentialsForm } from '@cloudbeaver/core-connections';
import { useController } from '@cloudbeaver/core-di';
import { CommonDialogWrapper, DialogComponentProps } from '@cloudbeaver/core-dialogs';
import { useTranslate } from '@cloudbeaver/core-localization';
@@ -73,10 +67,15 @@ export const ConnectionDialog = observer(function ConnectionDialog({
const sshConfig = controller.template?.networkHandlersConfig.find(
handler => handler.id === SSH_TUNNEL_ID
);
const sshAuthRequired = sshConfig?.enabled && !sshConfig.savePassword;
const isSSHAuthNeeded = sshConfig?.enabled && !sshConfig.savePassword;
const networkHandlers: string[] = [];
return styled(useStyles(styles, BASE_CONTAINERS_STYLES))(
if (sshAuthRequired) {
networkHandlers.push(SSH_TUNNEL_ID);
}
return styled(useStyles(styles))(
<CommonDialogWrapper
title={translate('basicConnection_connectionDialog_newConnection')}
subTitle={subtitle}
@@ -104,34 +103,14 @@ export const ConnectionDialog = observer(function ConnectionDialog({
</center>
) : (
<SubmittingForm ref={focusedRef} onSubmit={controller.onConnect}>
<Container>
<Group gap small>
{isSSHAuthNeeded && <GroupTitle>{translate('connections_database_authentication')}</GroupTitle>}
<ObjectPropertyInfoFormNew
autofillToken={`section-${controller.template?.id || ''} section-auth`}
properties={controller.authModel.properties}
state={controller.config.credentials}
disabled={controller.isConnecting}
/>
{credentialsSavingEnabled && (
<FieldCheckboxNew
id={controller.template?.id || 'DBAuthSaveCredentials'}
name="saveCredentials"
label={translate('connections_connection_edit_save_credentials')}
disabled={controller.isConnecting}
state={controller.config}
/>
)}
</Group>
{isSSHAuthNeeded && sshConfig && (
<SSHAuthForm
sshHandlerId={sshConfig.id}
config={controller.config}
disabled={controller.isConnecting}
allowPasswordSave={credentialsSavingEnabled}
/>
)}
</Container>
<ConnectionCredentialsForm
config={controller.config}
authModelId={controller.authModel.id}
networkHandlers={networkHandlers}
formId={controller.template?.id}
allowSaveCredentials={credentialsSavingEnabled}
disabled={controller.isConnecting}
/>
</SubmittingForm>
))}
{controller.responseMessage && (