#!/usr/bin/env python3 # this is a quick hack, to be improved later. trying to do the simplest # thing that will possibly work. ## # TODO ## ## * put page content into templates/static files ## * clean up FIXME import bottle from bottle import route, run, request, response, redirect from bottle import HTTPError, template from sqlalchemy.ext.declarative import declarative_base from bottle.ext import sqlalchemy from pprint import pprint from sqlalchemy import create_engine import urllib.parse import os import random import string from datetime import datetime, timedelta VERSION = '1.0.0' PORT = os.environ.get('PORT', 8080) DEBUG = os.environ.get('DEBUG', False) SQLITE_FILENAME = os.environ.get('SQLITE_FILENAME','/data/db.sqlite') DATABASE_URL = os.environ.get('DATABASE_URL','sqlite:///' + SQLITE_FILENAME) ADMIN_PSK = os.environ.get('ADMIN_PSK','hunter2') # sorry for global SQLBASE = declarative_base() # FIXME make this skip letters not in base58 def randomUpper(count): return ''.join(random.choice(string.ascii_uppercase) for _ in range(count)) # FIXME maybe make the IDs longer def genRandomTVID(): tvid = str(randomUpper(3) + "-" + randomUpper(3)) return tvid def serve(): app = bottle.Bottle() # pull in models from .db import TV engine = create_engine(DATABASE_URL, echo=False) plugin = sqlalchemy.Plugin( engine, SQLBASE.metadata, keyword='db', create=True, commit=True, use_kwargs=False ) app.install(plugin) # here we cookie them if they have no cookie, then redirect them to the # cookie'd value (whether preexisting or new). @app.get('/') def indexpage(): c = request.get_cookie("displayid") if c: # redirect return redirect('/tv/' + c) else: newid = genRandomTVID() response.set_cookie("displayid", newid) return redirect('/tv/' + newid) @app.get('/style.css') def stylesheet(): response.content_type = 'text/css' return template('style') # here we check to see if they have a redirect URL in the db. if they do # we send them there. if they don't, we display their ID really big, # reloading the page once per hour. @app.get('/tv/') def tvpage(db, displayid=None): # FIXME check for cookie, this is broken if displayid is None: return template('nocookie') # check db for tv id tv = db.query(TV).filter_by(displayid=displayid).first() if tv: tv.lastSeen = datetime.now() db.add(tv) if tv.target: return redirect(tv.target) else: return template('displayid', id=displayid, version=VERSION) else: # otherwise, just show their display ID bigly and keep them # bouncing periodically until some admin configures them # a target: # update lastseen here: newtv = TV(displayid=displayid,lastSeen=datetime.now()) db.add(newtv) return template('displayid', id=displayid, version=VERSION) @app.get('/admin/edit/') def displayeditform(db, displayid=None): c = request.get_cookie("psk") if not c: return redirect('/login') if c != ADMIN_PSK: return redirect('/logout') if not displayid: return redirect('/admin') tv = db.query(TV).filter_by(displayid=displayid).first() if tv is None: return redirect('/admin') return template('displayeditform', tv=tv, version=VERSION) @app.post('/admin/edit') def displayedithandler(db): # FIXME SECURITY csrf issue c = request.get_cookie("psk") if not c: return redirect('/login') if c != ADMIN_PSK: return redirect('/logout') displayid = request.forms.get('displayid') tv = db.query(TV).filter_by(displayid=displayid).first() if tv is None: return redirect('/admin') # FIXME make sure this is a valid URL tv.target = request.forms.get('target') tv.memo = request.forms.get('formmemo') db.add(tv) db.commit() return redirect('/admin') # here we display the administration list of TVs if logged in # if logged out then redirect to /login # FIXME make this use sessions instead of just storing PSK in a cookie # https://bottlepy.org/docs/dev/recipes.html @app.get('/admin') def adminpage(db): c = request.get_cookie("psk") if not c: return redirect('/login') if c != ADMIN_PSK: return redirect('/logout') # first, cleanup db of old entries: week_ago = datetime.now() - timedelta(days=7) db.query(TV).filter(TV.lastSeen < week_ago).delete() db.commit() tvs = db.query(TV).order_by(TV.lastSeen.desc()) response.headers['Cache-Control'] = 'no-cache' return template('adminpanel', tvs=tvs, version=VERSION) # here we ask for a password: @app.get('/login') def loginform(): msg = request.GET.msg return template('loginform', version=VERSION, msg=msg) @app.post('/checklogin') def checklogin(): attemptedPass = request.forms.get('password') if not attemptedPass: return redirect( '/login?msg=' + urllib.parse.quote_plus(u"Incorrect password.") ) if attemptedPass != ADMIN_PSK: return redirect( '/login?msg=' + urllib.parse.quote_plus(u"Incorrect password.") ) # password is right, cookie them: response.set_cookie("psk", attemptedPass) return redirect('/admin') @app.get('/logout') def logout(): response.set_cookie("psk", "") return redirect('/login') app.run(host='0.0.0.0', port=PORT, debug=DEBUG)