mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-28 08:03:36 +00:00
zoph: Use privileged decorator for actions
Tests: - Functional tests work - Dump/restore of database works - Initial setup works - MySQL Database is created - Configuration options are set - OSM is enabled by default - User who installed the app becomes admin - Setting configuration works - Enabling OSM - Setting admin user Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
d68a84d245
commit
7f8eebce4c
@ -1,14 +1,10 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
FreedomBox app to configure Zoph web application
|
||||
"""
|
||||
"""FreedomBox app to configure Zoph web application."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from plinth import actions
|
||||
from plinth import app as app_module
|
||||
from plinth import cfg, frontpage, menu
|
||||
from plinth.modules.apache.components import Webserver
|
||||
@ -17,7 +13,7 @@ from plinth.modules.firewall.components import Firewall
|
||||
from plinth.package import Packages
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
from . import manifest
|
||||
from . import manifest, privileged
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -88,45 +84,21 @@ class ZophApp(app_module.App):
|
||||
|
||||
def setup(self, old_version):
|
||||
"""Install and configure the app."""
|
||||
actions.superuser_run('zoph', ['pre-install'])
|
||||
privileged.pre_install()
|
||||
super().setup(old_version)
|
||||
actions.superuser_run('zoph', ['setup'])
|
||||
privileged.setup()
|
||||
self.enable()
|
||||
|
||||
|
||||
def set_configuration(admin_user=None, enable_osm=None):
|
||||
"""Configure Zoph."""
|
||||
args = []
|
||||
if admin_user:
|
||||
args += ['--admin-user', admin_user]
|
||||
|
||||
if enable_osm is not None:
|
||||
args += ['--enable-osm', str(enable_osm)]
|
||||
|
||||
actions.superuser_run('zoph', ['set-configuration'] + args)
|
||||
|
||||
|
||||
def is_configured():
|
||||
"""Return whether the Zoph app is configured."""
|
||||
output = actions.superuser_run('zoph', ['is-configured'])
|
||||
return output.strip() == 'true'
|
||||
|
||||
|
||||
def get_configuration():
|
||||
"""Return full configuration of Zoph."""
|
||||
configuration = actions.superuser_run('zoph', ['get-configuration'])
|
||||
return json.loads(configuration)
|
||||
|
||||
|
||||
class ZophBackupRestore(BackupRestore):
|
||||
"""Component to backup/restore Zoph database"""
|
||||
|
||||
def backup_pre(self, packet):
|
||||
"""Save database contents."""
|
||||
super().backup_pre(packet)
|
||||
actions.superuser_run('zoph', ['dump-database'])
|
||||
privileged.dump_database()
|
||||
|
||||
def restore_post(self, packet):
|
||||
"""Restore database contents."""
|
||||
super().restore_post(packet)
|
||||
actions.superuser_run('zoph', ['restore-database'])
|
||||
privileged.restore_database()
|
||||
|
||||
84
actions/zoph → plinth/modules/zoph/privileged.py
Executable file → Normal file
84
actions/zoph → plinth/modules/zoph/privileged.py
Executable file → Normal file
@ -1,47 +1,21 @@
|
||||
#!/usr/bin/python3
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Configuration helper for Zoph server.
|
||||
"""
|
||||
"""Configuration helper for Zoph server."""
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Optional
|
||||
|
||||
from plinth import action_utils
|
||||
from plinth.actions import privileged
|
||||
|
||||
APACHE_CONF = '/etc/apache2/conf-available/zoph.conf'
|
||||
DB_BACKUP_FILE = '/var/lib/plinth/backups-data/zoph-database.sql'
|
||||
|
||||
|
||||
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('pre-install',
|
||||
help='Perform Zoph pre-install configuration')
|
||||
subparser = subparsers.add_parser('setup',
|
||||
help='Perform Zoph configuration setup')
|
||||
subparsers.add_parser('get-configuration',
|
||||
help='Return the current configuration')
|
||||
subparser = subparsers.add_parser('set-configuration',
|
||||
help='Configure zoph')
|
||||
subparser.add_argument('--admin-user', help='Name of the admin user')
|
||||
subparser.add_argument('--enable-osm', help='Enable OpenSteetMap maps')
|
||||
subparsers.add_parser('is-configured', help='return true if configured')
|
||||
subparsers.add_parser('dump-database', help='Dump database to file')
|
||||
subparsers.add_parser('restore-database',
|
||||
help='Restore database from file')
|
||||
|
||||
subparsers.required = True
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def subcommand_pre_install(_):
|
||||
@privileged
|
||||
def pre_install():
|
||||
"""Preseed debconf values before packages are installed."""
|
||||
action_utils.debconf_set_selections([
|
||||
'zoph zoph/dbconfig-install boolean true',
|
||||
@ -49,7 +23,8 @@ def subcommand_pre_install(_):
|
||||
])
|
||||
|
||||
|
||||
def subcommand_get_configuration(_):
|
||||
@privileged
|
||||
def get_configuration() -> dict[str, str]:
|
||||
"""Return the current configuration."""
|
||||
configuration = {}
|
||||
process = subprocess.run(['zoph', '--dump-config'], stdout=subprocess.PIPE,
|
||||
@ -58,7 +33,7 @@ def subcommand_get_configuration(_):
|
||||
name, value = line.partition(':')[::2]
|
||||
configuration[name.strip()] = value[1:]
|
||||
|
||||
print(json.dumps(configuration))
|
||||
return configuration
|
||||
|
||||
|
||||
def _zoph_configure(key, value):
|
||||
@ -66,7 +41,8 @@ def _zoph_configure(key, value):
|
||||
subprocess.run(['zoph', '--config', key, value], check=True)
|
||||
|
||||
|
||||
def subcommand_setup(_):
|
||||
@privileged
|
||||
def setup():
|
||||
"""Setup Zoph configuration."""
|
||||
_zoph_configure('import.enable', 'true')
|
||||
_zoph_configure('import.upload', 'true')
|
||||
@ -88,19 +64,20 @@ def _get_db_name():
|
||||
return config['zoph']['db_name'].strip('"')
|
||||
|
||||
|
||||
def subcommand_set_configuration(arguments):
|
||||
@privileged
|
||||
def set_configuration(enable_osm: Optional[bool] = None,
|
||||
admin_user: Optional[str] = None):
|
||||
"""Setup Zoph Apache configuration."""
|
||||
_zoph_configure('interface.user.remote', 'true')
|
||||
|
||||
# Note that using OpenSteetmap as a mapping provider is a very nice
|
||||
# feature, but some people may regard its use as a privacy issue
|
||||
if arguments.enable_osm:
|
||||
value = 'osm' if arguments.enable_osm == 'True' else ''
|
||||
if enable_osm is not None:
|
||||
value = 'osm' if enable_osm else ''
|
||||
_zoph_configure('maps.provider', value)
|
||||
|
||||
if arguments.admin_user:
|
||||
if admin_user:
|
||||
# Edit the database to rename the admin user to FreedomBox admin user.
|
||||
admin_user = arguments.admin_user
|
||||
if not re.match(r'^[\w.@][\w.@-]+\Z', admin_user, flags=re.ASCII):
|
||||
# Check to avoid SQL injection
|
||||
raise ValueError('Invalid username')
|
||||
@ -112,13 +89,16 @@ def subcommand_set_configuration(arguments):
|
||||
check=True)
|
||||
|
||||
|
||||
def subcommand_is_configured(_):
|
||||
"""Print whether zoph app is configured."""
|
||||
subprocess.run(['zoph', '--get-config', 'interface.user.remote'],
|
||||
check=True)
|
||||
@privileged
|
||||
def is_configured() -> bool:
|
||||
"""Return whether zoph app is configured."""
|
||||
process = subprocess.run(['zoph', '--get-config', 'interface.user.remote'],
|
||||
stdout=subprocess.PIPE, check=True)
|
||||
return process.stdout.decode().strip() == 'true'
|
||||
|
||||
|
||||
def subcommand_dump_database(_):
|
||||
@privileged
|
||||
def dump_database():
|
||||
"""Dump database to file."""
|
||||
db_name = _get_db_name()
|
||||
os.makedirs(os.path.dirname(DB_BACKUP_FILE), exist_ok=True)
|
||||
@ -127,23 +107,11 @@ def subcommand_dump_database(_):
|
||||
check=True)
|
||||
|
||||
|
||||
def subcommand_restore_database(_):
|
||||
@privileged
|
||||
def restore_database():
|
||||
"""Restore database from file."""
|
||||
db_name = _get_db_name()
|
||||
subprocess.run(['mysqladmin', '--force', 'drop', db_name], check=False)
|
||||
subprocess.run(['mysqladmin', 'create', db_name], check=True)
|
||||
with open(DB_BACKUP_FILE, 'r', encoding='utf-8') as db_restore_file:
|
||||
subprocess.run(['mysql', db_name], stdin=db_restore_file, check=True)
|
||||
|
||||
|
||||
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()
|
||||
@ -1,7 +1,5 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
FreedomBox app for configuring Zoph photo organiser.
|
||||
"""
|
||||
"""FreedomBox app for configuring Zoph photo organiser."""
|
||||
|
||||
import logging
|
||||
|
||||
@ -14,9 +12,8 @@ from django.views.generic import TemplateView
|
||||
|
||||
from plinth import app as app_module
|
||||
from plinth import views
|
||||
from plinth.errors import ActionError
|
||||
from plinth.modules import zoph
|
||||
|
||||
from . import privileged
|
||||
from .forms import ZophForm
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -24,6 +21,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class SetupView(TemplateView):
|
||||
"""Show zoph setup page."""
|
||||
|
||||
template_name = 'zoph-pre-setup.html'
|
||||
success_url = reverse_lazy('zoph:index')
|
||||
|
||||
@ -38,18 +36,19 @@ class SetupView(TemplateView):
|
||||
def post(self, _request, *args, **kwargs):
|
||||
"""Handle form submission."""
|
||||
admin_user = self.request.user.get_username()
|
||||
zoph.set_configuration(admin_user=admin_user)
|
||||
privileged.set_configuration(admin_user=admin_user)
|
||||
return HttpResponseRedirect(reverse_lazy('zoph:index'))
|
||||
|
||||
|
||||
class ZophAppView(views.AppView):
|
||||
"""App configuration page."""
|
||||
|
||||
form_class = ZophForm
|
||||
app_id = 'zoph'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""Redirect to setup page if setup is not done yet."""
|
||||
if not zoph.is_configured():
|
||||
if not privileged.is_configured():
|
||||
return redirect('zoph:setup')
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
@ -57,7 +56,7 @@ class ZophAppView(views.AppView):
|
||||
def get_initial(self):
|
||||
"""Get the current settings from Zoph."""
|
||||
status = super().get_initial()
|
||||
config = zoph.get_configuration()
|
||||
config = privileged.get_configuration()
|
||||
status['enable_osm'] = (config['maps.provider'] == 'osm')
|
||||
return status
|
||||
|
||||
@ -67,9 +66,10 @@ class ZophAppView(views.AppView):
|
||||
new_status = form.cleaned_data
|
||||
if old_status['enable_osm'] != new_status['enable_osm']:
|
||||
try:
|
||||
zoph.set_configuration(enable_osm=new_status['enable_osm'])
|
||||
privileged.set_configuration(
|
||||
enable_osm=new_status['enable_osm'])
|
||||
messages.success(self.request, _('Configuration updated.'))
|
||||
except ActionError:
|
||||
except Exception:
|
||||
messages.error(self.request,
|
||||
_('An error occurred during configuration.'))
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user