Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wren-ui): Added FE Support for MySQL SSL Connection #1072

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
25 changes: 19 additions & 6 deletions wren-ui/src/apollo/server/adaptors/tests/ibisAdaptor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
IbisQueryResponse,
ValidationRules,
} from '../ibisAdaptor';
import { DataSourceName } from '../../types';
import { DataSourceName, SSLMode } from '../../types';
import { Manifest } from '../../mdl/type';
import {
BIG_QUERY_CONNECTION_INFO,
Expand Down Expand Up @@ -45,6 +45,8 @@ describe('IbisAdaptor', () => {
database: 'my-database',
user: 'my-user',
password: 'my-password',
sslMode: SSLMode.VERIFY_CA,
sslCA: 'encrypted-certificate-string',
};

const mockPostgresConnectionInfo: POSTGRES_CONNECTION_INFO = {
Expand Down Expand Up @@ -176,17 +178,28 @@ describe('IbisAdaptor', () => {
mockedAxios.post.mockResolvedValue(mockResponse);
// mock decrypt method in Encryptor to return the same password
mockedEncryptor.prototype.decrypt.mockReturnValue(
JSON.stringify({ password: mockMySQLConnectionInfo.password }),
JSON.stringify({
password: mockMySQLConnectionInfo.password,
...(mockMySQLConnectionInfo.sslCA && { sslCA: mockMySQLConnectionInfo.sslCA })
}),
);

const result = await ibisAdaptor.getConstraints(
DataSourceName.MYSQL,
mockMySQLConnectionInfo,
);
const expectConnectionInfo = Object.entries(mockMySQLConnectionInfo).reduce(
(acc, [key, value]) => ((acc[snakeCase(key)] = value), acc),
{},
);
const expectConnectionInfo = Object.entries(
mockMySQLConnectionInfo,
).reduce((acc, [key, value]) => {
if (key === 'sslCA') {
acc['sslCA'] = Buffer
.from(mockMySQLConnectionInfo.sslCA)
.toString('base64');
} else {
acc[key] = value;
}
return acc;
}, {});

expect(result).toEqual([]);
expect(mockedAxios.post).toHaveBeenCalledWith(
Expand Down
18 changes: 15 additions & 3 deletions wren-ui/src/apollo/server/dataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,27 @@ const dataSource = {

// mysql
[DataSourceName.MYSQL]: {
sensitiveProps: ['password'],
sensitiveProps: ['password', 'sslCA'],
toIbisConnectionInfo(connectionInfo) {
const decryptedConnectionInfo = decryptConnectionInfo(
DataSourceName.MYSQL,
connectionInfo,
);
const { host, port, database, user, password } =
const { host, port, database, user, password, ...ssl } =
decryptedConnectionInfo as MYSQL_CONNECTION_INFO;
return { host, port, database, user, password };
return {
host,
port,
database,
user,
password,
sslMode: ssl.sslMode,
...(ssl.sslCA && {
sslCA: Buffer.from(
ssl.sslCA,
).toString('base64')
}),
};
},
} as IDataSourceConnectionInfo<
MYSQL_CONNECTION_INFO,
Expand Down
2 changes: 2 additions & 0 deletions wren-ui/src/apollo/server/repositories/projectRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export interface MYSQL_CONNECTION_INFO {
user: string;
password: string;
database: string;
sslMode: string;
sslCA?: string;
ongdisheng marked this conversation as resolved.
Show resolved Hide resolved
}

export interface MS_SQL_CONNECTION_INFO {
Expand Down
1 change: 1 addition & 0 deletions wren-ui/src/apollo/server/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './manifest';
export * from './diagram';
export * from './metric';
export * from './context';
export * from './sslMode';
5 changes: 5 additions & 0 deletions wren-ui/src/apollo/server/types/sslMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum SSLMode {
DISABLE = 'Disable',
REQUIRE = 'Require',
VERIFY_CA = 'Verify CA',
}
89 changes: 87 additions & 2 deletions wren-ui/src/components/pages/setup/dataSources/MySQLProperties.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,72 @@
import { Form, Input } from 'antd';
import { useEffect, useState } from 'react';
import { Form, Input, Select, Button, Upload } from 'antd';
import UploadOutlined from '@ant-design/icons/UploadOutlined';
import { UploadFile } from 'antd/lib/upload/interface';
import { ERROR_TEXTS } from '@/utils/error';
import { FORM_MODE } from '@/utils/enum';
import { FORM_MODE, SSL_MODE } from '@/utils/enum';
import { hostValidator } from '@/utils/validator';

interface Props {
mode?: FORM_MODE;
}

const UploadSSL = (props) => {
const { onChange, value } = props;

const [fileList, setFileList] = useState<UploadFile[]>([]);

useEffect(() => {
if (!value) setFileList([]);
}, [value]);

const readFileContent = (file: any, callback: (value: string) => void) => {
const reader = new FileReader();
reader.onloadend = (_e) => {
const result = reader.result;

if (result) {
const fileContent = String(result);
callback(fileContent);
}
};

reader.readAsText(file);
};

const onUploadChange = (info) => {
const { file, fileList } = info;
if (fileList.length) {
const uploadFile = fileList[0];
readFileContent(file.originFileObj, (fileContent: string) => {
onChange && onChange(fileContent);
});
setFileList([uploadFile]);
}
};

const onRemove = () => {
setFileList([]);
onChange && onChange(undefined);
};

return (
<Upload
accept=".pem,.crt,.key"
fileList={fileList}
onChange={onUploadChange}
onRemove={onRemove}
maxCount={1}
>
<Button icon={<UploadOutlined />}>Click to upload SSL cert file</Button>
</Upload>
);
};

export default function MySQLProperties(props: Props) {
const { mode } = props;
const isEditMode = mode === FORM_MODE.EDIT;
const [sslMode, setSSLMode] = useState<string>(SSL_MODE.DISABLE);
const onSSLModeChange = (value: string) => setSSLMode(value)
return (
<>
<Form.Item
Expand Down Expand Up @@ -89,6 +146,34 @@ export default function MySQLProperties(props: Props) {
>
<Input placeholder="MySQL database name" disabled={isEditMode} />
</Form.Item>
<Form.Item label="SSL mode" name="sslMode" initialValue={SSL_MODE.DISABLE}>
<Select
style={{ width: 120 }}
onChange={onSSLModeChange}
disabled={isEditMode}
options={[
{ value: SSL_MODE.DISABLE },
{ value: SSL_MODE.REQUIRE },
{ value: SSL_MODE.VERIFY_CA },
]}
/>
</Form.Item>
{
sslMode === SSL_MODE.VERIFY_CA &&
<Form.Item
label="SSL CA file"
name="sslCA"
required
rules={[
{
required: true,
message: ERROR_TEXTS.CONNECTION.SSL_CERT.REQUIRED,
},
]}
>
<UploadSSL />
</Form.Item>
}
</>
);
}
1 change: 1 addition & 0 deletions wren-ui/src/utils/enum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './path';
export * from './diagram';
export * from './home';
export * from './settings';
export * from './sslMode';
5 changes: 5 additions & 0 deletions wren-ui/src/utils/enum/sslMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum SSL_MODE {
DISABLE = 'Disable',
REQUIRE = 'Require',
VERIFY_CA = 'Verify CA',
}
3 changes: 3 additions & 0 deletions wren-ui/src/utils/error/dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export const ERROR_TEXTS = {
ACCOUNT: {
REQUIRED: 'Please input account.',
},
SSL_CERT: {
REQUIRED: 'Please upload SSL cert file.',
},
},
ADD_RELATION: {
FROM_FIELD: {
Expand Down