#! /usr/bin/env python3 import os import sys import json import authn import index import falcon from db import DictDB from base64 import b64decode from wsgiref.simple_server import make_server try: database = os.environ['COUCHDB_NAME'] hostname = os.environ['COUCHDB_HOSTNAME'] username = os.environ['COUCHDB_USER'] password = os.environ['COUCHDB_PASSWORD'] except KeyError: print('The environment variables COUCHDB_NAME, COUCHDB_HOSTNAME,' + ' COUCHDB_USER and COUCHDB_PASSWORD must be set.') sys.exit(-1) class CollectorResource(): def __init__(self, db, users): self._db = db self._users = users def parse_error(data): return "I want valid JSON but got this:\n{}\n".format(data) def user_auth(self, auth_header, authfun): if not auth_header: return None, None # Fail. BAlit, b64 = auth_header.split() if BAlit != "Basic": return None, None # Fail userbytes, pwbytes = b64decode(b64).split(b':') try: user = userbytes.decode('utf-8') pw = pwbytes.decode('utf-8') except Exception: return None, None # Fail return authfun(user, pw) class EPGet(CollectorResource): def on_get(self, req, resp, key=None): out = list() selectors = dict() resp.status = falcon.HTTP_200 resp.content_type = falcon.MEDIA_JSON orgs = self.user_auth(req.auth, self._users.read_perms) if not orgs: resp.status = falcon.HTTP_401 resp.text = json.dumps( {'status': 'error', 'message': 'Invalid username or password\n'}) return if key: out = self._db.get(key) resp.text = json.dumps({'status': 'success', 'data': out}) return for param in req.params: for i in index.indexes: for j in i['index']['fields']: if j == param: selectors[param] = req.params[param] for org in orgs: selectors['domain'] = org data = self._db.search(**selectors) if data: out += data resp.text = json.dumps({'status': 'success', 'data': out}) class EPAdd(CollectorResource): def on_post(self, req, resp): resp.status = falcon.HTTP_200 resp.content_type = falcon.MEDIA_TEXT self._indata = [] orgs = self.user_auth(req.auth, self._users.write_perms) if not orgs: resp.status = falcon.HTTP_401 resp.text = json.dumps( {'status': 'error', 'message': 'Invalid user or password\n'}) return # NOTE: Allowing writing to _any_ org! # TODO: Allow only input where input.domain in orgs == True. # 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 Exception: resp.status = falcon.HTTP_400 resp.text = json.dumps( {'status': 'error', 'message': 'Need UTF-8\n'}) return try: json_data = json.loads(decodedin) except TypeError: print('DEBUG: type error') resp.status = falcon.HTTP_400 resp.text = json.dumps( {'status': 'error', 'message': CollectorResource.parse_error(decodedin)}) return except json.decoder.JSONDecodeError: print('DEBUG: json decode error') resp.status = falcon.HTTP_400 resp.text = json.dumps( {'status': 'error', 'message': CollectorResource.parse_error(decodedin)}) return keys = self._db.add(json_data) resp.text = json.dumps({'status': 'success', 'key': keys}) def main(port=8000, wsgi_helper=False): db = DictDB(database, hostname, username, password) users = authn.UserDB('wsgi_demo_users.yaml') resources_map = [ ('/sc/v0/add', EPAdd(db, users)), ('/sc/v0/get', EPGet(db, users)), ('/sc/v0/get/{key}', EPGet(db, users)) ] app = falcon.App(cors_enable=True) for url, res in resources_map: app.add_route(url, res) if not wsgi_helper: print('Serving on port 8000...') httpd = make_server('', port, app) httpd.serve_forever() return app if __name__ == '__main__': sys.exit(main()) else: app = main(port=8000, wsgi_helper=True)