Sunil Mohan Adapa b5783aec9e
config: Handle dropin config files with limited permissions
- During enable/disable of the component, status of the dropin configuration
files was being checked from non-privileged process. If the dropin configuration
file or its parent is readable only by root, then the operation fails. Fix this
by performing the status check in privileged mode.

Tests:

- Unit tests pass.

- Dropin configuration file for redis server works.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2024-04-19 06:42:11 -04:00

80 lines
2.6 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""Symlink and unlink configuration files into /etc."""
import importlib
import inspect
import shutil
from plinth import app as app_module
from plinth import module_loader
from plinth.actions import privileged
def _assert_managed_dropin_config(app_id: str, path: str):
"""Check that this is a path managed by the specified app."""
module_path = module_loader.get_module_import_path(app_id)
module = importlib.import_module(module_path)
module_classes = inspect.getmembers(module, inspect.isclass)
app_classes = [
cls[1] for cls in module_classes if issubclass(cls[1], app_module.App)
]
for cls in app_classes:
app = cls()
from plinth.config import DropinConfigs
components = app.get_components_of_type(DropinConfigs)
for component in components:
if path in component.etc_paths:
return
raise AssertionError('Not a managed drop-in config')
@privileged
def dropin_is_valid(app_id: str, path: str, copy_only: bool,
unlink_invalid: bool = False) -> bool:
"""Check if symlink from /etc/ to /usr/share/freedombox/etc is valid.
Optionally, drop the link if it is invalid.
"""
_assert_managed_dropin_config(app_id, path)
from plinth.config import DropinConfigs
etc_path = DropinConfigs.get_etc_path(path)
target = DropinConfigs.get_target_path(path)
if etc_path.exists() or etc_path.is_symlink():
if (not copy_only and etc_path.is_symlink()
and etc_path.readlink() == target):
return True
if (copy_only and etc_path.is_file()
and etc_path.read_text() == target.read_text()):
return True
if unlink_invalid:
etc_path.unlink(missing_ok=True)
return False
@privileged
def dropin_link(app_id: str, path: str, copy_only: bool):
"""Create a symlink from /etc/ to /usr/share/freedombox/etc."""
_assert_managed_dropin_config(app_id, path)
from plinth.config import DropinConfigs
target = DropinConfigs.get_target_path(path)
etc_path = DropinConfigs.get_etc_path(path)
etc_path.parent.mkdir(parents=True, exist_ok=True)
if copy_only:
shutil.copyfile(target, etc_path)
else:
etc_path.symlink_to(target)
@privileged
def dropin_unlink(app_id: str, path: str, missing_ok: bool = False):
"""Remove a symlink in /etc/."""
_assert_managed_dropin_config(app_id, path)
from plinth.config import DropinConfigs
etc_path = DropinConfigs.get_etc_path(path)
etc_path.unlink(missing_ok=missing_ok)