add permission "a" to show uploader IPs (#45)

This commit is contained in:
ed 2023-07-12 21:36:55 +00:00
parent b54b7213a7
commit 551d99b71b
8 changed files with 75 additions and 33 deletions

View file

@ -346,6 +346,7 @@ permissions:
* `d` (delete): delete files/folders * `d` (delete): delete files/folders
* `g` (get): only download files, cannot see folder contents or zip/tar * `g` (get): only download files, cannot see folder contents or zip/tar
* `G` (upget): same as `g` except uploaders get to see their own filekeys (see `fk` in examples below) * `G` (upget): same as `g` except uploaders get to see their own filekeys (see `fk` in examples below)
* `a` (admin): can see uploader IPs
examples: examples:
* add accounts named u1, u2, u3 with passwords p1, p2, p3: `-a u1:p1 -a u2:p2 -a u3:p3` * add accounts named u1, u2, u3 with passwords p1, p2, p3: `-a u1:p1 -a u2:p2 -a u3:p3`

View file

@ -138,6 +138,7 @@ in {
"d" (delete): permanently delete files and folders "d" (delete): permanently delete files and folders
"g" (get): download files, but cannot see folder contents "g" (get): download files, but cannot see folder contents
"G" (upget): "get", but can see filekeys of their own uploads "G" (upget): "get", but can see filekeys of their own uploads
"a" (upget): can see uploader IPs
For example: "rwmd" For example: "rwmd"

View file

@ -492,6 +492,7 @@ def get_sects():
"d" (delete): permanently delete files and folders "d" (delete): permanently delete files and folders
"g" (get): download files, but cannot see folder contents "g" (get): download files, but cannot see folder contents
"G" (upget): "get", but can see filekeys of their own uploads "G" (upget): "get", but can see filekeys of their own uploads
"a" (admin): can see uploader IPs
too many volflags to list here, see --help-flags too many volflags to list here, see --help-flags
@ -1073,7 +1074,7 @@ def add_db_metadata(ap):
ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/ffprobe parsers") ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/ffprobe parsers")
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping") ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)", ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash") default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash,up_ip,.up_at")
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)", ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
default=".vq,.aq,vc,ac,fmt,res,.fps") default=".vq,.aq,vc,ac,fmt,res,.fps")
ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file") ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
@ -1337,11 +1338,9 @@ def main(argv: Optional[list[str]] = None) -> None:
if re.match("c[^,]", opt): if re.match("c[^,]", opt):
mod = True mod = True
na.append("c," + opt[1:]) na.append("c," + opt[1:])
elif re.sub("^[rwmdgG]*", "", opt) and "," not in opt: elif re.sub("^[rwmdgGa]*", "", opt) and "," not in opt:
mod = True mod = True
perm = opt[0] perm = opt[0]
if perm == "a":
perm = "rw"
na.append(perm + "," + opt[1:]) na.append(perm + "," + opt[1:])
else: else:
na.append(opt) na.append(opt)

View file

@ -62,6 +62,7 @@ class AXS(object):
udel: Optional[Union[list[str], set[str]]] = None, udel: Optional[Union[list[str], set[str]]] = None,
uget: Optional[Union[list[str], set[str]]] = None, uget: Optional[Union[list[str], set[str]]] = None,
upget: Optional[Union[list[str], set[str]]] = None, upget: Optional[Union[list[str], set[str]]] = None,
uadmin: Optional[Union[list[str], set[str]]] = None,
) -> None: ) -> None:
self.uread: set[str] = set(uread or []) self.uread: set[str] = set(uread or [])
self.uwrite: set[str] = set(uwrite or []) self.uwrite: set[str] = set(uwrite or [])
@ -69,14 +70,11 @@ class AXS(object):
self.udel: set[str] = set(udel or []) self.udel: set[str] = set(udel or [])
self.uget: set[str] = set(uget or []) self.uget: set[str] = set(uget or [])
self.upget: set[str] = set(upget or []) self.upget: set[str] = set(upget or [])
self.uadmin: set[str] = set(uadmin or [])
def __repr__(self) -> str: def __repr__(self) -> str:
return "AXS(%s)" % ( ks = "uread uwrite umove udel uget upget uadmin".split()
", ".join( return "AXS(%s)" % (", ".join("%s=%r" % (k, self.__dict__[k]) for k in ks),)
"%s=%r" % (k, self.__dict__[k])
for k in "uread uwrite umove udel uget upget".split()
)
)
class Lim(object): class Lim(object):
@ -435,8 +433,8 @@ class VFS(object):
def can_access( def can_access(
self, vpath: str, uname: str self, vpath: str, uname: str
) -> tuple[bool, bool, bool, bool, bool, bool]: ) -> tuple[bool, bool, bool, bool, bool, bool, bool]:
"""can Read,Write,Move,Delete,Get,Upget""" """can Read,Write,Move,Delete,Get,Upget,Admin"""
if vpath: if vpath:
vn, _ = self._find(undot(vpath)) vn, _ = self._find(undot(vpath))
else: else:
@ -450,6 +448,7 @@ class VFS(object):
uname in c.udel or "*" in c.udel, uname in c.udel or "*" in c.udel,
uname in c.uget or "*" in c.uget, uname in c.uget or "*" in c.uget,
uname in c.upget or "*" in c.upget, uname in c.upget or "*" in c.upget,
uname in c.uadmin or "*" in c.uadmin,
) )
def get( def get(
@ -944,7 +943,7 @@ class AuthSrv(object):
try: try:
self._l(ln, 5, "volume access config:") self._l(ln, 5, "volume access config:")
sk, sv = ln.split(":") sk, sv = ln.split(":")
if re.sub("[rwmdgG]", "", sk) or not sk: if re.sub("[rwmdgGa]", "", sk) or not sk:
err = "invalid accs permissions list; " err = "invalid accs permissions list; "
raise Exception(err) raise Exception(err)
if " " in re.sub(", *", "", sv).strip(): if " " in re.sub(", *", "", sv).strip():
@ -953,7 +952,7 @@ class AuthSrv(object):
self._read_vol_str(sk, sv.replace(" ", ""), daxs[vp], mflags[vp]) self._read_vol_str(sk, sv.replace(" ", ""), daxs[vp], mflags[vp])
continue continue
except: except:
err += "accs entries must be 'rwmdgG: user1, user2, ...'" err += "accs entries must be 'rwmdgGa: user1, user2, ...'"
raise Exception(err) raise Exception(err)
if cat == catf: if cat == catf:
@ -989,7 +988,7 @@ class AuthSrv(object):
def _read_vol_str( def _read_vol_str(
self, lvl: str, uname: str, axs: AXS, flags: dict[str, Any] self, lvl: str, uname: str, axs: AXS, flags: dict[str, Any]
) -> None: ) -> None:
if lvl.strip("crwmdgG"): if lvl.strip("crwmdgGa"):
raise Exception("invalid volflag: {},{}".format(lvl, uname)) raise Exception("invalid volflag: {},{}".format(lvl, uname))
if lvl == "c": if lvl == "c":
@ -1021,6 +1020,7 @@ class AuthSrv(object):
("g", axs.uget), ("g", axs.uget),
("G", axs.uget), ("G", axs.uget),
("G", axs.upget), ("G", axs.upget),
("a", axs.uadmin),
]: # b bb bbb ]: # b bb bbb
if ch in lvl: if ch in lvl:
if un == "*": if un == "*":
@ -1092,7 +1092,7 @@ class AuthSrv(object):
if self.args.v: if self.args.v:
# list of src:dst:permset:permset:... # list of src:dst:permset:permset:...
# permset is <rwmdgG>[,username][,username] or <c>,<flag>[=args] # permset is <rwmdgGa>[,username][,username] or <c>,<flag>[=args]
for v_str in self.args.v: for v_str in self.args.v:
m = re_vol.match(v_str) m = re_vol.match(v_str)
if not m: if not m:
@ -1196,7 +1196,15 @@ class AuthSrv(object):
all_users = {} all_users = {}
missing_users = {} missing_users = {}
for axs in daxs.values(): for axs in daxs.values():
for d in [axs.uread, axs.uwrite, axs.umove, axs.udel, axs.uget, axs.upget]: for d in [
axs.uread,
axs.uwrite,
axs.umove,
axs.udel,
axs.uget,
axs.upget,
axs.uadmin,
]:
for usr in d: for usr in d:
all_users[usr] = 1 all_users[usr] = 1
if usr != "*" and usr not in acct: if usr != "*" and usr not in acct:
@ -1611,6 +1619,7 @@ class AuthSrv(object):
["delete", "udel"], ["delete", "udel"],
[" get", "uget"], [" get", "uget"],
[" upget", "upget"], [" upget", "upget"],
["uadmin", "uadmin"],
]: ]:
u = list(sorted(getattr(zv.axs, attr))) u = list(sorted(getattr(zv.axs, attr)))
u = ", ".join("\033[35meverybody\033[0m" if x == "*" else x for x in u) u = ", ".join("\033[35meverybody\033[0m" if x == "*" else x for x in u)
@ -1756,10 +1765,19 @@ class AuthSrv(object):
raise Exception("volume not found: " + zs) raise Exception("volume not found: " + zs)
self.log(str({"users": users, "vols": vols, "flags": flags})) self.log(str({"users": users, "vols": vols, "flags": flags}))
t = "/{}: read({}) write({}) move({}) del({}) get({}) upget({})" t = "/{}: read({}) write({}) move({}) del({}) get({}) upget({}) uadmin({})"
for k, zv in self.vfs.all_vols.items(): for k, zv in self.vfs.all_vols.items():
vc = zv.axs vc = zv.axs
vs = [k, vc.uread, vc.uwrite, vc.umove, vc.udel, vc.uget, vc.upget] vs = [
k,
vc.uread,
vc.uwrite,
vc.umove,
vc.udel,
vc.uget,
vc.upget,
vc.uadmin,
]
self.log(t.format(*vs)) self.log(t.format(*vs))
flag_v = "v" in flags flag_v = "v" in flags
@ -1898,6 +1916,7 @@ class AuthSrv(object):
"d": "udel", "d": "udel",
"g": "uget", "g": "uget",
"G": "upget", "G": "upget",
"a": "uadmin",
} }
users = {} users = {}
for pkey in perms.values(): for pkey in perms.values():
@ -2094,7 +2113,7 @@ def upgrade_cfg_fmt(
else: else:
sn = sn.replace(",", ", ") sn = sn.replace(",", ", ")
ret.append(" " + sn) ret.append(" " + sn)
elif sn[:1] in "rwmdgG": elif sn[:1] in "rwmdgGa":
if cat != catx: if cat != catx:
cat = catx cat = catx
ret.append(cat) ret.append(cat)

View file

@ -134,6 +134,7 @@ class FtpFs(AbstractedFS):
self.can_read = self.can_write = self.can_move = False self.can_read = self.can_write = self.can_move = False
self.can_delete = self.can_get = self.can_upget = False self.can_delete = self.can_get = self.can_upget = False
self.can_admin = False
self.listdirinfo = self.listdir self.listdirinfo = self.listdir
self.chdir(".") self.chdir(".")
@ -168,7 +169,7 @@ class FtpFs(AbstractedFS):
if not avfs: if not avfs:
raise FSE(t.format(vpath), 1) raise FSE(t.format(vpath), 1)
cr, cw, cm, cd, _, _ = avfs.can_access("", self.h.uname) cr, cw, cm, cd, _, _, _ = avfs.can_access("", self.h.uname)
if r and not cr or w and not cw or m and not cm or d and not cd: if r and not cr or w and not cw or m and not cm or d and not cd:
raise FSE(t.format(vpath), 1) raise FSE(t.format(vpath), 1)
@ -243,6 +244,7 @@ class FtpFs(AbstractedFS):
self.can_delete, self.can_delete,
self.can_get, self.can_get,
self.can_upget, self.can_upget,
self.can_admin,
) = avfs.can_access("", self.h.uname) ) = avfs.can_access("", self.h.uname)
def mkdir(self, path: str) -> None: def mkdir(self, path: str) -> None:

View file

@ -153,6 +153,7 @@ class HttpCli(object):
self.can_delete = False self.can_delete = False
self.can_get = False self.can_get = False
self.can_upget = False self.can_upget = False
self.can_admin = False
# post # post
self.parser: Optional[MultipartParser] = None self.parser: Optional[MultipartParser] = None
# end placeholders # end placeholders
@ -431,6 +432,7 @@ class HttpCli(object):
self.can_delete, self.can_delete,
self.can_get, self.can_get,
self.can_upget, self.can_upget,
self.can_admin,
) = ( ) = (
avn.can_access("", self.uname) if avn else [False] * 6 avn.can_access("", self.uname) if avn else [False] * 6
) )
@ -782,6 +784,7 @@ class HttpCli(object):
self.log("plugin override; access permitted") self.log("plugin override; access permitted")
self.can_read = self.can_write = self.can_move = True self.can_read = self.can_write = self.can_move = True
self.can_delete = self.can_get = self.can_upget = True self.can_delete = self.can_get = self.can_upget = True
self.can_admin = True
else: else:
return self.tx_404(True) return self.tx_404(True)
else: else:
@ -3535,6 +3538,8 @@ class HttpCli(object):
perms.append("get") perms.append("get")
if self.can_upget: if self.can_upget:
perms.append("upget") perms.append("upget")
if self.can_admin:
perms.append("admin")
url_suf = self.urlq({}, ["k"]) url_suf = self.urlq({}, ["k"])
is_ls = "ls" in self.uparam is_ls = "ls" in self.uparam
@ -3786,22 +3791,33 @@ class HttpCli(object):
if vn != dbv: if vn != dbv:
_, rd = vn.get_dbv(rd) _, rd = vn.get_dbv(rd)
erd_efn = (rd, fn)
q = "select mt.k, mt.v from up inner join mt on mt.w = substr(up.w,1,16) where up.rd = ? and up.fn = ? and +mt.k != 'x'" q = "select mt.k, mt.v from up inner join mt on mt.w = substr(up.w,1,16) where up.rd = ? and up.fn = ? and +mt.k != 'x'"
try: try:
r = icur.execute(q, (rd, fn)) r = icur.execute(q, erd_efn)
except Exception as ex: except Exception as ex:
if "database is locked" in str(ex): if "database is locked" in str(ex):
break break
try: try:
args = s3enc(idx.mem_cur, rd, fn) erd_efn = s3enc(idx.mem_cur, rd, fn)
r = icur.execute(q, args) r = icur.execute(q, erd_efn)
except: except:
t = "tag read error, {}/{}\n{}" t = "tag read error, {}/{}\n{}"
self.log(t.format(rd, fn, min_ex())) self.log(t.format(rd, fn, min_ex()))
break break
fe["tags"] = {k: v for k, v in r} fe["tags"] = {k: v for k, v in r}
if self.can_admin:
q = "select ip, at from up where rd=? and fn=?"
try:
zs1, zs2 = icur.execute(q, erd_efn).fetchone()
fe["tags"]["up_ip"] = zs1
fe["tags"][".up_at"] = zs2
except:
pass
_ = [tagset.add(k) for k in fe["tags"]] _ = [tagset.add(k) for k in fe["tags"]]
if icur: if icur:

View file

@ -5780,14 +5780,18 @@ var treectl = (function () {
for (var b = 0; b < res.taglist.length; b++) { for (var b = 0; b < res.taglist.length; b++) {
var k = res.taglist[b], var k = res.taglist[b],
v = (tn.tags || {})[k] || ""; v = (tn.tags || {})[k] || "",
sv = null;
if (k == ".dur") { if (k == ".dur")
var sv = v ? s2ms(v) : ""; sv = v ? s2ms(v) : "";
ln[ln.length - 1] += '</td><td sortv="' + v + '">' + sv; else if (k == ".up_at")
sv = v ? unix2iso(v) : "";
else {
ln.push(v);
continue; continue;
} }
ln.push(v); ln[ln.length - 1] += '</td><td sortv="' + v + '">' + sv;
} }
ln = ln.concat([tn.ext, unix2iso(tn.ts)]).join('</td><td>'); ln = ln.concat([tn.ext, unix2iso(tn.ts)]).join('</td><td>');
html.push(ln + '</td></tr>'); html.push(ln + '</td></tr>');
@ -6066,7 +6070,7 @@ function apply_perms(res) {
var axs = [], var axs = [],
aclass = '>', aclass = '>',
chk = ['read', 'write', 'move', 'delete', 'get']; chk = ['read', 'write', 'move', 'delete', 'get', 'admin'];
for (var a = 0; a < chk.length; a++) for (var a = 0; a < chk.length; a++)
if (has(perms, chk[a])) if (has(perms, chk[a]))

View file

@ -178,9 +178,9 @@ class TestVFS(unittest.TestCase):
self.assertEqual(n.realpath, os.path.join(td, "a")) self.assertEqual(n.realpath, os.path.join(td, "a"))
self.assertAxs(n.axs.uread, ["*"]) self.assertAxs(n.axs.uread, ["*"])
self.assertAxs(n.axs.uwrite, []) self.assertAxs(n.axs.uwrite, [])
perm_na = (False, False, False, False, False, False) perm_na = (False, False, False, False, False, False, False)
perm_rw = (True, True, False, False, False, False) perm_rw = (True, True, False, False, False, False, False)
perm_ro = (True, False, False, False, False, False) perm_ro = (True, False, False, False, False, False, False)
self.assertEqual(vfs.can_access("/", "*"), perm_na) self.assertEqual(vfs.can_access("/", "*"), perm_na)
self.assertEqual(vfs.can_access("/", "k"), perm_rw) self.assertEqual(vfs.can_access("/", "k"), perm_rw)
self.assertEqual(vfs.can_access("/a", "*"), perm_ro) self.assertEqual(vfs.can_access("/a", "*"), perm_ro)