apache: Use privileged decorator for actions

Tests:

- Initial setup works when a new container is created
- When transmission is enabled/disabled, the web configuration for it is
  enabled/disabled.
- When radicale is enabled/disabled, the uwsgi configuration for it is
  enabled/disabled.
- Sharing web configuration is disabled during backup and re-enabled.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2022-09-02 16:27:13 -07:00 committed by James Valleroy
parent fdbe537529
commit 3e2900b48b
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
6 changed files with 66 additions and 120 deletions

View File

@ -468,10 +468,9 @@ def _disable_searx() -> bool:
'/etc/uwsgi/apps-enabled/searx.ini').exists() '/etc/uwsgi/apps-enabled/searx.ini').exists()
if searx_is_enabled: if searx_is_enabled:
print('Disabling searx...', flush=True) print('Disabling searx...', flush=True)
subprocess.run([ subprocess.run(
'/usr/share/plinth/actions/apache', 'uwsgi-disable', '--name', ['/usr/share/plinth/actions/actions', 'apache', 'uwsgi_disable'],
'searx' input='{"args": ["searx"], "kwargs": {}}'.encode(), check=True)
], check=True)
return searx_is_enabled return searx_is_enabled
@ -486,9 +485,8 @@ def _update_searx(reenable=False):
if reenable: if reenable:
print('Re-enabling searx after upgrade...', flush=True) print('Re-enabling searx after upgrade...', flush=True)
subprocess.run([ subprocess.run([
'/usr/share/plinth/actions/apache', 'uwsgi-enable', '--name', '/usr/share/plinth/actions/actions', 'apache', 'uwsgi_enable'
'searx' ], input='{"args": ["searx"], "kwargs": {}}'.encode(), check=True)
], check=True)
def _perform_dist_upgrade(): def _perform_dist_upgrade():

View File

@ -1,12 +1,10 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
""" """FreedomBox app for Apache server."""
FreedomBox app for Apache server.
"""
import os import os
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from plinth import actions
from plinth import app as app_module from plinth import app as app_module
from plinth import cfg from plinth import cfg
from plinth.daemon import Daemon, RelatedDaemon from plinth.daemon import Daemon, RelatedDaemon
@ -15,6 +13,8 @@ from plinth.modules.letsencrypt.components import LetsEncrypt
from plinth.package import Packages from plinth.package import Packages
from plinth.utils import format_lazy, is_valid_user_name from plinth.utils import format_lazy, is_valid_user_name
from . import privileged
class ApacheApp(app_module.App): class ApacheApp(app_module.App):
"""FreedomBox app for Apache web server.""" """FreedomBox app for Apache web server."""
@ -60,9 +60,7 @@ class ApacheApp(app_module.App):
def setup(self, old_version): def setup(self, old_version):
"""Install and configure the app.""" """Install and configure the app."""
super().setup(old_version) super().setup(old_version)
actions.superuser_run('apache', privileged.setup(old_version)
['setup', '--old-version',
str(old_version)])
self.enable() self.enable()
@ -70,17 +68,17 @@ class ApacheApp(app_module.App):
def uws_directory_of_user(user): def uws_directory_of_user(user):
"""Returns the directory of the given user's website.""" """Return the directory of the given user's website."""
return '/home/{}/public_html'.format(user) return '/home/{}/public_html'.format(user)
def uws_url_of_user(user): def uws_url_of_user(user):
"""Returns the url path of the given user's website.""" """Return the url path of the given user's website."""
return '/~{}/'.format(user) return '/~{}/'.format(user)
def user_of_uws_directory(directory): def user_of_uws_directory(directory):
"""Returns the user of a given user website directory.""" """Return the user of a given user website directory."""
if directory.startswith('/home/'): if directory.startswith('/home/'):
pos_ini = 6 pos_ini = 6
elif directory.startswith('home/'): elif directory.startswith('home/'):
@ -97,7 +95,7 @@ def user_of_uws_directory(directory):
def user_of_uws_url(url): def user_of_uws_url(url):
"""Returns the user of a given user website url path.""" """Return the user of a given user website url path."""
MISSING = -1 MISSING = -1
pos_ini = url.find('~') pos_ini = url.find('~')
@ -113,7 +111,7 @@ def user_of_uws_url(url):
def uws_directory_of_url(url): def uws_directory_of_url(url):
"""Returns the directory of the user's website for the given url path. """Return the directory of the user's website for the given url path.
Note: It doesn't return the full OS file path to the url path! Note: It doesn't return the full OS file path to the url path!
""" """
@ -121,7 +119,7 @@ def uws_directory_of_url(url):
def uws_url_of_directory(directory): def uws_url_of_directory(directory):
"""Returns the url base path of the user's website for the given OS path. """Return the url base path of the user's website for the given OS path.
Note: It doesn't return the url path for the file! Note: It doesn't return the url path for the file!
""" """
@ -129,10 +127,10 @@ def uws_url_of_directory(directory):
def get_users_with_website(): def get_users_with_website():
"""Returns a dictionary of users with actual website subdirectory.""" """Return a dictionary of users with actual website subdirectory."""
def lst_sub_dirs(directory): def lst_sub_dirs(directory):
"""Returns the list of subdirectories of the given directory.""" """Return the list of subdirectories of the given directory."""
return [ return [
name for name in os.listdir(directory) name for name in os.listdir(directory)
if os.path.isdir(os.path.join(directory, name)) if os.path.isdir(os.path.join(directory, name))

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
""" """App component for other apps to use Apache configuration functionality."""
App component for other apps to use Apache configuration functionality.
"""
import re import re
import subprocess import subprocess
@ -9,7 +7,9 @@ import subprocess
from django.utils.text import format_lazy from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy from django.utils.translation import gettext_lazy
from plinth import action_utils, actions, app from plinth import action_utils, app
from . import privileged
class Webserver(app.LeaderComponent): class Webserver(app.LeaderComponent):
@ -47,14 +47,11 @@ class Webserver(app.LeaderComponent):
def enable(self): def enable(self):
"""Enable the Apache configuration.""" """Enable the Apache configuration."""
actions.superuser_run( privileged.enable(self.web_name, self.kind)
'apache', ['enable', '--name', self.web_name, '--kind', self.kind])
def disable(self): def disable(self):
"""Disable the Apache configuration.""" """Disable the Apache configuration."""
actions.superuser_run( privileged.disable(self.web_name, self.kind)
'apache',
['disable', '--name', self.web_name, '--kind', self.kind])
def diagnose(self): def diagnose(self):
"""Check if the web path is accessible by clients. """Check if the web path is accessible by clients.
@ -99,13 +96,11 @@ class Uwsgi(app.LeaderComponent):
def enable(self): def enable(self):
"""Enable the uWSGI configuration.""" """Enable the uWSGI configuration."""
actions.superuser_run('apache', privileged.uwsgi_enable(self.uwsgi_name)
['uwsgi-enable', '--name', self.uwsgi_name])
def disable(self): def disable(self):
"""Disable the uWSGI configuration.""" """Disable the uWSGI configuration."""
actions.superuser_run('apache', privileged.uwsgi_disable(self.uwsgi_name)
['uwsgi-disable', '--name', self.uwsgi_name])
def is_running(self): def is_running(self):
"""Return whether the uWSGI daemon is running with configuration.""" """Return whether the uWSGI daemon is running with configuration."""

85
actions/apache → plinth/modules/apache/privileged.py Executable file → Normal file
View File

@ -1,48 +1,13 @@
#!/usr/bin/python3
# -*- mode: python -*-
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
""" """Configure Apache web server."""
Configuration helper for Apache web server.
"""
import argparse
import glob import glob
import os import os
import re import re
import subprocess import subprocess
from plinth import action_utils from plinth import action_utils
from plinth.actions import privileged
def parse_arguments():
"""Return parsed command line arguments as dictionary"""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparser = subparsers.add_parser('setup', help='Setup for Apache')
subparser.add_argument(
'--old-version', type=int, required=True,
help='Earlier version of the app that is already setup.')
subparser = subparsers.add_parser(
'enable', help='Enable a site/config/module in apache')
subparser.add_argument('--name',
help='Name of the site/config/module to enable')
subparser.add_argument('--kind', choices=['site', 'config', 'module'])
subparser = subparsers.add_parser(
'disable', help='Disable a site/config/module in apache')
subparser.add_argument('--name',
help='Name of the site/config/module to disable')
subparser.add_argument('--kind', choices=['site', 'config', 'module'])
subparser = subparsers.add_parser(
'uwsgi-enable', help='Enable a site/config/module in UWSGI')
subparser.add_argument('--name',
help='Name of the site/config/module to enable')
subparser = subparsers.add_parser(
'uwsgi-disable', help='Disable a site/config/module in UWSGI')
subparser.add_argument('--name',
help='Name of the site/config/module to disable')
subparsers.required = True
return parser.parse_args()
def _get_sort_key_of_version(version): def _get_sort_key_of_version(version):
@ -87,14 +52,15 @@ def _disable_mod_php(webserver):
webserver.disable('php' + version, kind='module') webserver.disable('php' + version, kind='module')
def subcommand_setup(arguments): @privileged
def setup(old_version: int):
"""Setup Apache configuration.""" """Setup Apache configuration."""
# Regenerate the snakeoil self-signed SSL certificate. This is so that # Regenerate the snakeoil self-signed SSL certificate. This is so that
# FreedomBox images don't all have the same certificate. When FreedomBox # FreedomBox images don't all have the same certificate. When FreedomBox
# package is installed via apt, don't regenerate. When upgrading to newer # package is installed via apt, don't regenerate. When upgrading to newer
# version of Apache FreedomBox app and setting up for the first time don't # version of Apache FreedomBox app and setting up for the first time don't
# regenerate. # regenerate.
if action_utils.is_disk_image() and arguments.old_version == 0: if action_utils.is_disk_image() and old_version == 0:
subprocess.run([ subprocess.run([
'make-ssl-cert', 'generate-default-snakeoil', '--force-overwrite' 'make-ssl-cert', 'generate-default-snakeoil', '--force-overwrite'
], check=True) ], check=True)
@ -178,34 +144,33 @@ def subcommand_setup(arguments):
# TODO: Check that the (name, kind) is a managed by FreedomBox before # TODO: Check that the (name, kind) is a managed by FreedomBox before
# performing operation. # performing operation.
def subcommand_enable(arguments): @privileged
def enable(name: str, kind: str):
"""Enable an Apache site/config/module.""" """Enable an Apache site/config/module."""
action_utils.webserver_enable(arguments.name, arguments.kind) _assert_kind(kind)
action_utils.webserver_enable(name, kind)
def subcommand_disable(arguments): @privileged
def disable(name: str, kind: str):
"""Disable an Apache site/config/module.""" """Disable an Apache site/config/module."""
action_utils.webserver_disable(arguments.name, arguments.kind) _assert_kind(kind)
action_utils.webserver_disable(name, kind)
def subcommand_uwsgi_enable(arguments): def _assert_kind(kind: str):
"""Raise and exception if kind parameter has an unexpected value."""
if kind not in ('site', 'config', 'module'):
raise ValueError('Invalid value for parameter kind')
@privileged
def uwsgi_enable(name: str):
"""Enable uWSGI configuration and reload.""" """Enable uWSGI configuration and reload."""
action_utils.uwsgi_enable(arguments.name) action_utils.uwsgi_enable(name)
def subcommand_uwsgi_disable(arguments): @privileged
def uwsgi_disable(name: str):
"""Disable uWSGI configuration and reload.""" """Disable uWSGI configuration and reload."""
action_utils.uwsgi_disable(arguments.name) action_utils.uwsgi_disable(name)
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()

View File

@ -47,27 +47,22 @@ def test_webserver_is_enabled(webserver_is_enabled):
webserver_is_enabled.assert_has_calls([call('test-config', kind='module')]) webserver_is_enabled.assert_has_calls([call('test-config', kind='module')])
@patch('plinth.actions.superuser_run') @patch('plinth.modules.apache.privileged.enable')
def test_webserver_enable(superuser_run): def test_webserver_enable(enable):
"""Test that enabling webserver configuration works.""" """Test that enabling webserver configuration works."""
webserver = Webserver('test-webserver', 'test-config', kind='module') webserver = Webserver('test-webserver', 'test-config', kind='module')
webserver.enable() webserver.enable()
superuser_run.assert_has_calls([ enable.assert_has_calls([call('test-config', 'module')])
call('apache', ['enable', '--name', 'test-config', '--kind', 'module'])
])
@patch('plinth.actions.superuser_run') @patch('plinth.modules.apache.privileged.disable')
def test_webserver_disable(superuser_run): def test_webserver_disable(disable):
"""Test that disabling webserver configuration works.""" """Test that disabling webserver configuration works."""
webserver = Webserver('test-webserver', 'test-config', kind='module') webserver = Webserver('test-webserver', 'test-config', kind='module')
webserver.disable() webserver.disable()
superuser_run.assert_has_calls([ disable.assert_has_calls([call('test-config', 'module')])
call('apache',
['disable', '--name', 'test-config', '--kind', 'module'])
])
@patch('plinth.modules.apache.components.diagnose_url') @patch('plinth.modules.apache.components.diagnose_url')
@ -132,24 +127,22 @@ def test_uwsgi_is_enabled(uwsgi_is_enabled, service_is_enabled):
assert not uwsgi.is_enabled() assert not uwsgi.is_enabled()
@patch('plinth.actions.superuser_run') @patch('plinth.modules.apache.privileged.uwsgi_enable')
def test_uwsgi_enable(superuser_run): def test_uwsgi_enable(enable):
"""Test that enabling uwsgi configuration works.""" """Test that enabling uwsgi configuration works."""
uwsgi = Uwsgi('test-uwsgi', 'test-config') uwsgi = Uwsgi('test-uwsgi', 'test-config')
uwsgi.enable() uwsgi.enable()
superuser_run.assert_has_calls( enable.assert_has_calls([call('test-config')])
[call('apache', ['uwsgi-enable', '--name', 'test-config'])])
@patch('plinth.actions.superuser_run') @patch('plinth.modules.apache.privileged.uwsgi_disable')
def test_uwsgi_disable(superuser_run): def test_uwsgi_disable(disable):
"""Test that disabling uwsgi configuration works.""" """Test that disabling uwsgi configuration works."""
uwsgi = Uwsgi('test-uwsgi', 'test-config') uwsgi = Uwsgi('test-uwsgi', 'test-config')
uwsgi.disable() uwsgi.disable()
superuser_run.assert_has_calls( disable.assert_has_calls([call('test-config')])
[call('apache', ['uwsgi-disable', '--name', 'test-config'])])
@patch('plinth.action_utils.service_is_running') @patch('plinth.action_utils.service_is_running')

View File

@ -15,6 +15,7 @@ import logging
from plinth import action_utils, actions from plinth import action_utils, actions
from plinth import app as app_module from plinth import app as app_module
from plinth import setup from plinth import setup
from plinth.modules.apache import privileged as apache_privileged
from .components import BackupRestore from .components import BackupRestore
@ -340,16 +341,12 @@ class ApacheServiceHandler(ServiceHandler):
self.was_enabled = action_utils.webserver_is_enabled( self.was_enabled = action_utils.webserver_is_enabled(
self.web_name, kind=self.kind) self.web_name, kind=self.kind)
if self.was_enabled: if self.was_enabled:
actions.superuser_run( apache_privileged.disable(self.web_name, self.kind)
'apache',
['disable', '--name', self.web_name, '--kind', self.kind])
def restart(self): def restart(self):
"""Restart the service if it was earlier running.""" """Restart the service if it was earlier running."""
if self.was_enabled: if self.was_enabled:
actions.superuser_run( apache_privileged.enable(self.web_name, self.kind)
'apache',
['enable', '--name', self.web_name, '--kind', self.kind])
def _shutdown_services(components): def _shutdown_services(components):