From 6a80ffa8ccfd6a3de9388f5637d51636e8f18d57 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 15 Mar 2019 00:18:31 +0100 Subject: [PATCH] Fix Google Assistant User with Cloud (#22042) * Fix Google Assistant User with Cloud * Fix User Agent ID * respell * Fix object * Fix tests * fix lint * Fix lint --- homeassistant/components/cloud/__init__.py | 10 +++ homeassistant/components/cloud/client.py | 12 ++-- homeassistant/components/cloud/const.py | 1 + homeassistant/components/cloud/prefs.py | 14 +++- tests/components/cloud/test_init.py | 77 ++++++++++++++++++---- 5 files changed, 94 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index c854fe69be9ca1..2e324f0673802c 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -3,6 +3,7 @@ import voluptuous as vol +from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components.alexa import smart_home as alexa_sh from homeassistant.components.google_assistant import const as ga_c from homeassistant.const import ( @@ -136,12 +137,21 @@ async def async_setup(hass, config): else: kwargs = {CONF_MODE: DEFAULT_MODE} + # Alexa/Google custom config alexa_conf = kwargs.pop(CONF_ALEXA, None) or ALEXA_SCHEMA({}) google_conf = kwargs.pop(CONF_GOOGLE_ACTIONS, None) or GACTIONS_SCHEMA({}) + # Cloud settings prefs = CloudPreferences(hass) await prefs.async_initialize() + # Cloud user + if not prefs.cloud_user: + user = await hass.auth.async_create_system_user( + 'Home Assistant Cloud', [GROUP_ID_ADMIN]) + await prefs.async_update(cloud_user=user.id) + + # Initialize Cloud websession = hass.helpers.aiohttp_client.async_get_clientsession() client = CloudClient(hass, prefs, websession, alexa_conf, google_conf) cloud = hass.data[DOMAIN] = Cloud(client, **kwargs) diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index 063a9daf00a435..f73c16b19040f5 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -136,12 +136,16 @@ async def async_google_message( if not self._prefs.google_enabled: return ga.turned_off_response(payload) - cloud = self._hass.data[DOMAIN] - return await ga.async_handle_message( - self._hass, self.google_config, - cloud.claims['cognito:username'], payload + answer = await ga.async_handle_message( + self._hass, self.google_config, self.prefs.cloud_user, payload ) + # Fix AgentUserId + cloud = self._hass.data[DOMAIN] + answer['payload']['agentUserId'] = cloud.claims['cognito:username'] + + return answer + async def async_webhook_message( self, payload: Dict[Any, Any]) -> Dict[Any, Any]: """Process cloud webhook message to client.""" diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index 65e026389f05a4..fdedacd6dbbef6 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -7,6 +7,7 @@ PREF_ENABLE_REMOTE = 'remote_enabled' PREF_GOOGLE_ALLOW_UNLOCK = 'google_allow_unlock' PREF_CLOUDHOOKS = 'cloudhooks' +PREF_CLOUD_USER = 'cloud_user' CONF_ALEXA = 'alexa' CONF_ALIASES = 'aliases' diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index 263c17935cbafe..16ff8f0c213309 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -1,7 +1,7 @@ """Preference management for cloud.""" from .const import ( DOMAIN, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE, PREF_ENABLE_REMOTE, - PREF_GOOGLE_ALLOW_UNLOCK, PREF_CLOUDHOOKS) + PREF_GOOGLE_ALLOW_UNLOCK, PREF_CLOUDHOOKS, PREF_CLOUD_USER) STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 @@ -26,14 +26,16 @@ async def async_initialize(self): PREF_ENABLE_GOOGLE: True, PREF_ENABLE_REMOTE: False, PREF_GOOGLE_ALLOW_UNLOCK: False, - PREF_CLOUDHOOKS: {} + PREF_CLOUDHOOKS: {}, + PREF_CLOUD_USER: None, } self._prefs = prefs async def async_update(self, *, google_enabled=_UNDEF, alexa_enabled=_UNDEF, remote_enabled=_UNDEF, - google_allow_unlock=_UNDEF, cloudhooks=_UNDEF): + google_allow_unlock=_UNDEF, cloudhooks=_UNDEF, + cloud_user=_UNDEF): """Update user preferences.""" for key, value in ( (PREF_ENABLE_GOOGLE, google_enabled), @@ -41,6 +43,7 @@ async def async_update(self, *, google_enabled=_UNDEF, (PREF_ENABLE_REMOTE, remote_enabled), (PREF_GOOGLE_ALLOW_UNLOCK, google_allow_unlock), (PREF_CLOUDHOOKS, cloudhooks), + (PREF_CLOUD_USER, cloud_user), ): if value is not _UNDEF: self._prefs[key] = value @@ -75,3 +78,8 @@ def google_allow_unlock(self): def cloudhooks(self): """Return the published cloud webhooks.""" return self._prefs.get(PREF_CLOUDHOOKS, {}) + + @property + def cloud_user(self) -> str: + """Return ID from Home Assistant Cloud system user.""" + return self._prefs.get(PREF_CLOUD_USER) diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index d3e2e50f3a7d14..0de395c8bbc97d 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -1,24 +1,21 @@ """Test the cloud component.""" -from unittest.mock import MagicMock, patch +from unittest.mock import patch -from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START) +from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import cloud from homeassistant.components.cloud.const import DOMAIN - +from homeassistant.components.cloud.prefs import STORAGE_KEY +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +from homeassistant.setup import async_setup_component from tests.common import mock_coro -async def test_constructor_loads_info_from_config(): +async def test_constructor_loads_info_from_config(hass): """Test non-dev mode loads info from SERVERS constant.""" - hass = MagicMock(data={}) - - with patch( - "homeassistant.components.cloud.prefs.CloudPreferences." - "async_initialize", - return_value=mock_coro() - ): - result = await cloud.async_setup(hass, { + 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', @@ -79,3 +76,57 @@ async def test_startup_shutdown_events(hass, mock_cloud_fixture): await hass.async_block_till_done() assert mock_stop.called + + +async def test_setup_existing_cloud_user(hass, hass_storage): + """Test setup with API push default data.""" + user = await hass.auth.async_create_system_user('Cloud test') + hass_storage[STORAGE_KEY] = { + 'version': 1, + 'data': { + 'cloud_user': user.id + } + } + 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'] == user.id + + +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