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
|
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import json
import os
import socket
import tempfile
import threading
import unittest
import http.client
import deckit.module as mod
import deckit.server as srv
TOKEN = "test-token"
HOST = {"Host": "127.0.0.1:8765", "Cookie": f"deckit={TOKEN}"}
ORIGIN = {"Origin": "http://127.0.0.1:8765"}
class UnixConnection(http.client.HTTPConnection):
def __init__(self, path):
super().__init__("localhost")
self.path = path
def connect(self):
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(self.path)
class Server(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.dir = tempfile.TemporaryDirectory()
cls.sock = os.path.join(cls.dir.name, "http.sock")
srv.TOKEN = TOKEN
srv.REGISTRY = mod.Registry([
mod.Module("demo", "Demo", lambda: {"ok": True},
{"ping": lambda body: {"got": body.get("n")},
"boom": lambda body: int(body.get("x"))}),
])
cls.server = srv.UnixHTTPServer(cls.sock, srv.Handler)
threading.Thread(target=cls.server.serve_forever, daemon=True).start()
@classmethod
def tearDownClass(cls):
cls.server.shutdown()
cls.server.server_close()
cls.dir.cleanup()
def req(self, method, path, headers=None, body=None):
conn = UnixConnection(self.sock)
conn.request(method, path, body=body, headers=headers or {})
r = conn.getresponse()
data = r.read()
conn.close()
return r.status, data
def test_assets_and_api(self):
self.assertEqual(self.req("GET", "/", HOST)[0], 200)
self.assertEqual(self.req("GET", "/theme.css", HOST)[0], 200)
self.assertEqual(json.loads(self.req("GET", "/api/modules", HOST)[1]),
[{"name": "demo", "title": "Demo"}])
self.assertEqual(json.loads(self.req("GET", "/api/demo", HOST)[1]), {"ok": True})
def test_trust_and_auth(self):
self.assertEqual(self.req("GET", "/api/demo", {"Host": "evil.com"})[0], 403)
self.assertEqual(self.req("POST", "/api/demo/x", {**HOST, "Origin": "https://evil.com"})[0], 403)
self.assertEqual(self.req("GET", "/api/demo", {"Host": "127.0.0.1:8765"})[0], 401)
def test_unlock_and_bad_action(self):
conn = UnixConnection(self.sock)
conn.request("GET", f"/?t={TOKEN}", headers={"Host": "127.0.0.1:8765"})
r = conn.getresponse()
r.read()
conn.close()
self.assertEqual(r.status, 303)
self.assertIn("deckit=", r.getheader("Set-Cookie"))
self.assertIn("SameSite=Strict", r.getheader("Set-Cookie"))
self.assertEqual(self.req("POST", "/api/demo/nope", {**HOST, **ORIGIN})[0], 404)
def test_rejects_oversized_body(self):
big = "x" * 5000
status, _ = self.req("POST", "/api/demo/ping",
{**HOST, **ORIGIN, "Content-Type": "application/json"}, big)
self.assertEqual(status, 400)
def test_rejects_wrong_typed_body(self):
h = {**HOST, **ORIGIN, "Content-Type": "application/json"}
self.assertEqual(self.req("POST", "/api/demo/boom", h, '{"x": null}')[0], 400)
if __name__ == "__main__":
unittest.main()
|