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()