#! /usr/bin/env python3 import sys from wsgiref.simple_server import make_server import falcon import json from db import DictDB import time from base64 import b64decode class CollectorResource(): def __init__(self, db): self._db = db def parse_error(data): return "I want valid JSON but got this:\n{}\n".format(data) def user_authn(self, auth_header, authfun): if not auth_header: return None # Fail. BAlit, b64 = auth_header.split() if BAlit != "Basic": return None # Fail userbytes, pwbytes = b64decode(b64).split(b':') try: user = userbytes.decode('ascii') except: return None # Fail if authfun(user, pwbytes): return user # Success. return None # Fail. class EPGet(CollectorResource): def on_get(self, req, resp): resp.status = falcon.HTTP_200 resp.content_type = falcon.MEDIA_JSON out = [] userid = self.user_authn(req.auth, lambda user,_pw: user is not None) if not userid: resp.status = falcon.HTTP_401 resp.text = 'Invalid user or password\n' return out = [{time.ctime(key): dict} for (key, dict) in self._db.search('domain', dict_val=userid)] resp.text = json.dumps(out) + '\n' class EPAdd(CollectorResource): def on_post(self, req, resp): resp.status = falcon.HTTP_200 resp.content_type = falcon.MEDIA_TEXT self._indata = [] if self.user_authn(req.auth, lambda u,p: u == 'admin' and p == b'admin') is None: resp.status = falcon.HTTP_401 resp.text = 'Invalid user or password\n' return # TODO: can we do json.load(req.bounded_stream, # cls=customDecoder) where our decoder calls JSONDecoder after # decoding UTF-8? # NOTE: Reading the whole body in one go instead of streaming # it nicely. rawin = req.bounded_stream.read() try: decodedin = rawin.decode('UTF-8') except: resp.status = falcon.HTTP_400 resp.text = 'Need UTF-8\n' return try: keys = json.loads(decodedin, object_hook=self.handle_data) except TypeError: print('DEBUG: type error') resp.status = falcon.HTTP_400 resp.text = CollectorResource.parse_error(decodedin) return except json.decoder.JSONDecodeError: print('DEBUG: json decode error') resp.status = falcon.HTTP_400 resp.text = CollectorResource.parse_error(decodedin) return resp.text = '' for key in keys: resp.text += repr(key) + '\n' def handle_data(self, data): return self._db.add(data) # return key def init(url_res_map, addr = '', port = 8000): app = falcon.App() for url, res in url_res_map: app.add_route(url, res) return make_server(addr, port, app) def main(): # Simple demo. # Try adding some observations, basic auth admin:admin, and # include {"domain": "foo.se"} in some of them. # Try retreiving all observations for user 'foo.se' (basic auth # foo.se:whatever). db = DictDB('wsgi_demo.db') httpd = init([('/sc/v0/add', EPAdd(db)), ('/sc/v0/get', EPGet(db))]) print('Serving on port 8000...') httpd.serve_forever() if __name__ == '__main__': sys.exit(main())