Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Google Assistant User with Cloud #22042

Merged
merged 7 commits into from
Mar 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions homeassistant/components/cloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe GROUP_ID_USER?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should first see how our permissions will work out before I would want to consider that. The downside of the user group is that things like stop/restart HA will not be accessible.

Copy link
Contributor

@awarecan awarecan Mar 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a Cloud user, but in my assumption of HA Cloud function, this user is reserved for the Alexa/GA or other cloud web hook integration purpose. If user want to do admin task, such as restart, he could remote login via Cloud by using the administer creds

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the user rights are mapped local in Home Assistant and not inside the cloud. That means there is all transparent and controlled by local instance and not by external cloud.

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)
Expand Down
12 changes: 8 additions & 4 deletions homeassistant/components/cloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/cloud/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
14 changes: 11 additions & 3 deletions homeassistant/components/cloud/prefs.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -26,21 +26,24 @@ 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),
(PREF_ENABLE_ALEXA, alexa_enabled),
(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
Expand Down Expand Up @@ -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)
77 changes: 64 additions & 13 deletions tests/components/cloud/test_init.py
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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