"""Config flow for Minut Point.""" import asyncio from collections import OrderedDict import logging import async_timeout from pypoint import PointSession import voluptuous as vol from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView from homeassistant.core import callback from .const import CLIENT_ID, CLIENT_SECRET, DOMAIN AUTH_CALLBACK_PATH = "/api/minut" AUTH_CALLBACK_NAME = "api:minut" DATA_FLOW_IMPL = "point_flow_implementation" _LOGGER = logging.getLogger(__name__) @callback def register_flow_implementation(hass, domain, client_id, client_secret): """Register a flow implementation. domain: Domain of the component responsible for the implementation. name: Name of the component. client_id: Client id. client_secret: Client secret. """ if DATA_FLOW_IMPL not in hass.data: hass.data[DATA_FLOW_IMPL] = OrderedDict() hass.data[DATA_FLOW_IMPL][domain] = { CLIENT_ID: client_id, CLIENT_SECRET: client_secret, } @config_entries.HANDLERS.register("point") class PointFlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): """Initialize flow.""" self.flow_impl = None async def async_step_import(self, user_input=None): """Handle external yaml configuration.""" if self.hass.config_entries.async_entries(DOMAIN): return self.async_abort(reason="already_setup") self.flow_impl = DOMAIN return await self.async_step_auth() async def async_step_user(self, user_input=None): """Handle a flow start.""" flows = self.hass.data.get(DATA_FLOW_IMPL, {}) if self.hass.config_entries.async_entries(DOMAIN): return self.async_abort(reason="already_setup") if not flows: _LOGGER.debug("no flows") return self.async_abort(reason="no_flows") if len(flows) == 1: self.flow_impl = list(flows)[0] return await self.async_step_auth() if user_input is not None: self.flow_impl = user_input["flow_impl"] return await self.async_step_auth() return self.async_show_form( step_id="user", data_schema=vol.Schema({vol.Required("flow_impl"): vol.In(list(flows))}), ) async def async_step_auth(self, user_input=None): """Create an entry for auth.""" if self.hass.config_entries.async_entries(DOMAIN): return self.async_abort(reason="external_setup") errors = {} if user_input is not None: errors["base"] = "follow_link" try: with async_timeout.timeout(10): url = await self._get_authorization_url() except asyncio.TimeoutError: return self.async_abort(reason="authorize_url_timeout") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected error generating auth url") return self.async_abort(reason="authorize_url_fail") return self.async_show_form( step_id="auth", description_placeholders={"authorization_url": url}, errors=errors, ) async def _get_authorization_url(self): """Create Minut Point session and get authorization url.""" flow = self.hass.data[DATA_FLOW_IMPL][self.flow_impl] client_id = flow[CLIENT_ID] client_secret = flow[CLIENT_SECRET] point_session = PointSession(client_id, client_secret=client_secret) self.hass.http.register_view(MinutAuthCallbackView()) return point_session.get_authorization_url async def async_step_code(self, code=None): """Received code for authentication.""" if self.hass.config_entries.async_entries(DOMAIN): return self.async_abort(reason="already_setup") if code is None: return self.async_abort(reason="no_code") _LOGGER.debug( "Should close all flows below %s", self.hass.config_entries.flow.async_progress(), ) # Remove notification if no other discovery config entries in progress return await self._async_create_session(code) async def _async_create_session(self, code): """Create point session and entries.""" flow = self.hass.data[DATA_FLOW_IMPL][DOMAIN] client_id = flow[CLIENT_ID] client_secret = flow[CLIENT_SECRET] point_session = PointSession(client_id, client_secret=client_secret) token = await self.hass.async_add_executor_job( point_session.get_access_token, code ) _LOGGER.debug("Got new token") if not point_session.is_authorized: _LOGGER.error("Authentication Error") return self.async_abort(reason="auth_error") _LOGGER.info("Successfully authenticated Point") user_email = point_session.user().get("email") or "" return self.async_create_entry( title=user_email, data={ "token": token, "refresh_args": { "client_id": client_id, "client_secret": client_secret, }, }, ) class MinutAuthCallbackView(HomeAssistantView): """Minut Authorization Callback View.""" requires_auth = False url = AUTH_CALLBACK_PATH name = AUTH_CALLBACK_NAME @staticmethod async def get(request): """Receive authorization code.""" hass = request.app["hass"] if "code" in request.query: hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": "code"}, data=request.query["code"] ) ) return "OK!"