diff --git a/supervisor/resolution/checks/core_security.py b/supervisor/resolution/checks/core_security.py deleted file mode 100644 index 0c9a3aded..000000000 --- a/supervisor/resolution/checks/core_security.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Helpers to check core security.""" - -from enum import StrEnum -from pathlib import Path - -from awesomeversion import AwesomeVersion, AwesomeVersionException - -from ...const import CoreState -from ...coresys import CoreSys -from ..const import ContextType, IssueType, SuggestionType -from .base import CheckBase - - -def setup(coresys: CoreSys) -> CheckBase: - """Check setup function.""" - return CheckCoreSecurity(coresys) - - -class SecurityReference(StrEnum): - """Version references.""" - - CUSTOM_COMPONENTS_BELOW_2021_1_5 = "custom_components_below_2021_1_5" - - -class CheckCoreSecurity(CheckBase): - """CheckCoreSecurity class for check.""" - - async def run_check(self) -> None: - """Run check if not affected by issue.""" - # Security issue < 2021.1.5 & Custom components - try: - if self.sys_homeassistant.version < AwesomeVersion("2021.1.5"): - if await self.sys_run_in_executor(self._custom_components_exists): - self.sys_resolution.create_issue( - IssueType.SECURITY, - ContextType.CORE, - reference=SecurityReference.CUSTOM_COMPONENTS_BELOW_2021_1_5, - suggestions=[SuggestionType.EXECUTE_UPDATE], - ) - except (AwesomeVersionException, OSError): - return - - async def approve_check(self, reference: str | None = None) -> bool: - """Approve check if it is affected by issue.""" - try: - if self.sys_homeassistant.version >= AwesomeVersion("2021.1.5"): - return False - except AwesomeVersionException: - return True - return await self.sys_run_in_executor(self._custom_components_exists) - - def _custom_components_exists(self) -> bool: - """Return true if custom components folder exists. - - Must be run in executor. - """ - return Path(self.sys_config.path_homeassistant, "custom_components").exists() - - @property - def issue(self) -> IssueType: - """Return a IssueType enum.""" - return IssueType.SECURITY - - @property - def context(self) -> ContextType: - """Return a ContextType enum.""" - return ContextType.CORE - - @property - def states(self) -> list[CoreState]: - """Return a list of valid states when this check can run.""" - return [CoreState.RUNNING, CoreState.STARTUP] diff --git a/supervisor/resolution/module.py b/supervisor/resolution/module.py index 86895ea54..ff62ab3d0 100644 --- a/supervisor/resolution/module.py +++ b/supervisor/resolution/module.py @@ -27,7 +27,6 @@ from .const import ( from .data import HealthChanged, Issue, Suggestion, SupportedChanged from .evaluate import ResolutionEvaluation from .fixup import ResolutionFixup -from .notify import ResolutionNotify from .validate import SCHEMA_RESOLUTION_CONFIG _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -44,7 +43,6 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes): self._evaluate = ResolutionEvaluation(coresys) self._check = ResolutionCheck(coresys) self._fixup = ResolutionFixup(coresys) - self._notify = ResolutionNotify(coresys) self._suggestions: list[Suggestion] = [] self._issues: list[Issue] = [] @@ -85,11 +83,6 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes): """Return the ResolutionFixup class.""" return self._fixup - @property - def notify(self) -> ResolutionNotify: - """Return the ResolutionNotify class.""" - return self._notify - @property def issues(self) -> list[Issue]: """Return a list of issues.""" @@ -249,9 +242,6 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes): # Run autofix if possible await self.fixup.run_autofix() - # Create notification for any known issues - await self.notify.issue_notifications() - async def apply_suggestion(self, suggestion: Suggestion) -> None: """Apply suggested action.""" suggestion = self.get_suggestion_by_id(suggestion.uuid) diff --git a/supervisor/resolution/notify.py b/supervisor/resolution/notify.py deleted file mode 100644 index b34daff44..000000000 --- a/supervisor/resolution/notify.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Helper to notify Core about issues. - -This helper creates persistant notification in the Core UI. -In the future we want to remove this in favour of a "center" in the UI. -""" - -import logging - -from ..coresys import CoreSys, CoreSysAttributes -from ..exceptions import HomeAssistantAPIError -from .checks.core_security import SecurityReference -from .const import ContextType, IssueType -from .data import Issue - -_LOGGER: logging.Logger = logging.getLogger(__name__) - -ISSUE_SECURITY_CUSTOM_COMP_2021_1_5 = Issue( - IssueType.SECURITY, - ContextType.CORE, - reference=SecurityReference.CUSTOM_COMPONENTS_BELOW_2021_1_5, -) - - -class ResolutionNotify(CoreSysAttributes): - """Notify class for resolution.""" - - def __init__(self, coresys: CoreSys): - """Initialize the notify class.""" - self.coresys = coresys - - async def issue_notifications(self): - """Create persistant notifications about issues.""" - if ( - not self.sys_resolution.issues - or not await self.sys_homeassistant.api.check_api_state() - ): - return - - # This one issue must remain a persistent notification rather then a repair because repairs didn't exist in HA 2021.1.5 - if ISSUE_SECURITY_CUSTOM_COMP_2021_1_5 in self.sys_resolution.issues: - try: - async with self.sys_homeassistant.api.make_request( - "post", - "api/services/persistent_notification/create", - json={ - "title": "Security notification", - "message": "The Supervisor detected that this version of Home Assistant could be insecure in combination with custom integrations. [Update as soon as possible.](/hassio/dashboard)\n\nFor more information see the [Security alert](https://www.home-assistant.io/latest-security-alert).", - "notification_id": "supervisor_update_home_assistant_2021_1_5", - }, - ) as resp: - if resp.status in (200, 201): - _LOGGER.debug("Successfully created persistent_notification") - else: - _LOGGER.error("Can't create persistant notification") - except HomeAssistantAPIError as err: - _LOGGER.error("Can't create persistant notification: %s", err) diff --git a/tests/resolution/check/test_check_core_security.py b/tests/resolution/check/test_check_core_security.py deleted file mode 100644 index 45eb36685..000000000 --- a/tests/resolution/check/test_check_core_security.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Test core version check.""" - -# pylint: disable=import-error,protected-access -from pathlib import Path -from unittest.mock import patch - -from awesomeversion import AwesomeVersion - -from supervisor.const import CoreState -from supervisor.coresys import CoreSys -from supervisor.resolution.checks.core_security import CheckCoreSecurity -from supervisor.resolution.const import IssueType - - -async def test_base(coresys: CoreSys): - """Test check basics.""" - core_security = CheckCoreSecurity(coresys) - assert core_security.slug == "core_security" - assert core_security.enabled - - -async def test_check(coresys: CoreSys, tmp_path): - """Test check.""" - with patch("supervisor.config.CoreConfig.path_homeassistant", tmp_path): - core_security = CheckCoreSecurity(coresys) - await coresys.core.set_state(CoreState.RUNNING) - - assert len(coresys.resolution.issues) == 0 - - coresys.homeassistant._data["version"] = AwesomeVersion("2021.12.1") - await core_security.run_check() - assert len(coresys.resolution.issues) == 0 - - coresys.homeassistant._data["version"] = AwesomeVersion("landingpage") - await core_security.run_check() - assert len(coresys.resolution.issues) == 0 - - coresys.homeassistant._data["version"] = AwesomeVersion(None) - await core_security.run_check() - assert len(coresys.resolution.issues) == 0 - - coresys.homeassistant._data["version"] = AwesomeVersion("2021.1.5") - await core_security.run_check() - assert len(coresys.resolution.issues) == 0 - - coresys.homeassistant._data["version"] = AwesomeVersion("2021.1.2") - await core_security.run_check() - assert len(coresys.resolution.issues) == 0 - - Path(coresys.config.path_homeassistant, "custom_components").mkdir(parents=True) - await core_security.run_check() - - assert coresys.resolution.issues[-1].type == IssueType.SECURITY - - -async def test_approve(coresys: CoreSys, tmp_path): - """Test check.""" - with patch("supervisor.config.CoreConfig.path_homeassistant", tmp_path): - core_security = CheckCoreSecurity(coresys) - await coresys.core.set_state(CoreState.RUNNING) - coresys.homeassistant._data["version"] = None - assert await core_security.approve_check() - - coresys.homeassistant._data["version"] = AwesomeVersion("2021.1.3") - assert not await core_security.approve_check() - - coresys.homeassistant._data["version"] = AwesomeVersion("2021.1.2") - assert not await core_security.approve_check() - - Path(coresys.config.path_homeassistant, "custom_components").mkdir(parents=True) - assert await core_security.approve_check() - - -async def test_did_run(coresys: CoreSys): - """Test that the check ran as expected.""" - core_security = CheckCoreSecurity(coresys) - should_run = core_security.states - should_not_run = [state for state in CoreState if state not in should_run] - assert len(should_run) != 0 - assert len(should_not_run) != 0 - - with patch( - "supervisor.resolution.checks.core_security.CheckCoreSecurity.run_check", - return_value=None, - ) as check: - for state in should_run: - await coresys.core.set_state(state) - await core_security() - check.assert_called_once() - check.reset_mock() - - for state in should_not_run: - await coresys.core.set_state(state) - await core_security() - check.assert_not_called() - check.reset_mock() diff --git a/tests/resolution/test_check.py b/tests/resolution/test_check.py index 19e159571..51a753406 100644 --- a/tests/resolution/test_check.py +++ b/tests/resolution/test_check.py @@ -4,7 +4,7 @@ from unittest.mock import Mock, patch from supervisor.const import CoreState from supervisor.coresys import CoreSys -from supervisor.resolution.checks.core_security import CheckCoreSecurity +from supervisor.resolution.checks.free_space import CheckFreeSpace from supervisor.utils import check_exception_chain @@ -13,7 +13,7 @@ async def test_check_system_error(coresys: CoreSys, capture_exception: Mock): await coresys.core.set_state(CoreState.STARTUP) with ( - patch.object(CheckCoreSecurity, "run_check", side_effect=ValueError), + patch.object(CheckFreeSpace, "run_check", side_effect=ValueError), patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))), ): await coresys.resolution.check.check_system()