Skip to content

Commit

Permalink
WIP: Hass.io sent token to supervisor (#15536)
Browse files Browse the repository at this point in the history
Hass.io sent token to supervisor
  • Loading branch information
balloob authored Jul 23, 2018
1 parent 4e7dbf9 commit 8213b14
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 71 deletions.
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)

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

0 comments on commit 8213b14

Please sign in to comment.