monkeysphere: Drop app as it is not being used

Closes #2157.

Signed-off-by: Joseph Nuthalapati <njoseph@riseup.net>
[sunil: Split diaspora and tahoe-lafs into separate commits]
[sunil: Remove monkeysphere from help/tests/test_views.py]
[sunil: Add to configuration file removal in Debian package and setup.py]
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
Joseph Nuthalapati 2021-12-11 11:50:09 +05:30 committed by Sunil Mohan Adapa
parent f72505d300
commit ce5274d9ee
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
16 changed files with 15 additions and 916 deletions

View File

@ -1,318 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Configuration helper for monkeysphere.
"""
import argparse
import contextlib
import copy
import json
import os
import re
import shutil
import signal
import subprocess
import tempfile
import augeas
import psutil
def parse_arguments():
"""Return parsed command line arguments as dictionary."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
host_show_keys = subparsers.add_parser(
'host-show-keys', help='Show imported/importable keys')
host_show_keys.add_argument('key_id', nargs='?',
help='Optional KEYID to retrieve details for')
host_import_key = subparsers.add_parser(
'host-import-key', help='Import a key into monkeysphere')
host_import_key.add_argument('ssh_fingerprint',
help='SSH fingerprint of the key to import')
host_import_key.add_argument('domains', nargs='*',
help='List of available domains')
host_publish_key = subparsers.add_parser('host-publish-key',
help='Push host key to keyserver')
host_publish_key.add_argument('key_ids', nargs='*',
help='Optional list of KEYIDs')
host_cancel_publish = subparsers.add_parser(
'host-cancel-publish', help='Cancel a running publish operation')
host_cancel_publish.add_argument('pid', help='PID of the publish process')
subparsers.required = True
return parser.parse_args()
def get_ssh_keys(fingerprint_hash):
"""Return all SSH keys."""
keys = {}
key_files = ['/etc/ssh/ssh_host_rsa_key']
for key_file in key_files:
output = subprocess.check_output(
['ssh-keygen', '-l', '-E', fingerprint_hash, '-f', key_file])
fingerprint = output.decode().split()[1]
keys[fingerprint] = {
'ssh_fingerprint': fingerprint,
'service': 'ssh',
'key_file': key_file,
'available_domains': ['*']
}
return keys
def get_pem_ssh_fingerprint(pem_file, fingerprint_hash):
"""Return the SSH fingerprint of a PEM file."""
public_key = subprocess.check_output(
['openssl', 'rsa', '-in', pem_file, '-pubout'],
stderr=subprocess.DEVNULL)
ssh_public_key = subprocess.check_output(
['ssh-keygen', '-i', '-m', 'PKCS8', '-f', '/dev/stdin'],
input=public_key)
fingerprint = subprocess.check_output(
['ssh-keygen', '-l', '-E', fingerprint_hash, '-f', '/dev/stdin'],
input=ssh_public_key)
return fingerprint.decode().split()[1]
def get_https_keys(fingerprint_hash):
"""Return all HTTPS keys."""
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)
aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
aug.set('/augeas/load/Httpd/incl[last() + 1]',
'/etc/apache2/sites-available/*')
aug.set('/augeas/load/Httpd/incl[last() + 1]',
'/etc/apache2/conf-available/*')
aug.load()
# Read from default-tls.conf and default-ssl.conf
keys = {}
path = '/files/etc/apache2/sites-available//VirtualHost'
for match in aug.match(path):
host = {'available_domains': ['*'], 'service': 'https'}
for directive in aug.match(match + '/directive'):
name = aug.get(directive)
if name == 'ServerName':
host['available_domains'] = [aug.get(directive + '/arg')]
elif name in ('GnuTLSKeyFile', 'SSLCertificateKeyFile'):
host['key_file'] = aug.get(directive + '/arg')
if 'key_file' in host:
host['ssh_fingerprint'] = get_pem_ssh_fingerprint(
host['key_file'], fingerprint_hash)
keys[host['ssh_fingerprint']] = host
# Read from FreedomBox configured domains with proper SSL certs.
path = "/files/etc/apache2/sites-available//" \
"directive[. = 'Use'][arg[1] = 'FreedomBoxTLSSiteMacro']"
key_file = ("/files/etc/apache2//Macro[arg[1] = 'FreedomBoxTLSSiteMacro']"
"//VirtualHost/directive[. = 'GnuTLSKeyFile']/arg")
key_file = aug.get(key_file)
for match in aug.match(path):
domain = aug.get(match + '/arg[2]')
host = {
'available_domains': [domain],
'service': 'https',
'key_file': key_file.replace('$domain', domain)
}
host['ssh_fingerprint'] = get_pem_ssh_fingerprint(
host['key_file'], fingerprint_hash)
keys[host['ssh_fingerprint']] = host
return keys
def get_monkeysphere_keys(key_id=None):
"""Return the list of keys imported into monkeysphere."""
try:
key_ids = [] if not key_id else [key_id]
output = subprocess.check_output(['monkeysphere-host', 'show-keys'] +
key_ids, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
# no keys available
return {}
# parse output
default_dict = {'imported_domains': [], 'available_domains': []}
keys = [copy.deepcopy(default_dict)]
lines = output.decode().strip().split('\n')
for line in lines:
if line.startswith('pub'):
data = line.lstrip('pub').split()
keys[-1]['pub'] = data[0]
keys[-1]['date'] = data[1]
elif line.startswith('uid'):
uid = line.lstrip('uid').strip()
if uid.startswith('['):
uid = uid[uid.index(']') + 1:].strip()
keys[-1].setdefault('uids', []).append(uid)
matches = re.match(r'([a-zA-Z]*)://(.*)(:\d*)?', uid)
keys[-1]['service'] = matches.group(1)
keys[-1]['imported_domains'].append(matches.group(2))
elif line.startswith('OpenPGP fingerprint:'):
keys[-1]['openpgp_fingerprint'] = \
line.lstrip('OpenPGP fingerprint:')
elif line.startswith('ssh fingerprint:'):
data = line.lstrip('ssh fingerprint:').split()
keys[-1]['ssh_key_size'] = data[0]
keys[-1]['ssh_fingerprint'] = data[1]
keys[-1]['ssh_key_type'] = data[2].strip('()')
elif line == '':
keys.append(copy.deepcopy(default_dict))
return {key['ssh_fingerprint']: key for key in keys}
def get_merged_keys(key_id=None):
"""Return merged list of system and monkeysphere keys."""
keys = get_monkeysphere_keys(key_id)
# Monkeysphere used use MD5 for fingerprint hash and recently
# changed to SHA256. In case of SHA256 the string 'SHA256:' is
# being prepended to the fingerprint. Hoping that such a prefix
# will be available in all future changes, extract it from one key
# (assuming all the others will be the same) and use it.
fingerprint_hash = 'SHA256'
if keys:
fingerprint_hash = list(keys.keys())[0].split(':')[0]
system_keys = list(get_ssh_keys(fingerprint_hash).items()) + \
list(get_https_keys(fingerprint_hash).items())
for ssh_fingerprint, key in system_keys:
if key_id and ssh_fingerprint not in keys:
continue
if ssh_fingerprint in keys:
keys[ssh_fingerprint].update({
'available_domains': key['available_domains'],
'key_file': key['key_file']
})
else:
keys[ssh_fingerprint] = key
return keys
@contextlib.contextmanager
def _get_ssh_key_file_for_import(original_key_file, service):
"""Return an SSH key file that can be imported into monkeysphere.
If the key file is in PEM format, the key file can be used as it is.
Otherwise, if the file is in the OpenSSH key format, which is default since
7.8, then convert it to PEM format.
"""
if service != 'ssh':
yield original_key_file
return
first_line = open(original_key_file, 'r').readline()
if '--BEGIN OPENSSH PRIVATE KEY--' not in first_line:
yield original_key_file
return
with tempfile.TemporaryDirectory() as temp_directory:
key_file = os.path.join(temp_directory, 'ssh_key_file')
shutil.copy2(original_key_file, key_file)
# Convert OpenSSH format to PEM
subprocess.run(
['ssh-keygen', '-p', '-N', '', '-m', 'PEM', '-f', key_file],
check=True)
yield key_file
def subcommand_host_show_keys(arguments):
"""Show host key fingerprints."""
print(json.dumps({'keys': get_merged_keys(arguments.key_id)}))
def subcommand_host_import_key(arguments, second_run=False):
"""Import host SSH key."""
keys = get_merged_keys()
if arguments.ssh_fingerprint not in keys:
raise Exception('Unknown SSH fingerprint')
key = keys[arguments.ssh_fingerprint]
if '*' in key['available_domains']:
key['available_domains'] = arguments.domains
if 'openpgp_fingerprint' not in key and not second_run:
env = dict(os.environ, MONKEYSPHERE_PROMPT='false')
with _get_ssh_key_file_for_import(key['key_file'],
key['service']) as key_file:
subprocess.check_call([
'monkeysphere-host', 'import-key', key_file,
key['service'] + '://' + key['available_domains'][0]
], env=env)
subcommand_host_import_key(arguments, second_run=True)
else:
for domain in key['available_domains']:
if domain in key['imported_domains']:
continue
env = dict(os.environ, MONKEYSPHERE_PROMPT='false')
subprocess.check_call([
'monkeysphere-host', 'add-servicename',
key['service'] + '://' + domain, key['openpgp_fingerprint']
], env=env)
def subcommand_host_publish_key(arguments):
"""Push host key to keyserver."""
# setting TMPDIR as workaround for Debian bug #656750
proc = subprocess.Popen(
['monkeysphere-host', 'publish-keys'] + arguments.key_ids,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=dict(os.environ,
TMPDIR='/var/lib/monkeysphere/authentication/tmp/',
MONKEYSPHERE_PROMPT='false'))
output, error = proc.communicate()
output, error = output.decode(), error.decode()
if proc.returncode != 0:
raise Exception(output, error)
print(output)
def subcommand_host_cancel_publish(arguments):
"""Kill a running publish process."""
process = psutil.Process(int(arguments.pid))
# Perform tight checks on the process before killing for security.
arguments = process.cmdline()
if not arguments:
# Process already completed
return
# Remove the sudo prefix if present
while arguments[0] == 'sudo' or arguments[0].startswith('-'):
arguments = arguments[1:]
if len(arguments) >= 2 and \
arguments[0].split('/')[-1] == 'monkeysphere' and \
arguments[1] == 'host-publish-key':
process.send_signal(signal.SIGTERM)
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

@ -13,3 +13,4 @@ rm_conffile /etc/apt/preferences.d/50freedombox3.pref 20.5~
rm_conffile /etc/plinth/plinth.config 20.12~
rm_conffile /etc/plinth/custom-shortcuts.json 20.12~
rm_conffile /etc/plinth/modules-enabled/coquelicot 20.14~
rm_conffile /etc/plinth/modules-enabled/monkeysphere 21.16~

View File

@ -109,17 +109,16 @@ MANUAL_PAGES = ('Apache_userdir', 'APU', 'Backups', 'BananaPro', 'BeagleBone',
'freedombox-manual', 'GettingHelp', 'GitWeb', 'Hardware',
'I2P', 'Ikiwiki', 'Infinoted', 'Introduction', 'JSXC',
'LetsEncrypt', 'Maker', 'MatrixSynapse', 'MediaWiki',
'Minetest', 'MiniDLNA', 'MLDonkey', 'Monkeysphere', 'Mumble',
'NameServices', 'Networks', 'OpenVPN', 'OrangePiZero',
'PageKite', 'pcDuino3', 'Performance', 'PineA64+',
'PioneerEdition', 'Plinth', 'Power', 'Privoxy', 'Quassel',
'QuickStart', 'Radicale', 'RaspberryPi2', 'RaspberryPi3B+',
'RaspberryPi3B', 'RaspberryPi4B', 'ReleaseNotes', 'Rock64',
'RockPro64', 'Roundcube', 'Samba', 'Searx', 'SecureShell',
'Security', 'ServiceDiscovery', 'Shadowsocks', 'Sharing',
'Snapshots', 'Storage', 'Syncthing', 'TinyTinyRSS', 'Tor',
'Transmission', 'Upgrades', 'USBWiFi', 'Users', 'VirtualBox',
'WireGuard')
'Minetest', 'MiniDLNA', 'MLDonkey', 'Mumble', 'NameServices',
'Networks', 'OpenVPN', 'OrangePiZero', 'PageKite', 'pcDuino3',
'Performance', 'PineA64+', 'PioneerEdition', 'Plinth', 'Power',
'Privoxy', 'Quassel', 'QuickStart', 'Radicale', 'RaspberryPi2',
'RaspberryPi3B+', 'RaspberryPi3B', 'RaspberryPi4B',
'ReleaseNotes', 'Rock64', 'RockPro64', 'Roundcube', 'Samba',
'Searx', 'SecureShell', 'Security', 'ServiceDiscovery',
'Shadowsocks', 'Sharing', 'Snapshots', 'Storage', 'Syncthing',
'TinyTinyRSS', 'Tor', 'Transmission', 'Upgrades', 'USBWiFi',
'Users', 'VirtualBox', 'WireGuard')
_restricted_reason = ('Needs installed manual. '
'CI speed-optimized workspace does not provide it.')
not_restricted_environment = pytest.mark.skipif(not canary.exists(),

View File

@ -1,74 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for monkeysphere.
"""
from django.utils.translation import gettext_lazy as _
from plinth import app as app_module
from plinth import menu
from plinth.modules.backups.components import BackupRestore
from plinth.modules.users.components import UsersAndGroups
from plinth.package import Packages
from . import manifest
_description = [
_('With Monkeysphere, an OpenPGP key can be generated for each configured '
'domain serving SSH. The OpenPGP public key can then be uploaded to the '
'OpenPGP keyservers. Users connecting to this machine through SSH can '
'verify that they are connecting to the correct host. For users to '
'trust the key, at least one person (usually the machine owner) must '
'sign the key using the regular OpenPGP key signing process. See the '
'<a href="http://web.monkeysphere.info/getting-started-ssh/"> '
'Monkeysphere SSH documentation</a> for more details.'),
_('Monkeysphere can also generate an OpenPGP key for each Secure Web '
'Server (HTTPS) certificate installed on this machine. The OpenPGP '
'public key can then be uploaded to the OpenPGP keyservers. Users '
'accessing the web server through HTTPS can verify that they are '
'connecting to the correct host. To validate the certificate, the user '
'will need to install some software that is available on the '
'<a href="https://web.monkeysphere.info/download/"> Monkeysphere '
'website</a>.')
]
app = None
class MonkeysphereApp(app_module.App):
"""FreedomBox app for Monkeysphere."""
app_id = 'monkeysphere'
_version = 1
def __init__(self):
"""Create components for the app."""
super().__init__()
info = app_module.Info(app_id=self.app_id, version=self._version,
name=_('Monkeysphere'), icon='fa-certificate',
description=_description,
manual_page='Monkeysphere')
self.add(info)
menu_item = menu.Menu('menu-monkeysphere', info.name, None, info.icon,
'monkeysphere:index', parent_url_name='system',
advanced=True)
self.add(menu_item)
packages = Packages('packages-monkeysphere', ['monkeysphere'])
self.add(packages)
users_and_groups = UsersAndGroups('users-and-groups-monkeysphere',
reserved_usernames=['monkeysphere'])
self.add(users_and_groups)
backup_restore = BackupRestore('backup-restore-monkeysphere',
**manifest.backup)
self.add(backup_restore)
def setup(helper, old_version=None):
"""Install and configure the module."""
app.setup(old_version)

View File

@ -1 +0,0 @@
plinth.modules.monkeysphere

View File

@ -1,13 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Application manfiest for monkeysphere.
"""
backup = {
'config': {
'directories': ['/etc/monkeysphere/']
},
'secrets': {
'directories': ['/var/lib/monkeysphere/']
}
}

View File

@ -1,20 +0,0 @@
/*
# SPDX-License-Identifier: AGPL-3.0-or-later
*/
td li {
list-style: none;
line-height: 2rem;
}
td ul {
padding: 0;
}
.form-action.pull-right {
margin-right: 0.625rem;
}
.form-action button {
width: 7.875rem;
}

View File

@ -1,136 +0,0 @@
{% extends "app.html" %}
{% comment %}
# SPDX-License-Identifier: AGPL-3.0-or-later
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
{% load static %}
{% block page_head %}
<link type="text/css" rel="stylesheet"
href="{% static 'monkeysphere/monkeysphere.css' %}"/>
{% endblock %}
{% block configuration %}
{% if running %}
<p class="running-status-parent">
<span class="running-status loading"></span>
{% trans "Publishing key to keyserver..." %}
<form class="form" method="post"
action="{% url 'monkeysphere:cancel' %}">
{% csrf_token %}
<button type="submit" class="btn btn-warning btn-sm">
{% trans "Cancel" %}</button>
</form>
</p>
{% endif %}
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{% trans "Service" %}</th>
<th>{% trans "Domains" %}</th>
<th>{% trans "OpenPGP Fingerprint" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for key in status.keys.values|dictsort:"ssh_fingerprint" %}
<tr class="monkeysphere-service-{{ key.service }}">
<td>
{% if key.service == 'ssh' %}
{% trans "Secure Shell" %}
{% elif key.service == 'https' %}
{% trans "Web Server" %}
{% else %}
{% trans "Other" %}
{% endif %}
</td>
<td>
<ul>
{% for domain in key.all_domains %}
{% if domain not in key.imported_domains %}
<li>
<span class="badge badge-secondary"
><span class="fa fa-times"
aria-hidden="true"></span></span>
<span class="monkeysphere-importable-domain"
>{{ domain }}</span>
</li>
{% elif domain not in key.available_domains %}
<li>
<span class="badge badge-warning"
><span class="fa fa-exclamation-triangle"
aria-hidden="true"></span></span>
<span class="monkeysphere-unavailable-domain"
>{{ domain }}</span>
</li>
{% else %}
<li>
<span class="badge badge-success"
><span class="fa fa-check"
aria-hidden="true"></span></span>
<span class="monkeysphere-imported-domain"
>{{ domain }}</span>
</li>
{% endif %}
{% endfor %}
</ul>
</td>
<td>
{% if key.openpgp_fingerprint %}
<a href="{% url 'monkeysphere:details' key.openpgp_fingerprint %}"
title="{% blocktrans trimmed with fingerprint=key.openpgp_fingerprint %}
Show details for key {{ fingerprint }}
{% endblocktrans %}">
{{ key.openpgp_fingerprint }}
</a>
{% else %}
{% trans "-" %}
{% endif %}
</td>
<td>
{% if not key.openpgp_fingerprint %}
<form class="form pull-right form-action form-import"
method="post"
action="{% url 'monkeysphere:import' key.ssh_fingerprint %}">
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-sm pull-right button-import">
{% trans "Import Key" %}</button>
</form>
{% else %}
{% if not running %}
<form class="form pull-right form-action form-publish"
method="post"
action="{% url 'monkeysphere:publish' key.openpgp_fingerprint %}">
{% csrf_token %}
<button type="submit" class="btn btn-warning btn-sm pull-right button-publish">
{% trans "Publish Key" %}</button>
</form>
{% endif %}
{% if key.importable_domains %}
<form class="form pull-right form-action form-add-domain"
method="post"
action="{% url 'monkeysphere:import' key.ssh_fingerprint %}">
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-sm pull-right button-add-domains">
{% trans "Add Domains" %}</button>
</form>
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -1,85 +0,0 @@
{% extends "base.html" %}
{% comment %}
# SPDX-License-Identifier: AGPL-3.0-or-later
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
{% block content %}
<h2>{% trans "Monkeysphere" %}</h2>
<div class="table-responsive">
<table class="table">
<tbody>
<tr>
<td>{% trans "OpenPGP Fingerprint" %}</td>
<td>{{ key.openpgp_fingerprint }}</td>
</tr>
<tr>
<td>{% trans "OpenPGP User IDs" %}</td>
<td>{{ key.uids|join:', ' }}</td>
</tr>
<tr>
<td>{% trans "Key Import Date" %}</td>
<td>{{ key.date }}</td>
</tr>
<tr>
<td>{% trans "SSH Key Type" %}</td>
<td>{{ key.ssh_key_type }}</td>
</tr>
<tr>
<td>{% trans "SSH Key Size" %}</td>
<td>{{ key.ssh_key_size }}</td>
</tr>
<tr>
<td>{% trans "SSH Fingerprint" %}</td>
<td>{{ key.ssh_fingerprint }}</td>
</tr>
<tr>
<td>{% trans "Service" %}</td>
<td>
{% if key.service == 'ssh' %}
{% trans "Secure Shell" %}
{% elif key.service == 'https' %}
{% trans "Web Server" %}
{% else %}
{% trans "Other" %}
{% endif %}
</td>
</tr>
<tr>
<td>{% trans "Key File" %}</td>
<td>{{ key.key_file }}</td>
</tr>
<tr>
<td>{% trans "Available Domains" %}</td>
<td>{{ key.available_domains|join:', ' }}</td>
</tr>
<tr>
<td>{% trans "Added Domains" %}</td>
<td>{{ key.imported_domains|join:', ' }}</td>
</tr>
</tbody>
</table>
</div>
<p>
{% blocktrans trimmed %}
After this key is published to the keyservers, it can be signed using
<a href="https://www.gnupg.org/">GnuPG</a> with the following commands:
{% endblocktrans %}
</p>
<pre>
# {% trans "Download the key" %}
gpg --recv-key {{ key.openpgp_fingerprint }}
# {% trans "Sign the key" %}
gpg --sign-key {{ key.openpgp_fingerprint }}
# {% trans "Send the key back to the keyservers" %}
gpg --send-key {{ key.openpgp_fingerprint }}
</pre>
{% endblock %}

View File

@ -1,100 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Functional, browser based tests for monkeysphere app.
"""
import pytest
from plinth.tests import functional
pytestmark = [pytest.mark.system, pytest.mark.monkeysphere]
@pytest.fixture(scope='module', autouse=True)
def fixture_background(session_browser):
"""Login and install the app."""
functional.login(session_browser)
functional.set_advanced_mode(session_browser, True)
functional.install(session_browser, 'monkeysphere')
functional.set_domain_name(session_browser, 'mydomain.example')
yield
def test_import_ssh_keys(session_browser):
"""Test importing SSH keys."""
_import_key(session_browser, 'ssh', 'mydomain.example')
_assert_imported_key(session_browser, 'ssh', 'mydomain.example')
def test_import_https_keys(session_browser):
"""Test importing HTTPS keys."""
_import_key(session_browser, 'https', 'mydomain.example')
_assert_imported_key(session_browser, 'https', 'mydomain.example')
def test_publish_ssh_keys(session_browser):
"""Test publishing SSH keys."""
_import_key(session_browser, 'ssh', 'mydomain.example')
_publish_key(session_browser, 'ssh', 'mydomain.example')
def test_publish_https_keys(session_browser):
"""Test publishing HTTPS keys."""
_import_key(session_browser, 'https', 'mydomain.example')
_publish_key(session_browser, 'https', 'mydomain.example')
def test_backup_restore(session_browser):
"""Test backup and restore of keys"""
_import_key(session_browser, 'ssh', 'mydomain.example')
_import_key(session_browser, 'https', 'mydomain.example')
functional.backup_create(session_browser, 'monkeysphere',
'test_monkeysphere')
functional.backup_restore(session_browser, 'monkeysphere',
'test_monkeysphere')
_assert_imported_key(session_browser, 'ssh', 'mydomain.example')
_assert_imported_key(session_browser, 'https', 'mydomain.example')
def _find_domain(browser, key_type, domain_type, domain):
"""Iterate every domain of a given type which given key type."""
keys_of_type = browser.find_by_css(
'.monkeysphere-service-{}'.format(key_type))
for key_of_type in keys_of_type:
search_domains = key_of_type.find_by_css(
'.monkeysphere-{}-domain'.format(domain_type))
for search_domain in search_domains:
if search_domain.text == domain:
return key_of_type, search_domain
raise IndexError('Domain not found')
def _import_key(browser, key_type, domain):
"""Import a key of specified type for given domain into monkeysphere."""
try:
_assert_imported_key(browser, key_type, domain)
except IndexError:
pass
else:
return
key, _ = _find_domain(browser, key_type, 'importable', domain)
with functional.wait_for_page_update(browser):
key.find_by_css('.button-import').click()
def _assert_imported_key(browser, key_type, domain):
"""Assert that a key of specified type for given domain was imported.."""
functional.nav_to_module(browser, 'monkeysphere')
return _find_domain(browser, key_type, 'imported', domain)
def _publish_key(browser, key_type, domain):
"""Publish a key of specified type for given domain from monkeysphere."""
functional.nav_to_module(browser, 'monkeysphere')
key, _ = _find_domain(browser, key_type, 'imported', domain)
with functional.wait_for_page_update(browser):
key.find_by_css('.button-publish').click()
functional.wait_for_config_update(browser, 'monkeysphere')

View File

@ -1,19 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
URLs for the monkeysphere module.
"""
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^sys/monkeysphere/$', views.index, name='index'),
re_path(r'^sys/monkeysphere/(?P<ssh_fingerprint>[0-9A-Za-z:+/]+)/import/$',
views.import_key, name='import'),
re_path(r'^sys/monkeysphere/(?P<fingerprint>[0-9A-Fa-f]+)/details/$',
views.details, name='details'),
re_path(r'^sys/monkeysphere/(?P<fingerprint>[0-9A-Fa-f]+)/publish/$',
views.publish, name='publish'),
re_path(r'^sys/monkeysphere/cancel/$', views.cancel, name='cancel'),
]

View File

@ -1,135 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Views for the monkeysphere module.
"""
import json
from django.contrib import messages
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.decorators.http import require_POST
from plinth import actions
from plinth.modules import monkeysphere, names
publish_process = None
def index(request):
"""Serve configuration page."""
_collect_publish_result(request)
status = get_status()
return TemplateResponse(
request, 'monkeysphere.html', {
'app_info': monkeysphere.app.info,
'title': monkeysphere.app.info.name,
'status': status,
'running': bool(publish_process),
'refresh_page_sec': 3 if bool(publish_process) else None,
})
@require_POST
def import_key(request, ssh_fingerprint):
"""Import a key into monkeysphere."""
keys = get_keys()
available_domains = keys[ssh_fingerprint]['available_domains']
try:
actions.superuser_run('monkeysphere',
['host-import-key', ssh_fingerprint] +
list(available_domains))
messages.success(request, _('Imported key.'))
except actions.ActionError as exception:
messages.error(request, str(exception))
return redirect(reverse_lazy('monkeysphere:index'))
def details(request, fingerprint):
"""Get details for an OpenPGP key."""
return TemplateResponse(request, 'monkeysphere_details.html', {
'title': monkeysphere.app.info.name,
'key': get_key(fingerprint)
})
@require_POST
def publish(request, fingerprint):
"""Publish OpenPGP key for SSH service."""
global publish_process
if not publish_process:
publish_process = actions.superuser_run(
'monkeysphere', ['host-publish-key', fingerprint],
run_in_background=True)
return redirect(reverse_lazy('monkeysphere:index'))
@require_POST
def cancel(request):
"""Cancel ongoing process."""
global publish_process
if publish_process:
actions.superuser_run(
'monkeysphere', ['host-cancel-publish',
str(publish_process.pid)])
publish_process = None
messages.info(request, _('Cancelled key publishing.'))
return redirect(reverse_lazy('monkeysphere:index'))
def get_status():
"""Get the current status."""
return {'keys': get_keys()}
def get_keys(fingerprint=None):
"""Get keys."""
fingerprint = [fingerprint] if fingerprint else []
output = actions.superuser_run('monkeysphere',
['host-show-keys'] + fingerprint)
keys = json.loads(output)['keys']
domains = names.components.DomainName.list_names()
for key in keys.values():
key['imported_domains'] = set(key.get('imported_domains', []))
key['available_domains'] = set(key.get('available_domains', []))
if '*' in key['available_domains']:
key['available_domains'] = set(domains)
key['all_domains'] = sorted(key['available_domains'].union(
key['imported_domains']))
key['importable_domains'] = key['available_domains'] \
.difference(key['imported_domains'])
return keys
def get_key(fingerprint):
"""Get key by fingerprint."""
keys = get_keys(fingerprint)
return list(keys.values())[0] if len(keys) else None
def _collect_publish_result(request):
"""Handle publish process completion."""
global publish_process
if not publish_process:
return
return_code = publish_process.poll()
# Publish process is not complete yet
if return_code is None:
return
if not return_code:
messages.success(request, _('Published key to keyserver.'))
else:
messages.error(request, _('Error occurred while publishing key.'))
publish_process = None

View File

@ -50,9 +50,9 @@ _site_url = {
_sys_modules = [
'avahi', 'backups', 'bind', 'cockpit', 'config', 'datetime', 'diagnostics',
'dynamicdns', 'firewall', 'letsencrypt', 'monkeysphere', 'names',
'networks', 'pagekite', 'performance', 'power', 'security', 'snapshot',
'ssh', 'storage', 'upgrades', 'users'
'dynamicdns', 'firewall', 'letsencrypt', 'names', 'networks', 'pagekite',
'performance', 'power', 'security', 'snapshot', 'ssh', 'storage',
'upgrades', 'users'
]

View File

@ -40,7 +40,6 @@ markers = [
"minetest",
"minidlna",
"mldonkey",
"monkeysphere",
"mumble",
"openvpn",
"pagekite",

View File

@ -39,6 +39,7 @@ DISABLED_APPS_TO_REMOVE = [
'apps',
'coquelicot',
'diaspora',
'monkeysphere',
'owncloud',
'system',
'xmpp',