mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-28 08:03:36 +00:00
Merge pull request #82 from SunilMohanAdapa/django-routing
Use Django dispatcher instead of CherryPy dispatcher
This commit is contained in:
commit
e656e3ee47
2
LICENSES
2
LICENSES
@ -52,7 +52,7 @@ specified and linked otherwise.
|
||||
- modules/first_boot/first_boot.py :: -
|
||||
- modules/help/help.py :: -
|
||||
- modules/lib/auth_page.py :: -
|
||||
- modules/lib/auth.py :: Derived from [[http://tools.cherrypy.org/wiki/AuthenticationAndAccessRestrictions][Arnar Birisson's CherryPy wiki code]].
|
||||
- modules/lib/auth.py :: -
|
||||
- modules/lib/user_store.py :: -
|
||||
- modules/owncloud/owncloud.py :: -
|
||||
- modules/packages/packages.py :: -
|
||||
|
||||
6
Makefile
6
Makefile
@ -31,7 +31,7 @@ install: default apache-install freedombox-setup-install
|
||||
cp share/init.d/plinth $(DESTDIR)/etc/init.d
|
||||
cp -a lib/* $(DESTDIR)/usr/lib
|
||||
install plinth $(DESTDIR)/usr/bin/
|
||||
mkdir -p $(DESTDIR)/var/lib/plinth/cherrypy_sessions $(DESTDIR)/var/log/plinth $(DESTDIR)/var/run
|
||||
mkdir -p $(DESTDIR)/var/lib/plinth/sessions $(DESTDIR)/var/log/plinth $(DESTDIR)/var/run
|
||||
mkdir -p $(DESTDIR)/var/lib/plinth/data
|
||||
rm -f $(DESTDIR)/var/lib/plinth/users/sqlite3.distrib
|
||||
|
||||
@ -46,7 +46,7 @@ uninstall:
|
||||
$(DESTDIR)/usr/share/man/man1/plinth.1.gz $(DESTDIR)/var/run/plinth.pid
|
||||
|
||||
dirs:
|
||||
@mkdir -p data/cherrypy_sessions
|
||||
@mkdir -p data/sessions
|
||||
|
||||
config: Makefile
|
||||
@test -f plinth.config || cp plinth.sample.config plinth.config
|
||||
@ -59,7 +59,7 @@ html:
|
||||
@$(MAKE) -s -C doc html
|
||||
|
||||
clean:
|
||||
@rm -f cherrypy.config data/cherrypy_sessions/*
|
||||
@rm -f cherrypy.config data/sessions/*
|
||||
@find . -name "*~" -exec rm {} \;
|
||||
@find . -name ".#*" -exec rm {} \;
|
||||
@find . -name "#*" -exec rm {} \;
|
||||
|
||||
1
cfg.py
1
cfg.py
@ -32,7 +32,6 @@ pidfile = get_item(parser, 'Path', 'pidfile')
|
||||
host = get_item(parser, 'Network', 'host')
|
||||
port = int(get_item(parser, 'Network', 'port'))
|
||||
|
||||
html_root = None
|
||||
main_menu = Menu()
|
||||
|
||||
if store_file.endswith(".sqlite3"):
|
||||
|
||||
@ -10,12 +10,15 @@ import json
|
||||
import UserDict
|
||||
|
||||
import sqlite3
|
||||
|
||||
|
||||
class DefaultArg:
|
||||
class DefaultArg(object):
|
||||
pass
|
||||
|
||||
class Solutions:
|
||||
|
||||
class Solutions(object):
|
||||
Sqlite3 = 0
|
||||
|
||||
|
||||
class FileDict(UserDict.DictMixin):
|
||||
"A dictionary that stores its data persistantly in a file"
|
||||
@ -134,7 +137,7 @@ class FileDict(UserDict.DictMixin):
|
||||
def batch(self):
|
||||
return self._Batch(self)
|
||||
|
||||
class _Batch:
|
||||
class _Batch(object):
|
||||
def __init__(self, d):
|
||||
self.__d = d
|
||||
|
||||
|
||||
@ -6,7 +6,8 @@ cherrypy.log.error_file = cfg.status_log_file
|
||||
cherrypy.log.access_file = cfg.access_log_file
|
||||
cherrypy.log.screen = False
|
||||
|
||||
class Logger():
|
||||
|
||||
class Logger(object):
|
||||
"""By convention, log levels are DEBUG, INFO, WARNING, ERROR and CRITICAL."""
|
||||
def log(self, msg, level="DEBUG"):
|
||||
try:
|
||||
|
||||
29
menu.py
29
menu.py
@ -1,9 +1,8 @@
|
||||
from urlparse import urlparse
|
||||
import cherrypy
|
||||
import cfg
|
||||
|
||||
|
||||
class Menu():
|
||||
class Menu(object):
|
||||
"""One menu item."""
|
||||
def __init__(self, label="", icon="", url="#", order=50):
|
||||
"""label is the text that is displayed on the menu.
|
||||
@ -29,6 +28,17 @@ class Menu():
|
||||
self.order = order
|
||||
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
|
||||
|
||||
for item in self.items:
|
||||
if item.url == url:
|
||||
return item
|
||||
|
||||
raise KeyError('Menu item not found')
|
||||
|
||||
def sort_items(self):
|
||||
"""Sort the items in self.items by order."""
|
||||
self.items = sorted(self.items, key=lambda x: x.order, reverse=False)
|
||||
@ -48,16 +58,17 @@ class Menu():
|
||||
self.sort_items()
|
||||
return item
|
||||
|
||||
def active_p(self):
|
||||
"""Returns True if this menu item is active, otherwise False.
|
||||
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 urlparse(cherrypy.url()).path.startswith(self.url)
|
||||
anywhere above url we are visiting in the url tree.
|
||||
"""
|
||||
return request_path.startswith(self.url)
|
||||
|
||||
def active_item(self):
|
||||
def active_item(self, request):
|
||||
"""Return item list (e.g. submenu) of active menu item."""
|
||||
path = urlparse(cherrypy.url()).path
|
||||
for item in self.items:
|
||||
if path.startswith(item.url):
|
||||
if request.path.startswith(item.url):
|
||||
return item
|
||||
|
||||
2
model.py
2
model.py
@ -3,6 +3,8 @@ class User(dict):
|
||||
is a bcrypt hash of the password), salt, groups, and an email address.
|
||||
They can be blank or None, but the keys must exist. """
|
||||
def __init__(self, dict=None):
|
||||
super(User, self).__init__()
|
||||
|
||||
for key in ['username', 'name', 'passphrase', 'salt', 'email']:
|
||||
self[key] = ''
|
||||
for key in ['groups']:
|
||||
|
||||
137
module_loader.py
Normal file
137
module_loader.py
Normal file
@ -0,0 +1,137 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Discover, load and manage Plinth modules
|
||||
"""
|
||||
|
||||
import django
|
||||
import importlib
|
||||
import os
|
||||
|
||||
import cfg
|
||||
import urls
|
||||
|
||||
|
||||
def load_modules():
|
||||
"""
|
||||
Read names of enabled modules in modules/enabled directory and
|
||||
import them from modules directory.
|
||||
"""
|
||||
module_names = []
|
||||
modules = {}
|
||||
for name in os.listdir('modules/enabled'):
|
||||
full_name = 'modules.{module}'.format(module=name)
|
||||
|
||||
cfg.log.info('Importing {full_name}'.format(full_name=full_name))
|
||||
try:
|
||||
module = importlib.import_module(full_name)
|
||||
modules[name] = module
|
||||
module_names.append(name)
|
||||
except ImportError as exception:
|
||||
cfg.log.error(
|
||||
'Could not import modules/{module}: {exception}'
|
||||
.format(module=name, exception=exception))
|
||||
|
||||
_include_module_urls(full_name)
|
||||
|
||||
ordered_modules = []
|
||||
remaining_modules = dict(modules)
|
||||
for module_name in modules:
|
||||
if module_name not in remaining_modules:
|
||||
continue
|
||||
|
||||
module = remaining_modules.pop(module_name)
|
||||
try:
|
||||
_insert_modules(module_name, module, remaining_modules,
|
||||
ordered_modules)
|
||||
except KeyError:
|
||||
cfg.log.error('Unsatified dependency for module - %s' %
|
||||
(module_name,))
|
||||
|
||||
cfg.log.debug('Module load order - %s' % ordered_modules)
|
||||
|
||||
for module_name in ordered_modules:
|
||||
_initialize_module(modules[module_name])
|
||||
|
||||
|
||||
def _insert_modules(module_name, module, remaining_modules, ordered_modules):
|
||||
"""Insert modules into a list based on dependency order"""
|
||||
if module_name in ordered_modules:
|
||||
return
|
||||
|
||||
dependencies = []
|
||||
try:
|
||||
dependencies = module.DEPENDS
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
for dependency in dependencies:
|
||||
if dependency in ordered_modules:
|
||||
continue
|
||||
|
||||
try:
|
||||
module = remaining_modules.pop(dependency)
|
||||
except KeyError:
|
||||
cfg.log.error('Not found or circular dependency - %s, %s' %
|
||||
(module_name, dependency))
|
||||
raise
|
||||
|
||||
_insert_modules(dependency, module, remaining_modules, ordered_modules)
|
||||
|
||||
ordered_modules.append(module_name)
|
||||
|
||||
|
||||
def _include_module_urls(module_name):
|
||||
"""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)))
|
||||
except ImportError:
|
||||
cfg.log.debug('No URLs for {module}'.format(module=module_name))
|
||||
|
||||
|
||||
def _initialize_module(module):
|
||||
"""Call initialization method in the module if it exists"""
|
||||
try:
|
||||
init = module.init
|
||||
except AttributeError:
|
||||
cfg.log.debug('No init() for module - {module}'
|
||||
.format(module=module.__name__))
|
||||
return
|
||||
|
||||
try:
|
||||
init()
|
||||
except Exception as exception:
|
||||
cfg.log.error('Exception while running init for {module}: {exception}'
|
||||
.format(module=module, exception=exception))
|
||||
|
||||
|
||||
def get_template_directories():
|
||||
"""Return the list of template directories"""
|
||||
directory = os.path.dirname(os.path.abspath(__file__))
|
||||
core_directory = os.path.join(directory, 'templates')
|
||||
|
||||
directories = set((core_directory,))
|
||||
for name in os.listdir('modules/enabled'):
|
||||
directories.add(os.path.join('modules', name, 'templates'))
|
||||
|
||||
cfg.log.info('Template directories - %s' % directories)
|
||||
|
||||
return directories
|
||||
@ -16,10 +16,10 @@
|
||||
#
|
||||
|
||||
"""
|
||||
Plinth module for apps section page
|
||||
Plinth module for Apps section page
|
||||
"""
|
||||
|
||||
from . import apps
|
||||
from .apps import init
|
||||
|
||||
|
||||
__all__ = ['apps']
|
||||
__all__ = ['apps', 'init']
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
import cherrypy
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class Apps(PagePlugin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("apps")
|
||||
self.menu = cfg.main_menu.add_item("Apps", "icon-download-alt", "/apps", 80)
|
||||
self.menu.add_item("Chat", "icon-comment", "/../jwchat", 30)
|
||||
def init():
|
||||
"""Initailize the apps module"""
|
||||
cfg.main_menu.add_item("Apps", "icon-download-alt", "/apps", 80)
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return util.render_template(template='apps',
|
||||
title=_('User Applications'))
|
||||
|
||||
def index(request):
|
||||
"""Serve the apps index page"""
|
||||
return TemplateResponse(request, 'apps.html', {'title': _('Applications')})
|
||||
|
||||
28
modules/apps/urls.py
Normal file
28
modules/apps/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Apps module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.apps.apps',
|
||||
url(r'^apps/$', 'index')
|
||||
)
|
||||
@ -20,6 +20,8 @@ Plinth module for basic system configuration
|
||||
"""
|
||||
|
||||
from . import config
|
||||
from .config import init
|
||||
|
||||
__all__ = ['config', 'init']
|
||||
|
||||
__all__ = ['config']
|
||||
DEPENDS = ['system']
|
||||
|
||||
@ -19,17 +19,16 @@
|
||||
Plinth module for configuring timezone, hostname etc.
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
import re
|
||||
import socket
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
from ..lib.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
from ..lib.auth import login_required
|
||||
import util
|
||||
|
||||
|
||||
@ -89,67 +88,66 @@ and must not be greater than 63 characters in length.'),
|
||||
return time_zones
|
||||
|
||||
|
||||
class Configuration(PagePlugin):
|
||||
"""System configuration page"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
del args # Unused
|
||||
del kwargs # Unused
|
||||
def init():
|
||||
"""Initialize the module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item(_('Configure'), 'icon-cog', '/sys/config', 10)
|
||||
|
||||
self.register_page('sys.config')
|
||||
|
||||
self.menu = cfg.html_root.sys.menu.add_item(_('Configure'), 'icon-cog',
|
||||
'/sys/config', 10)
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve the configuration form"""
|
||||
status = get_status()
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the configuration form"""
|
||||
status = self.get_status()
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
is_expert = cfg.users.expert(request=request)
|
||||
if request.method == 'POST' and is_expert:
|
||||
form = ConfigurationForm(request.POST, prefix='configuration')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_apply_changes(status, form.cleaned_data, messages)
|
||||
status = get_status()
|
||||
form = ConfigurationForm(initial=status,
|
||||
prefix='configuration')
|
||||
else:
|
||||
form = ConfigurationForm(initial=status, prefix='configuration')
|
||||
|
||||
if kwargs and cfg.users.expert():
|
||||
form = ConfigurationForm(kwargs, prefix='configuration')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = ConfigurationForm(initial=status,
|
||||
prefix='configuration')
|
||||
return TemplateResponse(request, 'config.html',
|
||||
{'title': _('General Configuration'),
|
||||
'form': form,
|
||||
'messages_': messages,
|
||||
'is_expert': is_expert})
|
||||
|
||||
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
return {'hostname': get_hostname(),
|
||||
'time_zone': util.slurp('/etc/timezone').rstrip()}
|
||||
|
||||
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply the form changes"""
|
||||
if old_status['hostname'] != new_status['hostname']:
|
||||
if not set_hostname(new_status['hostname']):
|
||||
messages.append(('error', _('Setting hostname failed')))
|
||||
else:
|
||||
form = ConfigurationForm(initial=status, prefix='configuration')
|
||||
|
||||
return util.render_template(template='config',
|
||||
title=_('General Configuration'),
|
||||
form=form, messages=messages)
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
return {'hostname': get_hostname(),
|
||||
'time_zone': util.slurp('/etc/timezone').rstrip()}
|
||||
|
||||
@staticmethod
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply the form changes"""
|
||||
if old_status['hostname'] != new_status['hostname']:
|
||||
set_hostname(new_status['hostname'])
|
||||
messages.append(('success', _('Hostname set')))
|
||||
else:
|
||||
messages.append(('info', _('Hostname is unchanged')))
|
||||
else:
|
||||
messages.append(('info', _('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.append(('error',
|
||||
_('Error setting time zone - %s') % error))
|
||||
else:
|
||||
messages.append(('success', _('Time zone set')))
|
||||
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.append(('error',
|
||||
_('Error setting time zone - %s') % error))
|
||||
else:
|
||||
messages.append(('info', _('Time zone is unchanged')))
|
||||
messages.append(('success', _('Time zone set')))
|
||||
else:
|
||||
messages.append(('info', _('Time zone is unchanged')))
|
||||
|
||||
|
||||
def set_hostname(hostname):
|
||||
@ -166,6 +164,7 @@ def set_hostname(hostname):
|
||||
# don't persist/cache change unless it was saved successfuly
|
||||
sys_store = util.filedict_con(cfg.store_file, 'sys')
|
||||
sys_store['hostname'] = hostname
|
||||
except OSError as exception:
|
||||
raise cherrypy.HTTPError(500,
|
||||
'Updating hostname failed: %s' % exception)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
{% if cfg.users.expert %}
|
||||
{% if is_expert %}
|
||||
|
||||
{% include 'messages.html' %}
|
||||
|
||||
|
||||
28
modules/config/urls.py
Normal file
28
modules/config/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Configuration module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.config.config',
|
||||
url(r'^sys/config/$', 'index'),
|
||||
)
|
||||
@ -16,10 +16,12 @@
|
||||
#
|
||||
|
||||
"""
|
||||
Plinth module to system diagnostics
|
||||
Plinth module for system diagnostics
|
||||
"""
|
||||
|
||||
from . import diagnostics
|
||||
from .diagnostics import init
|
||||
|
||||
__all__ = ['diagnostics', 'init']
|
||||
|
||||
__all__ = ['diagnostics']
|
||||
DEPENDS = ['system']
|
||||
|
||||
@ -19,39 +19,32 @@
|
||||
Plinth module for running diagnostics
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from gettext import gettext as _
|
||||
from ..lib.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
from django.template.response import TemplateResponse
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
import util
|
||||
from ..lib.auth import login_required
|
||||
|
||||
|
||||
class diagnostics(PagePlugin):
|
||||
order = 30
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("sys.diagnostics")
|
||||
cfg.html_root.sys.menu.add_item("Diagnostics", "icon-screenshot", "/sys/diagnostics", 30)
|
||||
def init():
|
||||
"""Initialize the module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item("Diagnostics", "icon-screenshot", "/sys/diagnostics", 30)
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
return util.render_template(template='diagnostics',
|
||||
title=_('System Diagnostics'))
|
||||
|
||||
class test(PagePlugin):
|
||||
order = 31
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("sys.diagnostics.test")
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve the index page"""
|
||||
return TemplateResponse(request, 'diagnostics.html',
|
||||
{'title': _('System Diagnostics')})
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
output, error = actions.superuser_run("diagnostic-test")
|
||||
return util.render_template(template='diagnostics_test',
|
||||
title=_('Diagnostic Test'),
|
||||
diagnostics_output=output,
|
||||
diagnostics_error=error)
|
||||
|
||||
@login_required
|
||||
def test(request):
|
||||
"""Run diagnostics and the output page"""
|
||||
output, error = actions.superuser_run("diagnostic-test")
|
||||
return TemplateResponse(request, 'diagnostics_test.html',
|
||||
{'title': _('Diagnostic Test'),
|
||||
'diagnostics_output': output,
|
||||
'diagnostics_error': error})
|
||||
|
||||
29
modules/diagnostics/urls.py
Normal file
29
modules/diagnostics/urls.py
Normal file
@ -0,0 +1,29 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Diagnostics module
|
||||
"""
|
||||
|
||||
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'),
|
||||
)
|
||||
@ -20,6 +20,8 @@ Plinth module for expert mode configuration
|
||||
"""
|
||||
|
||||
from . import expert_mode
|
||||
from .expert_mode import init
|
||||
|
||||
__all__ = ['expert_mode', 'init']
|
||||
|
||||
__all__ = ['expert_mode']
|
||||
DEPENDS = ['system']
|
||||
|
||||
@ -1,79 +1,66 @@
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
from ..lib.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
|
||||
import cfg
|
||||
import util
|
||||
from ..lib.auth import login_required
|
||||
|
||||
|
||||
class ExpertsForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Form to configure expert mode"""
|
||||
|
||||
expert_mode = forms.BooleanField(
|
||||
label=_('Expert Mode'), required=False)
|
||||
|
||||
# XXX: Only present due to issue with submitting empty form
|
||||
dummy = forms.CharField(label='Dummy', initial='dummy',
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
def init():
|
||||
"""Initialize the module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item(_('Expert Mode'), 'icon-cog', '/sys/expert', 10)
|
||||
|
||||
|
||||
class Experts(PagePlugin):
|
||||
"""Expert forms page"""
|
||||
order = 60
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve the configuration form"""
|
||||
status = get_status(request)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page('sys.expert')
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
cfg.html_root.sys.menu.add_item(_('Expert Mode'), 'icon-cog',
|
||||
'/sys/expert', 10)
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the configuration form"""
|
||||
status = self.get_status()
|
||||
|
||||
cfg.log.info('Args - %s' % kwargs)
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = ExpertsForm(kwargs, prefix='experts')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = ExpertsForm(initial=status, prefix='experts')
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
form = ExpertsForm(request.POST, prefix='experts')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_apply_changes(request, form.cleaned_data, messages)
|
||||
status = get_status(request)
|
||||
form = ExpertsForm(initial=status, prefix='experts')
|
||||
else:
|
||||
form = ExpertsForm(initial=status, prefix='experts')
|
||||
|
||||
return util.render_template(template='expert_mode',
|
||||
title=_('Expert Mode'), form=form,
|
||||
messages=messages)
|
||||
return TemplateResponse(request, 'expert_mode.html',
|
||||
{'title': _('Expert Mode'),
|
||||
'form': form,
|
||||
'messages_': messages})
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
return {'expert_mode': cfg.users.expert()}
|
||||
|
||||
@staticmethod
|
||||
def _apply_changes(new_status, messages):
|
||||
"""Apply expert mode configuration"""
|
||||
message = ('info', _('Settings unchanged'))
|
||||
def get_status(request):
|
||||
"""Return the current status"""
|
||||
return {'expert_mode': cfg.users.expert(request=request)}
|
||||
|
||||
user = cfg.users.current()
|
||||
|
||||
if new_status['expert_mode']:
|
||||
if not 'expert' in user['groups']:
|
||||
user['groups'].append('expert')
|
||||
message = ('success', _('Expert mode enabled'))
|
||||
else:
|
||||
if 'expert' in user['groups']:
|
||||
user['groups'].remove('expert')
|
||||
message = ('success', _('Expert mode disabled'))
|
||||
def _apply_changes(request, new_status, messages):
|
||||
"""Apply expert mode configuration"""
|
||||
message = ('info', _('Settings unchanged'))
|
||||
|
||||
cfg.users.set(user['username'], user)
|
||||
messages.append(message)
|
||||
user = cfg.users.current(request=request)
|
||||
|
||||
if new_status['expert_mode']:
|
||||
if not 'expert' in user['groups']:
|
||||
user['groups'].append('expert')
|
||||
message = ('success', _('Expert mode enabled'))
|
||||
else:
|
||||
if 'expert' in user['groups']:
|
||||
user['groups'].remove('expert')
|
||||
message = ('success', _('Expert mode disabled'))
|
||||
|
||||
cfg.users.set(user['username'], user)
|
||||
messages.append(message)
|
||||
|
||||
28
modules/expert_mode/urls.py
Normal file
28
modules/expert_mode/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Expert Mode module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.expert_mode.expert_mode',
|
||||
url(r'^sys/expert/$', 'index'),
|
||||
)
|
||||
@ -20,6 +20,8 @@ Plinth module to configure a firewall
|
||||
"""
|
||||
|
||||
from . import firewall
|
||||
from .firewall import init
|
||||
|
||||
__all__ = ['firewall', 'init']
|
||||
|
||||
__all__ = ['firewall']
|
||||
DEPENDS = ['system']
|
||||
|
||||
@ -19,138 +19,134 @@
|
||||
Plinth module to configure a firewall
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
from ..lib.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
from ..lib.auth import login_required
|
||||
import service as service_module
|
||||
import util
|
||||
|
||||
|
||||
class Firewall(PagePlugin):
|
||||
"""Firewall menu entry and introduction page"""
|
||||
order = 40
|
||||
def init():
|
||||
"""Initailze firewall module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item(_('Firewall'), 'icon-flag', '/sys/firewall', 50)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
service_module.ENABLED.connect(on_service_enabled)
|
||||
|
||||
self.register_page('sys.firewall')
|
||||
cfg.html_root.sys.menu.add_item(_('Firewall'), 'icon-flag',
|
||||
'/sys/firewall', 50)
|
||||
|
||||
service_module.ENABLED.connect(self.on_service_enabled)
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve introcution page"""
|
||||
if not get_installed_status():
|
||||
return TemplateResponse(request, 'firewall.html',
|
||||
{'title': _('Firewall'),
|
||||
'firewall_status': 'not_installed'})
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve introcution page"""
|
||||
del kwargs # Unused
|
||||
if not get_enabled_status():
|
||||
return TemplateResponse(request, 'firewall.html',
|
||||
{'title': _('Firewall'),
|
||||
'firewall_status': 'not_running'})
|
||||
|
||||
if not self.get_installed_status():
|
||||
return util.render_template(template='firewall',
|
||||
title=_("Firewall"),
|
||||
firewall_status='not_installed')
|
||||
internal_enabled_services = get_enabled_services(zone='internal')
|
||||
external_enabled_services = get_enabled_services(zone='external')
|
||||
|
||||
if not self.get_enabled_status():
|
||||
return util.render_template(template='firewall',
|
||||
title=_("Firewall"),
|
||||
firewall_status='not_running')
|
||||
return TemplateResponse(
|
||||
request, 'firewall.html',
|
||||
{'title': _('Firewall'),
|
||||
'services': service_module.SERVICES.values(),
|
||||
'internal_enabled_services': internal_enabled_services,
|
||||
'external_enabled_services': external_enabled_services})
|
||||
|
||||
internal_enabled_services = self.get_enabled_services(zone='internal')
|
||||
external_enabled_services = self.get_enabled_services(zone='external')
|
||||
|
||||
return util.render_template(
|
||||
template='firewall', title=_('Firewall'),
|
||||
services=service_module.SERVICES.values(),
|
||||
internal_enabled_services=internal_enabled_services,
|
||||
external_enabled_services=external_enabled_services)
|
||||
def get_installed_status():
|
||||
"""Return whether firewall is installed"""
|
||||
output = _run(['get-installed'], superuser=True)
|
||||
return output.split()[0] == 'installed'
|
||||
|
||||
def get_installed_status(self):
|
||||
"""Return whether firewall is installed"""
|
||||
output = self._run(['get-installed'], superuser=True)
|
||||
return output.split()[0] == 'installed'
|
||||
|
||||
def get_enabled_status(self):
|
||||
"""Return whether firewall is installed"""
|
||||
output = self._run(['get-status'], superuser=True)
|
||||
return output.split()[0] == 'running'
|
||||
def get_enabled_status():
|
||||
"""Return whether firewall is installed"""
|
||||
output = _run(['get-status'], superuser=True)
|
||||
return output.split()[0] == 'running'
|
||||
|
||||
def get_enabled_services(self, zone):
|
||||
"""Return the status of various services currently enabled"""
|
||||
output = self._run(['get-enabled-services', '--zone', zone],
|
||||
superuser=True)
|
||||
return output.split()
|
||||
|
||||
def add_service(self, port, zone):
|
||||
"""Enable a service in firewall"""
|
||||
self._run(['add-service', port, '--zone', zone], superuser=True)
|
||||
def get_enabled_services(zone):
|
||||
"""Return the status of various services currently enabled"""
|
||||
output = _run(['get-enabled-services', '--zone', zone], superuser=True)
|
||||
return output.split()
|
||||
|
||||
def remove_service(self, port, zone):
|
||||
"""Remove a service in firewall"""
|
||||
self._run(['remove-service', port, '--zone', zone], superuser=True)
|
||||
|
||||
def on_service_enabled(self, sender, service_id, enabled, **kwargs):
|
||||
"""
|
||||
Enable/disable firewall ports when a service is
|
||||
enabled/disabled.
|
||||
"""
|
||||
del sender # Unused
|
||||
del kwargs # Unused
|
||||
def add_service(port, zone):
|
||||
"""Enable a service in firewall"""
|
||||
_run(['add-service', port, '--zone', zone], superuser=True)
|
||||
|
||||
internal_enabled_services = self.get_enabled_services(zone='internal')
|
||||
external_enabled_services = self.get_enabled_services(zone='external')
|
||||
|
||||
cfg.log.info('Service enabled - %s, %s' % (service_id, enabled))
|
||||
service = service_module.SERVICES[service_id]
|
||||
for port in service.ports:
|
||||
if enabled:
|
||||
if port not in internal_enabled_services:
|
||||
self.add_service(port, zone='internal')
|
||||
def remove_service(port, zone):
|
||||
"""Remove a service in firewall"""
|
||||
_run(['remove-service', port, '--zone', zone], superuser=True)
|
||||
|
||||
if (service.is_external and
|
||||
port not in external_enabled_services):
|
||||
self.add_service(port, zone='external')
|
||||
else:
|
||||
# service already configured.
|
||||
pass
|
||||
|
||||
def on_service_enabled(sender, service_id, enabled, **kwargs):
|
||||
"""
|
||||
Enable/disable firewall ports when a service is
|
||||
enabled/disabled.
|
||||
"""
|
||||
del sender # Unused
|
||||
del kwargs # Unused
|
||||
|
||||
internal_enabled_services = get_enabled_services(zone='internal')
|
||||
external_enabled_services = get_enabled_services(zone='external')
|
||||
|
||||
cfg.log.info('Service enabled - %s, %s' % (service_id, enabled))
|
||||
service = service_module.SERVICES[service_id]
|
||||
for port in service.ports:
|
||||
if enabled:
|
||||
if port not in internal_enabled_services:
|
||||
add_service(port, zone='internal')
|
||||
|
||||
if (service.is_external and
|
||||
port not in external_enabled_services):
|
||||
add_service(port, zone='external')
|
||||
else:
|
||||
if port in internal_enabled_services:
|
||||
enabled_services_on_port = [
|
||||
service_.is_enabled()
|
||||
for service_ in service_module.SERVICES.values()
|
||||
if port in service_.ports and
|
||||
service_id != service_.service_id]
|
||||
if not any(enabled_services_on_port):
|
||||
self.remove_service(port, zone='internal')
|
||||
|
||||
if port in external_enabled_services:
|
||||
enabled_services_on_port = [
|
||||
service_.is_enabled()
|
||||
for service_ in service_module.SERVICES.values()
|
||||
if port in service_.ports and
|
||||
service_id != service_.service_id and
|
||||
service_.is_external]
|
||||
if not any(enabled_services_on_port):
|
||||
self.remove_service(port, zone='external')
|
||||
|
||||
@staticmethod
|
||||
def _run(arguments, superuser=False):
|
||||
"""Run an given command and raise exception if there was an error"""
|
||||
command = 'firewall'
|
||||
|
||||
cfg.log.info('Running command - %s, %s, %s' % (command, arguments,
|
||||
superuser))
|
||||
|
||||
if superuser:
|
||||
output, error = actions.superuser_run(command, arguments)
|
||||
# service already configured.
|
||||
pass
|
||||
else:
|
||||
output, error = actions.run(command, arguments)
|
||||
if port in internal_enabled_services:
|
||||
enabled_services_on_port = [
|
||||
service_.is_enabled()
|
||||
for service_ in service_module.SERVICES.values()
|
||||
if port in service_.ports and
|
||||
service_id != service_.service_id]
|
||||
if not any(enabled_services_on_port):
|
||||
remove_service(port, zone='internal')
|
||||
|
||||
if error:
|
||||
raise Exception('Error setting/getting firewalld confguration - %s'
|
||||
% error)
|
||||
if port in external_enabled_services:
|
||||
enabled_services_on_port = [
|
||||
service_.is_enabled()
|
||||
for service_ in service_module.SERVICES.values()
|
||||
if port in service_.ports and
|
||||
service_id != service_.service_id and
|
||||
service_.is_external]
|
||||
if not any(enabled_services_on_port):
|
||||
remove_service(port, zone='external')
|
||||
|
||||
return output
|
||||
|
||||
def _run(arguments, superuser=False):
|
||||
"""Run an given command and raise exception if there was an error"""
|
||||
command = 'firewall'
|
||||
|
||||
cfg.log.info('Running command - %s, %s, %s' % (command, arguments,
|
||||
superuser))
|
||||
|
||||
if superuser:
|
||||
output, error = 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
|
||||
|
||||
@ -25,7 +25,7 @@ and outgoing network traffic on your {{ cfg.box_name }}. Keeping a
|
||||
firewall enabled and properly configured reduces risk of security
|
||||
threat from the Internet.</p>
|
||||
|
||||
<p>The following the current status:</p>
|
||||
<p>The following is the current status:</p>
|
||||
|
||||
{% if firewall_status = 'not_installed' %}
|
||||
<p>Firewall is not installed. Please install it. Firewall comes
|
||||
|
||||
28
modules/firewall/urls.py
Normal file
28
modules/firewall/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Firewall module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.firewall.firewall',
|
||||
url(r'^sys/firewall/$', 'index')
|
||||
)
|
||||
@ -21,5 +21,4 @@ Plinth module for first boot wizard
|
||||
|
||||
from . import first_boot
|
||||
|
||||
|
||||
__all__ = ['first_boot']
|
||||
|
||||
@ -18,16 +18,16 @@ The Plinth first-connection process has several stages:
|
||||
4. The user interacts with the box normally.
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
|
||||
from ..lib.auth import add_user
|
||||
from ..config import config
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
## TODO: flesh out these tests values
|
||||
@ -66,132 +66,124 @@ below. This key should not be the same as your key because you are not your \
|
||||
FreedomBox!'))
|
||||
|
||||
|
||||
class FirstBoot(PagePlugin):
|
||||
"""First boot wizard"""
|
||||
def index(request):
|
||||
"""Serve the index first boot page"""
|
||||
return state0(request)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
|
||||
# this is the url this page will hang off of (/firstboot)
|
||||
self.register_page('firstboot')
|
||||
self.register_page('firstboot/state0')
|
||||
self.register_page('firstboot/state1')
|
||||
def generate_box_key():
|
||||
"""Generate a box key"""
|
||||
return "fake key"
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, *args, **kwargs):
|
||||
return self.state0(*args, **kwargs)
|
||||
|
||||
def generate_box_key(self):
|
||||
return "fake key"
|
||||
def state0(request):
|
||||
"""
|
||||
In this state, we do time config over HTTP, name the box and
|
||||
server key selection.
|
||||
|
||||
@cherrypy.expose
|
||||
def state0(self, **kwargs):
|
||||
"""
|
||||
In this state, we do time config over HTTP, name the box and
|
||||
server key selection.
|
||||
All the parameters are form inputs. They get passed in when
|
||||
the form is submitted. This method checks the inputs and if
|
||||
they validate, uses them to take action. If they do not
|
||||
validate, it displays the form to give the user a chance to
|
||||
input correct values. It might display an error message (in
|
||||
the message parameter).
|
||||
|
||||
All the parameters are form inputs. They get passed in when
|
||||
the form is submitted. This method checks the inputs and if
|
||||
they validate, uses them to take action. If they do not
|
||||
validate, it displays the form to give the user a chance to
|
||||
input correct values. It might display an error message (in
|
||||
the message parameter).
|
||||
message is an optional string that we can display to the
|
||||
user. It's a good place to put error messages.
|
||||
"""
|
||||
try:
|
||||
if _read_state() >= 5:
|
||||
return HttpResponseRedirect(cfg.server_dir)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
message is an optional string that we can display to the
|
||||
user. It's a good place to put error messages.
|
||||
"""
|
||||
try:
|
||||
if FirstBoot._read_state() >= 5:
|
||||
raise cherrypy.HTTPRedirect(cfg.server_dir, 302)
|
||||
except KeyError:
|
||||
pass
|
||||
## Until LDAP is in place, we'll put the box key in the cfg.store_file
|
||||
status = get_state0()
|
||||
|
||||
## Until LDAP is in place, we'll put the box key in the cfg.store_file
|
||||
status = self.get_state0()
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
if request.method == 'POST':
|
||||
form = State0Form(request.POST, prefix='firstboot')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
success = _apply_state0(status, form.cleaned_data, messages)
|
||||
|
||||
if kwargs:
|
||||
form = State0Form(kwargs, prefix='firstboot')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
success = self._apply_state0(status, form.cleaned_data,
|
||||
messages)
|
||||
if success:
|
||||
# Everything is good, permanently mark and move to page 2
|
||||
_write_state(1)
|
||||
return HttpResponseRedirect(
|
||||
cfg.server_dir + '/firstboot/state1')
|
||||
else:
|
||||
form = State0Form(initial=status, prefix='firstboot')
|
||||
|
||||
if success:
|
||||
# Everything is good, permanently mark and move to page 2
|
||||
FirstBoot._write_state(1)
|
||||
raise cherrypy.HTTPRedirect('state1', 302)
|
||||
return TemplateResponse(request, 'firstboot_state0.html',
|
||||
{'title': _('First Boot!'),
|
||||
'form': form,
|
||||
'messages_': messages})
|
||||
|
||||
|
||||
def get_state0():
|
||||
"""Return the state for form state 0"""
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
|
||||
database:
|
||||
return {'hostname': config.get_hostname(),
|
||||
'box_key': database.get('box_key', None)}
|
||||
|
||||
|
||||
def _apply_state0(old_state, new_state, messages):
|
||||
"""Apply changes in state 0 form"""
|
||||
success = True
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
|
||||
database:
|
||||
database['about'] = 'Information about this FreedomBox'
|
||||
|
||||
if new_state['box_key']:
|
||||
database['box_key'] = new_state['box_key']
|
||||
elif not old_state['box_key']:
|
||||
database['box_key'] = generate_box_key()
|
||||
|
||||
if old_state['hostname'] != new_state['hostname']:
|
||||
config.set_hostname(new_state['hostname'])
|
||||
|
||||
error = add_user(new_state['username'], new_state['password'],
|
||||
'First user, please change', '', True)
|
||||
if error:
|
||||
messages.append(
|
||||
('error', _('User account creation failed: %s') % error))
|
||||
success = False
|
||||
else:
|
||||
form = State0Form(initial=status, prefix='firstboot')
|
||||
messages.append(('success', _('User account created')))
|
||||
|
||||
return util.render_template(template='firstboot_state0',
|
||||
title=_('First Boot!'), form=form,
|
||||
messages=messages)
|
||||
return success
|
||||
|
||||
@staticmethod
|
||||
def get_state0():
|
||||
"""Return the state for form state 0"""
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
|
||||
database:
|
||||
return {'hostname': config.get_hostname(),
|
||||
'box_key': database.get('box_key', None)}
|
||||
|
||||
def _apply_state0(self, old_state, new_state, messages):
|
||||
"""Apply changes in state 0 form"""
|
||||
success = True
|
||||
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
|
||||
database:
|
||||
database['about'] = 'Information about this FreedomBox'
|
||||
def state1(request):
|
||||
"""
|
||||
State 1 is when we have a box name and key. In this state,
|
||||
our task is to provide a certificate and maybe to guide the
|
||||
user through installing it. We automatically move to State 2,
|
||||
which is an HTTPS connection.
|
||||
|
||||
if new_state['box_key']:
|
||||
database['box_key'] = new_state['box_key']
|
||||
elif not old_state['box_key']:
|
||||
database['box_key'] = self.generate_box_key()
|
||||
TODO: HTTPS failure in State 2 should returns to state 1.
|
||||
"""
|
||||
# TODO complete first_boot handling
|
||||
# Make sure the user is not stuck on a dead end for now.
|
||||
_write_state(5)
|
||||
|
||||
if old_state['hostname'] != new_state['hostname']:
|
||||
config.set_hostname(new_state['hostname'])
|
||||
return TemplateResponse(request, 'firstboot_state1.html',
|
||||
{'title': _('Installing the Certificate')})
|
||||
|
||||
error = add_user(new_state['username'], new_state['password'],
|
||||
'First user, please change', '', True)
|
||||
if error:
|
||||
messages.append(
|
||||
('error', _('User account creation failed: %s') % error))
|
||||
success = False
|
||||
else:
|
||||
messages.append(('success', _('User account created')))
|
||||
|
||||
return success
|
||||
def _read_state():
|
||||
"""Read the current state from database"""
|
||||
with sqlite_db(cfg.store_file, table='firstboot',
|
||||
autocommit=True) as database:
|
||||
return database['state']
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
def state1():
|
||||
"""
|
||||
State 1 is when we have a box name and key. In this state,
|
||||
our task is to provide a certificate and maybe to guide the
|
||||
user through installing it. We automatically move to State 2,
|
||||
which is an HTTPS connection.
|
||||
|
||||
TODO: HTTPS failure in State 2 should returns to state 1.
|
||||
"""
|
||||
# TODO complete first_boot handling
|
||||
# Make sure the user is not stuck on a dead end for now.
|
||||
FirstBoot._write_state(5)
|
||||
|
||||
return util.render_template(template='firstboot_state1',
|
||||
title=_('Installing the Certificate'))
|
||||
|
||||
@staticmethod
|
||||
def _read_state():
|
||||
"""Read the current state from database"""
|
||||
with sqlite_db(cfg.store_file, table='firstboot',
|
||||
autocommit=True) as database:
|
||||
return database['state']
|
||||
|
||||
@staticmethod
|
||||
def _write_state(state):
|
||||
"""Write state to database"""
|
||||
with sqlite_db(cfg.store_file, table='firstboot',
|
||||
autocommit=True) as database:
|
||||
database['state'] = state
|
||||
def _write_state(state):
|
||||
"""Write state to database"""
|
||||
with sqlite_db(cfg.store_file, table='firstboot',
|
||||
autocommit=True) as database:
|
||||
database['state'] = state
|
||||
|
||||
30
modules/first_boot/urls.py
Normal file
30
modules/first_boot/urls.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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the First Boot module
|
||||
"""
|
||||
|
||||
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')
|
||||
)
|
||||
@ -20,6 +20,6 @@ Plinth module for help pages
|
||||
"""
|
||||
|
||||
from . import help # pylint: disable-msg=W0622
|
||||
from .help import init
|
||||
|
||||
|
||||
__all__ = ['help']
|
||||
__all__ = ['help', 'init']
|
||||
|
||||
@ -1,46 +1,40 @@
|
||||
import os
|
||||
import cherrypy
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
from django.template.response import TemplateResponse
|
||||
|
||||
import cfg
|
||||
import util
|
||||
|
||||
|
||||
class Help(PagePlugin):
|
||||
order = 20 # order of running init in PagePlugins
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("help")
|
||||
self.menu = cfg.main_menu.add_item(_("Documentation"), "icon-book", "/help", 101)
|
||||
self.menu.add_item(_("Where to Get Help"), "icon-search", "/help/index", 5)
|
||||
self.menu.add_item(_("Developer's Manual"), "icon-info-sign", "/help/view/plinth", 10)
|
||||
self.menu.add_item(_("FAQ"), "icon-question-sign", "/help/view/faq", 20)
|
||||
self.menu.add_item(_("%s Wiki" % cfg.box_name), "icon-pencil", "http://wiki.debian.org/FreedomBox", 30)
|
||||
self.menu.add_item(_("About"), "icon-star", "/help/about", 100)
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return util.render_template(template='help',
|
||||
title=_('Documentation and FAQ'))
|
||||
|
||||
@cherrypy.expose
|
||||
def about(self):
|
||||
return util.render_template(
|
||||
template='about',
|
||||
title=_('About the {box_name}').format(box_name=cfg.box_name))
|
||||
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.add_item(_('%s Wiki' % cfg.box_name), 'icon-pencil',
|
||||
'http://wiki.debian.org/FreedomBox', 30)
|
||||
menu.add_item(_('About'), 'icon-star', '/help/about', 100)
|
||||
|
||||
|
||||
class View(PagePlugin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("help.view")
|
||||
def index(request):
|
||||
"""Serve the index page"""
|
||||
return TemplateResponse(request, 'help.html',
|
||||
{'title': _('Documentation and FAQ')})
|
||||
|
||||
@cherrypy.expose
|
||||
def default(self, page=''):
|
||||
if page not in ['design', 'plinth', 'hacking', 'faq']:
|
||||
raise cherrypy.HTTPError(404, "The path '/help/view/%s' was not found." % page)
|
||||
|
||||
with open(os.path.join("doc", "%s.part.html" % page), 'r') as IF:
|
||||
main = IF.read()
|
||||
return util.render_template(title=_("%s Documentation" %
|
||||
cfg.product_name), main=main)
|
||||
def about(request):
|
||||
"""Serve the about page"""
|
||||
title = _('About the {box_name}').format(box_name=cfg.box_name)
|
||||
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()
|
||||
|
||||
title = _('%s Documentation') % cfg.product_name
|
||||
return TemplateResponse(request, 'login_nav.html',
|
||||
{'title': title, 'main': main})
|
||||
|
||||
34
modules/help/urls.py
Normal file
34
modules/help/urls.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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Help module
|
||||
"""
|
||||
|
||||
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'),
|
||||
)
|
||||
@ -23,7 +23,6 @@ from . import auth
|
||||
from . import auth_page
|
||||
from . import user_store
|
||||
|
||||
|
||||
__all__ = ['auth',
|
||||
'auth_page',
|
||||
'user_store']
|
||||
|
||||
@ -1,20 +1,13 @@
|
||||
# Form based authentication for CherryPy. Requires the
|
||||
# Session tool to be loaded.
|
||||
#
|
||||
# Thanks for this code is owed to Arnar Birgisson -at - gmail.com. It
|
||||
# is based on code he wrote that was retrieved from
|
||||
# http://tools.cherrypy.org/wiki/AuthenticationAndAccessRestrictions
|
||||
# on 1 February 2011.
|
||||
|
||||
import cherrypy
|
||||
import urllib
|
||||
from django.http.response import HttpResponseRedirect
|
||||
import functools
|
||||
from passlib.hash import bcrypt
|
||||
from passlib.exc import PasswordSizeError
|
||||
|
||||
import cfg
|
||||
import random
|
||||
from model import User
|
||||
|
||||
cfg.session_key = '_cp_username'
|
||||
cfg.session_key = '_username'
|
||||
|
||||
|
||||
def add_user(username, passphrase, name='', email='', expert=False):
|
||||
"""Add a new user with specified username and passphrase.
|
||||
@ -49,6 +42,7 @@ def add_user(username, passphrase, name='', email='', expert=False):
|
||||
cfg.log(error)
|
||||
return error
|
||||
|
||||
|
||||
def check_credentials(username, passphrase):
|
||||
"""Verifies credentials for username and passphrase.
|
||||
|
||||
@ -87,79 +81,20 @@ def check_credentials(username, passphrase):
|
||||
|
||||
if error:
|
||||
cfg.log(error)
|
||||
|
||||
return error
|
||||
|
||||
def check_auth(*args, **kwargs):
|
||||
"""A tool that looks in config for 'auth.require'. If found and it
|
||||
is not None, a login is required and the entry is evaluated as a
|
||||
list of conditions that the user must fulfill"""
|
||||
conditions = cherrypy.request.config.get('auth.require', None)
|
||||
# format GET params
|
||||
get_params = urllib.quote(cherrypy.request.request_line.split()[1])
|
||||
if conditions is not None:
|
||||
username = cherrypy.session.get(cfg.session_key)
|
||||
if username:
|
||||
cherrypy.request.login = username
|
||||
for condition in conditions:
|
||||
# A condition is just a callable that returns true or false
|
||||
if not condition():
|
||||
# Send old page as from_page parameter
|
||||
raise cherrypy.HTTPRedirect(cfg.server_dir + "/auth/login?from_page=%s" % get_params)
|
||||
else:
|
||||
# Send old page as from_page parameter
|
||||
raise cherrypy.HTTPRedirect(cfg.server_dir + "/auth/login?from_page=%s" % get_params)
|
||||
|
||||
cherrypy.tools.auth = cherrypy.Tool('before_handler', check_auth)
|
||||
|
||||
def require(*conditions):
|
||||
"""A decorator that appends conditions to the auth.require config
|
||||
variable."""
|
||||
def decorate(f):
|
||||
if not hasattr(f, '_cp_config'):
|
||||
f._cp_config = dict()
|
||||
if 'auth.require' not in f._cp_config:
|
||||
f._cp_config['auth.require'] = []
|
||||
f._cp_config['auth.require'].extend(conditions)
|
||||
return f
|
||||
return decorate
|
||||
|
||||
|
||||
# Conditions are callables that return True
|
||||
# if the user fulfills the conditions they define, False otherwise
|
||||
#
|
||||
# They can access the current username as cherrypy.request.login
|
||||
#
|
||||
# Define those at will however suits the application.
|
||||
|
||||
def member_of(groupname):
|
||||
def check():
|
||||
# replace with actual check if <username> is in <groupname>
|
||||
return cherrypy.request.login == 'joe' and groupname == 'admin'
|
||||
return check
|
||||
|
||||
def name_is(reqd_username):
|
||||
return lambda: reqd_username == cherrypy.request.login
|
||||
|
||||
# These might be handy
|
||||
|
||||
def any_of(*conditions):
|
||||
"""Returns True if any of the conditions match"""
|
||||
def check():
|
||||
for c in conditions:
|
||||
if c():
|
||||
return True
|
||||
return False
|
||||
return check
|
||||
|
||||
# By default all conditions are required, but this might still be
|
||||
# needed if you want to use it inside of an any_of(...) condition
|
||||
def all_of(*conditions):
|
||||
"""Returns True if all of the conditions match"""
|
||||
def check():
|
||||
for c in conditions:
|
||||
if not c():
|
||||
return False
|
||||
return True
|
||||
return check
|
||||
# XXX: Only required until we start using Django authentication system properly
|
||||
def login_required(func):
|
||||
"""A decorator to ensure that user is logged in before accessing a view"""
|
||||
@functools.wraps(func)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
"""Check that user is logged in"""
|
||||
if not request.session.get(cfg.session_key, None):
|
||||
return HttpResponseRedirect(
|
||||
cfg.server_dir + "/auth/login?from_page=%s" % request.path)
|
||||
|
||||
return func(request, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
@ -2,19 +2,17 @@
|
||||
Controller to provide login and logout actions
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
import cfg
|
||||
from django import forms
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
import auth
|
||||
import util
|
||||
|
||||
from . import auth
|
||||
|
||||
|
||||
class LoginForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Login form"""
|
||||
from_page = forms.CharField(widget=forms.HiddenInput(), required=False)
|
||||
|
||||
username = forms.CharField(label=_('Username'))
|
||||
password = forms.CharField(label=_('Passphrase'),
|
||||
widget=forms.PasswordInput())
|
||||
@ -33,48 +31,37 @@ class LoginForm(forms.Form): # pylint: disable-msg=W0232
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class AuthController(PagePlugin):
|
||||
"""Login and logout pages"""
|
||||
def login(request):
|
||||
"""Serve the login page"""
|
||||
form = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
if request.method == 'POST':
|
||||
form = LoginForm(request.POST, prefix='auth')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data['username']
|
||||
request.session[cfg.session_key] = username
|
||||
return HttpResponseRedirect(_get_from_page(request))
|
||||
else:
|
||||
form = LoginForm(prefix='auth')
|
||||
|
||||
self.register_page('auth')
|
||||
return TemplateResponse(request, 'form.html',
|
||||
{'title': _('Login'),
|
||||
'form': form,
|
||||
'submit_text': _('Login')})
|
||||
|
||||
def on_login(self, username):
|
||||
"""Called on successful login"""
|
||||
|
||||
def on_logout(self, username):
|
||||
"""Called on logout"""
|
||||
def logout(request):
|
||||
"""Logout and redirect to origin page"""
|
||||
try:
|
||||
del request.session[cfg.session_key]
|
||||
request.session.flush()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@cherrypy.expose
|
||||
def login(self, from_page=cfg.server_dir+"/", **kwargs):
|
||||
"""Serve the login page"""
|
||||
form = None
|
||||
return HttpResponseRedirect(_get_from_page(request))
|
||||
|
||||
if kwargs:
|
||||
form = LoginForm(kwargs, prefix='auth')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data['username']
|
||||
cherrypy.session[cfg.session_key] = username
|
||||
cherrypy.request.login = username
|
||||
self.on_login(username)
|
||||
raise cherrypy.HTTPRedirect(from_page or
|
||||
(cfg.server_dir + "/"))
|
||||
else:
|
||||
form = LoginForm(prefix='auth')
|
||||
|
||||
return util.render_template(template='form', title=_('Login'),
|
||||
form=form, submit_text=_('Login'))
|
||||
|
||||
@cherrypy.expose
|
||||
def logout(self, from_page=cfg.server_dir+"/"):
|
||||
sess = cherrypy.session
|
||||
username = sess.get(cfg.session_key, None)
|
||||
sess[cfg.session_key] = None
|
||||
if username:
|
||||
cherrypy.request.login = None
|
||||
self.on_logout(username)
|
||||
|
||||
raise cherrypy.HTTPRedirect(from_page or (cfg.server_dir + "/"))
|
||||
def _get_from_page(request):
|
||||
"""Return the 'from page' of a request"""
|
||||
return request.GET.get('from_page', cfg.server_dir + '/')
|
||||
|
||||
29
modules/lib/urls.py
Normal file
29
modules/lib/urls.py
Normal file
@ -0,0 +1,29 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Lib module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.lib.auth_page',
|
||||
url(r'^auth/login/$', 'login'),
|
||||
url(r'^auth/logout/$', 'logout')
|
||||
)
|
||||
@ -1,11 +1,13 @@
|
||||
import cherrypy
|
||||
import cfg
|
||||
from model import User
|
||||
from plugin_mount import UserStoreModule
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
|
||||
|
||||
class UserStore(UserStoreModule, sqlite_db):
|
||||
def __init__(self):
|
||||
super(UserStore, self).__init__()
|
||||
|
||||
self.db_file = cfg.user_db
|
||||
sqlite_db.__init__(self, self.db_file, autocommit=True, check_same_thread=False)
|
||||
self.__enter__()
|
||||
@ -13,25 +15,36 @@ class UserStore(UserStoreModule, sqlite_db):
|
||||
def close(self):
|
||||
self.__exit__(None,None,None)
|
||||
|
||||
def current(self, name=False):
|
||||
def current(self, request=None, name=False):
|
||||
"""Return current user, if there is one, else None.
|
||||
If name = True, return the username instead of the user."""
|
||||
try:
|
||||
username = cherrypy.session.get(cfg.session_key)
|
||||
if name:
|
||||
return username
|
||||
else:
|
||||
return self.get(username)
|
||||
except AttributeError:
|
||||
if not request:
|
||||
return None
|
||||
|
||||
def expert(self, username=None):
|
||||
|
||||
try:
|
||||
username = request.session[cfg.session_key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
if name:
|
||||
return username
|
||||
|
||||
return self.get(username)
|
||||
|
||||
def expert(self, username=None, request=None):
|
||||
"""Return whether the current or provided user is an expert"""
|
||||
if not username:
|
||||
username = self.current(name=True)
|
||||
groups = self.attr(username,"groups")
|
||||
if not request:
|
||||
return False
|
||||
|
||||
username = self.current(request=request, name=True)
|
||||
|
||||
groups = self.attr(username, 'groups')
|
||||
|
||||
if not groups:
|
||||
return False
|
||||
return 'expert' in groups
|
||||
|
||||
return 'expert' in groups
|
||||
|
||||
def attr(self, username=None, field=None):
|
||||
return self.get(username)[field]
|
||||
|
||||
@ -20,6 +20,8 @@ Plinth module to configure ownCloud
|
||||
"""
|
||||
|
||||
from . import owncloud
|
||||
from .owncloud import init
|
||||
|
||||
__all__ = ['owncloud', 'init']
|
||||
|
||||
__all__ = ['owncloud']
|
||||
DEPENDS = ['apps']
|
||||
|
||||
@ -1,85 +1,81 @@
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
from ..lib.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
from ..lib.auth import login_required
|
||||
import service
|
||||
import util
|
||||
|
||||
|
||||
SERVICE = None
|
||||
|
||||
|
||||
class OwnCloudForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""ownCloud configuration form"""
|
||||
enabled = forms.BooleanField(label=_('Enable ownCloud'), required=False)
|
||||
|
||||
# XXX: Only present due to issue with submitting empty form
|
||||
dummy = forms.CharField(label='Dummy', initial='dummy',
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
def init():
|
||||
"""Initialize the ownCloud module"""
|
||||
menu = cfg.main_menu.find('/apps')
|
||||
menu.add_item('Owncloud', 'icon-picture', '/apps/owncloud', 35)
|
||||
|
||||
status = get_status()
|
||||
|
||||
global SERVICE # pylint: disable-msg=W0603
|
||||
SERVICE = service.Service('owncloud', _('ownCloud'), ['http', 'https'],
|
||||
is_external=True, enabled=status['enabled'])
|
||||
|
||||
|
||||
class OwnCloud(PagePlugin):
|
||||
"""ownCloud configuration page"""
|
||||
order = 90
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve the ownCloud configuration page"""
|
||||
status = get_status()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page('apps.owncloud')
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
cfg.html_root.apps.menu.add_item('Owncloud', 'icon-picture',
|
||||
'/apps/owncloud', 35)
|
||||
|
||||
status = self.get_status()
|
||||
self.service = service.Service('owncloud', _('ownCloud'),
|
||||
['http', 'https'], is_external=True,
|
||||
enabled=status['enabled'])
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the ownCloud configuration page"""
|
||||
status = self.get_status()
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = OwnCloudForm(kwargs, prefix='owncloud')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = OwnCloudForm(initial=status, prefix='owncloud')
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
form = OwnCloudForm(request.POST, prefix='owncloud')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_apply_changes(status, form.cleaned_data, messages)
|
||||
status = get_status()
|
||||
form = OwnCloudForm(initial=status, prefix='owncloud')
|
||||
else:
|
||||
form = OwnCloudForm(initial=status, prefix='owncloud')
|
||||
|
||||
return util.render_template(template='owncloud', title=_('ownCloud'),
|
||||
form=form, messages=messages)
|
||||
return TemplateResponse(request, 'owncloud.html',
|
||||
{'title': _('ownCloud'),
|
||||
'form': form,
|
||||
'messages_': messages})
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
output, error = actions.run('owncloud-setup', 'status')
|
||||
if error:
|
||||
raise Exception('Error getting ownCloud status: %s' % error)
|
||||
|
||||
return {'enabled': 'enable' in output.split()}
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
output, error = actions.run('owncloud-setup', 'status')
|
||||
if error:
|
||||
raise Exception('Error getting ownCloud status: %s' % error)
|
||||
|
||||
def _apply_changes(self, old_status, new_status, messages):
|
||||
"""Apply the changes"""
|
||||
if old_status['enabled'] == new_status['enabled']:
|
||||
messages.append(('info', _('Setting unchanged')))
|
||||
return
|
||||
return {'enabled': 'enable' in output.split()}
|
||||
|
||||
if new_status['enabled']:
|
||||
messages.append(('success', _('ownCloud enabled')))
|
||||
option = 'enable'
|
||||
else:
|
||||
messages.append(('success', _('ownCloud disabled')))
|
||||
option = 'noenable'
|
||||
|
||||
actions.superuser_run('owncloud-setup', [option], async=True)
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply the changes"""
|
||||
if old_status['enabled'] == new_status['enabled']:
|
||||
messages.append(('info', _('Setting unchanged')))
|
||||
return
|
||||
|
||||
# Send a signal to other modules that the service is
|
||||
# enabled/disabled
|
||||
self.service.notify_enabled(self, new_status['enabled'])
|
||||
if new_status['enabled']:
|
||||
messages.append(('success', _('ownCloud enabled')))
|
||||
option = 'enable'
|
||||
else:
|
||||
messages.append(('success', _('ownCloud disabled')))
|
||||
option = 'noenable'
|
||||
|
||||
actions.superuser_run('owncloud-setup', [option], async=True)
|
||||
|
||||
# Send a signal to other modules that the service is
|
||||
# enabled/disabled
|
||||
SERVICE.notify_enabled(None, new_status['enabled'])
|
||||
|
||||
28
modules/owncloud/urls.py
Normal file
28
modules/owncloud/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the ownCloud module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.owncloud.owncloud',
|
||||
url(r'^apps/owncloud/$', 'index'),
|
||||
)
|
||||
@ -20,6 +20,8 @@ Plinth module to manage packages
|
||||
"""
|
||||
|
||||
from . import packages
|
||||
from .packages import init
|
||||
|
||||
__all__ = ['packages', 'init']
|
||||
|
||||
__all__ = ['packages']
|
||||
DEPENDS = ['system']
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
from ..lib.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
import util
|
||||
from ..lib.auth import login_required
|
||||
|
||||
|
||||
def get_modules_available():
|
||||
@ -28,10 +27,6 @@ def get_modules_enabled():
|
||||
|
||||
class PackagesForm(forms.Form):
|
||||
"""Packages form"""
|
||||
# XXX: Only present due to issue with submitting empty form
|
||||
dummy = forms.CharField(label='Dummy', initial='dummy',
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# pylint: disable-msg=E1002, E1101
|
||||
super(forms.Form, self).__init__(*args, **kwargs)
|
||||
@ -44,90 +39,83 @@ class PackagesForm(forms.Form):
|
||||
label=label, required=False)
|
||||
|
||||
|
||||
class Packages(PagePlugin):
|
||||
"""Package page"""
|
||||
order = 20
|
||||
def init():
|
||||
"""Initialize the Packages module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item('Package Manager', 'icon-gift', '/sys/packages', 20)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page('sys.packages')
|
||||
|
||||
cfg.html_root.sys.menu.add_item('Package Manager', 'icon-gift',
|
||||
'/sys/packages', 20)
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve the form"""
|
||||
status = get_status()
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, *args, **kwargs):
|
||||
"""Serve the form"""
|
||||
del args # Unused
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
status = self.get_status()
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = PackagesForm(kwargs, prefix='packages')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = PackagesForm(initial=status, prefix='packages')
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
form = PackagesForm(request.POST, prefix='packages')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_apply_changes(status, form.cleaned_data, messages)
|
||||
status = get_status()
|
||||
form = PackagesForm(initial=status, prefix='packages')
|
||||
else:
|
||||
form = PackagesForm(initial=status, prefix='packages')
|
||||
|
||||
return util.render_template(template='packages',
|
||||
title=_('Add/Remove Plugins'),
|
||||
form=form, messages=messages)
|
||||
return TemplateResponse(request, 'packages.html',
|
||||
{'title': _('Add/Remove Plugins'),
|
||||
'form': form,
|
||||
'messages_': messages})
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
modules_available = get_modules_available()
|
||||
modules_enabled = get_modules_enabled()
|
||||
|
||||
return {module + '_enabled': module in modules_enabled
|
||||
for module in modules_available}
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
modules_available = get_modules_available()
|
||||
modules_enabled = get_modules_enabled()
|
||||
|
||||
@staticmethod
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply form changes"""
|
||||
for field, enabled in new_status.items():
|
||||
if not field.endswith('_enabled'):
|
||||
continue
|
||||
return {module + '_enabled': module in modules_enabled
|
||||
for module in modules_available}
|
||||
|
||||
if old_status[field] == new_status[field]:
|
||||
continue
|
||||
|
||||
module = field.split('_enabled')[0]
|
||||
if enabled:
|
||||
output, error = actions.superuser_run(
|
||||
'module-manager', ['enable', cfg.python_root, module])
|
||||
del output # Unused
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply form changes"""
|
||||
for field, enabled in new_status.items():
|
||||
if not field.endswith('_enabled'):
|
||||
continue
|
||||
|
||||
# TODO: need to get plinth to load the module we just
|
||||
# enabled
|
||||
if error:
|
||||
messages.append(
|
||||
('error', _('Error enabling module - {module}').format(
|
||||
module=module)))
|
||||
else:
|
||||
messages.append(
|
||||
('success', _('Module enabled - {module}').format(
|
||||
if old_status[field] == new_status[field]:
|
||||
continue
|
||||
|
||||
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:
|
||||
messages.append(
|
||||
('error', _('Error enabling module - {module}').format(
|
||||
module=module)))
|
||||
else:
|
||||
messages.append(
|
||||
('success', _('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:
|
||||
messages.append(
|
||||
('error',
|
||||
_('Error disabling module - {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:
|
||||
messages.append(
|
||||
('error',
|
||||
_('Error disabling module - {module}').format(
|
||||
module=module)))
|
||||
else:
|
||||
messages.append(
|
||||
('success', _('Module disabled - {module}').format(
|
||||
module=module)))
|
||||
messages.append(
|
||||
('success', _('Module disabled - {module}').format(
|
||||
module=module)))
|
||||
|
||||
28
modules/packages/urls.py
Normal file
28
modules/packages/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Packages module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.packages.packages',
|
||||
url(r'^sys/packages/$', 'index'),
|
||||
)
|
||||
@ -20,6 +20,8 @@ Plinth module to configure PageKite
|
||||
"""
|
||||
|
||||
from . import pagekite
|
||||
from .pagekite import init
|
||||
|
||||
__all__ = ['pagekite', 'init']
|
||||
|
||||
__all__ = ['pagekite']
|
||||
DEPENDS = ['apps']
|
||||
|
||||
@ -19,45 +19,38 @@
|
||||
Plinth module for configuring PageKite service
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
from ..lib.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
import util
|
||||
from ..lib.auth import login_required
|
||||
|
||||
|
||||
class PageKite(PagePlugin):
|
||||
"""PageKite menu entry and introduction page"""
|
||||
order = 60
|
||||
def init():
|
||||
"""Intialize the PageKite module"""
|
||||
menu = cfg.main_menu.find('/apps')
|
||||
menu.add_item(_('Public Visibility (PageKite)'), 'icon-flag',
|
||||
'/apps/pagekite', 50)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
|
||||
self.register_page("apps.pagekite")
|
||||
cfg.html_root.apps.menu.add_item(
|
||||
_("Public Visibility (PageKite)"), "icon-flag",
|
||||
"/apps/pagekite", 50)
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve introdution page"""
|
||||
menu = {'title': _('PageKite'),
|
||||
'items': [{'url': '/apps/pagekite/configure',
|
||||
'text': _('Configure PageKite')}]}
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(**kwargs):
|
||||
"""Serve introdution page"""
|
||||
del kwargs # Unused
|
||||
sidebar_right = render_to_string('menu_block.html', {'menu': menu},
|
||||
RequestContext(request))
|
||||
|
||||
menu = {'title': _('PageKite'),
|
||||
'items': [{'url': '/apps/pagekite/configure',
|
||||
'text': _('Configure PageKite')}]}
|
||||
sidebar_right = util.render_template(template='menu_block', menu=menu)
|
||||
|
||||
return util.render_template(template='pagekite_introduction',
|
||||
title=_("Public Visibility (PageKite)"),
|
||||
sidebar_right=sidebar_right)
|
||||
return TemplateResponse(request, 'pagekite_introduction.html',
|
||||
{'title': _('Public Visibility (PageKite)'),
|
||||
'sidebar_right': sidebar_right})
|
||||
|
||||
|
||||
class TrimmedCharField(forms.CharField):
|
||||
@ -105,121 +98,114 @@ for your account if no secret is set on the kite'))
|
||||
https://pagekite.net/wiki/Howto/SshOverPageKite/">instructions</a>'))
|
||||
|
||||
|
||||
class Configure(PagePlugin): # pylint: disable-msg=C0103
|
||||
"""Main configuration form"""
|
||||
order = 65
|
||||
@login_required
|
||||
def configure(request):
|
||||
"""Serve the configuration form"""
|
||||
status = get_status()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
self.register_page("apps.pagekite.configure")
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the configuration form"""
|
||||
status = self.get_status()
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = ConfigureForm(kwargs, prefix='pagekite')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = ConfigureForm(initial=status, prefix='pagekite')
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
form = ConfigureForm(request.POST, prefix='pagekite')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_apply_changes(status, form.cleaned_data, messages)
|
||||
status = get_status()
|
||||
form = ConfigureForm(initial=status, prefix='pagekite')
|
||||
else:
|
||||
form = ConfigureForm(initial=status, prefix='pagekite')
|
||||
|
||||
return util.render_template(template='pagekite_configure',
|
||||
title=_('Configure PageKite'), form=form,
|
||||
messages=messages)
|
||||
return TemplateResponse(request, 'pagekite_configure.html',
|
||||
{'title': _('Configure PageKite'),
|
||||
'form': form,
|
||||
'messages_': messages})
|
||||
|
||||
def get_status(self):
|
||||
"""
|
||||
Return the current status of PageKite configuration by
|
||||
executing various actions.
|
||||
"""
|
||||
status = {}
|
||||
|
||||
# Check if PageKite is installed
|
||||
output = self._run(['get-installed'])
|
||||
cfg.log('Output - %s' % output)
|
||||
if output.split()[0] != 'installed':
|
||||
return None
|
||||
def get_status():
|
||||
"""
|
||||
Return the current status of PageKite configuration by
|
||||
executing various actions.
|
||||
"""
|
||||
status = {}
|
||||
|
||||
# PageKite service enabled/disabled
|
||||
output = self._run(['get-status'])
|
||||
status['enabled'] = (output.split()[0] == 'enabled')
|
||||
# Check if PageKite is installed
|
||||
output = _run(['get-installed'])
|
||||
cfg.log('Output - %s' % output)
|
||||
if output.split()[0] != 'installed':
|
||||
return None
|
||||
|
||||
# PageKite kite details
|
||||
output = self._run(['get-kite'])
|
||||
kite_details = output.split()
|
||||
status['kite_name'] = kite_details[0]
|
||||
status['kite_secret'] = kite_details[1]
|
||||
# PageKite service enabled/disabled
|
||||
output = _run(['get-status'])
|
||||
status['enabled'] = (output.split()[0] == 'enabled')
|
||||
|
||||
# Service status
|
||||
status['service'] = {}
|
||||
for service in ('http', 'ssh'):
|
||||
output = self._run(['get-service-status', service])
|
||||
status[service + '_enabled'] = (output.split()[0] == 'enabled')
|
||||
# PageKite kite details
|
||||
output = _run(['get-kite'])
|
||||
kite_details = output.split()
|
||||
status['kite_name'] = kite_details[0]
|
||||
status['kite_secret'] = kite_details[1]
|
||||
|
||||
return status
|
||||
# Service status
|
||||
status['service'] = {}
|
||||
for service in ('http', 'ssh'):
|
||||
output = _run(['get-service-status', service])
|
||||
status[service + '_enabled'] = (output.split()[0] == 'enabled')
|
||||
|
||||
def _apply_changes(self, old_status, new_status, messages):
|
||||
"""Apply the changes to PageKite configuration"""
|
||||
cfg.log.info('New status is - %s' % new_status)
|
||||
return status
|
||||
|
||||
if old_status != new_status:
|
||||
self._run(['stop'])
|
||||
|
||||
if old_status['enabled'] != new_status['enabled']:
|
||||
if new_status['enabled']:
|
||||
self._run(['set-status', 'enable'])
|
||||
messages.append(('success', _('PageKite enabled')))
|
||||
else:
|
||||
self._run(['set-status', 'disable'])
|
||||
messages.append(('success', _('PageKite disabled')))
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply the changes to PageKite configuration"""
|
||||
cfg.log.info('New status is - %s' % new_status)
|
||||
|
||||
if old_status['kite_name'] != new_status['kite_name'] or \
|
||||
old_status['kite_secret'] != new_status['kite_secret']:
|
||||
self._run(['set-kite', '--kite-name', new_status['kite_name'],
|
||||
'--kite-secret', new_status['kite_secret']])
|
||||
messages.append(('success', _('Kite details set')))
|
||||
if old_status != new_status:
|
||||
_run(['stop'])
|
||||
|
||||
for service in ['http', 'ssh']:
|
||||
if old_status[service + '_enabled'] != \
|
||||
new_status[service + '_enabled']:
|
||||
if new_status[service + '_enabled']:
|
||||
self._run(['set-service-status', service, 'enable'])
|
||||
messages.append(('success', _('Service enabled: {service}')
|
||||
.format(service=service)))
|
||||
else:
|
||||
self._run(['set-service-status', service, 'disable'])
|
||||
messages.append(('success',
|
||||
_('Service disabled: {service}')
|
||||
.format(service=service)))
|
||||
|
||||
if old_status != new_status:
|
||||
self._run(['start'])
|
||||
|
||||
@staticmethod
|
||||
def _run(arguments, superuser=True):
|
||||
"""Run an given command and raise exception if there was an error"""
|
||||
command = 'pagekite-configure'
|
||||
|
||||
cfg.log.info('Running command - %s, %s, %s' % (command, arguments,
|
||||
superuser))
|
||||
|
||||
if superuser:
|
||||
output, error = actions.superuser_run(command, arguments)
|
||||
if old_status['enabled'] != new_status['enabled']:
|
||||
if new_status['enabled']:
|
||||
_run(['set-status', 'enable'])
|
||||
messages.append(('success', _('PageKite enabled')))
|
||||
else:
|
||||
output, error = actions.run(command, arguments)
|
||||
_run(['set-status', 'disable'])
|
||||
messages.append(('success', _('PageKite disabled')))
|
||||
|
||||
if error:
|
||||
raise Exception('Error setting/getting PageKite confguration - %s'
|
||||
% error)
|
||||
if old_status['kite_name'] != new_status['kite_name'] or \
|
||||
old_status['kite_secret'] != new_status['kite_secret']:
|
||||
_run(['set-kite', '--kite-name', new_status['kite_name'],
|
||||
'--kite-secret', new_status['kite_secret']])
|
||||
messages.append(('success', _('Kite details set')))
|
||||
|
||||
return output
|
||||
for service in ['http', 'ssh']:
|
||||
if old_status[service + '_enabled'] != \
|
||||
new_status[service + '_enabled']:
|
||||
if new_status[service + '_enabled']:
|
||||
_run(['set-service-status', service, 'enable'])
|
||||
messages.append(('success', _('Service enabled: {service}')
|
||||
.format(service=service)))
|
||||
else:
|
||||
_run(['set-service-status', service, 'disable'])
|
||||
messages.append(('success',
|
||||
_('Service disabled: {service}')
|
||||
.format(service=service)))
|
||||
|
||||
if old_status != new_status:
|
||||
_run(['start'])
|
||||
|
||||
|
||||
def _run(arguments, superuser=True):
|
||||
"""Run an given command and raise exception if there was an error"""
|
||||
command = 'pagekite-configure'
|
||||
|
||||
cfg.log.info('Running command - %s, %s, %s' % (command, arguments,
|
||||
superuser))
|
||||
|
||||
if superuser:
|
||||
output, error = 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
|
||||
|
||||
29
modules/pagekite/urls.py
Normal file
29
modules/pagekite/urls.py
Normal file
@ -0,0 +1,29 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the PageKite module
|
||||
"""
|
||||
|
||||
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'),
|
||||
)
|
||||
@ -23,14 +23,15 @@ santiago_port = 52854
|
||||
# return True
|
||||
|
||||
class Santiago(PagePlugin):
|
||||
order = 90 # order of running init in PagePlugins
|
||||
def __init__(self, *args, **kwargs):
|
||||
order = 90 # order of running init in PagePlugins
|
||||
def __init__(self):
|
||||
super(Santiago, self).__init__()
|
||||
|
||||
self.register_page("santiago")
|
||||
self.santiago_address = self.get_santiago_address() #TODO: multiple santiago ports
|
||||
#set a listener on the santiago address
|
||||
|
||||
def get_santiago_address(self):
|
||||
def get_santiago_address(self):
|
||||
if 'santiago' in cfg.users['admin'] and 'address' in cfg.users['admin']['santiago']:
|
||||
return cfg.users['admin']['santiago']['address']
|
||||
else:
|
||||
@ -54,11 +55,11 @@ class Santiago(PagePlugin):
|
||||
print "Need to add these two lines to /etc/torrc:\n%s" % hidden_service_config
|
||||
return ""
|
||||
|
||||
def check_for_hidden_service(self):
|
||||
def check_for_hidden_service(self):
|
||||
pass
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self, *args, **kw):
|
||||
@cherrypy.expose
|
||||
def index(self, *args, **kw):
|
||||
|
||||
"""
|
||||
A request is a dict with some required keys:
|
||||
@ -111,12 +112,13 @@ class Santiago(PagePlugin):
|
||||
|
||||
## Plinth page to config santiago
|
||||
class santiago(PagePlugin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.menu = cfg.html_root.privacy.menu.add_item("Santiago", "icon-leaf", "/privacy/santiago", 10)
|
||||
self.register_page("privacy.santiago")
|
||||
def __init__(self):
|
||||
super(Santiago, self).__init__(self)
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
return "Santiago's config goes here."
|
||||
self.menu = cfg.html_root.privacy.menu.add_item("Santiago", "icon-leaf", "/privacy/santiago", 10)
|
||||
self.register_page("privacy.santiago")
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
return "Santiago's config goes here."
|
||||
|
||||
@ -20,6 +20,7 @@ Plinth module for system section page
|
||||
"""
|
||||
|
||||
from . import system
|
||||
from system import init
|
||||
|
||||
|
||||
__all__ = ['system']
|
||||
__all__ = ['system', 'init']
|
||||
|
||||
@ -1,21 +1,15 @@
|
||||
import cherrypy
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
from django.template.response import TemplateResponse
|
||||
|
||||
import cfg
|
||||
import util
|
||||
|
||||
sys_dir = "modules/installed/sys"
|
||||
|
||||
|
||||
class Sys(PagePlugin):
|
||||
order = 10
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("sys")
|
||||
self.menu = cfg.main_menu.add_item(_("System"), "icon-cog", "/sys", 100)
|
||||
self.menu.add_item(_("Users and Groups"), "icon-user", "/sys/users", 15)
|
||||
def init():
|
||||
"""Initialize the system module"""
|
||||
cfg.main_menu.add_item(_('System'), 'icon-cog', '/sys', 100)
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return util.render_template(template='system',
|
||||
title=_("System Configuration"))
|
||||
|
||||
def index(request):
|
||||
"""Serve the index page"""
|
||||
return TemplateResponse(request, 'system.html',
|
||||
{'title': _('System Configuration')})
|
||||
|
||||
28
modules/system/urls.py
Normal file
28
modules/system/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the System module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.system.system',
|
||||
url(r'^sys/$', 'index'),
|
||||
)
|
||||
@ -20,6 +20,8 @@ Plinth module to configure Tor
|
||||
"""
|
||||
|
||||
from . import tor
|
||||
from .tor import init
|
||||
|
||||
__all__ = ['tor', 'init']
|
||||
|
||||
__all__ = ['tor']
|
||||
DEPENDS = ['apps']
|
||||
|
||||
@ -19,36 +19,35 @@
|
||||
Plinth module for configuring Tor
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
from plugin_mount import PagePlugin
|
||||
from ..lib.auth import require
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
import util
|
||||
from ..lib.auth import login_required
|
||||
|
||||
|
||||
class tor(PagePlugin):
|
||||
order = 60 # order of running init in PagePlugins
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("apps.tor")
|
||||
cfg.html_root.apps.menu.add_item("Tor", "icon-eye-close", "/apps/tor",
|
||||
30)
|
||||
def init():
|
||||
"""Initialize the Tor module"""
|
||||
menu = cfg.main_menu.find('/apps')
|
||||
menu.add_item("Tor", "icon-eye-close", "/apps/tor", 30)
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
output, error = actions.superuser_run("tor-get-ports")
|
||||
port_info = output.split("\n")
|
||||
tor_ports = {}
|
||||
for line in port_info:
|
||||
try:
|
||||
(key, val) = line.split()
|
||||
tor_ports[key] = val
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
return util.render_template(template='tor',
|
||||
title=_('Tor Control Panel'),
|
||||
tor_ports=tor_ports)
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Service the index page"""
|
||||
output, error = actions.superuser_run("tor-get-ports")
|
||||
del error # Unused
|
||||
|
||||
port_info = output.split("\n")
|
||||
tor_ports = {}
|
||||
for line in port_info:
|
||||
try:
|
||||
(key, val) = line.split()
|
||||
tor_ports[key] = val
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
return TemplateResponse(request, 'tor.html',
|
||||
{'title': _('Tor Control Panel'),
|
||||
'tor_ports': tor_ports})
|
||||
|
||||
28
modules/tor/urls.py
Normal file
28
modules/tor/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Tor module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'modules.tor.tor',
|
||||
url(r'^apps/tor/$', 'index')
|
||||
)
|
||||
@ -20,6 +20,8 @@ Plinth module to manage users
|
||||
"""
|
||||
|
||||
from . import users
|
||||
from .users import init
|
||||
|
||||
__all__ = ['users', 'init']
|
||||
|
||||
__all__ = ['users']
|
||||
DEPENDS = ['system']
|
||||
|
||||
30
modules/users/urls.py
Normal file
30
modules/users/urls.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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Users module
|
||||
"""
|
||||
|
||||
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')
|
||||
)
|
||||
@ -1,34 +1,36 @@
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
from ..lib.auth import require, add_user
|
||||
from plugin_mount import PagePlugin
|
||||
|
||||
import cfg
|
||||
from ..lib.auth import add_user, login_required
|
||||
from model import User
|
||||
import util
|
||||
|
||||
|
||||
class Users(PagePlugin):
|
||||
order = 20 # order of running init in PagePlugins
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("sys.users")
|
||||
def init():
|
||||
"""Intialize the module"""
|
||||
menu = cfg.main_menu.find('/sys')
|
||||
menu.add_item(_('Users and Groups'), 'icon-user', '/sys/users', 15)
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index():
|
||||
"""Return a rendered users page"""
|
||||
menu = {'title': _('Users and Groups'),
|
||||
'items': [{'url': '/sys/users/add',
|
||||
'text': _('Add User')},
|
||||
{'url': '/sys/users/edit',
|
||||
'text': _('Edit Users')}]}
|
||||
sidebar_right = util.render_template(template='menu_block',
|
||||
menu=menu)
|
||||
return util.render_template(title="Manage Users and Groups",
|
||||
sidebar_right=sidebar_right)
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Return a rendered users page"""
|
||||
menu = {'title': _('Users and Groups'),
|
||||
'items': [{'url': '/sys/users/add',
|
||||
'text': _('Add User')},
|
||||
{'url': '/sys/users/edit',
|
||||
'text': _('Edit Users')}]}
|
||||
|
||||
sidebar_right = render_to_string('menu_block.html', {'menu': menu},
|
||||
RequestContext(request))
|
||||
|
||||
return TemplateResponse(request, 'login_nav.html',
|
||||
{'title': _('Manage Users and Groups'),
|
||||
'sidebar_right': sidebar_right})
|
||||
|
||||
|
||||
class UserAddForm(forms.Form): # pylint: disable-msg=W0232
|
||||
@ -48,48 +50,40 @@ and alphabet'),
|
||||
email = forms.EmailField(label=_('Email'), required=False)
|
||||
|
||||
|
||||
class UserAdd(PagePlugin):
|
||||
"""Add user page"""
|
||||
order = 30
|
||||
@login_required
|
||||
def add(request):
|
||||
"""Serve the form"""
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
|
||||
self.register_page('sys.users.add')
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the form"""
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = UserAddForm(kwargs, prefix='user')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._add_user(form.cleaned_data, messages)
|
||||
form = UserAddForm(prefix='user')
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
form = UserAddForm(request.POST, prefix='user')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_add_user(form.cleaned_data, messages)
|
||||
form = UserAddForm(prefix='user')
|
||||
else:
|
||||
form = UserAddForm(prefix='user')
|
||||
|
||||
return util.render_template(template='users_add', title=_('Add User'),
|
||||
form=form, messages=messages)
|
||||
return TemplateResponse(request, 'users_add.html',
|
||||
{'title': _('Add User'),
|
||||
'form': form,
|
||||
'messages_': messages})
|
||||
|
||||
@staticmethod
|
||||
def _add_user(data, messages):
|
||||
"""Add a user"""
|
||||
if cfg.users.exists(data['username']):
|
||||
messages.append(
|
||||
('error', _('User "{username}" already exists').format(
|
||||
username=data['username'])))
|
||||
return
|
||||
|
||||
add_user(data['username'], data['password'], data['full_name'],
|
||||
data['email'], False)
|
||||
def _add_user(data, messages):
|
||||
"""Add a user"""
|
||||
if cfg.users.exists(data['username']):
|
||||
messages.append(
|
||||
('success', _('User "{username}" added').format(
|
||||
('error', _('User "{username}" already exists').format(
|
||||
username=data['username'])))
|
||||
return
|
||||
|
||||
add_user(data['username'], data['password'], data['full_name'],
|
||||
data['email'], False)
|
||||
messages.append(
|
||||
('success', _('User "{username}" added').format(
|
||||
username=data['username'])))
|
||||
|
||||
|
||||
class UserEditForm(forms.Form): # pylint: disable-msg=W0232
|
||||
@ -108,64 +102,56 @@ class UserEditForm(forms.Form): # pylint: disable-msg=W0232
|
||||
self.fields['delete_user_' + user['username']] = field
|
||||
|
||||
|
||||
class UserEdit(PagePlugin):
|
||||
"""User edit page"""
|
||||
order = 35
|
||||
@login_required
|
||||
def edit(request):
|
||||
"""Serve the edit form"""
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
|
||||
self.register_page('sys.users.edit')
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the form"""
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = UserEditForm(kwargs, prefix='user')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(form.cleaned_data, messages)
|
||||
form = UserEditForm(prefix='user')
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
form = UserEditForm(request.POST, prefix='user')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_apply_edit_changes(request, form.cleaned_data, messages)
|
||||
form = UserEditForm(prefix='user')
|
||||
else:
|
||||
form = UserEditForm(prefix='user')
|
||||
|
||||
return util.render_template(template='users_edit',
|
||||
title=_('Edit or Delete User'),
|
||||
form=form, messages=messages)
|
||||
return TemplateResponse(request, 'users_edit.html',
|
||||
{'title': _('Edit or Delete User'),
|
||||
'form': form,
|
||||
'messages_': messages})
|
||||
|
||||
@staticmethod
|
||||
def _apply_changes(data, messages):
|
||||
"""Apply form changes"""
|
||||
for field, value in data.items():
|
||||
if not value:
|
||||
continue
|
||||
|
||||
if not field.startswith('delete_user_'):
|
||||
continue
|
||||
def _apply_edit_changes(request, data, messages):
|
||||
"""Apply form changes"""
|
||||
for field, value in data.items():
|
||||
if not value:
|
||||
continue
|
||||
|
||||
username = field.split('delete_user_')[1]
|
||||
if not field.startswith('delete_user_'):
|
||||
continue
|
||||
|
||||
cfg.log.info('%s asked to delete %s' %
|
||||
(cherrypy.session.get(cfg.session_key), username))
|
||||
username = field.split('delete_user_')[1]
|
||||
|
||||
if username == cfg.users.current(name=True):
|
||||
messages.append(
|
||||
('error',
|
||||
_('Can not delete current account - "%s"') % username))
|
||||
continue
|
||||
requesting_user = request.session.get(cfg.session_key, None)
|
||||
cfg.log.info('%s asked to delete %s' %
|
||||
(requesting_user, username))
|
||||
|
||||
if not cfg.users.exists(username):
|
||||
messages.append(('error',
|
||||
_('User "%s" does not exist') % username))
|
||||
continue
|
||||
if username == cfg.users.current(request=request, name=True):
|
||||
messages.append(
|
||||
('error',
|
||||
_('Can not delete current account - "%s"') % username))
|
||||
continue
|
||||
|
||||
try:
|
||||
cfg.users.remove(username)
|
||||
messages.append(('success', _('User "%s" deleted') % username))
|
||||
except IOError as exception:
|
||||
messages.append(('error', _('Error deleting "%s" - %s') %
|
||||
(username, exception)))
|
||||
if not cfg.users.exists(username):
|
||||
messages.append(('error',
|
||||
_('User "%s" does not exist') % username))
|
||||
continue
|
||||
|
||||
try:
|
||||
cfg.users.remove(username)
|
||||
messages.append(('success', _('User "%s" deleted') % username))
|
||||
except IOError as exception:
|
||||
messages.append(('error', _('Error deleting "%s" - %s') %
|
||||
(username, exception)))
|
||||
|
||||
@ -20,6 +20,8 @@ Plinth module to configure XMPP server
|
||||
"""
|
||||
|
||||
from . import xmpp
|
||||
from .xmpp import init
|
||||
|
||||
__all__ = ['xmpp', 'init']
|
||||
|
||||
__all__ = ['xmpp']
|
||||
DEPENDS = ['apps']
|
||||
|
||||
30
modules/xmpp/urls.py
Normal file
30
modules/xmpp/urls.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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the XMPP module
|
||||
"""
|
||||
|
||||
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')
|
||||
)
|
||||
@ -1,12 +1,13 @@
|
||||
import cherrypy
|
||||
from django import forms
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
from ..lib.auth import require
|
||||
from plugin_mount import PagePlugin
|
||||
import cfg
|
||||
|
||||
import actions
|
||||
import cfg
|
||||
from ..lib.auth import login_required
|
||||
import service
|
||||
import util
|
||||
|
||||
|
||||
SIDE_MENU = {'title': _('XMPP'),
|
||||
@ -16,38 +17,35 @@ SIDE_MENU = {'title': _('XMPP'),
|
||||
'text': 'Register XMPP Account'}]}
|
||||
|
||||
|
||||
class XMPP(PagePlugin):
|
||||
"""XMPP Page"""
|
||||
order = 60
|
||||
def init():
|
||||
"""Initialize the XMPP module"""
|
||||
menu = cfg.main_menu.find('/apps')
|
||||
menu.add_item('Chat', 'icon-comment', '/../jwchat', 20)
|
||||
menu.add_item('XMPP', 'icon-comment', '/apps/xmpp', 40)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page('apps.xmpp')
|
||||
cfg.html_root.apps.menu.add_item('XMPP', 'icon-comment',
|
||||
'/apps/xmpp', 40)
|
||||
service.Service(
|
||||
'xmpp-client', _('Chat Server - client connections'),
|
||||
is_external=True, enabled=True)
|
||||
service.Service(
|
||||
'xmpp-server', _('Chat Server - server connections'),
|
||||
is_external=True, enabled=True)
|
||||
service.Service(
|
||||
'xmpp-bosh', _('Chat Server - web interface'), is_external=True,
|
||||
enabled=True)
|
||||
|
||||
self.client_service = service.Service(
|
||||
'xmpp-client', _('Chat Server - client connections'),
|
||||
is_external=True, enabled=True)
|
||||
self.server_service = service.Service(
|
||||
'xmpp-server', _('Chat Server - server connections'),
|
||||
is_external=True, enabled=True)
|
||||
self.bosh_service = service.Service(
|
||||
'xmpp-bosh', _('Chat Server - web interface'), is_external=True,
|
||||
enabled=True)
|
||||
|
||||
@staticmethod
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(**kwargs):
|
||||
"""Serve XMPP page"""
|
||||
del kwargs # Unused
|
||||
main = "<p>XMPP Server Accounts and Configuration</p>"
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve XMPP page"""
|
||||
main = "<p>XMPP Server Accounts and Configuration</p>"
|
||||
|
||||
sidebar_right = util.render_template(template='menu_block',
|
||||
menu=SIDE_MENU)
|
||||
return util.render_template(title="XMPP Server", main=main,
|
||||
sidebar_right=sidebar_right)
|
||||
sidebar_right = render_to_string('menu_block.html', {'menu': SIDE_MENU},
|
||||
RequestContext(request))
|
||||
|
||||
return TemplateResponse(request, 'login_nav.html',
|
||||
{'title': _('XMPP Server'),
|
||||
'main': main,
|
||||
'sidebar_right': sidebar_right})
|
||||
|
||||
|
||||
class ConfigureForm(forms.Form): # pylint: disable-msg=W0232
|
||||
@ -57,83 +55,65 @@ class ConfigureForm(forms.Form): # pylint: disable-msg=W0232
|
||||
help_text=_('When enabled, anyone who can reach this server will be \
|
||||
allowed to register an account through an XMPP client'))
|
||||
|
||||
# XXX: Only present due to issue with submitting empty form
|
||||
dummy = forms.CharField(label='Dummy', initial='dummy',
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
@login_required
|
||||
def configure(request):
|
||||
"""Serve the configuration form"""
|
||||
status = get_status()
|
||||
|
||||
class Configure(PagePlugin):
|
||||
"""Configuration page"""
|
||||
order = 65
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("apps.xmpp.configure")
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the configuration form"""
|
||||
status = self.get_status()
|
||||
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = ConfigureForm(kwargs, prefix='xmpp')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._apply_changes(status, form.cleaned_data, messages)
|
||||
status = self.get_status()
|
||||
form = ConfigureForm(initial=status, prefix='xmpp')
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
form = ConfigureForm(request.POST, prefix='xmpp')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_apply_changes(status, form.cleaned_data, messages)
|
||||
status = get_status()
|
||||
form = ConfigureForm(initial=status, prefix='xmpp')
|
||||
else:
|
||||
form = ConfigureForm(initial=status, prefix='xmpp')
|
||||
|
||||
sidebar_right = util.render_template(template='menu_block',
|
||||
menu=SIDE_MENU)
|
||||
return util.render_template(template='xmpp_configure',
|
||||
title=_('Configure XMPP Server'),
|
||||
form=form, messages=messages,
|
||||
sidebar_right=sidebar_right)
|
||||
sidebar_right = render_to_string('menu_block.html', {'menu': SIDE_MENU},
|
||||
RequestContext(request))
|
||||
|
||||
@staticmethod
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
output, error = actions.run('xmpp-setup', 'status')
|
||||
if error:
|
||||
raise Exception('Error getting status: %s' % error)
|
||||
return TemplateResponse(request, 'xmpp_configure.html',
|
||||
{'title': _('Configure XMPP Server'),
|
||||
'form': form,
|
||||
'messages_': messages,
|
||||
'sidebar_right': sidebar_right})
|
||||
|
||||
return {'inband_enabled': 'inband_enable' in output.split()}
|
||||
|
||||
@staticmethod
|
||||
def sidebar_right(**kwargs):
|
||||
"""Return rendered string for sidebar on the right"""
|
||||
del kwargs # Unused
|
||||
def get_status():
|
||||
"""Return the current status"""
|
||||
output, error = actions.run('xmpp-setup', 'status')
|
||||
if error:
|
||||
raise Exception('Error getting status: %s' % error)
|
||||
|
||||
return util.render_template(template='menu_block', menu=SIDE_MENU)
|
||||
return {'inband_enabled': 'inband_enable' in output.split()}
|
||||
|
||||
@staticmethod
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply the form changes"""
|
||||
cfg.log.info('Status - %s, %s' % (old_status, new_status))
|
||||
|
||||
if old_status['inband_enabled'] == new_status['inband_enabled']:
|
||||
messages.append(('info', _('Setting unchanged')))
|
||||
return
|
||||
def _apply_changes(old_status, new_status, messages):
|
||||
"""Apply the form changes"""
|
||||
cfg.log.info('Status - %s, %s' % (old_status, new_status))
|
||||
|
||||
if new_status['inband_enabled']:
|
||||
messages.append(('success', _('Inband registration enabled')))
|
||||
option = 'inband_enable'
|
||||
else:
|
||||
messages.append(('success', _('Inband registration disabled')))
|
||||
option = 'noinband_enable'
|
||||
if old_status['inband_enabled'] == new_status['inband_enabled']:
|
||||
messages.append(('info', _('Setting unchanged')))
|
||||
return
|
||||
|
||||
cfg.log.info('Option - %s' % option)
|
||||
if new_status['inband_enabled']:
|
||||
messages.append(('success', _('Inband registration enabled')))
|
||||
option = 'inband_enable'
|
||||
else:
|
||||
messages.append(('success', _('Inband registration disabled')))
|
||||
option = 'noinband_enable'
|
||||
|
||||
_output, error = actions.superuser_run('xmpp-setup', [option])
|
||||
del _output
|
||||
if error:
|
||||
raise Exception('Error running command - %s' % error)
|
||||
cfg.log.info('Option - %s' % option)
|
||||
|
||||
_output, error = actions.superuser_run('xmpp-setup', [option])
|
||||
del _output # Unused
|
||||
if error:
|
||||
raise Exception('Error running command - %s' % error)
|
||||
|
||||
|
||||
class RegisterForm(forms.Form): # pylint: disable-msg=W0232
|
||||
@ -144,50 +124,43 @@ class RegisterForm(forms.Form): # pylint: disable-msg=W0232
|
||||
label=_('Password'), widget=forms.PasswordInput())
|
||||
|
||||
|
||||
class Register(PagePlugin):
|
||||
"""User registration page"""
|
||||
order = 65
|
||||
@login_required
|
||||
def register(request):
|
||||
"""Serve the registration form"""
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page('apps.xmpp.register')
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self, **kwargs):
|
||||
"""Serve the registration form"""
|
||||
form = None
|
||||
messages = []
|
||||
|
||||
if kwargs:
|
||||
form = RegisterForm(kwargs, prefix='xmpp')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
self._register_user(form.cleaned_data, messages)
|
||||
form = RegisterForm(prefix='xmpp')
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
form = RegisterForm(request.POST, prefix='xmpp')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_register_user(form.cleaned_data, messages)
|
||||
form = RegisterForm(prefix='xmpp')
|
||||
else:
|
||||
form = RegisterForm(prefix='xmpp')
|
||||
|
||||
sidebar_right = util.render_template(template='menu_block',
|
||||
menu=SIDE_MENU)
|
||||
return util.render_template(template='xmpp_register',
|
||||
title=_('Register XMPP Account'),
|
||||
form=form, messages=messages,
|
||||
sidebar_right=sidebar_right)
|
||||
sidebar_right = render_to_string('menu_block.html', {'menu': SIDE_MENU},
|
||||
RequestContext(request))
|
||||
|
||||
@staticmethod
|
||||
def _register_user(data, messages):
|
||||
"""Register a new XMPP user"""
|
||||
output, error = actions.superuser_run(
|
||||
'xmpp-register', [data['username'], data['password']])
|
||||
if error:
|
||||
raise Exception('Error registering user - %s' % error)
|
||||
return TemplateResponse(request, 'xmpp_register.html',
|
||||
{'title': _('Register XMPP Account'),
|
||||
'form': form,
|
||||
'messages_': messages,
|
||||
'sidebar_right': sidebar_right})
|
||||
|
||||
if 'successfully registered' in output:
|
||||
messages.append(('success',
|
||||
_('Registered account for %s' %
|
||||
data['username'])))
|
||||
else:
|
||||
messages.append(('error',
|
||||
_('Failed to register account for %s: %s') %
|
||||
(data['username'], output)))
|
||||
|
||||
def _register_user(data, messages):
|
||||
"""Register a new XMPP user"""
|
||||
output, error = actions.superuser_run(
|
||||
'xmpp-register', [data['username'], data['password']])
|
||||
if error:
|
||||
raise Exception('Error registering user - %s' % error)
|
||||
|
||||
if 'successfully registered' in output:
|
||||
messages.append(('success',
|
||||
_('Registered account for %s' %
|
||||
data['username'])))
|
||||
else:
|
||||
messages.append(('error',
|
||||
_('Failed to register account for %s: %s') %
|
||||
(data['username'], output)))
|
||||
|
||||
209
plinth.py
209
plinth.py
@ -1,28 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os, stat, sys, argparse
|
||||
from gettext import gettext as _
|
||||
import os, sys, argparse
|
||||
import cfg
|
||||
import django.conf
|
||||
import importlib
|
||||
import django.core.wsgi
|
||||
if not os.path.join(cfg.file_root, "vendor") in sys.path:
|
||||
sys.path.append(os.path.join(cfg.file_root, "vendor"))
|
||||
import re
|
||||
|
||||
import cherrypy
|
||||
from cherrypy import _cpserver
|
||||
from cherrypy.process.plugins import Daemonizer
|
||||
Daemonizer(cherrypy.engine).subscribe()
|
||||
|
||||
import module_loader
|
||||
import plugin_mount
|
||||
import service
|
||||
import util as u
|
||||
|
||||
from logger import Logger
|
||||
#from modules.auth import AuthController, require, member_of, name_is
|
||||
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
import socket
|
||||
|
||||
__version__ = "0.2.14"
|
||||
__author__ = "James Vasile"
|
||||
@ -32,79 +26,6 @@ __maintainer__ = "James Vasile"
|
||||
__email__ = "james@jamesvasile.com"
|
||||
__status__ = "Development"
|
||||
|
||||
import urlparse
|
||||
|
||||
def error_page(status, dynamic_msg, stock_msg):
|
||||
return u.render_template(template="err", title=status, main="<p>%s</p>%s" % (dynamic_msg, stock_msg))
|
||||
|
||||
def error_page_404(status, message, traceback, version):
|
||||
return error_page(status, message, """<p>If you believe this
|
||||
missing page should exist, please file a bug with either the Plinth
|
||||
project (<a href="https://github.com/NickDaly/Plinth/issues">it has
|
||||
an issue tracker</a>) or the people responsible for the module you
|
||||
are trying to access.</p>
|
||||
|
||||
<p>Sorry for the mistake.</p>
|
||||
""")
|
||||
|
||||
def error_page_500(status, message, traceback, version):
|
||||
cfg.log.error("500 Internal Server Error. Trackback is above.")
|
||||
more="""<p>This is an internal error and not something you caused
|
||||
or can fix. Please report the error on the <a
|
||||
href="https://github.com/jvasile/Plinth/issues">bug tracker</a> so
|
||||
we can fix it.</p>"""
|
||||
return error_page(status, message, "<p>%s</p><pre>%s</pre>" % (more, "\n".join(traceback.split("\n"))))
|
||||
|
||||
class Root(plugin_mount.PagePlugin):
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
## TODO: firstboot hijacking root should probably be in the firstboot module with a hook in plinth.py
|
||||
with sqlite_db(cfg.store_file, table="firstboot") as db:
|
||||
if not 'state' in db:
|
||||
# if we created a new user db, make sure it can't be read by everyone
|
||||
userdb_fname = '{}.sqlite3'.format(cfg.user_db)
|
||||
os.chmod(userdb_fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||
# cherrypy.InternalRedirect throws a 301, causing the
|
||||
# browser to cache the redirect, preventing the user from
|
||||
# navigating to /plinth until the browser is restarted.
|
||||
raise cherrypy.HTTPRedirect('firstboot', 307)
|
||||
elif db['state'] < 5:
|
||||
cfg.log("First Boot state = %d" % db['state'])
|
||||
raise cherrypy.InternalRedirect('firstboot/state%d' % db['state'])
|
||||
if cherrypy.session.get(cfg.session_key, None):
|
||||
raise cherrypy.InternalRedirect('apps')
|
||||
else:
|
||||
raise cherrypy.InternalRedirect('help/about')
|
||||
|
||||
|
||||
def load_modules():
|
||||
"""
|
||||
Read names of enabled modules in modules/enabled directory and
|
||||
import them from modules directory.
|
||||
"""
|
||||
for name in os.listdir('modules/enabled'):
|
||||
cfg.log.info('Importing modules/%s' % name)
|
||||
try:
|
||||
importlib.import_module('modules.{module}'.format(module=name))
|
||||
except ImportError as exception:
|
||||
cfg.log.error(
|
||||
'Could not import modules/{module}: {exception}'
|
||||
.format(module=name, exception=exception))
|
||||
|
||||
|
||||
def get_template_directories():
|
||||
"""Return the list of template directories"""
|
||||
directory = os.path.dirname(os.path.abspath(__file__))
|
||||
core_directory = os.path.join(directory, 'templates')
|
||||
|
||||
directories = set((core_directory,))
|
||||
for name in os.listdir('modules/enabled'):
|
||||
directories.add(os.path.join('modules', name, 'templates'))
|
||||
|
||||
cfg.log.info('Template directories - %s' % directories)
|
||||
|
||||
return directories
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(description='Plinth web interface for the FreedomBox.')
|
||||
@ -144,7 +65,13 @@ def set_config(args, element, default):
|
||||
# it wasn't in the config file, but set the default anyway.
|
||||
setattr(cfg, element, default)
|
||||
|
||||
def setup():
|
||||
|
||||
def setup_logging():
|
||||
"""Setup logging framework"""
|
||||
cfg.log = Logger()
|
||||
|
||||
|
||||
def setup_configuration():
|
||||
cfg = parse_arguments()
|
||||
|
||||
try:
|
||||
@ -155,55 +82,91 @@ def setup():
|
||||
pass
|
||||
|
||||
os.chdir(cfg.python_root)
|
||||
cherrypy.config.update({'error_page.404': error_page_404})
|
||||
cherrypy.config.update({'error_page.500': error_page_500})
|
||||
cfg.log = Logger()
|
||||
load_modules()
|
||||
cfg.html_root = Root()
|
||||
|
||||
cfg.users = plugin_mount.UserStoreModule.get_plugins()[0]
|
||||
cfg.page_plugins = plugin_mount.PagePlugin.get_plugins()
|
||||
cfg.log("Loaded %d page plugins" % len(cfg.page_plugins))
|
||||
|
||||
# Add an extra server
|
||||
server = _cpserver.Server()
|
||||
server.socket_host = '127.0.0.1'
|
||||
server.socket_port = 52854
|
||||
server.subscribe()
|
||||
def setup_server():
|
||||
"""Setup CherryPy server"""
|
||||
# Add an extra server
|
||||
server = _cpserver.Server()
|
||||
server.socket_host = '127.0.0.1'
|
||||
server.socket_port = 52854
|
||||
server.subscribe()
|
||||
|
||||
# Configure default server
|
||||
cherrypy.config.update(
|
||||
{'server.socket_host': cfg.host,
|
||||
'server.socket_port': cfg.port,
|
||||
'server.thread_pool':10,
|
||||
'tools.staticdir.root': cfg.file_root,
|
||||
'tools.sessions.on':True,
|
||||
'tools.auth.on':True,
|
||||
'tools.sessions.storage_type':"file",
|
||||
'tools.sessions.timeout':90,
|
||||
'tools.sessions.storage_path':"%s/cherrypy_sessions" % cfg.data_dir,})
|
||||
# Configure default server
|
||||
cherrypy.config.update(
|
||||
{'server.socket_host': cfg.host,
|
||||
'server.socket_port': cfg.port,
|
||||
'server.thread_pool': 10})
|
||||
|
||||
application = django.core.wsgi.get_wsgi_application()
|
||||
cherrypy.tree.graft(application, cfg.server_dir)
|
||||
|
||||
config = {
|
||||
'/': {'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.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,
|
||||
'username': request.session.get(cfg.session_key, None),
|
||||
'active_menu_urls': active_menu_urls
|
||||
}
|
||||
|
||||
|
||||
def configure_django():
|
||||
"""Setup Django configuration in the absense of .settings file"""
|
||||
context_processors = [
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.core.context_processors.debug',
|
||||
'django.core.context_processors.i18n',
|
||||
'django.core.context_processors.media',
|
||||
'django.core.context_processors.static',
|
||||
'django.core.context_processors.tz',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'plinth.context_processor']
|
||||
|
||||
template_directories = module_loader.get_template_directories()
|
||||
sessions_directory = os.path.join(cfg.data_dir, 'sessions')
|
||||
django.conf.settings.configure(
|
||||
DEBUG=False,
|
||||
ALLOWED_HOSTS=['127.0.0.1', 'localhost'],
|
||||
TEMPLATE_DIRS=template_directories,
|
||||
INSTALLED_APPS=['bootstrapform'],
|
||||
ROOT_URLCONF='urls',
|
||||
SESSION_ENGINE='django.contrib.sessions.backends.file',
|
||||
SESSION_FILE_PATH=sessions_directory,
|
||||
STATIC_URL=cfg.server_dir + '/static/',
|
||||
TEMPLATE_CONTEXT_PROCESSORS=context_processors)
|
||||
|
||||
config = {
|
||||
'/': {'tools.staticdir.root': '%s/static' % cfg.file_root,
|
||||
'tools.proxy.on': True,},
|
||||
'/static': {'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': "."},
|
||||
'/favicon.ico':{'tools.staticfile.on': True,
|
||||
'tools.staticfile.filename':
|
||||
"%s/static/theme/favicon.ico" % cfg.file_root}}
|
||||
cherrypy.tree.mount(cfg.html_root, cfg.server_dir, config=config)
|
||||
cherrypy.engine.signal_handler.subscribe()
|
||||
|
||||
def main():
|
||||
# Initialize basic services
|
||||
"""Intialize and start the application"""
|
||||
setup_logging()
|
||||
|
||||
service.init()
|
||||
|
||||
setup()
|
||||
setup_configuration()
|
||||
|
||||
# Configure Django
|
||||
template_directories = get_template_directories()
|
||||
django.conf.settings.configure(TEMPLATE_DIRS=template_directories,
|
||||
INSTALLED_APPS=['bootstrapform'])
|
||||
configure_django()
|
||||
|
||||
module_loader.load_modules()
|
||||
|
||||
cfg.users = plugin_mount.UserStoreModule.get_plugins()[0]
|
||||
|
||||
setup_server()
|
||||
|
||||
cherrypy.engine.start()
|
||||
cherrypy.engine.block()
|
||||
|
||||
@ -18,10 +18,14 @@ class PluginMount(type):
|
||||
def get_plugins(cls, *args, **kwargs):
|
||||
return cls.init_plugins(*args, **kwargs)
|
||||
|
||||
class MultiplePluginViolation:
|
||||
|
||||
class MultiplePluginViolation(Exception):
|
||||
"""Multiple plugins found for a type where only one is expected"""
|
||||
pass
|
||||
|
||||
|
||||
class PluginMountSingular(PluginMount):
|
||||
"""Plugin mounter that allows only one plugin of this meta type"""
|
||||
def __init__(cls, name, bases, attrs):
|
||||
if not hasattr(cls, 'plugins'):
|
||||
cls.plugins = []
|
||||
@ -29,42 +33,9 @@ class PluginMountSingular(PluginMount):
|
||||
if len(cls.plugins) > 0:
|
||||
raise MultiplePluginViolation
|
||||
cls.plugins.append(cls)
|
||||
|
||||
|
||||
def _setattr_deep(obj, path, value):
|
||||
"""If path is 'x.y.z' or ['x', 'y', 'z'] then perform obj.x.y.z = value"""
|
||||
if isinstance(path, basestring):
|
||||
path = path.split('.')
|
||||
|
||||
for part in path[:-1]:
|
||||
obj = getattr(obj, part)
|
||||
|
||||
setattr(obj, path[-1], value)
|
||||
|
||||
|
||||
class PagePlugin:
|
||||
"""
|
||||
Mount point for page plugins. Page plugins provide display pages
|
||||
in the interface (one menu item, for example).
|
||||
|
||||
order - How early should this plugin be loaded? Lower order is earlier.
|
||||
"""
|
||||
|
||||
order = 50
|
||||
|
||||
__metaclass__ = PluginMount
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""If cfg.html_root is none, then this is the html_root."""
|
||||
if not cfg.html_root:
|
||||
cfg.log('Setting html root to %s' % self.__class__.__name__)
|
||||
cfg.html_root = self
|
||||
|
||||
def register_page(self, url):
|
||||
cfg.log.info("Registering page: %s" % url)
|
||||
_setattr_deep(cfg.html_root, url, self)
|
||||
|
||||
|
||||
class UserStoreModule:
|
||||
class UserStoreModule(object):
|
||||
"""
|
||||
Mount Point for plugins that will manage the user backend storage,
|
||||
where we keep a hash for each user.
|
||||
@ -79,5 +50,5 @@ class UserStoreModule:
|
||||
compatibility with third party software. A future version of
|
||||
Plinth is likely to require LDAP.
|
||||
"""
|
||||
__metaclass__ = PluginMountSingular # singular because we can only use one user store at a time
|
||||
|
||||
# Singular because we can only use one user store at a time
|
||||
__metaclass__ = PluginMountSingular
|
||||
|
||||
36
templates/404.html
Normal file
36
templates/404.html
Normal file
@ -0,0 +1,36 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block title_block %}
|
||||
404
|
||||
{% endblock %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>Requested page {{ request_path }} was not found.</p>
|
||||
|
||||
<p>If you believe this missing page should exist, please file a bug with either
|
||||
the Plinth project (<a href="https://github.com/NickDaly/Plinth/issues">it
|
||||
has an issue tracker</a>) or the people responsible for the module you are
|
||||
trying to access.</p>
|
||||
|
||||
<p>Sorry for the mistake.</p>
|
||||
|
||||
{% endblock %}
|
||||
32
templates/500.html
Normal file
32
templates/500.html
Normal file
@ -0,0 +1,32 @@
|
||||
{% extends 'login_nav.html' %}
|
||||
{% comment %}
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% block title_block %}
|
||||
500
|
||||
{% endblock %}
|
||||
|
||||
{% block main_block %}
|
||||
|
||||
<p>This is an internal error and not something you caused or can fix. Please
|
||||
report the error on
|
||||
the <a href="https://github.com/NickDaly/Plinth/issues">bug tracker</a> so we
|
||||
can fix it.</p>
|
||||
|
||||
{% endblock %}
|
||||
@ -1,3 +1,4 @@
|
||||
{% load static %}
|
||||
<!doctype html>
|
||||
<!--[if lt IE 7 ]> <html class="ie ie6 no-js" lang="en"> <![endif]-->
|
||||
<!--[if IE 7 ]> <html class="ie ie7 no-js" lang="en"> <![endif]-->
|
||||
@ -24,25 +25,25 @@
|
||||
<meta name="msnbot" content="noindex, nofollow, noarchive, noodp" />
|
||||
<meta name="slurp" content="noindex, nofollow, noarchive, noodp, noydir" />
|
||||
|
||||
<meta name="title" content="{% if title %} {{ title }} {% else %} FreedomBox Dashboard {% endif %}" />
|
||||
<meta name="title" content="{% if title %} {{ title }} {% else %} FreedomBox {% endif %}" />
|
||||
<meta name="description" content="Plinth administrative interface for the FreedomBox" />
|
||||
<title>{% if title %} {{ title }} {% else %} FreedomBox Dashboard {% endif %}</title>
|
||||
<title>{% if title %} {{ title }} {% else %} FreedomBox {% endif %}</title>
|
||||
|
||||
<!-- This is the traditional favicon. Size: 16x16 or 32x32, transparency is OK -->
|
||||
<link rel="shortcut icon" href="{{ basehref }}/static/theme/img/favicon.ico" />
|
||||
<link rel="shortcut icon" href="{% static 'theme/img/favicon.ico' %}"/>
|
||||
|
||||
<!-- The is the icon for iOS's Web Clip. Size: 57x57 for older iPhones, 72x72 for iPads, 114x114 for iPhone4
|
||||
- To prevent iOS from applying its styles to the icon name it thusly: apple-touch-icon-precomposed.png
|
||||
- Transparency is not recommended (iOS will put a black BG behind the icon) -->
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="{{ basehref }}/static/theme/img/apple-touch-icon-57px-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ basehref }}/static/theme/img/apple-touch-icon-72px-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="{{ basehref }}/static/theme/img/apple-touch-icon-114px-precomposed.png" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="{% static 'theme/img/apple-touch-icon-57px-precomposed.png' %}"/>
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{% static 'theme/img/apple-touch-icon-72px-precomposed.png' %}"/>
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="{% static 'theme/img/apple-touch-icon-114px-precomposed.png' %}"/>
|
||||
|
||||
<!-- Bootstrap base CSS -->
|
||||
<link rel="stylesheet" href="{{ basehref }}/static/theme/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="{% static 'theme/css/bootstrap.min.css' %}"/>
|
||||
<!-- Bootstrap responsive CSS -->
|
||||
<link rel="stylesheet" href="{{ basehref }}/static/theme/css/bootstrap-responsive.min.css" />
|
||||
<link rel="stylesheet" href="{{ basehref }}/static/theme/css/plinth.css" />
|
||||
<link rel="stylesheet" href="{% static 'theme/css/bootstrap-responsive.min.css' %}"/>
|
||||
<link rel="stylesheet" href="{% static 'theme/css/plinth.css' %}"/>
|
||||
<!-- CSS from previous Plinth template, not sure what to keep yet -->
|
||||
{{ css|safe }}
|
||||
<!-- End Plinth CSS -->
|
||||
@ -57,7 +58,10 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</a>
|
||||
<a href="{{ basehref }}/" class="logo-top"><img src="{{ basehref }}/static/theme/img/freedombox-logo-32px.png" alt="FreedomBox" /></a><a class="brand" href="{{ basehref }}/">FreedomBox Dashboard</a>
|
||||
<a href="{{ basehref }}/" class="logo-top">
|
||||
<img src="{% static 'theme/img/freedombox-logo-32px.png' %}" alt="FreedomBox" />
|
||||
</a>
|
||||
<a class="brand" href="{{ basehref }}/">FreedomBox</a>
|
||||
{% block add_nav_and_login %}
|
||||
{% endblock %}
|
||||
</div><!--/.nav-collapse -->
|
||||
@ -111,10 +115,6 @@
|
||||
free software offered to you under the terms of
|
||||
the <a href="http://www.gnu.org/licenses/agpl.html" target="_blank">GNU Affero General Public
|
||||
License</a>, Version 3 or later. This Plinth theme was built by <a href="http://seandiggity.com" target="_blank">Sean "Diggity" O'Brien</a>.
|
||||
</p>
|
||||
<p>Current page: {{ current_url }}</p>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
{% endblock %}</p>
|
||||
</footer>
|
||||
@ -123,11 +123,11 @@
|
||||
|
||||
<!-- JavaScript <script> tags are placed at the end of the document to speed up initial page loads-->
|
||||
<!-- Local link to system Modernizr (includes HTML5 Shiv) -->
|
||||
<script type="text/javascript" src="{{ basehref }}/static/theme/js/libs/modernizr.min.js"></script>
|
||||
<script type="text/javascript" src="{% static 'theme/js/libs/modernizr.min.js' %}"></script>
|
||||
<!-- Local link to system jQuery -->
|
||||
<script type="text/javascript" src="{{ basehref }}/static/theme/js/libs/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="{% static 'theme/js/libs/jquery.min.js' %}"></script>
|
||||
<!-- Local link to system Bootstrap JS -->
|
||||
<script type="text/javascript" src="{{ basehref }}/static/theme/js/libs/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="{% static 'theme/js/libs/bootstrap.min.js' %}"></script>
|
||||
|
||||
{% block js_block %}
|
||||
{{ js|safe }}
|
||||
|
||||
@ -5,8 +5,13 @@
|
||||
|
||||
<ul class="nav">
|
||||
{% for item in main_menu.items %}
|
||||
<li class="{{ item.active_p|yesno:"active," }}">
|
||||
<a href="{{ item.url }}" class="{{ item.active_p|yesno:"active," }}">
|
||||
{% if item.url in active_menu_urls %}
|
||||
<li class="active">
|
||||
<a href="{{ item.url }}" class="active">
|
||||
{% else %}
|
||||
<li>
|
||||
<a href="{{ item.url }}">
|
||||
{% endif %}
|
||||
<span class="{{ item.icon }} icon-white nav-icon"></span>
|
||||
{{ item.label }}
|
||||
</a>
|
||||
|
||||
@ -21,16 +21,21 @@
|
||||
<li class="nav-header">Menu</li>
|
||||
{% for item in menu.items %}
|
||||
|
||||
<li class="{{ item.active_p|yesno:"active," }}">
|
||||
<a href="{{ item.url }}" class="{{ item.active_p|yesno:"active," }}">
|
||||
<span class="{{ item.icon }} {{ item.active_p|yesno:"icon-white," }}
|
||||
sidenav-icon"></span>
|
||||
{{ item.label }}
|
||||
</a>
|
||||
{% if item.items %}
|
||||
{% include "menu.html" with menu=item %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% if item.url in active_menu_urls %}
|
||||
<li class="active">
|
||||
<a href="{{ item.url }}" class="active">
|
||||
<span class="{{ item.icon }} icon-white sidenav-icon"></span>
|
||||
{% else %}
|
||||
<li>
|
||||
<a href="{{ item.url }}">
|
||||
<span class="{{ item.icon }} sidenav-icon"></span>
|
||||
{% endif %}
|
||||
{{ item.label }}
|
||||
</a>
|
||||
{% if item.items %}
|
||||
{% include "menu.html" with menu=item %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% for severity, message in messages %}
|
||||
{% for severity, message in messages_ %}
|
||||
<div class='alert alert-{{ severity }} alert-dismissable'>
|
||||
<a class="close" data-dismiss="alert">×</a>
|
||||
{{ message }}
|
||||
|
||||
@ -5,7 +5,6 @@ from actions import superuser_run, run
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
class TestPrivileged(unittest.TestCase):
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- mode: python; mode: auto-fill; fill-column: 80 -*-
|
||||
|
||||
import user_store, auth
|
||||
import auth
|
||||
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 Auth(unittest.TestCase):
|
||||
"""Test check_credentials function of auth to confirm it works as expected"""
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- mode: python; mode: auto-fill; fill-column: 80 -*-
|
||||
|
||||
import user_store
|
||||
from logger import Logger
|
||||
import cfg
|
||||
import unittest
|
||||
@ -13,6 +12,7 @@ cfg.log = Logger()
|
||||
|
||||
cherrypy.log.access_file = None
|
||||
|
||||
|
||||
class UserStore(unittest.TestCase):
|
||||
"""Test each function of user_store to confirm they work as expected"""
|
||||
|
||||
@ -83,4 +83,4 @@ class UserStore(unittest.TestCase):
|
||||
return user
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
||||
28
urls.py
Normal file
28
urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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 URLconf file containing all urls
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'views',
|
||||
url(r'^$', 'index')
|
||||
)
|
||||
27
util.py
27
util.py
@ -1,10 +1,8 @@
|
||||
import os
|
||||
import sys
|
||||
import cherrypy
|
||||
import cfg
|
||||
import sqlite3
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from filedict import FileDict
|
||||
|
||||
|
||||
@ -55,31 +53,6 @@ def find_keys(dic, val):
|
||||
"""return the key of dictionary dic given the value"""
|
||||
return [k for k, v in dic.iteritems() if v == val]
|
||||
|
||||
class Message():
|
||||
def __init__(self, msg=''):
|
||||
self.text = msg
|
||||
def add(self, text):
|
||||
self.text += "<br />%s" % text
|
||||
|
||||
|
||||
def render_template(template='login_nav', **kwargs):
|
||||
for key in ['sidebar_left', 'sidebar_right', 'main', 'js', 'nav', 'css',
|
||||
'title', 'basehref']:
|
||||
if not key in kwargs:
|
||||
kwargs[key] = ''
|
||||
|
||||
if kwargs['basehref'] == '':
|
||||
kwargs['basehref'] = cfg.server_dir
|
||||
|
||||
kwargs['template'] = template
|
||||
kwargs['main_menu'] = cfg.main_menu
|
||||
kwargs['submenu'] = cfg.main_menu.active_item()
|
||||
kwargs['current_url'] = cherrypy.url()
|
||||
kwargs['username'] = cherrypy.session.get(cfg.session_key)
|
||||
kwargs['cfg'] = cfg
|
||||
|
||||
return render_to_string(template + '.html', kwargs)
|
||||
|
||||
|
||||
def filedict_con(filespec=None, table='dict'):
|
||||
"""TODO: better error handling in filedict_con"""
|
||||
|
||||
52
views.py
Normal file
52
views.py
Normal file
@ -0,0 +1,52 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Main Plinth views
|
||||
"""
|
||||
|
||||
from django.http.response import HttpResponseRedirect
|
||||
import os
|
||||
import stat
|
||||
|
||||
import cfg
|
||||
from withsqlite.withsqlite import sqlite_db
|
||||
|
||||
|
||||
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:
|
||||
# If we created a new user db, make sure it can't be read by
|
||||
# everyone
|
||||
userdb_fname = '{}.sqlite3'.format(cfg.user_db)
|
||||
os.chmod(userdb_fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||
# 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:
|
||||
cfg.log('First boot state = %d' % database['state'])
|
||||
return HttpResponseRedirect(
|
||||
cfg.server_dir + '/firstboot/state%d' % database['state'])
|
||||
|
||||
if request.session.get(cfg.session_key, None):
|
||||
return HttpResponseRedirect(cfg.server_dir + '/apps')
|
||||
|
||||
return HttpResponseRedirect(cfg.server_dir + '/help/about')
|
||||
Loading…
x
Reference in New Issue
Block a user