Joseph Nuthalapati f69d8289f6
bepasty: Change default permissions to 'read'
Allow read access by URL by default.

Tests:

- Installing bepasty fresh show the default permissions as read.

- Upgrading bepasty from older version when default permissions are none sets
the default permissions to read.

- Upgrading bepasty from older version when default permissions are not none
retrains the permissions.

Signed-off-by: Joseph Nuthalapati <njoseph@riseup.net>
[sunil: Don't relocate setup() method]
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
Tested-by: Sunil Mohan Adapa <sunil@medhas.org>
2020-09-25 14:54:35 -07:00

220 lines
6.6 KiB
Python
Executable File

#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Configuration helper for bepasty.
"""
import argparse
import collections
import grp
import json
import os
import pathlib
import pwd
import secrets
import shutil
import string
import subprocess
import sys
import augeas
from plinth import action_utils
from plinth.modules import bepasty
DATA_DIR = '/var/lib/bepasty'
PASSWORD_LENGTH = 20
CONF_FILE = pathlib.Path('/etc/bepasty-freedombox.conf')
def parse_arguments():
"""Return parsed command line arguments as dictionary."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
setup = subparsers.add_parser(
'setup', help='Perform post-installation operations for bepasty')
setup.add_argument('--domain-name', required=True,
help='The domain name that will be used by bepasty')
subparsers.add_parser('get-configuration', help='Get all configuration')
add_password = subparsers.add_parser(
'add-password', help='Generate a password with given permissions')
add_password.add_argument(
'--permissions', nargs='+',
help='Any number of permissions from the set: {}'.format(', '.join(
bepasty.PERMISSIONS.keys())))
add_password.add_argument(
'--comment', required=False,
help='A comment for the password and its permissions')
subparsers.add_parser('remove-password',
help='Remove a password and its permissions')
set_default = subparsers.add_parser('set-default',
help='Set default permissions')
set_default.add_argument(
'--permissions', nargs='*',
help='Any number of permissions from the set: {}'.format(', '.join(
bepasty.PERMISSIONS.keys())))
subparsers.required = True
return parser.parse_args()
def _augeas_load():
"""Initialize Augeas."""
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)
aug.set('/augeas/load/Simplevars/lens', 'Simplevars.lns')
aug.set('/augeas/load/Simplevars/incl[last() + 1]', str(CONF_FILE))
aug.load()
return aug
def _key_path(key):
"""Return the augeas path for the key."""
return '/files' + str(CONF_FILE) + '/' + key
def conf_file_read():
"""Read and return the configuration."""
aug = _augeas_load()
conf = collections.OrderedDict()
for path in aug.match(_key_path('*')):
key = path.rsplit('/', 1)[-1]
if key[0] != '#':
conf[key] = json.loads(aug.get(path))
return conf
def conf_file_write(conf):
"""Write configuration to the file."""
aug = _augeas_load()
for key, value in conf.items():
if not key.startswith('#'):
value = json.dumps(value)
aug.set(_key_path(key), value)
aug.save()
def subcommand_setup(arguments):
"""Post installation actions for bepasty."""
# Create bepasty group if needed.
try:
grp.getgrnam('bepasty')
except KeyError:
subprocess.run(['addgroup', '--system', 'bepasty'], check=True)
# Create bepasty user if needed.
try:
pwd.getpwnam('bepasty')
except KeyError:
subprocess.run([
'adduser', '--system', '--ingroup', 'bepasty', '--home',
'/var/lib/bepasty', '--gecos', 'bepasty file sharing', 'bepasty'
], check=True)
# Create data directory if needed.
if not os.path.exists(DATA_DIR):
os.makedirs(DATA_DIR, mode=0o750)
shutil.chown(DATA_DIR, user='bepasty', group='bepasty')
# Create configuration file if needed.
if not CONF_FILE.is_file():
passwords = [_generate_password() for _ in range(3)]
conf = {
'#comment':
'This file is managed by FreedomBox. Only a small subset of '
'the original configuration format is supported. Each line '
'should be in KEY = VALUE format. VALUE must be a JSON '
'encoded string.',
'SITENAME': arguments.domain_name,
'STORAGE_FILESYSTEM_DIRECTORY': '/var/lib/bepasty',
'SECRET_KEY': secrets.token_hex(64),
'PERMISSIONS': {
passwords[0]: 'admin,list,create,read,delete',
passwords[1]: 'list,create,read,delete',
passwords[2]: 'list,read',
},
'PERMISSION_COMMENTS': {
passwords[0]: 'admin',
passwords[1]: 'editor',
passwords[2]: 'viewer',
},
'DEFAULT_PERMISSIONS': 'read',
}
conf_file_write(conf)
CONF_FILE.chmod(0o640)
shutil.chown(CONF_FILE, user='bepasty', group='bepasty')
def subcommand_get_configuration(_):
"""Get default permissions, passwords, permissions and comments."""
conf = conf_file_read()
print(json.dumps(conf))
def subcommand_add_password(arguments):
"""Generate a password with given permissions."""
conf = conf_file_read()
permissions = _format_permissions(arguments.permissions)
password = _generate_password()
conf['PERMISSIONS'][password] = permissions
if arguments.comment:
conf['PERMISSION_COMMENTS'][password] = arguments.comment
conf_file_write(conf)
action_utils.service_try_restart('uwsgi')
def subcommand_remove_password(_arguments):
"""Remove a password and its permissions."""
conf = conf_file_read()
password = ''.join(sys.stdin)
if password in conf['PERMISSIONS']:
del conf['PERMISSIONS'][password]
if password in conf['PERMISSION_COMMENTS']:
del conf['PERMISSION_COMMENTS'][password]
conf_file_write(conf)
action_utils.service_try_restart('uwsgi')
def subcommand_set_default(arguments):
"""Set default permissions."""
conf = {'DEFAULT_PERMISSIONS': _format_permissions(arguments.permissions)}
conf_file_write(conf)
action_utils.service_try_restart('uwsgi')
def _format_permissions(permissions=None):
"""Format permissions as comma-separated."""
return ','.join(set(bepasty.PERMISSIONS.keys()).intersection(
permissions)) if permissions else ''
def _generate_password():
"""Generate a random password."""
alphabet = string.ascii_letters + string.digits
return ''.join(secrets.choice(alphabet) for _ in range(PASSWORD_LENGTH))
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()