Only create cloud user if cloud in use (#29150)
* Only create cloud user if cloud in use * Pass context to alexa * Update requirements * Fix handing & design pattern for 0.30 * fix tests * Fix lint & tests * rename internal user
This commit is contained in:
parent
5d5d053bce
commit
b847d55077
15 changed files with 221 additions and 145 deletions
|
@ -4,7 +4,6 @@ import logging
|
||||||
from hass_nabucasa import Cloud
|
from hass_nabucasa import Cloud
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.auth.const import GROUP_ID_ADMIN
|
|
||||||
from homeassistant.components.alexa import const as alexa_const
|
from homeassistant.components.alexa import const as alexa_const
|
||||||
from homeassistant.components.google_assistant import const as ga_c
|
from homeassistant.components.google_assistant import const as ga_c
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -186,19 +185,6 @@ async def async_setup(hass, config):
|
||||||
prefs = CloudPreferences(hass)
|
prefs = CloudPreferences(hass)
|
||||||
await prefs.async_initialize()
|
await prefs.async_initialize()
|
||||||
|
|
||||||
# Cloud user
|
|
||||||
user = None
|
|
||||||
if prefs.cloud_user:
|
|
||||||
# Fetch the user. It can happen that the user no longer exists if
|
|
||||||
# an image was restored without restoring the cloud prefs.
|
|
||||||
user = await hass.auth.async_get_user(prefs.cloud_user)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
user = await hass.auth.async_create_system_user(
|
|
||||||
"Home Assistant Cloud", [GROUP_ID_ADMIN]
|
|
||||||
)
|
|
||||||
await prefs.async_update(cloud_user=user.id)
|
|
||||||
|
|
||||||
# Initialize Cloud
|
# Initialize Cloud
|
||||||
websession = hass.helpers.aiohttp_client.async_get_clientsession()
|
websession = hass.helpers.aiohttp_client.async_get_clientsession()
|
||||||
client = CloudClient(hass, prefs, websession, alexa_conf, google_conf)
|
client = CloudClient(hass, prefs, websession, alexa_conf, google_conf)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import logging
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from hass_nabucasa.client import CloudClient as Interface
|
from hass_nabucasa.client import CloudClient as Interface
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback, Context
|
||||||
from homeassistant.components.google_assistant import smart_home as ga
|
from homeassistant.components.google_assistant import smart_home as ga
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
@ -44,7 +44,6 @@ class CloudClient(Interface):
|
||||||
self.alexa_user_config = alexa_user_config
|
self.alexa_user_config = alexa_user_config
|
||||||
self._alexa_config = None
|
self._alexa_config = None
|
||||||
self._google_config = None
|
self._google_config = None
|
||||||
self.cloud = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_path(self) -> Path:
|
def base_path(self) -> Path:
|
||||||
|
@ -92,23 +91,22 @@ class CloudClient(Interface):
|
||||||
|
|
||||||
return self._alexa_config
|
return self._alexa_config
|
||||||
|
|
||||||
@property
|
async def get_google_config(self) -> google_config.CloudGoogleConfig:
|
||||||
def google_config(self) -> google_config.CloudGoogleConfig:
|
|
||||||
"""Return Google config."""
|
"""Return Google config."""
|
||||||
if not self._google_config:
|
if not self._google_config:
|
||||||
assert self.cloud is not None
|
assert self.cloud is not None
|
||||||
|
|
||||||
|
cloud_user = await self._prefs.get_cloud_user()
|
||||||
|
|
||||||
self._google_config = google_config.CloudGoogleConfig(
|
self._google_config = google_config.CloudGoogleConfig(
|
||||||
self._hass, self.google_user_config, self._prefs, self.cloud
|
self._hass, self.google_user_config, cloud_user, self._prefs, self.cloud
|
||||||
)
|
)
|
||||||
|
|
||||||
return self._google_config
|
return self._google_config
|
||||||
|
|
||||||
async def async_initialize(self, cloud) -> None:
|
async def logged_in(self) -> None:
|
||||||
"""Initialize the client."""
|
"""When user logs in."""
|
||||||
self.cloud = cloud
|
await self.prefs.async_set_username(self.cloud.username)
|
||||||
|
|
||||||
if not self.cloud.is_logged_in:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.alexa_config.enabled and self.alexa_config.should_report_state:
|
if self.alexa_config.enabled and self.alexa_config.should_report_state:
|
||||||
try:
|
try:
|
||||||
|
@ -116,14 +114,18 @@ class CloudClient(Interface):
|
||||||
except alexa_errors.NoTokenAvailable:
|
except alexa_errors.NoTokenAvailable:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if self.google_config.enabled:
|
if self._prefs.google_enabled:
|
||||||
self.google_config.async_enable_local_sdk()
|
gconf = await self.get_google_config()
|
||||||
|
|
||||||
if self.google_config.should_report_state:
|
gconf.async_enable_local_sdk()
|
||||||
self.google_config.async_enable_report_state()
|
|
||||||
|
if gconf.should_report_state:
|
||||||
|
gconf.async_enable_report_state()
|
||||||
|
|
||||||
async def cleanups(self) -> None:
|
async def cleanups(self) -> None:
|
||||||
"""Cleanup some stuff after logout."""
|
"""Cleanup some stuff after logout."""
|
||||||
|
await self.prefs.async_set_username(None)
|
||||||
|
|
||||||
self._google_config = None
|
self._google_config = None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -141,8 +143,13 @@ class CloudClient(Interface):
|
||||||
|
|
||||||
async def async_alexa_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
|
async def async_alexa_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
|
||||||
"""Process cloud alexa message to client."""
|
"""Process cloud alexa message to client."""
|
||||||
|
cloud_user = await self._prefs.get_cloud_user()
|
||||||
return await alexa_sh.async_handle_message(
|
return await alexa_sh.async_handle_message(
|
||||||
self._hass, self.alexa_config, payload, enabled=self._prefs.alexa_enabled
|
self._hass,
|
||||||
|
self.alexa_config,
|
||||||
|
payload,
|
||||||
|
context=Context(user_id=cloud_user),
|
||||||
|
enabled=self._prefs.alexa_enabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_google_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
|
async def async_google_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
|
||||||
|
@ -150,8 +157,10 @@ class CloudClient(Interface):
|
||||||
if not self._prefs.google_enabled:
|
if not self._prefs.google_enabled:
|
||||||
return ga.turned_off_response(payload)
|
return ga.turned_off_response(payload)
|
||||||
|
|
||||||
|
gconf = await self.get_google_config()
|
||||||
|
|
||||||
return await ga.async_handle_message(
|
return await ga.async_handle_message(
|
||||||
self._hass, self.google_config, self.prefs.cloud_user, payload
|
self._hass, gconf, gconf.cloud_user, payload
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_webhook_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
|
async def async_webhook_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
|
||||||
|
|
|
@ -17,6 +17,7 @@ PREF_DISABLE_2FA = "disable_2fa"
|
||||||
PREF_ALIASES = "aliases"
|
PREF_ALIASES = "aliases"
|
||||||
PREF_SHOULD_EXPOSE = "should_expose"
|
PREF_SHOULD_EXPOSE = "should_expose"
|
||||||
PREF_GOOGLE_LOCAL_WEBHOOK_ID = "google_local_webhook_id"
|
PREF_GOOGLE_LOCAL_WEBHOOK_ID = "google_local_webhook_id"
|
||||||
|
PREF_USERNAME = "username"
|
||||||
DEFAULT_SHOULD_EXPOSE = True
|
DEFAULT_SHOULD_EXPOSE = True
|
||||||
DEFAULT_DISABLE_2FA = False
|
DEFAULT_DISABLE_2FA = False
|
||||||
DEFAULT_ALEXA_REPORT_STATE = False
|
DEFAULT_ALEXA_REPORT_STATE = False
|
||||||
|
|
|
@ -23,10 +23,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
class CloudGoogleConfig(AbstractConfig):
|
class CloudGoogleConfig(AbstractConfig):
|
||||||
"""HA Cloud Configuration for Google Assistant."""
|
"""HA Cloud Configuration for Google Assistant."""
|
||||||
|
|
||||||
def __init__(self, hass, config, prefs, cloud):
|
def __init__(self, hass, config, cloud_user, prefs, cloud):
|
||||||
"""Initialize the Google config."""
|
"""Initialize the Google config."""
|
||||||
super().__init__(hass)
|
super().__init__(hass)
|
||||||
self._config = config
|
self._config = config
|
||||||
|
self._user = cloud_user
|
||||||
self._prefs = prefs
|
self._prefs = prefs
|
||||||
self._cloud = cloud
|
self._cloud = cloud
|
||||||
self._cur_entity_prefs = self._prefs.google_entity_configs
|
self._cur_entity_prefs = self._prefs.google_entity_configs
|
||||||
|
@ -46,7 +47,7 @@ class CloudGoogleConfig(AbstractConfig):
|
||||||
@property
|
@property
|
||||||
def agent_user_id(self):
|
def agent_user_id(self):
|
||||||
"""Return Agent User Id to use for query responses."""
|
"""Return Agent User Id to use for query responses."""
|
||||||
return self._cloud.claims["cognito:username"]
|
return self._cloud.username
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_config(self):
|
def entity_config(self):
|
||||||
|
@ -74,7 +75,12 @@ class CloudGoogleConfig(AbstractConfig):
|
||||||
@property
|
@property
|
||||||
def local_sdk_user_id(self):
|
def local_sdk_user_id(self):
|
||||||
"""Return the user ID to be used for actions received via the local SDK."""
|
"""Return the user ID to be used for actions received via the local SDK."""
|
||||||
return self._prefs.cloud_user
|
return self._user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cloud_user(self):
|
||||||
|
"""Return Cloud User account."""
|
||||||
|
return self._user
|
||||||
|
|
||||||
def should_expose(self, state):
|
def should_expose(self, state):
|
||||||
"""If a state object should be exposed."""
|
"""If a state object should be exposed."""
|
||||||
|
|
|
@ -174,9 +174,8 @@ class GoogleActionsSyncView(HomeAssistantView):
|
||||||
"""Trigger a Google Actions sync."""
|
"""Trigger a Google Actions sync."""
|
||||||
hass = request.app["hass"]
|
hass = request.app["hass"]
|
||||||
cloud: Cloud = hass.data[DOMAIN]
|
cloud: Cloud = hass.data[DOMAIN]
|
||||||
status = await cloud.client.google_config.async_sync_entities(
|
gconf = await cloud.client.get_google_config()
|
||||||
cloud.client.google_config.agent_user_id
|
status = await gconf.async_sync_entities(gconf.agent_user_id)
|
||||||
)
|
|
||||||
return self.json({}, status_code=status)
|
return self.json({}, status_code=status)
|
||||||
|
|
||||||
|
|
||||||
|
@ -194,11 +193,7 @@ class CloudLoginView(HomeAssistantView):
|
||||||
"""Handle login request."""
|
"""Handle login request."""
|
||||||
hass = request.app["hass"]
|
hass = request.app["hass"]
|
||||||
cloud = hass.data[DOMAIN]
|
cloud = hass.data[DOMAIN]
|
||||||
|
await cloud.login(data["email"], data["password"])
|
||||||
with async_timeout.timeout(REQUEST_TIMEOUT):
|
|
||||||
await hass.async_add_job(cloud.auth.login, data["email"], data["password"])
|
|
||||||
|
|
||||||
hass.async_add_job(cloud.iot.connect)
|
|
||||||
return self.json({"success": True})
|
return self.json({"success": True})
|
||||||
|
|
||||||
|
|
||||||
|
@ -479,7 +474,8 @@ async def websocket_remote_disconnect(hass, connection, msg):
|
||||||
async def google_assistant_list(hass, connection, msg):
|
async def google_assistant_list(hass, connection, msg):
|
||||||
"""List all google assistant entities."""
|
"""List all google assistant entities."""
|
||||||
cloud = hass.data[DOMAIN]
|
cloud = hass.data[DOMAIN]
|
||||||
entities = google_helpers.async_get_entities(hass, cloud.client.google_config)
|
gconf = await cloud.client.get_google_config()
|
||||||
|
entities = google_helpers.async_get_entities(hass, gconf)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"domain": "cloud",
|
"domain": "cloud",
|
||||||
"name": "Cloud",
|
"name": "Cloud",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/cloud",
|
"documentation": "https://www.home-assistant.io/integrations/cloud",
|
||||||
"requirements": ["hass-nabucasa==0.29"],
|
"requirements": ["hass-nabucasa==0.30"],
|
||||||
"dependencies": ["http", "webhook"],
|
"dependencies": ["http", "webhook"],
|
||||||
"codeowners": ["@home-assistant/cloud"]
|
"codeowners": ["@home-assistant/cloud"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
"""Preference management for cloud."""
|
"""Preference management for cloud."""
|
||||||
from ipaddress import ip_address
|
from ipaddress import ip_address
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.auth.models import User
|
||||||
|
from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||||
from homeassistant.util.logging import async_create_catching_coro
|
from homeassistant.util.logging import async_create_catching_coro
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
@ -19,6 +22,7 @@ from .const import (
|
||||||
PREF_SHOULD_EXPOSE,
|
PREF_SHOULD_EXPOSE,
|
||||||
PREF_ALEXA_ENTITY_CONFIGS,
|
PREF_ALEXA_ENTITY_CONFIGS,
|
||||||
PREF_ALEXA_REPORT_STATE,
|
PREF_ALEXA_REPORT_STATE,
|
||||||
|
PREF_USERNAME,
|
||||||
DEFAULT_ALEXA_REPORT_STATE,
|
DEFAULT_ALEXA_REPORT_STATE,
|
||||||
PREF_GOOGLE_REPORT_STATE,
|
PREF_GOOGLE_REPORT_STATE,
|
||||||
PREF_GOOGLE_LOCAL_WEBHOOK_ID,
|
PREF_GOOGLE_LOCAL_WEBHOOK_ID,
|
||||||
|
@ -47,16 +51,7 @@ class CloudPreferences:
|
||||||
prefs = await self._store.async_load()
|
prefs = await self._store.async_load()
|
||||||
|
|
||||||
if prefs is None:
|
if prefs is None:
|
||||||
prefs = {
|
prefs = self._empty_config("")
|
||||||
PREF_ENABLE_ALEXA: True,
|
|
||||||
PREF_ENABLE_GOOGLE: True,
|
|
||||||
PREF_ENABLE_REMOTE: False,
|
|
||||||
PREF_GOOGLE_SECURE_DEVICES_PIN: None,
|
|
||||||
PREF_GOOGLE_ENTITY_CONFIGS: {},
|
|
||||||
PREF_ALEXA_ENTITY_CONFIGS: {},
|
|
||||||
PREF_CLOUDHOOKS: {},
|
|
||||||
PREF_CLOUD_USER: None,
|
|
||||||
}
|
|
||||||
|
|
||||||
self._prefs = prefs
|
self._prefs = prefs
|
||||||
|
|
||||||
|
@ -166,6 +161,27 @@ class CloudPreferences:
|
||||||
updated_entities = {**entities, entity_id: updated_entity}
|
updated_entities = {**entities, entity_id: updated_entity}
|
||||||
await self.async_update(alexa_entity_configs=updated_entities)
|
await self.async_update(alexa_entity_configs=updated_entities)
|
||||||
|
|
||||||
|
async def async_set_username(self, username):
|
||||||
|
"""Set the username that is logged in."""
|
||||||
|
# Logging out.
|
||||||
|
if username is None:
|
||||||
|
user = await self._load_cloud_user()
|
||||||
|
|
||||||
|
if user is not None:
|
||||||
|
await self._hass.auth.async_remove_user(user)
|
||||||
|
await self._save_prefs({**self._prefs, PREF_CLOUD_USER: None})
|
||||||
|
return
|
||||||
|
|
||||||
|
cur_username = self._prefs.get(PREF_USERNAME)
|
||||||
|
|
||||||
|
if cur_username == username:
|
||||||
|
return
|
||||||
|
|
||||||
|
if cur_username is None:
|
||||||
|
await self._save_prefs({**self._prefs, PREF_USERNAME: username})
|
||||||
|
else:
|
||||||
|
await self._save_prefs(self._empty_config(username))
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
"""Return dictionary version."""
|
"""Return dictionary version."""
|
||||||
return {
|
return {
|
||||||
|
@ -178,7 +194,6 @@ class CloudPreferences:
|
||||||
PREF_ALEXA_REPORT_STATE: self.alexa_report_state,
|
PREF_ALEXA_REPORT_STATE: self.alexa_report_state,
|
||||||
PREF_GOOGLE_REPORT_STATE: self.google_report_state,
|
PREF_GOOGLE_REPORT_STATE: self.google_report_state,
|
||||||
PREF_CLOUDHOOKS: self.cloudhooks,
|
PREF_CLOUDHOOKS: self.cloudhooks,
|
||||||
PREF_CLOUD_USER: self.cloud_user,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -239,10 +254,29 @@ class CloudPreferences:
|
||||||
"""Return the published cloud webhooks."""
|
"""Return the published cloud webhooks."""
|
||||||
return self._prefs.get(PREF_CLOUDHOOKS, {})
|
return self._prefs.get(PREF_CLOUDHOOKS, {})
|
||||||
|
|
||||||
@property
|
async def get_cloud_user(self) -> str:
|
||||||
def cloud_user(self) -> str:
|
|
||||||
"""Return ID from Home Assistant Cloud system user."""
|
"""Return ID from Home Assistant Cloud system user."""
|
||||||
return self._prefs.get(PREF_CLOUD_USER)
|
user = await self._load_cloud_user()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
return user.id
|
||||||
|
|
||||||
|
user = await self._hass.auth.async_create_system_user(
|
||||||
|
"Home Assistant Cloud", [GROUP_ID_ADMIN]
|
||||||
|
)
|
||||||
|
await self.async_update(cloud_user=user.id)
|
||||||
|
return user.id
|
||||||
|
|
||||||
|
async def _load_cloud_user(self) -> Optional[User]:
|
||||||
|
"""Load cloud user if available."""
|
||||||
|
user_id = self._prefs.get(PREF_CLOUD_USER)
|
||||||
|
|
||||||
|
if user_id is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Fetch the user. It can happen that the user no longer exists if
|
||||||
|
# an image was restored without restoring the cloud prefs.
|
||||||
|
return await self._hass.auth.async_get_user(user_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _has_local_trusted_network(self) -> bool:
|
def _has_local_trusted_network(self) -> bool:
|
||||||
|
@ -283,3 +317,19 @@ class CloudPreferences:
|
||||||
|
|
||||||
for listener in self._listeners:
|
for listener in self._listeners:
|
||||||
self._hass.async_create_task(async_create_catching_coro(listener(self)))
|
self._hass.async_create_task(async_create_catching_coro(listener(self)))
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _empty_config(self, username):
|
||||||
|
"""Return an empty config."""
|
||||||
|
return {
|
||||||
|
PREF_ENABLE_ALEXA: True,
|
||||||
|
PREF_ENABLE_GOOGLE: True,
|
||||||
|
PREF_ENABLE_REMOTE: False,
|
||||||
|
PREF_GOOGLE_SECURE_DEVICES_PIN: None,
|
||||||
|
PREF_GOOGLE_ENTITY_CONFIGS: {},
|
||||||
|
PREF_ALEXA_ENTITY_CONFIGS: {},
|
||||||
|
PREF_CLOUDHOOKS: {},
|
||||||
|
PREF_CLOUD_USER: None,
|
||||||
|
PREF_USERNAME: username,
|
||||||
|
PREF_GOOGLE_LOCAL_WEBHOOK_ID: self._hass.components.webhook.async_generate_id(),
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ contextvars==2.4;python_version<"3.7"
|
||||||
cryptography==2.8
|
cryptography==2.8
|
||||||
defusedxml==0.6.0
|
defusedxml==0.6.0
|
||||||
distro==1.4.0
|
distro==1.4.0
|
||||||
hass-nabucasa==0.29
|
hass-nabucasa==0.30
|
||||||
home-assistant-frontend==20191119.6
|
home-assistant-frontend==20191119.6
|
||||||
importlib-metadata==0.23
|
importlib-metadata==0.23
|
||||||
jinja2>=2.10.3
|
jinja2>=2.10.3
|
||||||
|
|
|
@ -629,7 +629,7 @@ habitipy==0.2.0
|
||||||
hangups==0.4.9
|
hangups==0.4.9
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.29
|
hass-nabucasa==0.30
|
||||||
|
|
||||||
# homeassistant.components.mqtt
|
# homeassistant.components.mqtt
|
||||||
hbmqtt==0.9.5
|
hbmqtt==0.9.5
|
||||||
|
|
|
@ -208,7 +208,7 @@ ha-ffmpeg==2.0
|
||||||
hangups==0.4.9
|
hangups==0.4.9
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.29
|
hass-nabucasa==0.30
|
||||||
|
|
||||||
# homeassistant.components.mqtt
|
# homeassistant.components.mqtt
|
||||||
hbmqtt==0.9.5
|
hbmqtt==0.9.5
|
||||||
|
|
|
@ -7,6 +7,7 @@ import pytest
|
||||||
from homeassistant.core import State
|
from homeassistant.core import State
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.components.cloud import DOMAIN
|
from homeassistant.components.cloud import DOMAIN
|
||||||
|
from homeassistant.components.cloud.client import CloudClient
|
||||||
from homeassistant.components.cloud.const import PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE
|
from homeassistant.components.cloud.const import PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE
|
||||||
from tests.components.alexa import test_smart_home as test_alexa
|
from tests.components.alexa import test_smart_home as test_alexa
|
||||||
from tests.common import mock_coro
|
from tests.common import mock_coro
|
||||||
|
@ -187,25 +188,42 @@ async def test_google_config_expose_entity(hass, mock_cloud_setup, mock_cloud_lo
|
||||||
"""Test Google config exposing entity method uses latest config."""
|
"""Test Google config exposing entity method uses latest config."""
|
||||||
cloud_client = hass.data[DOMAIN].client
|
cloud_client = hass.data[DOMAIN].client
|
||||||
state = State("light.kitchen", "on")
|
state = State("light.kitchen", "on")
|
||||||
|
gconf = await cloud_client.get_google_config()
|
||||||
|
|
||||||
assert cloud_client.google_config.should_expose(state)
|
assert gconf.should_expose(state)
|
||||||
|
|
||||||
await cloud_client.prefs.async_update_google_entity_config(
|
await cloud_client.prefs.async_update_google_entity_config(
|
||||||
entity_id="light.kitchen", should_expose=False
|
entity_id="light.kitchen", should_expose=False
|
||||||
)
|
)
|
||||||
|
|
||||||
assert not cloud_client.google_config.should_expose(state)
|
assert not gconf.should_expose(state)
|
||||||
|
|
||||||
|
|
||||||
async def test_google_config_should_2fa(hass, mock_cloud_setup, mock_cloud_login):
|
async def test_google_config_should_2fa(hass, mock_cloud_setup, mock_cloud_login):
|
||||||
"""Test Google config disabling 2FA method uses latest config."""
|
"""Test Google config disabling 2FA method uses latest config."""
|
||||||
cloud_client = hass.data[DOMAIN].client
|
cloud_client = hass.data[DOMAIN].client
|
||||||
|
gconf = await cloud_client.get_google_config()
|
||||||
state = State("light.kitchen", "on")
|
state = State("light.kitchen", "on")
|
||||||
|
|
||||||
assert cloud_client.google_config.should_2fa(state)
|
assert gconf.should_2fa(state)
|
||||||
|
|
||||||
await cloud_client.prefs.async_update_google_entity_config(
|
await cloud_client.prefs.async_update_google_entity_config(
|
||||||
entity_id="light.kitchen", disable_2fa=True
|
entity_id="light.kitchen", disable_2fa=True
|
||||||
)
|
)
|
||||||
|
|
||||||
assert not cloud_client.google_config.should_2fa(state)
|
assert not gconf.should_2fa(state)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_username(hass):
|
||||||
|
"""Test we set username during loggin."""
|
||||||
|
prefs = MagicMock(
|
||||||
|
alexa_enabled=False,
|
||||||
|
google_enabled=False,
|
||||||
|
async_set_username=MagicMock(return_value=mock_coro()),
|
||||||
|
)
|
||||||
|
client = CloudClient(hass, prefs, None, {}, {})
|
||||||
|
client.cloud = MagicMock(is_logged_in=True, username="mock-username")
|
||||||
|
await client.logged_in()
|
||||||
|
|
||||||
|
assert len(prefs.async_set_username.mock_calls) == 1
|
||||||
|
assert prefs.async_set_username.mock_calls[0][1][0] == "mock-username"
|
||||||
|
|
|
@ -15,6 +15,7 @@ async def test_google_update_report_state(hass, cloud_prefs):
|
||||||
config = CloudGoogleConfig(
|
config = CloudGoogleConfig(
|
||||||
hass,
|
hass,
|
||||||
GACTIONS_SCHEMA({}),
|
GACTIONS_SCHEMA({}),
|
||||||
|
"mock-user-id",
|
||||||
cloud_prefs,
|
cloud_prefs,
|
||||||
Mock(claims={"cognito:username": "abcdefghjkl"}),
|
Mock(claims={"cognito:username": "abcdefghjkl"}),
|
||||||
)
|
)
|
||||||
|
@ -37,6 +38,7 @@ async def test_sync_entities(aioclient_mock, hass, cloud_prefs):
|
||||||
config = CloudGoogleConfig(
|
config = CloudGoogleConfig(
|
||||||
hass,
|
hass,
|
||||||
GACTIONS_SCHEMA({}),
|
GACTIONS_SCHEMA({}),
|
||||||
|
"mock-user-id",
|
||||||
cloud_prefs,
|
cloud_prefs,
|
||||||
Mock(
|
Mock(
|
||||||
google_actions_sync_url="http://example.com",
|
google_actions_sync_url="http://example.com",
|
||||||
|
@ -52,6 +54,7 @@ async def test_google_update_expose_trigger_sync(hass, cloud_prefs):
|
||||||
config = CloudGoogleConfig(
|
config = CloudGoogleConfig(
|
||||||
hass,
|
hass,
|
||||||
GACTIONS_SCHEMA({}),
|
GACTIONS_SCHEMA({}),
|
||||||
|
"mock-user-id",
|
||||||
cloud_prefs,
|
cloud_prefs,
|
||||||
Mock(claims={"cognito:username": "abcdefghjkl"}),
|
Mock(claims={"cognito:username": "abcdefghjkl"}),
|
||||||
)
|
)
|
||||||
|
@ -90,7 +93,7 @@ async def test_google_update_expose_trigger_sync(hass, cloud_prefs):
|
||||||
async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs):
|
async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs):
|
||||||
"""Test Google config responds to entity registry."""
|
"""Test Google config responds to entity registry."""
|
||||||
config = CloudGoogleConfig(
|
config = CloudGoogleConfig(
|
||||||
hass, GACTIONS_SCHEMA({}), cloud_prefs, hass.data["cloud"]
|
hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"]
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch.object(
|
with patch.object(
|
||||||
|
|
|
@ -103,32 +103,18 @@ async def test_google_actions_sync_fails(
|
||||||
assert req.status == 403
|
assert req.status == 403
|
||||||
|
|
||||||
|
|
||||||
async def test_login_view(hass, cloud_client, mock_cognito):
|
async def test_login_view(hass, cloud_client):
|
||||||
"""Test logging in."""
|
"""Test logging in."""
|
||||||
mock_cognito.id_token = jwt.encode(
|
hass.data["cloud"] = MagicMock(login=MagicMock(return_value=mock_coro()))
|
||||||
{"email": "hello@home-assistant.io", "custom:sub-exp": "2018-01-03"}, "test"
|
|
||||||
)
|
|
||||||
mock_cognito.access_token = "access_token"
|
|
||||||
mock_cognito.refresh_token = "refresh_token"
|
|
||||||
|
|
||||||
with patch("hass_nabucasa.iot.CloudIoT.connect") as mock_connect, patch(
|
req = await cloud_client.post(
|
||||||
"hass_nabucasa.auth.CognitoAuth._authenticate", return_value=mock_cognito
|
"/api/cloud/login", json={"email": "my_username", "password": "my_password"}
|
||||||
) as mock_auth:
|
)
|
||||||
req = await cloud_client.post(
|
|
||||||
"/api/cloud/login", json={"email": "my_username", "password": "my_password"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert req.status == 200
|
assert req.status == 200
|
||||||
result = await req.json()
|
result = await req.json()
|
||||||
assert result == {"success": True}
|
assert result == {"success": True}
|
||||||
|
|
||||||
assert len(mock_connect.mock_calls) == 1
|
|
||||||
|
|
||||||
assert len(mock_auth.mock_calls) == 1
|
|
||||||
result_user, result_pass = mock_auth.mock_calls[0][1]
|
|
||||||
assert result_user == "my_username"
|
|
||||||
assert result_pass == "my_password"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_login_view_random_exception(cloud_client):
|
async def test_login_view_random_exception(cloud_client):
|
||||||
"""Try logging in with invalid JSON."""
|
"""Try logging in with invalid JSON."""
|
||||||
|
@ -351,7 +337,6 @@ async def test_websocket_status(
|
||||||
"cloud": "connected",
|
"cloud": "connected",
|
||||||
"prefs": {
|
"prefs": {
|
||||||
"alexa_enabled": True,
|
"alexa_enabled": True,
|
||||||
"cloud_user": None,
|
|
||||||
"cloudhooks": {},
|
"cloudhooks": {},
|
||||||
"google_enabled": True,
|
"google_enabled": True,
|
||||||
"google_entity_configs": {},
|
"google_entity_configs": {},
|
||||||
|
|
|
@ -5,7 +5,6 @@ import pytest
|
||||||
|
|
||||||
from homeassistant.core import Context
|
from homeassistant.core import Context
|
||||||
from homeassistant.exceptions import Unauthorized
|
from homeassistant.exceptions import Unauthorized
|
||||||
from homeassistant.auth.const import GROUP_ID_ADMIN
|
|
||||||
from homeassistant.components import cloud
|
from homeassistant.components import cloud
|
||||||
from homeassistant.components.cloud.const import DOMAIN
|
from homeassistant.components.cloud.const import DOMAIN
|
||||||
from homeassistant.components.cloud.prefs import STORAGE_KEY
|
from homeassistant.components.cloud.prefs import STORAGE_KEY
|
||||||
|
@ -142,68 +141,11 @@ async def test_setup_existing_cloud_user(hass, hass_storage):
|
||||||
assert hass_storage[STORAGE_KEY]["data"]["cloud_user"] == user.id
|
assert hass_storage[STORAGE_KEY]["data"]["cloud_user"] == user.id
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_invalid_cloud_user(hass, hass_storage):
|
|
||||||
"""Test setup with API push default data."""
|
|
||||||
hass_storage[STORAGE_KEY] = {"version": 1, "data": {"cloud_user": "non-existing"}}
|
|
||||||
with patch("hass_nabucasa.Cloud.start", return_value=mock_coro()):
|
|
||||||
result = await async_setup_component(
|
|
||||||
hass,
|
|
||||||
"cloud",
|
|
||||||
{
|
|
||||||
"http": {},
|
|
||||||
"cloud": {
|
|
||||||
cloud.CONF_MODE: cloud.MODE_DEV,
|
|
||||||
"cognito_client_id": "test-cognito_client_id",
|
|
||||||
"user_pool_id": "test-user_pool_id",
|
|
||||||
"region": "test-region",
|
|
||||||
"relayer": "test-relayer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert result
|
|
||||||
|
|
||||||
assert hass_storage[STORAGE_KEY]["data"]["cloud_user"] != "non-existing"
|
|
||||||
cloud_user = await hass.auth.async_get_user(
|
|
||||||
hass_storage[STORAGE_KEY]["data"]["cloud_user"]
|
|
||||||
)
|
|
||||||
|
|
||||||
assert cloud_user
|
|
||||||
assert cloud_user.groups[0].id == GROUP_ID_ADMIN
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_setup_cloud_user(hass, hass_storage):
|
|
||||||
"""Test setup with API push default data."""
|
|
||||||
hass_storage[STORAGE_KEY] = {"version": 1, "data": {"cloud_user": None}}
|
|
||||||
with patch("hass_nabucasa.Cloud.start", return_value=mock_coro()):
|
|
||||||
result = await async_setup_component(
|
|
||||||
hass,
|
|
||||||
"cloud",
|
|
||||||
{
|
|
||||||
"http": {},
|
|
||||||
"cloud": {
|
|
||||||
cloud.CONF_MODE: cloud.MODE_DEV,
|
|
||||||
"cognito_client_id": "test-cognito_client_id",
|
|
||||||
"user_pool_id": "test-user_pool_id",
|
|
||||||
"region": "test-region",
|
|
||||||
"relayer": "test-relayer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert result
|
|
||||||
|
|
||||||
cloud_user = await hass.auth.async_get_user(
|
|
||||||
hass_storage[STORAGE_KEY]["data"]["cloud_user"]
|
|
||||||
)
|
|
||||||
|
|
||||||
assert cloud_user
|
|
||||||
assert cloud_user.groups[0].id == GROUP_ID_ADMIN
|
|
||||||
|
|
||||||
|
|
||||||
async def test_on_connect(hass, mock_cloud_fixture):
|
async def test_on_connect(hass, mock_cloud_fixture):
|
||||||
"""Test cloud on connect triggers."""
|
"""Test cloud on connect triggers."""
|
||||||
cl = hass.data["cloud"]
|
cl = hass.data["cloud"]
|
||||||
|
|
||||||
assert len(cl.iot._on_connect) == 4
|
assert len(cl.iot._on_connect) == 3
|
||||||
|
|
||||||
assert len(hass.states.async_entity_ids("binary_sensor")) == 0
|
assert len(hass.states.async_entity_ids("binary_sensor")) == 0
|
||||||
|
|
||||||
|
|
80
tests/components/cloud/test_prefs.py
Normal file
80
tests/components/cloud/test_prefs.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
"""Test Cloud preferences."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||||
|
from homeassistant.components.cloud.prefs import CloudPreferences, STORAGE_KEY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_username(hass):
|
||||||
|
"""Test we clear config if we set different username."""
|
||||||
|
prefs = CloudPreferences(hass)
|
||||||
|
await prefs.async_initialize()
|
||||||
|
|
||||||
|
assert prefs.google_enabled
|
||||||
|
|
||||||
|
await prefs.async_update(google_enabled=False)
|
||||||
|
|
||||||
|
assert not prefs.google_enabled
|
||||||
|
|
||||||
|
await prefs.async_set_username("new-username")
|
||||||
|
|
||||||
|
assert prefs.google_enabled
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_username_migration(hass):
|
||||||
|
"""Test we not clear config if we had no username."""
|
||||||
|
prefs = CloudPreferences(hass)
|
||||||
|
|
||||||
|
with patch.object(prefs, "_empty_config", return_value=prefs._empty_config(None)):
|
||||||
|
await prefs.async_initialize()
|
||||||
|
|
||||||
|
assert prefs.google_enabled
|
||||||
|
|
||||||
|
await prefs.async_update(google_enabled=False)
|
||||||
|
|
||||||
|
assert not prefs.google_enabled
|
||||||
|
|
||||||
|
await prefs.async_set_username("new-username")
|
||||||
|
|
||||||
|
assert not prefs.google_enabled
|
||||||
|
|
||||||
|
|
||||||
|
async def test_load_invalid_cloud_user(hass, hass_storage):
|
||||||
|
"""Test loading cloud user with invalid storage."""
|
||||||
|
hass_storage[STORAGE_KEY] = {"version": 1, "data": {"cloud_user": "non-existing"}}
|
||||||
|
|
||||||
|
prefs = CloudPreferences(hass)
|
||||||
|
await prefs.async_initialize()
|
||||||
|
|
||||||
|
cloud_user_id = await prefs.get_cloud_user()
|
||||||
|
|
||||||
|
assert cloud_user_id != "non-existing"
|
||||||
|
|
||||||
|
cloud_user = await hass.auth.async_get_user(
|
||||||
|
hass_storage[STORAGE_KEY]["data"]["cloud_user"]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert cloud_user
|
||||||
|
assert cloud_user.groups[0].id == GROUP_ID_ADMIN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_remove_cloud_user(hass, hass_storage):
|
||||||
|
"""Test creating and removing cloud user."""
|
||||||
|
hass_storage[STORAGE_KEY] = {"version": 1, "data": {"cloud_user": None}}
|
||||||
|
|
||||||
|
prefs = CloudPreferences(hass)
|
||||||
|
await prefs.async_initialize()
|
||||||
|
await prefs.async_set_username("user1")
|
||||||
|
|
||||||
|
cloud_user = await hass.auth.async_get_user(await prefs.get_cloud_user())
|
||||||
|
|
||||||
|
assert cloud_user
|
||||||
|
assert cloud_user.groups[0].id == GROUP_ID_ADMIN
|
||||||
|
|
||||||
|
await prefs.async_set_username("user2")
|
||||||
|
|
||||||
|
cloud_user2 = await hass.auth.async_get_user(await prefs.get_cloud_user())
|
||||||
|
|
||||||
|
assert cloud_user2
|
||||||
|
assert cloud_user2.groups[0].id == GROUP_ID_ADMIN
|
||||||
|
assert cloud_user2.id != cloud_user.id
|
Loading…
Add table
Add a link
Reference in a new issue