mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
SmartThings continue correct config flow after external auth (#34862)
This commit is contained in:
parent
97c82089b4
commit
6ae7f31947
@ -37,6 +37,7 @@ from .const import (
|
|||||||
TOKEN_REFRESH_INTERVAL,
|
TOKEN_REFRESH_INTERVAL,
|
||||||
)
|
)
|
||||||
from .smartapp import (
|
from .smartapp import (
|
||||||
|
format_unique_id,
|
||||||
setup_smartapp,
|
setup_smartapp,
|
||||||
setup_smartapp_endpoint,
|
setup_smartapp_endpoint,
|
||||||
smartapp_sync_subscriptions,
|
smartapp_sync_subscriptions,
|
||||||
@ -76,6 +77,15 @@ async def async_migrate_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
|||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||||
"""Initialize config entry which represents an installed SmartApp."""
|
"""Initialize config entry which represents an installed SmartApp."""
|
||||||
|
# For backwards compat
|
||||||
|
if entry.unique_id is None:
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry,
|
||||||
|
unique_id=format_unique_id(
|
||||||
|
entry.data[CONF_APP_ID], entry.data[CONF_LOCATION_ID]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if not validate_webhook_requirements(hass):
|
if not validate_webhook_requirements(hass):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"The 'base_url' of the 'http' integration must be configured and start with 'https://'"
|
"The 'base_url' of the 'http' integration must be configured and start with 'https://'"
|
||||||
|
@ -7,7 +7,7 @@ from pysmartthings.installedapp import format_install_url
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_FORBIDDEN
|
from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_FORBIDDEN, HTTP_UNAUTHORIZED
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
@ -26,6 +26,7 @@ from .const import (
|
|||||||
from .smartapp import (
|
from .smartapp import (
|
||||||
create_app,
|
create_app,
|
||||||
find_app,
|
find_app,
|
||||||
|
format_unique_id,
|
||||||
get_webhook_url,
|
get_webhook_url,
|
||||||
setup_smartapp,
|
setup_smartapp,
|
||||||
setup_smartapp_endpoint,
|
setup_smartapp_endpoint,
|
||||||
@ -138,7 +139,7 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
return self._show_step_pat(errors)
|
return self._show_step_pat(errors)
|
||||||
except ClientResponseError as ex:
|
except ClientResponseError as ex:
|
||||||
if ex.status == 401:
|
if ex.status == HTTP_UNAUTHORIZED:
|
||||||
errors[CONF_ACCESS_TOKEN] = "token_unauthorized"
|
errors[CONF_ACCESS_TOKEN] = "token_unauthorized"
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unauthorized error received setting up SmartApp", exc_info=True
|
"Unauthorized error received setting up SmartApp", exc_info=True
|
||||||
@ -183,6 +184,7 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.location_id = user_input[CONF_LOCATION_ID]
|
self.location_id = user_input[CONF_LOCATION_ID]
|
||||||
|
await self.async_set_unique_id(format_unique_id(self.app_id, self.location_id))
|
||||||
return await self.async_step_authorize()
|
return await self.async_step_authorize()
|
||||||
|
|
||||||
async def async_step_authorize(self, user_input=None):
|
async def async_step_authorize(self, user_input=None):
|
||||||
|
@ -39,7 +39,6 @@ from .const import (
|
|||||||
CONF_CLOUDHOOK_URL,
|
CONF_CLOUDHOOK_URL,
|
||||||
CONF_INSTALLED_APP_ID,
|
CONF_INSTALLED_APP_ID,
|
||||||
CONF_INSTANCE_ID,
|
CONF_INSTANCE_ID,
|
||||||
CONF_LOCATION_ID,
|
|
||||||
CONF_REFRESH_TOKEN,
|
CONF_REFRESH_TOKEN,
|
||||||
DATA_BROKERS,
|
DATA_BROKERS,
|
||||||
DATA_MANAGER,
|
DATA_MANAGER,
|
||||||
@ -53,6 +52,11 @@ from .const import (
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def format_unique_id(app_id: str, location_id: str) -> str:
|
||||||
|
"""Format the unique id for a config entry."""
|
||||||
|
return f"{app_id}_{location_id}"
|
||||||
|
|
||||||
|
|
||||||
async def find_app(hass: HomeAssistantType, api):
|
async def find_app(hass: HomeAssistantType, api):
|
||||||
"""Find an existing SmartApp for this installation of hass."""
|
"""Find an existing SmartApp for this installation of hass."""
|
||||||
apps = await api.apps()
|
apps = await api.apps()
|
||||||
@ -366,13 +370,20 @@ async def smartapp_sync_subscriptions(
|
|||||||
_LOGGER.debug("Subscriptions for app '%s' are up-to-date", installed_app_id)
|
_LOGGER.debug("Subscriptions for app '%s' are up-to-date", installed_app_id)
|
||||||
|
|
||||||
|
|
||||||
async def smartapp_install(hass: HomeAssistantType, req, resp, app):
|
async def _continue_flow(
|
||||||
"""Handle a SmartApp installation and continue the config flow."""
|
hass: HomeAssistantType,
|
||||||
|
app_id: str,
|
||||||
|
location_id: str,
|
||||||
|
installed_app_id: str,
|
||||||
|
refresh_token: str,
|
||||||
|
):
|
||||||
|
"""Continue a config flow if one is in progress for the specific installed app."""
|
||||||
|
unique_id = format_unique_id(app_id, location_id)
|
||||||
flow = next(
|
flow = next(
|
||||||
(
|
(
|
||||||
flow
|
flow
|
||||||
for flow in hass.config_entries.flow.async_progress()
|
for flow in hass.config_entries.flow.async_progress()
|
||||||
if flow["handler"] == DOMAIN
|
if flow["handler"] == DOMAIN and flow["context"]["unique_id"] == unique_id
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@ -380,18 +391,23 @@ async def smartapp_install(hass: HomeAssistantType, req, resp, app):
|
|||||||
await hass.config_entries.flow.async_configure(
|
await hass.config_entries.flow.async_configure(
|
||||||
flow["flow_id"],
|
flow["flow_id"],
|
||||||
{
|
{
|
||||||
CONF_INSTALLED_APP_ID: req.installed_app_id,
|
CONF_INSTALLED_APP_ID: installed_app_id,
|
||||||
CONF_LOCATION_ID: req.location_id,
|
CONF_REFRESH_TOKEN: refresh_token,
|
||||||
CONF_REFRESH_TOKEN: req.refresh_token,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Continued config flow '%s' for SmartApp '%s' under parent app '%s'",
|
"Continued config flow '%s' for SmartApp '%s' under parent app '%s'",
|
||||||
flow["flow_id"],
|
flow["flow_id"],
|
||||||
req.installed_app_id,
|
installed_app_id,
|
||||||
app.app_id,
|
app_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def smartapp_install(hass: HomeAssistantType, req, resp, app):
|
||||||
|
"""Handle a SmartApp installation and continue the config flow."""
|
||||||
|
await _continue_flow(
|
||||||
|
hass, app.app_id, req.location_id, req.installed_app_id, req.refresh_token
|
||||||
|
)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Installed SmartApp '%s' under parent app '%s'",
|
"Installed SmartApp '%s' under parent app '%s'",
|
||||||
req.installed_app_id,
|
req.installed_app_id,
|
||||||
@ -420,30 +436,9 @@ async def smartapp_update(hass: HomeAssistantType, req, resp, app):
|
|||||||
app.app_id,
|
app.app_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
flow = next(
|
await _continue_flow(
|
||||||
(
|
hass, app.app_id, req.location_id, req.installed_app_id, req.refresh_token
|
||||||
flow
|
|
||||||
for flow in hass.config_entries.flow.async_progress()
|
|
||||||
if flow["handler"] == DOMAIN
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
if flow is not None:
|
|
||||||
await hass.config_entries.flow.async_configure(
|
|
||||||
flow["flow_id"],
|
|
||||||
{
|
|
||||||
CONF_INSTALLED_APP_ID: req.installed_app_id,
|
|
||||||
CONF_LOCATION_ID: req.location_id,
|
|
||||||
CONF_REFRESH_TOKEN: req.refresh_token,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Continued config flow '%s' for SmartApp '%s' under parent app '%s'",
|
|
||||||
flow["flow_id"],
|
|
||||||
req.installed_app_id,
|
|
||||||
app.app_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Updated SmartApp '%s' under parent app '%s'", req.installed_app_id, app.app_id
|
"Updated SmartApp '%s' under parent app '%s'", req.installed_app_id, app.app_id
|
||||||
)
|
)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,6 @@ from pysmartthings import AppEntity, Capability
|
|||||||
|
|
||||||
from homeassistant.components.smartthings import smartapp
|
from homeassistant.components.smartthings import smartapp
|
||||||
from homeassistant.components.smartthings.const import (
|
from homeassistant.components.smartthings.const import (
|
||||||
CONF_INSTALLED_APP_ID,
|
|
||||||
CONF_LOCATION_ID,
|
|
||||||
CONF_REFRESH_TOKEN,
|
CONF_REFRESH_TOKEN,
|
||||||
DATA_MANAGER,
|
DATA_MANAGER,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -39,36 +37,6 @@ async def test_update_app_updated_needed(hass, app):
|
|||||||
assert mock_app.classifications == app.classifications
|
assert mock_app.classifications == app.classifications
|
||||||
|
|
||||||
|
|
||||||
async def test_smartapp_install_configures_flow(hass):
|
|
||||||
"""Test install event continues an existing flow."""
|
|
||||||
# Arrange
|
|
||||||
flow_id = str(uuid4())
|
|
||||||
flows = [{"flow_id": flow_id, "handler": DOMAIN}]
|
|
||||||
app = Mock()
|
|
||||||
app.app_id = uuid4()
|
|
||||||
request = Mock()
|
|
||||||
request.installed_app_id = str(uuid4())
|
|
||||||
request.auth_token = str(uuid4())
|
|
||||||
request.location_id = str(uuid4())
|
|
||||||
request.refresh_token = str(uuid4())
|
|
||||||
|
|
||||||
# Act
|
|
||||||
with patch.object(
|
|
||||||
hass.config_entries.flow, "async_progress", return_value=flows
|
|
||||||
), patch.object(hass.config_entries.flow, "async_configure") as configure_mock:
|
|
||||||
|
|
||||||
await smartapp.smartapp_install(hass, request, None, app)
|
|
||||||
|
|
||||||
configure_mock.assert_called_once_with(
|
|
||||||
flow_id,
|
|
||||||
{
|
|
||||||
CONF_INSTALLED_APP_ID: request.installed_app_id,
|
|
||||||
CONF_LOCATION_ID: request.location_id,
|
|
||||||
CONF_REFRESH_TOKEN: request.refresh_token,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_smartapp_update_saves_token(
|
async def test_smartapp_update_saves_token(
|
||||||
hass, smartthings_mock, location, device_factory
|
hass, smartthings_mock, location, device_factory
|
||||||
):
|
):
|
||||||
@ -92,36 +60,6 @@ async def test_smartapp_update_saves_token(
|
|||||||
assert entry.data[CONF_REFRESH_TOKEN] == request.refresh_token
|
assert entry.data[CONF_REFRESH_TOKEN] == request.refresh_token
|
||||||
|
|
||||||
|
|
||||||
async def test_smartapp_update_configures_flow(hass):
|
|
||||||
"""Test update event continues an existing flow."""
|
|
||||||
# Arrange
|
|
||||||
flow_id = str(uuid4())
|
|
||||||
flows = [{"flow_id": flow_id, "handler": DOMAIN}]
|
|
||||||
app = Mock()
|
|
||||||
app.app_id = uuid4()
|
|
||||||
request = Mock()
|
|
||||||
request.installed_app_id = str(uuid4())
|
|
||||||
request.auth_token = str(uuid4())
|
|
||||||
request.location_id = str(uuid4())
|
|
||||||
request.refresh_token = str(uuid4())
|
|
||||||
|
|
||||||
# Act
|
|
||||||
with patch.object(
|
|
||||||
hass.config_entries.flow, "async_progress", return_value=flows
|
|
||||||
), patch.object(hass.config_entries.flow, "async_configure") as configure_mock:
|
|
||||||
|
|
||||||
await smartapp.smartapp_update(hass, request, None, app)
|
|
||||||
|
|
||||||
configure_mock.assert_called_once_with(
|
|
||||||
flow_id,
|
|
||||||
{
|
|
||||||
CONF_INSTALLED_APP_ID: request.installed_app_id,
|
|
||||||
CONF_LOCATION_ID: request.location_id,
|
|
||||||
CONF_REFRESH_TOKEN: request.refresh_token,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_smartapp_uninstall(hass, config_entry):
|
async def test_smartapp_uninstall(hass, config_entry):
|
||||||
"""Test the config entry is unloaded when the app is uninstalled."""
|
"""Test the config entry is unloaded when the app is uninstalled."""
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user