Mercurial > templog
changeset 297:ca06059e76de
merge
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Sat, 06 Jul 2019 18:37:21 +0800 |
parents | 8441916e3095 (diff) 424f1446f214 (current diff) |
children | b0c0e88cbfcc |
files | py/uploader.py |
diffstat | 24 files changed, 108 insertions(+), 79 deletions(-) [+] |
line wrap: on
line diff
--- a/py/uploader.py Sat Jul 06 18:35:52 2019 +0800 +++ b/py/uploader.py Sat Jul 06 18:37:21 2019 +0800 @@ -64,7 +64,7 @@ @asyncio.coroutine def do(self): try: - readings = self.server.take_readings() + readings = self.server.take_readings() tosend = self.get_tosend(readings) D("tosend >>>%s<<<" % str(tosend)) nreadings = len(readings)
--- a/web/config.py Sat Jul 06 18:35:52 2019 +0800 +++ b/web/config.py Sat Jul 06 18:37:21 2019 +0800 @@ -9,13 +9,12 @@ # local config items HMAC_KEY = 'a hmac key' -ALLOWED_USERS = [] # list of sha1 hashes of client ssl keys -SSH_HOST = 'remotehost' -SSH_KEYFILE = '/home/matt/.ssh/somekey' -SSH_PROG = 'ssh' +ALLOWED_USERS = [] # list of hashes allowed, as provided by the Email link UPDATE_URL = 'http://evil.ucc.asn.au/~matt/templog/update' +EMAIL = "[email protected]" + GRAPH_WIDTH = 600 GRAPH_HEIGHT = 700 ZOOM = 1 @@ -24,15 +23,18 @@ LINE_WIDTH = 2 -SENSOR_NAMES = {'sensor_28 CE B2 1A 03 00 00 99': "Old Fridge", +SENSOR_NAMES = { + 'sensor_28 CE B2 1A 03 00 00 99': "Old Fridge", 'sensor_28 CC C1 1A 03 00 00 D4': "Old Ambient", 'sensor_28 49 BC 1A 03 00 00 54': "Old Wort", 'sensor_voltage': 'Voltage', 'sensor_fridge_setpoint': 'Setpoint', 'sensor_fridge_on': 'Cool', - 'sensor_28-0000042cf4dd': "Wort", + 'sensor_28-0000042cf4dd': "New Old Wort", + 'sensor_28-0000042d36cc': "Wort", 'sensor_28-0000042cccc4': "OldFridge", - 'sensor_28-0000042c6dbb': "Fridge", + 'sensor_28-0000042c6dbb': "New Old Fridge", + 'sensor_28-0000068922df': "Fridge", 'sensor_internal': "Processor", }
--- a/web/log.py Sat Jul 06 18:35:52 2019 +0800 +++ b/web/log.py Sat Jul 06 18:37:21 2019 +0800 @@ -51,8 +51,6 @@ 'DS:temp:GAUGE:600:-100:500', 'RRA:AVERAGE:0.5:1:1051200'] - print>>sys.stderr, sensor_rrd_path(sensor_id) - rrdtool.create(sensor_rrd_path(sensor_id), '--start', 'now-60d', *args) @@ -106,7 +104,7 @@ graph_args.append('DEF:raw%(vname)s=%(rrdfile)s:temp:AVERAGE' % locals()) # limit max temp to 50 graph_args.append('CDEF:%(vname)s=raw%(vname)s,38,GT,UNKN,raw%(vname)s,%(volts_mult)f,*,%(volts_shift)f,+,IF' % locals()) - unit = '<span face="Liberation Serif">º</span>C' + unit = '<span face="Liberation Serif">°</span>C' format_last_value = None if unit: @@ -143,8 +141,7 @@ sensor_lines.sort(key = lambda (legend, line): "Wort" in legend) graph_args += (line for (legend, line) in sensor_lines) - print>>sys.stderr, '\n'.join(graph_args) - + #print>>sys.stderr, '\n'.join(graph_args) end = int(start+length) start = int(start) @@ -224,8 +221,9 @@ def record_debug(params): f = debug_file('a+') - f.write('===== %s =====\n' % time.strftime('%a, %d %b %Y %H:%M:%S')) + f.write('===== start %s =====\n' % time.strftime('%a, %d %b %Y %H:%M:%S')) json.dump(params, f, sort_keys=True, indent=4) + f.write('===== end %s =====\n' % time.strftime('%a, %d %b %Y %H:%M:%S')) f.flush() return f @@ -289,22 +287,34 @@ debugf.write("Updated sensors in %.2f secs\n" % timedelta) debugf.flush() +# types used here define the type of a field _FIELD_DEFAULTS = { 'fridge_setpoint': 16.0, 'fridge_difference': 0.2, 'overshoot_delay': 720, # 12 minutes - 'overshoot_factor': 1, # ºC + 'overshoot_factor': 1.0, # °C 'disabled': False, 'nowort': True, 'fridge_range_lower': 3, 'fridge_range_upper': 3, } +def fake_params(): + """ for quicker testing """ + r = [] + r.append({'name': 'going', 'value': 'true', 'kind': 'yesno', 'title': 'going'}) + r.append({'name': 'temperature', 'value': 12.5, 'kind': 'number', 'title': 'temperature', 'digits': 1, 'amount': 0.1, 'unit': '°'}) + return r + def get_params(): + """ Can return None if there aren't any parameters yet, + otherwise returns the parameter list """ r = [] vals = read_current_params() + if not vals: + return None for k, v in _FIELD_DEFAULTS.iteritems(): n = {'name': k, 'value': type(v)(vals[k])} @@ -317,7 +327,7 @@ n['amount'] = 60 n['digits'] = 0; else: - n['unit'] = 'º' + n['unit'] = '°' n['amount'] = 0.1; n['digits'] = 1; n['kind'] = kind @@ -326,26 +336,6 @@ return json.dumps(r, sort_keys=True, indent=4) -def send_params(params): - # 'templog_receive' is ignored due to authorized_keys - # restrictions. the rpi has authorized_keys with - # command="/home/matt/templog/venv/bin/python /home/matt/templog/py/receive.py",no-pty,no-port-forwarding,no-x11-forwarding,no-agent-forwarding ssh-rsa AAAAB3NzaC.... - args = [config.SSH_PROG, '-i', config.SSH_KEYFILE, - config.SSH_HOST, 'templog_receive'] - try: - p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) - (out, err) = p.communicate(json.dumps(params)) - except OSError, e: - print>>sys.stderr, e - return "Failed update" - - if 'Good Update' in out: - return True - - print>>sys.stderr, "Strange return from update:" - print>>sys.stderr, out - return "Unexpected update result" - def same_type(a, b): ta = type(a) tb = type(b)
--- a/web/secure.py Sat Jul 06 18:35:52 2019 +0800 +++ b/web/secure.py Sat Jul 06 18:37:21 2019 +0800 @@ -11,47 +11,39 @@ import config -__all__ = ["get_csrf_blob", "check_csrf_blob", "setup_csrf", "get_user_hash", -"check_user_hash"] +__all__ = [ + "get_csrf_blob", + "check_csrf_blob", + "setup_csrf", + "check_cookie", + "init_cookie", +] + +AUTH_COOKIE = 'templogauth' +AUTH_COOKIE_LEN = 16 HASH=hashlib.sha1 CLEAN_RE = re.compile('[^a-z0-9A-Z]') -def clean_hash(h): - return CLEAN_RE.sub('', h.lower()) - -def get_user_hash(): - """ - Uses the following apache config. - Needs a separate port or IP to no-certificate SSL, SNI isn't good enough. - - <location /~matt/templog/set> - Require all granted - SSLVerifyClient optional_no_ca - SSLVerifyDepth 1 - SSLOptions +StdEnvVars +ExportCertData +OptRenegotiate - </location> - """ +def cookie_hash(c): + return hashlib.sha256(c).hexdigest() - verify = bottle.request.environ.get('SSL_CLIENT_VERIFY', '') - if not (verify == 'GENEROUS' or verify == 'SUCCESS'): - return 'FAILVERIFY' - blob = bottle.request.environ.get('SSL_CLIENT_CERT') - if not blob: - return 'NOCERT' +def init_cookie(): + """ Generates a new httponly auth cookie if required. + Returns the hash of the cookie (new or existing) + """ + c = bottle.request.get_cookie(AUTH_COOKIE) + if not c: + c = binascii.hexlify(os.urandom(AUTH_COOKIE_LEN)) + bottle.response.set_cookie(AUTH_COOKIE, c, secure=True, httponly=True) + return cookie_hash(c) - b64 = ''.join(l for l in blob.split('\n') - if not l.startswith('-')) - - return HASH(binascii.a2b_base64(b64)).hexdigest() - -def check_user_hash(allowed_users): - current_hash = clean_hash(get_user_hash()) - for a in allowed_users: - if current_hash == clean_hash(a): - return True - return False +def check_cookie(allowed_users): + c = bottle.request.get_cookie(AUTH_COOKIE) + if not c: + return False + return cookie_hash(c) in allowed_users def setup_csrf(): NONCE_SIZE=16 @@ -72,7 +64,7 @@ def get_csrf_blob(): expiry = int(config.CSRF_TIMEOUT + time.time()) - content = '%s-%s' % (get_user_hash(), expiry) + content = '%s-%s' % (init_cookie(), expiry) mac = hmac.new(_csrf_key, content).hexdigest() return "%s-%s" % (content, mac) @@ -83,7 +75,7 @@ return False user, expiry, mac = toks - if user != get_user_hash(): + if user != init_cookie(): print>>sys.stderr, "wrong user" return False
--- a/web/templog.py Sat Jul 06 18:35:52 2019 +0800 +++ b/web/templog.py Sat Jul 06 18:37:21 2019 +0800 @@ -69,7 +69,7 @@ @route('/set/update', method='post') def set_update(): - if not secure.check_user_hash(config.ALLOWED_USERS): + if not secure.check_cookie(config.ALLOWED_USERS): # the "Save" button should be disabled if the cert wasn't # good response.status = 403 @@ -92,12 +92,23 @@ @route('/set') def set(): - allowed = ["false", "true"][secure.check_user_hash(config.ALLOWED_USERS)] + cookie_hash = secure.init_cookie() + allowed = ["false", "true"][secure.check_cookie(config.ALLOWED_USERS)] response.set_header('Cache-Control', 'no-cache') + if request.query.fake: + inline_data = log.fake_params() + else: + inline_data = log.get_params() + if not inline_data: + response.status = 503 # Service Unavailable + return bottle.template('noparamsyet') + return bottle.template('set', - inline_data = log.get_params(), + inline_data = inline_data, csrf_blob = secure.get_csrf_blob(), - allowed = allowed) + allowed = allowed, + cookie_hash = cookie_hash, + email = urllib.quote(config.EMAIL)) def get_request_zoom(): """ returns (length, end) tuple.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/noparamsyet.tpl Sat Jul 06 18:37:21 2019 +0800 @@ -0,0 +1,21 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> +<head> +<title>Try again soon</title> +<meta name="viewport" content="width=device-width"> +<style type="text/css"> + body { + font-family: monospace; + text-align: center; + } +</style> +</head> +<body> +<p> +The templog fridge client hasn't sent its current settings to the server yet. +</p> +<p> +Try again in a minute or two. +</p> +</body> +</html>
--- a/web/views/set.tpl Sat Jul 06 18:35:52 2019 +0800 +++ b/web/views/set.tpl Sat Jul 06 18:37:21 2019 +0800 @@ -8,6 +8,7 @@ <script> %include riot.min.js </script> +<meta name="theme-color" content="#fff"> <style type="text/css"> span.no_selection { @@ -20,6 +21,10 @@ font-family: sans-serif; } +a { + color: #000; +} + input { border: 2px solid transparent; border-radius: 4px; @@ -28,6 +33,8 @@ padding: 0; font-size: 30pt; height: 34pt; + vertical-align: middle; + line-height: 1em; } input[type="button"] { @@ -36,7 +43,6 @@ -webkit-appearance: none; -moz-appearance: none; background:#fff; - vertical-align: center; } input[type="submit"] { @@ -80,6 +86,10 @@ //vertical-align: center; } +#mailauth { + display: none; +} + </style> <title>Set templog</title> </head> @@ -232,6 +242,7 @@ if (!allowed) { $("#savebutton").attr("disabled", true); $('#status').text("No cert") + $('#mailauth').show(); } $("#savebutton").click(function() { @@ -317,6 +328,8 @@ <span id="savebox"> <input type="button" id="savebutton" value="Save"/> <span id="status"></span> +<span id="mailauth"> <a href="mailto:{{email}}?Subject=Allow%20Templog&body=Hash%20is%20{{cookie_hash}}">Email</a> +</span> </span>
--- a/web/views/top.tpl Sat Jul 06 18:35:52 2019 +0800 +++ b/web/views/top.tpl Sat Jul 06 18:37:21 2019 +0800 @@ -3,7 +3,8 @@ <head> <title>Wort Temperature Log</title> <meta name="viewport" content="width=device-width"> -<style type="text/css"><!-- +<meta name="theme-color" content="#fff"> +<style type="text/css"> span.no_selection { -webkit-user-select: none; // webkit (safari, chrome) browsers -moz-user-select: none; // mozilla browsers @@ -19,7 +20,6 @@ width: 100%; max-width: {{graphwidth}}px; } -//--> </style> <title></title> </head>