You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by di...@apache.org on 2024/02/21 10:13:26 UTC

(superset) 04/10: Validate inputs

This is an automated email from the ASF dual-hosted git repository.

diegopucci pushed a commit to branch diego/ch78628/fix-disabled-ssh-toggle
in repository https://gitbox.apache.org/repos/asf/superset.git

commit a17aac711998b1227d72ba7cda36da1fa2dc4912
Author: geido <di...@gmail.com>
AuthorDate: Mon Feb 19 17:02:50 2024 +0200

    Validate inputs
---
 .../databases/DatabaseModal/SSHTunnelForm.tsx      | 12 ++++-----
 .../src/features/databases/DatabaseModal/index.tsx | 21 +++++++--------
 superset-frontend/src/features/databases/types.ts  | 30 +++++++++++++++++-----
 superset/commands/database/ssh_tunnel/create.py    |  3 +++
 superset/commands/database/ssh_tunnel/update.py    |  9 +++++++
 5 files changed, 49 insertions(+), 26 deletions(-)

diff --git a/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx b/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
index 7823d82faf..e0d1b16ff2 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { EventHandler, ChangeEvent, useState } from 'react';
+import React, { useState } from 'react';
 import { t, styled } from '@superset-ui/core';
 import { AntdForm, Col, Row } from 'src/components';
 import { Form, FormLabel } from 'src/components/Form';
@@ -24,7 +24,7 @@ import { Radio } from 'src/components/Radio';
 import { Input, TextArea } from 'src/components/Input';
 import { Input as AntdInput, Tooltip } from 'antd';
 import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
-import { DatabaseObject } from '../types';
+import { DatabaseObject, FieldPropTypes } from '../types';
 import { AuthType } from '.';
 
 const StyledDiv = styled.div`
@@ -54,9 +54,7 @@ const SSHTunnelForm = ({
   setSSHTunnelLoginMethod,
 }: {
   db: DatabaseObject | null;
-  onSSHTunnelParametersChange: EventHandler<
-    ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
-  >;
+  onSSHTunnelParametersChange: FieldPropTypes['changeMethods']['onSSHTunnelParametersChange'];
   setSSHTunnelLoginMethod: (method: AuthType) => void;
 }) => {
   const [usePassword, setUsePassword] = useState<AuthType>(AuthType.Password);
@@ -86,9 +84,9 @@ const SSHTunnelForm = ({
             </FormLabel>
             <Input
               name="server_port"
-              type="text"
               placeholder={t('22')}
-              value={db?.ssh_tunnel?.server_port || ''}
+              type="number"
+              value={db?.ssh_tunnel?.server_port}
               onChange={onSSHTunnelParametersChange}
               data-test="ssh-tunnel-server_port-input"
             />
diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
index a4a7ddad4a..3607e5e400 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
@@ -662,7 +662,12 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
       masked_encrypted_extra: db?.masked_encrypted_extra || '',
       server_cert: db?.server_cert || undefined,
       ssh_tunnel:
-        !isEmpty(db?.ssh_tunnel) && useSSHTunneling ? db.ssh_tunnel : undefined,
+        !isEmpty(db?.ssh_tunnel) && useSSHTunneling
+          ? {
+              ...db.ssh_tunnel,
+              server_port: Number(db.ssh_tunnel!.server_port),
+            }
+          : undefined,
     };
     setTestInProgress(true);
     testDatabaseConnection(
@@ -694,10 +699,6 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
     setValidationErrors(null);
   };
 
-  const handleClearSSHTunnelConfig = () => {
-    setDB({ type: ActionType.RemoveSSHTunnelConfig });
-  };
-
   const handleParametersChange = ({ target }: { target: HTMLInputElement }) => {
     onChange(ActionType.ParametersChange, {
       type: target.type,
@@ -1541,8 +1542,8 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
         typeof dbErrors === 'object'
           ? Object.values(dbErrors)
           : typeof dbErrors === 'string'
-            ? [dbErrors]
-            : [];
+          ? [dbErrors]
+          : [];
     } else if (
       !isEmpty(validationErrors) &&
       validationErrors?.error_type === 'GENERIC_DB_ENGINE_ERROR'
@@ -1578,11 +1579,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
   const renderSSHTunnelForm = () => (
     <SSHTunnelForm
       db={db as DatabaseObject}
-      onSSHTunnelParametersChange={({
-        target,
-      }: {
-        target: HTMLInputElement | HTMLTextAreaElement;
-      }) =>
+      onSSHTunnelParametersChange={({ target }) =>
         onChange(ActionType.ParametersSSHTunnelChange, {
           type: target.type,
           name: target.name,
diff --git a/superset-frontend/src/features/databases/types.ts b/superset-frontend/src/features/databases/types.ts
index 2f481d8315..1ae4a7f715 100644
--- a/superset-frontend/src/features/databases/types.ts
+++ b/superset-frontend/src/features/databases/types.ts
@@ -1,6 +1,6 @@
 import { JsonObject } from '@superset-ui/core';
 import { InputProps } from 'antd/lib/input';
-import { FormEvent } from 'react';
+import { ChangeEvent, EventHandler, FormEvent } from 'react';
 
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -236,23 +236,39 @@ export interface ExtraJson {
   version?: string;
 }
 
-type ParametersChangeValueType = Partial<Omit<HTMLInputElement, 'value'>> & {
-  value?: string | boolean;
+type CustomTextType = {
+  value?: string | boolean | number;
+  type?: string | null;
+  name?: string;
+  checked?: boolean;
 };
 
-type ParametersChangeType<T = ParametersChangeValueType> =
+type CustomHTMLInputElement = Omit<Partial<CustomTextType>, 'value' | 'type'> &
+  CustomTextType;
+
+type CustomHTMLTextAreaElement = Omit<
+  Partial<CustomTextType>,
+  'value' | 'type'
+> &
+  CustomTextType;
+
+export type CustomParametersChangeType<T = CustomTextType> =
   | FormEvent<InputProps>
   | { target: T };
 
+export type CustomEventHandlerType = EventHandler<
+  ChangeEvent<CustomHTMLInputElement | CustomHTMLTextAreaElement>
+>;
+
 export interface FieldPropTypes {
   required: boolean;
   hasTooltip?: boolean;
   tooltipText?: (value: any) => string;
   placeholder?: string;
-  onParametersChange: (event: ParametersChangeType) => void;
+  onParametersChange: (event: CustomParametersChangeType) => void;
   onParametersUploadFileChange: (value: any) => string;
   changeMethods: {
-    onParametersChange: (event: ParametersChangeType) => void;
+    onParametersChange: (event: CustomParametersChangeType) => void;
   } & {
     onChange: (value: any) => string;
   } & {
@@ -262,7 +278,7 @@ export interface FieldPropTypes {
     onRemoveTableCatalog: (idx: number) => void;
   } & {
     onExtraInputChange: (value: any) => void;
-    onSSHTunnelParametersChange: (value: any) => string;
+    onSSHTunnelParametersChange: CustomEventHandlerType;
   };
   validationErrors: JsonObject | null;
   getValidation: () => void;
diff --git a/superset/commands/database/ssh_tunnel/create.py b/superset/commands/database/ssh_tunnel/create.py
index cbfee3ce2a..59e083d4d8 100644
--- a/superset/commands/database/ssh_tunnel/create.py
+++ b/superset/commands/database/ssh_tunnel/create.py
@@ -57,6 +57,7 @@ class CreateSSHTunnelCommand(BaseCommand):
         server_address: Optional[str] = self._properties.get("server_address")
         server_port: Optional[int] = self._properties.get("server_port")
         username: Optional[str] = self._properties.get("username")
+        password: Optional[str] = self._properties.get("password")
         private_key: Optional[str] = self._properties.get("private_key")
         private_key_password: Optional[str] = self._properties.get(
             "private_key_password"
@@ -67,6 +68,8 @@ class CreateSSHTunnelCommand(BaseCommand):
             exceptions.append(SSHTunnelRequiredFieldValidationError("server_port"))
         if not username:
             exceptions.append(SSHTunnelRequiredFieldValidationError("username"))
+        if not private_key and not password:
+            exceptions.append(SSHTunnelRequiredFieldValidationError("password"))
         if private_key_password and private_key is None:
             exceptions.append(SSHTunnelRequiredFieldValidationError("private_key"))
         if exceptions:
diff --git a/superset/commands/database/ssh_tunnel/update.py b/superset/commands/database/ssh_tunnel/update.py
index ae7ee78afe..47f7d4947a 100644
--- a/superset/commands/database/ssh_tunnel/update.py
+++ b/superset/commands/database/ssh_tunnel/update.py
@@ -43,6 +43,15 @@ class UpdateSSHTunnelCommand(BaseCommand):
         self.validate()
         try:
             if self._model is not None:  # So we dont get incompatible types error
+                # unset password if private key is provided
+                if self._properties.get("private_key"):
+                    self._properties["password"] = None
+
+                # unset private key and password if password is provided
+                if self._properties.get("password"):
+                    self._properties["private_key"] = None
+                    self._properties["private_key_password"] = None
+
                 tunnel = SSHTunnelDAO.update(self._model, self._properties)
         except DAOUpdateFailedError as ex:
             raise SSHTunnelUpdateFailedError() from ex