comparison web/templog.py @ 293:d15dda1b1f76

merge
author Matt Johnston <matt@ucc.asn.au>
date Sat, 06 Jul 2019 18:29:45 +0800
parents f7261dd970da
children 6bacd8ca9f8f
comparison
equal deleted inserted replaced
292:28eb733cb803 293:d15dda1b1f76
9 import urllib 9 import urllib
10 import sys 10 import sys
11 import os 11 import os
12 import traceback 12 import traceback
13 import fcntl 13 import fcntl
14 import hashlib
14 15
15 import bottle 16 import bottle
16 from bottle import route, request, response 17 from bottle import route, request, response
17 18
18 import config 19 import config
21 import atomicfile 22 import atomicfile
22 23
23 DATE_FORMAT = '%Y%m%d-%H.%M' 24 DATE_FORMAT = '%Y%m%d-%H.%M'
24 ZOOM_SCALE = 2.0 25 ZOOM_SCALE = 2.0
25 26
27 class TemplogBottle(bottle.Bottle):
28 def run(*args, **argm):
29 argm['server'] = 'gevent'
30 super(TemplogBottle, self).run(*args, **argm)
31
32 bottle.default_app.push(TemplogBottle())
33
34 secure.setup_csrf()
35
26 @route('/update', method='post') 36 @route('/update', method='post')
27 def update(): 37 def update():
28 js_enc = request.forms.data 38 js_enc = request.forms.data
29 mac = request.forms.hmac 39 mac = request.forms.hmac
30 40
31 if hmac.new(config.HMAC_KEY, js_enc).hexdigest() != mac: 41 h = hmac.new(config.HMAC_KEY, js_enc.strip(), hashlib.sha256).hexdigest()
42 if h != mac:
32 raise bottle.HTTPError(code = 403, output = "Bad key") 43 raise bottle.HTTPError(code = 403, output = "Bad key")
33 44
34 js = zlib.decompress(binascii.a2b_base64(js_enc)) 45 js = zlib.decompress(binascii.a2b_base64(js_enc))
35 46
36 params = json.loads(js) 47 params = json.loads(js)
37 48
38 log.parse(params) 49 log.parse(params)
39 50
40 return "OK" 51 return "OK"
52
53 def make_graph(length, end):
54 length_minutes = int(length)
55 end = datetime.strptime(end, DATE_FORMAT)
56 start = end - timedelta(minutes=length_minutes)
57
58 start_epoch = time.mktime(start.timetuple())
59 return log.graph_png(start_epoch, length_minutes * 60)
60
61 def encode_data(data, mimetype):
62 return 'data:%s;base64,%s' % (mimetype, binascii.b2a_base64(data).rstrip())
41 63
42 @route('/graph.png') 64 @route('/graph.png')
43 def graph(): 65 def graph():
44 length_minutes = int(request.query.length)
45 end = datetime.strptime(request.query.end, DATE_FORMAT)
46 start = end - timedelta(minutes=length_minutes)
47
48 response.set_header('Content-Type', 'image/png') 66 response.set_header('Content-Type', 'image/png')
49 start_epoch = time.mktime(start.timetuple()) 67 minutes, endstr = get_request_zoom()
50 return log.graph_png(start_epoch, length_minutes * 60) 68 return make_graph(minutes, endstr)
51 69
52 @route('/set/update', method='post') 70 @route('/set/update', method='post')
53 def set_update(): 71 def set_update():
72 if not secure.check_cookie(config.ALLOWED_USERS):
73 # the "Save" button should be disabled if the cert wasn't
74 # good
75 response.status = 403
76 return "No cert, dodginess"
77
54 post_json = json.loads(request.forms.data) 78 post_json = json.loads(request.forms.data)
55 79
56 csrf_blob = post_json['csrf_blob'] 80 csrf_blob = post_json['csrf_blob']
57 81
58 if not secure.check_csrf_blob(csrf_blob): 82 if not secure.check_csrf_blob(csrf_blob):
59 bottle.response.status = 403 83 response.status = 403
60 return "Bad csrf" 84 return "Bad csrf"
61 85
62 ret = log.update_params(post_json['params']) 86 ret = log.update_params(post_json['params'])
63 if not ret is True: 87 if not ret is True:
64 bottle.response.status = 403 88 response.status = 409 # Conflict
65 return ret 89 return ret
66 90
67 return "Good" 91 return "Good"
68 92
69 @route('/set') 93 @route('/set')
70 def set(): 94 def set():
71 allowed = ["false", "true"][secure.check_user_hash(config.ALLOWED_USERS)] 95 cookie_hash = secure.init_cookie()
96 allowed = ["false", "true"][secure.check_cookie(config.ALLOWED_USERS)]
72 response.set_header('Cache-Control', 'no-cache') 97 response.set_header('Cache-Control', 'no-cache')
98 if request.query.fake:
99 inline_data = log.fake_params()
100 else:
101 inline_data = log.get_params()
102 if not inline_data:
103 response.status = 503 # Service Unavailable
104 return bottle.template('noparamsyet')
105
73 return bottle.template('set', 106 return bottle.template('set',
74 inline_data = log.get_params(), 107 inline_data = inline_data,
75 csrf_blob = secure.get_csrf_blob(), 108 csrf_blob = secure.get_csrf_blob(),
76 allowed = allowed) 109 allowed = allowed,
77 110 cookie_hash = cookie_hash,
78 @route('/set_current.json') 111 email = urllib.quote(config.EMAIL))
79 def set_fresh(): 112
80 response.set_header('Content-Type', 'application/javascript') 113 def get_request_zoom():
81 return log.get_current() 114 """ returns (length, end) tuple.
82 115 length is in minutes, end is a DATE_FORMAT string """
83 @route('/')
84 def top():
85
86 minutes = int(request.query.get('length', 26*60)) 116 minutes = int(request.query.get('length', 26*60))
87 117
88 if 'end' in request.query: 118 if 'end' in request.query:
89 end = datetime.strptime(request.query.end, DATE_FORMAT) 119 end = datetime.strptime(request.query.end, DATE_FORMAT)
90 else: 120 else:
91 end = datetime.now() 121 end = datetime.now()
92 122
93 if 'zoom' in request.query: 123 if 'zoom' in request.query:
94 orig_start = end - timedelta(minutes=minutes) 124 orig_start = end - timedelta(minutes=minutes)
95 orig_end = end 125 orig_end = end
96 xpos = int(request.query.x) 126 scale = float(request.query.scaledwidth) / config.GRAPH_WIDTH
127 xpos = int(request.query.x) / scale
97 xpos -= config.GRAPH_LEFT_MARGIN * config.ZOOM 128 xpos -= config.GRAPH_LEFT_MARGIN * config.ZOOM
98 129
99 if xpos >= 0 and xpos < config.GRAPH_WIDTH * config.ZOOM: 130 if xpos >= 0 and xpos < config.GRAPH_WIDTH * config.ZOOM:
100 click_time = orig_start \ 131 click_time = orig_start \
101 + timedelta(minutes=(float(xpos) / (config.GRAPH_WIDTH * config.ZOOM)) * minutes) 132 + timedelta(minutes=(float(xpos) / (config.GRAPH_WIDTH * config.ZOOM)) * minutes)
107 minutes = int(minutes*ZOOM_SCALE) 138 minutes = int(minutes*ZOOM_SCALE)
108 end += timedelta(minutes=minutes/2) 139 end += timedelta(minutes=minutes/2)
109 140
110 if end > datetime.now(): 141 if end > datetime.now():
111 end = datetime.now() 142 end = datetime.now()
112 143
144 endstr = end.strftime(DATE_FORMAT)
145 return (minutes, endstr)
146
147 @route('/')
148 def top():
149 minutes, endstr = get_request_zoom()
150
113 request.query.replace('length', minutes) 151 request.query.replace('length', minutes)
114 request.query.replace('end', end.strftime(DATE_FORMAT)) 152 request.query.replace('end', endstr)
115 153
116 urlparams = urllib.urlencode(request.query) 154 urlparams = urllib.urlencode(request.query)
155 graphdata = encode_data(make_graph(minutes, endstr), 'image/png')
117 return bottle.template('top', urlparams=urlparams, 156 return bottle.template('top', urlparams=urlparams,
118 end = end.strftime(DATE_FORMAT), 157 end = endstr,
119 length = minutes) 158 length = minutes,
159 graphwidth = config.GRAPH_WIDTH,
160 graphdata = graphdata)
120 161
121 @route('/debug') 162 @route('/debug')
122 def debuglog(): 163 def debuglog():
123 response.set_header('Content-Type', 'text/plain') 164 response.set_header('Content-Type', 'text/plain')
124 return log.tail_debug_log() 165 return log.tail_debug_log()
131 #return str(request.environ) 172 #return str(request.environ)
132 #yield "\n" 173 #yield "\n"
133 #var_lookup = environ['mod_ssl.var_lookup'] 174 #var_lookup = environ['mod_ssl.var_lookup']
134 #return var_lookup("SSL_SERVER_I_DN_O") 175 #return var_lookup("SSL_SERVER_I_DN_O")
135 176
177 @route('/h')
178 def headers():
179 response.set_header('Content-Type', 'text/plain')
180 return '\n'.join("%s: %s" % x for x in request.headers.items())
181
182 @route('/get_settings')
183 def get_settings():
184 response.set_header('Cache-Control', 'no-cache')
185 req_etag = request.headers.get('etag', None)
186 if req_etag:
187 # wait for it to change
188 # XXX this is meant to return True if it has been woken up
189 # but it isn't working. Instead compare epochtag below.
190 log.fridge_settings.wait(req_etag, timeout=config.LONG_POLL_TIMEOUT)
191
192 contents, epoch_tag = log.fridge_settings.get()
193 if epoch_tag == req_etag:
194 response.status = 304
195 return "Nothing happened"
196
197 response.set_header('Content-Type', 'application/json')
198 return json.dumps({'params': contents, 'epoch_tag': epoch_tag})
199
136 @bottle.get('/<filename:re:.*\.js>') 200 @bottle.get('/<filename:re:.*\.js>')
137 def javascripts(filename): 201 def javascripts(filename):
138 response.set_header('Cache-Control', "public, max-age=1296000") 202 response.set_header('Cache-Control', "public, max-age=1296000")
139 return bottle.static_file(filename, root='static') 203 return bottle.static_file(filename, root='static')
140 204
141 secure.setup_csrf()
142 205
143 def main(): 206 def main():
207 """ for standalone testing """
144 #bottle.debug(True) 208 #bottle.debug(True)
145 #bottle.run(reloader=True) 209 #bottle.run(reloader=True)
146 bottle.run(server='cgi', reloader=True) 210 bottle.run(server='cgi', reloader=True)
147 #bottle.run(port=9999, reloader=True) 211 #bottle.run(port=9999, reloader=True)
148 212