Skip to content
4 changes: 4 additions & 0 deletions homeassistant/components/hassio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from homeassistant.loader import bind_hass
from homeassistant.util.dt import utcnow

from .auth import async_setup_auth
from .handler import HassIO, HassioAPIError
from .discovery import async_setup_discovery
from .http import HassIOView
Expand Down Expand Up @@ -280,4 +281,7 @@ async def async_handle_core_service(call):
# Init discovery Hass.io feature
async_setup_discovery(hass, hassio, config)

# Init auth Hass.io feature
async_setup_auth(hass)

return True
67 changes: 67 additions & 0 deletions homeassistant/components/hassio/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Implement the auth feature from Hass.io for Add-ons."""
import logging
from ipaddress import ip_address
import os

from aiohttp import web
Comment thread
pvizeli marked this conversation as resolved.
from aiohttp.web_exceptions import (
Comment thread
balloob marked this conversation as resolved.
HTTPForbidden, HTTPNotFound, HTTPUnauthorized)

from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.const import KEY_REAL_IP


_LOGGER = logging.getLogger(__name__)

ATTR_USERNAME = 'username'
ATTR_PASSWORD = 'password'


@callback
def async_setup_auth(hass):
"""Auth setup."""
hassio_auth = HassIOAuth(hass)
hass.http.register_view(hassio_auth)


class HassIOAuth(HomeAssistantView):
"""Hass.io view to handle base part."""

name = "api:hassio_auth"
url = "/api/hassio_auth"

def __init__(self, hass):
"""Initialize WebView."""
self.hass = hass

async def post(self, request):
Comment thread
pvizeli marked this conversation as resolved.
Outdated
"""Handle new discovery requests."""
hassio_ip = os.environ['HASSIO'].split(':')[0]
if request[KEY_REAL_IP] != ip_address(hassio_ip):
_LOGGER.error(
"Invalid auth request from %s", request[KEY_REAL_IP])
raise HTTPForbidden()

data = await request.json()
await self._check_login(data[ATTR_USERNAME], data[ATTR_PASSWORD])
return web.Response(status=200)

def _get_provider(self):
"""Return Homeassistant auth provider."""
for prv in self.hass.auth.auth_providers:
if prv.type == 'homeassistant':
return prv

_LOGGER.error("Can't find Home Assistant auth.")
raise HTTPNotFound()

async def _check_login(self, username, password):
"""Check User credentials."""
provider = self._get_provider()

Comment thread
pvizeli marked this conversation as resolved.
try:
await provider.async_validate_login(username, password)
except HomeAssistantError:
raise HTTPUnauthorized() from None
55 changes: 55 additions & 0 deletions tests/components/hassio/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""The tests for the hassio component."""
import asyncio

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

'asyncio' imported but unused

from unittest.mock import patch, Mock

from homeassistant.const import HTTP_HEADER_HA_AUTH
from homeassistant.exceptions import HomeAssistantError

from tests.common import mock_coro, register_auth_provider
from . import API_PASSWORD


async def test_login_success(hass, hassio_client):
"""Test no auth needed for ."""
await register_auth_provider(hass, {'type': 'homeassistant'})

with patch('homeassistant.auth.providers.homeassistant.'
'HassAuthProvider.async_validate_login',
Mock(return_value=mock_coro())) as mock_login:
resp = await hassio_client.post(
'/api/hassio_auth',
json={
"username": "test",
"password": "123456"
},
headers={
HTTP_HEADER_HA_AUTH: API_PASSWORD
}
)

# Check we got right response
assert resp.status == 200
mock_login.assert_called_with("test", "123456")


async def test_login_error(hass, hassio_client):
"""Test no auth needed for error."""
await register_auth_provider(hass, {'type': 'homeassistant'})

with patch('homeassistant.auth.providers.homeassistant.'
'HassAuthProvider.async_validate_login',
Mock(side_effect=HomeAssistantError())) as mock_login:
resp = await hassio_client.post(
'/api/hassio_auth',
json={
"username": "test",
"password": "123456"
},
headers={
HTTP_HEADER_HA_AUTH: API_PASSWORD
}
)

# Check we got right response
assert resp.status == 401
mock_login.assert_called_with("test", "123456")