From 6912034ced9d7856722814bb61ebb51159630dd2 Mon Sep 17 00:00:00 2001 From: Tomek Dubrownik Date: Sun, 29 Jan 2012 02:34:11 +0100 Subject: [PATCH] basic user panel --- at.py | 121 ++++++++++++++++++++++++++------------ config.py | 4 +- static/css/basic.css | 17 ++++++ templates/account.html | 55 +++++++++++++++++ templates/basic.html | 24 ++++++++ templates/claim.html | 38 ++++++------ templates/main.html | 46 +++++++-------- templates/post_claim.html | 7 +-- templates/register.html | 45 +++++++++----- 9 files changed, 259 insertions(+), 98 deletions(-) create mode 100644 static/css/basic.css create mode 100644 templates/account.html create mode 100644 templates/basic.html diff --git a/at.py b/at.py index 21e4cc6..e702228 100644 --- a/at.py +++ b/at.py @@ -1,11 +1,11 @@ #!/usr/bin/env python2 # -*- coding: utf-8 -*- -import logging import sqlite3 import threading import traceback import json -from flask import Flask, render_template, abort, redirect, session, request, flash, g +from flask import Flask, render_template, abort, g, \ + redirect, session, request, flash, url_for from datetime import datetime from wsgiref import simple_server from pesto import Response, dispatcher_app @@ -18,8 +18,6 @@ import config app = Flask('at') app.secret_key = config.secret_key -logger = logging.getLogger() -conn = None updater = None from functools import wraps @@ -42,26 +40,32 @@ def req_to_ctx(): def strfts(ts, format='%d/%m/%Y %H:%M'): return datetime.fromtimestamp(ts).strftime(format) -def setup_db(): +@app.before_request +def make_connection(): conn = sqlite3.connect(config.db) conn.row_factory = sqlite3.Row conn.isolation_level = None # for autocommit mode - return conn + g.db = conn -DeviceInfo = namedtuple('DeviceInfo', ['hwaddr', 'owner', 'ignored']) -User = namedtuple('User', ['login', 'passwd', 'url']) +@app.teardown_request +def close_connection(exception): + g.db.close() + +DeviceInfo = namedtuple('DeviceInfo', ['hwaddr', 'name', 'owner', 'ignored']) +User = namedtuple('User', ['id', 'login', 'passwd', 'url']) def get_device_info(conn, hwaddr): return list(get_device_infos(conn, (hwaddrs,)))[0] def get_device_infos(conn, hwaddrs): - stmt = '''select hwaddr, name, ignored, login, url from + stmt = '''select hwaddr, name, ignored, userid, login, url from devices left join users on devices.owner = users.userid where devices.hwaddr in (''' + ','.join(['?'] * len(hwaddrs)) + ')' for row in conn.execute(stmt, hwaddrs): - owner = User(row['login'], None, row['url']) if row['login'] else None + owner = User(row['userid'], row['login'], None, row['url']) \ + if row['login'] else None ignored = row['ignored'] - yield DeviceInfo(row['hwaddr'], owner, ignored) + yield DeviceInfo(row['hwaddr'], row['name'], owner, ignored) class Updater(threading.Thread): def __init__(self, timeout, *a, **kw): @@ -91,7 +95,7 @@ class Updater(threading.Thread): self.lock.acquire() self.active[hwaddr] = (atime, ip, name) self.lock.release() - logger.info('updated %s with atime %s and ip %s', + app.logger.info('updated %s with atime %s and ip %s', hwaddr, strfts(atime), ip) class CapUpdater(Updater): @@ -102,15 +106,15 @@ class CapUpdater(Updater): while True: try: with open(self.cap_file, 'r', buffering=0) as f: - logger.info('Updater ready on cap file %s', self.cap_file) + 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) - logging.warning('Cap file %s closed, reopening', self.cap_file) + app.logger.warning('Cap file %s closed, reopening', self.cap_file) except Exception as e: - logging.error('Updater got an exception:\n' + \ + app.logger.error('Updater got an exception:\n' + \ traceback.format_exc(e)) sleep(10.0) @@ -126,7 +130,7 @@ class DnsmasqUpdater(Updater): try: mtime = os.stat(self.lease_file).st_mtime if mtime > self.last_modified: - logger.info('Lease file changed, updating') + app.logger.info('Lease file changed, updating') with open(self.lease_file, 'r') as f: for line in f: ts, hwaddr, ip, name, client_id = line.split(' ') @@ -134,7 +138,7 @@ class DnsmasqUpdater(Updater): self.last_modified = mtime sleep(3.0) except Exception as e: - logging.error('Updater got an exception:\n' + \ + app.logger.error('Updater got an exception:\n' + \ traceback.format_exc(e)) sleep(10.0) @@ -154,18 +158,17 @@ def list_all(): } result['users'] = map(prettify_user, result['users']) result['unknown'] = len(result['unknown']) - del result['login'] return json.dumps(result) def now_at(): devices = updater.get_active_devices() - device_infos = list(get_device_infos(conn, devices.keys())) + 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, login=session.get('login')) + return dict(users=users, unknown=unknown) restrict_to_hs = restrict_ip(prefix=config.claimable_prefix, exclude=config.claimable_exclude) @@ -178,12 +181,12 @@ def register_form(): @app.route('/register', methods=['POST']) @restrict_to_hs def register(): - login = request.form['login'] + login = request.form['login'].lower() url = request.form['url'] if 'wiki' in request.form: url = config.wiki_url % { 'login': login } try: - conn.execute('insert into users (login, url, pass) values (?, ?, ?)', + g.db.execute('insert into users (login, url, pass) values (?, ?, ?)', [login, url, sha256(request.form['password']).hexdigest()]) return redirect('/') except sqlite3.Error as e: @@ -195,21 +198,22 @@ def register(): def login_form(): return render_template('login.html', **req_to_ctx()) -def get_credentials(login, password): - row = conn.execute('select userid from users where login = ? and pass = ?', - [login, sha256(password).hexdigest()]).fetchone() - return row and row['userid'] +def get_user(conn, login, password): + row = conn.execute('select userid, login, pass, url from users where\ + login = ? and pass = ?', [login, sha256(password).hexdigest()]).fetchone() + return row and User(row['userid'], row['login'], None, row['url']) @app.route('/login', methods=['POST']) @restrict_to_hs def login(): - login = request.form.get('login', '') + login = request.form.get('login', '').lower() pwd = request.form.get('password', '') goto = request.values.get('goto') or '/' - userid = get_credentials(login, pwd) - if userid: - session['userid'] = userid - session['login'] = login + user = get_user(g.db, login, pwd) + if user: + session['userid'] = user.id + session['login'] = user.login + session['user'] = user return redirect(goto) else: flash('Username or password invalid', category='error') @@ -236,8 +240,7 @@ def login_required(f): @login_required def claim_form(): hwaddr, name = updater.get_device(request.remote_addr) - return render_template('claim.html', hwaddr=hwaddr, name=name, - login=session['login']) + return render_template('claim.html', hwaddr=hwaddr, name=name) @app.route('/claim', methods=['POST']) @restrict_to_hs @@ -250,18 +253,60 @@ def claim(): else: userid = session['userid'] try: - conn.execute('insert into devices (hwaddr, name, owner, ignored)\ + g.db.execute('insert into devices (hwaddr, name, owner, ignored)\ values (?, ?, ?, ?)', [hwaddr, request.form['name'], userid, False]) ctx = {} except sqlite3.Error as e: ctx = { 'error': 'Could not add device! Perhaps someone claimed it?' } 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.id]) + return (DeviceInfo(row['hwaddr'], row['name'], user, row['ignored']) for + row in devs) + +def set_password(conn, user, password): + return conn.execute('update users set pass = ? where userid = ?', + [sha256(password).hexdigest(), user.id]) + +@app.route('/account', methods=['GET','POST']) +@login_required +def account(): + if request.method == 'POST': + old = request.form['old'] + if get_user(g.db, session['login'], old) and \ + set_password(g.db, session['user'], request.form['new']): + flash('Password changed', category='message') + else: + flash('Could not change password!', category='error') + devices = get_user_devices(g.db, session['user']) + 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.id]) + +def delete_device(conn, hwaddr, user): + return conn.execute('delete from devices where hwaddr = ? and owner = ?', + [hwaddr, user.id]) + +@app.route('/devices///') +@login_required +def device(id, action): + user = session['user'] + 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')) + port = 8080 if __name__ == '__main__': - logger.setLevel(logging.DEBUG) - logger.addHandler(logging.StreamHandler()) - conn = setup_db() + import logging + app.logger.setLevel(logging.DEBUG) updater = DnsmasqUpdater(config.lease_file, config.lease_offset, config.timeout) updater.start() - app.run('0.0.0.0', config.port) + app.run('0.0.0.0', config.port, debug=config.debug) diff --git a/config.py b/config.py index e89b78c..67f9b04 100644 --- a/config.py +++ b/config.py @@ -1,3 +1,5 @@ +debug = True + port = 8080 db = './at.db' @@ -8,7 +10,7 @@ timeout = 3000 wiki_url = 'http://hackerspace.pl/wiki/doku.php?id=people:%(login)s:start' -claimable_prefix = '192.168.1.' +claimable_prefix = '' #'192.168.1.' claimable_exclude = [ # '127.0.0.1', ] diff --git a/static/css/basic.css b/static/css/basic.css new file mode 100644 index 0000000..d347512 --- /dev/null +++ b/static/css/basic.css @@ -0,0 +1,17 @@ +p.error { + color: red +} +p.message { + color: green +} +table.devices, .devices td, .devices th { + padding: .5em; + border: 1px solid black; + border-collapse: collapse; +} +td.invisible { + background-color: red +} +td.visible { + background-color: green +} diff --git a/templates/account.html b/templates/account.html new file mode 100644 index 0000000..91cd668 --- /dev/null +++ b/templates/account.html @@ -0,0 +1,55 @@ +{% extends "basic.html" %} +{% block content %} +Back to homepage +

Account settings

+{% for msg in get_flashed_messages(True) %} +

{{ msg[1] }}

+{% endfor %} +

Change password

+
+ + + + + + + + + + + + + + + + + +
Current password:
New password:
Confirm new password:
+
+

Claimed devices

+ + + + + + + + +{% for device in devices %} + + + + {% if device.ignored %} + + + {% else %} + + + {% endif %} + + + +{% endfor%} +
MACDevice nameVisibleToggle visibilityDelete
{{ device.hwaddr }}{{ device.name }}make visiblevisiblemake invisibledelete device
+

claim this device +{% endblock %} diff --git a/templates/basic.html b/templates/basic.html new file mode 100644 index 0000000..2ff8808 --- /dev/null +++ b/templates/basic.html @@ -0,0 +1,24 @@ + + + + {% block head %} + + {% block title %}Now at hackerspace{% endblock %} + {% endblock %} + + + {% block body %} +

+ {% block content %} + {% endblock %} + {% endblock %} + + diff --git a/templates/claim.html b/templates/claim.html index f86a3fb..413b7bc 100644 --- a/templates/claim.html +++ b/templates/claim.html @@ -1,17 +1,21 @@ - - -

Claiming a device

-{% if not hwaddr %} -

Unknown MAC. Are you sure you're in the hackerspace?

-{% else %} -You are about to claim {{ hwaddr }} as {{ login }}. Do you wish to continue? - - - - - - -
Device name (optional):
-{% endif %} - - +{% extends "basic.html" %} +{% block content %} +

Claiming a device

+ {% if not hwaddr %} +

Unknown MAC. Are you sure you're in the hackerspace?

+ {% else %} + You are about to claim {{ hwaddr }} as {{ session.login }}. Do you wish to continue? + + + + + + + + + + + +
Device name (optional):
+ {% endif %} +{% endblock %} diff --git a/templates/main.html b/templates/main.html index 038afd7..198b4d0 100644 --- a/templates/main.html +++ b/templates/main.html @@ -1,24 +1,22 @@ - - -

Now at hackerspace!

- -Recently at hackerspace: - -
-There are {{ unknown|length }} unknown devices operating. Claim this device! - - +{% extends "basic.html" %} +{% block title %} +Now at hackerspace +{% endblock %} +{% block content %} +

Now at hackerspace!

+ Recently at hackerspace: + +
+ There are {{ unknown|length }} unknown devices operating. + Claim this device! +{% endblock %} diff --git a/templates/post_claim.html b/templates/post_claim.html index 981eae2..858323b 100644 --- a/templates/post_claim.html +++ b/templates/post_claim.html @@ -1,5 +1,5 @@ - - +{% extends "basic.html" %} +{% block content %} {% if error %}

Error!

{{ error }}

@@ -8,5 +8,4 @@ Congratulations, you just claimed this device! go back {% endif %} - - +{% endblock %} diff --git a/templates/register.html b/templates/register.html index b3ba570..9c6399f 100644 --- a/templates/register.html +++ b/templates/register.html @@ -1,19 +1,36 @@ - - -

Register a new account +{% extends "basic.html" %} +{% block content %} +

Register a new account

{% for error in get_flashed_messages() %}

{{ error }}

{% endfor %}
- - - - - - - -
login
password
confirm password
homepage url
use wiki page as url
+ + + + + + + + + + + + + + + + + + + + + + + + + +
login
password
confirm password
homepage url
use wiki page as url
- - +{% endblock %}