From c3a8f3cb61b9701524cb591b30b681d251329244 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sat, 5 Apr 2014 14:22:28 +0900 Subject: [PATCH 1/6] Ignore .py.bak files created when compiling templates --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c68b0249d..5bce3ed73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ current-*.tar.gz *.pyc +*.py.bak *.tiny.css data/*.log data/cherrypy_sessions From afe345d01b6a23709fcdcc57a02802fddb8de8a4 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sat, 5 Apr 2014 14:22:52 +0900 Subject: [PATCH 2/6] Cleanup system configuration module - Add license header - Fix pylint, pep8 and pyflakes warnings and errors - Fix incorrect use of gettext._() - Don't store hostname and timezone in Plinth storage. Instead use values from probing the system - Don't use exec() - Other minor refactorings --- modules/installed/system/config.py | 203 +++++++++++++++++------------ 1 file changed, 117 insertions(+), 86 deletions(-) diff --git a/modules/installed/system/config.py b/modules/installed/system/config.py index 7d352beb9..2bc87df77 100644 --- a/modules/installed/system/config.py +++ b/modules/installed/system/config.py @@ -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 . +# + +""" +Plinth module for configuring timezone, hostname etc. +""" + import cherrypy +from gettext import gettext as _ try: import simplejson as json except ImportError: import json -from gettext import gettext as _ -from filedict import FileDict -from modules.auth import require -from plugin_mount import PagePlugin, FormPlugin -from actions import superuser_run +import os +import socket + +import actions import cfg from forms import Form -from model import User -from util import * -import platform +from modules.auth import require +from plugin_mount import PagePlugin, FormPlugin +import util + class Config(PagePlugin): + """System configuration page""" def __init__(self, *args, **kwargs): + del args # Unused + del kwargs # Unused + self.register_page("sys.config") @cherrypy.expose @require() def index(self): + """Serve configuration page""" parts = self.forms('/sys/config') - parts['title']=_("Configure this %s" % cfg.box_name) - return self.fill_template(**parts) + parts['title'] = _("Configure this {box_name}") \ + .format(box_name=cfg.box_name) + + return self.fill_template(**parts) # pylint: disable-msg=W0142 + 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 - standard. If hostname isn't valid, return message explaining why.""" - + """ + Return '' if name is a valid hostname by our standards (not just + by RFC 952 and RFC 1123. We're more conservative than the + standard. If hostname isn't valid, return message explaining why. + """ message = '' if len(name) > 63: message += "
Hostname too long (max is 63 characters)" - if not is_alphanumeric(name): + if not util.is_alphanumeric(name): message += "
Hostname must be alphanumeric" if not name[0] in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ": @@ -44,104 +73,106 @@ def valid_hostname(name): return message + 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): - "Sets machine hostname to hostname" - - # Hostname should be ASCII. If it's unicode but passed our valid_hostname check, convert to ASCII. + """Sets machine hostname to hostname""" + # Hostname should be ASCII. If it's unicode but passed our + # valid_hostname check, convert to ASCII. hostname = str(hostname) cfg.log.info("Changing hostname to '%s'" % hostname) try: - superuser_run("hostname-change", hostname) - # don't persist/cache change unless it was saved successfuly - sys_store = filedict_con(cfg.store_file, 'sys') - sys_store['hostname'] = hostname - except OSError, e: - raise cherrypy.HTTPError(500, "Updating hostname failed: %s" % e) + actions.superuser_run("hostname-change", hostname) + except OSError as exception: + raise cherrypy.HTTPError(500, + 'Updating hostname failed: %s' % exception) + class general(FormPlugin, PagePlugin): + """Form to update hostname and time zone""" url = ["/sys/config"] order = 30 - def help(self, *args, **kwargs): - return _(#"""Time Zone - """

Set your timezone to get accurate - timestamps. %(product)s will use this information to set your - %(box)s's systemwide timezone.

- """ % {'product':cfg.product_name, 'box':cfg.box_name}) + @staticmethod + def help(*args, **kwargs): + """Build and return the help content area""" + del args # Unused + del kwargs # Unused + + return _(''' +

Set your timezone to get accurate timestamps. {product} will use +this information to set your {box}'s systemwide timezone.

''').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(): - return '

' + _('Only members of the expert group are allowed to see and modify the system setup.') + '

' + return _(''' +

Only members of the expert group are allowed to see and modify the system +setup.

''') - sys_store = filedict_con(cfg.store_file, 'sys') - hostname = get_hostname() - # 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 + if not time_zone: + time_zone = get_time_zone() - ## 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__ if module_file.endswith(".pyc"): module_file = module_file[:-1] - time_zones = json.loads(slurp(os.path.join(os.path.dirname(os.path.realpath(module_file)), "time_zones"))) - for i in range(len(time_zones)): - if kwargs['time_zone'] == time_zones[i]: - time_zone_id = i - break - - ## A little sanity checking. Make sure the current timezone is in the list. + module_dir = os.path.dirname(os.path.realpath(module_file)) + time_zones_file = os.path.join(module_dir, 'time_zones') + time_zones = json.loads(util.slurp(time_zones_file)) try: - cfg.log('kwargs tz: %s, from_table: %s' % (kwargs['time_zone'], time_zones[time_zone_id])) - except NameError: - cfg.log.critical("Unknown Time Zone: %s" % kwargs['time_zone']) - raise cherrypy.HTTPError(500, "Unknown Time Zone: %s" % kwargs['time_zone']) + time_zone_id = time_zones.index(time_zone) + except ValueError: + cfg.log.critical("Unknown Time Zone: %s" % 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"), - action=cfg.server_dir + "/sys/config/general/index", - name="config_general_form", - message=message ) + action=cfg.server_dir + "/sys/config/general/index", + name="config_general_form", + message=message) form.html(self.help()) - form.dropdown(_("Time Zone"), name="time_zone", vals=time_zones, select=time_zone_id) - form.html("

Your hostname is the local name by which other machines on your LAN can reach you.

") - form.text_input('Hostname', name='hostname', value=kwargs['hostname']) + form.dropdown(_("Time Zone"), name="time_zone", vals=time_zones, + select=time_zone_id) + form.html(''' +

Your hostname is the local name by which other machines on your LAN +can reach you.

''') + form.text_input('Hostname', name='hostname', value=get_hostname()) form.submit(_("Submit")) + return form.render() - def process_form(self, time_zone='', hostname='', *args, **kwargs): - sys_store = filedict_con(cfg.store_file, 'sys') + @staticmethod + def process_form(time_zone='', hostname='', *args, **kwargs): + """Handle form submission""" + del args # Unused + del kwargs # Unused + message = '' - if hostname != sys_store['hostname']: + if hostname != get_hostname(): msg = valid_hostname(hostname) if msg == '': - old_val = sys_store['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 + set_hostname(hostname) else: 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." From 2079b3639a5a6c813d07234385b2e1ad148a11ae Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sat, 5 Apr 2014 14:52:51 +0900 Subject: [PATCH 3/6] Dont use exec for importing templates --- util.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/util.py b/util.py index 07000bedf..4f2ba018f 100644 --- a/util.py +++ b/util.py @@ -1,9 +1,12 @@ import os, sys import cherrypy import cfg +import importlib import sqlite3 + from filedict import FileDict + def mkdir(newdir): """works the way a good mkdir should :) - already exists, silently complete @@ -67,7 +70,8 @@ def page_template(template='login_nav', **kwargs): #if template=='base' and kwargs['sidebar_right']=='': # template='two_col' 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: submenu = cfg.main_menu.active_item().encode("sub_menu", render_subs=True) except AttributeError: From c0ea7ee2987984d1fb048cd6d48a919564d56ba0 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sat, 5 Apr 2014 15:38:19 +0900 Subject: [PATCH 4/6] Dont use exec() in system/wan module Also make the form submission actually work by adding hidden form value and '/' at the end of form submission URL. --- modules/installed/system/wan.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/modules/installed/system/wan.py b/modules/installed/system/wan.py index 55ce1ec1b..061ef50d1 100644 --- a/modules/installed/system/wan.py +++ b/modules/installed/system/wan.py @@ -5,7 +5,6 @@ try: except ImportError: import json from gettext import gettext as _ -from filedict import FileDict from modules.auth import require from plugin_mount import PagePlugin, FormPlugin import cfg @@ -45,32 +44,35 @@ class wan(FormPlugin, PagePlugin): def main(self, message='', **kwargs): store = filedict_con(cfg.store_file, 'sys') - defaults = {'wan_admin': "''", - 'wan_ssh': "''", - 'lan_ssh': "''", + defaults = {'wan_admin': '', + 'wan_ssh': '', + 'lan_ssh': '', } - for k,c in defaults.items(): - if not k in kwargs: + for key, value in defaults.items(): + if not key in kwargs: try: - kwargs[k] = store[k] + kwargs[key] = store[key] 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), - action=cfg.server_dir + "/sys/config/wan", - name="admin_wan_form", - message=message ) + form = Form(title=_("Accessing the %s" % cfg.box_name), + action=cfg.server_dir + "/sys/config/wan/", + name="admin_wan_form", + message=message) form.html(self.help()) if cfg.users.expert(): 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 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")) return form.render() def process_form(self, wan_admin='', wan_ssh='', lan_ssh='', *args, **kwargs): store = filedict_con(cfg.store_file, 'sys') for field in ['wan_admin', 'wan_ssh', 'lan_ssh']: - exec("store['%s'] = %s" % (field, field)) + store[field] = locals()[field] return "Settings updated." - From 2d39638020ce821179dc21f00bc42e0ba0ec6b27 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sat, 5 Apr 2014 16:08:41 +0900 Subject: [PATCH 5/6] Dont use exec() in plugin system --- plugin_mount.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/plugin_mount.py b/plugin_mount.py index 4983ea28a..69ff94552 100644 --- a/plugin_mount.py +++ b/plugin_mount.py @@ -42,17 +42,30 @@ def get_parts(obj, parts=None, *args, **kwargs): for v in fields: if not v in parts: parts[v] = '' - exec(""" -try: - if str(type(obj.%(v)s))=="": - parts[v] += obj.%(v)s(*args, **kwargs) - else: - parts[v] += obj.%(v)s -except AttributeError: - pass""" % {'v':v}) + + try: + method = getattr(obj, v) + if callable(method): + parts[v] = method(*args, **kwargs) + else: + parts[v] = method + except AttributeError: + pass 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: """ Mount point for page plugins. Page plugins provide display pages @@ -72,7 +85,8 @@ class PagePlugin: def register_page(self, 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): return u.page_template(*args, **kwargs) @@ -128,9 +142,10 @@ class FormPlugin(): def __init__(self, *args, **kwargs): for u in self.url: - exec "cfg.html_root.%s = self" % "%s.%s" % ('.'.join(u.split("/")[1:]), self.__class__.__name__) - cfg.log("Registered page: %s.%s" % ('.'.join(u.split("/")[1:]), self.__class__.__name__)) - + path = 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): return "

Override this method and replace it with a form.

" From 5a5ff04f8ea854e7a506e7b2c09e7355f9c7c318 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Thu, 10 Apr 2014 10:26:52 +0530 Subject: [PATCH 6/6] Dont use exec() in router module --- modules/installed/router/router.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/installed/router/router.py b/modules/installed/router/router.py index 6c1f42df0..105d3d69d 100644 --- a/modules/installed/router/router.py +++ b/modules/installed/router/router.py @@ -128,14 +128,13 @@ class wan(FormPlugin, PagePlugin): return '' store = filedict_con(cfg.store_file, 'router') - defaults = {'connect_type': "'DHCP'", - } - for k,c in defaults.items(): - if not k in kwargs: + defaults = {'connect_type': 'DHCP'} + for key, value in defaults.items(): + if not key in kwargs: try: - kwargs[k] = store[k] + kwargs[key] = store[key] 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", action=cfg.server_dir + "/router/setup/wan/index",