From cd512bd24cb488181fd53d5dfd88fcbe7766acfd Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 May 2023 11:53:49 -0700 Subject: [PATCH] mediawiki: Use drop-in config component for /etc files - Don't ship /etc/mediawiki/FreedomBoxSettings.php anymore. Create the file on first setup. Keep old file on update. - Simplify and unify how the configuration settings are read and written. Tests: - Run unit and functional tests. - All the drop-in config files in /etc/ are symlinks. - Shipped configuration is effective. - Upgrade from older version keeps old configuration. - Config files are all symlinks in /etc/ - When upgrading from older version FreedomBoxSettings.php does not change. FreedomBoxStaticSettings.php becomes a symlink. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- debian/freedombox.maintscript | 2 + plinth/modules/mediawiki/__init__.py | 64 +++++++++----- .../data/etc/mediawiki/FreedomBoxSettings.php | 26 ------ .../conf-available/mediawiki-freedombox.conf | 0 .../mediawiki/FreedomBoxStaticSettings.php | 5 +- plinth/modules/mediawiki/privileged.py | 87 +++++-------------- .../modules/mediawiki/tests/test_settings.py | 68 +++++++-------- plinth/modules/mediawiki/views.py | 28 ++---- 8 files changed, 106 insertions(+), 174 deletions(-) delete mode 100644 plinth/modules/mediawiki/data/etc/mediawiki/FreedomBoxSettings.php rename plinth/modules/mediawiki/data/{ => usr/share/freedombox}/etc/apache2/conf-available/mediawiki-freedombox.conf (100%) rename plinth/modules/mediawiki/data/{ => usr/share/freedombox}/etc/mediawiki/FreedomBoxStaticSettings.php (91%) diff --git a/debian/freedombox.maintscript b/debian/freedombox.maintscript index 5708889de..60e359d2b 100644 --- a/debian/freedombox.maintscript +++ b/debian/freedombox.maintscript @@ -117,3 +117,5 @@ rm_conffile /etc/letsencrypt/renewal-hooks/deploy/50-freedombox 23.10~ rm_conffile /etc/apache2/conf-available/matrix-synapse-plinth.conf 23.10~ rm_conffile /etc/fail2ban/jail.d/matrix-auth-freedombox.conf 23.10~ rm_conffile /etc/fail2ban/filter.d/matrix-auth-freedombox.conf 23.10~ +rm_conffile /etc/apache2/conf-available/mediawiki-freedombox.conf 23.10~ +rm_conffile /etc/mediawiki/FreedomBoxStaticSettings.php 23.10~ diff --git a/plinth/modules/mediawiki/__init__.py b/plinth/modules/mediawiki/__init__.py index 785a226e5..897b7bc7d 100644 --- a/plinth/modules/mediawiki/__init__.py +++ b/plinth/modules/mediawiki/__init__.py @@ -1,13 +1,14 @@ # SPDX-License-Identifier: AGPL-3.0-or-later """FreedomBox app to configure MediaWiki.""" -import re +import pathlib from urllib.parse import urlparse from django.utils.translation import gettext_lazy as _ from plinth import app as app_module from plinth import frontpage, menu +from plinth.config import DropinConfigs from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver from plinth.modules.backups.components import BackupRestore @@ -31,7 +32,6 @@ _description = [ 'logged in can make changes to the content.') ] -STATIC_CONFIG_FILE = '/etc/mediawiki/FreedomBoxStaticSettings.php' USER_CONFIG_FILE = '/etc/mediawiki/FreedomBoxSettings.php' @@ -40,7 +40,7 @@ class MediaWikiApp(app_module.App): app_id = 'mediawiki' - _version = 10 + _version = 11 def __init__(self): """Create components for the app.""" @@ -70,6 +70,12 @@ class MediaWikiApp(app_module.App): ['mediawiki', 'imagemagick', 'php-sqlite3']) self.add(packages) + dropin_configs = DropinConfigs('dropin-configs-mediawiki', [ + '/etc/apache2/conf-available/mediawiki-freedombox.conf', + '/etc/mediawiki/FreedomBoxStaticSettings.php', + ]) + self.add(dropin_configs) + firewall = Firewall('firewall-mediawiki', info.name, ports=['http', 'https'], is_external=True) self.add(firewall) @@ -109,7 +115,7 @@ class Shortcut(frontpage.Shortcut): def enable(self): """When enabled, check if MediaWiki is in private mode.""" super().enable() - self.login_required = privileged.private_mode('status') + self.login_required = get_config()['enable_private_mode'] def _get_config_value_in_file(setting_name, config_file): @@ -117,26 +123,46 @@ def _get_config_value_in_file(setting_name, config_file): with open(config_file, 'r', encoding='utf-8') as config: for line in config: if line.startswith(setting_name): - return re.findall(r'["\'][^"\']*["\']', line)[0].strip('"\'') + return line.partition('=')[2].strip('\n ;\'"') return None +def _get_static_config_file(): + """Return the path for the file containing static settings.""" + base_path = ('/usr/share/freedombox/etc/' + 'mediawiki/FreedomBoxStaticSettings.php') + for path in [ + pathlib.Path(base_path), + pathlib.Path(__file__).parent / 'data' / base_path.lstrip('/') + ]: + if path.exists(): + return path + + raise RuntimeError('Unable to find static config file') + + def _get_config_value(setting_name): """Return a configuration value from multiple configuration files.""" return _get_config_value_in_file(setting_name, USER_CONFIG_FILE) or \ - _get_config_value_in_file(setting_name, STATIC_CONFIG_FILE) + _get_config_value_in_file(setting_name, _get_static_config_file()) -def get_default_skin(): - """Return the value of the default skin.""" - return _get_config_value('$wgDefaultSkin') - - -def get_server_url(): - """Return the value of the server URL.""" +def get_config(): + """Return all the configuration settings.""" server_url = _get_config_value('$wgServer') - return urlparse(server_url).netloc + create_permission = _get_config_value( + "$wgGroupPermissions['*']['createaccount']") + read_permission = _get_config_value("$wgGroupPermissions['*']['read']") + print('=====', create_permission, read_permission) + return { + 'default_skin': _get_config_value('$wgDefaultSkin'), + 'domain': urlparse(server_url).netloc, + 'site_name': _get_config_value('$wgSitename') or 'Wiki', + 'default_lang': _get_config_value('$wgLanguageCode') or None, + 'enable_public_registrations': 'true' in create_permission, + 'enable_private_mode': 'false' in read_permission, + } def set_server_url(domain): @@ -146,13 +172,3 @@ def set_server_url(domain): protocol = 'http' privileged.set_server_url(f'{protocol}://{domain}') - - -def get_site_name(): - """Return the value of MediaWiki's site name.""" - return _get_config_value('$wgSitename') or 'Wiki' - - -def get_default_language(): - """Return the value of MediaWiki's default language""" - return _get_config_value('$wgLanguageCode') or None diff --git a/plinth/modules/mediawiki/data/etc/mediawiki/FreedomBoxSettings.php b/plinth/modules/mediawiki/data/etc/mediawiki/FreedomBoxSettings.php deleted file mode 100644 index f33f4a6e3..000000000 --- a/plinth/modules/mediawiki/data/etc/mediawiki/FreedomBoxSettings.php +++ /dev/null @@ -1,26 +0,0 @@ - Optional[bool]: - """Enable or Disable public registrations for MediaWiki.""" - if command not in ('enable', 'disable', 'status'): - raise ValueError('Invalid command') - - with open(CONF_FILE, 'r', encoding='utf-8') as conf_file: - lines = conf_file.readlines() - - def is_pub_reg_line(line): - return line.startswith("$wgGroupPermissions['*']['createaccount']") - - if command == 'status': - conf_lines = list(filter(is_pub_reg_line, lines)) - return bool(conf_lines and 'true' in conf_lines[0]) - - with open(CONF_FILE, 'w', encoding='utf-8') as conf_file: - for line in lines: - if is_pub_reg_line(line): - words = line.split() - if command == 'enable': - words[-1] = 'true;' - else: - words[-1] = 'false;' - conf_file.write(" ".join(words) + '\n') - else: - conf_file.write(line) - - return None - - -@privileged -def private_mode(command: str): - """Enable or Disable Private mode for wiki.""" - if command not in ('enable', 'disable', 'status'): - raise ValueError('Invalid command') - - with open(CONF_FILE, 'r', encoding='utf-8') as conf_file: - lines = conf_file.readlines() - - def is_read_line(line): - return line.startswith("$wgGroupPermissions['*']['read']") - - read_conf_lines = list(filter(is_read_line, lines)) - if command == 'status': - return (read_conf_lines and 'false' in read_conf_lines[0]) - - with open(CONF_FILE, 'w', encoding='utf-8') as conf_file: - conf_value = 'false;' if command == 'enable' else 'true;' - for line in lines: - if is_read_line(line): - words = line.split() - words[-1] = conf_value - conf_file.write(" ".join(words) + '\n') - else: - conf_file.write(line) - - if not read_conf_lines: - conf_file.write("$wgGroupPermissions['*']['read'] = " + - conf_value + '\n') - - def _update_setting(setting_name, setting_line): """Update the value of one setting in the config file.""" with open(CONF_FILE, 'r', encoding='utf-8') as conf_file: @@ -190,10 +132,26 @@ def _update_setting(setting_name, setting_line): conf_file.writelines(lines) +@privileged +def set_public_registrations(should_enable: bool): + """Enable or Disable public registrations for MediaWiki.""" + setting = "$wgGroupPermissions['*']['createaccount']" + conf_value = 'true' if should_enable else 'false' + _update_setting(setting, f'{setting} = {conf_value};\n') + + +@privileged +def set_private_mode(should_enable: bool): + """Enable or Disable Private mode for wiki.""" + setting = "$wgGroupPermissions['*']['read']" + conf_value = 'false' if should_enable else 'true' + _update_setting(setting, f'{setting} = {conf_value};\n') + + @privileged def set_default_skin(skin: str): """Set a default skin.""" - _update_setting('$wgDefaultSkin ', f'$wgDefaultSkin = "{skin}";\n') + _update_setting('$wgDefaultSkin', f'$wgDefaultSkin = "{skin}";\n') @privileged @@ -228,3 +186,4 @@ def uninstall(): """Remove Mediawiki's database and local config file.""" shutil.rmtree('/var/lib/mediawiki-db', ignore_errors=True) pathlib.Path(LOCAL_SETTINGS_CONF).unlink(missing_ok=True) + pathlib.Path(CONF_FILE).unlink(missing_ok=True) diff --git a/plinth/modules/mediawiki/tests/test_settings.py b/plinth/modules/mediawiki/tests/test_settings.py index 20d0c9e4a..6565da6cb 100644 --- a/plinth/modules/mediawiki/tests/test_settings.py +++ b/plinth/modules/mediawiki/tests/test_settings.py @@ -4,7 +4,6 @@ Test module for MediaWiki utility functions. """ import pathlib -import shutil from unittest.mock import patch import pytest @@ -17,60 +16,57 @@ current_directory = pathlib.Path(__file__).parent privileged_modules_to_mock = ['plinth.modules.mediawiki.privileged'] -@pytest.fixture(autouse=True) -def fixture_setup_configuration(conf_file): - """Set configuration file path in actions module.""" - privileged.CONF_FILE = conf_file +@pytest.fixture(name='test_configuration', autouse=True) +def fixture_test_configuration(tmp_path): + """Use a separate MediaWiki configuration for tests. - -@pytest.fixture(name='conf_file') -def fixture_conf_file(tmp_path): - """Uses a dummy configuration file.""" + FreedomBoxStaticSettings.php is used read-only from source code location. + """ settings_file_name = 'FreedomBoxSettings.php' conf_file = tmp_path / settings_file_name conf_file.touch() - shutil.copyfile( - str(current_directory / '..' / 'data' / 'etc' / 'mediawiki' / - settings_file_name), str(conf_file)) - return str(conf_file) - - -@pytest.fixture(name='test_configuration', autouse=True) -def fixture_test_configuration(conf_file): - """Use a separate MediaWiki configuration for tests. - - Uses local FreedomBoxStaticSettings.php, a temp version of - FreedomBoxSettings.php - - """ - data_directory = pathlib.Path(__file__).parent.parent / 'data' - static_config_file = str(data_directory / 'etc' / 'mediawiki' / - mediawiki.STATIC_CONFIG_FILE.split('/')[-1]) - with patch('plinth.modules.mediawiki.STATIC_CONFIG_FILE', - static_config_file), \ - patch('plinth.modules.mediawiki.USER_CONFIG_FILE', conf_file): + with (patch('plinth.modules.mediawiki.USER_CONFIG_FILE', conf_file), + patch('plinth.modules.mediawiki.privileged.CONF_FILE', conf_file)): yield +def test_private_mode(): + """Test enabling/disabling private mode.""" + assert not mediawiki.get_config()['enable_private_mode'] + privileged.set_private_mode(True) + assert mediawiki.get_config()['enable_private_mode'] + privileged.set_private_mode(False) + assert not mediawiki.get_config()['enable_private_mode'] + + +def test_public_registrations(): + """Test enabling/disabling public registrations.""" + assert not mediawiki.get_config()['enable_public_registrations'] + privileged.set_public_registrations(True) + assert mediawiki.get_config()['enable_public_registrations'] + privileged.set_public_registrations(False) + assert not mediawiki.get_config()['enable_public_registrations'] + + def test_default_skin(): """Test getting and setting the default skin.""" - assert mediawiki.get_default_skin() == 'timeless' + assert mediawiki.get_config()['default_skin'] == 'timeless' new_skin = 'vector' privileged.set_default_skin(new_skin) - assert mediawiki.get_default_skin() == new_skin + assert mediawiki.get_config()['default_skin'] == new_skin -def test_server_url(): +def test_domain(): """Test getting and setting $wgServer.""" - assert mediawiki.get_server_url() == 'freedombox.local' + assert mediawiki.get_config()['domain'] == 'freedombox.local' new_domain = 'mydomain.freedombox.rocks' mediawiki.set_server_url(new_domain) - assert mediawiki.get_server_url() == new_domain + assert mediawiki.get_config()['domain'] == new_domain def test_site_name(): """Test getting and setting $wgSitename.""" - assert mediawiki.get_site_name() == 'Wiki' + assert mediawiki.get_config()['site_name'] == 'Wiki' new_site_name = 'My MediaWiki' privileged.set_site_name(new_site_name) - assert mediawiki.get_site_name() == new_site_name + assert mediawiki.get_config()['site_name'] == new_site_name diff --git a/plinth/modules/mediawiki/views.py b/plinth/modules/mediawiki/views.py index f2b995e1e..c012c68fc 100644 --- a/plinth/modules/mediawiki/views.py +++ b/plinth/modules/mediawiki/views.py @@ -10,8 +10,7 @@ from plinth import app as app_module from plinth import views from plinth.modules import mediawiki -from . import (get_default_skin, - get_server_url, get_site_name, get_default_language, privileged) +from . import privileged from .forms import MediaWikiForm logger = logging.getLogger(__name__) @@ -27,20 +26,7 @@ class MediaWikiAppView(views.AppView): def get_initial(self): """Return the values to fill in the form.""" initial = super().get_initial() - initial.update({ - 'enable_public_registrations': - privileged.public_registrations('status'), - 'enable_private_mode': - privileged.private_mode('status'), - 'default_skin': - get_default_skin(), - 'domain': - get_server_url(), - 'site_name': - get_site_name(), - 'default_lang': - get_default_language() - }) + initial.update(mediawiki.get_config()) return initial def form_valid(self, form): @@ -66,7 +52,7 @@ class MediaWikiAppView(views.AppView): # note action public-registration restarts, if running now if new_config['enable_public_registrations']: if not new_config['enable_private_mode']: - privileged.public_registrations('enable') + privileged.set_public_registrations(True) messages.success(self.request, _('Public registrations enabled')) else: @@ -74,19 +60,19 @@ class MediaWikiAppView(views.AppView): self.request, 'Public registrations ' + 'cannot be enabled when private mode is enabled') else: - privileged.public_registrations('disable') + privileged.set_public_registrations(False) messages.success(self.request, _('Public registrations disabled')) if is_changed('enable_private_mode'): if new_config['enable_private_mode']: - privileged.private_mode('enable') + privileged.set_private_mode(True) messages.success(self.request, _('Private mode enabled')) if new_config['enable_public_registrations']: # If public registrations are enabled, then disable it - privileged.public_registrations('disable') + privileged.set_public_registrations(False) else: - privileged.private_mode('disable') + privileged.set_private_mode(False) messages.success(self.request, _('Private mode disabled')) app = app_module.App.get('mediawiki')