From aa920d45c3496d490546a0bd6813ce512ca0236b Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Thu, 1 Aug 2019 10:54:08 +0200 Subject: [PATCH 01/15] Enable cert_expiry config entries --- .../cert_expiry/.translations/en.json | 21 ++++ .../components/cert_expiry/__init__.py | 33 +++++++ .../components/cert_expiry/config_flow.py | 87 ++++++++++++++++ homeassistant/components/cert_expiry/const.py | 5 + .../components/cert_expiry/manifest.json | 1 + .../components/cert_expiry/sensor.py | 16 +-- .../components/cert_expiry/strings.json | 21 ++++ homeassistant/generated/config_flows.py | 1 + tests/components/cert_expiry/__init__.py | 1 + .../cert_expiry/test_config_flow.py | 99 +++++++++++++++++++ 10 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/cert_expiry/.translations/en.json create mode 100644 homeassistant/components/cert_expiry/config_flow.py create mode 100644 homeassistant/components/cert_expiry/const.py create mode 100644 homeassistant/components/cert_expiry/strings.json create mode 100644 tests/components/cert_expiry/__init__.py create mode 100644 tests/components/cert_expiry/test_config_flow.py diff --git a/homeassistant/components/cert_expiry/.translations/en.json b/homeassistant/components/cert_expiry/.translations/en.json new file mode 100644 index 00000000000000..8e32d11b0e14f6 --- /dev/null +++ b/homeassistant/components/cert_expiry/.translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "host_port_exists": "This host and port combination is already configured" + }, + "error": { + "host_port_exists": "This host and port combination is already configured" + }, + "step": { + "user": { + "data": { + "host": "The hostname of the certificate", + "name": "The name of the certificate", + "port": "The port of the certificate" + }, + "title": "Define the certificate to test" + } + }, + "title": "Certificate Expiry" + } +} \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/__init__.py b/homeassistant/components/cert_expiry/__init__.py index 78ceb60dd404b7..65f119549cd672 100644 --- a/homeassistant/components/cert_expiry/__init__.py +++ b/homeassistant/components/cert_expiry/__init__.py @@ -1 +1,34 @@ """The cert_expiry component.""" +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + +from .const import DOMAIN + + +async def async_setup(hass, config): + """Platform setup, do nothing.""" + return True + + +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): + """Load the saved entities.""" + hass.data.setdefault(DOMAIN, {}) + + # store the info for later + hass.data[DOMAIN][entry.entry_id] = entry + + @callback + def async_start(_): + """Load the entry after the start event.""" + for eid in hass.data[DOMAIN]: + entry = hass.data[DOMAIN][eid] + hass.async_create_task( + hass.config_entries.async_forward_entry_setup( + entry, 'sensor')) + hass.data[DOMAIN] = {} + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_start) + + return True diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py new file mode 100644 index 00000000000000..53f5c5abd24f4f --- /dev/null +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -0,0 +1,87 @@ +"""Config flow for the Cert Expiry platform.""" +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PORT, CONF_NAME, CONF_HOST +from homeassistant.core import HomeAssistant, callback +from homeassistant.util import slugify + +from .const import DOMAIN, DEFAULT_PORT, DEFAULT_NAME + + +@callback +def certexpiry_entries(hass: HomeAssistant): + """Return the host,port tuples for the domain.""" + return set((entry.data[CONF_HOST], entry.data[CONF_PORT]) for + entry in hass.config_entries.async_entries(DOMAIN)) + + +@config_entries.HANDLERS.register(DOMAIN) +class CertexpiryConfigFlow(config_entries.ConfigFlow): + """Handle a config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + def __init__(self) -> None: + """Initialize the config flow.""" + self._errors = {} + + def _prt_in_configuration_exists(self, host: str, prt: int) -> bool: + """Return True if host, port combination exists in configuration.""" + if (host, prt) in certexpiry_entries(self.hass): + return True + return False + + async def async_step_user(self, user_input=None): + """Step when user intializes a integration.""" + self._errors = {} + if user_input is not None: + name = slugify(user_input[CONF_NAME]) + host = user_input[CONF_HOST] + prt = user_input[CONF_PORT] + if not self._prt_in_configuration_exists(host, prt): + return self.async_create_entry( + title=name, + data={ + CONF_HOST: host, + CONF_PORT: prt + } + ) + self._errors[CONF_HOST] = 'host_port_exists' + else: + user_input = {} + user_input[CONF_NAME] = DEFAULT_NAME + user_input[CONF_HOST] = '' + user_input[CONF_PORT] = DEFAULT_PORT + + return self.async_show_form( + step_id='user', + data_schema=vol.Schema({ + vol.Required(CONF_NAME, + default=user_input[CONF_NAME]): str, + vol.Required(CONF_HOST, + default=user_input[CONF_HOST]): str, + vol.Required(CONF_PORT, + default=user_input[CONF_PORT]): int + }), + errors=self._errors + ) + + async def async_step_import(self, user_input=None): + """Import a config entry. + + Only host was required in the yaml file all other fields are optional + """ + host = user_input[CONF_HOST] + prt = user_input.get(CONF_PORT, DEFAULT_PORT) + name = user_input.get(CONF_NAME, host) + if self._prt_in_configuration_exists(host, prt): + return self.async_abort( + reason='host_port_exists' + ) + return await self.async_step_user({ + CONF_NAME: name, + CONF_HOST: host, + CONF_PORT: prt + }) diff --git a/homeassistant/components/cert_expiry/const.py b/homeassistant/components/cert_expiry/const.py new file mode 100644 index 00000000000000..e0a56ea4ec6f66 --- /dev/null +++ b/homeassistant/components/cert_expiry/const.py @@ -0,0 +1,5 @@ +"""Const for Cert Expiry.""" + +DOMAIN = "cert_expiry" +DEFAULT_NAME = 'SSL Certificate Expiry' +DEFAULT_PORT = 443 diff --git a/homeassistant/components/cert_expiry/manifest.json b/homeassistant/components/cert_expiry/manifest.json index 7ef2e0b7d105d6..efe9330caf937a 100644 --- a/homeassistant/components/cert_expiry/manifest.json +++ b/homeassistant/components/cert_expiry/manifest.json @@ -3,6 +3,7 @@ "name": "Cert expiry", "documentation": "https://www.home-assistant.io/components/cert_expiry", "requirements": [], + "config_flow": true, "dependencies": [], "codeowners": [] } diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index b1e0d819358e0d..bb1bd3447e5407 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -7,6 +7,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_NAME, @@ -16,7 +17,7 @@ ) from homeassistant.helpers.entity import Entity -_LOGGER = logging.getLogger(__name__) +from .const import DOMAIN, DEFAULT_NAME, DEFAULT_PORT DEFAULT_NAME = "SSL Certificate Expiry" DEFAULT_PORT = 443 @@ -40,16 +41,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def run_setup(event): """Wait until Home Assistant is fully initialized before creating. - Delay the setup until Home Assistant is fully initialized. - """ - server_name = config.get(CONF_HOST) - server_port = config.get(CONF_PORT) - sensor_name = config.get(CONF_NAME) + return True add_entities([SSLCertificate(sensor_name, server_name, server_port)], True) - # To allow checking of the HA certificate we must first be running. - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup) +async def async_setup_entry(hass, entry, async_add_entities): + """Add cert-expiry entry.""" + async_add_entities([SSLCertificate( + entry.title, entry.data[CONF_HOST], entry.data[CONF_PORT])], + True) class SSLCertificate(Entity): diff --git a/homeassistant/components/cert_expiry/strings.json b/homeassistant/components/cert_expiry/strings.json new file mode 100644 index 00000000000000..10943deeb22c56 --- /dev/null +++ b/homeassistant/components/cert_expiry/strings.json @@ -0,0 +1,21 @@ +{ + "config": { + "title": "Certificate Expiry", + "step": { + "user": { + "title": "Define the certificate to test", + "data": { + "name": "The name of the certificate", + "host": "The hostname of the certificate", + "port": "The port of the certificate" + } + } + }, + "error": { + "host_port_exists": "This host and port combination is already configured" + }, + "abort": { + "host_port_exists": "This host and port combination is already configured" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index de665ecf5a6e44..082e0f853f87ce 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -10,6 +10,7 @@ "ambient_station", "axis", "cast", + "cert_expiry", "daikin", "deconz", "dialogflow", diff --git a/tests/components/cert_expiry/__init__.py b/tests/components/cert_expiry/__init__.py new file mode 100644 index 00000000000000..5ef5adee2e2291 --- /dev/null +++ b/tests/components/cert_expiry/__init__.py @@ -0,0 +1 @@ +"""Tests for the Cert Expiry component.""" diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py new file mode 100644 index 00000000000000..687050bd5462a4 --- /dev/null +++ b/tests/components/cert_expiry/test_config_flow.py @@ -0,0 +1,99 @@ +"""Tests for the Cert Expiry config flow.""" +from homeassistant import data_entry_flow +from homeassistant.components.cert_expiry import config_flow +from homeassistant.components.cert_expiry.const import DEFAULT_PORT +from homeassistant.const import CONF_PORT, CONF_NAME, CONF_HOST + +from tests.common import MockConfigEntry + +NAME = 'Cert Expiry test 1 2 3' +PORT = 445 +HOST = 'google.com' + + +def init_config_flow(hass): + """Init a configuration flow.""" + flow = config_flow.CertexpiryConfigFlow() + flow.hass = hass + return flow + + +async def test_user(hass): + """Test user config.""" + flow = init_config_flow(hass) + + result = await flow.async_step_user() + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['step_id'] == 'user' + + # tets with all provided + result = await flow.async_step_user({ + CONF_NAME: NAME, CONF_HOST: HOST, CONF_PORT: PORT}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result['title'] == 'cert_expiry_test_1_2_3' + assert result['data'][CONF_HOST] == HOST + assert result['data'][CONF_PORT] == PORT + + +async def test_import(hass): + """Test import step.""" + flow = init_config_flow(hass) + + # import with only host + result = await flow.async_step_import({CONF_HOST: HOST}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result['title'] == 'google_com' + assert result['data'][CONF_HOST] == HOST + assert result['data'][CONF_PORT] == DEFAULT_PORT + + # import with only host + result = await flow.async_step_import({CONF_HOST: HOST, CONF_NAME: NAME}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result['title'] == 'cert_expiry_test_1_2_3' + assert result['data'][CONF_HOST] == HOST + assert result['data'][CONF_PORT] == DEFAULT_PORT + + # improt with host and port + result = await flow.async_step_import({CONF_HOST: HOST, CONF_PORT: PORT}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result['title'] == 'google_com' + assert result['data'][CONF_HOST] == HOST + assert result['data'][CONF_PORT] == PORT + + # import with all + result = await flow.async_step_import({CONF_HOST: HOST, CONF_PORT: PORT, + CONF_NAME: NAME}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result['title'] == 'cert_expiry_test_1_2_3' + assert result['data'][CONF_HOST] == HOST + assert result['data'][CONF_PORT] == PORT + + +async def test_abort_if_already_setup(hass): + """Test we abort if Daikin is already setup.""" + pass + flow = init_config_flow(hass) + MockConfigEntry(domain='cert_expiry', + data={CONF_PORT: DEFAULT_PORT, + CONF_NAME: NAME, + CONF_HOST: HOST}).add_to_hass(hass) + + # Should fail, same HOST and PORT (default) + result = await flow.async_step_import( + {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: DEFAULT_PORT}) + assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT + assert result['reason'] == 'host_port_exists' + + # Should same HOST and PORT (default) + result = await flow.async_step_user( + {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: DEFAULT_PORT}) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + assert result['errors'] == {CONF_HOST: 'host_port_exists'} + + # SHOULD pass, same Host diff PORT + result = await flow.async_step_import( + {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: 888}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result['title'] == 'cert_expiry_test_1_2_3' + assert result['data'][CONF_HOST] == HOST + assert result['data'][CONF_PORT] == 888 From 237025ad713784932e81c70d191a6b93b8522f2d Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Thu, 1 Aug 2019 11:13:13 +0200 Subject: [PATCH 02/15] add black --- .../components/cert_expiry/__init__.py | 4 +- .../components/cert_expiry/config_flow.py | 47 ++++------ homeassistant/components/cert_expiry/const.py | 2 +- .../components/cert_expiry/sensor.py | 21 ++--- .../cert_expiry/test_config_flow.py | 91 ++++++++++--------- 5 files changed, 81 insertions(+), 84 deletions(-) diff --git a/homeassistant/components/cert_expiry/__init__.py b/homeassistant/components/cert_expiry/__init__.py index 65f119549cd672..10a2133cbce4a0 100644 --- a/homeassistant/components/cert_expiry/__init__.py +++ b/homeassistant/components/cert_expiry/__init__.py @@ -25,8 +25,8 @@ def async_start(_): for eid in hass.data[DOMAIN]: entry = hass.data[DOMAIN][eid] hass.async_create_task( - hass.config_entries.async_forward_entry_setup( - entry, 'sensor')) + hass.config_entries.async_forward_entry_setup(entry, "sensor") + ) hass.data[DOMAIN] = {} hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_start) diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index 53f5c5abd24f4f..73cf80e9a4abbc 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -12,8 +12,10 @@ @callback def certexpiry_entries(hass: HomeAssistant): """Return the host,port tuples for the domain.""" - return set((entry.data[CONF_HOST], entry.data[CONF_PORT]) for - entry in hass.config_entries.async_entries(DOMAIN)) + return set( + (entry.data[CONF_HOST], entry.data[CONF_PORT]) + for entry in hass.config_entries.async_entries(DOMAIN) + ) @config_entries.HANDLERS.register(DOMAIN) @@ -42,30 +44,25 @@ async def async_step_user(self, user_input=None): prt = user_input[CONF_PORT] if not self._prt_in_configuration_exists(host, prt): return self.async_create_entry( - title=name, - data={ - CONF_HOST: host, - CONF_PORT: prt - } + title=name, data={CONF_HOST: host, CONF_PORT: prt} ) - self._errors[CONF_HOST] = 'host_port_exists' + self._errors[CONF_HOST] = "host_port_exists" else: user_input = {} user_input[CONF_NAME] = DEFAULT_NAME - user_input[CONF_HOST] = '' + user_input[CONF_HOST] = "" user_input[CONF_PORT] = DEFAULT_PORT return self.async_show_form( - step_id='user', - data_schema=vol.Schema({ - vol.Required(CONF_NAME, - default=user_input[CONF_NAME]): str, - vol.Required(CONF_HOST, - default=user_input[CONF_HOST]): str, - vol.Required(CONF_PORT, - default=user_input[CONF_PORT]): int - }), - errors=self._errors + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_NAME, default=user_input[CONF_NAME]): str, + vol.Required(CONF_HOST, default=user_input[CONF_HOST]): str, + vol.Required(CONF_PORT, default=user_input[CONF_PORT]): int, + } + ), + errors=self._errors, ) async def async_step_import(self, user_input=None): @@ -77,11 +74,7 @@ async def async_step_import(self, user_input=None): prt = user_input.get(CONF_PORT, DEFAULT_PORT) name = user_input.get(CONF_NAME, host) if self._prt_in_configuration_exists(host, prt): - return self.async_abort( - reason='host_port_exists' - ) - return await self.async_step_user({ - CONF_NAME: name, - CONF_HOST: host, - CONF_PORT: prt - }) + return self.async_abort(reason="host_port_exists") + return await self.async_step_user( + {CONF_NAME: name, CONF_HOST: host, CONF_PORT: prt} + ) diff --git a/homeassistant/components/cert_expiry/const.py b/homeassistant/components/cert_expiry/const.py index e0a56ea4ec6f66..b5e714f0502970 100644 --- a/homeassistant/components/cert_expiry/const.py +++ b/homeassistant/components/cert_expiry/const.py @@ -1,5 +1,5 @@ """Const for Cert Expiry.""" DOMAIN = "cert_expiry" -DEFAULT_NAME = 'SSL Certificate Expiry' +DEFAULT_NAME = "SSL Certificate Expiry" DEFAULT_PORT = 443 diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index bb1bd3447e5407..a39a5505748f56 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -19,9 +19,6 @@ from .const import DOMAIN, DEFAULT_NAME, DEFAULT_PORT -DEFAULT_NAME = "SSL Certificate Expiry" -DEFAULT_PORT = 443 - SCAN_INTERVAL = timedelta(hours=12) TIMEOUT = 10.0 @@ -37,19 +34,21 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up certificate expiry sensor.""" - - def run_setup(event): - """Wait until Home Assistant is fully initialized before creating. - + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config + ) + ) return True - add_entities([SSLCertificate(sensor_name, server_name, server_port)], True) async def async_setup_entry(hass, entry, async_add_entities): """Add cert-expiry entry.""" - async_add_entities([SSLCertificate( - entry.title, entry.data[CONF_HOST], entry.data[CONF_PORT])], - True) + async_add_entities( + [SSLCertificate(entry.title, entry.data[CONF_HOST], entry.data[CONF_PORT])], + True, + ) + return True class SSLCertificate(Entity): diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py index 687050bd5462a4..fa76d296c81141 100644 --- a/tests/components/cert_expiry/test_config_flow.py +++ b/tests/components/cert_expiry/test_config_flow.py @@ -6,9 +6,9 @@ from tests.common import MockConfigEntry -NAME = 'Cert Expiry test 1 2 3' +NAME = "Cert Expiry test 1 2 3" PORT = 445 -HOST = 'google.com' +HOST = "google.com" def init_config_flow(hass): @@ -23,16 +23,17 @@ async def test_user(hass): flow = init_config_flow(hass) result = await flow.async_step_user() - assert result['type'] == data_entry_flow.RESULT_TYPE_FORM - assert result['step_id'] == 'user' + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" # tets with all provided - result = await flow.async_step_user({ - CONF_NAME: NAME, CONF_HOST: HOST, CONF_PORT: PORT}) - assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result['title'] == 'cert_expiry_test_1_2_3' - assert result['data'][CONF_HOST] == HOST - assert result['data'][CONF_PORT] == PORT + result = await flow.async_step_user( + {CONF_NAME: NAME, CONF_HOST: HOST, CONF_PORT: PORT} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "cert_expiry_test_1_2_3" + assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_PORT] == PORT async def test_import(hass): @@ -41,59 +42,63 @@ async def test_import(hass): # import with only host result = await flow.async_step_import({CONF_HOST: HOST}) - assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result['title'] == 'google_com' - assert result['data'][CONF_HOST] == HOST - assert result['data'][CONF_PORT] == DEFAULT_PORT + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "google_com" + assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_PORT] == DEFAULT_PORT # import with only host result = await flow.async_step_import({CONF_HOST: HOST, CONF_NAME: NAME}) - assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result['title'] == 'cert_expiry_test_1_2_3' - assert result['data'][CONF_HOST] == HOST - assert result['data'][CONF_PORT] == DEFAULT_PORT + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "cert_expiry_test_1_2_3" + assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_PORT] == DEFAULT_PORT # improt with host and port result = await flow.async_step_import({CONF_HOST: HOST, CONF_PORT: PORT}) - assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result['title'] == 'google_com' - assert result['data'][CONF_HOST] == HOST - assert result['data'][CONF_PORT] == PORT + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "google_com" + assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_PORT] == PORT # import with all - result = await flow.async_step_import({CONF_HOST: HOST, CONF_PORT: PORT, - CONF_NAME: NAME}) - assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result['title'] == 'cert_expiry_test_1_2_3' - assert result['data'][CONF_HOST] == HOST - assert result['data'][CONF_PORT] == PORT + result = await flow.async_step_import( + {CONF_HOST: HOST, CONF_PORT: PORT, CONF_NAME: NAME} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "cert_expiry_test_1_2_3" + assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_PORT] == PORT async def test_abort_if_already_setup(hass): """Test we abort if Daikin is already setup.""" pass flow = init_config_flow(hass) - MockConfigEntry(domain='cert_expiry', - data={CONF_PORT: DEFAULT_PORT, - CONF_NAME: NAME, - CONF_HOST: HOST}).add_to_hass(hass) + MockConfigEntry( + domain="cert_expiry", + data={CONF_PORT: DEFAULT_PORT, CONF_NAME: NAME, CONF_HOST: HOST}, + ).add_to_hass(hass) # Should fail, same HOST and PORT (default) result = await flow.async_step_import( - {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: DEFAULT_PORT}) - assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT - assert result['reason'] == 'host_port_exists' + {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: DEFAULT_PORT} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "host_port_exists" # Should same HOST and PORT (default) result = await flow.async_step_user( - {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: DEFAULT_PORT}) - assert result['type'] == data_entry_flow.RESULT_TYPE_FORM - assert result['errors'] == {CONF_HOST: 'host_port_exists'} + {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: DEFAULT_PORT} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_HOST: "host_port_exists"} # SHOULD pass, same Host diff PORT result = await flow.async_step_import( - {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: 888}) - assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result['title'] == 'cert_expiry_test_1_2_3' - assert result['data'][CONF_HOST] == HOST - assert result['data'][CONF_PORT] == 888 + {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: 888} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "cert_expiry_test_1_2_3" + assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_PORT] == 888 From d7d3a9f775eab6e73c34a9691d276e6caef10236 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Thu, 1 Aug 2019 11:15:57 +0200 Subject: [PATCH 03/15] lint fixes --- homeassistant/components/cert_expiry/sensor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index a39a5505748f56..b5587451acf4c4 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -13,12 +13,13 @@ CONF_NAME, CONF_HOST, CONF_PORT, - EVENT_HOMEASSISTANT_START, ) from homeassistant.helpers.entity import Entity from .const import DOMAIN, DEFAULT_NAME, DEFAULT_PORT +_LOGGER = logging.getLogger(__name__) + SCAN_INTERVAL = timedelta(hours=12) TIMEOUT = 10.0 @@ -36,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up certificate expiry sensor.""" hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config + DOMAIN, context={"source": SOURCE_IMPORT}, data=config ) ) return True From 2f8cf17b83f82b25160e55ef62d68a87884d495b Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Thu, 1 Aug 2019 12:52:06 +0200 Subject: [PATCH 04/15] Rerun black --- .../components/cert_expiry/manifest.json | 14 ++++++------- .../components/cert_expiry/sensor.py | 6 +----- .../components/cert_expiry/strings.json | 20 +++++++++---------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/cert_expiry/manifest.json b/homeassistant/components/cert_expiry/manifest.json index efe9330caf937a..771e20e9a5301a 100644 --- a/homeassistant/components/cert_expiry/manifest.json +++ b/homeassistant/components/cert_expiry/manifest.json @@ -1,9 +1,9 @@ { - "domain": "cert_expiry", - "name": "Cert expiry", - "documentation": "https://www.home-assistant.io/components/cert_expiry", - "requirements": [], - "config_flow": true, - "dependencies": [], - "codeowners": [] + "domain": "cert_expiry", + "name": "Cert expiry", + "documentation": "https://www.home-assistant.io/components/cert_expiry", + "requirements": [], + "config_flow": true, + "dependencies": [], + "codeowners": [], } diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index b5587451acf4c4..762159a3fcf26e 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -9,11 +9,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_NAME, - CONF_HOST, - CONF_PORT, -) +from homeassistant.const import CONF_NAME, CONF_HOST, CONF_PORT from homeassistant.helpers.entity import Entity from .const import DOMAIN, DEFAULT_NAME, DEFAULT_PORT diff --git a/homeassistant/components/cert_expiry/strings.json b/homeassistant/components/cert_expiry/strings.json index 10943deeb22c56..c57f58b978c341 100644 --- a/homeassistant/components/cert_expiry/strings.json +++ b/homeassistant/components/cert_expiry/strings.json @@ -2,20 +2,20 @@ "config": { "title": "Certificate Expiry", "step": { - "user": { - "title": "Define the certificate to test", - "data": { - "name": "The name of the certificate", - "host": "The hostname of the certificate", - "port": "The port of the certificate" + "user": { + "title": "Define the certificate to test", + "data": { + "name": "The name of the certificate", + "host": "The hostname of the certificate", + "port": "The port of the certificate", + }, } - } }, "error": { - "host_port_exists": "This host and port combination is already configured" + "host_port_exists": "This host and port combination is already configured" }, "abort": { - "host_port_exists": "This host and port combination is already configured" - } + "host_port_exists": "This host and port combination is already configured" + }, } } From 3973724110340a223c7a8bea19a5569f9f6f1179 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Thu, 1 Aug 2019 12:58:35 +0200 Subject: [PATCH 05/15] Black on json files is a bad idea --- homeassistant/components/cert_expiry/manifest.json | 2 +- homeassistant/components/cert_expiry/strings.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/cert_expiry/manifest.json b/homeassistant/components/cert_expiry/manifest.json index 771e20e9a5301a..ebef1a932296d4 100644 --- a/homeassistant/components/cert_expiry/manifest.json +++ b/homeassistant/components/cert_expiry/manifest.json @@ -5,5 +5,5 @@ "requirements": [], "config_flow": true, "dependencies": [], - "codeowners": [], + "codeowners": [] } diff --git a/homeassistant/components/cert_expiry/strings.json b/homeassistant/components/cert_expiry/strings.json index c57f58b978c341..2e822a31567385 100644 --- a/homeassistant/components/cert_expiry/strings.json +++ b/homeassistant/components/cert_expiry/strings.json @@ -7,8 +7,8 @@ "data": { "name": "The name of the certificate", "host": "The hostname of the certificate", - "port": "The port of the certificate", - }, + "port": "The port of the certificate" + } } }, "error": { @@ -16,6 +16,6 @@ }, "abort": { "host_port_exists": "This host and port combination is already configured" - }, + } } } From 8aeffb51c4cddfd4aa94ec3d0f6ae671db1cfed8 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Mon, 26 Aug 2019 20:49:42 +0200 Subject: [PATCH 06/15] Work on comments --- homeassistant/components/cert_expiry/config_flow.py | 7 +++---- homeassistant/components/cert_expiry/sensor.py | 6 ++---- tests/components/cert_expiry/test_config_flow.py | 5 ++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index 73cf80e9a4abbc..0fd55db0411c96 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -18,8 +18,7 @@ def certexpiry_entries(hass: HomeAssistant): ) -@config_entries.HANDLERS.register(DOMAIN) -class CertexpiryConfigFlow(config_entries.ConfigFlow): +class CertexpiryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -29,9 +28,9 @@ def __init__(self) -> None: """Initialize the config flow.""" self._errors = {} - def _prt_in_configuration_exists(self, host: str, prt: int) -> bool: + def _prt_in_configuration_exists(self, host: str, port: int) -> bool: """Return True if host, port combination exists in configuration.""" - if (host, prt) in certexpiry_entries(self.hass): + if (host, port) in certexpiry_entries(self.hass): return True return False diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index 762159a3fcf26e..c212a1ab2cbef0 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -28,15 +28,13 @@ } ) - -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up certificate expiry sensor.""" hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=config + DOMAIN, context={"source": SOURCE_IMPORT}, data=dict(config) ) ) - return True async def async_setup_entry(hass, entry, async_add_entities): diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py index fa76d296c81141..e26ce7c5a8616c 100644 --- a/tests/components/cert_expiry/test_config_flow.py +++ b/tests/components/cert_expiry/test_config_flow.py @@ -72,8 +72,7 @@ async def test_import(hass): async def test_abort_if_already_setup(hass): - """Test we abort if Daikin is already setup.""" - pass + """Test we abort if the cert is already setup.""" flow = init_config_flow(hass) MockConfigEntry( domain="cert_expiry", @@ -87,7 +86,7 @@ async def test_abort_if_already_setup(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "host_port_exists" - # Should same HOST and PORT (default) + # Should be the same HOST and PORT (default) result = await flow.async_step_user( {CONF_HOST: HOST, CONF_NAME: NAME, CONF_PORT: DEFAULT_PORT} ) From 927bf2bb4fe3e0fd2ab69908b8286b0977ad7c2b Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Mon, 26 Aug 2019 20:55:49 +0200 Subject: [PATCH 07/15] Forgot the lint --- homeassistant/components/cert_expiry/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index c212a1ab2cbef0..a2b299dd26d3cd 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -28,6 +28,7 @@ } ) + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up certificate expiry sensor.""" hass.async_create_task( From 97fa14e46d31765749714cd597e499b0b1e675e7 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Tue, 27 Aug 2019 06:59:12 +0200 Subject: [PATCH 08/15] More comment work --- .../components/cert_expiry/config_flow.py | 21 ++++++++----------- .../cert_expiry/test_config_flow.py | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index 0fd55db0411c96..f9f0747d935dff 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -28,8 +28,10 @@ def __init__(self) -> None: """Initialize the config flow.""" self._errors = {} - def _prt_in_configuration_exists(self, host: str, port: int) -> bool: + def _prt_in_configuration_exists(self, user_input) -> bool: """Return True if host, port combination exists in configuration.""" + host = user_input[CONF_HOST] + port = user_input.get(CONF_PORT, DEFAULT_PORT) if (host, port) in certexpiry_entries(self.hass): return True return False @@ -38,10 +40,10 @@ async def async_step_user(self, user_input=None): """Step when user intializes a integration.""" self._errors = {} if user_input is not None: - name = slugify(user_input[CONF_NAME]) - host = user_input[CONF_HOST] - prt = user_input[CONF_PORT] - if not self._prt_in_configuration_exists(host, prt): + if not self._prt_in_configuration_exists(user_input): + name = slugify(user_input[CONF_NAME]) + host = user_input[CONF_HOST] + prt = user_input[CONF_PORT] return self.async_create_entry( title=name, data={CONF_HOST: host, CONF_PORT: prt} ) @@ -69,11 +71,6 @@ async def async_step_import(self, user_input=None): Only host was required in the yaml file all other fields are optional """ - host = user_input[CONF_HOST] - prt = user_input.get(CONF_PORT, DEFAULT_PORT) - name = user_input.get(CONF_NAME, host) - if self._prt_in_configuration_exists(host, prt): + if self._prt_in_configuration_exists(user_input): return self.async_abort(reason="host_port_exists") - return await self.async_step_user( - {CONF_NAME: name, CONF_HOST: host, CONF_PORT: prt} - ) + return await self.async_step_user(user_input) diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py index e26ce7c5a8616c..27223db34192d7 100644 --- a/tests/components/cert_expiry/test_config_flow.py +++ b/tests/components/cert_expiry/test_config_flow.py @@ -47,7 +47,7 @@ async def test_import(hass): assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_PORT] == DEFAULT_PORT - # import with only host + # import with host and name result = await flow.async_step_import({CONF_HOST: HOST, CONF_NAME: NAME}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "cert_expiry_test_1_2_3" From 2b3d933038324781f938ec555cac9b5a75d570a7 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Tue, 27 Aug 2019 09:50:40 +0200 Subject: [PATCH 09/15] Correctly set defaults --- homeassistant/components/cert_expiry/config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index f9f0747d935dff..223cc1b043e115 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -41,9 +41,9 @@ async def async_step_user(self, user_input=None): self._errors = {} if user_input is not None: if not self._prt_in_configuration_exists(user_input): - name = slugify(user_input[CONF_NAME]) host = user_input[CONF_HOST] - prt = user_input[CONF_PORT] + name = slugify(user_input.get(CONF_NAME, DEFAULT_NAME)) + prt = user_input.get(CONF_PORT, DEFAULT_PORT) return self.async_create_entry( title=name, data={CONF_HOST: host, CONF_PORT: prt} ) From 88bc4738443d2fa01757ace597491b9027ff66ff Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 28 Aug 2019 10:49:45 +0200 Subject: [PATCH 10/15] More comments --- .../cert_expiry/.translations/en.json | 5 +- .../components/cert_expiry/config_flow.py | 42 +++++++++++---- .../components/cert_expiry/strings.json | 5 +- .../cert_expiry/test_config_flow.py | 51 ++++++++++++++++--- 4 files changed, 84 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/cert_expiry/.translations/en.json b/homeassistant/components/cert_expiry/.translations/en.json index 8e32d11b0e14f6..b6aa1cefb02aed 100644 --- a/homeassistant/components/cert_expiry/.translations/en.json +++ b/homeassistant/components/cert_expiry/.translations/en.json @@ -4,7 +4,10 @@ "host_port_exists": "This host and port combination is already configured" }, "error": { - "host_port_exists": "This host and port combination is already configured" + "certificate_fetch_failed": "Can not fetch certificate from this host and port combination", + "connection_timeout": "Timeout whemn connecting to this host", + "host_port_exists": "This host and port combination is already configured", + "resolve_failed": "This host can not be resolved" }, "step": { "user": { diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index 223cc1b043e115..775ec60dae0c13 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -1,4 +1,6 @@ """Config flow for the Cert Expiry platform.""" +import socket +import ssl import voluptuous as vol from homeassistant import config_entries @@ -7,6 +9,7 @@ from homeassistant.util import slugify from .const import DOMAIN, DEFAULT_PORT, DEFAULT_NAME +from .sensor import TIMEOUT @callback @@ -36,18 +39,37 @@ def _prt_in_configuration_exists(self, user_input) -> bool: return True return False + def _test_connection(self, user_input=None): + """Test connection to the server and try to get the certtificate.""" + try: + address = (user_input[CONF_HOST], user_input.get(CONF_PORT, DEFAULT_PORT)) + with socket.create_connection(address, timeout=TIMEOUT) as sock: + ctx = ssl.create_default_context() + with ctx.wrap_socket(sock, server_hostname=address[0]): + return True + except socket.gaierror: + self._errors[CONF_HOST] = 'resolve_failed' + except socket.timeout: + self._errors[CONF_HOST] = 'connection_timeout' + except OSError: + self._errors[CONF_HOST] = 'certificate_fetch_failed' + return False + async def async_step_user(self, user_input=None): """Step when user intializes a integration.""" self._errors = {} if user_input is not None: - if not self._prt_in_configuration_exists(user_input): - host = user_input[CONF_HOST] - name = slugify(user_input.get(CONF_NAME, DEFAULT_NAME)) - prt = user_input.get(CONF_PORT, DEFAULT_PORT) - return self.async_create_entry( - title=name, data={CONF_HOST: host, CONF_PORT: prt} - ) - self._errors[CONF_HOST] = "host_port_exists" + # set some defaults in case we need to return to the form + if self._prt_in_configuration_exists(user_input): + self._errors[CONF_HOST] = "host_port_exists" + else: + if self._test_connection(user_input): + host = user_input[CONF_HOST] + name = slugify(user_input.get(CONF_NAME, DEFAULT_NAME)) + prt = user_input.get(CONF_PORT, DEFAULT_PORT) + return self.async_create_entry( + title=name, data={CONF_HOST: host, CONF_PORT: prt} + ) else: user_input = {} user_input[CONF_NAME] = DEFAULT_NAME @@ -58,9 +80,9 @@ async def async_step_user(self, user_input=None): step_id="user", data_schema=vol.Schema( { - vol.Required(CONF_NAME, default=user_input[CONF_NAME]): str, + vol.Required(CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME)): str, vol.Required(CONF_HOST, default=user_input[CONF_HOST]): str, - vol.Required(CONF_PORT, default=user_input[CONF_PORT]): int, + vol.Required(CONF_PORT, default=user_input.get(CONF_PORT, DEFAULT_PORT)): int, } ), errors=self._errors, diff --git a/homeassistant/components/cert_expiry/strings.json b/homeassistant/components/cert_expiry/strings.json index 2e822a31567385..8943643e8b392e 100644 --- a/homeassistant/components/cert_expiry/strings.json +++ b/homeassistant/components/cert_expiry/strings.json @@ -12,7 +12,10 @@ } }, "error": { - "host_port_exists": "This host and port combination is already configured" + "host_port_exists": "This host and port combination is already configured", + "resolve_failed": "This host can not be resolved", + "connection_timeout": "Timeout whemn connecting to this host", + "certificate_fetch_failed": "Can not fetch certificate from this host and port combination" }, "abort": { "host_port_exists": "This host and port combination is already configured" diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py index 27223db34192d7..775faee91d45d5 100644 --- a/tests/components/cert_expiry/test_config_flow.py +++ b/tests/components/cert_expiry/test_config_flow.py @@ -1,4 +1,8 @@ """Tests for the Cert Expiry config flow.""" +import pytest +import socket +from unittest.mock import patch + from homeassistant import data_entry_flow from homeassistant.components.cert_expiry import config_flow from homeassistant.components.cert_expiry.const import DEFAULT_PORT @@ -7,8 +11,15 @@ from tests.common import MockConfigEntry NAME = "Cert Expiry test 1 2 3" -PORT = 445 -HOST = "google.com" +PORT = 443 +HOST = "example.com" + + +@pytest.fixture(name="test_connect") +def mock_controller(): + """Mock a successfull _prt_in_configuration_exists.""" + with patch("homeassistant.components.cert_expiry.config_flow.CertexpiryConfigFlow._test_connection", return_value=True): + yield def init_config_flow(hass): @@ -18,7 +29,7 @@ def init_config_flow(hass): return flow -async def test_user(hass): +async def test_user(hass, test_connect): """Test user config.""" flow = init_config_flow(hass) @@ -36,14 +47,14 @@ async def test_user(hass): assert result["data"][CONF_PORT] == PORT -async def test_import(hass): +async def test_import(hass, test_connect): """Test import step.""" flow = init_config_flow(hass) # import with only host result = await flow.async_step_import({CONF_HOST: HOST}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "google_com" + assert result["title"] == "ssl_certificate_expiry" assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_PORT] == DEFAULT_PORT @@ -57,7 +68,7 @@ async def test_import(hass): # improt with host and port result = await flow.async_step_import({CONF_HOST: HOST, CONF_PORT: PORT}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "google_com" + assert result["title"] == "ssl_certificate_expiry" assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_PORT] == PORT @@ -71,7 +82,7 @@ async def test_import(hass): assert result["data"][CONF_PORT] == PORT -async def test_abort_if_already_setup(hass): +async def test_abort_if_already_setup(hass, test_connect): """Test we abort if the cert is already setup.""" flow = init_config_flow(hass) MockConfigEntry( @@ -101,3 +112,29 @@ async def test_abort_if_already_setup(hass): assert result["title"] == "cert_expiry_test_1_2_3" assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_PORT] == 888 + + +async def test_abort_on_socket_failed(hass): + """Test we abort of we have errors during socket creation.""" + flow = init_config_flow(hass) + + with patch("socket.create_connection", side_effect=socket.gaierror()): + result = await flow.async_step_user( + {CONF_HOST: HOST} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_HOST: "resolve_failed"} + + with patch("socket.create_connection", side_effect=socket.timeout()): + result = await flow.async_step_user( + {CONF_HOST: HOST} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_HOST: "connection_timeout"} + + with patch("socket.create_connection", side_effect=OSError()): + result = await flow.async_step_user( + {CONF_HOST: HOST} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_HOST: "certificate_fetch_failed"} From bdf36c2b5d38699922a66bf3ce07530bfae307c6 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 28 Aug 2019 10:53:16 +0200 Subject: [PATCH 11/15] Add codeowner --- CODEOWNERS | 1 + homeassistant/components/cert_expiry/manifest.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 3ede39518c1874..d51031486ef7cc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -46,6 +46,7 @@ homeassistant/components/broadlink/* @danielhiversen homeassistant/components/brunt/* @eavanvalkenburg homeassistant/components/bt_smarthub/* @jxwolstenholme homeassistant/components/buienradar/* @mjj4791 @ties +homeassistant/components/cert_expiry/* @cereal2nd homeassistant/components/cisco_ios/* @fbradyirl homeassistant/components/cisco_mobility_express/* @fbradyirl homeassistant/components/cisco_webex_teams/* @fbradyirl diff --git a/homeassistant/components/cert_expiry/manifest.json b/homeassistant/components/cert_expiry/manifest.json index ebef1a932296d4..781f27afb5f115 100644 --- a/homeassistant/components/cert_expiry/manifest.json +++ b/homeassistant/components/cert_expiry/manifest.json @@ -5,5 +5,5 @@ "requirements": [], "config_flow": true, "dependencies": [], - "codeowners": [] + "codeowners": ["@cereal2nd"] } From e69705140e3ef7a2d83ddb7b428d9b780cc81fc6 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 28 Aug 2019 11:12:44 +0200 Subject: [PATCH 12/15] Fix black --- .../components/cert_expiry/config_flow.py | 14 +++++++++----- .../components/cert_expiry/test_config_flow.py | 17 +++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index 775ec60dae0c13..334a561c7f369a 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -48,11 +48,11 @@ def _test_connection(self, user_input=None): with ctx.wrap_socket(sock, server_hostname=address[0]): return True except socket.gaierror: - self._errors[CONF_HOST] = 'resolve_failed' + self._errors[CONF_HOST] = "resolve_failed" except socket.timeout: - self._errors[CONF_HOST] = 'connection_timeout' + self._errors[CONF_HOST] = "connection_timeout" except OSError: - self._errors[CONF_HOST] = 'certificate_fetch_failed' + self._errors[CONF_HOST] = "certificate_fetch_failed" return False async def async_step_user(self, user_input=None): @@ -80,9 +80,13 @@ async def async_step_user(self, user_input=None): step_id="user", data_schema=vol.Schema( { - vol.Required(CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME)): str, + vol.Required( + CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME) + ): str, vol.Required(CONF_HOST, default=user_input[CONF_HOST]): str, - vol.Required(CONF_PORT, default=user_input.get(CONF_PORT, DEFAULT_PORT)): int, + vol.Required( + CONF_PORT, default=user_input.get(CONF_PORT, DEFAULT_PORT) + ): int, } ), errors=self._errors, diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py index 775faee91d45d5..f8c99496a563eb 100644 --- a/tests/components/cert_expiry/test_config_flow.py +++ b/tests/components/cert_expiry/test_config_flow.py @@ -18,7 +18,10 @@ @pytest.fixture(name="test_connect") def mock_controller(): """Mock a successfull _prt_in_configuration_exists.""" - with patch("homeassistant.components.cert_expiry.config_flow.CertexpiryConfigFlow._test_connection", return_value=True): + with patch( + "homeassistant.components.cert_expiry.config_flow.CertexpiryConfigFlow._test_connection", + return_value=True, + ): yield @@ -119,22 +122,16 @@ async def test_abort_on_socket_failed(hass): flow = init_config_flow(hass) with patch("socket.create_connection", side_effect=socket.gaierror()): - result = await flow.async_step_user( - {CONF_HOST: HOST} - ) + result = await flow.async_step_user({CONF_HOST: HOST}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {CONF_HOST: "resolve_failed"} with patch("socket.create_connection", side_effect=socket.timeout()): - result = await flow.async_step_user( - {CONF_HOST: HOST} - ) + result = await flow.async_step_user({CONF_HOST: HOST}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {CONF_HOST: "connection_timeout"} with patch("socket.create_connection", side_effect=OSError()): - result = await flow.async_step_user( - {CONF_HOST: HOST} - ) + result = await flow.async_step_user({CONF_HOST: HOST}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {CONF_HOST: "certificate_fetch_failed"} From ad4fa6e454f967e7fd4c5bac828e8177e68626fb Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 28 Aug 2019 15:35:41 +0200 Subject: [PATCH 13/15] More comments implemented --- .../components/cert_expiry/__init__.py | 15 +++------------ .../components/cert_expiry/config_flow.py | 10 +++------- homeassistant/components/cert_expiry/const.py | 1 + .../components/cert_expiry/helper.py | 19 +++++++++++++++++++ .../components/cert_expiry/sensor.py | 10 ++-------- 5 files changed, 28 insertions(+), 27 deletions(-) create mode 100644 homeassistant/components/cert_expiry/helper.py diff --git a/homeassistant/components/cert_expiry/__init__.py b/homeassistant/components/cert_expiry/__init__.py index 10a2133cbce4a0..ab68d5ba08bc43 100644 --- a/homeassistant/components/cert_expiry/__init__.py +++ b/homeassistant/components/cert_expiry/__init__.py @@ -4,8 +4,6 @@ from homeassistant.core import callback from homeassistant.helpers.typing import HomeAssistantType -from .const import DOMAIN - async def async_setup(hass, config): """Platform setup, do nothing.""" @@ -14,20 +12,13 @@ async def async_setup(hass, config): async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Load the saved entities.""" - hass.data.setdefault(DOMAIN, {}) - - # store the info for later - hass.data[DOMAIN][entry.entry_id] = entry @callback def async_start(_): """Load the entry after the start event.""" - for eid in hass.data[DOMAIN]: - entry = hass.data[DOMAIN][eid] - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "sensor") - ) - hass.data[DOMAIN] = {} + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "sensor") + ) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_start) diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index 334a561c7f369a..dd3463fff95c4e 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -1,6 +1,5 @@ """Config flow for the Cert Expiry platform.""" import socket -import ssl import voluptuous as vol from homeassistant import config_entries @@ -9,7 +8,7 @@ from homeassistant.util import slugify from .const import DOMAIN, DEFAULT_PORT, DEFAULT_NAME -from .sensor import TIMEOUT +from .helper import get_cert @callback @@ -42,11 +41,8 @@ def _prt_in_configuration_exists(self, user_input) -> bool: def _test_connection(self, user_input=None): """Test connection to the server and try to get the certtificate.""" try: - address = (user_input[CONF_HOST], user_input.get(CONF_PORT, DEFAULT_PORT)) - with socket.create_connection(address, timeout=TIMEOUT) as sock: - ctx = ssl.create_default_context() - with ctx.wrap_socket(sock, server_hostname=address[0]): - return True + get_cert(user_input[CONF_HOST], user_input.get(CONF_PORT, DEFAULT_PORT)) + return True except socket.gaierror: self._errors[CONF_HOST] = "resolve_failed" except socket.timeout: diff --git a/homeassistant/components/cert_expiry/const.py b/homeassistant/components/cert_expiry/const.py index b5e714f0502970..4129781f2a0fb2 100644 --- a/homeassistant/components/cert_expiry/const.py +++ b/homeassistant/components/cert_expiry/const.py @@ -3,3 +3,4 @@ DOMAIN = "cert_expiry" DEFAULT_NAME = "SSL Certificate Expiry" DEFAULT_PORT = 443 +TIMEOUT = 10.0 diff --git a/homeassistant/components/cert_expiry/helper.py b/homeassistant/components/cert_expiry/helper.py new file mode 100644 index 00000000000000..2b53947265f565 --- /dev/null +++ b/homeassistant/components/cert_expiry/helper.py @@ -0,0 +1,19 @@ +"""Helper functions for the Cert Expiry platform.""" +import socket +import ssl + +from .const import TIMEOUT + + +def get_cert(host, port): + """Get the ssl certificate for the host and port combination.""" + ctx = ssl.create_default_context() + try: + address = (host, port) + with socket.create_connection(address, timeout=TIMEOUT) as sock: + with ctx.wrap_socket(sock, server_hostname=address[0]) as ssock: + cert = ssock.getpeercert() + return cert + except Exception as excep: + raise excep + return None diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index a2b299dd26d3cd..fccfb295c0fff2 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -13,13 +13,12 @@ from homeassistant.helpers.entity import Entity from .const import DOMAIN, DEFAULT_NAME, DEFAULT_PORT +from .helper import get_cert _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(hours=12) -TIMEOUT = 10.0 - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -85,13 +84,8 @@ def available(self): def update(self): """Fetch the certificate information.""" - ctx = ssl.create_default_context() try: - address = (self.server_name, self.server_port) - with socket.create_connection(address, timeout=TIMEOUT) as sock: - with ctx.wrap_socket(sock, server_hostname=address[0]) as ssock: - cert = ssock.getpeercert() - + cert = get_cert(self.server_name, self.server_port) except socket.gaierror: _LOGGER.error("Cannot resolve hostname: %s", self.server_name) self._available = False From ce7c284d46d11d623401b1dbca0fc3bc3d7ad593 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 28 Aug 2019 15:56:07 +0200 Subject: [PATCH 14/15] Removed the catch --- homeassistant/components/cert_expiry/helper.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/cert_expiry/helper.py b/homeassistant/components/cert_expiry/helper.py index 2b53947265f565..9c10887293adbd 100644 --- a/homeassistant/components/cert_expiry/helper.py +++ b/homeassistant/components/cert_expiry/helper.py @@ -8,12 +8,8 @@ def get_cert(host, port): """Get the ssl certificate for the host and port combination.""" ctx = ssl.create_default_context() - try: - address = (host, port) - with socket.create_connection(address, timeout=TIMEOUT) as sock: - with ctx.wrap_socket(sock, server_hostname=address[0]) as ssock: - cert = ssock.getpeercert() - return cert - except Exception as excep: - raise excep - return None + address = (host, port) + with socket.create_connection(address, timeout=TIMEOUT) as sock: + with ctx.wrap_socket(sock, server_hostname=address[0]) as ssock: + cert = ssock.getpeercert() + return cert From 7e82acd94340e9f2c551f8a5a650c184ff22779e Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 28 Aug 2019 17:02:59 +0200 Subject: [PATCH 15/15] Add helper.py from cert_expiry to .coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 1d861d69c1dfe1..02d59b55f5f2a1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -93,6 +93,7 @@ omit = homeassistant/components/canary/camera.py homeassistant/components/cast/* homeassistant/components/cert_expiry/sensor.py + homeassistant/components/cert_expiry/helper.py homeassistant/components/channels/media_player.py homeassistant/components/cisco_ios/device_tracker.py homeassistant/components/cisco_mobility_express/device_tracker.py