bepasty: Add public access config form

Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
James Valleroy 2020-08-09 09:33:23 -04:00 committed by Joseph Nuthalapati
parent 7edc2f4e13
commit 65f4b6750b
No known key found for this signature in database
GPG Key ID: 5398F00A2FA43C35
6 changed files with 177 additions and 31 deletions

View File

@ -9,6 +9,7 @@ import json
import grp
import os
import pwd
import re
import secrets
import shutil
import string
@ -30,6 +31,7 @@ PERMISSIONS = {{
'{}': 'list,create,read,delete', # editor
'{}': 'list,read', # viewer
}}
DEFAULT_PERMISSIONS = ''
"""
PASSWORD_LENGTH = 20
@ -64,6 +66,15 @@ def parse_arguments():
remove_password.add_argument('--password', required=True,
help='The password to be removed')
subparsers.add_parser('get-default', help='Get default 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()
@ -121,34 +132,25 @@ def subcommand_list_passwords(_):
if line.startswith('}'):
in_permissions = False
else:
parts = line.split('#')
try:
comment = parts[1].strip()
except IndexError:
comment = ''
match = re.match(r"\s*'(.*)': '(.*)',\s*(#\s*.*)?", line)
if match:
password = match.group(1)
permissions = match.group(2).split(',')
comment = match.group(3) or ''
comment = comment.lstrip('#').strip()
parts = parts[0].split(':')
password = parts[0].replace("'", '').strip()
permissions = parts[1].replace(
"'", '').strip().rstrip(',').split(',')
passwords.append({
'password': password,
'permissions': ', '.join(permissions),
'comment': comment
})
passwords.append({
'password': password,
'permissions': ', '.join(permissions),
'comment': comment
})
print(json.dumps(passwords))
def subcommand_add_password(arguments):
"""Generate a password with given permissions"""
if arguments.permissions:
permissions = set(bepasty.PERMISSIONS.keys()).intersection(
arguments.permissions)
permissions = ','.join(permissions)
else:
permissions = ''
permissions = _format_permissions(arguments.permissions)
password = _generate_password()
with open(CONF_FILE, 'r') as conf_file:
lines = conf_file.readlines()
@ -194,6 +196,41 @@ def subcommand_remove_password(arguments):
action_utils.service_try_restart('uwsgi')
def subcommand_get_default(_):
"""Get default permissions"""
with open(CONF_FILE, 'r') as conf_file:
lines = conf_file.readlines()
for line in lines:
match = re.match(r"DEFAULT_PERMISSIONS = '(.*)'", line)
if match:
print(match.group(1).replace(',', ' '))
return
def subcommand_set_default(arguments):
"""Set default permissions"""
permissions = _format_permissions(arguments.permissions)
with open(CONF_FILE, 'r') as conf_file:
lines = conf_file.readlines()
with open(CONF_FILE, 'w') as conf_file:
for line in lines:
if line.startswith('DEFAULT_PERMISSIONS'):
conf_file.write(
"DEFAULT_PERMISSIONS = '{}'\n".format(permissions))
else:
conf_file.write(line)
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

View File

@ -44,6 +44,12 @@ PERMISSIONS = {
'admin': _('Admin (lock/unlock files)'),
}
DEFAULT_PERMISSIONS = {
'': _('None (password always required)'),
'read': _('Read files (using their web address)'),
'read list': _('List and read all files'),
}
class BepastyApp(app_module.App):
"""FreedomBox app for bepasty."""
@ -113,3 +119,16 @@ def remove_password(password):
"""Remove a password and its permissions"""
actions.superuser_run('bepasty',
['remove-password', '--password', password])
def get_default_permissions():
"""Get default permissions"""
output = actions.superuser_run('bepasty', ['get-default']).strip()
output = 'read list' if output == 'list read' else output
return output.strip()
def set_default_permissions(permissions):
"""Set default permissions"""
perm = permissions.split()
actions.superuser_run('bepasty', ['set-default', '--permissions'] + perm)

View File

@ -9,6 +9,16 @@ from django.utils.translation import ugettext_lazy as _
from plinth.modules import bepasty
class SetDefaultPermissionsForm(forms.Form):
"""Form to set default permissions"""
default_permissions = forms.ChoiceField(
choices=bepasty.DEFAULT_PERMISSIONS.items(), required=False,
widget=forms.RadioSelect(),
label=_('Public Access (default permissions)'),
help_text=_('Permissions for anonymous users, who have not provided a '
'password.'))
class AddPasswordForm(forms.Form):
"""Form to add a new password."""

View File

@ -13,6 +13,18 @@ Scenario: Enable bepasty application
When I enable the bepasty application
Then the bepasty site should be available
Scenario: Set default permissions to List and read all files
Given the bepasty application is enabled
And I am not logged in to bepasty
When I set the default permissions to List and read all files
Then I should be able to List all Items in bepasty
Scenario: Set default permissions to Read files
Given the bepasty application is enabled
And I am not logged in to bepasty
When I set the default permissions to Read files
Then I should not be able to List all Items in bepasty
Scenario: Add password
Given the bepasty application is enabled
When I add a password

View File

@ -3,7 +3,7 @@
Functional, browser based tests for bepasty app.
"""
from pytest_bdd import scenarios, then, when
from pytest_bdd import given, scenarios, then, when
from plinth.tests import functional
@ -12,6 +12,21 @@ scenarios('bepasty.feature')
last_password_added = None
@given('I am not logged in to bepasty')
def not_logged_in(session_browser):
_logout(session_browser)
@when('I set the default permissions to Read files')
def set_default_permissions_read(session_browser):
_set_default_permissions(session_browser, 'read')
@when('I set the default permissions to List and read all files')
def set_default_permissions_list_read(session_browser):
_set_default_permissions(session_browser, 'read list')
@when('I add a password')
def add_password(session_browser):
global last_password_added
@ -35,6 +50,22 @@ def should_not_login(session_browser):
assert not _can_login(session_browser, last_password_added)
@then('I should be able to List all Items in bepasty')
def should_list_all(session_browser):
assert _can_list_all(session_browser)
@then('I should not be able to List all Items in bepasty')
def should_not_list_all(session_browser):
assert _cannot_list_all(session_browser)
def _set_default_permissions(browser, permissions=''):
functional.nav_to_module(browser, 'bepasty')
browser.choose('default_permissions', permissions)
functional.submit(browser, form_class='form-configuration')
def _add_password(browser):
functional.visit(browser, '/plinth/apps/bepasty/add')
for permission in ['read', 'create', 'list', 'delete', 'admin']:
@ -45,7 +76,7 @@ def _add_password(browser):
def _remove_all_passwords(browser):
functional.visit(browser, '/plinth/apps/bepasty')
functional.nav_to_module(browser, 'bepasty')
while True:
remove_button = browser.find_by_css('.password-remove')
if remove_button:
@ -55,18 +86,32 @@ def _remove_all_passwords(browser):
def _get_password(browser):
functional.visit(browser, '/plinth/apps/bepasty')
functional.nav_to_module(browser, 'bepasty')
return browser.find_by_css('.password-password').first.text
def _can_login(browser, password):
functional.visit(browser, '/bepasty')
logout = browser.find_by_value('Logout')
if logout:
logout.click()
_logout(browser)
browser.fill('token', password)
login = browser.find_by_xpath('//form//button')
functional.submit(browser, login, '/bepasty')
return bool(browser.find_by_value('Logout'))
def _logout(browser):
functional.visit(browser, '/bepasty')
logout = browser.find_by_value('Logout')
if logout:
logout.click()
def _can_list_all(browser):
functional.visit(browser, '/bepasty')
return functional.eventually(browser.links.find_by_href,
['/bepasty/+list'], 5)
def _cannot_list_all(browser):
functional.visit(browser, '/bepasty/+list')
return functional.eventually(browser.is_text_present, ['Forbidden'], 5)

View File

@ -11,16 +11,17 @@ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.http import require_POST
from django.views.generic import FormView
from plinth.errors import ActionError
from plinth.modules import bepasty
from plinth.views import AppView
from .forms import AddPasswordForm
from .forms import AddPasswordForm, SetDefaultPermissionsForm
class BepastyView(AppView):
"""Serve configuration page."""
app_id = 'bepasty'
diagnostics_module_name = 'bepasty'
form_class = SetDefaultPermissionsForm
template_name = 'bepasty.html'
def get_context_data(self, **kwargs):
@ -29,6 +30,28 @@ class BepastyView(AppView):
context['passwords'] = bepasty.list_passwords()
return context
def get_initial(self):
"""Return the status of the service to fill in the form."""
initial = super().get_initial()
initial['default_permissions'] = bepasty.get_default_permissions()
return initial
def form_valid(self, form):
"""Apply the changes submitted in the form."""
old_data = form.initial
form_data = form.cleaned_data
if old_data['default_permissions'] != form_data['default_permissions']:
try:
bepasty.set_default_permissions(
form_data['default_permissions'])
messages.success(self.request, _('Configuration updated.'))
except ActionError:
messages.error(self.request,
_('An error occurred during configuration.'))
return super().form_valid(form)
class AddPasswordView(SuccessMessageMixin, FormView):
"""View to add a new password."""