Remove login details before logging SQL errors (#48758)
parent
5be1eacde9
commit
2555b10d49
|
@ -2,6 +2,7 @@
|
|||
import datetime
|
||||
import decimal
|
||||
import logging
|
||||
import re
|
||||
|
||||
import sqlalchemy
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
@ -18,6 +19,13 @@ CONF_COLUMN_NAME = "column"
|
|||
CONF_QUERIES = "queries"
|
||||
CONF_QUERY = "query"
|
||||
|
||||
DB_URL_RE = re.compile("//.*:.*@")
|
||||
|
||||
|
||||
def redact_credentials(data):
|
||||
"""Redact credentials from string data."""
|
||||
return DB_URL_RE.sub("//****:****@", data)
|
||||
|
||||
|
||||
def validate_sql_select(value):
|
||||
"""Validate that value is a SQL SELECT query."""
|
||||
|
@ -47,6 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
if not db_url:
|
||||
db_url = DEFAULT_URL.format(hass_config_path=hass.config.path(DEFAULT_DB_FILE))
|
||||
|
||||
sess = None
|
||||
try:
|
||||
engine = sqlalchemy.create_engine(db_url)
|
||||
sessmaker = scoped_session(sessionmaker(bind=engine))
|
||||
|
@ -56,10 +65,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
sess.execute("SELECT 1;")
|
||||
|
||||
except sqlalchemy.exc.SQLAlchemyError as err:
|
||||
_LOGGER.error("Couldn't connect using %s DB_URL: %s", db_url, err)
|
||||
_LOGGER.error(
|
||||
"Couldn't connect using %s DB_URL: %s",
|
||||
redact_credentials(db_url),
|
||||
redact_credentials(str(err)),
|
||||
)
|
||||
return
|
||||
finally:
|
||||
sess.close()
|
||||
if sess:
|
||||
sess.close()
|
||||
|
||||
queries = []
|
||||
|
||||
|
@ -147,7 +161,11 @@ class SQLSensor(SensorEntity):
|
|||
value = str(value)
|
||||
self._attributes[key] = value
|
||||
except sqlalchemy.exc.SQLAlchemyError as err:
|
||||
_LOGGER.error("Error executing query %s: %s", self._query, err)
|
||||
_LOGGER.error(
|
||||
"Error executing query %s: %s",
|
||||
self._query,
|
||||
redact_credentials(str(err)),
|
||||
)
|
||||
return
|
||||
finally:
|
||||
sess.close()
|
||||
|
|
|
@ -55,3 +55,43 @@ async def test_invalid_query(hass):
|
|||
|
||||
state = hass.states.get("sensor.count_tables")
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url,expected_patterns,not_expected_patterns",
|
||||
[
|
||||
(
|
||||
"sqlite://homeassistant:hunter2@homeassistant.local",
|
||||
["sqlite://****:****@homeassistant.local"],
|
||||
["sqlite://homeassistant:hunter2@homeassistant.local"],
|
||||
),
|
||||
(
|
||||
"sqlite://homeassistant.local",
|
||||
["sqlite://homeassistant.local"],
|
||||
[],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_invalid_url(hass, caplog, url, expected_patterns, not_expected_patterns):
|
||||
"""Test credentials in url is not logged."""
|
||||
config = {
|
||||
"sensor": {
|
||||
"platform": "sql",
|
||||
"db_url": url,
|
||||
"queries": [
|
||||
{
|
||||
"name": "count_tables",
|
||||
"query": "SELECT 5 as value",
|
||||
"column": "value",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
assert await async_setup_component(hass, "sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for pattern in not_expected_patterns:
|
||||
assert pattern not in caplog.text
|
||||
for pattern in expected_patterns:
|
||||
assert pattern in caplog.text
|
||||
|
|
Loading…
Reference in New Issue