Enhance SmartThings component subscription (#21124)

* Move to config v2 to store SmartApp oauth keys

* Add migration functionality.

* Regenerate refresh token on periodic basis

* Fix regenerate and misc. optimizations

* Review feedback

* Subscription sync logic now performs a difference operation

* Removed config entry reloading.
This commit is contained in:
Andrew Sayre 2019-02-22 13:35:12 -06:00 committed by Martin Hjelmare
parent d9712027e8
commit 8b38b82e73
14 changed files with 529 additions and 275 deletions

View file

@ -9,7 +9,8 @@ from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import (
CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, DOMAIN,
APP_OAUTH_CLIENT_NAME, APP_OAUTH_SCOPES, CONF_APP_ID, CONF_INSTALLED_APPS,
CONF_LOCATION_ID, CONF_OAUTH_CLIENT_ID, CONF_OAUTH_CLIENT_SECRET, DOMAIN,
VAL_UID_MATCHER)
from .smartapp import (
create_app, find_app, setup_smartapp, setup_smartapp_endpoint, update_app)
@ -35,7 +36,7 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow):
b) Config entries setup for all installations
"""
VERSION = 1
VERSION = 2
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
def __init__(self):
@ -43,6 +44,8 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow):
self.access_token = None
self.app_id = None
self.api = None
self.oauth_client_secret = None
self.oauth_client_id = None
async def async_step_import(self, user_input=None):
"""Occurs when a previously entry setup fails and is re-initiated."""
@ -50,7 +53,7 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow):
async def async_step_user(self, user_input=None):
"""Get access token and validate it."""
from pysmartthings import APIResponseError, SmartThings
from pysmartthings import APIResponseError, AppOAuth, SmartThings
errors = {}
if not self.hass.config.api.base_url.lower().startswith('https://'):
@ -83,10 +86,18 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow):
if app:
await app.refresh() # load all attributes
await update_app(self.hass, app)
# Get oauth client id/secret by regenerating it
app_oauth = AppOAuth(app.app_id)
app_oauth.client_name = APP_OAUTH_CLIENT_NAME
app_oauth.scope.extend(APP_OAUTH_SCOPES)
client = await self.api.generate_app_oauth(app_oauth)
else:
app = await create_app(self.hass, self.api)
app, client = await create_app(self.hass, self.api)
setup_smartapp(self.hass, app)
self.app_id = app.app_id
self.oauth_client_secret = client.client_secret
self.oauth_client_id = client.client_id
except APIResponseError as ex:
if ex.is_target_error():
errors['base'] = 'webhook_error'
@ -113,19 +124,23 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow):
async def async_step_wait_install(self, user_input=None):
"""Wait for SmartApp installation."""
from pysmartthings import InstalledAppStatus
errors = {}
if user_input is None:
return self._show_step_wait_install(errors)
# Find installed apps that were authorized
installed_apps = [app for app in await self.api.installed_apps(
installed_app_status=InstalledAppStatus.AUTHORIZED)
if app.app_id == self.app_id]
installed_apps = self.hass.data[DOMAIN][CONF_INSTALLED_APPS].copy()
if not installed_apps:
errors['base'] = 'app_not_installed'
return self._show_step_wait_install(errors)
self.hass.data[DOMAIN][CONF_INSTALLED_APPS].clear()
# Enrich the data
for installed_app in installed_apps:
installed_app[CONF_APP_ID] = self.app_id
installed_app[CONF_ACCESS_TOKEN] = self.access_token
installed_app[CONF_OAUTH_CLIENT_ID] = self.oauth_client_id
installed_app[CONF_OAUTH_CLIENT_SECRET] = self.oauth_client_secret
# User may have installed the SmartApp in more than one SmartThings
# location. Config flows are created for the additional installations
@ -133,21 +148,10 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow):
self.hass.async_create_task(
self.hass.config_entries.flow.async_init(
DOMAIN, context={'source': 'install'},
data={
CONF_APP_ID: installed_app.app_id,
CONF_INSTALLED_APP_ID: installed_app.installed_app_id,
CONF_LOCATION_ID: installed_app.location_id,
CONF_ACCESS_TOKEN: self.access_token
}))
data=installed_app))
# return entity for the first one.
installed_app = installed_apps[0]
return await self.async_step_install({
CONF_APP_ID: installed_app.app_id,
CONF_INSTALLED_APP_ID: installed_app.installed_app_id,
CONF_LOCATION_ID: installed_app.location_id,
CONF_ACCESS_TOKEN: self.access_token
})
# Create config entity for the first one.
return await self.async_step_install(installed_apps[0])
def _show_step_user(self, errors):
return self.async_show_form(