.. _Cython: https://cython.org/
+.. _change_8567:
+
+``str(engine.url)`` will obfuscate the password by default
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To avoid leakage of database passwords, calling ``str()`` on a
+:class:`.URL` will now enable the password obfuscation feature by default.
+Previously, this obfuscation would be in place for ``__repr__()`` calls
+but not ``__str__()``. This change will impact applications and test suites
+that attempt to invoke :func:`_sa.create_engine` given the stringified URL
+from another engine, such as::
+
+ >>> e1 = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
+ >>> e2 = create_engine(str(e1.url))
+
+The above engine ``e2`` will not have the correct password; it will have the
+obfuscated string ``"***"``.
+
+The preferred approach for the above pattern is to pass the
+:class:`.URL` object directly, there's no need to stringify::
+
+ >>> e1 = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
+ >>> e2 = create_engine(e1.url)
+
+Otherwise, for a stringified URL with cleartext password, use the
+:meth:`_url.URL.render_as_string` method, passing the
+:paramref:`_url.URL.render_as_string.hide_password` parameter
+as ``False``::
+
+ >>> e1 = create_engine("postgresql+psycopg2://scott:tiger@localhost/test")
+ >>> url_string = e1.url.render_as_string(hide_password=False)
+ >>> e2 = create_engine(url_string)
+
+
+:ticket:`8567`
+
+
.. _change_6980:
"with_variant()" clones the original TypeEngine rather than changing the type
"E:/work/src/LEM/db/hello.db",
None,
), u.database
- eq_(str(u), text)
+ eq_(u.render_as_string(hide_password=False), text)
def test_rfc1738_password(self):
u = url.make_url("dbtype://user:pass word + other%3Awords@host/dbname")
eq_(u.password, "pass word + other:words")
- eq_(str(u), "dbtype://user:pass word + other%3Awords@host/dbname")
+ eq_(str(u), "dbtype://user:***@host/dbname")
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dbtype://user:pass word + other%3Awords@host/dbname",
+ )
u = url.make_url(
"dbtype://username:apples%2Foranges@hostspec/database"
)
eq_(u.password, "apples/oranges")
- eq_(str(u), "dbtype://username:apples%2Foranges@hostspec/database")
+ eq_(str(u), "dbtype://username:***@hostspec/database")
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dbtype://username:apples%2Foranges@hostspec/database",
+ )
u = url.make_url(
"dbtype://username:apples%40oranges%40%40@hostspec/database"
)
eq_(u.password, "apples@oranges@@")
+ eq_(str(u), "dbtype://username:***@hostspec/database")
eq_(
- str(u),
+ u.render_as_string(hide_password=False),
"dbtype://username:apples%40oranges%40%40@hostspec/database",
)
u = url.make_url("dbtype://username%40:@hostspec/database")
eq_(u.password, "")
eq_(u.username, "username@")
- eq_(str(u), "dbtype://username%40:@hostspec/database")
+ eq_(
+ # Do not reveal an empty password
+ str(u),
+ "dbtype://username%40:***@hostspec/database",
+ )
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dbtype://username%40:@hostspec/database",
+ )
u = url.make_url("dbtype://username:pass%2Fword@hostspec/database")
eq_(u.password, "pass/word")
- eq_(str(u), "dbtype://username:pass%2Fword@hostspec/database")
+ eq_(str(u), "dbtype://username:***@hostspec/database")
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dbtype://username:pass%2Fword@hostspec/database",
+ )
def test_password_custom_obj(self):
class SecurePassword(str):
)
eq_(u.password, "secured_password")
- eq_(str(u), "dbtype://x:secured_password@localhost")
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dbtype://x:secured_password@localhost",
+ )
# test in-place modification
sp.value = "new_secured_password"
eq_(u.password, sp)
- eq_(str(u), "dbtype://x:new_secured_password@localhost")
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dbtype://x:new_secured_password@localhost",
+ )
u = u.set(password="hi")
eq_(u.password, "hi")
- eq_(str(u), "dbtype://x:hi@localhost")
+ eq_(u.render_as_string(hide_password=False), "dbtype://x:hi@localhost")
u = u._replace(password=None)
is_(u.password, None)
- eq_(str(u), "dbtype://x@localhost")
+ eq_(u.render_as_string(hide_password=False), "dbtype://x@localhost")
def test_query_string(self):
u = url.make_url("dialect://user:pass@host/db?arg1=param1&arg2=param2")
eq_(u.query, {"arg1": "param1", "arg2": "param2"})
- eq_(str(u), "dialect://user:pass@host/db?arg1=param1&arg2=param2")
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dialect://user:pass@host/db?arg1=param1&arg2=param2",
+ )
u = url.make_url("dialect://user:pass@host/?arg1=param1&arg2=param2")
eq_(u.query, {"arg1": "param1", "arg2": "param2"})
eq_(u.database, "")
- eq_(str(u), "dialect://user:pass@host/?arg1=param1&arg2=param2")
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dialect://user:pass@host/?arg1=param1&arg2=param2",
+ )
u = url.make_url("dialect://user:pass@host?arg1=param1&arg2=param2")
eq_(u.query, {"arg1": "param1", "arg2": "param2"})
eq_(u.database, None)
- eq_(str(u), "dialect://user:pass@host?arg1=param1&arg2=param2")
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dialect://user:pass@host?arg1=param1&arg2=param2",
+ )
u = url.make_url(
"dialect://user:pass@host:450?arg1=param1&arg2=param2"
)
eq_(u.port, 450)
eq_(u.query, {"arg1": "param1", "arg2": "param2"})
- eq_(str(u), "dialect://user:pass@host:450?arg1=param1&arg2=param2")
+ eq_(
+ u.render_as_string(hide_password=False),
+ "dialect://user:pass@host:450?arg1=param1&arg2=param2",
+ )
u = url.make_url(
"dialect://user:pass@host/db?arg1=param1&arg2=param2&arg2=param3"
)
eq_(u.query, {"arg1": "param1", "arg2": ("param2", "param3")})
eq_(
- str(u),
+ u.render_as_string(hide_password=False),
"dialect://user:pass@host/db?arg1=param1&arg2=param2&arg2=param3",
)
test_url = "dialect://user:pass@host/db?arg1%3D=param1&arg2=param+2"
u = url.make_url(test_url)
eq_(u.query, {"arg1=": "param1", "arg2": "param 2"})
- eq_(str(u), test_url)
+ eq_(u.render_as_string(hide_password=False), test_url)
def test_comparison(self):
common_url = (
assert e.url.drivername == e2.url.drivername == "mysql+mysqldb"
assert e.url.username == e2.url.username == "scott"
assert e2.url is u
- assert str(u) == "mysql+mysqldb://scott:tiger@localhost/test"
+ eq_(
+ u.render_as_string(hide_password=False),
+ "mysql+mysqldb://scott:tiger@localhost/test",
+ )
assert repr(u) == "mysql+mysqldb://scott:***@localhost/test"
assert repr(e) == "Engine(mysql+mysqldb://scott:***@localhost/test)"
assert repr(e2) == "Engine(mysql+mysqldb://scott:***@localhost/test)"