"""Onboarding views.""" import asyncio import voluptuous as vol from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView from homeassistant.const import HTTP_FORBIDDEN from homeassistant.core import callback from .const import ( DEFAULT_AREAS, DOMAIN, STEP_CORE_CONFIG, STEP_INTEGRATION, STEP_USER, STEPS, ) async def async_setup(hass, data, store): """Set up the onboarding view.""" hass.http.register_view(OnboardingView(data, store)) hass.http.register_view(UserOnboardingView(data, store)) hass.http.register_view(CoreConfigOnboardingView(data, store)) hass.http.register_view(IntegrationOnboardingView(data, store)) class OnboardingView(HomeAssistantView): """Return the onboarding status.""" requires_auth = False url = "/api/onboarding" name = "api:onboarding" def __init__(self, data, store): """Initialize the onboarding view.""" self._store = store self._data = data async def get(self, request): """Return the onboarding status.""" return self.json( [{"step": key, "done": key in self._data["done"]} for key in STEPS] ) class _BaseOnboardingView(HomeAssistantView): """Base class for onboarding.""" step = None def __init__(self, data, store): """Initialize the onboarding view.""" self._store = store self._data = data self._lock = asyncio.Lock() @callback def _async_is_done(self): """Return if this step is done.""" return self.step in self._data["done"] async def _async_mark_done(self, hass): """Mark step as done.""" self._data["done"].append(self.step) await self._store.async_save(self._data) if set(self._data["done"]) == set(STEPS): hass.data[DOMAIN] = True class UserOnboardingView(_BaseOnboardingView): """View to handle create user onboarding step.""" url = "/api/onboarding/users" name = "api:onboarding:users" requires_auth = False step = STEP_USER @RequestDataValidator( vol.Schema( { vol.Required("name"): str, vol.Required("username"): str, vol.Required("password"): str, vol.Required("client_id"): str, vol.Required("language"): str, } ) ) async def post(self, request, data): """Handle user creation, area creation.""" hass = request.app["hass"] async with self._lock: if self._async_is_done(): return self.json_message("User step already done", HTTP_FORBIDDEN) provider = _async_get_hass_provider(hass) await provider.async_initialize() user = await hass.auth.async_create_user(data["name"], [GROUP_ID_ADMIN]) await hass.async_add_executor_job( provider.data.add_auth, data["username"], data["password"] ) credentials = await provider.async_get_or_create_credentials( {"username": data["username"]} ) await provider.data.async_save() await hass.auth.async_link_user(user, credentials) if "person" in hass.config.components: await hass.components.person.async_create_person( data["name"], user_id=user.id ) # Create default areas using the users supplied language. translations = await hass.helpers.translation.async_get_translations( data["language"] ) area_registry = await hass.helpers.area_registry.async_get_registry() for area in DEFAULT_AREAS: area_registry.async_create( translations[f"component.onboarding.area.{area}"] ) await self._async_mark_done(hass) # Return authorization code for fetching tokens and connect # during onboarding. auth_code = hass.components.auth.create_auth_code(data["client_id"], user) return self.json({"auth_code": auth_code}) class CoreConfigOnboardingView(_BaseOnboardingView): """View to finish core config onboarding step.""" url = "/api/onboarding/core_config" name = "api:onboarding:core_config" step = STEP_CORE_CONFIG async def post(self, request): """Handle finishing core config step.""" hass = request.app["hass"] async with self._lock: if self._async_is_done(): return self.json_message( "Core config step already done", HTTP_FORBIDDEN ) await self._async_mark_done(hass) await hass.config_entries.flow.async_init( "met", context={"source": "onboarding"} ) return self.json({}) class IntegrationOnboardingView(_BaseOnboardingView): """View to finish integration onboarding step.""" url = "/api/onboarding/integration" name = "api:onboarding:integration" step = STEP_INTEGRATION @RequestDataValidator(vol.Schema({vol.Required("client_id"): str})) async def post(self, request, data): """Handle token creation.""" hass = request.app["hass"] user = request["hass_user"] async with self._lock: if self._async_is_done(): return self.json_message( "Integration step already done", HTTP_FORBIDDEN ) await self._async_mark_done(hass) # Return authorization code so we can redirect user and log them in auth_code = hass.components.auth.create_auth_code(data["client_id"], user) return self.json({"auth_code": auth_code}) @callback def _async_get_hass_provider(hass): """Get the Home Assistant auth provider.""" for prv in hass.auth.auth_providers: if prv.type == "homeassistant": return prv raise RuntimeError("No Home Assistant provider found")