Skip to content

Commit

Permalink
Convert cert_expiry to use asyncio (home-assistant#106919)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Jan 5, 2024
1 parent 9a15a5b commit 24ee64e
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 22 deletions.
34 changes: 22 additions & 12 deletions homeassistant/components/cert_expiry/helper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Helper functions for the Cert Expiry platform."""
import asyncio
import datetime
from functools import cache
import socket
import ssl
from typing import Any

from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
Expand All @@ -21,31 +24,38 @@ def _get_default_ssl_context():
return ssl.create_default_context()


def get_cert(
async def async_get_cert(
hass: HomeAssistant,
host: str,
port: int,
):
) -> dict[str, Any]:
"""Get the certificate for the host and port combination."""
ctx = _get_default_ssl_context()
address = (host, port)
with socket.create_connection(address, timeout=TIMEOUT) as sock, ctx.wrap_socket(
sock, server_hostname=address[0]
) as ssock:
cert = ssock.getpeercert()
return cert
async with asyncio.timeout(TIMEOUT):
transport, _ = await hass.loop.create_connection(
asyncio.Protocol,
host,
port,
ssl=_get_default_ssl_context(),
happy_eyeballs_delay=0.25,
server_hostname=host,
)
try:
return transport.get_extra_info("peercert")
finally:
transport.close()


async def get_cert_expiry_timestamp(
hass: HomeAssistant,
hostname: str,
port: int,
):
) -> datetime.datetime:
"""Return the certificate's expiration timestamp."""
try:
cert = await hass.async_add_executor_job(get_cert, hostname, port)
cert = await async_get_cert(hass, hostname, port)
except socket.gaierror as err:
raise ResolveFailed(f"Cannot resolve hostname: {hostname}") from err
except socket.timeout as err:
except asyncio.TimeoutError as err:
raise ConnectionTimeout(
f"Connection timeout with server: {hostname}:{port}"
) from err
Expand Down
13 changes: 7 additions & 6 deletions tests/components/cert_expiry/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Tests for the Cert Expiry config flow."""
import asyncio
import socket
import ssl
from unittest.mock import patch
Expand Down Expand Up @@ -48,7 +49,7 @@ async def test_user_with_bad_cert(hass: HomeAssistant) -> None:
assert result["step_id"] == "user"

with patch(
"homeassistant.components.cert_expiry.helper.get_cert",
"homeassistant.components.cert_expiry.helper.async_get_cert",
side_effect=ssl.SSLError("some error"),
):
result = await hass.config_entries.flow.async_configure(
Expand Down Expand Up @@ -153,7 +154,7 @@ async def test_import_with_name(hass: HomeAssistant) -> None:
async def test_bad_import(hass: HomeAssistant) -> None:
"""Test import step."""
with patch(
"homeassistant.components.cert_expiry.helper.get_cert",
"homeassistant.components.cert_expiry.helper.async_get_cert",
side_effect=ConnectionRefusedError(),
):
result = await hass.config_entries.flow.async_init(
Expand Down Expand Up @@ -198,7 +199,7 @@ async def test_abort_on_socket_failed(hass: HomeAssistant) -> None:
)

with patch(
"homeassistant.components.cert_expiry.helper.get_cert",
"homeassistant.components.cert_expiry.helper.async_get_cert",
side_effect=socket.gaierror(),
):
result = await hass.config_entries.flow.async_configure(
Expand All @@ -208,8 +209,8 @@ async def test_abort_on_socket_failed(hass: HomeAssistant) -> None:
assert result["errors"] == {CONF_HOST: "resolve_failed"}

with patch(
"homeassistant.components.cert_expiry.helper.get_cert",
side_effect=socket.timeout(),
"homeassistant.components.cert_expiry.helper.async_get_cert",
side_effect=asyncio.TimeoutError,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_HOST: HOST}
Expand All @@ -218,7 +219,7 @@ async def test_abort_on_socket_failed(hass: HomeAssistant) -> None:
assert result["errors"] == {CONF_HOST: "connection_timeout"}

with patch(
"homeassistant.components.cert_expiry.helper.get_cert",
"homeassistant.components.cert_expiry.helper.async_get_cert",
side_effect=ConnectionRefusedError,
):
result = await hass.config_entries.flow.async_configure(
Expand Down
9 changes: 5 additions & 4 deletions tests/components/cert_expiry/test_sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async def test_async_setup_entry_bad_cert(hass: HomeAssistant) -> None:
)

with patch(
"homeassistant.components.cert_expiry.helper.get_cert",
"homeassistant.components.cert_expiry.helper.async_get_cert",
side_effect=ssl.SSLError("some error"),
):
entry.add_to_hass(hass)
Expand Down Expand Up @@ -146,7 +146,7 @@ async def test_update_sensor_network_errors(hass: HomeAssistant) -> None:
next_update = starting_time + timedelta(hours=24)

with freeze_time(next_update), patch(
"homeassistant.components.cert_expiry.helper.get_cert",
"homeassistant.components.cert_expiry.helper.async_get_cert",
side_effect=socket.gaierror,
):
async_fire_time_changed(hass, utcnow() + timedelta(hours=24))
Expand Down Expand Up @@ -174,7 +174,7 @@ async def test_update_sensor_network_errors(hass: HomeAssistant) -> None:
next_update = starting_time + timedelta(hours=72)

with freeze_time(next_update), patch(
"homeassistant.components.cert_expiry.helper.get_cert",
"homeassistant.components.cert_expiry.helper.async_get_cert",
side_effect=ssl.SSLError("something bad"),
):
async_fire_time_changed(hass, utcnow() + timedelta(hours=72))
Expand All @@ -189,7 +189,8 @@ async def test_update_sensor_network_errors(hass: HomeAssistant) -> None:
next_update = starting_time + timedelta(hours=96)

with freeze_time(next_update), patch(
"homeassistant.components.cert_expiry.helper.get_cert", side_effect=Exception()
"homeassistant.components.cert_expiry.helper.async_get_cert",
side_effect=Exception(),
):
async_fire_time_changed(hass, utcnow() + timedelta(hours=96))
await hass.async_block_till_done()
Expand Down

0 comments on commit 24ee64e

Please sign in to comment.