mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-19 15:16:33 +00:00
Make api soon available (#2119)
* Make api soon available * add more tests
This commit is contained in:
parent
ccb8e5fe06
commit
0f60fdd20b
@ -43,7 +43,10 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self.security: SecurityMiddleware = SecurityMiddleware(coresys)
|
self.security: SecurityMiddleware = SecurityMiddleware(coresys)
|
||||||
self.webapp: web.Application = web.Application(
|
self.webapp: web.Application = web.Application(
|
||||||
client_max_size=MAX_CLIENT_SIZE,
|
client_max_size=MAX_CLIENT_SIZE,
|
||||||
middlewares=[self.security.token_validation],
|
middlewares=[
|
||||||
|
self.security.system_validation,
|
||||||
|
self.security.token_validation,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# service stuff
|
# service stuff
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from aiohttp.web import middleware
|
from aiohttp.web import Request, RequestHandler, Response, middleware
|
||||||
from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized
|
from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
@ -12,9 +12,10 @@ from ..const import (
|
|||||||
ROLE_DEFAULT,
|
ROLE_DEFAULT,
|
||||||
ROLE_HOMEASSISTANT,
|
ROLE_HOMEASSISTANT,
|
||||||
ROLE_MANAGER,
|
ROLE_MANAGER,
|
||||||
|
CoreState,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from .utils import excract_supervisor_token
|
from .utils import api_return_error, excract_supervisor_token
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -108,12 +109,30 @@ ADDONS_ROLE_ACCESS = {
|
|||||||
class SecurityMiddleware(CoreSysAttributes):
|
class SecurityMiddleware(CoreSysAttributes):
|
||||||
"""Security middleware functions."""
|
"""Security middleware functions."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize security middleware."""
|
"""Initialize security middleware."""
|
||||||
self.coresys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
|
|
||||||
@middleware
|
@middleware
|
||||||
async def token_validation(self, request, handler):
|
async def system_validation(
|
||||||
|
self, request: Request, handler: RequestHandler
|
||||||
|
) -> Response:
|
||||||
|
"""Check if core is ready to response."""
|
||||||
|
if self.sys_core.state not in (
|
||||||
|
CoreState.STARTUP,
|
||||||
|
CoreState.RUNNING,
|
||||||
|
CoreState.FREEZE,
|
||||||
|
):
|
||||||
|
return api_return_error(
|
||||||
|
message=f"System is not ready with state: {self.sys_core.state.value}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return await handler(request)
|
||||||
|
|
||||||
|
@middleware
|
||||||
|
async def token_validation(
|
||||||
|
self, request: Request, handler: RequestHandler
|
||||||
|
) -> Response:
|
||||||
"""Check security access of this layer."""
|
"""Check security access of this layer."""
|
||||||
request_from = None
|
request_from = None
|
||||||
supervisor_token = excract_supervisor_token(request)
|
supervisor_token = excract_supervisor_token(request)
|
||||||
|
@ -97,11 +97,15 @@ def api_process_raw(content):
|
|||||||
return wrap_method
|
return wrap_method
|
||||||
|
|
||||||
|
|
||||||
def api_return_error(error: Optional[Any] = None) -> web.Response:
|
def api_return_error(
|
||||||
|
error: Optional[Exception] = None, message: Optional[str] = None
|
||||||
|
) -> web.Response:
|
||||||
"""Return an API error message."""
|
"""Return an API error message."""
|
||||||
|
if error and not message:
|
||||||
message = get_message_from_exception_chain(error)
|
message = get_message_from_exception_chain(error)
|
||||||
if check_exception_chain(error, DockerAPIError):
|
if check_exception_chain(error, DockerAPIError):
|
||||||
message = format_message(message)
|
message = format_message(message)
|
||||||
|
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
{
|
{
|
||||||
JSON_RESULT: RESULT_ERROR,
|
JSON_RESULT: RESULT_ERROR,
|
||||||
|
@ -111,6 +111,10 @@ class Core(CoreSysAttributes):
|
|||||||
"""Start setting up supervisor orchestration."""
|
"""Start setting up supervisor orchestration."""
|
||||||
self.state = CoreState.SETUP
|
self.state = CoreState.SETUP
|
||||||
|
|
||||||
|
# rest api views
|
||||||
|
await self.sys_api.load()
|
||||||
|
await self.sys_api.start()
|
||||||
|
|
||||||
# Load DBus
|
# Load DBus
|
||||||
await self.sys_dbus.load()
|
await self.sys_dbus.load()
|
||||||
|
|
||||||
@ -138,9 +142,6 @@ class Core(CoreSysAttributes):
|
|||||||
# Load Add-ons
|
# Load Add-ons
|
||||||
await self.sys_addons.load()
|
await self.sys_addons.load()
|
||||||
|
|
||||||
# rest api views
|
|
||||||
await self.sys_api.load()
|
|
||||||
|
|
||||||
# load last available data
|
# load last available data
|
||||||
await self.sys_snapshots.load()
|
await self.sys_snapshots.load()
|
||||||
|
|
||||||
@ -189,7 +190,6 @@ class Core(CoreSysAttributes):
|
|||||||
async def start(self):
|
async def start(self):
|
||||||
"""Start Supervisor orchestration."""
|
"""Start Supervisor orchestration."""
|
||||||
self.state = CoreState.STARTUP
|
self.state = CoreState.STARTUP
|
||||||
await self.sys_api.start()
|
|
||||||
|
|
||||||
# Check if system is healthy
|
# Check if system is healthy
|
||||||
if not self.supported:
|
if not self.supported:
|
||||||
|
61
tests/api/test_security.py
Normal file
61
tests/api/test_security.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"""Test API security layer."""
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from supervisor.api import RestAPI
|
||||||
|
from supervisor.const import CoreState
|
||||||
|
from supervisor.coresys import CoreSys
|
||||||
|
|
||||||
|
# pylint: disable=redefined-outer-name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def api_system(aiohttp_client, run_dir, coresys: CoreSys):
|
||||||
|
"""Fixture for RestAPI client."""
|
||||||
|
api = RestAPI(coresys)
|
||||||
|
api.webapp = web.Application()
|
||||||
|
await api.load()
|
||||||
|
|
||||||
|
api.webapp.middlewares.append(api.security.system_validation)
|
||||||
|
yield await aiohttp_client(api.webapp)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_security_system_initialize(api_system, coresys: CoreSys):
|
||||||
|
"""Test security."""
|
||||||
|
coresys.core.state = CoreState.INITIALIZE
|
||||||
|
|
||||||
|
resp = await api_system.get("/supervisor/ping")
|
||||||
|
result = await resp.json()
|
||||||
|
assert resp.status == 400
|
||||||
|
assert result["result"] == "error"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_security_system_setup(api_system, coresys: CoreSys):
|
||||||
|
"""Test security."""
|
||||||
|
coresys.core.state = CoreState.SETUP
|
||||||
|
|
||||||
|
resp = await api_system.get("/supervisor/ping")
|
||||||
|
result = await resp.json()
|
||||||
|
assert resp.status == 400
|
||||||
|
assert result["result"] == "error"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_security_system_running(api_system, coresys: CoreSys):
|
||||||
|
"""Test security."""
|
||||||
|
coresys.core.state = CoreState.RUNNING
|
||||||
|
|
||||||
|
resp = await api_system.get("/supervisor/ping")
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_security_system_startup(api_system, coresys: CoreSys):
|
||||||
|
"""Test security."""
|
||||||
|
coresys.core.state = CoreState.STARTUP
|
||||||
|
|
||||||
|
resp = await api_system.get("/supervisor/ping")
|
||||||
|
assert resp.status == 200
|
@ -1,4 +1,5 @@
|
|||||||
"""Common test functions."""
|
"""Common test functions."""
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, PropertyMock, patch
|
from unittest.mock import MagicMock, PropertyMock, patch
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
@ -164,3 +165,12 @@ def store_manager(coresys: CoreSys):
|
|||||||
sm_obj.repositories = set(coresys.config.addons_repositories)
|
sm_obj.repositories = set(coresys.config.addons_repositories)
|
||||||
with patch("supervisor.store.data.StoreData.update", return_value=MagicMock()):
|
with patch("supervisor.store.data.StoreData.update", return_value=MagicMock()):
|
||||||
yield sm_obj
|
yield sm_obj
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def run_dir(tmp_path):
|
||||||
|
"""Fixture to inject hassio env."""
|
||||||
|
with patch("supervisor.core.RUN_SUPERVISOR_STATE") as mock_run:
|
||||||
|
tmp_state = Path(tmp_path, "supervisor")
|
||||||
|
mock_run.write_text = tmp_state.write_text
|
||||||
|
yield tmp_state
|
||||||
|
@ -1,22 +1,7 @@
|
|||||||
"""Testing handling with CoreState."""
|
"""Testing handling with CoreState."""
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from supervisor.const import CoreState
|
from supervisor.const import CoreState
|
||||||
|
|
||||||
# pylint: disable=redefined-outer-name
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def run_dir(tmp_path):
|
|
||||||
"""Fixture to inject hassio env."""
|
|
||||||
with patch("supervisor.core.RUN_SUPERVISOR_STATE") as mock_run:
|
|
||||||
tmp_state = Path(tmp_path, "supervisor")
|
|
||||||
mock_run.write_text = tmp_state.write_text
|
|
||||||
yield tmp_state
|
|
||||||
|
|
||||||
|
|
||||||
def test_write_state(run_dir, coresys):
|
def test_write_state(run_dir, coresys):
|
||||||
"""Test write corestate to /run/supervisor."""
|
"""Test write corestate to /run/supervisor."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user