mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-28 08:03:36 +00:00
config: Add user websites as choices for homepage config.
Closes: #1981 Closes also most of threads in !1952. Signed-off-by: Fioddor Superconcentrado <fioddor@gmail.com> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
f527d9af83
commit
337e8c28dd
@ -2,6 +2,7 @@
|
||||
"""
|
||||
FreedomBox app for Apache server.
|
||||
"""
|
||||
import os
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@ -11,7 +12,7 @@ from plinth import cfg
|
||||
from plinth.daemon import Daemon
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.modules.letsencrypt.components import LetsEncrypt
|
||||
from plinth.utils import format_lazy
|
||||
from plinth.utils import format_lazy, is_valid_user_name
|
||||
|
||||
version = 8
|
||||
|
||||
@ -19,8 +20,9 @@ is_essential = True
|
||||
|
||||
managed_services = ['apache2', 'uwsgi']
|
||||
|
||||
managed_packages = ['apache2', 'php-fpm', 'ssl-cert', 'uwsgi',
|
||||
'uwsgi-plugin-python3']
|
||||
managed_packages = [
|
||||
'apache2', 'php-fpm', 'ssl-cert', 'uwsgi', 'uwsgi-plugin-python3'
|
||||
]
|
||||
|
||||
app = None
|
||||
|
||||
@ -63,3 +65,81 @@ def setup(helper, old_version=None):
|
||||
actions.superuser_run(
|
||||
'apache',
|
||||
['setup', '--old-version', str(old_version)])
|
||||
|
||||
|
||||
# (U)ser (W)eb (S)ites
|
||||
|
||||
|
||||
def uws_usr2dir(user):
|
||||
"""Returns the directory of the given user's website."""
|
||||
return '/home/{}/public_html'.format(user)
|
||||
|
||||
|
||||
def uws_usr2url(user):
|
||||
"""Returns the url path of the given user's website."""
|
||||
return '/~{}/'.format(user)
|
||||
|
||||
|
||||
def uws_dir2usr(directory):
|
||||
"""Returns the user of a given user website directory."""
|
||||
if directory.startswith('/home/'):
|
||||
pos_ini = 6
|
||||
elif directory.startswith('home/'):
|
||||
pos_ini = 5
|
||||
else:
|
||||
return None
|
||||
|
||||
pos_end = directory.find('/public_html')
|
||||
if pos_end == -1:
|
||||
return None
|
||||
|
||||
user = directory[pos_ini:pos_end]
|
||||
return user if is_valid_user_name(user) else None
|
||||
|
||||
|
||||
def uws_url2usr(url):
|
||||
"""Returns the user of a given user website url path."""
|
||||
MISSING = -1
|
||||
|
||||
pos_ini = url.find('~')
|
||||
if pos_ini == MISSING:
|
||||
return None
|
||||
|
||||
pos_end = url.find('/', pos_ini)
|
||||
if pos_end == MISSING:
|
||||
pos_end = len(url)
|
||||
|
||||
user = url[pos_ini + 1:pos_end]
|
||||
return user if is_valid_user_name(user) else None
|
||||
|
||||
|
||||
def uws_url2dir(url):
|
||||
"""Returns the directory of the user's website for the given url path.
|
||||
|
||||
Note: It doesn't return the full OS file path to the url path!
|
||||
"""
|
||||
return uws_usr2dir(uws_url2usr(url))
|
||||
|
||||
|
||||
def uws_dir2url(directory):
|
||||
"""Returns the url base path of the user's website for the given OS path.
|
||||
|
||||
Note: It doesn't return the url path for the file!
|
||||
"""
|
||||
return uws_usr2url(uws_dir2usr(directory))
|
||||
|
||||
|
||||
def get_users_with_website():
|
||||
"""Returns a dictionary of users with actual website subdirectory."""
|
||||
|
||||
def lst_sub_dirs(directory):
|
||||
"""Returns the list of subdirectories of the given directory."""
|
||||
return [
|
||||
name for name in os.listdir(directory)
|
||||
if os.path.isdir(os.path.join(directory, name))
|
||||
]
|
||||
|
||||
return {
|
||||
name: uws_usr2url(name)
|
||||
for name in lst_sub_dirs('/home') if os.path.isdir(uws_usr2dir(name))
|
||||
}
|
||||
|
||||
@ -72,6 +72,7 @@ def test_webserver_disable(superuser_run):
|
||||
@patch('plinth.modules.apache.components.diagnose_url_on_all')
|
||||
def test_webserver_diagnose(diagnose_url_on_all, diagnose_url):
|
||||
"""Test running diagnostics."""
|
||||
|
||||
def on_all_side_effect(url, check_certificate):
|
||||
return [('test-result-' + url, 'success')]
|
||||
|
||||
|
||||
30
plinth/modules/apache/tests/test_uws.py
Normal file
30
plinth/modules/apache/tests/test_uws.py
Normal file
@ -0,0 +1,30 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Test module for (U)ser (Web) (S)ites.
|
||||
"""
|
||||
|
||||
from plinth.modules.apache import (uws_usr2dir, uws_dir2url, uws_url2usr,
|
||||
uws_usr2url, uws_url2dir, uws_dir2usr)
|
||||
|
||||
|
||||
def test_uws_namings():
|
||||
"""Test name solvers for user, url and directory of UWS."""
|
||||
|
||||
assert '/home/usr/public_html' == uws_usr2dir('usr')
|
||||
assert '/~usr/' == uws_usr2url('usr')
|
||||
|
||||
f = uws_dir2usr
|
||||
assert f('/home/usr/lacks/the/UWS/directory') is None
|
||||
assert 'usr' == f('/home/usr/public_html/is/a/normal/UWS/file')
|
||||
assert 'usr' == f('/home/usr/public_html/is/a/normal/UWS/path/')
|
||||
assert '€.;#@|' == f('/home/€.;#@|/public_html/is/stange/but/valid/')
|
||||
|
||||
f = uws_url2usr
|
||||
assert f('/usr/is/not/a/valid/UWS/url/due/to/missing/tilde') is None
|
||||
assert 'usr' == f('whatever/~usr/is/considered/a/valid/UWS/path')
|
||||
assert 'usr' == f('~usr')
|
||||
assert 'usr' == f('~usr/')
|
||||
assert 'usr' == f('/~usr/')
|
||||
|
||||
assert '/home/usr/public_html' == uws_url2dir('~usr/any/file')
|
||||
assert '/~usr/' == uws_dir2url('/home/usr/public_html/path/to/file')
|
||||
@ -12,6 +12,8 @@ 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 import (uws_url2usr, uws_usr2url,
|
||||
get_users_with_website)
|
||||
from plinth.modules.names.components import DomainType
|
||||
from plinth.signals import domain_added
|
||||
|
||||
@ -81,13 +83,56 @@ def get_hostname():
|
||||
return socket.gethostname()
|
||||
|
||||
|
||||
def _get_home_page_url():
|
||||
def home_page_url2scid(url):
|
||||
"""Returns the shortcut ID of the given home page url."""
|
||||
|
||||
if url in ('/plinth/', '/plinth', 'plinth'):
|
||||
return 'plinth'
|
||||
|
||||
if url == '/index.html':
|
||||
return 'apache-default'
|
||||
|
||||
if url and url.startswith('/~'):
|
||||
return 'uws-{}'.format(uws_url2usr(url))
|
||||
|
||||
shortcuts = frontpage.Shortcut.list()
|
||||
for shortcut in shortcuts:
|
||||
if shortcut.url == url:
|
||||
return shortcut.component_id
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _home_page_scid2url(shortcut_id):
|
||||
"""Returns the url for the given home page shortcut ID."""
|
||||
if shortcut_id is None:
|
||||
url = None
|
||||
elif shortcut_id == 'plinth':
|
||||
url = '/plinth/'
|
||||
elif shortcut_id == 'apache-default':
|
||||
url = '/index.html'
|
||||
elif shortcut_id.startswith('uws-'):
|
||||
user = shortcut_id[4:]
|
||||
if user in get_users_with_website():
|
||||
url = uws_usr2url(user)
|
||||
else:
|
||||
url = None
|
||||
else:
|
||||
shortcuts = frontpage.Shortcut.list()
|
||||
aux = [
|
||||
shortcut.url for shortcut in shortcuts
|
||||
if shortcut_id == shortcut.component_id
|
||||
]
|
||||
url = aux[0] if 1 == len(aux) else None
|
||||
|
||||
return url
|
||||
|
||||
|
||||
def _get_home_page_url(conf_file):
|
||||
"""Get the default application for the domain."""
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
|
||||
conf_file = APACHE_HOMEPAGE_CONFIG if os.path.exists(
|
||||
APACHE_HOMEPAGE_CONFIG) else FREEDOMBOX_APACHE_CONFIG
|
||||
aug.set('/augeas/load/Httpd/incl[last() + 1]', conf_file)
|
||||
aug.load()
|
||||
|
||||
@ -103,35 +148,20 @@ def _get_home_page_url():
|
||||
|
||||
def get_home_page():
|
||||
"""Return the shortcut ID that is set as current home page."""
|
||||
url = _get_home_page_url()
|
||||
if url in ['/plinth/', '/plinth', 'plinth']:
|
||||
return 'plinth'
|
||||
CONF_FILE = APACHE_HOMEPAGE_CONFIG if os.path.exists(
|
||||
APACHE_HOMEPAGE_CONFIG) else FREEDOMBOX_APACHE_CONFIG
|
||||
|
||||
if url == '/index.html':
|
||||
return 'apache-default'
|
||||
|
||||
shortcuts = frontpage.Shortcut.list()
|
||||
for shortcut in shortcuts:
|
||||
if shortcut.url == url:
|
||||
return shortcut.component_id
|
||||
|
||||
return None
|
||||
url = _get_home_page_url(CONF_FILE)
|
||||
return home_page_url2scid(url)
|
||||
|
||||
|
||||
def change_home_page(shortcut_id):
|
||||
"""Change the FreedomBox's default redirect to URL of the shortcut
|
||||
specified.
|
||||
"""
|
||||
if shortcut_id == 'plinth':
|
||||
url = '/plinth/'
|
||||
elif shortcut_id == 'apache-default':
|
||||
url = '/index.html'
|
||||
else:
|
||||
shortcuts = frontpage.Shortcut.list()
|
||||
url = [
|
||||
shortcut.url for shortcut in shortcuts
|
||||
if shortcut.component_id == shortcut_id
|
||||
][0]
|
||||
url = _home_page_scid2url(shortcut_id)
|
||||
if url is None:
|
||||
url = '/plinth/' # fall back to default url if scid is unknown.
|
||||
|
||||
# URL may be a reverse_lazy() proxy
|
||||
actions.superuser_run('config', ['set-home-page', str(url)])
|
||||
|
||||
@ -14,6 +14,9 @@ from django.utils.translation import ugettext_lazy
|
||||
|
||||
from plinth import cfg, frontpage
|
||||
from plinth.utils import format_lazy
|
||||
from plinth.modules.apache import get_users_with_website
|
||||
|
||||
from . import home_page_url2scid
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -32,9 +35,13 @@ def get_homepage_choices():
|
||||
shortcuts = frontpage.Shortcut.list(web_apps_only=True)
|
||||
shortcut_choices = [(shortcut.component_id, shortcut.name)
|
||||
for shortcut in shortcuts if shortcut.is_enabled()]
|
||||
uws_choices = \
|
||||
[(home_page_url2scid(url),
|
||||
format_lazy(ugettext_lazy("{user}'s website"), user=user))
|
||||
for user, url in get_users_with_website().items()]
|
||||
apache_default = ('apache-default', _('Apache Default'))
|
||||
plinth = ('plinth', _('FreedomBox Service (Plinth)'))
|
||||
return [apache_default, plinth] + shortcut_choices
|
||||
return [apache_default, plinth] + uws_choices + shortcut_choices
|
||||
|
||||
|
||||
class ConfigurationForm(forms.Form):
|
||||
|
||||
@ -3,11 +3,16 @@
|
||||
Tests for config module.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from plinth import __main__ as plinth_main
|
||||
from unittest.mock import (patch, MagicMock)
|
||||
|
||||
from ..forms import ConfigurationForm
|
||||
from plinth import __main__ as plinth_main
|
||||
from plinth.modules.apache import uws_usr2dir
|
||||
from plinth.modules.config import (home_page_url2scid, get_home_page,
|
||||
_home_page_scid2url, change_home_page)
|
||||
from plinth.modules.config.forms import ConfigurationForm
|
||||
|
||||
|
||||
def test_hostname_field():
|
||||
@ -64,6 +69,111 @@ def test_domainname_field():
|
||||
assert not form.is_valid()
|
||||
|
||||
|
||||
def test_homepage_mapping():
|
||||
"""Basic tests for homepage functions."""
|
||||
f = home_page_url2scid
|
||||
assert f(None) is None
|
||||
assert f('/unknown/url') is None
|
||||
assert 'plinth' == f('/plinth/')
|
||||
assert 'plinth' == f('/plinth')
|
||||
assert 'plinth' == f('plinth')
|
||||
assert 'apache-default' == f('/index.html')
|
||||
assert 'uws-user' == f('/~user')
|
||||
assert 'uws-user' == f('/~user/whatever/else')
|
||||
# assert 'config' == f('/plinth/apps/sharing/')
|
||||
|
||||
f = _home_page_scid2url
|
||||
assert f(None) is None
|
||||
assert '/plinth/' == f('plinth')
|
||||
assert '/index.html' == f('apache-default')
|
||||
|
||||
|
||||
def test_homepage_mapping_skip_ci():
|
||||
"""Special tests for homepage functions."""
|
||||
|
||||
try:
|
||||
UWS_DIRECTORY = uws_usr2dir(os.getlogin())
|
||||
except OSError:
|
||||
reason = "Needs access to ~/ directory. " \
|
||||
+ "CI sandboxed workspace doesn't provide it."
|
||||
pytest.skip(reason)
|
||||
|
||||
if os.path.exists(UWS_DIRECTORY):
|
||||
reason = "UWS dir {} exists already.".format(UWS_DIRECTORY)
|
||||
pytest.skip(reason)
|
||||
|
||||
f = _home_page_scid2url
|
||||
os.mkdir(UWS_DIRECTORY)
|
||||
assert '/~fbx/' == f('uws-fbx')
|
||||
os.rmdir(UWS_DIRECTORY)
|
||||
assert f('uws-fbx') is None
|
||||
|
||||
|
||||
class Dict2Obj(object):
|
||||
"""Mock object made out of any dict."""
|
||||
|
||||
def __init__(self, a_dict):
|
||||
self.__dict__ = a_dict
|
||||
|
||||
|
||||
@patch('plinth.frontpage.Shortcut.list', MagicMock(return_value=[
|
||||
Dict2Obj({'url': 'url/for/'+id, 'component_id': id})
|
||||
for id in ('a', 'b')
|
||||
]))
|
||||
def test_homepage_field():
|
||||
"""Test homepage changes.
|
||||
|
||||
Test Cases:
|
||||
1) FreedomBox Homepage (default),
|
||||
2) Apache default,
|
||||
3) A user's website of an...
|
||||
3.1) unexisting user
|
||||
3.2) existing user without a page
|
||||
3.3) existing user page.
|
||||
4) A FreedomBox App.
|
||||
4.1) unknown app
|
||||
4.2) uninstalled app
|
||||
4.3) disabled app
|
||||
4.4) enabled app
|
||||
|
||||
Note: If run on a pristine unconfigured FreedomBox, this test will leave
|
||||
the homepage default-configured. (Imperfect cleanup in such case).
|
||||
|
||||
Pending: Specific test cases to distiguish 4.1,2,3.
|
||||
Currently they share the same test case.
|
||||
"""
|
||||
try:
|
||||
UWS_DIRECTORY = uws_usr2dir(os.getlogin())
|
||||
except OSError:
|
||||
reason = "Needs access to ~/ directory, etc. " \
|
||||
+ "CI sandboxed workspace doesn't provide it."
|
||||
pytest.skip(reason)
|
||||
|
||||
DEFAULT_HOME_PAGE = 'plinth'
|
||||
ORIGINAL_HOME_PAGE = get_home_page() or DEFAULT_HOME_PAGE
|
||||
|
||||
if ORIGINAL_HOME_PAGE not in (DEFAULT_HOME_PAGE, None):
|
||||
reason = "Unexpected home page {}.".format(ORIGINAL_HOME_PAGE)
|
||||
pytest.skip(reason)
|
||||
|
||||
# invalid changes fall back to default:
|
||||
for scid in ('uws-unexisting', 'uws-fbx', 'missing_app'):
|
||||
change_home_page(scid)
|
||||
assert get_home_page() == DEFAULT_HOME_PAGE
|
||||
|
||||
os.mkdir(UWS_DIRECTORY)
|
||||
|
||||
# valid changes actually happen:
|
||||
for scid in ('b', 'a', 'uws-fbx', 'apache-default', 'plinth'):
|
||||
change_home_page(scid)
|
||||
assert get_home_page() == scid
|
||||
|
||||
# cleanup:
|
||||
change_home_page(ORIGINAL_HOME_PAGE)
|
||||
os.rmdir(UWS_DIRECTORY)
|
||||
assert get_home_page() == ORIGINAL_HOME_PAGE
|
||||
|
||||
|
||||
def test_locale_path():
|
||||
"""
|
||||
Test that the 'locale' directory is in the same folder as __main__.py.
|
||||
|
||||
@ -10,11 +10,26 @@ import pytest
|
||||
import ruamel.yaml
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from plinth.utils import YAMLFile, is_user_admin
|
||||
from plinth.utils import YAMLFile, is_user_admin, is_valid_user_name
|
||||
|
||||
|
||||
def test_is_valid_user_name():
|
||||
"""Test valid user names in Debian."""
|
||||
f = is_valid_user_name
|
||||
assert not f('this_user_name_is_too_long_to_be_valid')
|
||||
assert not f('-invalid')
|
||||
assert not f('not\tvalid')
|
||||
assert not f('not\nvalid')
|
||||
assert not f('not valid')
|
||||
assert not f('not:valid')
|
||||
assert not f('not/valid')
|
||||
assert not f('not\\valid')
|
||||
assert f('€.;#@|')
|
||||
|
||||
|
||||
class TestIsAdminUser:
|
||||
"""Test class for is_user_admin utility."""
|
||||
|
||||
@staticmethod
|
||||
@pytest.fixture(name='web_request')
|
||||
def fixture_web_request():
|
||||
|
||||
@ -58,6 +58,24 @@ def user_group_view(func, group_name):
|
||||
return func
|
||||
|
||||
|
||||
def is_valid_user_name(user_name):
|
||||
"""Check if the given username is valid.
|
||||
|
||||
Note: Debian is VERY flexible with user names.
|
||||
"""
|
||||
if 32 < len(user_name):
|
||||
return False
|
||||
|
||||
if user_name.startswith('-'):
|
||||
return False
|
||||
|
||||
for forbidden in (' \n\t/\\:'):
|
||||
if forbidden in user_name:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def is_user_admin(request, cached=False):
|
||||
"""Return whether user is an administrator."""
|
||||
if not request.user.is_authenticated:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user