From 9b19bb8a81c5e4954c1dbb5c4fcfc906480b7db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergiusz=20=27q3k=27=20Baza=C5=84ski?= Date: Mon, 16 Mar 2015 22:48:35 +0100 Subject: [PATCH] fuck this thing and fuck it again --- at.py | 174 +++++++++++++++++++++++++++++++++++++++------------------- 1 file 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///') @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