You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by hu...@apache.org on 2023/03/15 01:21:18 UTC

[superset] branch master updated: chore: Migrate /superset/schemas_access_for_file_upload to v1 (#23227)

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

hugh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 9920ab3fd9 chore: Migrate /superset/schemas_access_for_file_upload to v1 (#23227)
9920ab3fd9 is described below

commit 9920ab3fd9230674e159b2e649565d4a6dc1db88
Author: Hugh A. Miles II <hu...@gmail.com>
AuthorDate: Tue Mar 14 19:21:10 2023 -0600

    chore: Migrate /superset/schemas_access_for_file_upload to v1 (#23227)
    
    Co-authored-by: Diego Medina <di...@gmail.com>
---
 docs/static/resources/openapi.json                 | 158 ++++++++++++++-------
 superset/constants.py                              |   1 +
 superset/databases/api.py                          |  54 ++++++-
 superset/databases/schemas.py                      |   7 +
 .../form_view/database_schemas_selector.html       |   5 +-
 superset/views/core.py                             |   1 +
 tests/integration_tests/databases/api_tests.py     |  41 ++++++
 7 files changed, 212 insertions(+), 55 deletions(-)

diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json
index 7729d413da..ad299216fc 100644
--- a/docs/static/resources/openapi.json
+++ b/docs/static/resources/openapi.json
@@ -1834,10 +1834,10 @@
             "type": "string"
           },
           "last_saved_by": {
-            "$ref": "#/components/schemas/ChartDataRestApi.get_list.User3"
+            "$ref": "#/components/schemas/ChartDataRestApi.get_list.User1"
           },
           "owners": {
-            "$ref": "#/components/schemas/ChartDataRestApi.get_list.User1"
+            "$ref": "#/components/schemas/ChartDataRestApi.get_list.User3"
           },
           "params": {
             "nullable": true,
@@ -1925,16 +1925,11 @@
           "last_name": {
             "maxLength": 64,
             "type": "string"
-          },
-          "username": {
-            "maxLength": 64,
-            "type": "string"
           }
         },
         "required": [
           "first_name",
-          "last_name",
-          "username"
+          "last_name"
         ],
         "type": "object"
       },
@@ -1972,11 +1967,16 @@
           "last_name": {
             "maxLength": 64,
             "type": "string"
+          },
+          "username": {
+            "maxLength": 64,
+            "type": "string"
           }
         },
         "required": [
           "first_name",
-          "last_name"
+          "last_name",
+          "username"
         ],
         "type": "object"
       },
@@ -2627,10 +2627,10 @@
             "type": "string"
           },
           "last_saved_by": {
-            "$ref": "#/components/schemas/ChartRestApi.get_list.User3"
+            "$ref": "#/components/schemas/ChartRestApi.get_list.User1"
           },
           "owners": {
-            "$ref": "#/components/schemas/ChartRestApi.get_list.User1"
+            "$ref": "#/components/schemas/ChartRestApi.get_list.User3"
           },
           "params": {
             "nullable": true,
@@ -2718,16 +2718,11 @@
           "last_name": {
             "maxLength": 64,
             "type": "string"
-          },
-          "username": {
-            "maxLength": 64,
-            "type": "string"
           }
         },
         "required": [
           "first_name",
-          "last_name",
-          "username"
+          "last_name"
         ],
         "type": "object"
       },
@@ -2765,11 +2760,16 @@
           "last_name": {
             "maxLength": 64,
             "type": "string"
+          },
+          "username": {
+            "maxLength": 64,
+            "type": "string"
           }
         },
         "required": [
           "first_name",
-          "last_name"
+          "last_name",
+          "username"
         ],
         "type": "object"
       },
@@ -3420,7 +3420,7 @@
             "readOnly": true
           },
           "created_by": {
-            "$ref": "#/components/schemas/DashboardRestApi.get_list.User2"
+            "$ref": "#/components/schemas/DashboardRestApi.get_list.User1"
           },
           "created_on_delta_humanized": {
             "readOnly": true
@@ -3446,7 +3446,7 @@
             "type": "string"
           },
           "owners": {
-            "$ref": "#/components/schemas/DashboardRestApi.get_list.User1"
+            "$ref": "#/components/schemas/DashboardRestApi.get_list.User2"
           },
           "position_json": {
             "nullable": true,
@@ -3520,10 +3520,6 @@
       },
       "DashboardRestApi.get_list.User1": {
         "properties": {
-          "email": {
-            "maxLength": 64,
-            "type": "string"
-          },
           "first_name": {
             "maxLength": 64,
             "type": "string"
@@ -3535,22 +3531,20 @@
           "last_name": {
             "maxLength": 64,
             "type": "string"
-          },
-          "username": {
-            "maxLength": 64,
-            "type": "string"
           }
         },
         "required": [
-          "email",
           "first_name",
-          "last_name",
-          "username"
+          "last_name"
         ],
         "type": "object"
       },
       "DashboardRestApi.get_list.User2": {
         "properties": {
+          "email": {
+            "maxLength": 64,
+            "type": "string"
+          },
           "first_name": {
             "maxLength": 64,
             "type": "string"
@@ -3562,11 +3556,17 @@
           "last_name": {
             "maxLength": 64,
             "type": "string"
+          },
+          "username": {
+            "maxLength": 64,
+            "type": "string"
           }
         },
         "required": [
+          "email",
           "first_name",
-          "last_name"
+          "last_name",
+          "username"
         ],
         "type": "object"
       },
@@ -4293,6 +4293,18 @@
         },
         "type": "object"
       },
+      "DatabaseSchemaAccessForFileUploadResponse": {
+        "properties": {
+          "schemas": {
+            "description": "The list of schemas allowed for the database to upload information",
+            "items": {
+              "type": "string"
+            },
+            "type": "array"
+          }
+        },
+        "type": "object"
+      },
       "DatabaseTablesResponse": {
         "properties": {
           "extra": {
@@ -4917,7 +4929,7 @@
             "$ref": "#/components/schemas/DatasetRestApi.get.TableColumn"
           },
           "created_by": {
-            "$ref": "#/components/schemas/DatasetRestApi.get.User2"
+            "$ref": "#/components/schemas/DatasetRestApi.get.User1"
           },
           "created_on": {
             "format": "date-time",
@@ -4981,7 +4993,7 @@
             "type": "integer"
           },
           "owners": {
-            "$ref": "#/components/schemas/DatasetRestApi.get.User1"
+            "$ref": "#/components/schemas/DatasetRestApi.get.User2"
           },
           "schema": {
             "maxLength": 255,
@@ -5195,23 +5207,14 @@
             "maxLength": 64,
             "type": "string"
           },
-          "id": {
-            "format": "int32",
-            "type": "integer"
-          },
           "last_name": {
             "maxLength": 64,
             "type": "string"
-          },
-          "username": {
-            "maxLength": 64,
-            "type": "string"
           }
         },
         "required": [
           "first_name",
-          "last_name",
-          "username"
+          "last_name"
         ],
         "type": "object"
       },
@@ -5221,14 +5224,23 @@
             "maxLength": 64,
             "type": "string"
           },
+          "id": {
+            "format": "int32",
+            "type": "integer"
+          },
           "last_name": {
             "maxLength": 64,
             "type": "string"
+          },
+          "username": {
+            "maxLength": 64,
+            "type": "string"
           }
         },
         "required": [
           "first_name",
-          "last_name"
+          "last_name",
+          "username"
         ],
         "type": "object"
       },
@@ -6971,7 +6983,7 @@
             "type": "integer"
           },
           "created_by": {
-            "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User2"
+            "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User1"
           },
           "created_on": {
             "format": "date-time",
@@ -7021,7 +7033,7 @@
             "type": "string"
           },
           "owners": {
-            "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User1"
+            "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User2"
           },
           "recipients": {
             "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.ReportRecipients"
@@ -7082,10 +7094,6 @@
             "maxLength": 64,
             "type": "string"
           },
-          "id": {
-            "format": "int32",
-            "type": "integer"
-          },
           "last_name": {
             "maxLength": 64,
             "type": "string"
@@ -7103,6 +7111,10 @@
             "maxLength": 64,
             "type": "string"
           },
+          "id": {
+            "format": "int32",
+            "type": "integer"
+          },
           "last_name": {
             "maxLength": 64,
             "type": "string"
@@ -15301,6 +15313,50 @@
         ]
       }
     },
+    "/api/v1/database/{pk}/schemas_access_for_file_upload/": {
+      "get": {
+        "parameters": [
+          {
+            "in": "path",
+            "name": "pk",
+            "required": true,
+            "schema": {
+              "type": "integer"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/DatabaseSchemaAccessForFileUploadResponse"
+                }
+              }
+            },
+            "description": "The list of the database schemas where to upload information"
+          },
+          "401": {
+            "$ref": "#/components/responses/401"
+          },
+          "404": {
+            "$ref": "#/components/responses/404"
+          },
+          "500": {
+            "$ref": "#/components/responses/500"
+          }
+        },
+        "security": [
+          {
+            "jwt": []
+          }
+        ],
+        "summary": "The list of the database schemas where to upload information",
+        "tags": [
+          "Database"
+        ]
+      }
+    },
     "/api/v1/database/{pk}/select_star/{table_name}/": {
       "get": {
         "description": "Get database select star for table",
diff --git a/superset/constants.py b/superset/constants.py
index 69a7bc7a43..7007e77e3f 100644
--- a/superset/constants.py
+++ b/superset/constants.py
@@ -145,6 +145,7 @@ MODEL_API_RW_METHOD_PERMISSION_MAP = {
     "delete_ssh_tunnel": "write",
     "get_updated_since": "read",
     "stop_query": "read",
+    "schemas_access_for_file_upload": "read",
     "get_objects": "read",
     "get_all_objects": "read",
     "add_objects": "write",
diff --git a/superset/databases/api.py b/superset/databases/api.py
index f9be5e7e97..233990ccd5 100644
--- a/superset/databases/api.py
+++ b/superset/databases/api.py
@@ -65,6 +65,7 @@ from superset.databases.schemas import (
     DatabasePostSchema,
     DatabasePutSchema,
     DatabaseRelatedObjectsResponse,
+    DatabaseSchemaAccessForFileUploadResponse,
     DatabaseTablesResponse,
     DatabaseTestConnectionSchema,
     DatabaseValidateParametersSchema,
@@ -120,6 +121,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
         "validate_parameters",
         "validate_sql",
         "delete_ssh_tunnel",
+        "schemas_access_for_file_upload",
     }
     resource_name = "database"
     class_permission_name = "Database"
@@ -222,6 +224,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
     openapi_spec_tag = "Database"
     openapi_spec_component_schemas = (
         DatabaseFunctionNamesResponse,
+        DatabaseSchemaAccessForFileUploadResponse,
         DatabaseRelatedObjectsResponse,
         DatabaseTablesResponse,
         DatabaseTestConnectionSchema,
@@ -814,7 +817,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
             )
         except NoSuchTableError:
             self.incr_stats("error", self.select_star.__name__)
-            return self.response(404, message="Table not found in the database")
+            return self.response(404, message="Table not found on the database")
         self.incr_stats("success", self.select_star.__name__)
         return self.response(200, result=result)
 
@@ -1453,3 +1456,52 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
                 exc_info=True,
             )
             return self.response_400(message=str(ex))
+
+    @expose("/<int:pk>/schemas_access_for_file_upload/")
+    @protect()
+    @safe
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}"
+        f".schemas_access_for_file_upload",
+        log_to_statsd=False,
+    )
+    def schemas_access_for_file_upload(self, pk: int) -> Response:
+        """The list of the database schemas where to upload information
+        ---
+        get:
+          summary:
+            The list of the database schemas where to upload information
+          parameters:
+          - in: path
+            name: pk
+            schema:
+              type: integer
+          responses:
+            200:
+              description: The list of the database schemas where to upload information
+              content:
+                application/json:
+                  schema:
+                    $ref: "#/components/schemas/DatabaseSchemaAccessForFileUploadResponse"
+            401:
+              $ref: '#/components/responses/401'
+            404:
+              $ref: '#/components/responses/404'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        database = DatabaseDAO.find_by_id(pk)
+        if not database:
+            return self.response_404()
+
+        schemas_allowed = database.get_schema_access_for_file_upload()
+        # the list schemas_allowed should not be empty here
+        # and the list schemas_allowed_processed returned from security_manager
+        # should not be empty either,
+        # otherwise the database should have been filtered out
+        # in CsvToDatabaseForm
+        schemas_allowed_processed = security_manager.get_schemas_accessible_by_user(
+            database, schemas_allowed, True
+        )
+        return self.response(200, schemas=schemas_allowed_processed)
diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py
index 288408969d..7a1e99404f 100644
--- a/superset/databases/schemas.py
+++ b/superset/databases/schemas.py
@@ -808,3 +808,10 @@ def encrypted_field_properties(self, field: Any, **_) -> Dict[str, Any]:  # type
         if self.openapi_version.major > 2:
             ret["x-encrypted-extra"] = True
     return ret
+
+
+class DatabaseSchemaAccessForFileUploadResponse(Schema):
+    schemas = fields.List(
+        fields.String(),
+        description="The list of schemas allowed for the database to upload information",
+    )
diff --git a/superset/templates/superset/form_view/database_schemas_selector.html b/superset/templates/superset/form_view/database_schemas_selector.html
index b9efb68d7e..ac827c1330 100644
--- a/superset/templates/superset/form_view/database_schemas_selector.html
+++ b/superset/templates/superset/form_view/database_schemas_selector.html
@@ -32,12 +32,11 @@ under the License.
   function update_schemas_allowed_for_csv_upload(db_id) {
     $.ajax({
       method: "GET",
-      url: "/superset/schemas_access_for_file_upload",
-      data: {db_id: db_id},
+      url: `/api/v1/database/${db_id}/schemas_access_for_file_upload/`,
       dataType: 'json',
       contentType: "application/json; charset=utf-8"
     }).done(function (data) {
-      change_schema_field_in_formview(data)
+      change_schema_field_in_formview(data ? data.schemas : [])
     }).fail(function (error) {
       var errorMsg = error.responseJSON.error;
       alert("ERROR: " + errorMsg);
diff --git a/superset/views/core.py b/superset/views/core.py
index 7c5609ca2c..3bd0ec651e 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -2747,6 +2747,7 @@ class Superset(BaseSupersetView):  # pylint: disable=too-many-public-methods
     @has_access_api
     @event_logger.log_this
     @expose("/schemas_access_for_file_upload")
+    @deprecated()
     def schemas_access_for_file_upload(self) -> FlaskResponse:
         """
         This method exposes an API endpoint to
diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py
index 3859c0be51..a30a951884 100644
--- a/tests/integration_tests/databases/api_tests.py
+++ b/tests/integration_tests/databases/api_tests.py
@@ -3619,3 +3619,44 @@ class TestDatabaseApi(SupersetTestCase):
             return
         self.assertEqual(rv.status_code, 422)
         self.assertIn("Kaboom!", response["errors"][0]["message"])
+
+    @mock.patch(
+        "superset.security.SupersetSecurityManager.get_schemas_accessible_by_user"
+    )
+    @mock.patch("superset.security.SupersetSecurityManager.can_access_database")
+    @mock.patch("superset.security.SupersetSecurityManager.can_access_all_datasources")
+    def test_schemas_access_for_csv_upload_not_found_endpoint(
+        self,
+        mock_can_access_all_datasources,
+        mock_can_access_database,
+        mock_schemas_accessible,
+    ):
+        self.login(username="gamma")
+        self.create_fake_db()
+        mock_can_access_database.return_value = False
+        mock_schemas_accessible.return_value = ["this_schema_is_allowed_too"]
+        rv = self.client.get(f"/api/v1/database/120ff/schemas_access_for_file_upload")
+        self.assertEqual(rv.status_code, 404)
+        self.delete_fake_db()
+
+    @mock.patch(
+        "superset.security.SupersetSecurityManager.get_schemas_accessible_by_user"
+    )
+    @mock.patch("superset.security.SupersetSecurityManager.can_access_database")
+    @mock.patch("superset.security.SupersetSecurityManager.can_access_all_datasources")
+    def test_schemas_access_for_csv_upload_endpoint(
+        self,
+        mock_can_access_all_datasources,
+        mock_can_access_database,
+        mock_schemas_accessible,
+    ):
+        self.login(username="admin")
+        dbobj = self.create_fake_db()
+        mock_can_access_all_datasources.return_value = False
+        mock_can_access_database.return_value = False
+        mock_schemas_accessible.return_value = ["this_schema_is_allowed_too"]
+        data = self.get_json_resp(
+            url=f"/api/v1/database/{dbobj.id}/schemas_access_for_file_upload"
+        )
+        assert data == {"schemas": ["this_schema_is_allowed_too"]}
+        self.delete_fake_db()