commit b2118234e7b85ad4381ef2caddb9ec37e131fba4 Author: Arturo Filastò <art...@filasto.net> Date: Fri Apr 1 19:48:14 2016 +0200
Add support for starting tests from the HTTP API --- ooni/director.py | 2 +- ooni/web/resources.py | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ ooni/web/root.py | 40 ++++++++++++ ooni/webui.py | 57 +++++++++++++++++ 4 files changed, 270 insertions(+), 1 deletion(-) diff --git a/ooni/director.py b/ooni/director.py index d6503f2..9897ffd 100644 --- a/ooni/director.py +++ b/ooni/director.py @@ -134,7 +134,7 @@ class Director(object): elif config.tor.control_port and config.tor_state is None: yield connect_to_control_port() - if config.global_options['no-geoip']: + if config.global_options.get('no-geoip'): aux = [False] if config.global_options.get('annotations') is not None: annotations = [k.lower() for k in config.global_options['annotations'].keys()] diff --git a/ooni/web/resources.py b/ooni/web/resources.py new file mode 100644 index 0000000..336fdc0 --- /dev/null +++ b/ooni/web/resources.py @@ -0,0 +1,172 @@ +import json +from twisted.web import resource +from twisted.python import usage + +from ooni import errors +from ooni.nettest import NetTestLoader + + +class WuiResource(resource.Resource): + isLeaf = True + XSRF_HEADER = 'X-XSRF-TOKEN' + # XXX set this to true when stable version + XSRF_PROTECTION = False + + def __init__(self, director): + self.director = director + resource.Resource.__init__(self) + + def check_xsrf(self, request): + if self.XSRF_PROTECTION is False: + return True + if request.requestHeaders.hasHeader(self.XSRF_HEADER): + return True + return False + + def render(self, request): + if not self.check_xsrf(request): + obj = { + 'error_code': 400, + 'error_message': ('Missing cross site request forgery ' + 'header \'{}\''.format(self.XSRF_HEADER)) + } + request.setResponseCode(403) + return self.render_json(obj, request) + obj = resource.Resource.render(self, request) + return self.render_json(obj, request) + + def render_json(self, obj, request): + json_string = json.dumps(obj) + "\n" + request.setHeader('Content-Type', 'application/json') + request.setHeader('Content-Length', len(json_string)) + return json_string + + +class DecksGenerate(WuiResource): + def render_GET(self, request): + return {"generate": "deck"} + + +class DecksStart(WuiResource): + def __init__(self, director, deck_name): + WuiResource.__init__(self, director) + self.deck_name = deck_name + + def render_GET(self, request): + return {"start": self.deck_name} + + +class DecksStop(WuiResource): + def __init__(self, director, deck_id): + WuiResource.__init__(self, director) + self.deck_id = deck_id + + def render_GET(self, request): + return {"stop": self.deck_id} + + +class DecksStatus(WuiResource): + def __init__(self, director, deck_name): + WuiResource.__init__(self, director) + self.deck_name = deck_name + + def render_GET(self, request): + return {"deck": self.deck_name} + + +class DecksList(WuiResource): + def render_GET(self, request): + return {"deck": "list"} + + +def getNetTestLoader(test_options, test_file): + """ + Args: + test_options: (dict) containing as keys the option names. + + test_file: (string) the path to the test_file to be run. + Returns: + an instance of :class:`ooni.nettest.NetTestLoader` with the specified + test_file and the specified options. + """ + options = [] + for k, v in test_options.items(): + options.append('--'+k) + options.append(v) + + net_test_loader = NetTestLoader(options, + test_file=test_file) + return net_test_loader + +class TestsStart(WuiResource): + def __init__(self, director, test_name): + WuiResource.__init__(self, director) + self.test_name = test_name + + def render_POST(self, request): + try: + net_test = self.director.netTests[self.test_name] + except KeyError: + request.setResponseCode(500) + return { + 'error_code': 500, + 'error_message': 'Could not find the specified test' + } + test_options = json.load(request.content) + net_test_loader = getNetTestLoader(test_options, net_test['path']) + try: + net_test_loader.checkOptions() + # XXX we actually want to generate the report_filename in a smart + # way so that we can know where it is located and learn the results + # of the measurement. + report_filename = None + self.director.startNetTest(net_test_loader, report_filename) + except errors.MissingRequiredOption, option_name: + request.setResponseCode(500) + return { + 'error_code': 501, + 'error_message': ('Missing required option: ' + '\'{}\''.format(option_name)) + } + except usage.UsageError: + request.setResponseCode(500) + return { + 'error_code': 502, + 'error_message': 'Error in parsing options' + } + except errors.InsufficientPrivileges: + request.setResponseCode(500) + return { + 'error_code': 503, + 'error_message': 'Insufficient priviledges' + } + + return {"deck": "list"} + + +class TestsStop(WuiResource): + def __init__(self, director, test_id): + WuiResource.__init__(self, director) + self.test_id = test_id + + def render_GET(self, request): + return {"deck": "list"} + + +class TestsStatus(WuiResource): + def __init__(self, director, test_id): + WuiResource.__init__(self, director) + self.test_id = test_id + + def render_GET(self, request): + return {"deck": "list"} + + +class TestsList(WuiResource): + def render_GET(self, request): + return self.director.netTests + + +class Results(WuiResource): + def render_GET(self, request): + return {"result": "bar"} diff --git a/ooni/web/root.py b/ooni/web/root.py new file mode 100644 index 0000000..7ceba4a --- /dev/null +++ b/ooni/web/root.py @@ -0,0 +1,40 @@ +import os +import re +from twisted.web import resource, static + +from .resources import DecksGenerate, DecksStart, DecksStop +from .resources import DecksStatus, DecksList, TestsStart +from .resources import TestsStop, TestsStatus, TestsList +from .resources import Results + + +class OONIProbeWebRoot(resource.Resource): + routes = [ + ('^/decks/generate$', DecksGenerate), + ('^/decks/(.*)/start$', DecksStart), + ('^/decks/(.*)/stop$', DecksStop), + ('^/decks/(.*)$', DecksStatus), + ('^/decks$', DecksList), + ('^/tests/(.*)/start$', TestsStart), + ('^/tests/(.*)/stop$', TestsStop), + ('^/tests/(.*)$', TestsStatus), + ('^/tests$', TestsList), + ('^/results$', Results) + ] + + def __init__(self, config, director): + resource.Resource.__init__(self) + + self._director = director + self._config = config + self._route_map = map(lambda x: (re.compile(x[0]), x[1]), self.routes) + + wui_directory = os.path.join(self._config.data_directory, 'ui', 'app') + self._static = static.File(wui_directory) + + def getChild(self, path, request): + for route, r in self._route_map: + match = route.search(request.path) + if match: + return r(self._director, *match.groups()) + return self._static.getChild(path, request) diff --git a/ooni/webui.py b/ooni/webui.py new file mode 100644 index 0000000..e2fe450 --- /dev/null +++ b/ooni/webui.py @@ -0,0 +1,57 @@ +import os + +from twisted.scripts import twistd +from twisted.python import usage +from twisted.internet import reactor +from twisted.web import server +from twisted.application import service + +from ooni.web.root import OONIProbeWebRoot +from ooni.settings import config +from ooni.director import Director +from ooni.utils import log + +class WebUI(service.MultiService): + portNum = 8822 + def startService(self): + service.MultiService.startService(self) + config.set_paths() + config.initialize_ooni_home() + config.read_config_file() + def _started(res): + log.msg("Director started") + root = server.Site(OONIProbeWebRoot(config, director)) + self._port = reactor.listenTCP(self.portNum, root) + director = Director() + d = director.start() + d.addCallback(_started) + d.addErrback(self._startupFailed) + + def _startupFailed(self, err): + log.err("Failed to start the director") + log.exception(err) + os.abort() + + def stopService(self): + if self._port: + self._port.stopListening() + +class StartOoniprobeWebUIPlugin: + tapname = "ooniprobe" + def makeService(self, so): + return WebUI() + +class MyTwistdConfig(twistd.ServerOptions): + subCommands = [("StartOoniprobeWebUI", None, usage.Options, "ooniprobe web ui")] + +def start(): + twistd_args = ["--nodaemon"] + twistd_config = MyTwistdConfig() + twistd_args.append("StartOoniprobeWebUI") + try: + twistd_config.parseOptions(twistd_args) + except usage.error, ue: + print("ooniprobe: usage error from twistd: {}\n".format(ue)) + twistd_config.loadedPlugins = {"StartOoniprobeWebUI": StartOoniprobeWebUIPlugin()} + twistd.runApp(twistd_config) + return 0
_______________________________________________ tor-commits mailing list tor-commits@lists.torproject.org https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits