summaryrefslogtreecommitdiff
path: root/src/wsgi.py
blob: aed351357739548a062f488ca8ebb4756628d3af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#! /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:
            json_data = json.loads(decodedin)
        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

        key = self._db.add(json_data)
        resp.text = repr(key) + '\n'


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())