2019-05-01 20:05:40 +00:00
|
|
|
"""Config flow for Ambiclimate."""
|
|
|
|
import logging
|
|
|
|
|
2021-07-20 16:57:40 +00:00
|
|
|
from aiohttp import web
|
2019-05-01 20:05:40 +00:00
|
|
|
import ambiclimate
|
|
|
|
|
|
|
|
from homeassistant import config_entries
|
|
|
|
from homeassistant.components.http import HomeAssistantView
|
2020-05-30 15:27:20 +00:00
|
|
|
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
2019-05-01 20:05:40 +00:00
|
|
|
from homeassistant.core import callback
|
|
|
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
2020-05-08 19:53:28 +00:00
|
|
|
from homeassistant.helpers.network import get_url
|
2019-12-09 10:49:35 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
from .const import (
|
|
|
|
AUTH_CALLBACK_NAME,
|
|
|
|
AUTH_CALLBACK_PATH,
|
|
|
|
DOMAIN,
|
|
|
|
STORAGE_KEY,
|
2019-12-09 10:49:35 +00:00
|
|
|
STORAGE_VERSION,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
DATA_AMBICLIMATE_IMPL = "ambiclimate_flow_implementation"
|
2019-05-01 20:05:40 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def register_flow_implementation(hass, client_id, client_secret):
|
|
|
|
"""Register a ambiclimate implementation.
|
|
|
|
|
|
|
|
client_id: Client id.
|
|
|
|
client_secret: Client secret.
|
|
|
|
"""
|
|
|
|
hass.data.setdefault(DATA_AMBICLIMATE_IMPL, {})
|
|
|
|
|
|
|
|
hass.data[DATA_AMBICLIMATE_IMPL] = {
|
|
|
|
CONF_CLIENT_ID: client_id,
|
|
|
|
CONF_CLIENT_SECRET: client_secret,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-30 21:28:25 +00:00
|
|
|
class AmbiclimateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
2019-05-01 20:05:40 +00:00
|
|
|
"""Handle a config flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
"""Initialize flow."""
|
|
|
|
self._registered_view = False
|
|
|
|
self._oauth = None
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
|
|
|
"""Handle external yaml configuration."""
|
2021-05-11 20:00:12 +00:00
|
|
|
self._async_abort_entries_match()
|
2019-05-01 20:05:40 +00:00
|
|
|
|
|
|
|
config = self.hass.data.get(DATA_AMBICLIMATE_IMPL, {})
|
|
|
|
|
|
|
|
if not config:
|
|
|
|
_LOGGER.debug("No config")
|
2020-10-18 18:55:32 +00:00
|
|
|
return self.async_abort(reason="missing_configuration")
|
2019-05-01 20:05:40 +00:00
|
|
|
|
|
|
|
return await self.async_step_auth()
|
|
|
|
|
|
|
|
async def async_step_auth(self, user_input=None):
|
|
|
|
"""Handle a flow start."""
|
2021-05-11 20:00:12 +00:00
|
|
|
self._async_abort_entries_match()
|
2019-05-01 20:05:40 +00:00
|
|
|
|
|
|
|
errors = {}
|
|
|
|
|
|
|
|
if user_input is not None:
|
2019-07-31 19:25:30 +00:00
|
|
|
errors["base"] = "follow_link"
|
2019-05-01 20:05:40 +00:00
|
|
|
|
|
|
|
if not self._registered_view:
|
|
|
|
self._generate_view()
|
|
|
|
|
|
|
|
return self.async_show_form(
|
2019-07-31 19:25:30 +00:00
|
|
|
step_id="auth",
|
|
|
|
description_placeholders={
|
|
|
|
"authorization_url": await self._get_authorize_url(),
|
|
|
|
"cb_url": self._cb_url(),
|
|
|
|
},
|
2019-05-01 20:05:40 +00:00
|
|
|
errors=errors,
|
|
|
|
)
|
|
|
|
|
|
|
|
async def async_step_code(self, code=None):
|
|
|
|
"""Received code for authentication."""
|
2021-05-11 20:00:12 +00:00
|
|
|
self._async_abort_entries_match()
|
2019-05-01 20:05:40 +00:00
|
|
|
|
2021-10-31 17:56:25 +00:00
|
|
|
if await self._get_token_info(code) is None:
|
2019-07-31 19:25:30 +00:00
|
|
|
return self.async_abort(reason="access_token")
|
2019-05-01 20:05:40 +00:00
|
|
|
|
|
|
|
config = self.hass.data[DATA_AMBICLIMATE_IMPL].copy()
|
2019-07-31 19:25:30 +00:00
|
|
|
config["callback_url"] = self._cb_url()
|
2019-05-01 20:05:40 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
return self.async_create_entry(title="Ambiclimate", data=config)
|
2019-05-01 20:05:40 +00:00
|
|
|
|
|
|
|
async def _get_token_info(self, code):
|
|
|
|
oauth = self._generate_oauth()
|
|
|
|
try:
|
|
|
|
token_info = await oauth.get_access_token(code)
|
|
|
|
except ambiclimate.AmbiclimateOauthError:
|
|
|
|
_LOGGER.error("Failed to get access token", exc_info=True)
|
|
|
|
return None
|
|
|
|
|
|
|
|
store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
|
|
|
await store.async_save(token_info)
|
|
|
|
|
|
|
|
return token_info
|
|
|
|
|
|
|
|
def _generate_view(self):
|
|
|
|
self.hass.http.register_view(AmbiclimateAuthCallbackView())
|
|
|
|
self._registered_view = True
|
|
|
|
|
|
|
|
def _generate_oauth(self):
|
|
|
|
config = self.hass.data[DATA_AMBICLIMATE_IMPL]
|
|
|
|
clientsession = async_get_clientsession(self.hass)
|
|
|
|
callback_url = self._cb_url()
|
|
|
|
|
2020-05-08 00:29:47 +00:00
|
|
|
return ambiclimate.AmbiclimateOAuth(
|
2019-07-31 19:25:30 +00:00
|
|
|
config.get(CONF_CLIENT_ID),
|
|
|
|
config.get(CONF_CLIENT_SECRET),
|
|
|
|
callback_url,
|
|
|
|
clientsession,
|
|
|
|
)
|
2019-05-01 20:05:40 +00:00
|
|
|
|
|
|
|
def _cb_url(self):
|
2020-05-08 19:53:28 +00:00
|
|
|
return f"{get_url(self.hass)}{AUTH_CALLBACK_PATH}"
|
2019-05-01 20:05:40 +00:00
|
|
|
|
|
|
|
async def _get_authorize_url(self):
|
|
|
|
oauth = self._generate_oauth()
|
|
|
|
return oauth.get_authorize_url()
|
|
|
|
|
|
|
|
|
|
|
|
class AmbiclimateAuthCallbackView(HomeAssistantView):
|
|
|
|
"""Ambiclimate Authorization Callback View."""
|
|
|
|
|
|
|
|
requires_auth = False
|
|
|
|
url = AUTH_CALLBACK_PATH
|
|
|
|
name = AUTH_CALLBACK_NAME
|
|
|
|
|
2021-07-20 16:57:40 +00:00
|
|
|
async def get(self, request: web.Request) -> str:
|
2019-05-01 20:05:40 +00:00
|
|
|
"""Receive authorization token."""
|
2021-09-18 11:52:59 +00:00
|
|
|
# pylint: disable=no-self-use
|
2021-10-22 09:34:45 +00:00
|
|
|
if (code := request.query.get("code")) is None:
|
2019-05-01 20:05:40 +00:00
|
|
|
return "No code"
|
2019-07-31 19:25:30 +00:00
|
|
|
hass = request.app["hass"]
|
2019-05-01 20:05:40 +00:00
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.flow.async_init(
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN, context={"source": "code"}, data=code
|
|
|
|
)
|
|
|
|
)
|
2019-05-01 20:05:40 +00:00
|
|
|
return "OK!"
|