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

Hass.io sent token to supervisor #15536

Merged
merged 2 commits into from
Jul 23, 2018
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
26 changes: 24 additions & 2 deletions homeassistant/components/hassio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

DOMAIN = 'hassio'
DEPENDENCIES = ['http']
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1

CONF_FRONTEND_REPO = 'development_repo'

Expand Down Expand Up @@ -167,6 +169,21 @@ def async_setup(hass, config):
_LOGGER.error("Not connected with Hass.io")
return False

store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
data = yield from store.async_load()

if data is None:
data = {}

if 'hassio_user' in data:
user = yield from hass.auth.async_get_user(data['hassio_user'])
refresh_token = list(user.refresh_tokens.values())[0]
else:
user = yield from hass.auth.async_create_system_user('Hass.io')
refresh_token = yield from hass.auth.async_create_refresh_token(user)
data['hassio_user'] = user.id
yield from store.async_save(data)

# This overrides the normal API call that would be forwarded
development_repo = config.get(DOMAIN, {}).get(CONF_FRONTEND_REPO)
if development_repo is not None:
Expand All @@ -186,8 +203,13 @@ def async_setup(hass, config):
embed_iframe=True,
)

if 'http' in config:
yield from hassio.update_hass_api(config['http'])
# Temporary. No refresh token tells supervisor to use API password.
if hass.auth.active:
token = refresh_token.token
else:
token = None

yield from hassio.update_hass_api(config.get('http', {}), token)
Copy link
Member

Choose a reason for hiding this comment

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

Do we want migrate it to await?

Copy link
Member Author

Choose a reason for hiding this comment

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

In a different PR.


if 'homeassistant' in config:
yield from hassio.update_hass_timezone(config['homeassistant'])
Expand Down
21 changes: 9 additions & 12 deletions homeassistant/components/hassio/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,19 @@

def _api_bool(funct):
"""Return a boolean."""
@asyncio.coroutine
def _wrapper(*argv, **kwargs):
async def _wrapper(*argv, **kwargs):
"""Wrap function."""
data = yield from funct(*argv, **kwargs)
data = await funct(*argv, **kwargs)
return data and data['result'] == "ok"

return _wrapper


def _api_data(funct):
"""Return data of an api."""
@asyncio.coroutine
def _wrapper(*argv, **kwargs):
async def _wrapper(*argv, **kwargs):
"""Wrap function."""
data = yield from funct(*argv, **kwargs)
data = await funct(*argv, **kwargs)
if data and data['result'] == "ok":
return data['data']
return None
Expand Down Expand Up @@ -94,24 +92,23 @@ def check_homeassistant_config(self):
return self.send_command("/homeassistant/check", timeout=300)

@_api_bool
def update_hass_api(self, http_config):
"""Update Home Assistant API data on Hass.io.

This method return a coroutine.
"""
async def update_hass_api(self, http_config, refresh_token):
"""Update Home Assistant API data on Hass.io."""
port = http_config.get(CONF_SERVER_PORT) or SERVER_PORT
options = {
'ssl': CONF_SSL_CERTIFICATE in http_config,
'port': port,
'password': http_config.get(CONF_API_PASSWORD),
'watchdog': True,
'refresh_token': refresh_token,
}

if CONF_SERVER_HOST in http_config:
options['watchdog'] = False
_LOGGER.warning("Don't use 'server_host' options with Hass.io")

return self.send_command("/homeassistant/options", payload=options)
return await self.send_command("/homeassistant/options",
payload=options)

@_api_bool
def update_hass_timezone(self, core_config):
Expand Down
136 changes: 79 additions & 57 deletions tests/components/hassio/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import os
from unittest.mock import patch, Mock

import pytest

from homeassistant.setup import async_setup_component
from homeassistant.components.hassio import async_check_config
from homeassistant.components.hassio import (
STORAGE_KEY, async_check_config)

from tests.common import mock_coro

Expand All @@ -15,35 +18,35 @@
}


@asyncio.coroutine
def test_setup_api_ping(hass, aioclient_mock):
"""Test setup with API ping."""
@pytest.fixture(autouse=True)
def mock_all(aioclient_mock):
"""Mock all setup requests."""
aioclient_mock.post(
"http://127.0.0.1/homeassistant/options", json={'result': 'ok'})
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
aioclient_mock.post(
"http://127.0.0.1/supervisor/options", json={'result': 'ok'})
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'ok', 'data': {'last_version': '10.0'}})


@asyncio.coroutine
def test_setup_api_ping(hass, aioclient_mock):
"""Test setup with API ping."""
with patch.dict(os.environ, MOCK_ENVIRON):
result = yield from async_setup_component(hass, 'hassio', {})
assert result

assert aioclient_mock.call_count == 2
assert aioclient_mock.call_count == 3
assert hass.components.hassio.get_homeassistant_version() == "10.0"
assert hass.components.hassio.is_hassio()


@asyncio.coroutine
def test_setup_api_push_api_data(hass, aioclient_mock):
"""Test setup with API push."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'ok', 'data': {'last_version': '10.0'}})
aioclient_mock.post(
"http://127.0.0.1/homeassistant/options", json={'result': 'ok'})

with patch.dict(os.environ, MOCK_ENVIRON):
result = yield from async_setup_component(hass, 'hassio', {
'http': {
Expand All @@ -64,14 +67,6 @@ def test_setup_api_push_api_data(hass, aioclient_mock):
@asyncio.coroutine
def test_setup_api_push_api_data_server_host(hass, aioclient_mock):
"""Test setup with API push with active server host."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'ok', 'data': {'last_version': '10.0'}})
aioclient_mock.post(
"http://127.0.0.1/homeassistant/options", json={'result': 'ok'})

with patch.dict(os.environ, MOCK_ENVIRON):
result = yield from async_setup_component(hass, 'hassio', {
'http': {
Expand All @@ -90,19 +85,61 @@ def test_setup_api_push_api_data_server_host(hass, aioclient_mock):
assert not aioclient_mock.mock_calls[1][2]['watchdog']


@asyncio.coroutine
def test_setup_api_push_api_data_default(hass, aioclient_mock):
async def test_setup_api_push_api_data_default(hass, aioclient_mock,
hass_storage):
"""Test setup with API push default data."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'ok', 'data': {'last_version': '10.0'}})
aioclient_mock.post(
"http://127.0.0.1/homeassistant/options", json={'result': 'ok'})
with patch.dict(os.environ, MOCK_ENVIRON), \
patch('homeassistant.auth.AuthManager.active', return_value=True):
result = await async_setup_component(hass, 'hassio', {
'http': {},
'hassio': {}
})
assert result

assert aioclient_mock.call_count == 3
assert not aioclient_mock.mock_calls[1][2]['ssl']
assert aioclient_mock.mock_calls[1][2]['password'] is None
assert aioclient_mock.mock_calls[1][2]['port'] == 8123
refresh_token = aioclient_mock.mock_calls[1][2]['refresh_token']
hassio_user = await hass.auth.async_get_user(
hass_storage[STORAGE_KEY]['data']['hassio_user']
)
assert hassio_user is not None
assert hassio_user.system_generated
assert refresh_token in hassio_user.refresh_tokens


async def test_setup_api_push_api_data_no_auth(hass, aioclient_mock,
hass_storage):
"""Test setup with API push default data."""
with patch.dict(os.environ, MOCK_ENVIRON):
result = yield from async_setup_component(hass, 'hassio', {
result = await async_setup_component(hass, 'hassio', {
'http': {},
'hassio': {}
})
assert result

assert aioclient_mock.call_count == 3
assert not aioclient_mock.mock_calls[1][2]['ssl']
assert aioclient_mock.mock_calls[1][2]['password'] is None
assert aioclient_mock.mock_calls[1][2]['port'] == 8123
assert aioclient_mock.mock_calls[1][2]['refresh_token'] is None


async def test_setup_api_existing_hassio_user(hass, aioclient_mock,
hass_storage):
"""Test setup with API push default data."""
user = await hass.auth.async_create_system_user('Hass.io test')
token = await hass.auth.async_create_refresh_token(user)
hass_storage[STORAGE_KEY] = {
'version': 1,
'data': {
'hassio_user': user.id
}
}
with patch.dict(os.environ, MOCK_ENVIRON), \
patch('homeassistant.auth.AuthManager.active', return_value=True):
result = await async_setup_component(hass, 'hassio', {
'http': {},
'hassio': {}
})
Expand All @@ -112,19 +149,12 @@ def test_setup_api_push_api_data_default(hass, aioclient_mock):
assert not aioclient_mock.mock_calls[1][2]['ssl']
assert aioclient_mock.mock_calls[1][2]['password'] is None
assert aioclient_mock.mock_calls[1][2]['port'] == 8123
assert aioclient_mock.mock_calls[1][2]['refresh_token'] == token.token


@asyncio.coroutine
def test_setup_core_push_timezone(hass, aioclient_mock):
"""Test setup with API push default data."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'ok', 'data': {'last_version': '10.0'}})
aioclient_mock.post(
"http://127.0.0.1/supervisor/options", json={'result': 'ok'})

with patch.dict(os.environ, MOCK_ENVIRON):
result = yield from async_setup_component(hass, 'hassio', {
'hassio': {},
Expand All @@ -134,29 +164,21 @@ def test_setup_core_push_timezone(hass, aioclient_mock):
})
assert result

assert aioclient_mock.call_count == 3
assert aioclient_mock.mock_calls[1][2]['timezone'] == "testzone"
assert aioclient_mock.call_count == 4
assert aioclient_mock.mock_calls[2][2]['timezone'] == "testzone"


@asyncio.coroutine
def test_setup_hassio_no_additional_data(hass, aioclient_mock):
"""Test setup with API push default data."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'ok', 'data': {'last_version': '10.0'}})
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={'result': 'ok'})

with patch.dict(os.environ, MOCK_ENVIRON), \
patch.dict(os.environ, {'HASSIO_TOKEN': "123456"}):
result = yield from async_setup_component(hass, 'hassio', {
'hassio': {},
})
assert result

assert aioclient_mock.call_count == 2
assert aioclient_mock.call_count == 3
assert aioclient_mock.mock_calls[-1][3]['X-HASSIO-KEY'] == "123456"


Expand Down Expand Up @@ -234,14 +256,14 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
'hassio', 'addon_stdin', {'addon': 'test', 'input': 'test'})
yield from hass.async_block_till_done()

assert aioclient_mock.call_count == 4
assert aioclient_mock.call_count == 5
assert aioclient_mock.mock_calls[-1][2] == 'test'

yield from hass.services.async_call('hassio', 'host_shutdown', {})
yield from hass.services.async_call('hassio', 'host_reboot', {})
yield from hass.async_block_till_done()

assert aioclient_mock.call_count == 6
assert aioclient_mock.call_count == 7

yield from hass.services.async_call('hassio', 'snapshot_full', {})
yield from hass.services.async_call('hassio', 'snapshot_partial', {
Expand All @@ -251,7 +273,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
})
yield from hass.async_block_till_done()

assert aioclient_mock.call_count == 8
assert aioclient_mock.call_count == 9
assert aioclient_mock.mock_calls[-1][2] == {
'addons': ['test'], 'folders': ['ssl'], 'password': "123456"}

Expand All @@ -267,7 +289,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock):
})
yield from hass.async_block_till_done()

assert aioclient_mock.call_count == 10
assert aioclient_mock.call_count == 11
assert aioclient_mock.mock_calls[-1][2] == {
'addons': ['test'], 'folders': ['ssl'], 'homeassistant': False,
'password': "123456"
Expand All @@ -289,17 +311,17 @@ def test_service_calls_core(hassio_env, hass, aioclient_mock):
yield from hass.services.async_call('homeassistant', 'stop')
yield from hass.async_block_till_done()

assert aioclient_mock.call_count == 1
assert aioclient_mock.call_count == 2

yield from hass.services.async_call('homeassistant', 'check_config')
yield from hass.async_block_till_done()

assert aioclient_mock.call_count == 2
assert aioclient_mock.call_count == 3

yield from hass.services.async_call('homeassistant', 'restart')
yield from hass.async_block_till_done()

assert aioclient_mock.call_count == 4
assert aioclient_mock.call_count == 5


@asyncio.coroutine
Expand Down