core/homeassistant/components/onboarding/views.py

297 lines
9.6 KiB
Python
Raw Normal View History

"""Onboarding views."""
from __future__ import annotations
import asyncio
from http import HTTPStatus
from aiohttp.web_exceptions import HTTPUnauthorized
import voluptuous as vol
from homeassistant.auth.const import GROUP_ID_ADMIN
from homeassistant.components import person
from homeassistant.components.auth import indieauth
from homeassistant.components.http import KEY_HASS_REFRESH_TOKEN_ID
from homeassistant.components.http.data_validator import RequestDataValidator
2018-07-31 19:39:37 +00:00
from homeassistant.components.http.view import HomeAssistantView
from homeassistant.core import callback
from homeassistant.helpers import area_registry as ar
from homeassistant.helpers.system_info import async_get_system_info
from homeassistant.helpers.translation import async_get_translations
from .const import (
DEFAULT_AREAS,
2019-07-31 19:25:30 +00:00
DOMAIN,
STEP_ANALYTICS,
STEP_CORE_CONFIG,
STEP_INTEGRATION,
2019-07-31 19:25:30 +00:00
STEP_USER,
STEPS,
)
async def async_setup(hass, data, store):
2018-07-31 19:39:37 +00:00
"""Set up the onboarding view."""
hass.http.register_view(OnboardingView(data, store))
hass.http.register_view(InstallationTypeOnboardingView(data))
hass.http.register_view(UserOnboardingView(data, store))
hass.http.register_view(CoreConfigOnboardingView(data, store))
hass.http.register_view(IntegrationOnboardingView(data, store))
hass.http.register_view(AnalyticsOnboardingView(data, store))
class OnboardingView(HomeAssistantView):
2018-07-31 19:39:37 +00:00
"""Return the onboarding status."""
requires_auth = False
2019-07-31 19:25:30 +00:00
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."""
2019-07-31 19:25:30 +00:00
return self.json(
[{"step": key, "done": key in self._data["done"]} for key in STEPS]
)
class InstallationTypeOnboardingView(HomeAssistantView):
"""Return the installation type during onboarding."""
requires_auth = False
url = "/api/onboarding/installation_type"
name = "api:onboarding:installation_type"
def __init__(self, data):
"""Initialize the onboarding installation type view."""
self._data = data
async def get(self, request):
"""Return the onboarding status."""
if self._data["done"]:
raise HTTPUnauthorized()
hass = request.app["hass"]
info = await async_get_system_info(hass)
return self.json({"installation_type": info["installation_type"]})
class _BaseOnboardingView(HomeAssistantView):
"""Base class for onboarding."""
step: str | None = 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."""
2019-07-31 19:25:30 +00:00
return self.step in self._data["done"]
async def _async_mark_done(self, hass):
"""Mark step as done."""
2019-07-31 19:25:30 +00:00
self._data["done"].append(self.step)
await self._store.async_save(self._data)
2019-07-31 19:25:30 +00:00
if set(self._data["done"]) == set(STEPS):
hass.data[DOMAIN] = True
class UserOnboardingView(_BaseOnboardingView):
"""View to handle create user onboarding step."""
2019-07-31 19:25:30 +00:00
url = "/api/onboarding/users"
name = "api:onboarding:users"
requires_auth = False
step = STEP_USER
2019-07-31 19:25:30 +00:00
@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."""
2019-07-31 19:25:30 +00:00
hass = request.app["hass"]
async with self._lock:
if self._async_is_done():
return self.json_message("User step already done", HTTPStatus.FORBIDDEN)
provider = _async_get_hass_provider(hass)
await provider.async_initialize()
2021-11-29 22:01:03 +00:00
user = await hass.auth.async_create_user(
data["name"], group_ids=[GROUP_ID_ADMIN]
)
await hass.async_add_executor_job(
2019-07-31 19:25:30 +00:00
provider.data.add_auth, data["username"], data["password"]
)
credentials = await provider.async_get_or_create_credentials(
{"username": data["username"]}
)
2018-07-17 08:57:05 +00:00
await provider.data.async_save()
await hass.auth.async_link_user(user, credentials)
2019-07-31 19:25:30 +00:00
if "person" in hass.config.components:
await person.async_create_person(hass, data["name"], user_id=user.id)
# Create default areas using the users supplied language.
translations = await async_get_translations(
hass, data["language"], "area", {DOMAIN}
2019-07-31 19:25:30 +00:00
)
area_registry = ar.async_get(hass)
for area in DEFAULT_AREAS:
name = translations[f"component.onboarding.area.{area}"]
# Guard because area might have been created by an automatically
# set up integration.
if not area_registry.async_get_area_by_name(name):
area_registry.async_create(name)
await self._async_mark_done(hass)
# Return authorization code for fetching tokens and connect
# during onboarding.
# pylint: disable=import-outside-toplevel
from homeassistant.components.auth import create_auth_code
auth_code = create_auth_code(hass, data["client_id"], credentials)
2019-07-31 19:25:30 +00:00
return self.json({"auth_code": auth_code})
class CoreConfigOnboardingView(_BaseOnboardingView):
"""View to finish core config onboarding step."""
2019-07-31 19:25:30 +00:00
url = "/api/onboarding/core_config"
name = "api:onboarding:core_config"
step = STEP_CORE_CONFIG
async def post(self, request):
"""Handle finishing core config step."""
2019-07-31 19:25:30 +00:00
hass = request.app["hass"]
async with self._lock:
if self._async_is_done():
return self.json_message(
"Core config step already done", HTTPStatus.FORBIDDEN
)
await self._async_mark_done(hass)
2022-02-21 17:13:02 +00:00
# Integrations to set up when finishing onboarding
onboard_integrations = ["met", "radio_browser"]
# pylint: disable=import-outside-toplevel
from homeassistant.components import hassio
if (
hassio.is_hassio(hass)
and "raspberrypi" in hassio.get_core_info(hass)["machine"]
):
2022-02-21 17:13:02 +00:00
onboard_integrations.append("rpi_power")
# Set up integrations after onboarding
await asyncio.gather(
*(
hass.config_entries.flow.async_init(
domain, context={"source": "onboarding"}
)
for domain in onboard_integrations
)
2022-02-21 17:13:02 +00:00
)
return self.json({})
class IntegrationOnboardingView(_BaseOnboardingView):
"""View to finish integration onboarding step."""
2019-07-31 19:25:30 +00:00
url = "/api/onboarding/integration"
name = "api:onboarding:integration"
step = STEP_INTEGRATION
@RequestDataValidator(
vol.Schema({vol.Required("client_id"): str, vol.Required("redirect_uri"): str})
)
async def post(self, request, data):
"""Handle token creation."""
2019-07-31 19:25:30 +00:00
hass = request.app["hass"]
refresh_token_id = request[KEY_HASS_REFRESH_TOKEN_ID]
async with self._lock:
if self._async_is_done():
return self.json_message(
"Integration step already done", HTTPStatus.FORBIDDEN
)
await self._async_mark_done(hass)
# Validate client ID and redirect uri
if not await indieauth.verify_redirect_uri(
request.app["hass"], data["client_id"], data["redirect_uri"]
):
return self.json_message(
"invalid client id or redirect uri", HTTPStatus.BAD_REQUEST
)
refresh_token = await hass.auth.async_get_refresh_token(refresh_token_id)
if refresh_token is None or refresh_token.credential is None:
return self.json_message(
"Credentials for user not available", HTTPStatus.FORBIDDEN
)
# Return authorization code so we can redirect user and log them in
# pylint: disable=import-outside-toplevel
from homeassistant.components.auth import create_auth_code
auth_code = create_auth_code(
hass, data["client_id"], refresh_token.credential
)
2019-07-31 19:25:30 +00:00
return self.json({"auth_code": auth_code})
class AnalyticsOnboardingView(_BaseOnboardingView):
"""View to finish analytics onboarding step."""
url = "/api/onboarding/analytics"
name = "api:onboarding:analytics"
step = STEP_ANALYTICS
async def post(self, request):
"""Handle finishing analytics step."""
hass = request.app["hass"]
async with self._lock:
if self._async_is_done():
return self.json_message(
"Analytics config step already done", HTTPStatus.FORBIDDEN
)
await self._async_mark_done(hass)
return self.json({})
@callback
def _async_get_hass_provider(hass):
"""Get the Home Assistant auth provider."""
for prv in hass.auth.auth_providers:
2019-07-31 19:25:30 +00:00
if prv.type == "homeassistant":
return prv
2019-07-31 19:25:30 +00:00
raise RuntimeError("No Home Assistant provider found")