mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +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/
|
||||
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
|
||||
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
|
||||
@ -979,6 +984,27 @@ License: Apache-2.0
|
||||
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'.
|
||||
|
||||
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
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
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