1
0
mirror of https://github.com/Tygs/0bin.git synced 2023-08-10 21:13:00 +03:00

404 css jolifygationnement merge

This commit is contained in:
papee 2020-08-12 15:23:42 +02:00
commit 425a947051
8 changed files with 258 additions and 55 deletions

View File

@ -3,3 +3,4 @@ clize==4.1.1
lockfile==0.12.2
sigtools==2.0.2
bottle==0.12.18
Beaker==1.11.0

View File

@ -7,10 +7,16 @@
import sys
import re
import secrets
import hashlib
import _thread as thread
from zerobin.utils import settings, SettingsValidationError, drop_privileges
from zerobin.utils import (
settings,
SettingsValidationError,
drop_privileges,
ensure_var_env,
hash_password,
)
from zerobin.routes import get_app
from zerobin.paste import Paste
@ -43,13 +49,7 @@ def runserver(
settings.PASTE_ID_LENGTH = paste_id_length or settings.PASTE_ID_LENGTH
settings.DEBUG = bool(debug) if debug is not None else settings.DEBUG
settings.VAR_DIR.mkdir(exist_ok=True, parents=True)
settings.PASTE_FILES_ROOT.mkdir(exist_ok=True, parents=True)
secret_key_file = settings.VAR_DIR / "secret_key"
if not secret_key_file.is_file():
secret_key_file.write_text(secrets.token_urlsafe(64))
settings.SECRET_KEY = secret_key_file.read_text()
ensure_var_env()
try:
_, app = get_app(debug, settings_file, compressed_static, settings=settings)
@ -60,6 +60,7 @@ def runserver(
thread.start_new_thread(drop_privileges, (settings.USER, settings.GROUP))
if settings.DEBUG:
print(f"Admin URL: http://{settings.HOST}:{settings.PORT}{settings.ADMIN_URL}")
run(
app, host=settings.HOST, port=settings.PORT, reloader=True, server=server,
)
@ -104,13 +105,51 @@ def delete_paste(*pastes, quiet=False):
print("Paste {} doesn't exist".format(paste_uuid))
def print_admin_url():
""" Print the route to the 0bin admin.
The admin route is generated by zerobin so that bots won't easily
bruteforce it. To get the full URL, simply preppend your website domain
name to it.
E.G:
If this command prints:
"/admin/f1cc3972a4b933c734b37906940cf69886161492ee4eb7c1faff5d7b5e92efb8"
Then the admin url is:
"http://yourdomain.com/admin/f1cc3972a4b933c734b37906940cf69886161492ee4eb7c1faff5d7b5e92efb8"
Adapt "http" and "yourdomain.com" to your configuration.
In debug mode, the dev server will print the url when starting.
"""
ensure_var_env()
print(settings.ADMIN_URL)
def set_admin_password(password):
""" Set the password for the admin
It will be stored as a scrypt hash in a file in the var dir.
"""
ensure_var_env()
settings.ADMIN_PASSWORD_FILE.write_bytes(hash_password(password))
def main():
subcommands = [runserver, delete_paste]
subcommands = [runserver, delete_paste, print_admin_url, set_admin_password]
subcommand_names = [
clize.util.name_py2cli(name)
for name in clize.util.dict_from_names(subcommands).keys()
]
if len(sys.argv) < 2 or sys.argv[1] not in subcommand_names:
sys.argv.insert(1, subcommand_names[0])
clize.run(runserver, delete_paste)
clize.run(runserver, delete_paste, print_admin_url, set_admin_password)

View File

@ -19,11 +19,6 @@ STATIC_FILES_ROOT = ROOT_DIR / "static"
# otherwise, will use the ordinary files
COMPRESSED_STATIC_FILES = False
# absolute path where the paste files should be store
# default in projectdirectory/static/content/
# use "/" even under Windows
PASTE_FILES_ROOT = VAR_DIR / "content"
# A tuple of absolute paths of directory where to look the template for
# the first one will be the first to be looked into
# if you want to override, it needs to be it a directory at the begining of
@ -48,7 +43,12 @@ GROUP = None
# Be carreful if your site have to many pastes this can hurt your hard drive performances.
# Refresh counter interval. Default to every minute after a paste.
DISPLAY_COUNTER = True
REFRESH_COUNTER = 60 * 1
REFRESH_COUNTER = 60 * 1 # Fill this if you want to
ADMIN_CREDENTIALS = {
"username": None,
"password": None,
}
# Names/links to insert in the menu bar.
# Any link with "mailto:" will be escaped to prevent spam
@ -68,3 +68,4 @@ MAX_SIZE = 1024 * 500
# total number of unique pastes can be calculated as 2^(6*PASTE_ID_LENGTH)
# for PASTE_ID_LENGTH=8, for example, it's 2^(6*8) = 281 474 976 710 656
PASTE_ID_LENGTH = 8

View File

@ -7,20 +7,37 @@ import sys
import _thread as thread
import urllib.parse as urlparse
from urllib.parse import urlparse, parse_qs
from datetime import datetime, timedelta
from zerobin import __version__
from zerobin.utils import settings, SettingsValidationError, dmerge
import bottle
from bottle import Bottle, static_file, view, request, HTTPResponse
from bottle import (
Bottle,
debug,
static_file,
view,
request,
HTTPResponse,
redirect,
abort,
)
from beaker.middleware import SessionMiddleware
from zerobin import __version__
from zerobin.utils import (
settings,
SettingsValidationError,
ensure_var_env,
check_password,
)
from zerobin.paste import Paste
app = Bottle()
ensure_var_env()
GLOBAL_CONTEXT = {
"settings": settings,
"VERSION": __version__,
@ -29,23 +46,79 @@ GLOBAL_CONTEXT = {
}
app = Bottle()
ADMIN_LOGIN_URL = settings.ADMIN_URL + "login/"
@app.route("/")
@view("home")
def index():
return GLOBAL_CONTEXT
@app.route("/faq/")
@app.get("/faq/")
@view("faq")
def faq():
return GLOBAL_CONTEXT
@app.route("/paste/create", method="POST")
@app.get(settings.ADMIN_URL)
@app.post(settings.ADMIN_URL)
@view("admin")
def admin():
session = request.environ.get("beaker.session")
if not session or not session.get("is_authenticated"):
redirect(ADMIN_LOGIN_URL)
paste_id = request.forms.get("paste", "")
if paste_id:
try:
if "/paste/" in paste_id:
paste_id = urlparse(paste_id).path.split("/path/")[-1]
paste = Paste.load(paste_id)
paste.delete()
except (TypeError, ValueError, FileNotFoundError):
return {"status": "error", "message": f"Cannot find paste '{paste_id}'"}
return {"status": "ok", "message": "Paste deleted"}
return {"status": "ok", **GLOBAL_CONTEXT}
@app.get(ADMIN_LOGIN_URL)
@app.post(ADMIN_LOGIN_URL)
@view("login")
def login():
password = request.forms.get("password")
if password:
if not check_password(password):
return {"status": "error", "message": "Wrong password", **GLOBAL_CONTEXT}
session = request.environ.get("beaker.session")
session["is_authenticated"] = True
session.save()
redirect(settings.ADMIN_URL)
return {"status": "ok", **GLOBAL_CONTEXT}
@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")
def create_paste():
try:
body = urlparse.parse_qs(request.body.read(int(settings.MAX_SIZE * 1.1)))
body = parse_qs(request.body.read(int(settings.MAX_SIZE * 1.1)))
except ValueError:
return {"status": "error", "message": "Wrong data payload."}
@ -95,7 +168,7 @@ def create_paste():
}
@app.route("/paste/:paste_id", method="GET")
@app.get("/paste/:paste_id")
@view("paste")
def display_paste(paste_id):
@ -125,11 +198,10 @@ def display_paste(paste_id):
except (TypeError, ValueError):
return error404(ValueError)
context = {"paste": paste, "keep_alive": keep_alive}
return dmerge(context, GLOBAL_CONTEXT)
return {"paste": paste, "keep_alive": keep_alive, **GLOBAL_CONTEXT}
@app.route("/paste/:paste_id", method="DELETE")
@app.delete("/paste/:paste_id")
def delete_paste(paste_id):
try:
@ -154,7 +226,7 @@ def error404(code):
return GLOBAL_CONTEXT
@app.route("/static/<filename:path>")
@app.get("/static/<filename:path>")
def server_static(filename):
return static_file(filename, root=settings.STATIC_FILES_ROOT)
@ -187,3 +259,14 @@ def get_app(debug=None, settings_file="", compressed_static=None, settings=setti
bottle.debug(True)
return settings, app
app = SessionMiddleware(
app,
{
"session.type": "file",
"session.cookie_expires": 300,
"session.data_dir": settings.SESSIONS_DIR,
"session.auto": True,
},
)

View File

@ -2,9 +2,10 @@ import time
import os
import glob
import tempfile
import sys
import codecs
import unicodedata
import hashlib
import secrets
from functools import partial
from zerobin import default_settings
@ -12,16 +13,10 @@ from zerobin import default_settings
try:
from zerobin.privilege import drop_privileges_permanently, coerce_user, coerce_group
except (AttributeError):
pass # privilege does't work on several plateform
pass # privilege does't work on several plateforms
try:
from runpy import run_path
except ImportError:
# python-2.6 or earlier - use simplier less-optimized execfile()
def run_path(file_path):
mod_globals = {"__file__": file_path}
execfile(file_path, mod_globals)
return mod_globals
from runpy import run_path
def drop_privileges(user=None, group=None, wait=5):
@ -47,17 +42,6 @@ def drop_privileges(user=None, group=None, wait=5):
print("Failed to drop privileges. Running with current user.")
def dmerge(*args):
"""
Return new directionay being the sum of all merged dictionaries passed
as arguments
"""
dictionary = {}
for arg in args:
dictionary.update(arg)
return dictionary
class SettingsValidationError(Exception):
pass
@ -134,3 +118,55 @@ def as_unicode(obj):
return unicode(obj)
except NameError:
return str(obj)
def ensure_var_env():
""" Ensure all the variable things we generate are available.
This will make sure we have:
- a var dir
- a content dir
- a secret key
- an admin URL
"""
settings.VAR_DIR.mkdir(exist_ok=True, parents=True)
settings.PASTE_FILES_ROOT = settings.VAR_DIR / "content"
settings.PASTE_FILES_ROOT.mkdir(exist_ok=True)
settings.SESSIONS_DIR = settings.VAR_DIR / "sessions"
settings.SESSIONS_DIR.mkdir(exist_ok=True)
secret_key_file = settings.VAR_DIR / "secret_key"
if not secret_key_file.is_file():
secret_key_file.write_text(secrets.token_urlsafe(64))
settings.SECRET_KEY = secret_key_file.read_text()
admin_password_file = settings.VAR_DIR / "admin_password"
if not secret_key_file.is_file():
admin_password_file.write_text(
"No password set. Use the set_admin_passord command. Don't write this file by hand."
)
settings.ADMIN_PASSWORD_FILE = admin_password_file
payload = ("admin" + settings.SECRET_KEY).encode("ascii")
settings.ADMIN_URL = "/admin/" + hashlib.sha256(payload).hexdigest() + "/"
def hash_password(password):
return hashlib.scrypt(
password.encode("utf8"),
salt=settings.SECRET_KEY.encode("ascii"),
n=16384,
r=8,
p=1,
dklen=32,
)
def check_password(password):
try:
return settings.ADMIN_PASSWORD_FILE.read_bytes() == hash_password(password)
except (FileNotFoundError, AttributeError):
return False

25
zerobin/views/admin.tpl Normal file
View File

@ -0,0 +1,25 @@
<form action="./delete/" method="post">
%if status == "error":
<div class="alert alert-danger" role="alert alert-danger">
{{message}}
</div>
%end
<div>
<div class="form-group">
<label>Paste to delete</label>
<input name="paste" type="text" class="form-control" placeholder="Paste URL or ID">
</div>
<button type="submit" class="btn btn-black">Delete</button>
</div>
</form>
<form action="./logout/" method="post">
<div>
<button type="submit" class="btn btn-black">Logout</button>
</div>
</form>
% rebase('base', settings=settings, pastes_count=pastes_count)

View File

@ -35,14 +35,14 @@
<nav>
<ul>
<li class="submenu"><a href="#" @click.prevent="openPreviousPastesMenu = !openPreviousPastesMenu">Previous
pastes +</a>
pastes v</a>
<ul class="previous-pastes" id="topmenu" v-if="openPreviousPastesMenu"
@mouseleave="openPreviousPastesMenu =false">
<li class="item active" v-if="previousPastes.length === 0">
<a href="#">No paste yet...</a>
<a href="#">No paste yet</a>
</li>
<li class="item active" v-for="paste in previousPastes">
<a :href="paste.link" @click="forceLoad(paste.link)">{% paste.displayDate %}.</a>
<a :href="paste.link" @click="forceLoad(paste.link)">{% paste.displayDate %}</a>
</li>
</ul>
</li>

18
zerobin/views/login.tpl Normal file
View File

@ -0,0 +1,18 @@
<form action="." method="post">
<div class="login-form">
<form>
<label>Password</label>
%if status == "error":
<div class="alert alert-danger" role="alert alert-danger">
{{message}}
</div>
%end
<input type="password" class="form-control" placeholder="Password" name="password">
<button type="submit" class="btn btn-black">Login</button>
</form>
</div>
</form>
% rebase('base', settings=settings, pastes_count=pastes_count)