mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 00:52:16 -06:00
new access level g
This commit is contained in:
parent
12b7317831
commit
5849c446ed
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -55,4 +55,5 @@
|
||||||
"py27"
|
"py27"
|
||||||
],
|
],
|
||||||
"python.linting.enabled": true,
|
"python.linting.enabled": true,
|
||||||
|
"python.pythonPath": "/usr/bin/python3"
|
||||||
}
|
}
|
|
@ -257,6 +257,7 @@ permissions:
|
||||||
* `w` (write): upload files, move files *into* this folder
|
* `w` (write): upload files, move files *into* this folder
|
||||||
* `m` (move): move files/folders *from* this folder
|
* `m` (move): move files/folders *from* this folder
|
||||||
* `d` (delete): delete files/folders
|
* `d` (delete): delete files/folders
|
||||||
|
* `g` (get): only download files, cannot see folder contents or zip/tar
|
||||||
|
|
||||||
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`
|
||||||
|
|
|
@ -219,6 +219,7 @@ def run_argparse(argv, formatter):
|
||||||
"w" (write): upload files; need "r" to see the uploads
|
"w" (write): upload files; need "r" to see the uploads
|
||||||
"m" (move): move files and folders; need "w" at destination
|
"m" (move): move files and folders; need "w" at destination
|
||||||
"d" (delete): permanently delete files and folders
|
"d" (delete): permanently delete files and folders
|
||||||
|
"g" (get): download files, but cannot see folder contents
|
||||||
|
|
||||||
too many volflags to list here, see the other sections
|
too many volflags to list here, see the other sections
|
||||||
|
|
||||||
|
@ -493,7 +494,7 @@ def main(argv=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("^[rwmd]*", "", opt) and "," not in opt:
|
elif re.sub("^[rwmdg]*", "", opt) and "," not in opt:
|
||||||
mod = True
|
mod = True
|
||||||
perm = opt[0]
|
perm = opt[0]
|
||||||
if perm == "a":
|
if perm == "a":
|
||||||
|
|
|
@ -29,17 +29,18 @@ LEELOO_DALLAS = "leeloo_dallas"
|
||||||
|
|
||||||
|
|
||||||
class AXS(object):
|
class AXS(object):
|
||||||
def __init__(self, uread=None, uwrite=None, umove=None, udel=None):
|
def __init__(self, uread=None, uwrite=None, umove=None, udel=None, uget=None):
|
||||||
self.uread = {} if uread is None else {k: 1 for k in uread}
|
self.uread = {} if uread is None else {k: 1 for k in uread}
|
||||||
self.uwrite = {} if uwrite is None else {k: 1 for k in uwrite}
|
self.uwrite = {} if uwrite is None else {k: 1 for k in uwrite}
|
||||||
self.umove = {} if umove is None else {k: 1 for k in umove}
|
self.umove = {} if umove is None else {k: 1 for k in umove}
|
||||||
self.udel = {} if udel is None else {k: 1 for k in udel}
|
self.udel = {} if udel is None else {k: 1 for k in udel}
|
||||||
|
self.uget = {} if uget is None else {k: 1 for k in uget}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "AXS({})".format(
|
return "AXS({})".format(
|
||||||
", ".join(
|
", ".join(
|
||||||
"{}={!r}".format(k, self.__dict__[k])
|
"{}={!r}".format(k, self.__dict__[k])
|
||||||
for k in "uread uwrite umove udel".split()
|
for k in "uread uwrite umove udel uget".split()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -215,6 +216,7 @@ class VFS(object):
|
||||||
self.awrite = {}
|
self.awrite = {}
|
||||||
self.amove = {}
|
self.amove = {}
|
||||||
self.adel = {}
|
self.adel = {}
|
||||||
|
self.aget = {}
|
||||||
else:
|
else:
|
||||||
self.histpath = None
|
self.histpath = None
|
||||||
self.all_vols = None
|
self.all_vols = None
|
||||||
|
@ -222,6 +224,7 @@ class VFS(object):
|
||||||
self.awrite = None
|
self.awrite = None
|
||||||
self.amove = None
|
self.amove = None
|
||||||
self.adel = None
|
self.adel = None
|
||||||
|
self.aget = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "VFS({})".format(
|
return "VFS({})".format(
|
||||||
|
@ -308,7 +311,7 @@ class VFS(object):
|
||||||
|
|
||||||
def can_access(self, vpath, uname):
|
def can_access(self, vpath, uname):
|
||||||
# type: (str, str) -> tuple[bool, bool, bool, bool]
|
# type: (str, str) -> tuple[bool, bool, bool, bool]
|
||||||
"""can Read,Write,Move,Delete"""
|
"""can Read,Write,Move,Delete,Get"""
|
||||||
vn, _ = self._find(vpath)
|
vn, _ = self._find(vpath)
|
||||||
c = vn.axs
|
c = vn.axs
|
||||||
return [
|
return [
|
||||||
|
@ -316,10 +319,20 @@ class VFS(object):
|
||||||
uname in c.uwrite or "*" in c.uwrite,
|
uname in c.uwrite or "*" in c.uwrite,
|
||||||
uname in c.umove or "*" in c.umove,
|
uname in c.umove or "*" in c.umove,
|
||||||
uname in c.udel or "*" in c.udel,
|
uname in c.udel or "*" in c.udel,
|
||||||
|
uname in c.uget or "*" in c.uget,
|
||||||
]
|
]
|
||||||
|
|
||||||
def get(self, vpath, uname, will_read, will_write, will_move=False, will_del=False):
|
def get(
|
||||||
# type: (str, str, bool, bool, bool, bool) -> tuple[VFS, str]
|
self,
|
||||||
|
vpath,
|
||||||
|
uname,
|
||||||
|
will_read,
|
||||||
|
will_write,
|
||||||
|
will_move=False,
|
||||||
|
will_del=False,
|
||||||
|
will_get=False,
|
||||||
|
):
|
||||||
|
# type: (str, str, bool, bool, bool, bool, bool) -> tuple[VFS, str]
|
||||||
"""returns [vfsnode,fs_remainder] if user has the requested permissions"""
|
"""returns [vfsnode,fs_remainder] if user has the requested permissions"""
|
||||||
vn, rem = self._find(vpath)
|
vn, rem = self._find(vpath)
|
||||||
c = vn.axs
|
c = vn.axs
|
||||||
|
@ -329,6 +342,7 @@ class VFS(object):
|
||||||
[will_write, c.uwrite, "write"],
|
[will_write, c.uwrite, "write"],
|
||||||
[will_move, c.umove, "move"],
|
[will_move, c.umove, "move"],
|
||||||
[will_del, c.udel, "delete"],
|
[will_del, c.udel, "delete"],
|
||||||
|
[will_get, c.uget, "get"],
|
||||||
]:
|
]:
|
||||||
if req and (uname not in d and "*" not in d) and uname != LEELOO_DALLAS:
|
if req and (uname not in d and "*" not in d) and uname != LEELOO_DALLAS:
|
||||||
m = "you don't have {}-access for this location"
|
m = "you don't have {}-access for this location"
|
||||||
|
@ -368,7 +382,7 @@ class VFS(object):
|
||||||
for name, vn2 in sorted(self.nodes.items()):
|
for name, vn2 in sorted(self.nodes.items()):
|
||||||
ok = False
|
ok = False
|
||||||
axs = vn2.axs
|
axs = vn2.axs
|
||||||
axs = [axs.uread, axs.uwrite, axs.umove, axs.udel]
|
axs = [axs.uread, axs.uwrite, axs.umove, axs.udel, axs.uget]
|
||||||
for pset in permsets:
|
for pset in permsets:
|
||||||
ok = True
|
ok = True
|
||||||
for req, lst in zip(pset, axs):
|
for req, lst in zip(pset, axs):
|
||||||
|
@ -561,7 +575,7 @@ class AuthSrv(object):
|
||||||
|
|
||||||
def _read_vol_str(self, lvl, uname, axs, flags):
|
def _read_vol_str(self, lvl, uname, axs, flags):
|
||||||
# type: (str, str, AXS, any) -> None
|
# type: (str, str, AXS, any) -> None
|
||||||
if lvl.strip("crwmd"):
|
if lvl.strip("crwmdg"):
|
||||||
raise Exception("invalid volume flag: {},{}".format(lvl, uname))
|
raise Exception("invalid volume flag: {},{}".format(lvl, uname))
|
||||||
|
|
||||||
if lvl == "c":
|
if lvl == "c":
|
||||||
|
@ -588,6 +602,9 @@ class AuthSrv(object):
|
||||||
if "d" in lvl:
|
if "d" in lvl:
|
||||||
axs.udel[un] = 1
|
axs.udel[un] = 1
|
||||||
|
|
||||||
|
if "g" in lvl:
|
||||||
|
axs.uget[un] = 1
|
||||||
|
|
||||||
def _read_volflag(self, flags, name, value, is_list):
|
def _read_volflag(self, flags, name, value, is_list):
|
||||||
if name not in ["mtp"]:
|
if name not in ["mtp"]:
|
||||||
flags[name] = value
|
flags[name] = value
|
||||||
|
@ -625,7 +642,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 <rwmd>[,username][,username] or <c>,<flag>[=args]
|
# permset is <rwmdg>[,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:
|
||||||
|
@ -692,20 +709,21 @@ class AuthSrv(object):
|
||||||
vfs.all_vols = {}
|
vfs.all_vols = {}
|
||||||
vfs.get_all_vols(vfs.all_vols)
|
vfs.get_all_vols(vfs.all_vols)
|
||||||
|
|
||||||
for perm in "read write move del".split():
|
for perm in "read write move del get".split():
|
||||||
axs_key = "u" + perm
|
axs_key = "u" + perm
|
||||||
unames = ["*"] + list(acct.keys())
|
unames = ["*"] + list(acct.keys())
|
||||||
umap = {x: [] for x in unames}
|
umap = {x: [] for x in unames}
|
||||||
for usr in unames:
|
for usr in unames:
|
||||||
for mp, vol in vfs.all_vols.items():
|
for mp, vol in vfs.all_vols.items():
|
||||||
if usr in getattr(vol.axs, axs_key):
|
axs = getattr(vol.axs, axs_key)
|
||||||
|
if usr in axs or "*" in axs:
|
||||||
umap[usr].append(mp)
|
umap[usr].append(mp)
|
||||||
setattr(vfs, "a" + perm, umap)
|
setattr(vfs, "a" + perm, umap)
|
||||||
|
|
||||||
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]:
|
for d in [axs.uread, axs.uwrite, axs.umove, axs.udel, axs.uget]:
|
||||||
for usr in d.keys():
|
for usr in d.keys():
|
||||||
all_users[usr] = 1
|
all_users[usr] = 1
|
||||||
if usr != "*" and usr not in acct:
|
if usr != "*" and usr not in acct:
|
||||||
|
@ -930,6 +948,7 @@ class AuthSrv(object):
|
||||||
[" write", "uwrite"],
|
[" write", "uwrite"],
|
||||||
[" move", "umove"],
|
[" move", "umove"],
|
||||||
["delete", "udel"],
|
["delete", "udel"],
|
||||||
|
[" get", "uget"],
|
||||||
]:
|
]:
|
||||||
u = list(sorted(getattr(v.axs, attr).keys()))
|
u = list(sorted(getattr(v.axs, attr).keys()))
|
||||||
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)
|
||||||
|
@ -997,10 +1016,10 @@ class AuthSrv(object):
|
||||||
raise Exception("volume not found: " + v)
|
raise Exception("volume not found: " + v)
|
||||||
|
|
||||||
self.log({"users": users, "vols": vols, "flags": flags})
|
self.log({"users": users, "vols": vols, "flags": flags})
|
||||||
m = "/{}: read({}) write({}) move({}) del({})"
|
m = "/{}: read({}) write({}) move({}) del({}) get({})"
|
||||||
for k, v in self.vfs.all_vols.items():
|
for k, v in self.vfs.all_vols.items():
|
||||||
vc = v.axs
|
vc = v.axs
|
||||||
self.log(m.format(k, vc.uread, vc.uwrite, vc.umove, vc.udel))
|
self.log(m.format(k, vc.uread, vc.uwrite, vc.umove, vc.udel, vc.uget))
|
||||||
|
|
||||||
flag_v = "v" in flags
|
flag_v = "v" in flags
|
||||||
flag_ln = "ln" in flags
|
flag_ln = "ln" in flags
|
||||||
|
@ -1014,7 +1033,7 @@ class AuthSrv(object):
|
||||||
for u in users:
|
for u in users:
|
||||||
self.log("checking /{} as {}".format(v, u))
|
self.log("checking /{} as {}".format(v, u))
|
||||||
try:
|
try:
|
||||||
vn, _ = self.vfs.get(v, u, True, False, False, False)
|
vn, _ = self.vfs.get(v, u, True, False, False, False, False)
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,7 @@ class HttpCli(object):
|
||||||
self.wvol = self.asrv.vfs.awrite[self.uname]
|
self.wvol = self.asrv.vfs.awrite[self.uname]
|
||||||
self.mvol = self.asrv.vfs.amove[self.uname]
|
self.mvol = self.asrv.vfs.amove[self.uname]
|
||||||
self.dvol = self.asrv.vfs.adel[self.uname]
|
self.dvol = self.asrv.vfs.adel[self.uname]
|
||||||
|
self.gvol = self.asrv.vfs.aget[self.uname]
|
||||||
|
|
||||||
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
|
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
|
||||||
self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
|
self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
|
||||||
|
@ -379,8 +380,8 @@ class HttpCli(object):
|
||||||
return self.tx_file(static_path)
|
return self.tx_file(static_path)
|
||||||
|
|
||||||
x = self.asrv.vfs.can_access(self.vpath, self.uname)
|
x = self.asrv.vfs.can_access(self.vpath, self.uname)
|
||||||
self.can_read, self.can_write, self.can_move, self.can_delete = x
|
self.can_read, self.can_write, self.can_move, self.can_delete, self.can_get = x
|
||||||
if not self.can_read and not self.can_write:
|
if not self.can_read and not self.can_write and not self.can_get:
|
||||||
if self.vpath:
|
if self.vpath:
|
||||||
self.log("inaccessible: [{}]".format(self.vpath))
|
self.log("inaccessible: [{}]".format(self.vpath))
|
||||||
raise Pebkac(404)
|
raise Pebkac(404)
|
||||||
|
@ -1784,13 +1785,13 @@ class HttpCli(object):
|
||||||
except:
|
except:
|
||||||
raise Pebkac(404)
|
raise Pebkac(404)
|
||||||
|
|
||||||
if self.can_read:
|
if rem.startswith(".hist/up2k.") or (
|
||||||
if rem.startswith(".hist/up2k.") or (
|
rem.endswith("/dir.txt") and rem.startswith(".hist/th/")
|
||||||
rem.endswith("/dir.txt") and rem.startswith(".hist/th/")
|
):
|
||||||
):
|
raise Pebkac(403)
|
||||||
raise Pebkac(403)
|
|
||||||
|
|
||||||
is_dir = stat.S_ISDIR(st.st_mode)
|
is_dir = stat.S_ISDIR(st.st_mode)
|
||||||
|
if self.can_read:
|
||||||
th_fmt = self.uparam.get("th")
|
th_fmt = self.uparam.get("th")
|
||||||
if th_fmt is not None:
|
if th_fmt is not None:
|
||||||
if is_dir:
|
if is_dir:
|
||||||
|
@ -1815,11 +1816,11 @@ class HttpCli(object):
|
||||||
|
|
||||||
return self.tx_ico(rem)
|
return self.tx_ico(rem)
|
||||||
|
|
||||||
if not is_dir:
|
if not is_dir and (self.can_read or self.can_get):
|
||||||
if abspath.endswith(".md") and "raw" not in self.uparam:
|
if abspath.endswith(".md") and "raw" not in self.uparam:
|
||||||
return self.tx_md(abspath)
|
return self.tx_md(abspath)
|
||||||
|
|
||||||
return self.tx_file(abspath)
|
return self.tx_file(abspath)
|
||||||
|
|
||||||
srv_info = []
|
srv_info = []
|
||||||
|
|
||||||
|
@ -1859,6 +1860,8 @@ class HttpCli(object):
|
||||||
perms.append("move")
|
perms.append("move")
|
||||||
if self.can_delete:
|
if self.can_delete:
|
||||||
perms.append("delete")
|
perms.append("delete")
|
||||||
|
if self.can_get:
|
||||||
|
perms.append("get")
|
||||||
|
|
||||||
url_suf = self.urlq({}, [])
|
url_suf = self.urlq({}, [])
|
||||||
is_ls = "ls" in self.uparam
|
is_ls = "ls" in self.uparam
|
||||||
|
@ -1928,6 +1931,9 @@ class HttpCli(object):
|
||||||
if not stat.S_ISDIR(st.st_mode):
|
if not stat.S_ISDIR(st.st_mode):
|
||||||
raise Pebkac(404)
|
raise Pebkac(404)
|
||||||
|
|
||||||
|
if "zip" in self.uparam or "tar" in self.uparam:
|
||||||
|
raise Pebkac(403)
|
||||||
|
|
||||||
html = self.j2(tpl, **j2a)
|
html = self.j2(tpl, **j2a)
|
||||||
self.reply(html.encode("utf-8", "replace"), headers=NO_STORE)
|
self.reply(html.encode("utf-8", "replace"), headers=NO_STORE)
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -227,9 +227,22 @@ function goto(dest) {
|
||||||
clmod(obj[a], 'act');
|
clmod(obj[a], 'act');
|
||||||
|
|
||||||
if (dest) {
|
if (dest) {
|
||||||
var ui = ebi('op_' + dest);
|
var ui = ebi('op_' + dest),
|
||||||
|
lnk = QS('#ops>a[data-dest=' + dest + ']'),
|
||||||
|
nps = lnk.getAttribute('data-perm');
|
||||||
|
|
||||||
|
nps = nps && nps.length ? nps.split(' ') : [];
|
||||||
|
|
||||||
|
if (perms.length)
|
||||||
|
for (var a = 0; a < nps.length; a++)
|
||||||
|
if (!has(perms, nps[a]))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!has(perms, 'read') && !has(perms, 'write') && (dest == 'up2k'))
|
||||||
|
return;
|
||||||
|
|
||||||
clmod(ui, 'act', true);
|
clmod(ui, 'act', true);
|
||||||
QS('#ops>a[data-dest=' + dest + ']').className += " act";
|
lnk.className += " act";
|
||||||
|
|
||||||
var fn = window['goto_' + dest];
|
var fn = window['goto_' + dest];
|
||||||
if (fn)
|
if (fn)
|
||||||
|
@ -3426,7 +3439,7 @@ function apply_perms(newperms) {
|
||||||
|
|
||||||
var axs = [],
|
var axs = [],
|
||||||
aclass = '>',
|
aclass = '>',
|
||||||
chk = ['read', 'write', 'move', 'delete'];
|
chk = ['read', 'write', 'move', 'delete', 'get'];
|
||||||
|
|
||||||
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]))
|
||||||
|
@ -3480,7 +3493,7 @@ function apply_perms(newperms) {
|
||||||
|
|
||||||
ebi('widget').style.display = have_read ? '' : 'none';
|
ebi('widget').style.display = have_read ? '' : 'none';
|
||||||
thegrid.setvis(have_read);
|
thegrid.setvis(have_read);
|
||||||
if (!have_read)
|
if (!have_read && have_write)
|
||||||
goto('up2k');
|
goto('up2k');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -528,7 +528,7 @@ function up2k_init(subtle) {
|
||||||
got_deps = true;
|
got_deps = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (perms.length && !has(perms, 'read'))
|
if (perms.length && !has(perms, 'read') && has(perms, 'write'))
|
||||||
goto('up2k');
|
goto('up2k');
|
||||||
|
|
||||||
function setmsg(msg, type) {
|
function setmsg(msg, type) {
|
||||||
|
|
|
@ -98,7 +98,7 @@ class TestHttpCli(unittest.TestCase):
|
||||||
if not vol.startswith(top):
|
if not vol.startswith(top):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
mode = vol[-2].replace("a", "rwmd")
|
mode = vol[-2].replace("a", "rw")
|
||||||
usr = vol[-1]
|
usr = vol[-1]
|
||||||
if usr == "a":
|
if usr == "a":
|
||||||
usr = ""
|
usr = ""
|
||||||
|
|
|
@ -197,10 +197,10 @@ 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, [])
|
||||||
self.assertEqual(vfs.can_access("/", "*"), [False, False, False, False])
|
self.assertEqual(vfs.can_access("/", "*"), [False, False, False, False, False])
|
||||||
self.assertEqual(vfs.can_access("/", "k"), [True, True, False, False])
|
self.assertEqual(vfs.can_access("/", "k"), [True, True, False, False, False])
|
||||||
self.assertEqual(vfs.can_access("/a", "*"), [True, False, False, False])
|
self.assertEqual(vfs.can_access("/a", "*"), [True, False, False, False, False])
|
||||||
self.assertEqual(vfs.can_access("/a", "k"), [True, False, False, False])
|
self.assertEqual(vfs.can_access("/a", "k"), [True, False, False, False, False])
|
||||||
|
|
||||||
# breadth-first construction
|
# breadth-first construction
|
||||||
vfs = AuthSrv(
|
vfs = AuthSrv(
|
||||||
|
|
Loading…
Reference in a new issue