mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-18 09:10:49 +00:00
Merge pull request #91 from SunilMohanAdapa/fonfon-dev
Additional fixes over merge request #89
This commit is contained in:
commit
b78df30c8b
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
current-*.tar.gz
|
||||
*.pyc
|
||||
*.py.bak
|
||||
*.swp
|
||||
*.tiny.css
|
||||
data/*.log
|
||||
data/cherrypy_sessions
|
||||
@ -28,4 +29,4 @@ data/plinth.sqlite3
|
||||
predepend
|
||||
build/
|
||||
*.pid
|
||||
.emacs.desktop*
|
||||
.emacs.desktop*
|
||||
|
||||
60
actions.py
60
actions.py
@ -32,7 +32,8 @@ Actions run commands with this contract (version 1.1):
|
||||
|
||||
C. Only one action can be called at a time.
|
||||
|
||||
This prevents us from appending multiple (unexpected) actions to the call.
|
||||
This prevents us from appending multiple (unexpected) actions to the
|
||||
call.
|
||||
|
||||
$ action="echo '$options'; echo 'oops'"
|
||||
$ options="hi"
|
||||
@ -51,8 +52,8 @@ Actions run commands with this contract (version 1.1):
|
||||
easier than detecting if it occurs.
|
||||
|
||||
The options list is coerced into a space-separated string before being
|
||||
shell-escaped. Option lists including shell escape characters may need to
|
||||
be unescaped on the receiving end.
|
||||
shell-escaped. Option lists including shell escape characters may need
|
||||
to be unescaped on the receiving end.
|
||||
|
||||
E. Actions must exist in the actions directory.
|
||||
|
||||
@ -72,11 +73,19 @@ Actions run commands with this contract (version 1.1):
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pipes, shlex, subprocess
|
||||
import pipes
|
||||
import subprocess
|
||||
|
||||
import cfg
|
||||
from errors import ActionError
|
||||
|
||||
|
||||
def run(action, options = None, async = False):
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def run(action, options=None, async=False):
|
||||
"""Safely run a specific action as the current user.
|
||||
|
||||
See actions._run for more information.
|
||||
@ -84,7 +93,8 @@ def run(action, options = None, async = False):
|
||||
"""
|
||||
return _run(action, options, async, False)
|
||||
|
||||
def superuser_run(action, options = None, async = False):
|
||||
|
||||
def superuser_run(action, options=None, async=False):
|
||||
"""Safely run a specific action as root.
|
||||
|
||||
See actions._run for more information.
|
||||
@ -92,27 +102,26 @@ def superuser_run(action, options = None, async = False):
|
||||
"""
|
||||
return _run(action, options, async, True)
|
||||
|
||||
def _run(action, options = None, async = False, run_as_root = False):
|
||||
|
||||
def _run(action, options=None, async=False, run_as_root=False):
|
||||
"""Safely run a specific action as a normal user or root.
|
||||
|
||||
actions are pulled from the actions directory.
|
||||
Actions are pulled from the actions directory.
|
||||
- options are added to the action command.
|
||||
- async: run asynchronously or wait for the command to complete.
|
||||
- run_as_root: execute the command through sudo.
|
||||
|
||||
options are added to the action command.
|
||||
|
||||
async: run asynchronously or wait for the command to complete.
|
||||
|
||||
run_as_root: execute the command through sudo.
|
||||
"""
|
||||
DIRECTORY = "actions"
|
||||
|
||||
if options == None:
|
||||
if options is None:
|
||||
options = []
|
||||
|
||||
# contract 3A and 3B: don't call anything outside of the actions directory.
|
||||
if os.sep in action:
|
||||
raise ValueError("Action can't contain:" + os.sep)
|
||||
|
||||
cmd = DIRECTORY + os.sep + action
|
||||
cmd = os.path.join(cfg.actions_dir, action)
|
||||
if not os.path.realpath(cmd).startswith(cfg.actions_dir):
|
||||
raise ValueError("Action has to be in directory %s" % cfg.actions_dir)
|
||||
|
||||
# contract 3C: interpret shell escape sequences as literal file names.
|
||||
# contract 3E: fail if the action doesn't exist or exists elsewhere.
|
||||
@ -121,25 +130,32 @@ def _run(action, options = None, async = False, run_as_root = False):
|
||||
|
||||
cmd = [cmd]
|
||||
|
||||
# contract: 3C, 3D: don't allow users to insert escape characters in options
|
||||
# contract: 3C, 3D: don't allow users to insert escape characters in
|
||||
# options
|
||||
if options:
|
||||
if not hasattr(options, "__iter__"):
|
||||
options = [options]
|
||||
|
||||
cmd += [pipes.quote(option) for option in options]
|
||||
|
||||
# contract 1: commands can run via sudo.
|
||||
if run_as_root:
|
||||
cmd = ["sudo", "-n"] + cmd
|
||||
|
||||
LOGGER.info('Executing command - %s', cmd)
|
||||
|
||||
# contract 3C: don't interpret shell escape sequences.
|
||||
# contract 5 (and 6-ish).
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout = subprocess.PIPE,
|
||||
stderr= subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False)
|
||||
|
||||
if not async:
|
||||
output, error = proc.communicate()
|
||||
return output, error
|
||||
if proc.returncode != 0:
|
||||
LOGGER.error('Error executing command - %s, %s, %s', cmd, output,
|
||||
error)
|
||||
raise ActionError(action, output, error)
|
||||
|
||||
return output
|
||||
|
||||
4
cfg.py
4
cfg.py
@ -12,6 +12,7 @@ python_root = None
|
||||
data_dir = None
|
||||
store_file = None
|
||||
user_db = None
|
||||
actions_dir = None
|
||||
status_log_file = None
|
||||
access_log_file = None
|
||||
pidfile = None
|
||||
@ -19,7 +20,7 @@ host = None
|
||||
port = None
|
||||
debug = False
|
||||
no_daemon = False
|
||||
server_dir = ''
|
||||
server_dir = '/'
|
||||
|
||||
main_menu = Menu()
|
||||
|
||||
@ -41,6 +42,7 @@ def read():
|
||||
('Path', 'data_dir'),
|
||||
('Path', 'store_file'),
|
||||
('Path', 'user_db'),
|
||||
('Path', 'actions_dir'),
|
||||
('Path', 'status_log_file'),
|
||||
('Path', 'access_log_file'),
|
||||
('Path', 'pidfile'),
|
||||
|
||||
34
context_processors.py
Normal file
34
context_processors.py
Normal file
@ -0,0 +1,34 @@
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Django context processors to provide common data to templates.
|
||||
"""
|
||||
|
||||
import re
|
||||
import cfg
|
||||
|
||||
|
||||
def common(request):
|
||||
"""Add additional context values to RequestContext for use in templates."""
|
||||
slash_indices = [match.start() for match in re.finditer('/', request.path)]
|
||||
active_menu_urls = [request.path[:index + 1] for index in slash_indices]
|
||||
return {
|
||||
'cfg': cfg,
|
||||
'submenu': cfg.main_menu.active_item(request),
|
||||
'active_menu_urls': active_menu_urls
|
||||
}
|
||||
@ -82,7 +82,7 @@ latex: $(LATEX)
|
||||
|
||||
|
||||
# This gets us the html sections complete with TOC, but without the
|
||||
# HTML and head section boilerplate. /help/view uses the parts.
|
||||
# HTML and head section boilerplate. /help/page uses the parts.
|
||||
%.part.html: %.html
|
||||
csplit -s -f $@ $< '%.*<body>%'
|
||||
sed '1d' $@00 > $@01
|
||||
|
||||
30
errors.py
Normal file
30
errors.py
Normal file
@ -0,0 +1,30 @@
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Project specific errors
|
||||
"""
|
||||
|
||||
|
||||
class PlinthError(Exception):
|
||||
"""Base class for all Plinth specific errors."""
|
||||
pass
|
||||
|
||||
|
||||
class ActionError(PlinthError):
|
||||
"""Use this error for exceptions when executing an action."""
|
||||
pass
|
||||
46
menu.py
46
menu.py
@ -1,5 +1,4 @@
|
||||
from urlparse import urlparse
|
||||
import cfg
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
|
||||
class Menu(object):
|
||||
@ -20,19 +19,20 @@ class Menu(object):
|
||||
orders, but feel free to disregard that. If you need more
|
||||
granularity, don't bother renumbering things. Feel free to
|
||||
use fractional orders.
|
||||
"""
|
||||
|
||||
"""
|
||||
self.label = label
|
||||
self.icon = icon
|
||||
self.url = url
|
||||
self.order = order
|
||||
# TODO: With an ordered dictionary for self.items we could access the
|
||||
# items by their URL directly instead of searching for them each time,
|
||||
# which we do currently with the 'get' method
|
||||
self.items = []
|
||||
|
||||
def find(self, url, basehref=True):
|
||||
"""Return a menu item with given URL"""
|
||||
if basehref and url.startswith('/'):
|
||||
url = cfg.server_dir + url
|
||||
|
||||
def get(self, urlname, url_args=None, url_kwargs=None):
|
||||
"""Return a menu item with given URL name."""
|
||||
url = reverse(urlname, args=url_args, kwargs=url_kwargs)
|
||||
for item in self.items:
|
||||
if item.url == url:
|
||||
return item
|
||||
@ -43,32 +43,28 @@ class Menu(object):
|
||||
"""Sort the items in self.items by order."""
|
||||
self.items = sorted(self.items, key=lambda x: x.order, reverse=False)
|
||||
|
||||
def add_item(self, label, icon, url, order=50, basehref=True):
|
||||
"""This method creates a menu item with the parameters, adds
|
||||
that menu item to this menu, and returns the item.
|
||||
def add_urlname(self, label, icon, urlname, order=50, url_args=None,
|
||||
url_kwargs=None):
|
||||
"""Add a named URL to the menu (via add_item).
|
||||
|
||||
If BASEHREF is true and url start with a slash, prepend the
|
||||
cfg.server_dir to it"""
|
||||
url_args and url_kwargs will be passed on to Django reverse().
|
||||
|
||||
if basehref and url.startswith("/"):
|
||||
url = cfg.server_dir + url
|
||||
"""
|
||||
url = reverse(urlname, args=url_args, kwargs=url_kwargs)
|
||||
return self.add_item(label, icon, url, order)
|
||||
|
||||
def add_item(self, label, icon, url, order=50):
|
||||
"""Create a new menu item with given parameters, add it to this menu and
|
||||
return it.
|
||||
|
||||
"""
|
||||
item = Menu(label=label, icon=icon, url=url, order=order)
|
||||
self.items.append(item)
|
||||
self.sort_items()
|
||||
return item
|
||||
|
||||
def is_active(self, request_path):
|
||||
"""
|
||||
Returns True if this menu item is active, otherwise False.
|
||||
|
||||
We can tell if a menu is active if the menu item points
|
||||
anywhere above url we are visiting in the url tree.
|
||||
"""
|
||||
return request_path.startswith(self.url)
|
||||
|
||||
def active_item(self, request):
|
||||
"""Return item list (e.g. submenu) of active menu item."""
|
||||
"""Return the first active item (e.g. submenu) that is found."""
|
||||
for item in self.items:
|
||||
if request.path.startswith(item.url):
|
||||
return item
|
||||
|
||||
@ -25,9 +25,12 @@ import logging
|
||||
import os
|
||||
|
||||
import urls
|
||||
import cfg
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
LOADED_MODULES = []
|
||||
|
||||
|
||||
def load_modules():
|
||||
"""
|
||||
@ -47,8 +50,10 @@ def load_modules():
|
||||
except Exception as exception:
|
||||
LOGGER.exception('Could not import modules/%s: %s',
|
||||
name, exception)
|
||||
if cfg.debug:
|
||||
raise
|
||||
|
||||
_include_module_urls(full_name)
|
||||
_include_module_urls(full_name, name)
|
||||
|
||||
ordered_modules = []
|
||||
remaining_modules = dict(modules)
|
||||
@ -68,6 +73,7 @@ def load_modules():
|
||||
|
||||
for module_name in ordered_modules:
|
||||
_initialize_module(modules[module_name])
|
||||
LOADED_MODULES.append(module_name)
|
||||
|
||||
|
||||
def _insert_modules(module_name, module, remaining_modules, ordered_modules):
|
||||
@ -97,15 +103,17 @@ def _insert_modules(module_name, module, remaining_modules, ordered_modules):
|
||||
ordered_modules.append(module_name)
|
||||
|
||||
|
||||
def _include_module_urls(module_name):
|
||||
def _include_module_urls(module_name, namespace):
|
||||
"""Include the module's URLs in global project URLs list"""
|
||||
url_module = module_name + '.urls'
|
||||
try:
|
||||
urls.urlpatterns += django.conf.urls.patterns(
|
||||
'', django.conf.urls.url(
|
||||
r'', django.conf.urls.include(url_module)))
|
||||
r'', django.conf.urls.include(url_module, namespace)))
|
||||
except ImportError:
|
||||
LOGGER.debug('No URLs for %s', module_name)
|
||||
if cfg.debug:
|
||||
raise
|
||||
|
||||
|
||||
def _initialize_module(module):
|
||||
@ -121,6 +129,8 @@ def _initialize_module(module):
|
||||
except Exception as exception:
|
||||
LOGGER.exception('Exception while running init for %s: %s',
|
||||
module, exception)
|
||||
if cfg.debug:
|
||||
raise
|
||||
|
||||
|
||||
def get_template_directories():
|
||||
|
||||
@ -6,7 +6,7 @@ import cfg
|
||||
|
||||
def init():
|
||||
"""Initailize the apps module"""
|
||||
cfg.main_menu.add_item("Apps", "icon-download-alt", "/apps", 80)
|
||||
cfg.main_menu.add_urlname("Apps", "icon-download-alt", "apps:index", 80)
|
||||
|
||||
|
||||
def index(request):
|
||||
|
||||
@ -24,5 +24,5 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.apps.apps',
|
||||
url(r'^apps/$', 'index')
|
||||
url(r'^apps/$', 'index', name='index')
|
||||
)
|
||||
|
||||
@ -95,8 +95,8 @@ and must not be greater than 63 characters in length.'),
|
||||
|
||||
def init():
|
||||
"""Initialize the module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item(_('Configure'), 'icon-cog', '/sys/config', 10)
|
||||
menu = cfg.main_menu.get('system:index')
|
||||
menu.add_urlname(_('Configure'), 'icon-cog', 'config:index', 10)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -133,20 +133,22 @@ def get_status():
|
||||
def _apply_changes(request, old_status, new_status):
|
||||
"""Apply the form changes"""
|
||||
if old_status['hostname'] != new_status['hostname']:
|
||||
if not set_hostname(new_status['hostname']):
|
||||
messages.error(request, _('Setting hostname failed'))
|
||||
try:
|
||||
set_hostname(new_status['hostname'])
|
||||
except Exception as exception:
|
||||
messages.error(request, _('Error setting hostname: %s') %
|
||||
str(exception))
|
||||
else:
|
||||
messages.success(request, _('Hostname set'))
|
||||
else:
|
||||
messages.info(request, _('Hostname is unchanged'))
|
||||
|
||||
if old_status['time_zone'] != new_status['time_zone']:
|
||||
output, error = actions.superuser_run('timezone-change',
|
||||
[new_status['time_zone']])
|
||||
del output # Unused
|
||||
if error:
|
||||
messages.error(request,
|
||||
_('Error setting time zone - %s') % error)
|
||||
try:
|
||||
actions.superuser_run('timezone-change', [new_status['time_zone']])
|
||||
except Exception as exception:
|
||||
messages.error(request, _('Error setting time zone: %s') %
|
||||
str(exception))
|
||||
else:
|
||||
messages.success(request, _('Time zone set'))
|
||||
else:
|
||||
@ -160,11 +162,6 @@ def set_hostname(hostname):
|
||||
hostname = str(hostname)
|
||||
|
||||
LOGGER.info('Changing hostname to - %s', hostname)
|
||||
try:
|
||||
actions.superuser_run('xmpp-pre-hostname-change')
|
||||
actions.superuser_run('hostname-change', hostname)
|
||||
actions.superuser_run('xmpp-hostname-change', hostname, async=True)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
return True
|
||||
actions.superuser_run('xmpp-pre-hostname-change')
|
||||
actions.superuser_run('hostname-change', hostname)
|
||||
actions.superuser_run('xmpp-hostname-change', hostname, async=True)
|
||||
|
||||
@ -24,5 +24,5 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.config.config',
|
||||
url(r'^sys/config/$', 'index'),
|
||||
url(r'^sys/config/$', 'index', name='index'),
|
||||
)
|
||||
|
||||
@ -25,12 +25,13 @@ from gettext import gettext as _
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
from errors import ActionError
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize the module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item("Diagnostics", "icon-screenshot", "/sys/diagnostics", 30)
|
||||
menu = cfg.main_menu.get('system:index')
|
||||
menu.add_urlname("Diagnostics", "icon-screenshot", "diagnostics:index", 30)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -43,7 +44,15 @@ def index(request):
|
||||
@login_required
|
||||
def test(request):
|
||||
"""Run diagnostics and the output page"""
|
||||
output, error = actions.superuser_run("diagnostic-test")
|
||||
output = ''
|
||||
error = ''
|
||||
try:
|
||||
output = actions.superuser_run("diagnostic-test")
|
||||
except ActionError as exception:
|
||||
output, error = exception.args[1:]
|
||||
except Exception as exception:
|
||||
error = str(exception)
|
||||
|
||||
return TemplateResponse(request, 'diagnostics_test.html',
|
||||
{'title': _('Diagnostic Test'),
|
||||
'diagnostics_output': output,
|
||||
|
||||
@ -24,8 +24,8 @@
|
||||
system to confirm that network services are running and configured
|
||||
properly. It may take a minute to complete.</p>
|
||||
|
||||
<p><a class="btn btn-primary btn-large"
|
||||
href="{{ basehref }}/sys/diagnostics/test">Run diagnostic test
|
||||
»</a></p>
|
||||
<p><a class="btn btn-primary btn-large" href="{% url 'diagnostics:test' %}">
|
||||
Run diagnostic test »
|
||||
</a></p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@ -24,6 +24,6 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.diagnostics.diagnostics',
|
||||
url(r'^sys/diagnostics/$', 'index'),
|
||||
url(r'^sys/diagnostics/test/$', 'test'),
|
||||
url(r'^sys/diagnostics/$', 'index', name='index'),
|
||||
url(r'^sys/diagnostics/test/$', 'test', name='test'),
|
||||
)
|
||||
|
||||
@ -16,8 +16,8 @@ class ExpertsForm(forms.Form): # pylint: disable-msg=W0232
|
||||
|
||||
def init():
|
||||
"""Initialize the module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item(_('Expert Mode'), 'icon-cog', '/sys/expert', 10)
|
||||
menu = cfg.main_menu.get('system:index')
|
||||
menu.add_urlname(_('Expert Mode'), 'icon-cog', 'expert_mode:index', 10)
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
@ -24,5 +24,5 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.expert_mode.expert_mode',
|
||||
url(r'^sys/expert/$', 'index'),
|
||||
url(r'^sys/expert/$', 'index', name='index'),
|
||||
)
|
||||
|
||||
@ -34,8 +34,8 @@ LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def init():
|
||||
"""Initailze firewall module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item(_('Firewall'), 'icon-flag', '/sys/firewall', 50)
|
||||
menu = cfg.main_menu.get('system:index')
|
||||
menu.add_urlname(_('Firewall'), 'icon-flag', 'firewall:index', 50)
|
||||
|
||||
service_module.ENABLED.connect(on_service_enabled)
|
||||
|
||||
@ -141,15 +141,7 @@ def _run(arguments, superuser=False):
|
||||
"""Run an given command and raise exception if there was an error"""
|
||||
command = 'firewall'
|
||||
|
||||
LOGGER.info('Running command - %s, %s, %s', command, arguments, superuser)
|
||||
|
||||
if superuser:
|
||||
output, error = actions.superuser_run(command, arguments)
|
||||
return actions.superuser_run(command, arguments)
|
||||
else:
|
||||
output, error = actions.run(command, arguments)
|
||||
|
||||
if error:
|
||||
raise Exception('Error setting/getting firewalld confguration - %s'
|
||||
% error)
|
||||
|
||||
return output
|
||||
return actions.run(command, arguments)
|
||||
|
||||
@ -24,5 +24,5 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.firewall.firewall',
|
||||
url(r'^sys/firewall/$', 'index')
|
||||
url(r'^sys/firewall/$', 'index', name='index')
|
||||
)
|
||||
|
||||
@ -21,6 +21,7 @@ The Plinth first-connection process has several stages:
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.core import validators
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
@ -94,7 +95,7 @@ def state0(request):
|
||||
"""
|
||||
try:
|
||||
if _read_state() >= 5:
|
||||
return HttpResponseRedirect(cfg.server_dir)
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@ -112,8 +113,7 @@ def state0(request):
|
||||
if success:
|
||||
# Everything is good, permanently mark and move to page 2
|
||||
_write_state(1)
|
||||
return HttpResponseRedirect(
|
||||
cfg.server_dir + '/firstboot/state1')
|
||||
return HttpResponseRedirect(reverse('first_boot:state1'))
|
||||
else:
|
||||
form = State0Form(initial=status, prefix='firstboot')
|
||||
|
||||
|
||||
55
modules/first_boot/middleware.py
Normal file
55
modules/first_boot/middleware.py
Normal file
@ -0,0 +1,55 @@
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Django middleware to redirect to firstboot wizard if it has not be run
|
||||
yet.
|
||||
"""
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http.response import HttpResponseRedirect
|
||||
import logging
|
||||
|
||||
import cfg
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FirstBootMiddleware(object):
|
||||
"""Forward to firstboot page if firstboot isn't finished yet."""
|
||||
|
||||
@staticmethod
|
||||
def process_request(request):
|
||||
"""Handle a request as Django middleware request handler."""
|
||||
# Prevent redirecting to first boot wizard in a loop by
|
||||
# checking if we are already in first boot wizard.
|
||||
if request.path.startswith(reverse('first_boot:index')):
|
||||
return
|
||||
|
||||
with sqlite_db(cfg.store_file, table='firstboot') as database:
|
||||
if 'state' not in database:
|
||||
# Permanent redirect causes the browser to cache the redirect,
|
||||
# preventing the user from navigating to /plinth until the
|
||||
# browser is restarted.
|
||||
return HttpResponseRedirect(reverse('first_boot:index'))
|
||||
|
||||
if database['state'] < 5:
|
||||
LOGGER.info('First boot state - %d', database['state'])
|
||||
return HttpResponseRedirect(reverse('first_boot:state%d' %
|
||||
database['state']))
|
||||
@ -23,7 +23,7 @@
|
||||
{% block main_block %}
|
||||
|
||||
<p>Welcome screen not completely implemented yet. Press <a
|
||||
href="{{basehref }}/apps">continue</a> to see the rest of the
|
||||
href="{% url 'apps:index' %}">continue</a> to see the rest of the
|
||||
web interface.</p>
|
||||
|
||||
<ul>
|
||||
|
||||
@ -24,7 +24,7 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.first_boot.first_boot',
|
||||
url(r'^firstboot/$', 'index'),
|
||||
url(r'^firstboot/state0/$', 'state0'),
|
||||
url(r'^firstboot/state1/$', 'state1')
|
||||
url(r'^firstboot/$', 'index', name='index'),
|
||||
url(r'^firstboot/state0/$', 'state0', name='state0'),
|
||||
url(r'^firstboot/state1/$', 'state1', name='state1')
|
||||
)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import os
|
||||
from gettext import gettext as _
|
||||
from django.http import Http404
|
||||
from django.template.response import TemplateResponse
|
||||
|
||||
import cfg
|
||||
@ -7,15 +8,17 @@ import cfg
|
||||
|
||||
def init():
|
||||
"""Initialize the Help module"""
|
||||
menu = cfg.main_menu.add_item(_('Documentation'), 'icon-book', '/help',
|
||||
101)
|
||||
menu.add_item(_("Where to Get Help"), "icon-search", "/help/index", 5)
|
||||
menu.add_item(_('Developer\'s Manual'), 'icon-info-sign',
|
||||
'/help/view/plinth', 10)
|
||||
menu.add_item(_('FAQ'), 'icon-question-sign', '/help/view/faq', 20)
|
||||
menu = cfg.main_menu.add_urlname(_('Documentation'), 'icon-book',
|
||||
'help:index', 101)
|
||||
menu.add_urlname(_('Where to Get Help'), 'icon-search',
|
||||
'help:index_explicit', 5)
|
||||
menu.add_urlname(_('Developer\'s Manual'), 'icon-info-sign',
|
||||
'help:helppage', 10, url_args=('plinth',))
|
||||
menu.add_urlname(_('FAQ'), 'icon-question-sign', 'help:helppage', 20,
|
||||
url_args=('faq',))
|
||||
menu.add_item(_('%s Wiki' % cfg.box_name), 'icon-pencil',
|
||||
'http://wiki.debian.org/FreedomBox', 30)
|
||||
menu.add_item(_('About'), 'icon-star', '/help/about', 100)
|
||||
menu.add_urlname(_('About'), 'icon-star', 'help:about', 100)
|
||||
|
||||
|
||||
def index(request):
|
||||
@ -30,10 +33,14 @@ def about(request):
|
||||
return TemplateResponse(request, 'about.html', {'title': title})
|
||||
|
||||
|
||||
def default(request, page=''):
|
||||
"""Serve the documentation pages"""
|
||||
with open(os.path.join('doc', '%s.part.html' % page), 'r') as input_file:
|
||||
main = input_file.read()
|
||||
def helppage(request, page):
|
||||
"""Serve a help page from the 'doc' directory"""
|
||||
try:
|
||||
with open(os.path.join('doc', '%s.part.html' % page), 'r') \
|
||||
as input_file:
|
||||
main = input_file.read()
|
||||
except IOError:
|
||||
raise Http404
|
||||
|
||||
title = _('%s Documentation') % cfg.product_name
|
||||
return TemplateResponse(request, 'base.html',
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of Plinth.
|
||||
@ -20,7 +21,7 @@
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<img src="{{ basehref }}/static/theme/img/freedombox-logo-250px.png"
|
||||
<img src="{% static 'theme/img/freedombox-logo-250px.png' %}"
|
||||
class="main-graphic" />
|
||||
|
||||
<p>We live in a world where our use of the network is mediated by
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
<p>There are a variety of places to go for help with
|
||||
{{ cfg.product_name }} and the box it runs on.</p>
|
||||
|
||||
<p>This front end has a <a href="{{ basehref }}/help/view/plinth">
|
||||
<p>This front end has a <a href="{% url 'help:helppage' 'plinth' %}">
|
||||
developer's manual</a>. It isn't complete, but it is the first place
|
||||
to look. Feel free to offer suggestions, edits, and screenshots for
|
||||
completing it!</p>
|
||||
@ -39,7 +39,7 @@ about the {{ cfg.box_name }} and almost surely know nothing of this
|
||||
front end, but they are an incredible resource for general Debian
|
||||
issues.</p>
|
||||
|
||||
<p>There is no <a href="{{ basehref }}/help/view/faq">FAQ</a> because
|
||||
<p>There is no <a href="{% url 'help:helppage' 'faq' %}">FAQ</a> because
|
||||
the question frequency is currently zero for all questions.</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@ -24,11 +24,11 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.help.help',
|
||||
url(r'^help/$', 'index'),
|
||||
url(r'^help/index/$', 'index'),
|
||||
url(r'^help/about/$', 'about'),
|
||||
url(r'^help/view/(?P<page>design)/$', 'default'),
|
||||
url(r'^help/view/(?P<page>plinth)/$', 'default'),
|
||||
url(r'^help/view/(?P<page>hacking)/$', 'default'),
|
||||
url(r'^help/view/(?P<page>faq)/$', 'default'),
|
||||
)
|
||||
# having two urls for one page is a hack to help the current url/menu
|
||||
# system highlight the correct menu item. Every submenu-item with the same
|
||||
# url prefix as the main-menu is highlighted automatically.
|
||||
url(r'^help/$', 'index', name='index'),
|
||||
url(r'^help/index/$', 'index', name='index_explicit'),
|
||||
url(r'^help/about/$', 'about', name='about'),
|
||||
url(r'^help/page/(plinth|hacking|faq)/$', 'helppage', name='helppage'),
|
||||
)
|
||||
|
||||
@ -20,14 +20,13 @@ URLs for the Lib module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
import cfg
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'',
|
||||
url(r'^accounts/login/$', 'django.contrib.auth.views.login',
|
||||
{'template_name': 'login.html'}),
|
||||
{'template_name': 'login.html'}, name='login'),
|
||||
url(r'^accounts/logout/$', 'django.contrib.auth.views.logout',
|
||||
{'next_page': cfg.server_dir})
|
||||
{'next_page': reverse_lazy('index')}, name='logout')
|
||||
)
|
||||
|
||||
@ -19,8 +19,8 @@ class OwnCloudForm(forms.Form): # pylint: disable-msg=W0232
|
||||
|
||||
def init():
|
||||
"""Initialize the ownCloud module"""
|
||||
menu = cfg.main_menu.find('/apps')
|
||||
menu.add_item('Owncloud', 'icon-picture', '/apps/owncloud', 35)
|
||||
menu = cfg.main_menu.get('apps:index')
|
||||
menu.add_urlname('Owncloud', 'icon-picture', 'owncloud:index', 35)
|
||||
|
||||
status = get_status()
|
||||
|
||||
@ -53,10 +53,7 @@ def index(request):
|
||||
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
output, error = actions.run('owncloud-setup', 'status')
|
||||
if error:
|
||||
raise Exception('Error getting ownCloud status: %s' % error)
|
||||
|
||||
output = actions.run('owncloud-setup', 'status')
|
||||
return {'enabled': 'enable' in output.split()}
|
||||
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
{{ form|bootstrap }}
|
||||
|
||||
<p>When enabled, the owncloud installation will be available
|
||||
from <a href="/owncloud">owncloud</a> on the web server. Visit
|
||||
from <a href="/owncloud">owncloud</a> on the web server. Visit
|
||||
this URL to set up the initial administration account for
|
||||
owncloud.</p>
|
||||
|
||||
|
||||
@ -24,5 +24,5 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.owncloud.owncloud',
|
||||
url(r'^apps/owncloud/$', 'index'),
|
||||
url(r'^apps/owncloud/$', 'index', name='index'),
|
||||
)
|
||||
|
||||
@ -10,19 +10,13 @@ import cfg
|
||||
|
||||
def get_modules_available():
|
||||
"""Return list of all modules"""
|
||||
output, error = actions.run('module-manager', ['list-available'])
|
||||
if error:
|
||||
raise Exception('Error getting modules: %s' % error)
|
||||
|
||||
output = actions.run('module-manager', ['list-available'])
|
||||
return output.split()
|
||||
|
||||
|
||||
def get_modules_enabled():
|
||||
"""Return list of all modules"""
|
||||
output, error = actions.run('module-manager', ['list-enabled'])
|
||||
if error:
|
||||
raise Exception('Error getting enabled modules - %s' % error)
|
||||
|
||||
output = actions.run('module-manager', ['list-enabled'])
|
||||
return output.split()
|
||||
|
||||
|
||||
@ -42,8 +36,8 @@ class PackagesForm(forms.Form):
|
||||
|
||||
def init():
|
||||
"""Initialize the Packages module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item('Package Manager', 'icon-gift', '/sys/packages', 20)
|
||||
menu = cfg.main_menu.get('system:index')
|
||||
menu.add_urlname('Package Manager', 'icon-gift', 'packages:index', 20)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -88,13 +82,12 @@ def _apply_changes(request, old_status, new_status):
|
||||
|
||||
module = field.split('_enabled')[0]
|
||||
if enabled:
|
||||
output, error = actions.superuser_run(
|
||||
'module-manager', ['enable', cfg.python_root, module])
|
||||
del output # Unused
|
||||
|
||||
# TODO: need to get plinth to load the module we just
|
||||
# enabled
|
||||
if error:
|
||||
try:
|
||||
actions.superuser_run(
|
||||
'module-manager', ['enable', cfg.python_root, module])
|
||||
except Exception:
|
||||
# TODO: need to get plinth to load the module we just
|
||||
# enabled
|
||||
messages.error(
|
||||
request, _('Error enabling module - {module}').format(
|
||||
module=module))
|
||||
@ -103,13 +96,12 @@ def _apply_changes(request, old_status, new_status):
|
||||
request, _('Module enabled - {module}').format(
|
||||
module=module))
|
||||
else:
|
||||
output, error = actions.superuser_run(
|
||||
'module-manager', ['disable', cfg.python_root, module])
|
||||
del output # Unused
|
||||
|
||||
# TODO: need a smoother way for plinth to unload the
|
||||
# module
|
||||
if error:
|
||||
try:
|
||||
actions.superuser_run(
|
||||
'module-manager', ['disable', cfg.python_root, module])
|
||||
except Exception:
|
||||
# TODO: need a smoother way for plinth to unload the
|
||||
# module
|
||||
messages.error(
|
||||
request, _('Error disabling module - {module}').format(
|
||||
module=module))
|
||||
|
||||
@ -24,5 +24,5 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.packages.packages',
|
||||
url(r'^sys/packages/$', 'index'),
|
||||
url(r'^sys/packages/$', 'index', name='index'),
|
||||
)
|
||||
|
||||
@ -23,6 +23,7 @@ from django import forms
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core import validators
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.response import TemplateResponse
|
||||
@ -38,16 +39,16 @@ LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def init():
|
||||
"""Intialize the PageKite module"""
|
||||
menu = cfg.main_menu.find('/apps')
|
||||
menu.add_item(_('Public Visibility (PageKite)'), 'icon-flag',
|
||||
'/apps/pagekite', 50)
|
||||
menu = cfg.main_menu.get('apps:index')
|
||||
menu.add_urlname(_('Public Visibility (PageKite)'), 'icon-flag',
|
||||
'pagekite:index', 50)
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve introdution page"""
|
||||
menu = {'title': _('PageKite'),
|
||||
'items': [{'url': '/apps/pagekite/configure',
|
||||
'items': [{'url': reverse_lazy('pagekite:configure'),
|
||||
'text': _('Configure PageKite')}]}
|
||||
|
||||
sidebar_right = render_to_string('menu_block.html', {'menu': menu},
|
||||
@ -197,15 +198,7 @@ def _run(arguments, superuser=True):
|
||||
"""Run an given command and raise exception if there was an error"""
|
||||
command = 'pagekite-configure'
|
||||
|
||||
LOGGER.info('Running command - %s, %s, %s', command, arguments, superuser)
|
||||
|
||||
if superuser:
|
||||
output, error = actions.superuser_run(command, arguments)
|
||||
return actions.superuser_run(command, arguments)
|
||||
else:
|
||||
output, error = actions.run(command, arguments)
|
||||
|
||||
if error:
|
||||
raise Exception('Error setting/getting PageKite confguration - %s'
|
||||
% error)
|
||||
|
||||
return output
|
||||
return actions.run(command, arguments)
|
||||
|
||||
@ -49,7 +49,7 @@ there. In future, it might be possible to use your buddy's
|
||||
|
||||
<p>
|
||||
<a class='btn btn-primary btn-lg'
|
||||
href="{{ basehref }}/apps/pagekite/configure">Configure
|
||||
href="{% url 'pagekite:configure' %}">Configure
|
||||
PageKite</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -24,6 +24,6 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.pagekite.pagekite',
|
||||
url(r'^apps/pagekite/$', 'index'),
|
||||
url(r'^apps/pagekite/configure/$', 'configure'),
|
||||
url(r'^apps/pagekite/$', 'index', name='index'),
|
||||
url(r'^apps/pagekite/configure/$', 'configure', name='configure'),
|
||||
)
|
||||
|
||||
@ -6,7 +6,7 @@ import cfg
|
||||
|
||||
def init():
|
||||
"""Initialize the system module"""
|
||||
cfg.main_menu.add_item(_('System'), 'icon-cog', '/sys', 100)
|
||||
cfg.main_menu.add_urlname(_('System'), 'icon-cog', 'system:index', 100)
|
||||
|
||||
|
||||
def index(request):
|
||||
|
||||
@ -24,5 +24,5 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.system.system',
|
||||
url(r'^sys/$', 'index'),
|
||||
url(r'^sys/$', 'index', name='index'),
|
||||
)
|
||||
|
||||
@ -29,15 +29,14 @@ import cfg
|
||||
|
||||
def init():
|
||||
"""Initialize the Tor module"""
|
||||
menu = cfg.main_menu.find('/apps')
|
||||
menu.add_item("Tor", "icon-eye-close", "/apps/tor", 30)
|
||||
menu = cfg.main_menu.get('apps:index')
|
||||
menu.add_urlname("Tor", "icon-eye-close", "tor:index", 30)
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Service the index page"""
|
||||
output, error = actions.superuser_run("tor-get-ports")
|
||||
del error # Unused
|
||||
output = actions.superuser_run("tor-get-ports")
|
||||
|
||||
port_info = output.split("\n")
|
||||
tor_ports = {}
|
||||
|
||||
@ -24,5 +24,5 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.tor.tor',
|
||||
url(r'^apps/tor/$', 'index')
|
||||
url(r'^apps/tor/$', 'index', name='index')
|
||||
)
|
||||
|
||||
@ -24,7 +24,7 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.users.users',
|
||||
url(r'^sys/users/$', 'index'),
|
||||
url(r'^sys/users/add/$', 'add'),
|
||||
url(r'^sys/users/edit/$', 'edit')
|
||||
url(r'^sys/users/$', 'index', name='index'),
|
||||
url(r'^sys/users/add/$', 'add', name='add'),
|
||||
url(r'^sys/users/edit/$', 'edit', name='edit')
|
||||
)
|
||||
|
||||
@ -3,6 +3,7 @@ from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import validators
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.response import TemplateResponse
|
||||
@ -18,17 +19,17 @@ LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def init():
|
||||
"""Intialize the module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item(_('Users and Groups'), 'icon-user', '/sys/users', 15)
|
||||
menu = cfg.main_menu.get('system:index')
|
||||
menu.add_urlname(_('Users and Groups'), 'icon-user', 'users:index', 15)
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Return a rendered users page"""
|
||||
menu = {'title': _('Users and Groups'),
|
||||
'items': [{'url': '/sys/users/add',
|
||||
'items': [{'url': reverse_lazy('users:add'),
|
||||
'text': _('Add User')},
|
||||
{'url': '/sys/users/edit',
|
||||
{'url': reverse_lazy('users:edit'),
|
||||
'text': _('Edit Users')}]}
|
||||
|
||||
sidebar_right = render_to_string('menu_block.html', {'menu': menu},
|
||||
|
||||
@ -24,7 +24,7 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.xmpp.xmpp',
|
||||
url(r'^apps/xmpp/$', 'index'),
|
||||
url(r'^apps/xmpp/configure/$', 'configure'),
|
||||
url(r'^apps/xmpp/register/$', 'register')
|
||||
url(r'^apps/xmpp/$', 'index', name='index'),
|
||||
url(r'^apps/xmpp/configure/$', 'configure', name='configure'),
|
||||
url(r'^apps/xmpp/register/$', 'register', name='register')
|
||||
)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.response import TemplateResponse
|
||||
@ -14,18 +15,26 @@ import service
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SIDE_MENU = {'title': _('XMPP'),
|
||||
'items': [{'url': '/apps/xmpp/configure',
|
||||
'text': 'Configure XMPP Server'},
|
||||
{'url': '/apps/xmpp/register',
|
||||
'text': 'Register XMPP Account'}]}
|
||||
SIDE_MENU = {
|
||||
'title': _('XMPP'),
|
||||
'items': [
|
||||
{
|
||||
'url': reverse_lazy('xmpp:configure'),
|
||||
'text': _('Configure XMPP Server'),
|
||||
},
|
||||
{
|
||||
'url': reverse_lazy('xmpp:register'),
|
||||
'text': _('Register XMPP Account'),
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize the XMPP module"""
|
||||
menu = cfg.main_menu.find('/apps')
|
||||
menu = cfg.main_menu.get('apps:index')
|
||||
menu.add_item('Chat', 'icon-comment', '/../jwchat', 20)
|
||||
menu.add_item('XMPP', 'icon-comment', '/apps/xmpp', 40)
|
||||
menu.add_urlname('XMPP', 'icon-comment', 'xmpp:index', 40)
|
||||
|
||||
service.Service(
|
||||
'xmpp-client', _('Chat Server - client connections'),
|
||||
@ -88,10 +97,7 @@ def configure(request):
|
||||
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
output, error = actions.run('xmpp-setup', 'status')
|
||||
if error:
|
||||
raise Exception('Error getting status: %s' % error)
|
||||
|
||||
output = actions.run('xmpp-setup', 'status')
|
||||
return {'inband_enabled': 'inband_enable' in output.split()}
|
||||
|
||||
|
||||
@ -111,11 +117,7 @@ def _apply_changes(request, old_status, new_status):
|
||||
option = 'noinband_enable'
|
||||
|
||||
LOGGER.info('Option - %s', option)
|
||||
|
||||
_output, error = actions.superuser_run('xmpp-setup', [option])
|
||||
del _output # Unused
|
||||
if error:
|
||||
raise Exception('Error running command - %s' % error)
|
||||
actions.superuser_run('xmpp-setup', [option])
|
||||
|
||||
|
||||
class RegisterForm(forms.Form): # pylint: disable-msg=W0232
|
||||
@ -151,10 +153,8 @@ def register(request):
|
||||
|
||||
def _register_user(request, data):
|
||||
"""Register a new XMPP user"""
|
||||
output, error = actions.superuser_run(
|
||||
output = actions.superuser_run(
|
||||
'xmpp-register', [data['username'], data['password']])
|
||||
if error:
|
||||
raise Exception('Error registering user - %s' % error)
|
||||
|
||||
if 'successfully registered' in output:
|
||||
messages.success(request, _('Registered account for %s') %
|
||||
|
||||
56
plinth.py
56
plinth.py
@ -35,6 +35,7 @@ def parse_arguments():
|
||||
parser.add_argument(
|
||||
'--pidfile', default='plinth.pid',
|
||||
help='specify a file in which the server may write its pid')
|
||||
# TODO: server_dir is actually a url prefix; use a better variable name
|
||||
parser.add_argument(
|
||||
'--server_dir', default='/',
|
||||
help='web server path under which to serve')
|
||||
@ -101,7 +102,22 @@ def setup_server():
|
||||
'/': {'tools.staticdir.root': '%s/static' % cfg.file_root,
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': '.'}}
|
||||
cherrypy.tree.mount(None, cfg.server_dir + '/static', config)
|
||||
cherrypy.tree.mount(None, django.conf.settings.STATIC_URL, config)
|
||||
|
||||
# TODO: our modules are mimicking django apps. It'd be better to convert
|
||||
# our modules to Django apps instead of reinventing the wheel.
|
||||
# (we'll still have to serve the static files with cherrypy though)
|
||||
for module in module_loader.LOADED_MODULES:
|
||||
static_dir = os.path.join(cfg.file_root, 'modules', module, 'static')
|
||||
if not os.path.isdir(static_dir):
|
||||
continue
|
||||
|
||||
config = {
|
||||
'/': {'tools.staticdir.root': static_dir,
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': '.'}}
|
||||
urlprefix = "%s%s" % (django.conf.settings.STATIC_URL, module)
|
||||
cherrypy.tree.mount(None, urlprefix, config)
|
||||
|
||||
if not cfg.no_daemon:
|
||||
Daemonizer(cherrypy.engine).subscribe()
|
||||
@ -109,23 +125,14 @@ def setup_server():
|
||||
cherrypy.engine.signal_handler.subscribe()
|
||||
|
||||
|
||||
def context_processor(request):
|
||||
"""Add additional context values to RequestContext for use in templates"""
|
||||
path_parts = request.path.split('/')
|
||||
active_menu_urls = ['/'.join(path_parts[:length])
|
||||
for length in xrange(1, len(path_parts))]
|
||||
return {
|
||||
'cfg': cfg,
|
||||
'main_menu': cfg.main_menu,
|
||||
'submenu': cfg.main_menu.active_item(request),
|
||||
'request_path': request.path,
|
||||
'basehref': cfg.server_dir,
|
||||
'active_menu_urls': active_menu_urls
|
||||
}
|
||||
|
||||
|
||||
def configure_django():
|
||||
"""Setup Django configuration in the absense of .settings file"""
|
||||
|
||||
# In module_loader.py we reverse URLs for the menu before having a proper
|
||||
# request. In this case, get_script_prefix (used by reverse) returns the
|
||||
# wrong prefix. Set it here manually to have the correct prefix right away.
|
||||
django.core.urlresolvers.set_script_prefix(cfg.server_dir)
|
||||
|
||||
context_processors = [
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.core.context_processors.debug',
|
||||
@ -134,7 +141,7 @@ def configure_django():
|
||||
'django.core.context_processors.static',
|
||||
'django.core.context_processors.tz',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'plinth.context_processor']
|
||||
'context_processors.common']
|
||||
|
||||
logging_configuration = {
|
||||
'version': 1,
|
||||
@ -179,9 +186,18 @@ def configure_django():
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.messages'],
|
||||
LOGGING=logging_configuration,
|
||||
LOGIN_URL=cfg.server_dir + '/accounts/login/',
|
||||
LOGIN_REDIRECT_URL=cfg.server_dir + '/',
|
||||
LOGOUT_URL=cfg.server_dir + '/accounts/logout/',
|
||||
LOGIN_URL='lib:login',
|
||||
LOGIN_REDIRECT_URL='apps:index',
|
||||
LOGOUT_URL='lib:logout',
|
||||
MIDDLEWARE_CLASSES=(
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'modules.first_boot.middleware.FirstBootMiddleware',
|
||||
),
|
||||
ROOT_URLCONF='urls',
|
||||
SESSION_ENGINE='django.contrib.sessions.backends.file',
|
||||
SESSION_FILE_PATH=sessions_directory,
|
||||
|
||||
@ -10,6 +10,7 @@ log_dir = %(data_dir)s
|
||||
pid_dir = %(data_dir)s
|
||||
python_root = %(file_root)s
|
||||
server_dir = plinth/
|
||||
actions_dir = %(file_root)s/actions
|
||||
|
||||
# file locations
|
||||
store_file = %(data_dir)s/store.sqlite3
|
||||
|
||||
@ -47,6 +47,8 @@
|
||||
<!-- CSS from previous Plinth template, not sure what to keep yet -->
|
||||
{{ css|safe }}
|
||||
<!-- End Plinth CSS -->
|
||||
{% block app_head %}<!-- placeholder for app/module-specific head files -->{% endblock %}
|
||||
{% block page_head %}<!-- placeholder for page-specific head files -->{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<!--[if lt IE 7]><p class=chromeframe>Your browser is <em>ancient!</em> <a href="http://mozilla.org/firefox">Upgrade to a modern version of Firefox</a> to experience this site.</p><![endif]-->
|
||||
@ -59,15 +61,15 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</a>
|
||||
<a href="{{ basehref }}/" class="logo-top">
|
||||
<a href="{% url 'index' %}" class="logo-top">
|
||||
<img src="{% static 'theme/img/freedombox-logo-32px.png' %}"
|
||||
alt="FreedomBox" />
|
||||
</a>
|
||||
<a class="brand" href="{{ basehref }}/">FreedomBox</a>
|
||||
<a class="brand" href="{% url 'index' %}">FreedomBox</a>
|
||||
{% block add_nav_and_login %}
|
||||
<div class="nav-collapse">
|
||||
<ul class="nav">
|
||||
{% for item in main_menu.items %}
|
||||
{% for item in cfg.main_menu.items %}
|
||||
{% if item.url in active_menu_urls %}
|
||||
<li class="active">
|
||||
<a href="{{ item.url }}" class="active">
|
||||
@ -85,15 +87,15 @@
|
||||
{% if user.is_authenticated %}
|
||||
<p class="navbar-text pull-right">
|
||||
<i class="icon-user icon-white nav-icon"></i>
|
||||
Logged in as <a href="{{ user.username }}">{{ user.username }}</a>.
|
||||
<a href="{{ basehref }}/accounts/logout" title="Log out">
|
||||
Logged in as <a href="#">{{ user.username }}</a>.
|
||||
<a href="{% url 'lib:logout' %}" title="Log out">
|
||||
Log out</a>.
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="navbar-text pull-right">
|
||||
Not logged in.
|
||||
<i class="icon-user icon-white nav-icon"></i>
|
||||
<a href="{{ basehref }}/accounts/login" title="Log in">
|
||||
<a href="{% url 'lib:login' %}" title="Log in">
|
||||
Log in</a>.
|
||||
</p>
|
||||
{% endif %}
|
||||
@ -169,5 +171,7 @@
|
||||
{% block js_block %}
|
||||
{{ js|safe }}
|
||||
{% endblock %}
|
||||
{% block app_js %}<!-- placeholder for app-specific js files -->{% endblock %}
|
||||
{% block page_js %}<!-- placeholder for page-specific js files -->{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<li class='nav-header'>{{ menu.title }}</li>
|
||||
{% for item in menu.items %}
|
||||
<li>
|
||||
<a href="{{ basehref }}{{ item.url }}">{{ item.text }}</a>
|
||||
<a href="{{ item.url }}">{{ item.text }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@ -7,6 +7,11 @@ import shlex
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
import cfg
|
||||
|
||||
ROOT_DIR = os.path.split(os.path.abspath(os.path.split(__file__)[0]))[0]
|
||||
cfg.actions_dir = os.path.join(ROOT_DIR, 'actions')
|
||||
|
||||
class TestPrivileged(unittest.TestCase):
|
||||
"""Verify that privileged actions perform as expected.
|
||||
|
||||
@ -25,10 +30,9 @@ class TestPrivileged(unittest.TestCase):
|
||||
os.remove("actions/echo")
|
||||
os.remove("actions/id")
|
||||
|
||||
def test_run_as_root(self):
|
||||
"""1. Privileged actions run as root.
|
||||
|
||||
"""
|
||||
def notest_run_as_root(self):
|
||||
"""1. Privileged actions run as root. """
|
||||
# TODO: it's not allowed to call a symlink in the actions dir anymore
|
||||
self.assertEqual(
|
||||
"0", # user 0 is root
|
||||
superuser_run("id", "-ur")[0].strip())
|
||||
@ -75,45 +79,33 @@ class TestPrivileged(unittest.TestCase):
|
||||
for option in options:
|
||||
with self.assertRaises(ValueError):
|
||||
output = run(action, option)
|
||||
|
||||
# if it somewhow doesn't error, we'd better not evaluate the
|
||||
# data.
|
||||
# if it somewhow doesn't error, we'd better not evaluate
|
||||
# the data.
|
||||
self.assertFalse("2" in output[0])
|
||||
|
||||
def test_breakout_option_string(self):
|
||||
"""3D. Option strings can't be used to run other actions.
|
||||
|
||||
Verify that shell control characters aren't interpreted.
|
||||
|
||||
"""
|
||||
action = "echo"
|
||||
# counting is safer than actual badness.
|
||||
options = "good; echo $((1+1))"
|
||||
|
||||
output, error = run(action, options)
|
||||
|
||||
self.assertFalse("2" in output)
|
||||
self.assertRaises(ValueError, run, action, options)
|
||||
|
||||
def test_breakout_option_list(self):
|
||||
"""3D. Option lists can't be used to run other actions.
|
||||
|
||||
Verify that only a string of options is accepted and that we can't just
|
||||
tack additional shell control characters onto the list.
|
||||
|
||||
"""
|
||||
action = "echo"
|
||||
# counting is safer than actual badness.
|
||||
options = ["good", ";", "echo $((1+1))"]
|
||||
|
||||
output, error = run(action, options)
|
||||
|
||||
# we'd better not evaluate the data.
|
||||
self.assertFalse("2" in output)
|
||||
self.assertRaises(ValueError, run, action, options)
|
||||
|
||||
def test_multiple_options(self):
|
||||
"""4. Multiple options can be provided as a list.
|
||||
|
||||
"""
|
||||
def notest_multiple_options(self):
|
||||
""" 4. Multiple options can be provided as a list. """
|
||||
# TODO: it's not allowed to call a symlink in the actions dir anymore
|
||||
self.assertEqual(
|
||||
subprocess.check_output(shlex.split("id -ur")).strip(),
|
||||
run("id", ["-u" ,"-r"])[0].strip())
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- mode: python; mode: auto-fill; fill-column: 80 -*-
|
||||
|
||||
import auth
|
||||
from logger import Logger
|
||||
import cfg
|
||||
import unittest
|
||||
import cherrypy
|
||||
import plugin_mount
|
||||
import os
|
||||
cfg.log = Logger()
|
||||
|
||||
cherrypy.log.access_file = None
|
||||
|
||||
|
||||
class Auth(unittest.TestCase):
|
||||
"""Test check_credentials function of auth to confirm it works as expected"""
|
||||
|
||||
def setUp(self):
|
||||
cfg.user_db = os.path.join(cfg.file_root, "tests/testdata/users");
|
||||
cfg.users = plugin_mount.UserStoreModule.get_plugins()[0]
|
||||
|
||||
def tearDown(self):
|
||||
for user in cfg.users.get_all():
|
||||
cfg.users.remove(user[0])
|
||||
cfg.users.close()
|
||||
|
||||
def test_add_user(self):
|
||||
self.assertIsNone(auth.add_user("test_user", "password"))
|
||||
self.assertIsNotNone(auth.add_user(None, "password"))
|
||||
self.assertIsNotNone(auth.add_user("test_user", None))
|
||||
self.assertIsNotNone(auth.add_user("test_user", "password"))
|
||||
|
||||
def test_password_check(self):
|
||||
auth.add_user("test_user", "password")
|
||||
|
||||
# check_credentials returns None if there is no error,
|
||||
# or returns error string
|
||||
self.assertIsNone(auth.check_credentials("test_user", "password"))
|
||||
self.assertIsNotNone(auth.check_credentials("test_user", "wrong"))
|
||||
|
||||
def test_nonexistent_user(self):
|
||||
self.assertIsNotNone(auth.check_credentials("test_user", "password"))
|
||||
|
||||
def test_password_too_long(self):
|
||||
password = "x" * 4097
|
||||
self.assertIsNotNone(auth.add_user("test_user", password))
|
||||
self.assertIsNotNone(auth.check_credentials("test_user", password))
|
||||
|
||||
def test_salt_is_random(self):
|
||||
auth.add_user("test_user1", "password")
|
||||
auth.add_user("test_user2", "password")
|
||||
self.assertNotEqual(
|
||||
cfg.users["test_user1"]["salt"],
|
||||
cfg.users["test_user2"]["salt"]
|
||||
)
|
||||
|
||||
def test_hash_is_random(self):
|
||||
auth.add_user("test_user1", "password")
|
||||
auth.add_user("test_user2", "password")
|
||||
self.assertNotEqual(
|
||||
cfg.users["test_user1"]["passphrase"],
|
||||
cfg.users["test_user2"]["passphrase"]
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@ -1,86 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- mode: python; mode: auto-fill; fill-column: 80 -*-
|
||||
|
||||
from logger import Logger
|
||||
import cfg
|
||||
import unittest
|
||||
import cherrypy
|
||||
import plugin_mount
|
||||
import os
|
||||
from model import User
|
||||
cfg.log = Logger()
|
||||
|
||||
cherrypy.log.access_file = None
|
||||
|
||||
|
||||
class UserStore(unittest.TestCase):
|
||||
"""Test each function of user_store to confirm they work as expected"""
|
||||
|
||||
def setUp(self):
|
||||
cfg.user_db = os.path.join(cfg.file_root, "tests/testdata/users");
|
||||
self.userstore = plugin_mount.UserStoreModule.get_plugins()[0]
|
||||
|
||||
def tearDown(self):
|
||||
for user in self.userstore.get_all():
|
||||
self.userstore.remove(user[0])
|
||||
self.userstore.close()
|
||||
|
||||
def test_user_does_not_exist(self):
|
||||
self.assertEqual(self.userstore.exists("notausername"),False)
|
||||
|
||||
def test_user_does_exist(self):
|
||||
self.add_user("isausername", False)
|
||||
self.assertEqual(self.userstore.exists("isausername"),True)
|
||||
|
||||
def test_add_user(self):
|
||||
self.assertEqual(len(self.userstore.items()),0)
|
||||
self.add_user("test_user", False)
|
||||
self.assertEqual(len(self.userstore.items()),1)
|
||||
|
||||
def test_user_is_in_expert_group(self):
|
||||
self.add_user("test_user", True)
|
||||
self.assertEqual(self.userstore.expert("test_user"),True)
|
||||
|
||||
def test_user_is_not_in_expert_group(self):
|
||||
self.add_user("test_user", False)
|
||||
self.assertEqual(self.userstore.expert("test_user"),False)
|
||||
|
||||
def test_user_removal(self):
|
||||
self.assertEqual(len(self.userstore.items()),0)
|
||||
self.add_user("test_user", False)
|
||||
self.assertEqual(len(self.userstore.items()),1)
|
||||
self.userstore.remove("test_user")
|
||||
self.assertEqual(len(self.userstore.items()),0)
|
||||
|
||||
def test_get_user_email_attribute(self):
|
||||
self.add_user("test_user", False,"test@home")
|
||||
self.assertEqual(self.userstore.attr("test_user","email"),"test@home")
|
||||
|
||||
def test_get_user(self):
|
||||
test_user = self.add_user("test_user", False)
|
||||
self.assertEqual(self.userstore.get("test_user"),test_user)
|
||||
|
||||
def test_get_all_users(self):
|
||||
self.add_user("test_user1", False)
|
||||
self.add_user("test_user2", False)
|
||||
self.assertEqual(len(self.userstore.get_all()),2)
|
||||
|
||||
def add_user(self, test_username, add_to_expert_group, email=''):
|
||||
test_user = self.create_user(test_username, email)
|
||||
if add_to_expert_group:
|
||||
test_user = self.add_user_to_expert_group(test_user)
|
||||
self.userstore.set(test_username,test_user)
|
||||
return test_user
|
||||
|
||||
def create_user(self, username, email=''):
|
||||
test_user = User()
|
||||
test_user["username"] = username
|
||||
test_user["email"] = email
|
||||
return test_user
|
||||
|
||||
def add_user_to_expert_group(self, user):
|
||||
user["groups"] = ["expert"]
|
||||
return user
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
2
urls.py
2
urls.py
@ -24,5 +24,5 @@ from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'views',
|
||||
url(r'^$', 'index')
|
||||
url(r'^$', 'index', name='index')
|
||||
)
|
||||
|
||||
26
util.py
26
util.py
@ -2,23 +2,23 @@ import os
|
||||
|
||||
|
||||
def mkdir(newdir):
|
||||
"""works the way a good mkdir should :)
|
||||
"""works the way a good mkdir should :)
|
||||
- already exists, silently complete
|
||||
- regular file in the way, raise an exception
|
||||
- parent directory(ies) does not exist, make them as well
|
||||
"""
|
||||
if os.path.isdir(newdir):
|
||||
pass
|
||||
elif os.path.isfile(newdir):
|
||||
raise OSError("a file with the same name as the desired " \
|
||||
"""
|
||||
if os.path.isdir(newdir):
|
||||
pass
|
||||
elif os.path.isfile(newdir):
|
||||
raise OSError("a file with the same name as the desired " \
|
||||
"dir, '%s', already exists." % newdir)
|
||||
else:
|
||||
head, tail = os.path.split(newdir)
|
||||
if head and not os.path.isdir(head):
|
||||
mkdir(head)
|
||||
#print "mkdir %s" % repr(newdir)
|
||||
if tail:
|
||||
os.mkdir(newdir)
|
||||
else:
|
||||
head, tail = os.path.split(newdir)
|
||||
if head and not os.path.isdir(head):
|
||||
mkdir(head)
|
||||
#print "mkdir %s" % repr(newdir)
|
||||
if tail:
|
||||
os.mkdir(newdir)
|
||||
|
||||
|
||||
def slurp(filespec):
|
||||
|
||||
25
views.py
25
views.py
@ -19,32 +19,13 @@
|
||||
Main Plinth views
|
||||
"""
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http.response import HttpResponseRedirect
|
||||
import logging
|
||||
|
||||
import cfg
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def index(request):
|
||||
"""Serve the main index page"""
|
||||
# TODO: Move firstboot handling to firstboot module somehow
|
||||
with sqlite_db(cfg.store_file, table='firstboot') as database:
|
||||
if not 'state' in database:
|
||||
# Permanent redirect causes the browser to cache the redirect,
|
||||
# preventing the user from navigating to /plinth until the
|
||||
# browser is restarted.
|
||||
return HttpResponseRedirect(cfg.server_dir + '/firstboot')
|
||||
|
||||
if database['state'] < 5:
|
||||
LOGGER.info('First boot state - %d', database['state'])
|
||||
return HttpResponseRedirect(
|
||||
cfg.server_dir + '/firstboot/state%d' % database['state'])
|
||||
|
||||
if request.user.is_authenticated():
|
||||
return HttpResponseRedirect(cfg.server_dir + '/apps')
|
||||
return HttpResponseRedirect(reverse('apps:index'))
|
||||
|
||||
return HttpResponseRedirect(cfg.server_dir + '/help/about')
|
||||
return HttpResponseRedirect(reverse('help:about'))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user