summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSergiusz 'q3k' Bazański <q3k@q3k.org>2015-03-16 22:48:35 +0100
committerSergiusz 'q3k' Bazański <q3k@q3k.org>2015-03-16 22:48:35 +0100
commit9b19bb8a81c5e4954c1dbb5c4fcfc906480b7db7 (patch)
tree8ff7571f8c5ffca746ccb18e1617375af49b4155
parentdbd8b53cfdf9bfb3ae62be2af4a9b096412b863c (diff)
downloadcheckinator-9b19bb8a81c5e4954c1dbb5c4fcfc906480b7db7.tar.gz
checkinator-9b19bb8a81c5e4954c1dbb5c4fcfc906480b7db7.tar.bz2
checkinator-9b19bb8a81c5e4954c1dbb5c4fcfc906480b7db7.zip
fuck this thing and fuck it again
-rw-r--r--at.py174
1 files changed, 118 insertions, 56 deletions
diff --git a/at.py b/at.py
index 2bc94d7..d7e2b38 100644
--- a/at.py
+++ b/at.py
@@ -7,135 +7,161 @@ import json
import requests
import os
import logging
+
from flask import Flask, render_template, abort, g, \
- redirect, session, request, flash, url_for
-from werkzeug.contrib.fixers import ProxyFix
+ redirect, session, request, flash, url_for, make_response
from datetime import datetime
-from wsgiref import simple_server
-from pesto import Response, dispatcher_app
from time import sleep, time, mktime
from collections import namedtuple
from urllib import urlencode
-from hashlib import sha256
+
app = Flask('at')
app.config.from_pyfile('at.cfg')
-app.wsgi_app = ProxyFix(app.wsgi_app)
app.jinja_env.add_extension('jinja2.ext.i18n')
app.jinja_env.install_null_translations()
app.updater = None
+
from functools import wraps
+
+def v4addr():
+ r_addr = request.remote_addr
+ if r_addr.startswith('::ffff:'):
+ r_addr = r_addr[7:]
+ return r_addr
+
+
def restrict_ip(prefix='', exclude=[]):
def decorator(f):
@wraps(f)
def func(*a, **kw):
- r_addr = request.remote_addr
+ r_addr = v4addr()
if not r_addr.startswith(prefix) or r_addr in exclude:
abort(403)
return f(*a, **kw)
return func
return decorator
+
def req_to_ctx():
return dict(request.form.iteritems())
+
@app.template_filter('strfts')
def strfts(ts, format='%d/%m/%Y %H:%M'):
return datetime.fromtimestamp(ts).strftime(format)
+
@app.template_filter('wikiurl')
def wikiurl(user):
- return app.config['WIKI_URL'] % { 'login': user }
+ return app.config['WIKI_URL'] % {'login': user}
+
@app.before_request
def make_connection():
conn = sqlite3.connect(app.config['DB'])
conn.row_factory = sqlite3.Row
- conn.isolation_level = None # for autocommit mode
+ conn.isolation_level = None # for autocommit mode
g.db = conn
+
@app.teardown_request
def close_connection(exception):
g.db.close()
+
DeviceInfo = namedtuple('DeviceInfo', ['hwaddr', 'name', 'owner', 'ignored'])
+
def get_device_info(conn, hwaddr):
- return list(get_device_infos(conn, (hwaddrs,)))[0]
+ return list(get_device_infos(conn, (hwaddr,)))[0]
+
def get_device_infos(conn, hwaddrs):
- stmt = '''select hwaddr, name, ignored, owner from
- devices where devices.hwaddr in (''' + ','.join(['?'] * len(hwaddrs)) + ')'
+ in_clause = '({})'.format(', '.join(['?'] * len(hwaddrs)))
+ stmt = '''select hwaddr, name, ignored, owner from
+ devices where devices.hwaddr in ''' + in_clause
for row in conn.execute(stmt, hwaddrs):
owner = row['owner'] or ''
ignored = row['ignored']
yield DeviceInfo(row['hwaddr'], row['name'], owner, ignored)
+
class Updater(threading.Thread):
- def __init__(self, timeout, lease_offset = 0, *a, **kw):
+ def __init__(self, timeout, lease_offset=0, *a, **kw):
self.timeout = timeout
self.lock = threading.Lock()
self.lease_offset = lease_offset
self.active = {}
threading.Thread.__init__(self, *a, **kw)
self.daemon = True
+
def purge_stale(self):
now = time()
for addr, (atime, ip, name) in self.active.items():
if now - atime > self.timeout:
del self.active[addr]
+
def get_active_devices(self):
self.lock.acquire()
self.purge_stale()
r = dict(self.active)
self.lock.release()
return r
+
def get_device(self, ip):
- for hwaddr, (atime, dip, name) in \
- self.get_active_devices().iteritems():
+ active_devices = self.get_active_devices().iteritems()
+ for hwaddr, (atime, dip, name) in active_devices:
if ip == dip:
return hwaddr, name
return None, None
- def update(self, hwaddr, atime = None, ip = None, name = None):
+
+ def update(self, hwaddr, atime=None, ip=None, name=None):
if atime:
atime -= self.lease_offset
else:
- atime = time()
+ atime = time()
self.lock.acquire()
self.active[hwaddr] = (atime, ip, name)
self.lock.release()
app.logger.info('updated %s with atime %s and ip %s',
- hwaddr, strfts(atime), ip)
+ hwaddr, strfts(atime), ip)
+
class CapUpdater(Updater):
def __init__(self, cap_file, *a, **kw):
self.cap_file = cap_file
Updater.__init__(self, *a, **kw)
+
def run(self):
while True:
try:
with open(self.cap_file, 'r', buffering=0) as f:
- app.logger.info('Updater ready on cap file %s', self.cap_file)
- while True:
- hwaddr = f.readline().strip()
- if not hwaddr:
- break
- self.update(hwaddr)
- app.logger.warning('Cap file %s closed, reopening', self.cap_file)
+ app.logger.info('Updater ready on cap file %s',
+ self.cap_file)
+ lines = [l.strip() for l in f.read().split('\n')]
+ for hwaddr in lines:
+ if hwaddr:
+ self.update(hwaddr)
+ app.logger.warning('Cap file %s closed, reopening',
+ self.cap_file)
except Exception as e:
- app.logger.error('Updater got an exception:\n' + \
- traceback.format_exc(e))
+ app.logger.error('Updater got an exception:\n' +
+ traceback.format_exc(e))
sleep(10.0)
+
class MtimeUpdater(Updater):
def __init__(self, lease_file, *a, **kw):
self.lease_file = lease_file
self.last_modified = 0
Updater.__init__(self, *a, **kw)
+
def file_changed(self, f):
pass
+
def run(self):
while True:
try:
@@ -147,19 +173,23 @@ class MtimeUpdater(Updater):
self.last_modified = mtime
sleep(3.0)
except Exception as e:
- app.logger.error('Updater got an exception:\n' + \
- traceback.format_exc(e))
+ app.logger.error('Updater got an exception:\n' +
+ traceback.format_exc(e))
sleep(10.0)
+
class DnsmasqUpdater(MtimeUpdater):
def file_changed(self, f):
for line in f:
ts, hwaddr, ip, name, client_id = line.split(' ')
self.update(hwaddr, int(ts), ip, name)
+
class DhcpdUpdater(MtimeUpdater):
def file_changed(self, f):
lease = False
+ # for use by next-line logic
+ ip = None
for line in f:
line = line.split('#')[0]
cmd = line.strip().split()
@@ -168,7 +198,8 @@ class DhcpdUpdater(MtimeUpdater):
if lease:
field = cmd[0]
if(field == 'starts'):
- dt = datetime.strptime(' '.join(cmd[2:]), '%Y/%m/%d %H:%M:%S;')
+ dt = datetime.strptime(' '.join(cmd[2:]),
+ '%Y/%m/%d %H:%M:%S;')
atime = mktime(dt.utctimetuple())
if(field == 'client-hostname'):
name = cmd[1][1:-2]
@@ -182,14 +213,17 @@ class DhcpdUpdater(MtimeUpdater):
ip = cmd[1]
name, hwaddr, atime = [None] * 3
lease = True
-
+
+
@app.route('/')
def main_view():
return render_template('main.html', **now_at())
+
@app.route('/api')
def list_all():
result = now_at()
+
def prettify_user((user, atime)):
return {
'login': user,
@@ -198,83 +232,105 @@ def list_all():
}
result['users'] = map(prettify_user, result['users'])
result['unknown'] = len(result['unknown'])
- return json.dumps(result)
+ res = make_response(json.dumps(result), 200)
+ res.headers['Access-Control-Allow-Origin'] = '*'
+ return res
+
def now_at():
devices = app.updater.get_active_devices()
device_infos = list(get_device_infos(g.db, devices.keys()))
device_infos.sort(key=lambda di: devices.__getitem__)
- users = list(dict((info.owner, devices[info.hwaddr][0]) for info in device_infos
- if info.owner and not info.ignored).iteritems())
- users.sort(key=lambda (u, a): a, reverse=True)
unknown = set(devices.keys()) - set(d.hwaddr for d in device_infos)
- return dict(users=users, unknown=unknown)
-restrict_to_hs = restrict_ip(prefix=app.config['CLAIMABLE_PREFIX'],
- exclude=app.config['CLAIMABLE_EXCLUDE'])
+ users = {}
+ for info in device_infos:
+ if info.owner not in users:
+ users[info.owner] = devices[info.hwaddr][0]
+ users_sorted = sorted(users.items(), key=lambda (u, a): a, reverse=True)
+
+ return dict(users=users_sorted, unknown=unknown)
+
+
+restrict_to_hs = restrict_ip(prefix=app.config['CLAIMABLE_PREFIX'],
+ exclude=app.config['CLAIMABLE_EXCLUDE'])
+
@app.route('/login', methods=['GET'])
def login_form():
return render_template('login.html', **req_to_ctx())
+
@app.route('/login', methods=['POST'])
def login():
login = request.form.get('login', '').lower()
pwd = request.form.get('password', '')
goto = request.values.get('goto') or '/'
+ data = dict(login=login, password=pwd)
if requests.post('https://auth.hackerspace.pl', verify=False,
- data = { 'login': login, 'password': pwd }).status_code == 200:
+ data=data).status_code == 200:
session['login'] = login
return redirect(goto)
else:
flash('Username or password invalid', category='error')
return login_form()
+
@app.route('/logout')
def logout():
session.clear()
return redirect('/')
+
def login_required(f):
@wraps(f)
def func(*a, **kw):
if 'login' not in session:
flash('You must log in to continue', category='error')
- return redirect('/login?' +
- urlencode({'goto': request.path}))
+ return redirect('/login?' +
+ urlencode({'goto': request.path}))
return f(*a, **kw)
return func
+
@app.route('/claim', methods=['GET'])
@restrict_to_hs
@login_required
def claim_form():
- hwaddr, name = app.updater.get_device(request.remote_addr)
+ hwaddr, name = app.updater.get_device(v4addr())
return render_template('claim.html', hwaddr=hwaddr, name=name)
+
@app.route('/claim', methods=['POST'])
@restrict_to_hs
@login_required
def claim():
- hwaddr, lease_name = app.updater.get_device(request.remote_addr)
+ hwaddr, lease_name = app.updater.get_device(v4addr())
ctx = None
if not hwaddr:
- ctx = { 'error': 'Invalid device.' }
+ ctx = dict(error='Invalid device.')
else:
login = session['login']
try:
- g.db.execute('insert into devices (hwaddr, name, owner, ignored)\
- values (?, ?, ?, ?)', [hwaddr, request.form['name'], login, False])
+ g.db.execute('''
+insert into devices (hwaddr, name, owner, ignored) values (?, ?, ?, ?)''',
+ [hwaddr, request.form['name'], login, False])
ctx = {}
- except sqlite3.Error as e:
- ctx = { 'error': 'Could not add device! Perhaps someone claimed it?' }
+ except sqlite3.Error:
+ error = 'Could not add device! Perhaps someone claimed it?'
+ ctx = dict(error=error)
return render_template('post_claim.html', **ctx)
+
def get_user_devices(conn, user):
devs = conn.execute('select hwaddr, name, ignored from devices where\
owner = ?', [user])
- return (DeviceInfo(row['hwaddr'], row['name'], user, row['ignored']) for
- row in devs)
+ device_info = []
+ for row in devs:
+ di = DeviceInfo(row['hwaddr'], row['name'], user, row['ignored'])
+ device_info.append(di)
+ return device_info
+
@app.route('/account', methods=['GET'])
@login_required
@@ -282,13 +338,18 @@ def account():
devices = get_user_devices(g.db, session['login'])
return render_template('account.html', devices=devices)
+
def set_ignored(conn, hwaddr, user, value):
- return conn.execute('update devices set ignored = ? where hwaddr = ? and owner = ?',
- [value, hwaddr, user])
+ return conn.execute('''
+update devices set ignored = ? where hwaddr = ? and owner = ?''',
+ [value, hwaddr, user])
+
def delete_device(conn, hwaddr, user):
- return conn.execute('delete from devices where hwaddr = ? and owner = ?',
- [hwaddr, user])
+ return conn.execute('''
+delete from devices where hwaddr = ? and owner = ?''',
+ [hwaddr, user])
+
@app.route('/devices/<id>/<action>/')
@login_required
@@ -302,10 +363,11 @@ def device(id, action):
delete_device(g.db, id, user)
return redirect(url_for('account'))
+
@app.before_first_request
def setup():
- updater = DhcpdUpdater(app.config['LEASE_FILE'], app.config['TIMEOUT'],
- app.config['LEASE_OFFSET'])
+ updater = DhcpdUpdater(app.config['LEASE_FILE'], app.config['TIMEOUT'],
+ app.config['LEASE_OFFSET'])
updater.start()
app.updater = updater