2018-07-17 08:49:15 +00:00
|
|
|
"""Onboarding views."""
|
|
|
|
import asyncio
|
|
|
|
|
|
|
|
import voluptuous as vol
|
|
|
|
|
2020-03-30 18:33:43 +00:00
|
|
|
from homeassistant.auth.const import GROUP_ID_ADMIN
|
2020-06-17 19:13:28 +00:00
|
|
|
from homeassistant.components.auth import indieauth
|
2018-07-17 08:49:15 +00:00
|
|
|
from homeassistant.components.http.data_validator import RequestDataValidator
|
2018-07-31 19:39:37 +00:00
|
|
|
from homeassistant.components.http.view import HomeAssistantView
|
2020-06-17 19:13:28 +00:00
|
|
|
from homeassistant.const import HTTP_BAD_REQUEST, HTTP_FORBIDDEN
|
2018-07-31 19:39:37 +00:00
|
|
|
from homeassistant.core import callback
|
2018-07-17 08:49:15 +00:00
|
|
|
|
2019-05-23 00:24:46 +00:00
|
|
|
from .const import (
|
2019-12-07 06:25:15 +00:00
|
|
|
DEFAULT_AREAS,
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN,
|
2019-12-07 06:25:15 +00:00
|
|
|
STEP_CORE_CONFIG,
|
|
|
|
STEP_INTEGRATION,
|
2019-07-31 19:25:30 +00:00
|
|
|
STEP_USER,
|
|
|
|
STEPS,
|
|
|
|
)
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_setup(hass, data, store):
|
2018-07-31 19:39:37 +00:00
|
|
|
"""Set up the onboarding view."""
|
2018-07-17 08:49:15 +00:00
|
|
|
hass.http.register_view(OnboardingView(data, store))
|
|
|
|
hass.http.register_view(UserOnboardingView(data, store))
|
2019-05-23 00:24:46 +00:00
|
|
|
hass.http.register_view(CoreConfigOnboardingView(data, store))
|
2019-05-08 05:51:24 +00:00
|
|
|
hass.http.register_view(IntegrationOnboardingView(data, store))
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
class OnboardingView(HomeAssistantView):
|
2018-07-31 19:39:37 +00:00
|
|
|
"""Return the onboarding status."""
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
requires_auth = False
|
2019-07-31 19:25:30 +00:00
|
|
|
url = "/api/onboarding"
|
|
|
|
name = "api:onboarding"
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
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]
|
|
|
|
)
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
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."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return self.step in self._data["done"]
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
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)
|
2018-07-17 08:49:15 +00:00
|
|
|
await self._store.async_save(self._data)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if set(self._data["done"]) == set(STEPS):
|
2019-05-08 05:51:24 +00:00
|
|
|
hass.data[DOMAIN] = True
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
class UserOnboardingView(_BaseOnboardingView):
|
2019-05-08 05:51:24 +00:00
|
|
|
"""View to handle create user onboarding step."""
|
2018-07-17 08:49:15 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
url = "/api/onboarding/users"
|
|
|
|
name = "api:onboarding:users"
|
2019-05-08 05:51:24 +00:00
|
|
|
requires_auth = False
|
2018-07-17 08:49:15 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
2018-07-17 08:49:15 +00:00
|
|
|
async def post(self, request, data):
|
2019-05-08 05:51:24 +00:00
|
|
|
"""Handle user creation, area creation."""
|
2019-07-31 19:25:30 +00:00
|
|
|
hass = request.app["hass"]
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
async with self._lock:
|
|
|
|
if self._async_is_done():
|
2020-04-09 15:41:17 +00:00
|
|
|
return self.json_message("User step already done", HTTP_FORBIDDEN)
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
provider = _async_get_hass_provider(hass)
|
|
|
|
await provider.async_initialize()
|
|
|
|
|
2020-03-30 18:33:43 +00:00
|
|
|
user = await hass.auth.async_create_user(data["name"], [GROUP_ID_ADMIN])
|
2018-07-17 08:49:15 +00:00
|
|
|
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()
|
2018-07-17 08:49:15 +00:00
|
|
|
await hass.auth.async_link_user(user, credentials)
|
2019-07-31 19:25:30 +00:00
|
|
|
if "person" in hass.config.components:
|
2019-02-14 04:00:08 +00:00
|
|
|
await hass.components.person.async_create_person(
|
2019-07-31 19:25:30 +00:00
|
|
|
data["name"], user_id=user.id
|
2019-02-14 04:00:08 +00:00
|
|
|
)
|
2019-03-08 21:51:42 +00:00
|
|
|
|
2019-05-08 05:51:24 +00:00
|
|
|
# Create default areas using the users supplied language.
|
2019-07-31 19:25:30 +00:00
|
|
|
translations = await hass.helpers.translation.async_get_translations(
|
2020-04-20 03:35:49 +00:00
|
|
|
data["language"], "area", DOMAIN
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-05-08 05:51:24 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
area_registry = await hass.helpers.area_registry.async_get_registry()
|
2019-05-08 05:51:24 +00:00
|
|
|
|
|
|
|
for area in DEFAULT_AREAS:
|
|
|
|
area_registry.async_create(
|
2019-09-03 18:35:00 +00:00
|
|
|
translations[f"component.onboarding.area.{area}"]
|
2019-05-08 05:51:24 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
await self._async_mark_done(hass)
|
|
|
|
|
|
|
|
# Return authorization code for fetching tokens and connect
|
|
|
|
# during onboarding.
|
2019-07-31 19:25:30 +00:00
|
|
|
auth_code = hass.components.auth.create_auth_code(data["client_id"], user)
|
|
|
|
return self.json({"auth_code": auth_code})
|
2019-05-08 05:51:24 +00:00
|
|
|
|
|
|
|
|
2019-05-23 00:24:46 +00:00
|
|
|
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"
|
2019-05-23 00:24:46 +00:00
|
|
|
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"]
|
2019-05-23 00:24:46 +00:00
|
|
|
|
|
|
|
async with self._lock:
|
|
|
|
if self._async_is_done():
|
2020-04-09 15:41:17 +00:00
|
|
|
return self.json_message(
|
|
|
|
"Core config step already done", HTTP_FORBIDDEN
|
|
|
|
)
|
2019-05-23 00:24:46 +00:00
|
|
|
|
|
|
|
await self._async_mark_done(hass)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
await hass.config_entries.flow.async_init(
|
|
|
|
"met", context={"source": "onboarding"}
|
|
|
|
)
|
2019-06-19 21:41:27 +00:00
|
|
|
|
2019-05-23 00:24:46 +00:00
|
|
|
return self.json({})
|
|
|
|
|
|
|
|
|
2019-05-08 05:51:24 +00:00
|
|
|
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"
|
2019-05-08 05:51:24 +00:00
|
|
|
step = STEP_INTEGRATION
|
|
|
|
|
2020-06-17 19:13:28 +00:00
|
|
|
@RequestDataValidator(
|
|
|
|
vol.Schema({vol.Required("client_id"): str, vol.Required("redirect_uri"): str})
|
|
|
|
)
|
2019-05-08 05:51:24 +00:00
|
|
|
async def post(self, request, data):
|
2019-05-23 00:24:46 +00:00
|
|
|
"""Handle token creation."""
|
2019-07-31 19:25:30 +00:00
|
|
|
hass = request.app["hass"]
|
|
|
|
user = request["hass_user"]
|
2019-05-08 05:51:24 +00:00
|
|
|
|
|
|
|
async with self._lock:
|
|
|
|
if self._async_is_done():
|
2020-04-09 15:41:17 +00:00
|
|
|
return self.json_message(
|
|
|
|
"Integration step already done", HTTP_FORBIDDEN
|
|
|
|
)
|
2019-05-08 05:51:24 +00:00
|
|
|
|
2018-07-17 08:49:15 +00:00
|
|
|
await self._async_mark_done(hass)
|
|
|
|
|
2020-06-17 19:13:28 +00:00
|
|
|
# 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", HTTP_BAD_REQUEST
|
|
|
|
)
|
|
|
|
|
2019-05-08 05:51:24 +00:00
|
|
|
# Return authorization code so we can redirect user and log them in
|
2019-07-31 19:25:30 +00:00
|
|
|
auth_code = hass.components.auth.create_auth_code(data["client_id"], user)
|
|
|
|
return self.json({"auth_code": auth_code})
|
2019-03-08 21:51:42 +00:00
|
|
|
|
2018-07-17 08:49:15 +00:00
|
|
|
|
|
|
|
@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":
|
2018-07-17 08:49:15 +00:00
|
|
|
return prv
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
raise RuntimeError("No Home Assistant provider found")
|