Merge branch 'master' of salsa.debian.org:freedombox-team/plinth

This commit is contained in:
James Valleroy 2019-04-29 20:05:21 -04:00
commit cb43d5ad09
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
9 changed files with 423 additions and 52 deletions

View File

@ -23,7 +23,7 @@ import argparse
import os
from plinth import action_utils, cfg
from plinth.modules.i2p.helpers import TunnelEditor
from plinth.modules.i2p.helpers import RouterEditor, TunnelEditor
cfg.read()
module_config_path = os.path.join(cfg.config_dir, 'modules-enabled')
@ -43,6 +43,9 @@ def parse_arguments():
'add-favorite', help='Add an eepsite to the list of favorites')
subparser.add_argument('--name', help='Name of the entry', required=True)
subparser.add_argument('--url', help='URL of the entry', required=True)
subparser.add_argument('--description', help='Short description',
required=False)
subparser.add_argument('--icon', help='URL to icon', required=False)
subparser = subparsers.add_parser('set-tunnel-property',
help='Modify configuration of a tunnel')
@ -87,36 +90,11 @@ def subcommand_add_favorite(arguments):
:param arguments:
:type arguments:
"""
router_config_path = os.path.join(I2P_CONF_DIR, 'router.config')
# Read config
with open(router_config_path) as config_file:
config_lines = config_file.readlines()
found_favorites = False
url = arguments.url
new_favorite = '{name},{description},{url},{icon},'.format(
name=arguments.name, description='', url=arguments.url,
icon='/themes/console/images/eepsite.png')
for i in range(len(config_lines)):
line = config_lines[i]
# Find favorites line
if line.startswith('routerconsole.favorites'):
found_favorites = True
if url in line:
print('URL already in favorites')
exit(0)
# Append favorite
config_lines[i] = line.strip() + new_favorite + '\n'
break
if not found_favorites:
config_lines.append('routerconsole.favorites=' + new_favorite + '\n')
# Update config
with open(router_config_path, mode='w') as config_file:
config_file.writelines(config_lines)
editor = RouterEditor()
editor.read_conf().add_favorite(arguments.name, url, arguments.description,
arguments.icon).write_conf()
print('Added {} to favorites'.format(url))

View File

@ -23,6 +23,7 @@ from django.utils.translation import ugettext_lazy as _
from plinth import action_utils, actions, frontpage
from plinth import service as service_module
from plinth.menu import main_menu
from plinth.modules.i2p.resources import FAVORITES
from plinth.modules.users import register_group
from .manifest import backup, clients
@ -59,13 +60,6 @@ proxies_service = None
manual_page = 'I2P'
additional_favorites = [
('Searx instance', 'http://ransack.i2p'),
('Torrent tracker', 'http://tracker2.postman.i2p'),
('YaCy Legwork', 'http://legwork.i2p'),
('YaCy Seeker', 'http://seeker.i2p'),
]
tunnels_to_manage = {
'I2P HTTP Proxy': 'i2p-http-proxy-freedombox',
'I2P HTTPS Proxy': 'i2p-https-proxy-freedombox',
@ -101,14 +95,22 @@ def setup(helper, old_version=None):
helper.call('post', disable)
# Add favorites to the configuration
for fav_name, fav_url in additional_favorites:
helper.call('post', actions.superuser_run, 'i2p', [
for fav in FAVORITES:
args = [
'add-favorite',
'--name',
fav_name,
fav.get('name'),
'--url',
fav_url,
])
fav.get('url'),
]
if 'icon' in fav:
args.extend(['--icon', fav.get('icon')])
if 'description' in fav:
args.extend(['--description', fav.get('description')])
helper.call('post', actions.superuser_run, 'i2p', args)
# Tunnels to all interfaces
for tunnel in tunnels_to_manage:

View File

@ -19,12 +19,14 @@ Various helpers for the I2P app.
import os
import re
from collections import OrderedDict
import augeas
I2P_CONF_DIR = '/var/lib/i2p/i2p-config'
FILE_TUNNEL_CONF = os.path.join(I2P_CONF_DIR, 'i2ptunnel.config')
TUNNEL_IDX_REGEX = re.compile(r'tunnel.(\d+).name$')
I2P_ROUTER_CONF = os.path.join(I2P_CONF_DIR, 'router.config')
class TunnelEditor():
@ -133,3 +135,110 @@ class TunnelEditor():
def __setitem__(self, tunnel_prop, value):
self.aug.set(self.calc_prop_path(tunnel_prop), value)
class RouterEditor():
"""Helper to edit I2P router configuration file using augeas.
:type aug: augeas.Augeas
"""
FAVORITE_PROP = 'routerconsole.favorites'
FAVORITE_TUPLE_SIZE = 4
def __init__(self, filename=None):
self.conf_filename = filename or I2P_ROUTER_CONF
self.aug = None
def read_conf(self):
"""Load an instance of Augeaus for processing APT configuration.
Chainable method.
"""
self.aug = augeas.Augeas(
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
self.aug.set('/augeas/load/Properties/lens', 'Properties.lns')
self.aug.set('/augeas/load/Properties/incl[last() + 1]',
self.conf_filename)
self.aug.load()
return self
def write_conf(self):
"""Write changes to the configuration file to disk.
Chainable method.
"""
self.aug.save()
return self
@property
def favorite_property(self):
"""Return the favourites property from configuration file."""
return '/files{filename}/{prop}'.format(filename=self.conf_filename,
prop=self.FAVORITE_PROP)
def add_favorite(self, name, url, description=None, icon=None):
"""Add a favorite to the router configuration file.
Favorites are in a single string and separated by ','. none of the
incoming params can therefore use commas. I2P replaces the commas by
dots.
That's ok for the name and description, but not for the url and icon.
:type name: basestring
:type url: basestring
:type description: basestring
:type icon: basestring
"""
if not description:
description = ''
if not icon:
icon = '/themes/console/images/eepsite.png'
if ',' in url:
raise ValueError('URL cannot contain commas')
if ',' in icon:
raise ValueError('Icon cannot contain commas')
name = name.replace(',', '.')
description = description.replace(',', '.')
prop = self.favorite_property
favorites = self.aug.get(prop) or ''
new_favorite = '{name},{description},{url},{icon},'.format(
name=name, description=description, url=url, icon=icon)
self.aug.set(prop, favorites + new_favorite)
return self
def get_favorites(self):
"""Return list of favorites."""
favs_string = self.aug.get(self.favorite_property) or ''
favs_split = favs_string.split(',')
# There's a trailing comma --> 1 extra
favs_len = len(favs_split)
if favs_len > 0:
favs_split = favs_split[:-1]
favs_len = len(favs_split)
if favs_len % self.FAVORITE_TUPLE_SIZE:
raise SyntaxError("Invalid number of fields in favorite line")
favs = OrderedDict()
for index in range(0, favs_len, self.FAVORITE_TUPLE_SIZE):
next_index = index + self.FAVORITE_TUPLE_SIZE
name, description, url, icon = favs_split[index:next_index]
favs[url] = {
'name': name,
'description': description,
'icon': icon
}
return favs

View File

@ -14,6 +14,9 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Application manifest for I2P.
"""
from django.utils.translation import ugettext_lazy as _

View File

@ -0,0 +1,144 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Pre-defined list of favorites for I2P and some additional favorites.
"""
DEFAULT_FAVORITES = [
{
'name': 'anoncoin.i2p',
'description': 'The Anoncoin project',
'icon': '/themes/console/images/anoncoin_32.png',
'url': 'http://anoncoin.i2p/'
},
{
'name': 'Dev Builds',
'description': 'Development builds of I2P',
'icon': '/themes/console/images/script_gear.png',
'url': 'http://bobthebuilder.i2p/'
},
{
'name': 'Dev Forum',
'description': 'Development forum',
'icon': '/themes/console/images/group_gear.png',
'url': 'http://zzz.i2p/'
},
{
'name': 'echelon.i2p',
'description': 'I2P Applications',
'icon': '/themes/console/images/box_open.png',
'url': 'http://echelon.i2p/'
},
{
'name': 'exchanged.i2p',
'description': 'Anonymous cryptocurrency exchange',
'icon': '/themes/console/images/exchanged.png',
'url': 'http://exchanged.i2p/'
},
{
'name': 'I2P Bug Reports',
'description': 'Bug tracker',
'icon': '/themes/console/images/bug.png',
'url': 'http://trac.i2p2.i2p/report/1'
},
{
'name': 'I2P FAQ',
'description': 'Frequently Asked Questions',
'icon': '/themes/console/images/question.png',
'url': 'http://i2p-projekt.i2p/faq'
},
{
'name': 'I2P Forum',
'description': 'Community forum',
'icon': '/themes/console/images/group.png',
'url': 'http://i2pforum.i2p/'
},
{
'name': 'I2P Plugins',
'description': 'Add-on directory',
'icon': '/themes/console/images/info/plugin_link.png',
'url': 'http://i2pwiki.i2p/index.php?title=Plugins'
},
{
'name': 'I2P Technical Docs',
'description': 'Technical documentation',
'icon': '/themes/console/images/education.png',
'url': 'http://i2p-projekt.i2p/how'
},
{
'name': 'I2P Wiki',
'description': 'Anonymous wiki - share the knowledge',
'icon': '/themes/console/images/i2pwiki_logo.png',
'url': 'http://i2pwiki.i2p/'
},
{
'name': 'Planet I2P',
'description': 'I2P News',
'icon': '/themes/console/images/world.png',
'url': 'http://planet.i2p/'
},
{
'name': 'PrivateBin',
'description': 'Encrypted I2P Pastebin',
'icon': '/themes/console/images/paste_plain.png',
'url': 'http://paste.crypthost.i2p/'
},
{
'name': 'Project Website',
'description': 'I2P home page',
'icon': '/themes/console/images/info_rhombus.png',
'url': 'http://i2p-projekt.i2p/'
},
{
'name': 'stats.i2p',
'description': 'I2P Network Statistics',
'icon': '/themes/console/images/chart_line.png',
'url': 'http://stats.i2p/cgi-bin/dashboard.cgi'
},
{
'name': 'The Tin Hat',
'description': 'Privacy guides and tutorials',
'icon': '/themes/console/images/thetinhat.png',
'url': 'http://secure.thetinhat.i2p/'
},
{
'name': 'Trac Wiki',
'description': '',
'icon': '/themes/console/images/billiard_marker.png',
'url': 'http://trac.i2p2.i2p/'
}
]
ADDITIONAL_FAVORITES = [
{
'name': 'Searx instance',
'url': 'http://ransack.i2p'
},
{
'name': 'Torrent tracker',
'url': 'http://tracker2.postman.i2p'
},
{
'name': 'YaCy Legwork',
'url': 'http://legwork.i2p'
},
{
'name': 'YaCy Seeker',
'url': 'http://seeker.i2p'
},
]
FAVORITES = DEFAULT_FAVORITES + ADDITIONAL_FAVORITES

View File

@ -0,0 +1,23 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Common parts for all I2P tests.
"""
from pathlib import Path
DATA_DIR = Path(__file__).parent / 'data'

View File

@ -0,0 +1,23 @@
# NOTE: This I2P config file must use UTF-8 encoding
i2np.lastIPChange=1555091394049
i2np.ntcp2.iv=i3yLM2tPW6QH5h4YYZ8EWQ==
i2np.ntcp2.sp=j1K18jMDa5SPH23R2cHMJ-dUyPdo~uooZp6Uz06qP0k=
i2np.udp.internalPort=18778
i2np.udp.port=18778
jbigi.lastProcessor=Haswell Celeron/Pentium w/ AVX model 60/64
router.blocklistVersion=1523108115000
router.firstInstalled=1555091372597
router.firstVersion=0.9.38
router.inboundPool.randomKey=c1BGKzCBpxYfsH0AiEMIS39zYWzyBuWO9lYTqeA91dk=
router.outboundPool.randomKey=Of0jkHuGeUAHr~NoIAQbY930fbYacb4NyX3CjbVKKFI=
router.passwordManager.migrated=true
router.previousVersion=0.9.38
router.startup.jetty9.migrated=true
router.updateDisabled=true
router.updateLastInstalled=1555091372597
routerconsole.country=
routerconsole.favorites=anoncoin.i2p,The Anoncoin project,http://anoncoin.i2p/,/themes/console/images/anoncoin_32.png,Dev Builds,Development builds of I2P,http://bobthebuilder.i2p/,/themes/console/images/script_gear.png,Dev Forum,Development forum,http://zzz.i2p/,/themes/console/images/group_gear.png,echelon.i2p,I2P Applications,http://echelon.i2p/,/themes/console/images/box_open.png,exchanged.i2p,Anonymous cryptocurrency exchange,http://exchanged.i2p/,/themes/console/images/exchanged.png,I2P Bug Reports,Bug tracker,http://trac.i2p2.i2p/report/1,/themes/console/images/bug.png,I2P FAQ,Frequently Asked Questions,http://i2p-projekt.i2p/faq,/themes/console/images/question.png,I2P Forum,Community forum,http://i2pforum.i2p/,/themes/console/images/group.png,I2P Plugins,Add-on directory,http://i2pwiki.i2p/index.php?title=Plugins,/themes/console/images/info/plugin_link.png,I2P Technical Docs,Technical documentation,http://i2p-projekt.i2p/how,/themes/console/images/education.png,I2P Wiki,Anonymous wiki - share the knowledge,http://i2pwiki.i2p/,/themes/console/images/i2pwiki_logo.png,Planet I2P,I2P News,http://planet.i2p/,/themes/console/images/world.png,PrivateBin,Encrypted I2P Pastebin,http://paste.crypthost.i2p/,/themes/console/images/paste_plain.png,Project Website,I2P home page,http://i2p-projekt.i2p/,/themes/console/images/info_rhombus.png,stats.i2p,I2P Network Statistics,http://stats.i2p/cgi-bin/dashboard.cgi,/themes/console/images/chart_line.png,The Tin Hat,Privacy guides and tutorials,http://secure.thetinhat.i2p/,/themes/console/images/thetinhat.png,Trac Wiki,,http://trac.i2p2.i2p/,/themes/console/images/billiard_marker.png,
routerconsole.lang=en
routerconsole.newsLastChecked=1555093599462
routerconsole.newsLastUpdated=1553195540000
routerconsole.welcomeWizardComplete=true

View File

@ -1,17 +1,18 @@
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
# This file is part of FreedomBox.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Unit tests for helpers of I2P application.

View File

@ -0,0 +1,88 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Test I2P router configuration editing helper.
"""
import pytest
from plinth.modules.i2p.helpers import RouterEditor
from plinth.modules.i2p.tests import DATA_DIR
ROUTER_CONF_PATH = str(DATA_DIR / 'router.config')
@pytest.fixture
def editor():
"""Return editor instance object for each test."""
return RouterEditor(ROUTER_CONF_PATH)
def test_count_favorites(editor):
"""Test counting favorites."""
editor.read_conf()
favorites = editor.get_favorites()
assert len(favorites.keys()) == 17
def test_add_normal_favorite(editor):
"""Test adding a normal favorite."""
editor.read_conf()
name = 'Somewhere'
url = 'http://somewhere-again.i2p'
description = "Just somewhere else"
editor.add_favorite(name, url, description)
favorites = editor.get_favorites()
assert url in favorites
favorite = favorites[url]
assert favorite['name'] == name
assert favorite['description'] == description
assert len(favorites) == 18
def test_add_favorite_with_comma(editor):
"""Test adding a favorite with common in its name."""
editor.read_conf()
name = 'Name,with,comma'
expected_name = name.replace(',', '.')
url = 'http://url-without-comma.i2p'
description = "Another,comma,to,play,with"
expected_description = description.replace(',', '.')
editor.add_favorite(name, url, description)
favorites = editor.get_favorites()
assert url in favorites
favorite = favorites[url]
assert favorite['name'] == expected_name
assert favorite['description'] == expected_description
assert len(favorites) == 18
def test_add_fav_to_empty_config(editor):
"""Test adding favorite to empty configuration."""
editor.conf_filename = '/tmp/inexistent.conf'
editor.read_conf()
assert not editor.get_favorites()
name = 'Test Favorite'
url = 'http://test-fav.i2p'
editor.add_favorite(name, url)
assert len(editor.get_favorites()) == 1