Skip to content

Commit 1b822a4

Browse files
committed
Implement ssl_require parameter mapping for various database backends
1 parent afad969 commit 1b822a4

2 files changed

Lines changed: 81 additions & 6 deletions

File tree

dj_database_url/__init__.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ def parse(
170170
conn_max_age,
171171
conn_health_checks,
172172
disable_server_side_cursors,
173-
ssl_require,
174173
test_options,
175174
)
176175

@@ -211,6 +210,9 @@ def parse(
211210
parsed_config["OPTIONS"].update(settings.pop("OPTIONS", {}))
212211
parsed_config.update(settings)
213212

213+
if ssl_require:
214+
_configure_ssl(parsed_config)
215+
214216
if not parsed_config["OPTIONS"]:
215217
parsed_config.pop("OPTIONS")
216218
return parsed_config
@@ -229,12 +231,43 @@ def _parse_value(value: str) -> OptionType:
229231
return value
230232

231233

234+
def _configure_ssl(parsed_config: DBConfig) -> None:
235+
assert "OPTIONS" in parsed_config
236+
options = parsed_config["OPTIONS"]
237+
assert "ENGINE" in parsed_config
238+
backend = parsed_config["ENGINE"]
239+
240+
if backend in (
241+
"django.db.backends.mysql",
242+
"django.contrib.gis.db.backends.mysql",
243+
):
244+
options["ssl_mode"] = "REQUIRED"
245+
elif backend in (
246+
"django.db.backends.postgresql",
247+
"django.contrib.gis.db.backends.postgis",
248+
"django_redshift_backend",
249+
"django_cockroachdb",
250+
"timescale.db.backends.postgresql",
251+
"timescale.db.backends.postgis",
252+
):
253+
options["sslmode"] = "require"
254+
elif backend in (
255+
"mssql",
256+
"sql_server.pyodbc",
257+
):
258+
current_extra = options.get("extra_params", "")
259+
if "Encrypt=yes" not in current_extra:
260+
if current_extra:
261+
options["extra_params"] = f"{current_extra};Encrypt=yes"
262+
else:
263+
options["extra_params"] = "Encrypt=yes"
264+
265+
232266
def _convert_to_settings(
233267
engine: Optional[str],
234268
conn_max_age: Optional[int],
235269
conn_health_checks: bool,
236270
disable_server_side_cursors: bool,
237-
ssl_require: bool,
238271
test_options: Optional[dict[str, Any]],
239272
) -> DBConfig:
240273
settings: DBConfig = {
@@ -244,9 +277,6 @@ def _convert_to_settings(
244277
}
245278
if engine:
246279
settings["ENGINE"] = engine
247-
if ssl_require:
248-
settings["OPTIONS"] = {}
249-
settings["OPTIONS"]["sslmode"] = "require"
250280
if test_options:
251281
settings["TEST"] = test_options
252282
return settings

tests/test_dj_database_url.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,10 +673,55 @@ def test_register_multiple_times__no_duplicates_in_uses_netloc(self) -> None:
673673
os.environ,
674674
{"DATABASE_URL": "postgres://user:password@instance.amazonaws.com:5431/d8r8?"},
675675
)
676-
def test_ssl_require(self) -> None:
676+
def test_ssl_require_postgres(self) -> None:
677677
url = dj_database_url.config(ssl_require=True)
678678
assert url["OPTIONS"] == {'sslmode': 'require'}
679679

680+
@mock.patch.dict(
681+
os.environ,
682+
{"DATABASE_URL": "mysql://user:password@instance.amazonaws.com:3306/dbname"},
683+
)
684+
def test_ssl_require_mysql(self) -> None:
685+
url = dj_database_url.config(ssl_require=True)
686+
assert url["OPTIONS"] == {"ssl_mode": "REQUIRED"}
687+
688+
@mock.patch.dict(
689+
os.environ,
690+
{"DATABASE_URL": "mssqlms://user:password@instance.amazonaws.com:1234/dbname"},
691+
)
692+
def test_ssl_require_mssql(self) -> None:
693+
url = dj_database_url.config(ssl_require=True)
694+
assert url["OPTIONS"] == {"extra_params": "Encrypt=yes"}
695+
696+
@mock.patch.dict(
697+
os.environ,
698+
{
699+
"DATABASE_URL": (
700+
"mssql://user:password@instance.amazonaws.com:1234/dbname"
701+
"?extra_params=TrustServerCertificate=yes"
702+
)
703+
},
704+
)
705+
def test_ssl_require_mssql_existing_extra_params(self) -> None:
706+
url = dj_database_url.config(ssl_require=True)
707+
assert url["OPTIONS"] == {
708+
"extra_params": "TrustServerCertificate=yes;Encrypt=yes"
709+
}
710+
711+
@mock.patch.dict(
712+
os.environ,
713+
{
714+
"DATABASE_URL": (
715+
"mssql://user:password@instance.amazonaws.com:1234/dbname"
716+
"?extra_params=Encrypt=yes"
717+
)
718+
},
719+
)
720+
def test_ssl_require_mssql_already_encrypted(self) -> None:
721+
url = dj_database_url.config(ssl_require=True)
722+
# Should NOT append Encrypt=yes again
723+
assert url["OPTIONS"] == {"extra_params": "Encrypt=yes"}
724+
680725
def test_options_int_values(self) -> None:
681726
"""Ensure that options with integer values are parsed correctly."""
682727
url = dj_database_url.parse(

0 commit comments

Comments
 (0)