dosubot[bot] commented on issue #29336:
URL: https://github.com/apache/superset/issues/29336#issuecomment-2184918898

   <!-- Greeting -->
   Hello @andy1xx8! I'm here to assist you with any bugs, questions, or 
contributions. Let's work together to resolve the issue you've encountered.
   
   <!-- Answer -->
   To address the security issue where sensitive details like IP, port, table, 
and database names are exposed in the Superset UI, you can follow these steps:
   
   1. **Define User-Friendly Error Messages**: Use the `SupersetError` class to 
create user-friendly error messages that map to backend errors. This class 
includes a mechanism to map error types to issue codes and user-facing messages.
   
   2. **Log Detailed Exceptions on the Backend**: Ensure that detailed 
exceptions, including sensitive information, are logged on the backend but not 
exposed to the user.
   
   3. **Modify the Error Handling Logic**: Update the error handling logic to 
catch exceptions, log the detailed error, and return a `SupersetError` instance 
with a user-friendly message.
   
   Here is an example implementation:
   
   ### Backend (Python)
   ```python
   import logging
   from flask import jsonify
   from superset.exceptions import SupersetError, SupersetErrorType, 
SupersetErrorException, SupersetErrorsException
   from superset.utils import error_msg_from_exception, json_error_response, 
json_errors_response
   
   # Configure logging
   logger = logging.getLogger(__name__)
   
   def handle_exception(e):
       # Log the detailed exception on the backend
       logger.error("An error occurred: %s", str(e), exc_info=True)
   
       # Create a user-friendly error message
       user_friendly_error = SupersetError(
           message="An unexpected error occurred. Please try again later.",
           error_type=SupersetErrorType.GENERIC_BACKEND_ERROR,
           level=ErrorLevel.ERROR
       )
   
       # Return the user-friendly error message to the client
       return jsonify(user_friendly_error.to_dict()), 500
   
   # Example usage in a Flask route
   @app.route('/some_endpoint')
   def some_endpoint():
       try:
           # Your logic here
           pass
       except Exception as e:
           return handle_exception(e)
   ```
   
   ### Frontend (JavaScript/TypeScript)
   Use the `getClientErrorObject` function to handle API request errors and 
convert them to consistent Superset API errors:
   
   ```ts
   import {
     COMMON_ERR_MESSAGES,
     JsonObject,
     SupersetClientResponse,
     t,
     SupersetError,
     ErrorTypeEnum,
   } from '@superset-ui/core';
   
   export type ClientErrorObject = {
     error: string;
     errors?: SupersetError[];
     link?: string;
     message?: string;
     severity?: string;
     stacktrace?: string;
     statusText?: string;
   } & Partial<SupersetClientResponse>;
   
   export function parseErrorJson(responseObject: JsonObject): 
ClientErrorObject {
     let error = { ...responseObject };
     if (error.errors && error.errors.length > 0) {
       error.error = error.description = error.errors[0].message;
       error.link = error.errors[0]?.extra?.link;
     }
     if (!error.error && error.message) {
       if (typeof error.message === 'object') {
         error.error =
           Object.values(error.message as Record<string, string[]>)[0]?.[0] ||
           t('Invalid input');
       }
       if (typeof error.message === 'string') {
         error.error = error.message;
       }
     }
     if (error.stack) {
       error = {
         ...error,
         error:
           t('Unexpected error: ') +
           (error.description || t('(no description, click to see stack 
trace)')),
         stacktrace: error.stack,
       };
     } else if (error.responseText && error.responseText.indexOf('CSRF') >= 0) {
       error = {
         ...error,
         error: t(COMMON_ERR_MESSAGES.SESSION_TIMED_OUT),
       };
     }
   
     return { ...error, error: error.error };
   }
   
   export function getClientErrorObject(
     response:
       | SupersetClientResponse
       | TimeoutError
       | { response: Response }
       | string,
   ): Promise<ClientErrorObject> {
     return new Promise(resolve => {
       if (typeof response === 'string') {
         resolve({ error: response });
         return;
       }
   
       if (
         response instanceof TypeError &&
         response.message === 'Failed to fetch'
       ) {
         resolve({
           error: t('Network error'),
         });
         return;
       }
       if (
         'timeout' in response &&
         'statusText' in response &&
         response.statusText === 'timeout'
       ) {
         resolve({
           ...response,
           error: t('Request timed out'),
           errors: [
             {
               error_type: ErrorTypeEnum.FRONTEND_TIMEOUT_ERROR,
               extra: {
                 timeout: response.timeout / 1000,
                 issue_codes: [
                   {
                     code: 1000,
                     message: t('Issue 1000 - The dataset is too large to 
query.'),
                   },
                   {
                     code: 1001,
                     message: t(
                       'Issue 1001 - The database is under an unusual load.',
                     ),
                   },
                 ],
               },
               level: 'error',
               message: 'Request timed out',
             },
           ],
         });
         return;
       }
   
       const responseObject =
         response instanceof Response ? response : response.response;
       if (responseObject && !responseObject.bodyUsed) {
         responseObject
           .clone()
           .json()
           .then(errorJson => {
             const error = { ...responseObject, ...errorJson };
             resolve(parseErrorJson(error));
           })
           .catch(() => {
             responseObject.text().then((errorText: any) => {
               resolve({ ...responseObject, error: errorText });
             });
           });
         return;
       }
   
       let error = (response as any).statusText || (response as any).message;
       if (!error) {
         console.error('non-standard error:', response);
         error = t('An error occurred');
       }
       resolve({
         ...responseObject,
         error,
       });
     });
   }
   ```
   
   ### Register Error Message Components
   Register specific error message components for different error types in the 
frontend:
   
   ```ts
   import { ErrorTypeEnum } from '@superset-ui/core';
   import getErrorMessageComponentRegistry from 
'src/components/ErrorMessage/getErrorMessageComponentRegistry';
   import TimeoutErrorMessage from 
'src/components/ErrorMessage/TimeoutErrorMessage';
   import DatabaseErrorMessage from 
'src/components/ErrorMessage/DatabaseErrorMessage';
   import MarshmallowErrorMessage from 
'src/components/ErrorMessage/MarshmallowErrorMessage';
   import ParameterErrorMessage from 
'src/components/ErrorMessage/ParameterErrorMessage';
   import DatasetNotFoundErrorMessage from 
'src/components/ErrorMessage/DatasetNotFoundErrorMessage';
   import OAuth2RedirectMessage from 
'src/components/ErrorMessage/OAuth2RedirectMessage';
   
   import setupErrorMessagesExtra from './setupErrorMessagesExtra';
   
   export default function setupErrorMessages() {
     const errorMessageComponentRegistry = getErrorMessageComponentRegistry();
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.FRONTEND_TIMEOUT_ERROR,
       TimeoutErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.BACKEND_TIMEOUT_ERROR,
       TimeoutErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.DATABASE_NOT_FOUND_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.GENERIC_DB_ENGINE_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.GENERIC_BACKEND_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.COLUMN_DOES_NOT_EXIST_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.TABLE_DOES_NOT_EXIST_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.MISSING_TEMPLATE_PARAMS_ERROR,
       ParameterErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.INVALID_TEMPLATE_PARAMS_ERROR,
       ParameterErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.RESULTS_BACKEND_NOT_CONFIGURED_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.DML_NOT_ALLOWED_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.INVALID_CTAS_QUERY_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.INVALID_CVAS_QUERY_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.QUERY_SECURITY_ACCESS_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.CONNECTION_INVALID_HOSTNAME_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.RESULTS_BACKEND_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.ASYNC_WORKERS_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.SQLLAB_TIMEOUT_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.CONNECTION_PORT_CLOSED_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.CONNECTION_HOST_DOWN_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.CONNECTION_INVALID_USERNAME_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.CONNECTION_INVALID_PASSWORD_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.CONNECTION_ACCESS_DENIED_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.CONNECTION_UNKNOWN_DATABASE_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.SCHEMA_DOES_NOT_EXIST_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.OBJECT_DOES_NOT_EXIST_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.SYNTAX_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.CONNECTION_DATABASE_PERMISSIONS_ERROR,
       DatabaseErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.FAILED_FETCHING_DATASOURCE_INFO_ERROR,
       DatasetNotFoundErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.MARSHMALLOW_ERROR,
       MarshmallowErrorMessage,
     );
     errorMessageComponentRegistry.registerValue(
       ErrorTypeEnum.OAUTH2_REDIRECT,
       OAuth2RedirectMessage,
     );
     setupErrorMessagesExtra();
   }
   ```
   
   This approach ensures that sensitive information is logged on the backend 
while providing a user-friendly error message to the client. The 
`getClientErrorObject` function in the frontend ensures that API request errors 
are consistently converted to `ClientErrorObject` instances, which can then be 
displayed to the user in a user-friendly manner. The error message component 
registry allows for specific handling and display of different error types, 
enhancing the overall user experience 
<sup>[[1]](https://github.com/apache/superset/blob/master/superset/errors.py)</sup><sup>[[2]](https://github.com/apache/superset/blob/master/superset/views/base.py)</sup><sup>[[3]](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-ui-core/src/query/api/v1/handleError.ts)</sup><sup>[[4]](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts)</sup><sup>[[5]](https://github.com/apache/superset/blob/mas
 ter/superset-frontend/src/setup/setupErrorMessages.ts)</sup>.
   
   
   <!-- Dosu Comment Footer -->
   *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to