Skip to content

Commit b479e93

Browse files
dpgasparvillebro
andauthored
fix: add disallowed query params for engines specs (#23217)
Co-authored-by: Ville Brofeldt <[email protected]>
1 parent 42db7e5 commit b479e93

File tree

4 files changed

+38
-0
lines changed

4 files changed

+38
-0
lines changed

superset/db_engine_specs/base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
354354
# This set will give the keywords for data limit statements
355355
# to consider for the engines with TOP SQL parsing
356356
top_keywords: Set[str] = {"TOP"}
357+
# A set of disallowed connection query parameters
358+
disallow_uri_query_params: Set[str] = set()
357359

358360
force_column_alias_quotes = False
359361
arraysize = 0
@@ -1724,6 +1726,19 @@ def get_public_information(cls) -> Dict[str, Any]:
17241726
"disable_ssh_tunneling": cls.disable_ssh_tunneling,
17251727
}
17261728

1729+
@classmethod
1730+
def validate_database_uri(cls, sqlalchemy_uri: URL) -> None:
1731+
"""
1732+
Validates a database SQLAlchemy URI per engine spec.
1733+
Use this to implement a final validation for unwanted connection configuration
1734+
1735+
:param sqlalchemy_uri:
1736+
"""
1737+
if existing_disallowed := cls.disallow_uri_query_params.intersection(
1738+
sqlalchemy_uri.query
1739+
):
1740+
raise ValueError(f"Forbidden query parameter(s): {existing_disallowed}")
1741+
17271742

17281743
# schema for adding a database by providing parameters instead of the
17291744
# full SQLAlchemy URI

superset/db_engine_specs/mysql.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class MySQLEngineSpec(BaseEngineSpec, BasicParametersMixin):
173173
{},
174174
),
175175
}
176+
disallow_uri_query_params = {"local_infile"}
176177

177178
@classmethod
178179
def convert_dttm(

superset/models/core.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,8 @@ def _get_sqla_engine(
424424
sqlalchemy_url = make_url_safe(
425425
sqlalchemy_uri if sqlalchemy_uri else self.sqlalchemy_uri_decrypted
426426
)
427+
self.db_engine_spec.validate_database_uri(sqlalchemy_url)
428+
427429
sqlalchemy_url = self.db_engine_spec.adjust_database_uri(sqlalchemy_url, schema)
428430
effective_username = self.get_effective_user(sqlalchemy_url)
429431
# If using MySQL or Presto for example, will set url.username

tests/unit_tests/db_engine_specs/test_mysql.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
TINYINT,
3434
TINYTEXT,
3535
)
36+
from sqlalchemy.engine.url import make_url
3637

3738
from superset.utils.core import GenericDataType
3839
from tests.unit_tests.db_engine_specs.utils import (
@@ -99,6 +100,25 @@ def test_convert_dttm(
99100
assert_convert_dttm(spec, target_type, expected_result, dttm)
100101

101102

103+
@pytest.mark.parametrize(
104+
"sqlalchemy_uri,error",
105+
[
106+
("mysql://user:password@host/db1?local_infile=1", True),
107+
("mysql://user:password@host/db1?local_infile=0", True),
108+
("mysql://user:password@host/db1", False),
109+
],
110+
)
111+
def test_validate_database_uri(sqlalchemy_uri: str, error: bool) -> None:
112+
from superset.db_engine_specs.mysql import MySQLEngineSpec
113+
114+
url = make_url(sqlalchemy_uri)
115+
if error:
116+
with pytest.raises(ValueError):
117+
MySQLEngineSpec.validate_database_uri(url)
118+
return
119+
MySQLEngineSpec.validate_database_uri(url)
120+
121+
102122
@patch("sqlalchemy.engine.Engine.connect")
103123
def test_get_cancel_query_id(engine_mock: Mock) -> None:
104124
from superset.db_engine_specs.mysql import MySQLEngineSpec

0 commit comments

Comments
 (0)