shadowsocks: Use privileged decorator for actions

Tests:

- Functional tests work
- Initial setup works
- Setting configuration works, correct configuration is updated in the
  configuration files and app shows the values correctly.

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-08-26 10:45:43 -07:00 committed by James Valleroy
parent 97706cef8e
commit 6e7b31a3cf
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 28 additions and 69 deletions

View File

@ -1,12 +1,9 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app to configure Shadowsocks.
"""
"""FreedomBox app to configure Shadowsocks."""
from django.urls import reverse_lazy
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.daemon import Daemon
@ -15,7 +12,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
_description = [
_('Shadowsocks is a lightweight and secure SOCKS5 proxy, designed to '
@ -85,5 +82,5 @@ class ShadowsocksApp(app_module.App):
def setup(self, old_version):
"""Install and configure the app."""
super().setup(old_version)
actions.superuser_run('shadowsocks', ['setup'])
privileged.setup()
self.enable()

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for configuring Shadowsocks.
"""
"""FreedomBox app for configuring Shadowsocks."""
from django import forms
from django.utils.translation import gettext_lazy as _
@ -22,10 +20,10 @@ METHODS = [('chacha20-ietf-poly1305',
class TrimmedCharField(forms.CharField):
"""Trim the contents of a CharField"""
"""Trim the contents of a CharField."""
def clean(self, value):
"""Clean and validate the field value"""
"""Clean and validate the field value."""
if value:
value = value.strip()
@ -33,7 +31,8 @@ class TrimmedCharField(forms.CharField):
class ShadowsocksForm(forms.Form):
"""Shadowsocks configuration form"""
"""Shadowsocks configuration form."""
server = TrimmedCharField(label=_('Server'),
help_text=_('Server hostname or IP address'))

View File

@ -1,42 +1,24 @@
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Helper script for configuring Shadowsocks.
"""
"""Configure Shadowsocks."""
import argparse
import json
import os
import pathlib
import random
import string
import sys
from shutil import move
from typing import Union
from plinth import action_utils
from plinth.modules.shadowsocks import ShadowsocksApp
from plinth.actions import privileged
SHADOWSOCKS_CONFIG_SYMLINK = '/etc/shadowsocks-libev/freedombox.json'
SHADOWSOCKS_CONFIG_ACTUAL = \
'/var/lib/private/shadowsocks-libev/freedombox/freedombox.json'
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('setup', help='Perform initial setup steps')
subparsers.add_parser('get-config',
help='Read and print JSON config to stdout')
subparsers.add_parser('merge-config',
help='Merge JSON config from stdin with existing')
subparsers.required = True
return parser.parse_args()
def subcommand_setup(_):
@privileged
def setup():
"""Perform initial setup steps."""
# Only client socks5 proxy is supported for now. Disable the
# server component.
@ -76,16 +58,16 @@ def subcommand_setup(_):
if not wrong_state_dir.is_symlink() and wrong_state_dir.is_dir():
wrong_state_dir.rmdir()
from plinth.modules.shadowsocks import ShadowsocksApp
if action_utils.service_is_enabled(ShadowsocksApp.DAEMON):
action_utils.service_restart(ShadowsocksApp.DAEMON)
def subcommand_get_config(_):
@privileged
def get_config() -> dict[str, Union[int, str]]:
"""Read and print Shadowsocks configuration."""
try:
print(open(SHADOWSOCKS_CONFIG_SYMLINK, 'r', encoding='utf-8').read())
except Exception:
sys.exit(1)
config = open(SHADOWSOCKS_CONFIG_SYMLINK, 'r', encoding='utf-8').read()
return json.loads(config)
def _merge_config(config):
@ -103,26 +85,13 @@ def _merge_config(config):
open(SHADOWSOCKS_CONFIG_SYMLINK, 'w', encoding='utf-8').write(new_config)
def subcommand_merge_config(_):
@privileged
def merge_config(config: dict[str, Union[int, str]]):
"""Configure Shadowsocks."""
config = sys.stdin.read()
config = json.loads(config)
_merge_config(config)
# Don't try_restart because initial configuration may not be valid so
# shadowsocks will not be running even when enabled.
from . import ShadowsocksApp
if action_utils.service_is_enabled(ShadowsocksApp.DAEMON):
action_utils.service_restart(ShadowsocksApp.DAEMON)
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

@ -1,21 +1,18 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for configuring Shadowsocks.
"""
import json
"""FreedomBox app for configuring Shadowsocks."""
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from plinth import actions, views
from plinth.errors import ActionError
from plinth import views
from . import privileged
from .forms import ShadowsocksForm
class ShadowsocksAppView(views.AppView):
"""Configuration view for Shadowsocks local socks5 proxy."""
app_id = 'shadowsocks'
form_class = ShadowsocksForm
@ -23,10 +20,8 @@ class ShadowsocksAppView(views.AppView):
"""Get initial values for form."""
status = super().get_initial()
try:
configuration = actions.superuser_run('shadowsocks',
['get-config'])
status.update(json.loads(configuration))
except ActionError:
status.update(privileged.get_config())
except Exception:
status.update({
'server': '',
'server_port': 8388,
@ -54,8 +49,7 @@ class ShadowsocksAppView(views.AppView):
'method': new_status['method'],
}
actions.superuser_run('shadowsocks', ['merge-config'],
input=json.dumps(new_config).encode())
privileged.merge_config(new_config)
messages.success(self.request, _('Configuration updated'))
return super().form_valid(form)