"""Config flow for Withings.""" from collections import OrderedDict import logging from typing import Optional import aiohttp import nokia import voluptuous as vol from homeassistant import config_entries, data_entry_flow from homeassistant.components.http import HomeAssistantView from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback from . import const DATA_FLOW_IMPL = "withings_flow_implementation" _LOGGER = logging.getLogger(__name__) @callback def register_flow_implementation(hass, client_id, client_secret, base_url, profiles): """Register a flow implementation. hass: Home assistant object. client_id: Client id. client_secret: Client secret. base_url: Base url of home assistant instance. profiles: The profiles to work with. """ if DATA_FLOW_IMPL not in hass.data: hass.data[DATA_FLOW_IMPL] = OrderedDict() hass.data[DATA_FLOW_IMPL] = { const.CLIENT_ID: client_id, const.CLIENT_SECRET: client_secret, const.BASE_URL: base_url, const.PROFILES: profiles, } @config_entries.HANDLERS.register(const.DOMAIN) class WithingsFlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): """Initialize flow.""" self.flow_profile = None self.data = None def async_profile_config_entry(self, profile: str) -> Optional[ConfigEntry]: """Get a profile config entry.""" entries = self.hass.config_entries.async_entries(const.DOMAIN) for entry in entries: if entry.data.get(const.PROFILE) == profile: return entry return None def get_auth_client(self, profile: str): """Get a new auth client.""" flow = self.hass.data[DATA_FLOW_IMPL] client_id = flow[const.CLIENT_ID] client_secret = flow[const.CLIENT_SECRET] base_url = flow[const.BASE_URL].rstrip("/") callback_uri = "{}/{}?flow_id={}&profile={}".format( base_url.rstrip("/"), const.AUTH_CALLBACK_PATH.lstrip("/"), self.flow_id, profile, ) return nokia.NokiaAuth( client_id, client_secret, callback_uri, scope=",".join(["user.info", "user.metrics", "user.activity"]), ) async def async_step_import(self, user_input=None): """Create user step.""" return await self.async_step_user(user_input) async def async_step_user(self, user_input=None): """Create an entry for selecting a profile.""" flow = self.hass.data.get(DATA_FLOW_IMPL) if not flow: return self.async_abort(reason="no_flows") if user_input: return await self.async_step_auth(user_input) return self.async_show_form( step_id="user", data_schema=vol.Schema( {vol.Required(const.PROFILE): vol.In(flow.get(const.PROFILES))} ), ) async def async_step_auth(self, user_input=None): """Create an entry for auth.""" if user_input.get(const.CODE): self.data = user_input return self.async_external_step_done(next_step_id="finish") profile = user_input.get(const.PROFILE) auth_client = self.get_auth_client(profile) url = auth_client.get_authorize_url() return self.async_external_step(step_id="auth", url=url) async def async_step_finish(self, user_input=None): """Received code for authentication.""" data = user_input or self.data or {} _LOGGER.debug( "Should close all flows below %s", self.hass.config_entries.flow.async_progress(), ) profile = data[const.PROFILE] code = data[const.CODE] return await self._async_create_session(profile, code) async def _async_create_session(self, profile, code): """Create withings session and entries.""" auth_client = self.get_auth_client(profile) _LOGGER.debug("Requesting credentials with code: %s.", code) credentials = auth_client.get_credentials(code) return self.async_create_entry( title=profile, data={const.PROFILE: profile, const.CREDENTIALS: credentials.__dict__}, ) class WithingsAuthCallbackView(HomeAssistantView): """Withings Authorization Callback View.""" requires_auth = False url = const.AUTH_CALLBACK_PATH name = const.AUTH_CALLBACK_NAME def __init__(self): """Constructor.""" async def get(self, request): """Receive authorization code.""" hass = request.app["hass"] code = request.query.get("code") profile = request.query.get("profile") flow_id = request.query.get("flow_id") if not flow_id: return aiohttp.web_response.Response( status=400, text="'flow_id' argument not provided in url." ) if not profile: return aiohttp.web_response.Response( status=400, text="'profile' argument not provided in url." ) if not code: return aiohttp.web_response.Response( status=400, text="'code' argument not provided in url." ) try: await hass.config_entries.flow.async_configure( flow_id, {const.PROFILE: profile, const.CODE: code} ) return aiohttp.web_response.Response( status=200, headers={"content-type": "text/html"}, text="", ) except data_entry_flow.UnknownFlow: return aiohttp.web_response.Response(status=400, text="Unknown flow")