Add Hass.io user headers to supervisor proxy (#19395)
* Add Hass.io user headers to supervisor proxy * Update test_http.py * Fix tests * Update test_auth.py
This commit is contained in:
commit
2ffadde0a3
6 changed files with 54 additions and 18 deletions
homeassistant/components/hassio
tests
|
@ -10,3 +10,5 @@ ATTR_USERNAME = 'username'
|
||||||
ATTR_PASSWORD = 'password'
|
ATTR_PASSWORD = 'password'
|
||||||
|
|
||||||
X_HASSIO = 'X-HASSIO-KEY'
|
X_HASSIO = 'X-HASSIO-KEY'
|
||||||
|
X_HASS_USER_ID = 'X-HASS-USER-ID'
|
||||||
|
X_HASS_IS_ADMIN = 'X-HASS-IS-ADMIN'
|
||||||
|
|
|
@ -17,7 +17,7 @@ from aiohttp.web_exceptions import HTTPBadGateway
|
||||||
|
|
||||||
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
|
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
|
||||||
|
|
||||||
from .const import X_HASSIO
|
from .const import X_HASSIO, X_HASS_USER_ID, X_HASS_IS_ADMIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -75,9 +75,16 @@ class HassIOView(HomeAssistantView):
|
||||||
read_timeout = _get_timeout(path)
|
read_timeout = _get_timeout(path)
|
||||||
hass = request.app['hass']
|
hass = request.app['hass']
|
||||||
|
|
||||||
try:
|
|
||||||
data = None
|
data = None
|
||||||
headers = {X_HASSIO: os.environ.get('HASSIO_TOKEN', "")}
|
headers = {
|
||||||
|
X_HASSIO: os.environ.get('HASSIO_TOKEN', ""),
|
||||||
|
}
|
||||||
|
user = request.get('hass_user')
|
||||||
|
if user is not None:
|
||||||
|
headers[X_HASS_USER_ID] = request['hass_user'].id
|
||||||
|
headers[X_HASS_IS_ADMIN] = str(int(request['hass_user'].is_admin))
|
||||||
|
|
||||||
|
try:
|
||||||
with async_timeout.timeout(10, loop=hass.loop):
|
with async_timeout.timeout(10, loop=hass.loop):
|
||||||
data = await request.read()
|
data = await request.read()
|
||||||
if data:
|
if data:
|
||||||
|
|
|
@ -27,7 +27,7 @@ def hassio_env():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def hassio_client(hassio_env, hass, aiohttp_client, legacy_auth):
|
def hassio_stubs(hassio_env, hass, hass_client, aioclient_mock):
|
||||||
"""Create mock hassio http client."""
|
"""Create mock hassio http client."""
|
||||||
with patch('homeassistant.components.hassio.HassIO.update_hass_api',
|
with patch('homeassistant.components.hassio.HassIO.update_hass_api',
|
||||||
Mock(return_value=mock_coro({"result": "ok"}))), \
|
Mock(return_value=mock_coro({"result": "ok"}))), \
|
||||||
|
@ -40,13 +40,27 @@ def hassio_client(hassio_env, hass, aiohttp_client, legacy_auth):
|
||||||
'api_password': API_PASSWORD
|
'api_password': API_PASSWORD
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hassio_client(hassio_stubs, hass, hass_client):
|
||||||
|
"""Return a Hass.io HTTP client."""
|
||||||
|
yield hass.loop.run_until_complete(hass_client())
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def hassio_noauth_client(hassio_stubs, hass, aiohttp_client):
|
||||||
|
"""Return a Hass.io HTTP client without auth."""
|
||||||
yield hass.loop.run_until_complete(aiohttp_client(hass.http.app))
|
yield hass.loop.run_until_complete(aiohttp_client(hass.http.app))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def hassio_handler(hass, aioclient_mock):
|
def hassio_handler(hass, aioclient_mock):
|
||||||
"""Create mock hassio handler."""
|
"""Create mock hassio handler."""
|
||||||
websession = hass.helpers.aiohttp_client.async_get_clientsession()
|
async def get_client_session():
|
||||||
|
return hass.helpers.aiohttp_client.async_get_clientsession()
|
||||||
|
|
||||||
|
websession = hass.loop.run_until_complete(get_client_session())
|
||||||
|
|
||||||
with patch.dict(os.environ, {'HASSIO_TOKEN': HASSIO_TOKEN}):
|
with patch.dict(os.environ, {'HASSIO_TOKEN': HASSIO_TOKEN}):
|
||||||
yield HassIO(hass.loop, websession, "127.0.0.1")
|
yield HassIO(hass.loop, websession, "127.0.0.1")
|
||||||
|
|
|
@ -4,14 +4,12 @@ from unittest.mock import patch, Mock
|
||||||
from homeassistant.const import HTTP_HEADER_HA_AUTH
|
from homeassistant.const import HTTP_HEADER_HA_AUTH
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from tests.common import mock_coro, register_auth_provider
|
from tests.common import mock_coro
|
||||||
from . import API_PASSWORD
|
from . import API_PASSWORD
|
||||||
|
|
||||||
|
|
||||||
async def test_login_success(hass, hassio_client):
|
async def test_login_success(hass, hassio_client):
|
||||||
"""Test no auth needed for ."""
|
"""Test no auth needed for ."""
|
||||||
await register_auth_provider(hass, {'type': 'homeassistant'})
|
|
||||||
|
|
||||||
with patch('homeassistant.auth.providers.homeassistant.'
|
with patch('homeassistant.auth.providers.homeassistant.'
|
||||||
'HassAuthProvider.async_validate_login',
|
'HassAuthProvider.async_validate_login',
|
||||||
Mock(return_value=mock_coro())) as mock_login:
|
Mock(return_value=mock_coro())) as mock_login:
|
||||||
|
@ -34,8 +32,6 @@ async def test_login_success(hass, hassio_client):
|
||||||
|
|
||||||
async def test_login_error(hass, hassio_client):
|
async def test_login_error(hass, hassio_client):
|
||||||
"""Test no auth needed for error."""
|
"""Test no auth needed for error."""
|
||||||
await register_auth_provider(hass, {'type': 'homeassistant'})
|
|
||||||
|
|
||||||
with patch('homeassistant.auth.providers.homeassistant.'
|
with patch('homeassistant.auth.providers.homeassistant.'
|
||||||
'HassAuthProvider.async_validate_login',
|
'HassAuthProvider.async_validate_login',
|
||||||
Mock(side_effect=HomeAssistantError())) as mock_login:
|
Mock(side_effect=HomeAssistantError())) as mock_login:
|
||||||
|
@ -58,8 +54,6 @@ async def test_login_error(hass, hassio_client):
|
||||||
|
|
||||||
async def test_login_no_data(hass, hassio_client):
|
async def test_login_no_data(hass, hassio_client):
|
||||||
"""Test auth with no data -> error."""
|
"""Test auth with no data -> error."""
|
||||||
await register_auth_provider(hass, {'type': 'homeassistant'})
|
|
||||||
|
|
||||||
with patch('homeassistant.auth.providers.homeassistant.'
|
with patch('homeassistant.auth.providers.homeassistant.'
|
||||||
'HassAuthProvider.async_validate_login',
|
'HassAuthProvider.async_validate_login',
|
||||||
Mock(side_effect=HomeAssistantError())) as mock_login:
|
Mock(side_effect=HomeAssistantError())) as mock_login:
|
||||||
|
@ -77,8 +71,6 @@ async def test_login_no_data(hass, hassio_client):
|
||||||
|
|
||||||
async def test_login_no_username(hass, hassio_client):
|
async def test_login_no_username(hass, hassio_client):
|
||||||
"""Test auth with no username in data -> error."""
|
"""Test auth with no username in data -> error."""
|
||||||
await register_auth_provider(hass, {'type': 'homeassistant'})
|
|
||||||
|
|
||||||
with patch('homeassistant.auth.providers.homeassistant.'
|
with patch('homeassistant.auth.providers.homeassistant.'
|
||||||
'HassAuthProvider.async_validate_login',
|
'HassAuthProvider.async_validate_login',
|
||||||
Mock(side_effect=HomeAssistantError())) as mock_login:
|
Mock(side_effect=HomeAssistantError())) as mock_login:
|
||||||
|
@ -100,8 +92,6 @@ async def test_login_no_username(hass, hassio_client):
|
||||||
|
|
||||||
async def test_login_success_extra(hass, hassio_client):
|
async def test_login_success_extra(hass, hassio_client):
|
||||||
"""Test auth with extra data."""
|
"""Test auth with extra data."""
|
||||||
await register_auth_provider(hass, {'type': 'homeassistant'})
|
|
||||||
|
|
||||||
with patch('homeassistant.auth.providers.homeassistant.'
|
with patch('homeassistant.auth.providers.homeassistant.'
|
||||||
'HassAuthProvider.async_validate_login',
|
'HassAuthProvider.async_validate_login',
|
||||||
Mock(return_value=mock_coro())) as mock_login:
|
Mock(return_value=mock_coro())) as mock_login:
|
||||||
|
|
|
@ -40,9 +40,10 @@ def test_forward_request(hassio_client):
|
||||||
'build_type', [
|
'build_type', [
|
||||||
'supervisor/info', 'homeassistant/update', 'host/info'
|
'supervisor/info', 'homeassistant/update', 'host/info'
|
||||||
])
|
])
|
||||||
def test_auth_required_forward_request(hassio_client, build_type):
|
def test_auth_required_forward_request(hassio_noauth_client, build_type):
|
||||||
"""Test auth required for normal request."""
|
"""Test auth required for normal request."""
|
||||||
resp = yield from hassio_client.post("/api/hassio/{}".format(build_type))
|
resp = yield from hassio_noauth_client.post(
|
||||||
|
"/api/hassio/{}".format(build_type))
|
||||||
|
|
||||||
# Check we got right response
|
# Check we got right response
|
||||||
assert resp.status == 401
|
assert resp.status == 401
|
||||||
|
@ -135,3 +136,20 @@ def test_bad_gateway_when_cannot_find_supervisor(hassio_client):
|
||||||
HTTP_HEADER_HA_AUTH: API_PASSWORD
|
HTTP_HEADER_HA_AUTH: API_PASSWORD
|
||||||
})
|
})
|
||||||
assert resp.status == 502
|
assert resp.status == 502
|
||||||
|
|
||||||
|
|
||||||
|
async def test_forwarding_user_info(hassio_client, hass_admin_user,
|
||||||
|
aioclient_mock):
|
||||||
|
"""Test that we forward user info correctly."""
|
||||||
|
aioclient_mock.get('http://127.0.0.1/hello')
|
||||||
|
|
||||||
|
resp = await hassio_client.get('/api/hassio/hello')
|
||||||
|
|
||||||
|
# Check we got right response
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
assert len(aioclient_mock.mock_calls) == 1
|
||||||
|
|
||||||
|
req_headers = aioclient_mock.mock_calls[0][-1]
|
||||||
|
req_headers['X-HASS-USER-ID'] == hass_admin_user.id
|
||||||
|
req_headers['X-HASS-IS-ADMIN'] == '1'
|
||||||
|
|
|
@ -182,6 +182,11 @@ class AiohttpClientMockResponse:
|
||||||
"""Return yarl of URL."""
|
"""Return yarl of URL."""
|
||||||
return self._url
|
return self._url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content_type(self):
|
||||||
|
"""Return yarl of URL."""
|
||||||
|
return self._headers.get('content-type')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def content(self):
|
def content(self):
|
||||||
"""Return content."""
|
"""Return content."""
|
||||||
|
|
Loading…
Add table
Reference in a new issue