Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for reauth flow #301

Merged
merged 3 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 41 additions & 45 deletions custom_components/opensprinkler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
from aiohttp.client_exceptions import InvalidURL
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_NAME,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_URL,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity_platform import async_get_platforms
Expand All @@ -32,6 +32,7 @@
from .const import (
CONF_INDEX,
CONF_RUN_SECONDS,
DEFAULT_NAME,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
SCHEMA_SERVICE_PAUSE_STATIONS,
Expand Down Expand Up @@ -69,53 +70,48 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
url = entry.data.get(CONF_URL)
password = entry.data.get(CONF_PASSWORD)
verify_ssl = entry.data.get(CONF_VERIFY_SSL)
try:
opts = {"session": async_get_clientsession(hass), "verify_ssl": verify_ssl}
controller = OpenSprinkler(url, password, opts)
controller.refresh_on_update = False

async def async_update_data():
"""Fetch data from OpenSprinkler."""
_LOGGER.debug("refreshing data")
async with async_timeout.timeout(TIMEOUT):
try:
await controller.refresh()
except OpenSprinklerAuthError as e:
# wrong password, tell user to re-enter the password
raise ConfigEntryAuthFailed from e
except (
InvalidURL,
OpenSprinklerConnectionError,
) as e:
raise UpdateFailed from e

if not controller._state:
raise UpdateFailed("Error fetching OpenSprinkler state")

return controller._state

scan_interval = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="OpenSprinkler resource status",
update_method=async_update_data,
update_interval=timedelta(seconds=scan_interval),
)
opts = {"session": async_get_clientsession(hass), "verify_ssl": verify_ssl}

# initial load before loading platforms
await coordinator.async_refresh()
if not coordinator.last_update_success:
raise ConfigEntryNotReady
controller = OpenSprinkler(url, password, opts)
controller.refresh_on_update = False

hass.data[DOMAIN][entry.entry_id] = {
"coordinator": coordinator,
"controller": controller,
}
async def async_update_data():
"""Fetch data from OpenSprinkler."""
_LOGGER.debug("refreshing data")
async with async_timeout.timeout(TIMEOUT):
try:
await controller.refresh()
except OpenSprinklerAuthError as e:
# wrong password, tell user to re-enter the password
_LOGGER.debug(f"auth failure: {e}")
raise ConfigEntryAuthFailed from e
except (
InvalidURL,
OpenSprinklerConnectionError,
) as e:
raise UpdateFailed from e

if not controller._state:
raise UpdateFailed("Error fetching OpenSprinkler state")

return controller._state

scan_interval = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=f"{entry.data.get(CONF_NAME, DEFAULT_NAME)} resource status",
update_method=async_update_data,
update_interval=timedelta(seconds=scan_interval),
)

# initial load before loading platforms
await coordinator.async_config_entry_first_refresh()

except (OpenSprinklerAuthError, OpenSprinklerConnectionError) as exc:
_LOGGER.error("Unable to connect to OpenSprinkler controller: %s", str(exc))
raise ConfigEntryNotReady
hass.data[DOMAIN][entry.entry_id] = {
"coordinator": coordinator,
"controller": controller,
}

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

Expand Down
48 changes: 48 additions & 0 deletions custom_components/opensprinkler/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Config flow for OpenSprinkler integration."""

import logging
from typing import Any

import voluptuous as vol
from aiohttp.client_exceptions import InvalidURL
Expand All @@ -12,6 +13,7 @@
CONF_URL,
CONF_VERIFY_SSL,
)
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import slugify
from pyopensprinkler import Controller as OpenSprinkler
Expand All @@ -30,6 +32,11 @@
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
}
)
REAUTH_SCHEMA = vol.Schema(
{
vol.Required(CONF_PASSWORD): str,
}
)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
Expand Down Expand Up @@ -93,6 +100,47 @@ async def async_step_import(self, user_input):
"""Handle import."""
return await self.async_step_user(user_input)

async def async_step_reauth(self, user_input: dict[str, Any]) -> FlowResult:
"""Handle reauthorization."""

existing_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
errors = {}
if user_input is not None:
password = user_input[CONF_PASSWORD]

url = existing_entry.data[CONF_URL]
verify_ssl = existing_entry.data[CONF_VERIFY_SSL]
opts = {
"session": async_get_clientsession(self.hass),
"verify_ssl": verify_ssl,
}

try:
controller = OpenSprinkler(url, password, opts)
await controller.refresh()
except InvalidURL:
errors["base"] = "invalid_url"
except OpenSprinklerConnectionError:
errors["base"] = "cannot_connect"
except OpenSprinklerAuthError:
errors["base"] = "invalid_auth"
else:
self.hass.config_entries.async_update_entry(
existing_entry,
data={
**existing_entry.data,
CONF_PASSWORD: password,
},
)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")

return self.async_show_form(
step_id="reauth", data_schema=REAUTH_SCHEMA, errors=errors
)


class MacAddressRequiredError(Exception):
"""Error to mac address required."""
10 changes: 9 additions & 1 deletion custom_components/opensprinkler/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"title": "OpenSprinkler",
"config": {
"abort": {
"already_configured": "Device is already configured"
"already_configured": "Device is already configured",
"reauth_successful": "Re-authentication was successful"
},
"error": {
"invalid_url": "URL is malformed (example: http://192.168.0.1)",
Expand All @@ -21,6 +22,13 @@
"name": "Controller Name"
},
"title": "Connect to the OpenSprinkler controller"
},
"reauth": {
"data": {
"url": "URL",
"password": "Password"
},
"title": "Enter new password"
}
}
},
Expand Down
10 changes: 9 additions & 1 deletion custom_components/opensprinkler/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"title": "OpenSprinkler",
"config": {
"abort": {
"already_configured": "Gerät ist bereits konfiguriert"
"already_configured": "Gerät ist bereits konfiguriert",
"reauth_successful": "Die erneute Authentifizierung war erfolgreich"
},
"error": {
"invalid_url": "URL ist ungültig (Beispiel: http://192.168.0.1)",
Expand All @@ -20,6 +21,13 @@
"name": "Controller Name"
},
"title": "Stellen Sie eine Verbindung zum OpenSprinkler-Controller her"
},
"reauth": {
"data": {
"url": "URL",
"password": "Passwort"
},
"title": "Neues Passwort eingeben"
}
}
},
Expand Down
12 changes: 10 additions & 2 deletions custom_components/opensprinkler/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"title": "OpenSprinkler",
"config": {
"abort": {
"already_configured": "Device is already configured"
"already_configured": "Device is already configured",
"reauth_successful": "Re-authentication was successful"
},
"error": {
"invalid_url": "URL is malformed (example: http://192.168.0.1)",
Expand All @@ -21,6 +22,13 @@
"name": "Controller Name"
},
"title": "Connect to the OpenSprinkler controller"
},
"reauth": {
"data": {
"url": "URL",
"password": "Password"
},
"title": "Enter new password"
}
}
},
Expand Down Expand Up @@ -91,7 +99,7 @@
},
"pause_duration": {
"name": "Pause duration",
"description": "Pause duration in seconds."
"description": "Duration to pause in seconds."
}
}
},
Expand Down
10 changes: 9 additions & 1 deletion custom_components/opensprinkler/translations/sk.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"title": "OpenSprinkler",
"config": {
"abort": {
"already_configured": "Zariadenie je už nakonfigurované"
"already_configured": "Zariadenie je už nakonfigurované",
"reauth_successful": "Opätovné overenie bolo úspešné"
},
"error": {
"invalid_url": "Adresa URL má nesprávny tvar (príklad: http://192.168.0.1)",
Expand All @@ -21,6 +22,13 @@
"name": "Názov ovládača"
},
"title": "Pripojte sa k ovládaču OpenSprinkler"
},
"reauth": {
"data": {
"url": "URL",
"password": "Heslo"
},
"title": "Zadajte nové heslo"
}
}
},
Expand Down
Loading