0bin/zerobin/routes.py

268 lines
6.7 KiB
Python
Raw Normal View History

2012-05-14 19:17:49 +04:00
"""
Script including controller, rooting, and dependency management.
2012-05-14 19:17:49 +04:00
"""
import os
2015-05-10 20:19:02 +03:00
2020-08-14 19:18:17 +03:00
from distutils.util import strtobool
2015-05-10 20:19:02 +03:00
2020-08-14 19:18:17 +03:00
from urllib.parse import urlparse
2015-05-10 20:19:02 +03:00
2012-05-14 19:17:49 +04:00
from datetime import datetime, timedelta
import bottle
2020-08-12 16:21:49 +03:00
from bottle import (
Bottle,
static_file,
view,
request,
HTTPResponse,
redirect,
)
2012-05-14 19:17:49 +04:00
2020-08-12 10:19:38 +03:00
from beaker.middleware import SessionMiddleware
from zerobin import __version__
from zerobin.utils import (
SettingsValidationError,
2020-08-13 15:36:42 +03:00
ensure_app_context,
2020-08-12 10:19:38 +03:00
check_password,
2020-08-13 15:36:42 +03:00
settings,
2020-08-12 10:19:38 +03:00
)
2015-05-10 20:19:02 +03:00
from zerobin.paste import Paste
2012-05-14 19:17:49 +04:00
2020-08-13 15:36:42 +03:00
ensure_app_context()
2020-08-12 10:19:38 +03:00
2012-05-14 19:17:49 +04:00
GLOBAL_CONTEXT = {
2020-08-11 12:55:29 +03:00
"settings": settings,
2020-08-11 17:37:03 +03:00
"VERSION": __version__,
2020-08-11 12:55:29 +03:00
"pastes_count": Paste.get_pastes_count(),
"refresh_counter": datetime.now(),
2012-05-14 19:17:49 +04:00
}
2012-05-14 19:17:49 +04:00
2020-08-12 16:21:49 +03:00
app = Bottle()
ADMIN_LOGIN_URL = settings.ADMIN_URL + "login/"
2020-08-12 10:19:38 +03:00
2020-08-11 12:55:29 +03:00
@app.route("/")
@view("home")
2012-05-14 19:17:49 +04:00
def index():
return GLOBAL_CONTEXT
2020-08-12 16:21:49 +03:00
@app.get("/faq/")
2020-08-11 12:55:29 +03:00
@view("faq")
2012-05-21 19:14:01 +04:00
def faq():
2012-05-21 14:25:24 +04:00
return GLOBAL_CONTEXT
2020-08-18 10:17:58 +03:00
@app.route("/buy_bitcoin")
@view("buy_bitcoin")
def index():
return GLOBAL_CONTEXT
2012-05-21 14:25:24 +04:00
2020-08-12 16:21:49 +03:00
@app.get(settings.ADMIN_URL)
@app.post(settings.ADMIN_URL)
2020-08-12 10:19:38 +03:00
@view("admin")
def admin():
session = request.environ.get("beaker.session")
if not session or not session.get("is_authenticated"):
2020-08-12 16:21:49 +03:00
redirect(ADMIN_LOGIN_URL)
paste_id = request.forms.get("paste", "")
if paste_id:
try:
if "/paste/" in paste_id:
2020-08-12 16:39:07 +03:00
paste_id = urlparse(paste_id).path.split("/paste/")[-1]
2020-08-12 16:21:49 +03:00
paste = Paste.load(paste_id)
paste.delete()
except (TypeError, ValueError, FileNotFoundError):
2020-08-12 16:39:07 +03:00
return {
"status": "error",
"message": f"Cannot find paste '{paste_id}'",
**GLOBAL_CONTEXT,
}
2020-08-12 10:19:38 +03:00
2020-08-12 16:39:07 +03:00
return {"status": "ok", "message": "Paste deleted", **GLOBAL_CONTEXT}
2020-08-12 10:19:38 +03:00
2020-08-14 16:08:55 +03:00
return {"status": "ok", "message": "", **GLOBAL_CONTEXT}
2020-08-12 10:19:38 +03:00
2020-08-12 16:21:49 +03:00
@app.get(ADMIN_LOGIN_URL)
@app.post(ADMIN_LOGIN_URL)
@view("login")
2020-08-12 10:19:38 +03:00
def login():
password = request.forms.get("password")
if password:
if not check_password(password):
2020-08-12 16:21:49 +03:00
return {"status": "error", "message": "Wrong password", **GLOBAL_CONTEXT}
2020-08-12 10:19:38 +03:00
session = request.environ.get("beaker.session")
session["is_authenticated"] = True
session.save()
redirect(settings.ADMIN_URL)
2020-08-12 16:21:49 +03:00
return {"status": "ok", **GLOBAL_CONTEXT}
2020-08-12 10:19:38 +03:00
2020-08-12 16:21:49 +03:00
@app.post(settings.ADMIN_URL + "logout/")
@view("logout")
def logout():
session = request.environ.get("beaker.session")
session["is_authenticated"] = False
session.save()
redirect("/")
@app.post("/paste/create")
2012-05-14 19:17:49 +04:00
def create_paste():
2020-08-11 12:55:29 +03:00
2020-08-14 16:08:55 +03:00
# Reject what is too small, too big, or what does not seem encrypted to
# limit a abuses
content = request.forms.get("content", "")
if '{"iv":' not in content or not (0 < len(content) < settings.MAX_SIZE):
2020-08-11 12:55:29 +03:00
return {"status": "error", "message": "Wrong data payload."}
2015-05-10 20:19:02 +03:00
2020-08-14 16:08:55 +03:00
expiration = request.forms.get("expiration", "burn_after_reading")
title = request.forms.get("title", "")
2020-08-14 17:41:45 +03:00
btc_tip_address = request.forms.get("btcTipAddress", "")
2015-05-10 20:19:02 +03:00
2020-08-14 16:08:55 +03:00
paste = Paste(
expiration=expiration,
content=content,
uuid_length=settings.PASTE_ID_LENGTH,
title=title,
2020-08-14 17:41:45 +03:00
btc_tip_address=btc_tip_address,
2020-08-14 16:08:55 +03:00
)
paste.save()
2015-05-10 20:19:02 +03:00
2020-08-14 16:08:55 +03:00
# If refresh time elapsed pick up, update the counter
if settings.DISPLAY_COUNTER:
2015-05-10 20:19:02 +03:00
2020-08-14 16:08:55 +03:00
paste.increment_counter()
2015-05-10 20:19:02 +03:00
2020-08-14 16:08:55 +03:00
now = datetime.now()
timeout = GLOBAL_CONTEXT["refresh_counter"] + timedelta(
seconds=settings.REFRESH_COUNTER
)
if timeout < now:
GLOBAL_CONTEXT["pastes_count"] = Paste.get_pastes_count()
GLOBAL_CONTEXT["refresh_counter"] = now
2012-05-14 19:17:49 +04:00
2020-08-14 16:08:55 +03:00
return {"status": "ok", "paste": paste.uuid, "owner_key": paste.owner_key}
2012-05-14 19:17:49 +04:00
2020-08-12 16:21:49 +03:00
@app.get("/paste/:paste_id")
2020-08-11 12:55:29 +03:00
@view("paste")
2012-05-14 19:17:49 +04:00
def display_paste(paste_id):
now = datetime.now()
keep_alive = False
try:
paste = Paste.load(paste_id)
# Delete the paste if it expired:
2015-05-10 20:19:02 +03:00
if not isinstance(paste.expiration, datetime):
2012-05-14 19:17:49 +04:00
# burn_after_reading contains the paste creation date
# if this read appends 10 seconds after the creation date
# we don't delete the paste because it means it's the redirection
# to the paste that happens during the paste creation
try:
2020-08-11 12:55:29 +03:00
keep_alive = paste.expiration.split("#")[1]
keep_alive = datetime.strptime(keep_alive, "%Y-%m-%d %H:%M:%S.%f")
2012-05-14 19:17:49 +04:00
keep_alive = now < keep_alive + timedelta(seconds=10)
except IndexError:
keep_alive = False
if not keep_alive:
paste.delete()
elif paste.expiration < now:
paste.delete()
raise ValueError()
except (TypeError, ValueError):
return error404(ValueError)
2020-08-12 10:19:38 +03:00
return {"paste": paste, "keep_alive": keep_alive, **GLOBAL_CONTEXT}
2012-05-14 19:17:49 +04:00
2020-08-12 16:21:49 +03:00
@app.delete("/paste/:paste_id")
2020-08-11 17:37:03 +03:00
def delete_paste(paste_id):
try:
paste = Paste.load(paste_id)
except (TypeError, ValueError):
return error404(ValueError)
if paste.owner_key != request.forms.get("owner_key", None):
return HTTPResponse(status=403, body="Wrong owner key")
paste.delete()
return {
"status": "ok",
"message": "Paste deleted",
}
2012-05-14 19:17:49 +04:00
@app.error(404)
2020-08-11 12:55:29 +03:00
@view("404")
2012-05-14 19:17:49 +04:00
def error404(code):
return GLOBAL_CONTEXT
2020-08-12 16:21:49 +03:00
@app.get("/static/<filename:path>")
def server_static(filename):
return static_file(filename, root=settings.STATIC_FILES_ROOT)
2020-08-13 15:36:42 +03:00
def get_app(debug=None, config_dir="", data_dir=""):
"""
Return a tuple (settings, app) configured using passed
parameters and/or a setting file.
"""
2020-08-13 15:36:42 +03:00
data_dir = data_dir or os.environ.get("ZEROBIN_DATA_DIR")
config_dir = config_dir or os.environ.get("ZEROBIN_CONFIG_DIR")
2020-08-13 15:36:42 +03:00
ensure_app_context(config_dir=config_dir, data_dir=data_dir)
2012-05-17 13:13:40 +04:00
2020-08-14 19:18:17 +03:00
if debug is None:
settings.DEBUG = bool(
strtobool(os.environ.get("ZEROBIN_DEBUG", str(settings.DEBUG)))
)
else:
settings.DEBUG = debug
2012-05-17 13:13:40 +04:00
2020-08-13 15:36:42 +03:00
settings.DISPLAY_COUNTER = bool(
os.environ.get("ZEROBIN_DISPLAY_COUNTER", settings.DISPLAY_COUNTER)
)
settings.REFRESH_COUNTER = int(
os.environ.get("ZEROBIN_REFRESH_COUNTER", settings.REFRESH_COUNTER)
)
settings.MAX_SIZE = int(os.environ.get("ZEROBIN_MAX_SIZE", settings.MAX_SIZE))
settings.PASTE_ID_LENGTH = int(
os.environ.get("ZEROBIN_PASTE_ID_LENGTH", settings.PASTE_ID_LENGTH)
)
2012-05-14 19:17:49 +04:00
2020-08-13 15:36:42 +03:00
if settings.PASTE_ID_LENGTH < 4:
raise SettingsValidationError("PASTE_ID_LENGTH cannot be lower than 4")
return settings, app
2020-08-12 16:21:49 +03:00
app = SessionMiddleware(
app,
{
"session.type": "file",
"session.cookie_expires": 300,
"session.data_dir": settings.SESSIONS_DIR,
"session.auto": True,
},
)