Merge: Sunil's exec commits.

This commit is contained in:
Nick Daly 2014-04-23 19:21:52 -05:00
commit 3c2a5be9af
6 changed files with 173 additions and 119 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
current-*.tar.gz current-*.tar.gz
*.pyc *.pyc
*.py.bak
*.tiny.css *.tiny.css
data/*.log data/*.log
data/cherrypy_sessions data/cherrypy_sessions

View File

@ -128,14 +128,13 @@ class wan(FormPlugin, PagePlugin):
return '' return ''
store = filedict_con(cfg.store_file, 'router') store = filedict_con(cfg.store_file, 'router')
defaults = {'connect_type': "'DHCP'", defaults = {'connect_type': 'DHCP'}
} for key, value in defaults.items():
for k,c in defaults.items(): if not key in kwargs:
if not k in kwargs:
try: try:
kwargs[k] = store[k] kwargs[key] = store[key]
except KeyError: except KeyError:
exec("if not '%(k)s' in kwargs: store['%(k)s'] = kwargs['%(k)s'] = %(c)s" % {'k':k, 'c':c}) store[key] = kwargs[key] = value
form = Form(title="WAN Connection", form = Form(title="WAN Connection",
action=cfg.server_dir + "/router/setup/wan/index", action=cfg.server_dir + "/router/setup/wan/index",

View File

@ -1,42 +1,71 @@
import os, subprocess #
from socket import gethostname # 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/>.
#
"""
Plinth module for configuring timezone, hostname etc.
"""
import cherrypy import cherrypy
from gettext import gettext as _
try: try:
import simplejson as json import simplejson as json
except ImportError: except ImportError:
import json import json
from gettext import gettext as _ import os
from filedict import FileDict import socket
from modules.auth import require
from plugin_mount import PagePlugin, FormPlugin import actions
from actions import superuser_run
import cfg import cfg
from forms import Form from forms import Form
from model import User from modules.auth import require
from util import * from plugin_mount import PagePlugin, FormPlugin
import platform import util
class Config(PagePlugin): class Config(PagePlugin):
"""System configuration page"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
del args # Unused
del kwargs # Unused
self.register_page("sys.config") self.register_page("sys.config")
@cherrypy.expose @cherrypy.expose
@require() @require()
def index(self): def index(self):
"""Serve configuration page"""
parts = self.forms('/sys/config') parts = self.forms('/sys/config')
parts['title']=_("Configure this %s" % cfg.box_name) parts['title'] = _("Configure this {box_name}") \
return self.fill_template(**parts) .format(box_name=cfg.box_name)
return self.fill_template(**parts) # pylint: disable-msg=W0142
def valid_hostname(name): def valid_hostname(name):
"""Return '' if name is a valid hostname by our standards (not """
just by RFC 952 and RFC 1123. We're more conservative than the Return '' if name is a valid hostname by our standards (not just
standard. If hostname isn't valid, return message explaining why.""" by RFC 952 and RFC 1123. We're more conservative than the
standard. If hostname isn't valid, return message explaining why.
"""
message = '' message = ''
if len(name) > 63: if len(name) > 63:
message += "<br />Hostname too long (max is 63 characters)" message += "<br />Hostname too long (max is 63 characters)"
if not is_alphanumeric(name): if not util.is_alphanumeric(name):
message += "<br />Hostname must be alphanumeric" message += "<br />Hostname must be alphanumeric"
if not name[0] in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ": if not name[0] in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
@ -44,106 +73,110 @@ def valid_hostname(name):
return message return message
def get_hostname(): def get_hostname():
return gethostname() """Return the current hostname of the system"""
return socket.gethostname()
def get_time_zone():
"""Return currently set system's timezone"""
return util.slurp('/etc/timezone').rstrip()
def set_hostname(hostname): def set_hostname(hostname):
"Sets machine hostname to hostname" """Sets machine hostname to hostname"""
# Hostname should be ASCII. If it's unicode but passed our
# Hostname should be ASCII. If it's unicode but passed our valid_hostname check, convert to ASCII. # valid_hostname check, convert to ASCII.
hostname = str(hostname) hostname = str(hostname)
cfg.log.info("Changing hostname to '%s'" % hostname) cfg.log.info("Changing hostname to '%s'" % hostname)
try: try:
superuser_run("xmpp-pre-hostname-change") actions.superuser_run("xmpp-pre-hostname-change")
superuser_run("hostname-change", hostname) actions.superuser_run("hostname-change", hostname)
superuser_run("xmpp-hostname-change", hostname, async=True) actions.superuser_run("xmpp-hostname-change", hostname, async=True)
# don't persist/cache change unless it was saved successfuly # don't persist/cache change unless it was saved successfuly
sys_store = filedict_con(cfg.store_file, 'sys') sys_store = util.filedict_con(cfg.store_file, 'sys')
sys_store['hostname'] = hostname sys_store['hostname'] = hostname
except OSError, e: except OSError as exception:
raise cherrypy.HTTPError(500, "Updating hostname failed: %s" % e) raise cherrypy.HTTPError(500,
'Updating hostname failed: %s' % exception)
class general(FormPlugin, PagePlugin): class general(FormPlugin, PagePlugin):
"""Form to update hostname and time zone"""
url = ["/sys/config"] url = ["/sys/config"]
order = 30 order = 30
def help(self, *args, **kwargs): @staticmethod
return _(#"""<strong>Time Zone</strong> def help(*args, **kwargs):
"""<p>Set your timezone to get accurate """Build and return the help content area"""
timestamps. %(product)s will use this information to set your del args # Unused
%(box)s's systemwide timezone.</p> del kwargs # Unused
""" % {'product':cfg.product_name, 'box':cfg.box_name})
return _('''
<p>Set your timezone to get accurate timestamps. {product} will use
this information to set your {box}'s systemwide timezone.</p>''').format(
product=cfg.product_name, box=cfg.box_name)
def main(self, message='', time_zone=None, **kwargs):
"""Build and return the main content area which is the form"""
del kwargs # Unused
def main(self, message='', **kwargs):
if not cfg.users.expert(): if not cfg.users.expert():
return '<p>' + _('Only members of the expert group are allowed to see and modify the system setup.') + '</p>' return _('''
<p>Only members of the expert group are allowed to see and modify the system
setup.</p>''')
sys_store = filedict_con(cfg.store_file, 'sys') if not time_zone:
hostname = get_hostname() time_zone = get_time_zone()
# this layer of persisting configuration in sys_store could/should be
# removed -BLN
defaults = {'time_zone': "slurp('/etc/timezone').rstrip()",
'hostname': "hostname",
}
for k,c in defaults.items():
if not k in kwargs:
try:
kwargs[k] = sys_store[k]
except KeyError:
exec("if not '%(k)s' in kwargs: sys_store['%(k)s'] = kwargs['%(k)s'] = %(c)s" % {'k':k, 'c':c})
# over-ride the sys_store cached value
kwargs['hostname'] = hostname
## Get the list of supported timezones and the index in that list of the current one # Get the list of supported timezones and the index in that
# list of the current one
module_file = __file__ module_file = __file__
if module_file.endswith(".pyc"): if module_file.endswith(".pyc"):
module_file = module_file[:-1] module_file = module_file[:-1]
time_zones = json.loads(slurp(os.path.join(os.path.dirname(os.path.realpath(module_file)), "time_zones"))) module_dir = os.path.dirname(os.path.realpath(module_file))
for i in range(len(time_zones)): time_zones_file = os.path.join(module_dir, 'time_zones')
if kwargs['time_zone'] == time_zones[i]: time_zones = json.loads(util.slurp(time_zones_file))
time_zone_id = i
break
## A little sanity checking. Make sure the current timezone is in the list.
try: try:
cfg.log('kwargs tz: %s, from_table: %s' % (kwargs['time_zone'], time_zones[time_zone_id])) time_zone_id = time_zones.index(time_zone)
except NameError: except ValueError:
cfg.log.critical("Unknown Time Zone: %s" % kwargs['time_zone']) cfg.log.critical("Unknown Time Zone: %s" % time_zone)
raise cherrypy.HTTPError(500, "Unknown Time Zone: %s" % kwargs['time_zone']) raise cherrypy.HTTPError(500, "Unknown Time Zone: %s" % time_zone)
## And now, the form. # And now, the form.
form = Form(title=_("General Config"), form = Form(title=_("General Config"),
action=cfg.server_dir + "/sys/config/general/index", action=cfg.server_dir + "/sys/config/general/index",
name="config_general_form", name="config_general_form",
message=message ) message=message)
form.html(self.help()) form.html(self.help())
form.dropdown(_("Time Zone"), name="time_zone", vals=time_zones, select=time_zone_id) form.dropdown(_("Time Zone"), name="time_zone", vals=time_zones,
form.html("<p>Your hostname is the local name by which other machines on your LAN can reach you.</p>") select=time_zone_id)
form.text_input('Hostname', name='hostname', value=kwargs['hostname']) form.html('''
<p>Your hostname is the local name by which other machines on your LAN
can reach you.</p>''')
form.text_input('Hostname', name='hostname', value=get_hostname())
form.submit(_("Submit")) form.submit(_("Submit"))
return form.render() return form.render()
def process_form(self, time_zone='', hostname='', *args, **kwargs): @staticmethod
sys_store = filedict_con(cfg.store_file, 'sys') def process_form(time_zone='', hostname='', *args, **kwargs):
"""Handle form submission"""
del args # Unused
del kwargs # Unused
message = '' message = ''
if hostname != sys_store['hostname']: if hostname != get_hostname():
msg = valid_hostname(hostname) msg = valid_hostname(hostname)
if msg == '': if msg == '':
old_val = sys_store['hostname'] set_hostname(hostname)
try:
set_hostname(hostname)
except Exception, e:
cfg.log.error(e)
cfg.log.info("Trying to restore old hostname value.")
set_hostname(old_val)
raise
else: else:
message += msg message += msg
time_zone = time_zone.strip()
if time_zone != sys_store['time_zone']:
cfg.log.info("Setting timezone to %s" % time_zone)
superuser_run("timezone-change", [time_zone])
sys_store['time_zone'] = time_zone
return message or "Settings updated."
time_zone = time_zone.strip()
if time_zone != get_time_zone():
cfg.log.info("Setting timezone to %s" % time_zone)
actions.superuser_run("timezone-change", [time_zone])
return message or "Settings updated."

View File

@ -5,7 +5,6 @@ try:
except ImportError: except ImportError:
import json import json
from gettext import gettext as _ from gettext import gettext as _
from filedict import FileDict
from modules.auth import require from modules.auth import require
from plugin_mount import PagePlugin, FormPlugin from plugin_mount import PagePlugin, FormPlugin
import cfg import cfg
@ -45,32 +44,35 @@ class wan(FormPlugin, PagePlugin):
def main(self, message='', **kwargs): def main(self, message='', **kwargs):
store = filedict_con(cfg.store_file, 'sys') store = filedict_con(cfg.store_file, 'sys')
defaults = {'wan_admin': "''", defaults = {'wan_admin': '',
'wan_ssh': "''", 'wan_ssh': '',
'lan_ssh': "''", 'lan_ssh': '',
} }
for k,c in defaults.items(): for key, value in defaults.items():
if not k in kwargs: if not key in kwargs:
try: try:
kwargs[k] = store[k] kwargs[key] = store[key]
except KeyError: except KeyError:
exec("if not '%(k)s' in kwargs: store['%(k)s'] = kwargs['%(k)s'] = %(c)s" % {'k':k, 'c':c}) store[key] = kwargs[key] = value
form = Form(title=_("Accessing the %s" % cfg.box_name), form = Form(title=_("Accessing the %s" % cfg.box_name),
action=cfg.server_dir + "/sys/config/wan", action=cfg.server_dir + "/sys/config/wan/",
name="admin_wan_form", name="admin_wan_form",
message=message ) message=message)
form.html(self.help()) form.html(self.help())
if cfg.users.expert(): if cfg.users.expert():
form.checkbox(_("Allow access to Plinth from WAN"), name="wan_admin", checked=kwargs['wan_admin']) form.checkbox(_("Allow access to Plinth from WAN"), name="wan_admin", checked=kwargs['wan_admin'])
form.checkbox(_("Allow SSH access from LAN"), name="lan_ssh", checked=kwargs['lan_ssh']) form.checkbox(_("Allow SSH access from LAN"), name="lan_ssh", checked=kwargs['lan_ssh'])
form.checkbox(_("Allow SSH access from WAN"), name="wan_ssh", checked=kwargs['wan_ssh']) form.checkbox(_("Allow SSH access from WAN"), name="wan_ssh", checked=kwargs['wan_ssh'])
# Hidden field is needed because checkbox doesn't post if not checked
form.hidden(name="submitted", value="True")
form.submit(_("Submit")) form.submit(_("Submit"))
return form.render() return form.render()
def process_form(self, wan_admin='', wan_ssh='', lan_ssh='', *args, **kwargs): def process_form(self, wan_admin='', wan_ssh='', lan_ssh='', *args, **kwargs):
store = filedict_con(cfg.store_file, 'sys') store = filedict_con(cfg.store_file, 'sys')
for field in ['wan_admin', 'wan_ssh', 'lan_ssh']: for field in ['wan_admin', 'wan_ssh', 'lan_ssh']:
exec("store['%s'] = %s" % (field, field)) store[field] = locals()[field]
return "Settings updated." return "Settings updated."

View File

@ -42,17 +42,30 @@ def get_parts(obj, parts=None, *args, **kwargs):
for v in fields: for v in fields:
if not v in parts: if not v in parts:
parts[v] = '' parts[v] = ''
exec("""
try: try:
if str(type(obj.%(v)s))=="<type 'instancemethod'>": method = getattr(obj, v)
parts[v] += obj.%(v)s(*args, **kwargs) if callable(method):
else: parts[v] = method(*args, **kwargs)
parts[v] += obj.%(v)s else:
except AttributeError: parts[v] = method
pass""" % {'v':v}) except AttributeError:
pass
return parts return parts
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: class PagePlugin:
""" """
Mount point for page plugins. Page plugins provide display pages Mount point for page plugins. Page plugins provide display pages
@ -72,7 +85,8 @@ class PagePlugin:
def register_page(self, url): def register_page(self, url):
cfg.log.info("Registering page: %s" % url) cfg.log.info("Registering page: %s" % url)
exec "cfg.html_root.%s = self" % (url) _setattr_deep(cfg.html_root, url, self)
def fill_template(self, *args, **kwargs): def fill_template(self, *args, **kwargs):
return u.page_template(*args, **kwargs) return u.page_template(*args, **kwargs)
@ -128,8 +142,9 @@ class FormPlugin():
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
for u in self.url: for u in self.url:
exec "cfg.html_root.%s = self" % "%s.%s" % ('.'.join(u.split("/")[1:]), self.__class__.__name__) path = u.split("/")[1:] + [self.__class__.__name__]
cfg.log("Registered page: %s.%s" % ('.'.join(u.split("/")[1:]), self.__class__.__name__)) _setattr_deep(cfg.html_root, path, self)
cfg.log("Registered page: %s" % '.'.join(path))
def main(self, *args, **kwargs): def main(self, *args, **kwargs):
return "<p>Override this method and replace it with a form.</p>" return "<p>Override this method and replace it with a form.</p>"

View File

@ -1,9 +1,12 @@
import os, sys import os, sys
import cherrypy import cherrypy
import cfg import cfg
import importlib
import sqlite3 import sqlite3
from filedict import FileDict from filedict import FileDict
def mkdir(newdir): def mkdir(newdir):
"""works the way a good mkdir should :) """works the way a good mkdir should :)
- already exists, silently complete - already exists, silently complete
@ -67,7 +70,8 @@ def page_template(template='login_nav', **kwargs):
#if template=='base' and kwargs['sidebar_right']=='': #if template=='base' and kwargs['sidebar_right']=='':
# template='two_col' # template='two_col'
if isinstance(template, basestring): if isinstance(template, basestring):
exec ("from templates.%s import %s as template" % (template, template)) template_module = importlib.import_module('templates.' + template)
template = getattr(template_module, template)
try: try:
submenu = cfg.main_menu.active_item().encode("sub_menu", render_subs=True) submenu = cfg.main_menu.active_item().encode("sub_menu", render_subs=True)
except AttributeError: except AttributeError: