changeset 296:8441916e3095

merge
author Matt Johnston <matt@ucc.asn.au>
date Sat, 06 Jul 2019 18:36:46 +0800
parents 6bacd8ca9f8f (diff) 8ea6c90774e3 (current diff)
children ca06059e76de
files web/templog.py
diffstat 9 files changed, 77 insertions(+), 75 deletions(-) [+]
line wrap: on
line diff
--- a/py/config.py	Wed Mar 23 21:34:44 2016 +0800
+++ b/py/config.py	Sat Jul 06 18:36:46 2019 +0800
@@ -12,7 +12,7 @@
 
 PARAMS_FILE = os.path.join(os.path.dirname(__file__), 'tempserver.conf')
 
-SENSOR_BASE_DIR = '/sys/devices/w1_bus_master2'
+SENSOR_BASE_DIR = '/sys/devices/w1_bus_master1'
 FRIDGE_GPIO_PIN = 17
 #WORT_NAME = '28-0000042cf4dd'
 #FRIDGE_NAME = '28-0000042cccc4'
--- a/py/sensor_ds18b20.py	Wed Mar 23 21:34:44 2016 +0800
+++ b/py/sensor_ds18b20.py	Sat Jul 06 18:36:46 2019 +0800
@@ -62,7 +62,7 @@
                 D("no match")
                 return None
             temp = int(match.groups(1)[0]) / 1000.0
-            if temp > 80:
+            if temp > 80 or temp == 0:
                 E("Problem reading sensor '%s': %f" % (s, temp))
                 return None
             return temp
--- a/py/uploader.py	Wed Mar 23 21:34:44 2016 +0800
+++ b/py/uploader.py	Sat Jul 06 18:36:46 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	Wed Mar 23 21:34:44 2016 +0800
+++ b/web/config.py	Sat Jul 06 18:36:46 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	Wed Mar 23 21:34:44 2016 +0800
+++ b/web/log.py	Sat Jul 06 18:36:46 2019 +0800
@@ -104,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:
@@ -221,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
 
@@ -291,13 +292,20 @@
     'fridge_setpoint': 16.0,
     'fridge_difference': 0.2,
     'overshoot_delay': 720, # 12 minutes
-    'overshoot_factor': 1.0, # º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 """
@@ -319,7 +327,7 @@
                 n['amount'] = 60
                 n['digits'] = 0;
             else:
-                n['unit'] = 'º'
+                n['unit'] = '°'
                 n['amount'] = 0.1;
                 n['digits'] = 1;
         n['kind'] = kind
@@ -328,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	Wed Mar 23 21:34:44 2016 +0800
+++ b/web/secure.py	Sat Jul 06 18:36:46 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	Wed Mar 23 21:34:44 2016 +0800
+++ b/web/templog.py	Sat Jul 06 18:36:46 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,9 +92,13 @@
 
 @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')
-    inline_data = log.get_params()
+    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')
@@ -102,7 +106,9 @@
     return bottle.template('set', 
         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.
--- a/web/views/set.tpl	Wed Mar 23 21:34:44 2016 +0800
+++ b/web/views/set.tpl	Sat Jul 06 18:36:46 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	Wed Mar 23 21:34:44 2016 +0800
+++ b/web/views/top.tpl	Sat Jul 06 18:36:46 2019 +0800
@@ -3,6 +3,7 @@
 <head>
 <title>Wort Temperature Log</title>
 <meta name="viewport" content="width=device-width">
+<meta name="theme-color" content="#fff">
 <style type="text/css">
 span.no_selection {
     -webkit-user-select: none; // webkit (safari, chrome) browsers