From ffaeb2b96daf62219801723487d9ba841b502fd3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 6 Nov 2020 11:52:11 +0100 Subject: [PATCH] Add validate session API (#2223) --- supervisor/api/__init__.py | 1 + supervisor/api/ingress.py | 17 ++++++++++++++- tests/api/test_ingress.py | 43 ++++++++++++++++++++++++++++++++++++++ tests/api/test_network.py | 2 +- 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/api/test_ingress.py diff --git a/supervisor/api/__init__.py b/supervisor/api/__init__.py index bf9b6723e..64b6c708c 100644 --- a/supervisor/api/__init__.py +++ b/supervisor/api/__init__.py @@ -342,6 +342,7 @@ class RestAPI(CoreSysAttributes): self.webapp.add_routes( [ web.post("/ingress/session", api_ingress.create_session), + web.post("/ingress/validate_session", api_ingress.validate_session), web.get("/ingress/panels", api_ingress.panels), web.view("/ingress/{token}/{path:.*}", api_ingress.handler), ] diff --git a/supervisor/api/ingress.py b/supervisor/api/ingress.py index fc8f84d36..7e3098a5f 100644 --- a/supervisor/api/ingress.py +++ b/supervisor/api/ingress.py @@ -12,6 +12,7 @@ from aiohttp.web_exceptions import ( HTTPUnauthorized, ) from multidict import CIMultiDict, istr +import voluptuous as vol from ..addons.addon import Addon from ..const import ( @@ -27,10 +28,12 @@ from ..const import ( REQUEST_FROM, ) from ..coresys import CoreSysAttributes -from .utils import api_process +from .utils import api_process, api_validate _LOGGER: logging.Logger = logging.getLogger(__name__) +VALIDATE_SESSION_DATA = vol.Schema({ATTR_SESSION: str}) + class APIIngress(CoreSysAttributes): """Ingress view to handle add-on webui routing.""" @@ -78,6 +81,18 @@ class APIIngress(CoreSysAttributes): session = self.sys_ingress.create_session() return {ATTR_SESSION: session} + @api_process + async def validate_session(self, request: web.Request) -> Dict[str, Any]: + """Validate session and extending how long it's valid for.""" + self._check_ha_access(request) + + data = await api_validate(VALIDATE_SESSION_DATA, request) + + # Check Ingress Session + if not self.sys_ingress.validate_session(data[ATTR_SESSION]): + _LOGGER.warning("No valid ingress session %s", data[ATTR_SESSION]) + raise HTTPUnauthorized() + async def handler( self, request: web.Request ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]: diff --git a/tests/api/test_ingress.py b/tests/api/test_ingress.py new file mode 100644 index 000000000..4da5aea07 --- /dev/null +++ b/tests/api/test_ingress.py @@ -0,0 +1,43 @@ +"""Test ingress API.""" +from unittest.mock import patch + +import pytest + + +@pytest.fixture +def stub_auth(): + """Bypass auth check.""" + with patch("supervisor.api.ingress.APIIngress._check_ha_access") as mock_auth: + yield mock_auth + + +@pytest.mark.asyncio +async def test_validate_session(stub_auth, api_client, coresys): + """Test validating ingress session.""" + coresys.core.sys_homeassistant.supervisor_token = "ABCD" + resp = await api_client.post( + "/ingress/validate_session", + json={"session": "non-existing"}, + ) + assert resp.status == 401 + assert len(stub_auth.mock_calls) == 1 + + resp = await api_client.post("/ingress/session") + result = await resp.json() + assert len(stub_auth.mock_calls) == 2 + + assert "session" in result["data"] + session = result["data"]["session"] + assert session in coresys.ingress.sessions + + valid_time = coresys.ingress.sessions[session] + + resp = await api_client.post( + "/ingress/validate_session", + json={"session": session}, + ) + assert resp.status == 200 + assert len(stub_auth.mock_calls) == 3 + assert await resp.json() == {"result": "ok", "data": {}} + + assert coresys.ingress.sessions[session] > valid_time diff --git a/tests/api/test_network.py b/tests/api/test_network.py index 5271f9a9c..d796503a4 100644 --- a/tests/api/test_network.py +++ b/tests/api/test_network.py @@ -1,4 +1,4 @@ -"""Test NetwrokInterface API.""" +"""Test NetworkInterface API.""" import pytest from supervisor.const import DOCKER_NETWORK, DOCKER_NETWORK_MASK