add filekeys

This commit is contained in:
ed 2021-09-15 23:17:02 +02:00
parent abc0424c26
commit ef1c55286f
5 changed files with 56 additions and 5 deletions

View file

@ -834,6 +834,11 @@ on public copyparty instances with anonymous upload enabled:
* unless `--no-readme` is set: by uploading/modifying a file named `readme.md`
* if `move` access is granted AND none of `--no-logues`, `--no-dot-mv`, `--no-dot-ren` is set: by uploading some .html file and renaming it to `.epilogue.html` (uploading it directly is blocked)
other misc:
* you can disable directory listings by giving accesslevel `g` instead of `r`, only accepting direct URLs to files
* combine this with volume-flag `c,fk` to generate per-file accesskeys; users which have full read-access will then see URLs with `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404
## gotchas

View file

@ -104,7 +104,7 @@ def ensure_cert():
cert_insec = os.path.join(E.mod, "res/insecure.pem")
cert_cfg = os.path.join(E.cfg, "cert.pem")
if not os.path.exists(cert_cfg):
shutil.copy2(cert_insec, cert_cfg)
shutil.copy(cert_insec, cert_cfg)
try:
if filecmp.cmp(cert_cfg, cert_insec):
@ -203,6 +203,11 @@ def run_argparse(argv, formatter):
description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
)
try:
fk_salt = unicode(os.path.getmtime(os.path.join(E.cfg, "cert.pem")))
except:
fk_salt = "hunter2"
sects = [
[
"accounts",
@ -280,6 +285,10 @@ def run_argparse(argv, formatter):
\033[36mmtp=.bpm=f,audio-bpm.py\033[35m uses the "audio-bpm.py" program to
generate ".bpm" tags from uploads (f = overwrite tags)
\033[36mmtp=ahash,vhash=media-hash.py\033[35m collects two tags at once
\033[0mothers:
\033[36mfk=8\033[35m generates per-file accesskeys,
which will then be required at the "g" accesslevel
\033[0m"""
),
],
@ -361,6 +370,7 @@ def run_argparse(argv, formatter):
ap2 = ap.add_argument_group('safety options')
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt")
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt")
ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile")
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")

View file

@ -834,6 +834,11 @@ class AuthSrv(object):
if use:
vol.lim = lim
for vol in vfs.all_vols.values():
fk = vol.flags.get("fk")
if fk:
vol.flags["fk"] = int(fk) if fk is not True else 8
for vol in vfs.all_vols.values():
if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
vol.flags["gz"] = False # def.pk

View file

@ -1394,7 +1394,9 @@ class HttpCli(object):
#
# send reply
if is_compressed or "cache" in self.uparam:
if not is_compressed and "cache" not in self.uparam:
self.out_headers.update(NO_CACHE)
else:
self.out_headers.pop("Cache-Control")
self.out_headers["Accept-Ranges"] = "bytes"
@ -1617,7 +1619,7 @@ class HttpCli(object):
return True
def tx_404(self):
m = '<h1>404 not found</h1><p>or maybe you don\'t have access -- try logging in or <a href="/?h">go home</a></p>'
m = '<h1>404 not found &nbsp;┐( ´ -`)┌</h1><p>or maybe you don\'t have access -- try logging in or <a href="/?h">go home</a></p>'
html = self.j2("splash", this=self, qvpath=quotep(self.vpath), msg=m)
self.reply(html.encode("utf-8"), status=404)
return True
@ -1829,6 +1831,15 @@ class HttpCli(object):
return self.tx_ico(rem)
if not is_dir and (self.can_read or self.can_get):
if not self.can_read and "fk" in vn.flags:
correct = gen_filekey(
self.args.fk_salt, abspath, st.st_size, 0 if ANYWIN else st.st_ino
)[: vn.flags["fk"]]
got = self.uparam.get("k")
if got != correct:
self.log("wrong filekey, want {}, got {}".format(correct, got))
return self.tx_404()
if abspath.endswith(".md") and "raw" not in self.uparam:
return self.tx_md(abspath)
@ -1987,6 +1998,8 @@ class HttpCli(object):
idx = self.conn.get_u2idx()
icur = idx.get_cur(dbv.realpath)
add_fk = vn.flags.get("fk")
dirs = []
files = []
for fn in vfs_ls:
@ -2032,9 +2045,19 @@ class HttpCli(object):
except:
ext = "%"
if add_fk:
href = "{}?k={}".format(
quotep(href),
gen_filekey(
self.args.fk_salt, fspath, sz, 0 if ANYWIN else inf.st_ino
)[:add_fk],
)
else:
href = quotep(href)
item = {
"lead": margin,
"href": quotep(href),
"href": href,
"name": fn,
"sz": sz,
"ext": ext,

View file

@ -19,7 +19,7 @@ import subprocess as sp # nosec
from datetime import datetime
from collections import Counter
from .__init__ import PY2, WINDOWS, ANYWIN, VT100
from .__init__ import PY2, WINDOWS, ANYWIN, VT100, unicode
from .stolen import surrogateescape
FAKE_MP = False
@ -745,6 +745,14 @@ def read_header(sr):
return ret[:ofs].decode("utf-8", "surrogateescape").lstrip("\r\n").split("\r\n")
def gen_filekey(salt, fspath, fsize, inode):
return base64.urlsafe_b64encode(
hashlib.sha512(
"{} {} {} {}".format(salt, fspath, fsize, inode).encode("utf-8", "replace")
).digest()
).decode("ascii")
def humansize(sz, terse=False):
for unit in ["B", "KiB", "MiB", "GiB", "TiB"]:
if sz < 1024: