mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-20 10:34:30 +00:00
bepasty: New app for file upload and sharing
Signed-off-by: James Valleroy <jvalleroy@mailbox.org> Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
parent
66537b1f9c
commit
7edc2f4e13
213
actions/bepasty
Executable file
213
actions/bepasty
Executable file
@ -0,0 +1,213 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
"""
|
||||||
|
Configuration helper for bepasty.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import grp
|
||||||
|
import os
|
||||||
|
import pwd
|
||||||
|
import secrets
|
||||||
|
import shutil
|
||||||
|
import string
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from plinth import action_utils
|
||||||
|
from plinth.modules import bepasty
|
||||||
|
|
||||||
|
DATA_DIR = '/var/lib/bepasty'
|
||||||
|
|
||||||
|
CONF_FILE = '/etc/bepasty-freedombox.conf'
|
||||||
|
|
||||||
|
CONF_CONTENTS = """
|
||||||
|
SITENAME = '{}'
|
||||||
|
STORAGE_FILESYSTEM_DIRECTORY = '/var/lib/bepasty'
|
||||||
|
SECRET_KEY = '{}'
|
||||||
|
PERMISSIONS = {{
|
||||||
|
'{}': 'admin,list,create,read,delete', # admin
|
||||||
|
'{}': 'list,create,read,delete', # editor
|
||||||
|
'{}': 'list,read', # viewer
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
PASSWORD_LENGTH = 20
|
||||||
|
|
||||||
|
|
||||||
|
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',
|
||||||
|
help='The domain name that will be used by bepasty')
|
||||||
|
|
||||||
|
subparsers.add_parser(
|
||||||
|
'list-passwords',
|
||||||
|
help='Get a list of passwords, their permissions and comments')
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
remove_password = subparsers.add_parser(
|
||||||
|
'remove-password', help='Remove a password and its permissions')
|
||||||
|
remove_password.add_argument('--password', required=True,
|
||||||
|
help='The password to be removed')
|
||||||
|
|
||||||
|
subparsers.required = True
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
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 is 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 os.path.isfile(CONF_FILE):
|
||||||
|
# Generate secrets
|
||||||
|
secret_key = secrets.token_hex(64)
|
||||||
|
passwords = []
|
||||||
|
for i in range(3):
|
||||||
|
passwords.append(_generate_password())
|
||||||
|
|
||||||
|
with open(CONF_FILE, 'w') as conf_file:
|
||||||
|
conf_file.write(
|
||||||
|
CONF_CONTENTS.format(arguments.domain_name, secret_key,
|
||||||
|
*passwords))
|
||||||
|
|
||||||
|
os.chmod(CONF_FILE, 0o640)
|
||||||
|
shutil.chown(CONF_FILE, user='bepasty', group='bepasty')
|
||||||
|
|
||||||
|
|
||||||
|
def subcommand_list_passwords(_):
|
||||||
|
"""Get a list of passwords, their permissions and comments"""
|
||||||
|
with open(CONF_FILE, 'r') as conf_file:
|
||||||
|
lines = conf_file.readlines()
|
||||||
|
|
||||||
|
passwords = []
|
||||||
|
in_permissions = False
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('PERMISSIONS'):
|
||||||
|
in_permissions = True
|
||||||
|
elif in_permissions:
|
||||||
|
if line.startswith('}'):
|
||||||
|
in_permissions = False
|
||||||
|
else:
|
||||||
|
parts = line.split('#')
|
||||||
|
try:
|
||||||
|
comment = parts[1].strip()
|
||||||
|
except IndexError:
|
||||||
|
comment = ''
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
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 = ''
|
||||||
|
|
||||||
|
password = _generate_password()
|
||||||
|
with open(CONF_FILE, 'r') as conf_file:
|
||||||
|
lines = conf_file.readlines()
|
||||||
|
|
||||||
|
with open(CONF_FILE, 'w') as conf_file:
|
||||||
|
in_permissions = False
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('PERMISSIONS'):
|
||||||
|
in_permissions = True
|
||||||
|
elif in_permissions:
|
||||||
|
if line.startswith('}'):
|
||||||
|
in_permissions = False
|
||||||
|
conf_file.write(" '{}': '{}',".format(
|
||||||
|
password, permissions))
|
||||||
|
if arguments.comment:
|
||||||
|
conf_file.write(' # {}'.format(arguments.comment))
|
||||||
|
|
||||||
|
conf_file.write('\n')
|
||||||
|
|
||||||
|
conf_file.write(line)
|
||||||
|
|
||||||
|
action_utils.service_try_restart('uwsgi')
|
||||||
|
|
||||||
|
|
||||||
|
def subcommand_remove_password(arguments):
|
||||||
|
"""Remove a password and its permissions"""
|
||||||
|
with open(CONF_FILE, 'r') as conf_file:
|
||||||
|
lines = conf_file.readlines()
|
||||||
|
|
||||||
|
with open(CONF_FILE, 'w') as conf_file:
|
||||||
|
in_permissions = False
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('PERMISSIONS'):
|
||||||
|
in_permissions = True
|
||||||
|
elif in_permissions:
|
||||||
|
if line.startswith('}'):
|
||||||
|
in_permissions = False
|
||||||
|
elif arguments.password in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
conf_file.write(line)
|
||||||
|
|
||||||
|
action_utils.service_try_restart('uwsgi')
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_password():
|
||||||
|
"""Generate a random password"""
|
||||||
|
alphabet = string.ascii_letters + string.digits
|
||||||
|
return ''.join(secrets.choice(alphabet) for i 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()
|
||||||
26
debian/copyright
vendored
26
debian/copyright
vendored
@ -39,6 +39,11 @@ Copyright: Marie Van den Broeck (https://thenounproject.com/marie49/)
|
|||||||
Comment: https://thenounproject.com/icon/162372/
|
Comment: https://thenounproject.com/icon/162372/
|
||||||
License: CC-BY-SA-3.0
|
License: CC-BY-SA-3.0
|
||||||
|
|
||||||
|
Files: static/themes/default/icons/bepasty.svg
|
||||||
|
Copyright: (c) 2014 by the Bepasty Team, see the AUTHORS file.
|
||||||
|
Comment: https://github.com/bepasty/bepasty-server/blob/master/src/bepasty/static/app/bepasty.svg
|
||||||
|
License: BSD-2-clause
|
||||||
|
|
||||||
Files: static/themes/default/icons/cockpit.svg
|
Files: static/themes/default/icons/cockpit.svg
|
||||||
Copyright: Cockpit Authors (https://github.com/cockpit-project/cockpit/blob/master/AUTHORS)
|
Copyright: Cockpit Authors (https://github.com/cockpit-project/cockpit/blob/master/AUTHORS)
|
||||||
Comment: https://github.com/cockpit-project/cockpit/blob/master/src/branding/default/logo.svg
|
Comment: https://github.com/cockpit-project/cockpit/blob/master/src/branding/default/logo.svg
|
||||||
@ -979,6 +984,27 @@ License: Apache-2.0
|
|||||||
On Debian systems, the full text of the Apache Software License version 2 can
|
On Debian systems, the full text of the Apache Software License version 2 can
|
||||||
be found in the file `/usr/share/common-licenses/Apache-2.0'.
|
be found in the file `/usr/share/common-licenses/Apache-2.0'.
|
||||||
|
|
||||||
|
License: BSD-2-clause
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
.
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
.
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
License: BSD-3-clause
|
License: BSD-3-clause
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions
|
modification, are permitted provided that the following conditions
|
||||||
|
|||||||
115
plinth/modules/bepasty/__init__.py
Normal file
115
plinth/modules/bepasty/__init__.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
"""
|
||||||
|
FreedomBox app for bepasty.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from plinth import actions
|
||||||
|
from plinth import app as app_module
|
||||||
|
from plinth import frontpage, menu
|
||||||
|
from plinth.modules.apache.components import Uwsgi, Webserver
|
||||||
|
from plinth.modules.firewall.components import Firewall
|
||||||
|
|
||||||
|
from .manifest import backup, clients # noqa, pylint: disable=unused-import
|
||||||
|
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
managed_packages = ['bepasty', 'uwsgi', 'uwsgi-plugin-python3']
|
||||||
|
|
||||||
|
managed_services = ['uwsgi']
|
||||||
|
|
||||||
|
description = [
|
||||||
|
_('bepasty is a web application that allows all types of files to be '
|
||||||
|
'uploaded and shared.'),
|
||||||
|
_('bepasty does not use usernames for login. It only uses passwords. For '
|
||||||
|
'each password, a set of permissions can be selected. Once you have '
|
||||||
|
'created a password, you can share it with the users who should have the'
|
||||||
|
' associated permissions.'),
|
||||||
|
_('You can also create multiple passwords with the same set of privileges,'
|
||||||
|
' and distribute them to different people or groups. This will allow '
|
||||||
|
'you to later revoke access for a single person or group, by removing '
|
||||||
|
'their password from the list.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
app = None
|
||||||
|
|
||||||
|
PERMISSIONS = {
|
||||||
|
'read': _('Read files (using their web address)'),
|
||||||
|
'create': _('Create or upload files'),
|
||||||
|
'list': _('List all files'),
|
||||||
|
'delete': _('Delete files'),
|
||||||
|
'admin': _('Admin (lock/unlock files)'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BepastyApp(app_module.App):
|
||||||
|
"""FreedomBox app for bepasty."""
|
||||||
|
|
||||||
|
app_id = 'bepasty'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Create components for the app."""
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
info = app_module.Info(self.app_id, version, name=_('bepasty'),
|
||||||
|
icon_filename='bepasty',
|
||||||
|
short_description=_('File Sharing'),
|
||||||
|
description=description, manual_page='bepasty',
|
||||||
|
clients=clients)
|
||||||
|
self.add(info)
|
||||||
|
|
||||||
|
menu_item = menu.Menu('menu-bepasty', info.name,
|
||||||
|
info.short_description, info.icon_filename,
|
||||||
|
'bepasty:index', parent_url_name='apps')
|
||||||
|
self.add(menu_item)
|
||||||
|
|
||||||
|
shortcut = frontpage.Shortcut('shortcut-bepasty', info.name,
|
||||||
|
info.short_description,
|
||||||
|
info.icon_filename, '/bepasty',
|
||||||
|
clients=clients)
|
||||||
|
self.add(shortcut)
|
||||||
|
|
||||||
|
firewall = Firewall('firewall-bepasty', info.name,
|
||||||
|
ports=['http', 'https'], is_external=True)
|
||||||
|
self.add(firewall)
|
||||||
|
|
||||||
|
uwsgi = Uwsgi('uwsgi-bepasty', 'bepasty-freedombox')
|
||||||
|
self.add(uwsgi)
|
||||||
|
|
||||||
|
webserver = Webserver('webserver-bepasty', 'bepasty-freedombox')
|
||||||
|
self.add(webserver)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(helper, old_version=None):
|
||||||
|
"""Install and configure the module."""
|
||||||
|
helper.install(managed_packages)
|
||||||
|
helper.call('post', actions.superuser_run, 'bepasty',
|
||||||
|
['setup', '--domain-name', 'freedombox.local'])
|
||||||
|
helper.call('post', app.enable)
|
||||||
|
|
||||||
|
|
||||||
|
def list_passwords():
|
||||||
|
"""Get a list of passwords, their permissions and comments"""
|
||||||
|
output = actions.superuser_run('bepasty', ['list-passwords'])
|
||||||
|
return json.loads(output)
|
||||||
|
|
||||||
|
|
||||||
|
def add_password(permissions=None, comment=None):
|
||||||
|
"""Generate a password with given permissions"""
|
||||||
|
command = ['add-password']
|
||||||
|
if permissions:
|
||||||
|
command += ['--permissions'] + permissions
|
||||||
|
|
||||||
|
if comment:
|
||||||
|
command += ['--comment', comment]
|
||||||
|
|
||||||
|
actions.superuser_run('bepasty', command)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_password(password):
|
||||||
|
"""Remove a password and its permissions"""
|
||||||
|
actions.superuser_run('bepasty',
|
||||||
|
['remove-password', '--password', password])
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
##
|
||||||
|
## On all sites, provide bepasty on a path: /bepasty
|
||||||
|
##
|
||||||
|
|
||||||
|
# Redirect /bepasty to /bepasty/
|
||||||
|
<Location ~ ^/bepasty$>
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_URI} ^/bepasty$
|
||||||
|
RewriteRule .* /bepasty/ [R=301,L]
|
||||||
|
</IfModule>
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
<Location /bepasty/>
|
||||||
|
ProxyPass unix:/run/uwsgi/app/bepasty-freedombox/socket|uwsgi://bepasty/
|
||||||
|
</Location>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
plinth.modules.bepasty
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
# Use packaged file after #966314 is done.
|
||||||
|
|
||||||
|
[uwsgi]
|
||||||
|
# Who will run the code
|
||||||
|
uid = bepasty
|
||||||
|
gid = bepasty
|
||||||
|
|
||||||
|
# disable logging for privacy
|
||||||
|
#disable-logging = true
|
||||||
|
|
||||||
|
autoload = false
|
||||||
|
|
||||||
|
# Number of workers (usually CPU count)
|
||||||
|
workers = 2
|
||||||
|
|
||||||
|
# The right granted on the created socket
|
||||||
|
chmod-socket = 666
|
||||||
|
|
||||||
|
# Plugin to use and interpretor config
|
||||||
|
single-interpreter = true
|
||||||
|
master = true
|
||||||
|
plugin = python3
|
||||||
|
enable-threads = true
|
||||||
|
lazy-apps = true
|
||||||
|
|
||||||
|
# Module to import
|
||||||
|
module = bepasty.wsgi
|
||||||
|
env = BEPASTY_CONFIG=/etc/bepasty-freedombox.conf
|
||||||
|
|
||||||
|
pythonpath = /usr/lib/python3/dist-packages/
|
||||||
|
buffer-size = 32768
|
||||||
24
plinth/modules/bepasty/forms.py
Normal file
24
plinth/modules/bepasty/forms.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
"""
|
||||||
|
Django forms for bepasty app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from plinth.modules import bepasty
|
||||||
|
|
||||||
|
|
||||||
|
class AddPasswordForm(forms.Form):
|
||||||
|
"""Form to add a new password."""
|
||||||
|
|
||||||
|
permissions = forms.MultipleChoiceField(
|
||||||
|
choices=bepasty.PERMISSIONS.items(),
|
||||||
|
widget=forms.CheckboxSelectMultiple, required=False,
|
||||||
|
label=_('Permissions'), help_text=_(
|
||||||
|
'Users that log in with this password will have the selected '
|
||||||
|
'permissions.'))
|
||||||
|
|
||||||
|
comment = forms.CharField(
|
||||||
|
label=_('Comment'), required=False, strip=True, help_text=_(
|
||||||
|
'Any comment to help you remember the purpose of this password.'))
|
||||||
24
plinth/modules/bepasty/manifest.py
Normal file
24
plinth/modules/bepasty/manifest.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from plinth.clients import validate
|
||||||
|
from plinth.modules.backups.api import validate as validate_backup
|
||||||
|
|
||||||
|
clients = validate([{
|
||||||
|
'name': _('bepasty'),
|
||||||
|
'platforms': [{
|
||||||
|
'type': 'web',
|
||||||
|
'url': '/bepasty'
|
||||||
|
}]
|
||||||
|
}])
|
||||||
|
|
||||||
|
backup = validate_backup({
|
||||||
|
'config': {
|
||||||
|
'files': ['/etc/bepasty-freedombox.conf']
|
||||||
|
},
|
||||||
|
'data': {
|
||||||
|
'directories': ['/var/lib/bepasty']
|
||||||
|
},
|
||||||
|
'services': ['uwsgi'],
|
||||||
|
})
|
||||||
55
plinth/modules/bepasty/templates/bepasty.html
Normal file
55
plinth/modules/bepasty/templates/bepasty.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{% extends "app.html" %}
|
||||||
|
{% comment %}
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load bootstrap %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block configuration %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
<h3>{% trans "Manage Passwords" %}</h3>
|
||||||
|
|
||||||
|
<div class="btn-toolbar">
|
||||||
|
<a href="{% url 'bepasty:add' %}" class="btn btn-default"
|
||||||
|
role="button" title="{% trans 'Add password' %}">
|
||||||
|
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||||
|
{% trans 'Add password' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if not passwords %}
|
||||||
|
<p>{% trans 'No passwords currently configured.' %}</p>
|
||||||
|
{% else %}
|
||||||
|
<table class="table table-bordered table-condensed table-striped" id="passwords-list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Password" %}</th>
|
||||||
|
<th>{% trans "Permissions" %}</th>
|
||||||
|
<th>{% trans "Comment" %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for password in passwords %}
|
||||||
|
<tr id="password-{{ password.password }}" class="password">
|
||||||
|
<td class="password-password">{{ password.password }}</td>
|
||||||
|
<td class="password-permissions">{{ password.permissions }}</td>
|
||||||
|
<td class="password-comment">{{ password.comment }}</td>
|
||||||
|
<td class="password-operations">
|
||||||
|
<form class="form form-inline" method="post"
|
||||||
|
action="{% url 'bepasty:remove' password.password %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button class="password-remove btn btn-sm btn-default fa fa-trash-o"
|
||||||
|
type="submit"></button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
23
plinth/modules/bepasty/templates/bepasty_add.html
Normal file
23
plinth/modules/bepasty/templates/bepasty_add.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% comment %}
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load bootstrap %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>{{ title }}</h3>
|
||||||
|
|
||||||
|
<form class="form form-add" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{{ form|bootstrap }}
|
||||||
|
|
||||||
|
<input type="submit" class="btn btn-primary"
|
||||||
|
value="{% trans "Submit" %}"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
0
plinth/modules/bepasty/tests/__init__.py
Normal file
0
plinth/modules/bepasty/tests/__init__.py
Normal file
39
plinth/modules/bepasty/tests/bepasty.feature
Normal file
39
plinth/modules/bepasty/tests/bepasty.feature
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
@apps @bepasty
|
||||||
|
Feature: bepasty File Sharing
|
||||||
|
Run bepasty file upload and sharing app.
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given I'm a logged in user
|
||||||
|
Given the bepasty application is installed
|
||||||
|
|
||||||
|
Scenario: Enable bepasty application
|
||||||
|
Given the bepasty application is disabled
|
||||||
|
When I enable the bepasty application
|
||||||
|
Then the bepasty site should be available
|
||||||
|
|
||||||
|
Scenario: Add password
|
||||||
|
Given the bepasty application is enabled
|
||||||
|
When I add a password
|
||||||
|
Then I should be able to login to bepasty with that password
|
||||||
|
|
||||||
|
Scenario: Remove password
|
||||||
|
Given the bepasty application is enabled
|
||||||
|
When I remove all passwords
|
||||||
|
Then I should not be able to login to bepasty with that password
|
||||||
|
|
||||||
|
@backups
|
||||||
|
Scenario: Backup and restore bepasty
|
||||||
|
Given the bepasty application is enabled
|
||||||
|
When I add a password
|
||||||
|
And I create a backup of the bepasty app data with name test_bepasty
|
||||||
|
And I remove all passwords
|
||||||
|
And I restore the bepasty app data backup with name test_bepasty
|
||||||
|
Then the bepasty site should be available
|
||||||
|
And I should be able to login to bepasty with that password
|
||||||
|
|
||||||
|
Scenario: Disable bepasty application
|
||||||
|
Given the bepasty application is enabled
|
||||||
|
When I disable the bepasty application
|
||||||
|
Then the bepasty site should not be available
|
||||||
72
plinth/modules/bepasty/tests/test_functional.py
Normal file
72
plinth/modules/bepasty/tests/test_functional.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
"""
|
||||||
|
Functional, browser based tests for bepasty app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pytest_bdd import scenarios, then, when
|
||||||
|
|
||||||
|
from plinth.tests import functional
|
||||||
|
|
||||||
|
scenarios('bepasty.feature')
|
||||||
|
|
||||||
|
last_password_added = None
|
||||||
|
|
||||||
|
|
||||||
|
@when('I add a password')
|
||||||
|
def add_password(session_browser):
|
||||||
|
global last_password_added
|
||||||
|
_remove_all_passwords(session_browser)
|
||||||
|
_add_password(session_browser)
|
||||||
|
last_password_added = _get_password(session_browser)
|
||||||
|
|
||||||
|
|
||||||
|
@when('I remove all passwords')
|
||||||
|
def remove_all_passwords(session_browser):
|
||||||
|
_remove_all_passwords(session_browser)
|
||||||
|
|
||||||
|
|
||||||
|
@then('I should be able to login to bepasty with that password')
|
||||||
|
def should_login(session_browser):
|
||||||
|
assert _can_login(session_browser, last_password_added)
|
||||||
|
|
||||||
|
|
||||||
|
@then('I should not be able to login to bepasty with that password')
|
||||||
|
def should_not_login(session_browser):
|
||||||
|
assert not _can_login(session_browser, last_password_added)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_password(browser):
|
||||||
|
functional.visit(browser, '/plinth/apps/bepasty/add')
|
||||||
|
for permission in ['read', 'create', 'list', 'delete', 'admin']:
|
||||||
|
browser.find_by_css('#id_bepasty-permissions input[value="{}"]'.format(
|
||||||
|
permission)).check()
|
||||||
|
browser.fill('bepasty-comment', 'bepasty functional test')
|
||||||
|
functional.submit(browser, form_class='form-add')
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_all_passwords(browser):
|
||||||
|
functional.visit(browser, '/plinth/apps/bepasty')
|
||||||
|
while True:
|
||||||
|
remove_button = browser.find_by_css('.password-remove')
|
||||||
|
if remove_button:
|
||||||
|
functional.submit(browser, remove_button)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def _get_password(browser):
|
||||||
|
functional.visit(browser, '/plinth/apps/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()
|
||||||
|
|
||||||
|
browser.fill('token', password)
|
||||||
|
login = browser.find_by_xpath('//form//button')
|
||||||
|
functional.submit(browser, login, '/bepasty')
|
||||||
|
|
||||||
|
return bool(browser.find_by_value('Logout'))
|
||||||
15
plinth/modules/bepasty/urls.py
Normal file
15
plinth/modules/bepasty/urls.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
"""
|
||||||
|
URLs for the bepasty module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from .views import AddPasswordView, BepastyView, remove
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^apps/bepasty/$', BepastyView.as_view(), name='index'),
|
||||||
|
url(r'^apps/bepasty/add/$', AddPasswordView.as_view(), name='add'),
|
||||||
|
url(r'^apps/bepasty/(?P<password>[A-Za-z0-9]+)/remove/$', remove,
|
||||||
|
name='remove'),
|
||||||
|
]
|
||||||
62
plinth/modules/bepasty/views.py
Normal file
62
plinth/modules/bepasty/views.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
"""
|
||||||
|
Views for the bepasty app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
from django.views.generic import FormView
|
||||||
|
|
||||||
|
from plinth.modules import bepasty
|
||||||
|
from plinth.views import AppView
|
||||||
|
|
||||||
|
from .forms import AddPasswordForm
|
||||||
|
|
||||||
|
|
||||||
|
class BepastyView(AppView):
|
||||||
|
"""Serve configuration page."""
|
||||||
|
app_id = 'bepasty'
|
||||||
|
diagnostics_module_name = 'bepasty'
|
||||||
|
template_name = 'bepasty.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""Return additional context for rendering the template."""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['passwords'] = bepasty.list_passwords()
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class AddPasswordView(SuccessMessageMixin, FormView):
|
||||||
|
"""View to add a new password."""
|
||||||
|
form_class = AddPasswordForm
|
||||||
|
prefix = 'bepasty'
|
||||||
|
template_name = 'bepasty_add.html'
|
||||||
|
success_url = reverse_lazy('bepasty:index')
|
||||||
|
success_message = _('Password added.')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""Return additional context for rendering the template."""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['title'] = _('Add Password')
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""Add the password on valid form submission."""
|
||||||
|
_add_password(form.cleaned_data)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_password(form_data):
|
||||||
|
bepasty.add_password(form_data['permissions'], form_data['comment'])
|
||||||
|
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
def remove(request, password):
|
||||||
|
"""View to remove a password."""
|
||||||
|
bepasty.remove_password(password)
|
||||||
|
messages.success(request, _('Password deleted.'))
|
||||||
|
return redirect(reverse_lazy('bepasty:index'))
|
||||||
BIN
static/themes/default/icons/bepasty.png
Normal file
BIN
static/themes/default/icons/bepasty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
22
static/themes/default/icons/bepasty.svg
Normal file
22
static/themes/default/icons/bepasty.svg
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:osb="http://www.openswatchbook.org/uri/2009/osb" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="1792" height="1792" viewBox="0 0 1792 1792" id="svg3384" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="bepasty.svg" inkscape:export-filename="/media/Volume/Dokumente/Inkscape/bepasty.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90">
|
||||||
|
<metadata id="metadata3392">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs id="defs3390">
|
||||||
|
<linearGradient id="linearGradient4986" osb:paint="solid">
|
||||||
|
<stop style="stop-color:#ffffff;stop-opacity:1;" offset="0" id="stop4988"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1011" id="namedview3388" showgrid="false" inkscape:zoom="0.22739956" inkscape:cx="410.40107" inkscape:cy="781.49232" inkscape:window-x="0" inkscape:window-y="27" inkscape:window-maximized="1" inkscape:current-layer="svg3384"/>
|
||||||
|
<path d="M1596 380q28 28 48 76t20 88v1152q0 40-28 68t-68 28h-1344q-40 0-68-28t-28-68v-1600q0-40 28-68t68-28h896q40 0 88 20t76 48zm-444-244v376h376q-10-29-22-41l-313-313q-12-12-41-22zm384 1528v-1024h-416q-40 0-68-28t-28-68v-416h-768v1536h1280z" id="path3386" style="fill:#0000ff"/>
|
||||||
|
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:37.96647644px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="70.780403" y="1636.4702" id="text3394" sodipodi:linespacing="125%" transform="scale(0.95420813,1.0479894)"><tspan sodipodi:role="line" id="tspan3396" x="70.780403" y="1636.4702" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2254.98071289px;font-family:'Cabin Condensed';-inkscape-font-specification:'Cabin Condensed, ';fill:#0000ff">B</tspan></text>
|
||||||
|
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:40px;line-height:87.99999952%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="1412.0957" y="762.97424" id="text3398" sodipodi:linespacing="88%"><tspan sodipodi:role="line" id="tspan3400" x="1412.0957" y="762.97424" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:237.5px;line-height:87.99999952%;font-family:'Cabin Condensed';-inkscape-font-specification:'Cabin Condensed, ';fill:#0000ff">p</tspan><tspan sodipodi:role="line" x="1412.0957" y="971.97424" id="tspan3402" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:237.5px;line-height:87.99999952%;font-family:'Cabin Condensed';-inkscape-font-specification:'Cabin Condensed, ';fill:#0000ff">a</tspan><tspan sodipodi:role="line" x="1412.0957" y="1180.9742" id="tspan3404" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:237.5px;line-height:87.99999952%;font-family:'Cabin Condensed';-inkscape-font-specification:'Cabin Condensed, ';fill:#0000ff">s</tspan><tspan sodipodi:role="line" x="1412.0957" y="1389.9742" id="tspan3406" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:237.5px;line-height:87.99999952%;font-family:'Cabin Condensed';-inkscape-font-specification:'Cabin Condensed, ';fill:#0000ff">t</tspan><tspan sodipodi:role="line" x="1412.0957" y="1598.9742" id="tspan3408" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:237.5px;line-height:87.99999952%;font-family:'Cabin Condensed';-inkscape-font-specification:'Cabin Condensed, ';fill:#0000ff">y</tspan></text>
|
||||||
|
<g inkscape:groupmode="layer" id="layer1" inkscape:label="E" style="display:inline;opacity:0.578"/>
|
||||||
|
<g inkscape:groupmode="layer" id="layer2" inkscape:label="Deck" style="display:inline;opacity:1"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.6 KiB |
Loading…
x
Reference in New Issue
Block a user