mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-04-02 00:07:16 +01:00
Remove obsolete persistent notification system (#6623)
The core_security check (HA < 2021.1.5 with custom components) and the ResolutionNotify class that created persistent notifications for it are no longer needed. The minimum supported HA version is well past 2021.1.5, so this check can never trigger. The notify module was the only consumer of persistent notifications and had no other users. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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]
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user