summaryrefslogtreecommitdiffstats
path: root/at/web.py
diff options
context:
space:
mode:
Diffstat (limited to 'at/web.py')
-rw-r--r--at/web.py240
1 files changed, 240 insertions, 0 deletions
diff --git a/at/web.py b/at/web.py
new file mode 100644
index 0000000..288a5bc
--- /dev/null
+++ b/at/web.py
@@ -0,0 +1,240 @@
+import json
+import requests
+import sqlite3
+from datetime import datetime
+from collections import namedtuple
+from urllib.parse import urlencode
+from functools import wraps
+from flask import Flask, render_template, abort, g, \
+ redirect, request, flash, url_for, make_response
+
+from .dhcp import DhcpdUpdater
+
+from spaceauth import SpaceAuth, login_required, current_user, cap_required
+
+DeviceInfo = namedtuple('DeviceInfo', ['hwaddr', 'name', 'owner', 'ignored'])
+
+def v4addr():
+ if request.headers.getlist("X-Forwarded-For"):
+ r_addr = request.headers.getlist("X-Forwarded-For")[-1]
+ else:
+ r_addr = request.remote_addr
+ if r_addr.startswith('::ffff:'):
+ r_addr = r_addr[7:]
+ return r_addr
+
+
+def req_to_ctx():
+ return dict(iter(request.form.items()))
+
+def get_device_info(conn, hwaddr):
+ return list(get_device_infos(conn, (hwaddr,)))[0]
+
+
+def get_device_infos(conn, 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)
+
+def app(instance_path):
+ app = Flask('at', instance_path=instance_path, instance_relative_config=True)
+ app.config.from_pyfile('at.cfg')
+ app.jinja_env.add_extension('jinja2.ext.i18n')
+ app.jinja_env.install_null_translations()
+ app.updater = None
+
+ if app.config.get('PROXY_FIX'):
+ from werkzeug.contrib.fixers import ProxyFix
+ app.wsgi_app = ProxyFix(app.wsgi_app)
+
+ auth = SpaceAuth(app)
+
+ def restrict_ip(prefix='', exclude=[]):
+ def decorator(f):
+ @wraps(f)
+ def func(*a, **kw):
+ r_addr = v4addr()
+ if not r_addr.startswith(prefix) or r_addr in exclude:
+ app.logger.info('got IP %s, rejecting', r_addr)
+ abort(403)
+
+ return f(*a, **kw)
+ return func
+ return decorator
+
+
+ @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}
+
+
+ @app.before_request
+ def make_connection():
+ conn = sqlite3.connect(app.config['DB'])
+ conn.row_factory = sqlite3.Row
+ conn.isolation_level = None # for autocommit mode
+ g.db = conn
+
+
+ @app.teardown_request
+ def close_connection(exception):
+ g.db.close()
+
+
+ @app.route('/')
+ def main_view():
+ return render_template('main.html', **now_at())
+
+
+ @app.route('/api')
+ def list_all():
+ data = now_at()
+
+ def prettify_user(xxx_todo_changeme):
+ (user, atime) = xxx_todo_changeme
+ if user == 'greenmaker':
+ user = 'dreammaker'
+ return {
+ 'login': user,
+ 'timestamp': atime,
+ 'pretty_time': strfts(atime),
+ }
+ result = {}
+ result['users'] = list(map(prettify_user, data.pop('users')))
+ result.update((k, len(v)) for k, v in list(data.items()))
+ res = make_response(json.dumps(result), 200)
+ res.headers['Access-Control-Allow-Origin'] = '*'
+ return res
+
+
+ def now_at():
+ result = dict()
+ devices = app.updater.get_active_devices()
+ device_infos = list(get_device_infos(g.db, list(devices.keys())))
+ device_infos.sort(key=lambda di: devices[di.hwaddr])
+ unknown = set(devices.keys()) - set(d.hwaddr for d in device_infos)
+ # das kektop sorting maschine
+ for name, prefixes in list(app.config['SPECIAL_DEVICES'].items()):
+ result[name] = set()
+ for u in unknown.copy():
+ if u.startswith(prefixes):
+ result[name].add(u)
+ unknown.discard(u)
+
+ result['unknown'] = unknown
+
+ users = {}
+ for info in device_infos:
+ if info.owner not in users and not info.ignored:
+ users[info.owner] = devices[info.hwaddr][0]
+ result['users'] = sorted(list(users.items()), key=lambda u_a: u_a[1], reverse=True)
+
+ return result
+
+
+ restrict_to_hs = restrict_ip(prefix=app.config['CLAIMABLE_PREFIX'],
+ exclude=app.config['CLAIMABLE_EXCLUDE'])
+
+
+ @app.route('/claim', methods=['GET'])
+ @restrict_to_hs
+ @login_required
+ def claim_form():
+ 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(v4addr())
+ ctx = None
+ if not hwaddr:
+ ctx = dict(error='Invalid device.')
+ else:
+ login = current_user.id
+ try:
+ g.db.execute('''
+ insert into devices (hwaddr, name, owner, ignored) values (?, ?, ?, ?)''',
+ [hwaddr, request.form['name'], login, False])
+ ctx = {}
+ 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])
+ 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
+ def account():
+ devices = get_user_devices(g.db, current_user.id)
+ 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])
+
+
+ def delete_device(conn, hwaddr, user):
+ return conn.execute('''
+ delete from devices where hwaddr = ? and owner = ?''',
+ [hwaddr, user])
+
+ @app.route('/devices/<id>/<action>/')
+ @login_required
+ def device(id, action):
+ user = current_user.id
+ if action == 'hide':
+ set_ignored(g.db, id, user, True)
+ if action == 'show':
+ set_ignored(g.db, id, user, False)
+ if action == 'delete':
+ delete_device(g.db, id, user)
+ return redirect(url_for('account'))
+
+
+ @app.route('/admin')
+ @cap_required('staff')
+ def admin():
+ data = now_at()
+ return render_template('admin.html', data=data)
+
+
+ #@app.before_first_request
+ def setup():
+ updater = DhcpdUpdater(app.config['LEASE_FILE'], app.config['TIMEOUT'],
+ app.config['LEASE_OFFSET'])
+ app.logger.info('parsing leases file for the first time')
+ updater._trigger_update()
+ app.logger.info('leases file parsed')
+ updater.start()
+ app.updater = updater
+
+ setup()
+
+ return app
+
+if __name__ == '__main__':
+ app.logger.setLevel(logging.DEBUG)
+ app.run('0.0.0.0', 8080, debug=True)