Add reauth flow for lyric (#47863)

This commit is contained in:
Aidan Timson 2021-04-17 18:20:16 +01:00 committed by GitHub
parent ad967cfebb
commit 912d5c347c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 4 deletions

View File

@ -6,7 +6,9 @@ from datetime import timedelta
import logging import logging
from typing import Any from typing import Any
from aiohttp.client_exceptions import ClientResponseError
from aiolyric import Lyric from aiolyric import Lyric
from aiolyric.exceptions import LyricAuthenticationException, LyricException
from aiolyric.objects.device import LyricDevice from aiolyric.objects.device import LyricDevice
from aiolyric.objects.location import LyricLocation from aiolyric.objects.location import LyricLocation
import async_timeout import async_timeout
@ -15,6 +17,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import ( from homeassistant.helpers import (
aiohttp_client, aiohttp_client,
config_entry_oauth2_flow, config_entry_oauth2_flow,
@ -29,7 +32,7 @@ from homeassistant.helpers.update_coordinator import (
from .api import ConfigEntryLyricClient, LyricLocalOAuth2Implementation from .api import ConfigEntryLyricClient, LyricLocalOAuth2Implementation
from .config_flow import OAuth2FlowHandler from .config_flow import OAuth2FlowHandler
from .const import DOMAIN, LYRIC_EXCEPTIONS, OAUTH2_AUTHORIZE, OAUTH2_TOKEN from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
@ -94,7 +97,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async with async_timeout.timeout(60): async with async_timeout.timeout(60):
await lyric.get_locations() await lyric.get_locations()
return lyric return lyric
except LYRIC_EXCEPTIONS as exception: except LyricAuthenticationException as exception:
raise ConfigEntryAuthFailed from exception
except (LyricException, ClientResponseError) as exception:
raise UpdateFailed(exception) from exception raise UpdateFailed(exception) from exception
coordinator = DataUpdateCoordinator( coordinator = DataUpdateCoordinator(

View File

@ -1,6 +1,8 @@
"""Config flow for Honeywell Lyric.""" """Config flow for Honeywell Lyric."""
import logging import logging
import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers import config_entry_oauth2_flow
@ -21,3 +23,25 @@ class OAuth2FlowHandler(
def logger(self) -> logging.Logger: def logger(self) -> logging.Logger:
"""Return logger.""" """Return logger."""
return logging.getLogger(__name__) return logging.getLogger(__name__)
async def async_step_reauth(self, user_input=None):
"""Perform reauth upon an API authentication error."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(self, user_input=None):
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
return await self.async_step_user()
async def async_oauth_create_entry(self, data: dict) -> dict:
"""Create an oauth config entry or update existing entry for reauth."""
existing_entry = await self.async_set_unique_id(DOMAIN)
if existing_entry:
self.hass.config_entries.async_update_entry(existing_entry, data=data)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(title="Lyric", data=data)

View File

@ -3,11 +3,16 @@
"step": { "step": {
"pick_implementation": { "pick_implementation": {
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
},
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
"description": "The Lyric integration needs to re-authenticate your account."
} }
}, },
"abort": { "abort": {
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]" "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}, },
"create_entry": { "create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]" "default": "[%key:common::config_flow::create_entry::authenticated%]"

View File

@ -2,7 +2,8 @@
"config": { "config": {
"abort": { "abort": {
"authorize_url_timeout": "Timeout generating authorize URL.", "authorize_url_timeout": "Timeout generating authorize URL.",
"missing_configuration": "The component is not configured. Please follow the documentation." "missing_configuration": "The component is not configured. Please follow the documentation.",
"reauth_successful": "Re-authentication was successful"
}, },
"create_entry": { "create_entry": {
"default": "Successfully authenticated" "default": "Successfully authenticated"
@ -10,6 +11,10 @@
"step": { "step": {
"pick_implementation": { "pick_implementation": {
"title": "Pick Authentication Method" "title": "Pick Authentication Method"
},
"reauth_confirm": {
"description": "The Lyric integration needs to re-authenticate your account.",
"title": "Reauthenticate Integration"
} }
} }
} }

View File

@ -11,6 +11,8 @@ from homeassistant.components.lyric.const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers import config_entry_oauth2_flow
from tests.common import MockConfigEntry
CLIENT_ID = "1234" CLIENT_ID = "1234"
CLIENT_SECRET = "5678" CLIENT_SECRET = "5678"
@ -131,3 +133,69 @@ async def test_abort_if_authorization_timeout(
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "authorize_url_timeout" assert result["reason"] == "authorize_url_timeout"
async def test_reauthentication_flow(
hass, aiohttp_client, aioclient_mock, current_request_with_host
):
"""Test reauthentication flow."""
await setup.async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
CONF_CLIENT_ID: CLIENT_ID,
CONF_CLIENT_SECRET: CLIENT_SECRET,
},
DOMAIN_HTTP: {CONF_BASE_URL: "https://example.com"},
},
)
old_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=DOMAIN,
version=1,
data={"id": "timmo", "auth_implementation": DOMAIN},
)
old_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "reauth"}, data=old_entry.data
)
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
result = await hass.config_entries.flow.async_configure(flows[0]["flow_id"], {})
# pylint: disable=protected-access
state = config_entry_oauth2_flow._encode_jwt(
hass,
{
"flow_id": result["flow_id"],
"redirect_uri": "https://example.com/auth/external/callback",
},
)
client = await aiohttp_client(hass.http.app)
await client.get(f"/auth/external/callback?code=abcd&state={state}")
aioclient_mock.post(
OAUTH2_TOKEN,
json={
"refresh_token": "mock-refresh-token",
"access_token": "mock-access-token",
"type": "Bearer",
"expires_in": 60,
},
)
with patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"):
with patch(
"homeassistant.components.lyric.async_setup_entry", return_value=True
) as mock_setup:
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "reauth_successful"
assert len(mock_setup.mock_calls) == 1