From 37138ee83b4421db1587e328603e9d595f9e63bb Mon Sep 17 00:00:00 2001 From: Joseph Nuthalpati Date: Fri, 4 Aug 2017 23:19:37 +0530 Subject: [PATCH] mediawiki: Add wiki application Installs and configures MediaWiki. SSO integration is not included yet. Signed-off-by: Joseph Nuthalapati Reviewed-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- LICENSES | 1 + actions/mediawiki | 143 ++++++++++++++++++++++ data/etc/plinth/modules-enabled/mediawiki | 1 + plinth/action_utils.py | 85 +++++-------- plinth/modules/mediawiki/__init__.py | 125 +++++++++++++++++++ plinth/modules/mediawiki/forms.py | 32 +++++ plinth/modules/mediawiki/manifest.py | 28 +++++ plinth/modules/mediawiki/urls.py | 27 ++++ plinth/modules/mediawiki/views.py | 52 ++++++++ plinth/utils.py | 17 +++ static/themes/default/icons/mediawiki.png | Bin 0 -> 16330 bytes 11 files changed, 460 insertions(+), 51 deletions(-) create mode 100755 actions/mediawiki create mode 100644 data/etc/plinth/modules-enabled/mediawiki create mode 100644 plinth/modules/mediawiki/__init__.py create mode 100644 plinth/modules/mediawiki/forms.py create mode 100644 plinth/modules/mediawiki/manifest.py create mode 100644 plinth/modules/mediawiki/urls.py create mode 100644 plinth/modules/mediawiki/views.py create mode 100644 static/themes/default/icons/mediawiki.png diff --git a/LICENSES b/LICENSES index d0e7cd9b5..84f71d4f9 100644 --- a/LICENSES +++ b/LICENSES @@ -69,3 +69,4 @@ otherwise. - static/themes/default/icons/apple.png :: [[https://thenounproject.com/icon/1203053/download/color/000000/png][CC BY 3.0 US]] - static/themes/default/icons/windows.png :: [[https://thenounproject.com/icon/1206946/download/color/000000/png][CC BY 3.0 US]] - static/themes/default/icons/gnu-linux.png :: [[https://upload.wikimedia.org/wikipedia/commons/9/95/Tux-icon-mono.svg][Public Domain]] +- static/themes/default/icons/mediawiki.svg :: [[http://tango.freedesktop.org/][Public Domain]] diff --git a/actions/mediawiki b/actions/mediawiki new file mode 100755 index 000000000..7f322d01f --- /dev/null +++ b/actions/mediawiki @@ -0,0 +1,143 @@ +#!/usr/bin/python3 +# -*- mode: python -*- +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Configuration helper for MediaWiki. +""" + +import argparse +import os +import re +import subprocess +import sys +import tempfile + +from plinth import action_utils +from plinth.utils import generate_password, grep + +MAINTENANCE_SCRIPTS_DIR = "/usr/share/mediawiki/maintenance" +CONF_FILE = '/var/lib/mediawiki/LocalSettings.php' + + +def parse_arguments(): + """Return parsed command line arguments as dictionary.""" + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') + + subparsers.add_parser('enable', help='Enable MediaWiki') + subparsers.add_parser('disable', help='Disable MediaWiki') + subparsers.add_parser('setup', help='Setup MediaWiki') + change_password = subparsers.add_parser('change-password', + help='Change user password') + change_password.add_argument('--username', default='admin', + help='name of the MediaWiki user') + change_password.add_argument('--password', + help='new password for the MediaWiki user') + + subparsers.required = True + return parser.parse_args() + + +def subcommand_setup(_): + """Run the installer script to create database and configuration file.""" + data_dir = '/var/lib/mediawiki-db/' + if not os.path.exists(data_dir): + os.mkdir(data_dir) + + if not os.path.exists(os.path.join(data_dir, 'my_wiki.sqlite')): + install_script = os.path.join(MAINTENANCE_SCRIPTS_DIR, 'install.php') + password = generate_password() + with tempfile.NamedTemporaryFile() as password_file_handle: + password_file_handle.write(password.encode()) + subprocess.check_call([ + 'php', install_script, '--confpath=/etc/mediawiki', + '--dbtype=sqlite', '--dbpath=' + data_dir, + '--scriptpath=/mediawiki', '--passfile', + password_file_handle.name, 'Wiki', 'admin' + ]) + subprocess.run(['chmod', '-R', 'o-rwx', data_dir], check=True) + subprocess.run(['chown', '-R', 'www-data:www-data', data_dir], check=True) + _disable_public_registrations() + _disable_anonymous_editing() + _change_logo() + + +def _disable_public_registrations(): + """Edit MediaWiki configuration to disable public registrations.""" + if not grep(r'\$wgGroupPermissions.*createaccount', CONF_FILE): + with open(CONF_FILE, 'a') as file_handle: + file_handle.write( + "$wgGroupPermissions['*']['createaccount'] = false;\n") + + +def _disable_anonymous_editing(): + """Edit MediaWiki configuration to allow anonymous users from editing. + + MediaWiki instances get a lot of spam bot typically. + """ + if not grep(r'\$wgGroupPermissions.*edit', CONF_FILE): + with open(CONF_FILE, 'a') as file_handle: + file_handle.write("$wgGroupPermissions['*']['edit'] = false;\n") + + +def _change_logo(): + """Change the placeholder logo to MediaWiki's official logo""" + lines = open(CONF_FILE, 'r').readlines() + with open(CONF_FILE, 'w') as file_handle: + for line in lines: + if re.match('^\s*\$wgLogo', line): + line = line.replace('assets/wiki.png', 'assets/mediawiki.png') + + file_handle.write(line) + + +def subcommand_change_password(arguments): + """Change the password for a given user""" + new_password = ''.join(sys.stdin) + change_password_script = os.path.join(MAINTENANCE_SCRIPTS_DIR, + 'changePassword.php') + + subprocess.check_call([ + 'php', change_password_script, '--user', arguments.username, + '--password', new_password + ]) + + +def subcommand_enable(_): + """Enable web configuration and reload.""" + action_utils.service_enable('mediawiki-jobrunner') + action_utils.webserver_enable('mediawiki') + + +def subcommand_disable(_): + """Disable web configuration and reload.""" + action_utils.webserver_disable('mediawiki') + action_utils.service_disable('mediawiki-jobrunner') + + +def main(): + """Parse arguments and perform all duties.""" + arguments = parse_arguments() + + subcommand = arguments.subcommand.replace('-', '_') + subcommand_method = globals()['subcommand_' + subcommand] + subcommand_method(arguments) + + +if __name__ == '__main__': + main() diff --git a/data/etc/plinth/modules-enabled/mediawiki b/data/etc/plinth/modules-enabled/mediawiki new file mode 100644 index 000000000..261fc06e2 --- /dev/null +++ b/data/etc/plinth/modules-enabled/mediawiki @@ -0,0 +1 @@ +plinth.modules.mediawiki diff --git a/plinth/action_utils.py b/plinth/action_utils.py index 4d1e5b446..dbf94cd0c 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -24,8 +24,8 @@ import shutil import socket import subprocess import tempfile -import psutil +import psutil from django.utils.translation import ugettext as _ logger = logging.getLogger(__name__) @@ -43,15 +43,11 @@ def service_is_running(servicename): """ try: if is_systemd_running(): - subprocess.run( - ['systemctl', 'status', servicename], - check=True, - stdout=subprocess.DEVNULL) + subprocess.run(['systemctl', 'status', servicename], check=True, + stdout=subprocess.DEVNULL) else: - subprocess.run( - ['service', servicename, 'status'], - check=True, - stdout=subprocess.DEVNULL) + subprocess.run(['service', servicename, 'status'], check=True, + stdout=subprocess.DEVNULL) return True except subprocess.CalledProcessError: @@ -63,11 +59,8 @@ def service_is_running(servicename): def service_is_enabled(service_name): """Check if service is enabled in systemd.""" try: - subprocess.run( - ['systemctl', 'is-enabled', service_name], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run(['systemctl', 'is-enabled', service_name], check=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return True except subprocess.CalledProcessError: return False @@ -96,41 +89,41 @@ def service_unmask(service_name): def service_start(service_name): """Start a service with systemd or sysvinit.""" if is_systemd_running(): - subprocess.run( - ['systemctl', 'start', service_name], stdout=subprocess.DEVNULL) + subprocess.run(['systemctl', 'start', service_name], + stdout=subprocess.DEVNULL) else: - subprocess.run( - ['service', service_name, 'start'], stdout=subprocess.DEVNULL) + subprocess.run(['service', service_name, 'start'], + stdout=subprocess.DEVNULL) def service_stop(service_name): """Stop a service with systemd or sysvinit.""" if is_systemd_running(): - subprocess.run( - ['systemctl', 'stop', service_name], stdout=subprocess.DEVNULL) + subprocess.run(['systemctl', 'stop', service_name], + stdout=subprocess.DEVNULL) else: - subprocess.run( - ['service', service_name, 'stop'], stdout=subprocess.DEVNULL) + subprocess.run(['service', service_name, 'stop'], + stdout=subprocess.DEVNULL) def service_restart(service_name): """Restart a service with systemd or sysvinit.""" if is_systemd_running(): - subprocess.run( - ['systemctl', 'restart', service_name], stdout=subprocess.DEVNULL) + subprocess.run(['systemctl', 'restart', service_name], + stdout=subprocess.DEVNULL) else: - subprocess.run( - ['service', service_name, 'restart'], stdout=subprocess.DEVNULL) + subprocess.run(['service', service_name, 'restart'], + stdout=subprocess.DEVNULL) def service_reload(service_name): """Reload a service with systemd or sysvinit.""" if is_systemd_running(): - subprocess.run( - ['systemctl', 'reload', service_name], stdout=subprocess.DEVNULL) + subprocess.run(['systemctl', 'reload', service_name], + stdout=subprocess.DEVNULL) else: - subprocess.run( - ['service', service_name, 'reload'], stdout=subprocess.DEVNULL) + subprocess.run(['service', service_name, 'reload'], + stdout=subprocess.DEVNULL) def webserver_is_enabled(name, kind='config'): @@ -141,8 +134,8 @@ def webserver_is_enabled(name, kind='config'): option_map = {'config': '-c', 'site': '-s', 'module': '-m'} try: # Don't print anything on the terminal - subprocess.check_output( - ['a2query', option_map[kind], name], stderr=subprocess.STDOUT) + subprocess.check_output(['a2query', option_map[kind], name], + stderr=subprocess.STDOUT) return True except subprocess.CalledProcessError: return False @@ -309,13 +302,8 @@ def _check_port(port, kind='tcp', listen_address=None): return False -def diagnose_url(url, - kind=None, - env=None, - check_certificate=True, - extra_options=None, - wrapper=None, - expected_output=None): +def diagnose_url(url, kind=None, env=None, check_certificate=True, + extra_options=None, wrapper=None, expected_output=None): """Run a diagnostic on whether a URL is accessible. Kind can be '4' for IPv4 or '6' for IPv6. @@ -335,12 +323,9 @@ def diagnose_url(url, command.append({'4': '-4', '6': '-6'}[kind]) try: - process = subprocess.run( - command, - env=env, - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + process = subprocess.run(command, env=env, check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) result = 'passed' if expected_output and expected_output not in process.stdout.decode(): result = 'failed' @@ -376,10 +361,8 @@ def diagnose_netcat(host, port, input='', negate=False): """Run a diagnostic using netcat.""" try: process = subprocess.Popen( - ['nc', host, str(port)], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + ['nc', host, str(port)], stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) process.communicate(input=input.encode()) if process.returncode != 0: result = 'failed' @@ -473,8 +456,8 @@ Owners: {package} ''' override_data = '' for key, value in config.items(): - override_data += override_template.format( - package=package, key=key, value=value) + override_data += override_template.format(package=package, key=key, + value=value) with tempfile.NamedTemporaryFile(mode='w', delete=False) as override_file: override_file.write(override_data) diff --git a/plinth/modules/mediawiki/__init__.py b/plinth/modules/mediawiki/__init__.py new file mode 100644 index 000000000..980700d91 --- /dev/null +++ b/plinth/modules/mediawiki/__init__.py @@ -0,0 +1,125 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Plinth module to configure MediaWiki. +""" + +from django.utils.translation import ugettext_lazy as _ + +from plinth import service as service_module +from plinth import action_utils, actions, frontpage +from plinth.menu import main_menu + +from .manifest import clients + +version = 1 + +managed_packages = ['mediawiki', 'imagemagick', 'php-sqlite3'] + +name = _('MediaWiki') + +short_description = _('Wiki') + +description = [ + _('MediaWiki is the wiki engine that powers Wikipedia and other WikiMedia ' + 'projects. A wiki engine is a program for creating a collaboratively ' + 'edited website. You can use MediaWiki to host a wiki-like website, ' + 'take notes or collaborate with friends on projects.'), + _('This MediaWiki instance comes with a randomly generated administrator ' + 'password. You can set a new password in the Configuration section and ' + 'login using the "admin" account. You can then create more user ' + 'accounts from MediaWiki itself by going to the ' + 'Special:CreateAccount page'), + _('Anyone with a link to this Wiki can read it. Only users that are ' + 'logged in can make changes to the content.') +] + +service = None + +clients = clients + + +def init(): + """Intialize the module.""" + menu = main_menu.get('apps') + menu.add_urlname(name, 'glyphicon-edit', 'mediawiki:index', + short_description) + + global service + setup_helper = globals()['setup_helper'] + if setup_helper.get_state() != 'needs-setup': + service = service_module.Service( + 'mediawiki', name, ports=['http', 'https'], is_external=True, + is_enabled=is_enabled, enable=enable, disable=disable) + + if is_enabled(): + add_shortcut() + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(managed_packages) + helper.call('setup', actions.superuser_run, 'mediawiki', ['setup']) + helper.call('enable', actions.superuser_run, 'mediawiki', ['enable']) + global service + if service is None: + service = service_module.Service( + 'mediawiki', + name, + is_external=True, + is_enabled=is_enabled, + enable=enable, + disable=disable, + ports=['http', 'https'], ) + helper.call('post', service.notify_enabled, None, True) + helper.call('post', add_shortcut) + + +def add_shortcut(): + """Helper method to add a shortcut to the frontpage.""" + frontpage.add_shortcut('mediawiki', name, + short_description=short_description, + url='/mediawiki', login_required=True) + + +def is_enabled(): + """Return whether the module is enabled.""" + return action_utils.webserver_is_enabled('mediawiki') + + +def enable(): + """Enable the module.""" + actions.superuser_run('mediawiki', ['enable']) + add_shortcut() + + +def disable(): + """Enable the module.""" + actions.superuser_run('mediawiki', ['disable']) + frontpage.remove_shortcut('mediawiki') + + +def diagnose(): + """Run diagnostics and return the results.""" + results = [] + + results.extend( + action_utils.diagnose_url_on_all('https://{host}/mediawiki', + check_certificate=False)) + + return results diff --git a/plinth/modules/mediawiki/forms.py b/plinth/modules/mediawiki/forms.py new file mode 100644 index 000000000..4367d75ab --- /dev/null +++ b/plinth/modules/mediawiki/forms.py @@ -0,0 +1,32 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Plinth module for configuring MediaWiki. +""" + +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from plinth.forms import ServiceForm + + +class MediaWikiForm(ServiceForm): # pylint: disable=W0232 + """MediaWiki configuration form.""" + password = forms.CharField(label=_('Administrator Password'), help_text=_( + 'Set a new password for MediaWiki\'s administrator account (admin). ' + 'Leave this field blank to keep the current password.'), + required=False, widget=forms.PasswordInput) diff --git a/plinth/modules/mediawiki/manifest.py b/plinth/modules/mediawiki/manifest.py new file mode 100644 index 000000000..352e38b28 --- /dev/null +++ b/plinth/modules/mediawiki/manifest.py @@ -0,0 +1,28 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +from django.utils.translation import ugettext_lazy as _ + +from plinth.clients import validate + +clients = validate([{ + 'name': _('MediaWiki'), + 'platforms': [{ + 'type': 'web', + 'url': '/mediawiki' + }] +}]) diff --git a/plinth/modules/mediawiki/urls.py b/plinth/modules/mediawiki/urls.py new file mode 100644 index 000000000..27763878e --- /dev/null +++ b/plinth/modules/mediawiki/urls.py @@ -0,0 +1,27 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +URLs for the mediawiki module. +""" + +from django.conf.urls import url + +from .views import MediaWikiServiceView + +urlpatterns = [ + url(r'^apps/mediawiki/$', MediaWikiServiceView.as_view(), name='index'), +] diff --git a/plinth/modules/mediawiki/views.py b/plinth/modules/mediawiki/views.py new file mode 100644 index 000000000..c9d403f58 --- /dev/null +++ b/plinth/modules/mediawiki/views.py @@ -0,0 +1,52 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Plinth module for configuring MediaWiki. +""" + +import logging + +from django.contrib import messages +from django.utils.translation import ugettext as _ + +from plinth import actions, views +from plinth.modules import mediawiki + +from .forms import MediaWikiForm + +logger = logging.getLogger(__name__) + + +class MediaWikiServiceView(views.ServiceView): + """Serve configuration page.""" + clients = mediawiki.clients + description = mediawiki.description + diagnostics_module_name = 'mediawiki' + service_id = 'mediawiki' + form_class = MediaWikiForm + show_status_block = False + + def form_valid(self, form): + """Apply the changes submitted in the form.""" + form_data = form.cleaned_data + + if form_data['password']: + actions.superuser_run('mediawiki', ['change-password'], + input=form_data['password'].encode()) + messages.success(self.request, _('Password updated')) + + return super().form_valid(form) diff --git a/plinth/utils.py b/plinth/utils.py index b56aff1ec..edf5e3118 100644 --- a/plinth/utils.py +++ b/plinth/utils.py @@ -20,6 +20,9 @@ Miscellaneous utility methods. import importlib import os +import random +import re +import string from django.utils.functional import lazy @@ -119,3 +122,17 @@ class YAMLFile(object): def yes_or_no(cond): return 'yes' if cond else 'no' + + +def generate_password(size=32): + """Generate a random password using ascii alphabet and digits.""" + chars = (random.SystemRandom().choice(string.ascii_letters + string.digits) + for _ in range(size)) + return ''.join(chars) + + +def grep(pattern, file_name): + """Return lines of a file matching a pattern.""" + return [ + line.rstrip() for line in open(file_name) if re.search(pattern, line) + ] diff --git a/static/themes/default/icons/mediawiki.png b/static/themes/default/icons/mediawiki.png new file mode 100644 index 0000000000000000000000000000000000000000..6d2706cf93dda53606a4adf4aa197ebcb4352097 GIT binary patch literal 16330 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