#!/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)