mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
hooks: add permission filtering, argv-prepend;
hooks can be restricted to users with certain permissions, for example `--xm aw,notify-send` will only `notify-send` if user has write-access the user's list of permissions are now also included in the json that is passed to the hook if enabled; `--xm aw,j,notify-send` will now also stop parsing flags when encountering a blank value, allowing to specify any initial arguments to the command: `--xm aw,j,,notify-send,hey` would run `notify-send` with `hey` as its first argument, and the json would be the 2nd argument, similarly `--xm ,notify-send,hey` when no flags specified this is somewhat explained in `--help-hooks`, but additional related features are planned in the near future and will all be better documented when the dust settles
This commit is contained in:
parent
84e8e1ddfb
commit
d749683d48
|
@ -634,12 +634,12 @@ def get_sects():
|
|||
\033[36mxban\033[35m executes CMD if someone gets banned
|
||||
\033[0m
|
||||
can be defined as --args or volflags; for example \033[36m
|
||||
--xau notify-send
|
||||
-v .::r:c,xau=notify-send
|
||||
--xau foo.py
|
||||
-v .::r:c,xau=bar.py
|
||||
\033[0m
|
||||
commands specified as --args are appended to volflags;
|
||||
each --arg and volflag can be specified multiple times,
|
||||
each command will execute in order unless one returns non-zero
|
||||
hooks specified as commandline --args are appended to volflags;
|
||||
each commandline --arg and volflag can be specified multiple times,
|
||||
each hook will execute in order unless one returns non-zero
|
||||
|
||||
optionally prefix the command with comma-sep. flags similar to -mtp:
|
||||
|
||||
|
@ -650,6 +650,10 @@ def get_sects():
|
|||
\033[36mtN\033[35m sets an N sec timeout before the command is abandoned
|
||||
\033[36miN\033[35m xiu only: volume must be idle for N sec (default = 5)
|
||||
|
||||
\033[36mar\033[35m only run hook if user has read-access
|
||||
\033[36marw\033[35m only run hook if user has read-write-access
|
||||
\033[36marwmd\033[35m ...and so on... (doesn't work for xiu or xban)
|
||||
|
||||
\033[36mkt\033[35m kills the entire process tree on timeout (default),
|
||||
\033[36mkm\033[35m kills just the main process
|
||||
\033[36mkn\033[35m lets it continue running until copyparty is terminated
|
||||
|
@ -659,6 +663,21 @@ def get_sects():
|
|||
\033[36mc2\033[35m show only stdout
|
||||
\033[36mc3\033[35m mute all process otput
|
||||
\033[0m
|
||||
examples:
|
||||
|
||||
\033[36m--xm some.py\033[35m runs \033[33msome.py msgtxt\033[35m on each 📟 message;
|
||||
\033[33mmsgtxt\033[35m is the message that was written into the web-ui
|
||||
|
||||
\033[36m--xm j,some.py\033[35m runs \033[33msome.py jsontext\033[35m on each 📟 message;
|
||||
\033[33mjsontext\033[35m is the message info (ip, user, ..., msg-text)
|
||||
|
||||
\033[36m--xm aw,j,some.py\033[35m requires user to have write-access
|
||||
|
||||
\033[36m--xm aw,,notify-send,hey,--\033[35m shows an OS alert on linux;
|
||||
the \033[33m,,\033[35m stops copyparty from reading the rest as flags and
|
||||
the \033[33m--\033[35m stops notify-send from reading the message as args
|
||||
and the alert will be "hey" followed by the messagetext
|
||||
\033[0m
|
||||
each hook is executed once for each event, except for \033[36mxiu\033[0m
|
||||
which builds up a backlog of uploads, running the hook just once
|
||||
as soon as the volume has been idle for iN seconds (5 by default)
|
||||
|
@ -685,7 +704,10 @@ def get_sects():
|
|||
\033[36mstash\033[35m dumps the data to file and returns length + checksum
|
||||
\033[36msave,get\033[35m dumps to file and returns the page like a GET
|
||||
\033[36mprint,get\033[35m prints the data in the log and returns GET
|
||||
(leave out the ",get" to return an error instead)
|
||||
(leave out the ",get" to return an error instead)\033[0m
|
||||
|
||||
note that the \033[35m--xm\033[0m hook will only run if \033[35m--urlform\033[0m
|
||||
is either \033[36mprint\033[0m or the default \033[36mprint,get\033[0m
|
||||
"""
|
||||
),
|
||||
],
|
||||
|
|
|
@ -477,6 +477,13 @@ class VFS(object):
|
|||
)
|
||||
# skip uhtml because it's rarely needed
|
||||
|
||||
def get_perms(self, vpath: str, uname: str) -> str:
|
||||
zbl = self.can_access(vpath, uname)
|
||||
ret = "".join(ch for ch, ok in zip("rwmdgGa.", zbl) if ok)
|
||||
if "rwmd" in ret and "a." in ret:
|
||||
ret += "A"
|
||||
return ret
|
||||
|
||||
def get(
|
||||
self,
|
||||
vpath: str,
|
||||
|
|
|
@ -470,9 +470,10 @@ class FtpHandler(FTPHandler):
|
|||
None,
|
||||
xbu,
|
||||
ap,
|
||||
vfs.canonical(rem),
|
||||
vp,
|
||||
"",
|
||||
self.uname,
|
||||
self.hub.asrv.vfs.get_perms(vp, self.uname),
|
||||
0,
|
||||
0,
|
||||
self.cli_ip,
|
||||
|
|
|
@ -699,6 +699,7 @@ class HttpCli(object):
|
|||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
"",
|
||||
time.time(),
|
||||
0,
|
||||
self.ip,
|
||||
|
@ -1635,6 +1636,7 @@ class HttpCli(object):
|
|||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||
time.time(),
|
||||
len(buf),
|
||||
self.ip,
|
||||
|
@ -1784,6 +1786,7 @@ class HttpCli(object):
|
|||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||
at,
|
||||
remains,
|
||||
self.ip,
|
||||
|
@ -1874,6 +1877,7 @@ class HttpCli(object):
|
|||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||
mt,
|
||||
post_sz,
|
||||
self.ip,
|
||||
|
@ -2556,6 +2560,7 @@ class HttpCli(object):
|
|||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||
at,
|
||||
0,
|
||||
self.ip,
|
||||
|
@ -2619,6 +2624,7 @@ class HttpCli(object):
|
|||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||
at,
|
||||
sz,
|
||||
self.ip,
|
||||
|
@ -2863,6 +2869,7 @@ class HttpCli(object):
|
|||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||
time.time(),
|
||||
0,
|
||||
self.ip,
|
||||
|
@ -2901,6 +2908,7 @@ class HttpCli(object):
|
|||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||
new_lastmod,
|
||||
sz,
|
||||
self.ip,
|
||||
|
|
|
@ -240,7 +240,7 @@ class SMB(object):
|
|||
|
||||
xbu = vfs.flags.get("xbu")
|
||||
if xbu and not runhook(
|
||||
self.nlog, xbu, ap, vpath, "", "", 0, 0, "1.7.6.2", 0, ""
|
||||
self.nlog, xbu, ap, vpath, "", "", "", 0, 0, "1.7.6.2", 0, ""
|
||||
):
|
||||
yeet("blocked by xbu server config: " + vpath)
|
||||
|
||||
|
|
|
@ -328,7 +328,7 @@ class Tftpd(object):
|
|||
|
||||
xbu = vfs.flags.get("xbu")
|
||||
if xbu and not runhook(
|
||||
self.nlog, xbu, ap, vpath, "", "", 0, 0, "8.3.8.7", 0, ""
|
||||
self.nlog, xbu, ap, vpath, "", "", "", 0, 0, "8.3.8.7", 0, ""
|
||||
):
|
||||
yeet("blocked by xbu server config: " + vpath)
|
||||
|
||||
|
|
|
@ -2770,6 +2770,7 @@ class Up2k(object):
|
|||
job["vtop"],
|
||||
job["host"],
|
||||
job["user"],
|
||||
self.asrv.vfs.get_perms(job["vtop"], job["user"]),
|
||||
job["lmod"],
|
||||
job["size"],
|
||||
job["addr"],
|
||||
|
@ -3297,6 +3298,7 @@ class Up2k(object):
|
|||
djoin(vtop, rd, fn),
|
||||
host,
|
||||
usr,
|
||||
self.asrv.vfs.get_perms(djoin(vtop, rd, fn), usr),
|
||||
int(ts),
|
||||
sz,
|
||||
ip,
|
||||
|
@ -3496,6 +3498,7 @@ class Up2k(object):
|
|||
vpath,
|
||||
"",
|
||||
uname,
|
||||
self.asrv.vfs.get_perms(vpath, uname),
|
||||
stl.st_mtime,
|
||||
st.st_size,
|
||||
ip,
|
||||
|
@ -3529,6 +3532,7 @@ class Up2k(object):
|
|||
vpath,
|
||||
"",
|
||||
uname,
|
||||
self.asrv.vfs.get_perms(vpath, uname),
|
||||
stl.st_mtime,
|
||||
st.st_size,
|
||||
ip,
|
||||
|
@ -3661,7 +3665,18 @@ class Up2k(object):
|
|||
xar = dvn.flags.get("xar")
|
||||
if xbr:
|
||||
if not runhook(
|
||||
self.log, xbr, sabs, svp, "", uname, stl.st_mtime, st.st_size, "", 0, ""
|
||||
self.log,
|
||||
xbr,
|
||||
sabs,
|
||||
svp,
|
||||
"",
|
||||
uname,
|
||||
self.asrv.vfs.get_perms(svp, uname),
|
||||
stl.st_mtime,
|
||||
st.st_size,
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
):
|
||||
t = "move blocked by xbr server config: {}".format(svp)
|
||||
self.log(t, 1)
|
||||
|
@ -3686,7 +3701,20 @@ class Up2k(object):
|
|||
self.rescan_cond.notify_all()
|
||||
|
||||
if xar:
|
||||
runhook(self.log, xar, dabs, dvp, "", uname, 0, 0, "", 0, "")
|
||||
runhook(
|
||||
self.log,
|
||||
xar,
|
||||
dabs,
|
||||
dvp,
|
||||
"",
|
||||
uname,
|
||||
self.asrv.vfs.get_perms(dvp, uname),
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
)
|
||||
|
||||
return "k"
|
||||
|
||||
|
@ -3785,7 +3813,20 @@ class Up2k(object):
|
|||
wunlink(self.log, sabs, svn.flags)
|
||||
|
||||
if xar:
|
||||
runhook(self.log, xar, dabs, dvp, "", uname, 0, 0, "", 0, "")
|
||||
runhook(
|
||||
self.log,
|
||||
xar,
|
||||
dabs,
|
||||
dvp,
|
||||
"",
|
||||
uname,
|
||||
self.asrv.vfs.get_perms(dvp, uname),
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
)
|
||||
|
||||
return "k"
|
||||
|
||||
|
@ -4074,6 +4115,7 @@ class Up2k(object):
|
|||
vp_chk,
|
||||
job["host"],
|
||||
job["user"],
|
||||
self.asrv.vfs.get_perms(vp_chk, job["user"]),
|
||||
int(job["lmod"]),
|
||||
job["size"],
|
||||
job["addr"],
|
||||
|
|
|
@ -2992,7 +2992,8 @@ def retchk(
|
|||
|
||||
def _parsehook(
|
||||
log: Optional["NamedLogger"], cmd: str
|
||||
) -> tuple[bool, bool, bool, float, dict[str, Any], str]:
|
||||
) -> tuple[str, bool, bool, bool, float, dict[str, Any], list[str]]:
|
||||
areq = ""
|
||||
chk = False
|
||||
fork = False
|
||||
jtxt = False
|
||||
|
@ -3017,8 +3018,12 @@ def _parsehook(
|
|||
cap = int(arg[1:]) # 0=none 1=stdout 2=stderr 3=both
|
||||
elif arg.startswith("k"):
|
||||
kill = arg[1:] # [t]ree [m]ain [n]one
|
||||
elif arg.startswith("a"):
|
||||
areq = arg[1:] # required perms
|
||||
elif arg.startswith("i"):
|
||||
pass
|
||||
elif not arg:
|
||||
break
|
||||
else:
|
||||
t = "hook: invalid flag {} in {}"
|
||||
(log or print)(t.format(arg, ocmd))
|
||||
|
@ -3045,9 +3050,11 @@ def _parsehook(
|
|||
"capture": cap,
|
||||
}
|
||||
|
||||
cmd = os.path.expandvars(os.path.expanduser(cmd))
|
||||
argv = cmd.split(",") if "," in cmd else [cmd]
|
||||
|
||||
return chk, fork, jtxt, wait, sp_ka, cmd
|
||||
argv[0] = os.path.expandvars(os.path.expanduser(argv[0]))
|
||||
|
||||
return areq, chk, fork, jtxt, wait, sp_ka, argv
|
||||
|
||||
|
||||
def runihook(
|
||||
|
@ -3056,10 +3063,9 @@ def runihook(
|
|||
vol: "VFS",
|
||||
ups: list[tuple[str, int, int, str, str, str, int]],
|
||||
) -> bool:
|
||||
ocmd = cmd
|
||||
chk, fork, jtxt, wait, sp_ka, cmd = _parsehook(log, cmd)
|
||||
bcmd = [sfsenc(cmd)]
|
||||
if cmd.endswith(".py"):
|
||||
_, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
|
||||
bcmd = [sfsenc(x) for x in acmd]
|
||||
if acmd[0].endswith(".py"):
|
||||
bcmd = [sfsenc(pybin)] + bcmd
|
||||
|
||||
vps = [vjoin(*list(s3dec(x[3], x[4]))) for x in ups]
|
||||
|
@ -3084,7 +3090,7 @@ def runihook(
|
|||
|
||||
t0 = time.time()
|
||||
if fork:
|
||||
Daemon(runcmd, ocmd, [bcmd], ka=sp_ka)
|
||||
Daemon(runcmd, cmd, bcmd, ka=sp_ka)
|
||||
else:
|
||||
rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
|
||||
if chk and rc:
|
||||
|
@ -3105,14 +3111,20 @@ def _runhook(
|
|||
vp: str,
|
||||
host: str,
|
||||
uname: str,
|
||||
perms: str,
|
||||
mt: float,
|
||||
sz: int,
|
||||
ip: str,
|
||||
at: float,
|
||||
txt: str,
|
||||
) -> bool:
|
||||
ocmd = cmd
|
||||
chk, fork, jtxt, wait, sp_ka, cmd = _parsehook(log, cmd)
|
||||
areq, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
|
||||
if areq:
|
||||
for ch in areq:
|
||||
if ch not in perms:
|
||||
t = "user %s not allowed to run hook %s; need perms %s, have %s"
|
||||
log(t % (uname, cmd, areq, perms))
|
||||
return True # fallthrough to next hook
|
||||
if jtxt:
|
||||
ja = {
|
||||
"ap": ap,
|
||||
|
@ -3123,21 +3135,22 @@ def _runhook(
|
|||
"at": at or time.time(),
|
||||
"host": host,
|
||||
"user": uname,
|
||||
"perms": perms,
|
||||
"txt": txt,
|
||||
}
|
||||
arg = json.dumps(ja)
|
||||
else:
|
||||
arg = txt or ap
|
||||
|
||||
acmd = [cmd, arg]
|
||||
if cmd.endswith(".py"):
|
||||
acmd += [arg]
|
||||
if acmd[0].endswith(".py"):
|
||||
acmd = [pybin] + acmd
|
||||
|
||||
bcmd = [fsenc(x) if x == ap else sfsenc(x) for x in acmd]
|
||||
|
||||
t0 = time.time()
|
||||
if fork:
|
||||
Daemon(runcmd, ocmd, [bcmd], ka=sp_ka)
|
||||
Daemon(runcmd, cmd, [bcmd], ka=sp_ka)
|
||||
else:
|
||||
rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
|
||||
if chk and rc:
|
||||
|
@ -3158,6 +3171,7 @@ def runhook(
|
|||
vp: str,
|
||||
host: str,
|
||||
uname: str,
|
||||
perms: str,
|
||||
mt: float,
|
||||
sz: int,
|
||||
ip: str,
|
||||
|
@ -3167,7 +3181,7 @@ def runhook(
|
|||
vp = vp.replace("\\", "/")
|
||||
for cmd in cmds:
|
||||
try:
|
||||
if not _runhook(log, cmd, ap, vp, host, uname, mt, sz, ip, at, txt):
|
||||
if not _runhook(log, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt):
|
||||
return False
|
||||
except Exception as ex:
|
||||
(log or print)("hook: {}".format(ex))
|
||||
|
|
|
@ -1064,7 +1064,7 @@ ebi('ops').innerHTML = (
|
|||
'<a href="#" data-perm="write" data-dest="bup" tt="' + L.ot_bup + '">🎈</a>' +
|
||||
'<a href="#" data-perm="write" data-dest="mkdir" tt="' + L.ot_mkdir + '">📂</a>' +
|
||||
'<a href="#" data-perm="read write" data-dest="new_md" tt="' + L.ot_md + '">📝</a>' +
|
||||
'<a href="#" data-perm="write" data-dest="msg" tt="' + L.ot_msg + '">📟</a>' +
|
||||
'<a href="#" data-dest="msg" tt="' + L.ot_msg + '">📟</a>' +
|
||||
'<a href="#" data-dest="player" tt="' + L.ot_mp + '">🎺</a>' +
|
||||
'<a href="#" data-dest="cfg" tt="' + L.ot_cfg + '">⚙️</a>' +
|
||||
(IE ? '<span id="noie">' + L.ot_noie + '</span>' : '') +
|
||||
|
|
Loading…
Reference in a new issue