made actions.py more pep8 compliant; added one more check to verify that an action is within the actions directory; moved actions directory path into the settings instead of hard-coding it

This commit is contained in:
fonfon 2014-07-10 06:02:33 +00:00
parent aa65205403
commit a40d42eb60
3 changed files with 22 additions and 23 deletions

View File

@ -47,11 +47,11 @@ Actions run commands with this contract (version 1.1):
$ $action # oops, the file system is gone! $ $action # oops, the file system is gone!
Arguments that fail this validation won't, but probably should, raise a Arguments that fail this validation won't, but probably should, raise a
ValueError. They don't because sanitizing this case is significantly ValueError. They don't because sanitizing this case is significantly
easier than detecting if it occurs. easier than detecting if it occurs.
The options list is coerced into a space-separated string before being The options list is coerced into a space-separated string before being
shell-escaped. Option lists including shell escape characters may need to shell-escaped. Option lists including shell escape characters may need to
be unescaped on the receiving end. be unescaped on the receiving end.
E. Actions must exist in the actions directory. E. Actions must exist in the actions directory.
@ -73,46 +73,44 @@ Actions run commands with this contract (version 1.1):
""" """
import os import os
import pipes, shlex, subprocess import pipes
import subprocess
import cfg
def run(action, options = None, async = False): def run(action, options=None, async=False):
"""Safely run a specific action as the current user. """Safely run a specific action as the current user.
See actions._run for more information. See actions._run for more information.
""" """
return _run(action, options, async, False) return _run(action, options, async, False)
def superuser_run(action, options = None, async = False):
def superuser_run(action, options=None, async=False):
"""Safely run a specific action as root. """Safely run a specific action as root.
See actions._run for more information. See actions._run for more information.
""" """
return _run(action, options, async, True) return _run(action, options, async, True)
def _run(action, options = None, async = False, run_as_root = False):
def _run(action, options=None, async=False, run_as_root=False):
"""Safely run a specific action as a normal user or root. """Safely run a specific action as a normal user or root.
actions are pulled from the actions directory. actions are pulled from the actions directory.
- options are added to the action command.
options are added to the action command. - async: run asynchronously or wait for the command to complete.
- run_as_root: execute the command through sudo.
async: run asynchronously or wait for the command to complete.
run_as_root: execute the command through sudo.
""" """
DIRECTORY = "actions"
if options == None: if options is None:
options = [] options = []
# contract 3A and 3B: don't call anything outside of the actions directory. # contract 3A and 3B: don't call anything outside of the actions directory.
if os.sep in action: if os.sep in action:
raise ValueError("Action can't contain:" + os.sep) raise ValueError("Action can't contain:" + os.sep)
cmd = DIRECTORY + os.sep + action cmd = cfg.actions_dir + os.sep + action
if not os.path.realpath(cmd).startswith(cfg.actions_dir):
raise ValueError("Action has to be in directory %s" % cfg.actions_dir)
# contract 3C: interpret shell escape sequences as literal file names. # contract 3C: interpret shell escape sequences as literal file names.
# contract 3E: fail if the action doesn't exist or exists elsewhere. # contract 3E: fail if the action doesn't exist or exists elsewhere.
@ -125,7 +123,6 @@ def _run(action, options = None, async = False, run_as_root = False):
if options: if options:
if not hasattr(options, "__iter__"): if not hasattr(options, "__iter__"):
options = [options] options = [options]
cmd += [pipes.quote(option) for option in options] cmd += [pipes.quote(option) for option in options]
# contract 1: commands can run via sudo. # contract 1: commands can run via sudo.
@ -136,8 +133,8 @@ def _run(action, options = None, async = False, run_as_root = False):
# contract 5 (and 6-ish). # contract 5 (and 6-ish).
proc = subprocess.Popen( proc = subprocess.Popen(
cmd, cmd,
stdout = subprocess.PIPE, stdout=subprocess.PIPE,
stderr= subprocess.PIPE, stderr=subprocess.PIPE,
shell=False) shell=False)
if not async: if not async:

1
cfg.py
View File

@ -41,6 +41,7 @@ def read():
('Path', 'data_dir'), ('Path', 'data_dir'),
('Path', 'store_file'), ('Path', 'store_file'),
('Path', 'user_db'), ('Path', 'user_db'),
('Path', 'actions_dir'),
('Path', 'status_log_file'), ('Path', 'status_log_file'),
('Path', 'access_log_file'), ('Path', 'access_log_file'),
('Path', 'pidfile'), ('Path', 'pidfile'),

View File

@ -10,6 +10,7 @@ log_dir = %(data_dir)s
pid_dir = %(data_dir)s pid_dir = %(data_dir)s
python_root = %(file_root)s python_root = %(file_root)s
server_dir = plinth/ server_dir = plinth/
actions_dir = %(file_root)s/actions
# file locations # file locations
store_file = %(data_dir)s/store.sqlite3 store_file = %(data_dir)s/store.sqlite3