mirror of
https://github.com/home-assistant/core.git
synced 2026-07-10 16:19:29 +01:00
Fix hassio auth IndexError on Supervisor Unix socket requests (#169911)
This commit is contained in:
committed by
Franck Nijhof
parent
aac49a567f
commit
a4227ef1bc
@@ -12,6 +12,7 @@ import voluptuous as vol
|
||||
from homeassistant.auth.models import User
|
||||
from homeassistant.auth.providers import homeassistant as auth_ha
|
||||
from homeassistant.components.http import KEY_HASS, KEY_HASS_USER, HomeAssistantView
|
||||
from homeassistant.components.http.const import is_supervisor_unix_socket_request
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -41,14 +42,18 @@ class HassIOBaseAuth(HomeAssistantView):
|
||||
|
||||
def _check_access(self, request: web.Request) -> None:
|
||||
"""Check if this call is from Supervisor."""
|
||||
# Check caller IP
|
||||
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
|
||||
assert request.transport
|
||||
if ip_address(request.transport.get_extra_info("peername")[0]) != ip_address(
|
||||
hassio_ip
|
||||
):
|
||||
_LOGGER.error("Invalid auth request from %s", request.remote)
|
||||
raise HTTPUnauthorized
|
||||
# Requests over the Supervisor Unix socket are authenticated by the
|
||||
# http auth middleware as the Supervisor user, so the caller-IP check
|
||||
# below does not apply (and would crash, since `peername` is empty for
|
||||
# Unix sockets). The user-ID check still runs to ensure only the
|
||||
# Supervisor user can reach this endpoint.
|
||||
if not is_supervisor_unix_socket_request(request):
|
||||
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
|
||||
assert request.transport
|
||||
peername = request.transport.get_extra_info("peername")
|
||||
if not peername or ip_address(peername[0]) != ip_address(hassio_ip):
|
||||
_LOGGER.error("Invalid auth request from %s", request.remote)
|
||||
raise HTTPUnauthorized
|
||||
|
||||
# Check caller token
|
||||
if request[KEY_HASS_USER].id != self.user.id:
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
"""The tests for the hassio component."""
|
||||
|
||||
from contextlib import AbstractContextManager, ExitStack as DefaultContext
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from aiohttp.web_exceptions import HTTPUnauthorized
|
||||
import pytest
|
||||
|
||||
from homeassistant.auth.providers.homeassistant import InvalidAuth
|
||||
from homeassistant.components.hassio.auth import HassIOBaseAuth
|
||||
from homeassistant.components.hassio.const import DATA_CONFIG_STORE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
async def test_auth_success(hassio_client_supervisor: TestClient) -> None:
|
||||
@@ -162,6 +168,45 @@ async def test_password_fails_no_auth(hassio_noauth_client: TestClient) -> None:
|
||||
assert resp.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("peername", "unix_socket", "expectation"),
|
||||
[
|
||||
# Unix socket transports report an empty string for peername. Before
|
||||
# the fix this raised IndexError on `peername[0]`.
|
||||
("", True, DefaultContext()),
|
||||
# Defensive: a TCP transport with no peername at all should be
|
||||
# rejected, not crash.
|
||||
(None, False, pytest.raises(HTTPUnauthorized)),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("hassio_stubs")
|
||||
async def test_check_access_unix_socket_or_missing_peername(
|
||||
hass: HomeAssistant,
|
||||
peername: str | None,
|
||||
unix_socket: bool,
|
||||
expectation: AbstractContextManager,
|
||||
) -> None:
|
||||
"""Test _check_access handles Unix socket requests and missing peername."""
|
||||
hassio_user_id = hass.data[DATA_CONFIG_STORE].data.hassio_user
|
||||
assert hassio_user_id is not None
|
||||
user = await hass.auth.async_get_user(hassio_user_id)
|
||||
assert user is not None
|
||||
|
||||
auth_view = HassIOBaseAuth(hass, user)
|
||||
request = MagicMock()
|
||||
request.transport.get_extra_info.return_value = peername
|
||||
request.__getitem__.return_value = user
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.hassio.auth.is_supervisor_unix_socket_request",
|
||||
return_value=unix_socket,
|
||||
),
|
||||
expectation,
|
||||
):
|
||||
auth_view._check_access(request)
|
||||
|
||||
|
||||
async def test_password_no_user(hassio_client_supervisor: TestClient) -> None:
|
||||
"""Test changing password for invalid user."""
|
||||
resp = await hassio_client_supervisor.post(
|
||||
|
||||
Reference in New Issue
Block a user