Make api soon available (#2119)

* Make api soon available

* add more tests
This commit is contained in:
Pascal Vizeli 2020-10-12 15:56:29 +02:00 committed by GitHub
parent ccb8e5fe06
commit 0f60fdd20b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 30 deletions

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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:

View 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

View File

@ -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

View File

@ -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."""