1
0
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:
Stefan Agner
2026-03-11 18:50:07 +01:00
committed by GitHub
parent 3c703667ce
commit 83c8c0aab0
5 changed files with 2 additions and 236 deletions

View File

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

View File

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

View File

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

View File

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

View File

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