From 2e53f7979a2b6f88dfcf0145224b3f649576a668 Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 3 Jun 2025 20:03:17 +0000 Subject: [PATCH] IdP: multiple group rules for ${u} and ${g} until now, ${u} would match all users, ${u%-foo} would exclude users in group foo, ${u%+foo} would only include users in group foo now, the following is also possible: ${u%-foo,%-bar} excludes users in group foo and/or group bar, ${u%+foo,%+bar} only includes users which are in groups foo AND bar, ${g%-foo} skips group foo (includes all others), ${g%-foo,%-bar} skips group foo and/or bar (includes all others) see ./docs/examples/docker/idp/copyparty.conf ; https://github.com/9001/copyparty/blob/hovudstraum/docs/examples/docker/idp/copyparty.conf --- README.md | 2 +- copyparty/authsrv.py | 40 ++++++++++++++------- docs/examples/docker/idp/copyparty.conf | 7 ++++ tests/res/idp/7.conf | 46 +++++++++++++++++++++++++ tests/test_idp.py | 39 +++++++++++++++++++++ 5 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 tests/res/idp/7.conf diff --git a/README.md b/README.md index f7d2ef3a..14127da1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ turn almost any device into a file server with resumable uploads/downloads using * 🔌 protocols: [http](#the-browser) // [webdav](#webdav-server) // [ftp](#ftp-server) // [tftp](#tftp-server) // [smb/cifs](#smb-server) * 📱 [android app](#android-app) // [iPhone shortcuts](#ios-shortcuts) -👉 **[Get started](#quickstart)!** or visit the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running from a basement in finland +👉 **[Get started](#quickstart)!** or visit the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running on a nuc in my basement 📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer) diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index c0d451e7..98e05631 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -72,7 +72,9 @@ SSEELOG = " ({})".format(SEE_LOG) BAD_CFG = "invalid config; {}".format(SEE_LOG) SBADCFG = " ({})".format(BAD_CFG) -PTN_U_GRP = re.compile(r"\$\{u%([+-])([^}]+)\}") +PTN_U_GRP = re.compile(r"\$\{u(%[+-][^}]+)\}") +PTN_G_GRP = re.compile(r"\$\{g(%[+-][^}]+)\}") +PTN_SIGIL = re.compile(r"(\${[ug][}%])") class CfgEx(Exception): @@ -965,15 +967,27 @@ class AuthSrv(object): un_gn = [("", "")] for un, gn in un_gn: - m = PTN_U_GRP.search(dst0) - if m: - req, gnc = m.groups() - hit = gnc in (un_gns.get(un) or []) - if req == "+": - if not hit: - continue - elif hit: + rejected = False + for ptn in [PTN_U_GRP, PTN_G_GRP]: + m = ptn.search(dst0) + if not m: continue + zs = m.group(1) + zs = zs.replace(",%+", "\n%+") + zs = zs.replace(",%-", "\n%-") + for rule in zs.split("\n"): + gnc = rule[2:] + if ptn == PTN_U_GRP: + # is user member of group? + hit = gnc in (un_gns.get(un) or []) + else: + # is it this specific group? + hit = gn == gnc + + if rule.startswith("%+") != hit: + rejected = True + if rejected: + continue # if ap/vp has a user/group placeholder, make sure to keep # track so the same user/group is mapped when setting perms; @@ -988,6 +1002,8 @@ class AuthSrv(object): src = src1.replace("${g}", gn or "\n") dst = dst1.replace("${g}", gn or "\n") + src = PTN_G_GRP.sub(gn or "\n", src) + dst = PTN_G_GRP.sub(gn or "\n", dst) if src == src1 and dst == dst1: gn = "" @@ -1862,7 +1878,7 @@ class AuthSrv(object): is_shr = shr and zv.vpath.split("/")[0] == shr if histp and not is_shr and histp in rhisttab: zv2 = rhisttab[histp] - t = "invalid config; multiple volumes share the same histpath (database+thumbnails location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]" + t = "invalid config; multiple volumes share the same histpath (database+thumbnails location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: /%s [%s]" t = t % (histp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath) self.log(t, 1) raise Exception(t) @@ -1876,7 +1892,7 @@ class AuthSrv(object): is_shr = shr and zv.vpath.split("/")[0] == shr if dbp and not is_shr and dbp in rdbpaths: zv2 = rdbpaths[dbp] - t = "invalid config; multiple volumes share the same dbpath (database location):\n dbpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]" + t = "invalid config; multiple volumes share the same dbpath (database location):\n dbpath: %s\n volume 1: /%s [%s]\n volume 2: /%s [%s]" t = t % (dbp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath) self.log(t, 1) raise Exception(t) @@ -2393,7 +2409,7 @@ class AuthSrv(object): idp_vn, _ = vfs.get(idp_vp, "*", False, False) idp_vp0 = idp_vn.vpath0 - sigils = set(re.findall(r"(\${[ug][}%])", idp_vp0)) + sigils = set(PTN_SIGIL.findall(idp_vp0)) if len(sigils) > 1: t = '\nWARNING: IdP-volume "/%s" created by "/%s" has multiple IdP placeholders: %s' self.idp_warn.append(t % (idp_vp, idp_vp0, list(sigils))) diff --git a/docs/examples/docker/idp/copyparty.conf b/docs/examples/docker/idp/copyparty.conf index d14dd7b1..3e842f02 100644 --- a/docs/examples/docker/idp/copyparty.conf +++ b/docs/examples/docker/idp/copyparty.conf @@ -106,3 +106,10 @@ /w/tank1 [/m8s] /w/tank2 + + +# some other things you can do: +# [/demo/${u%-su,%-fds}] # users which are NOT members of "su" or "fds" +# [/demo/${u%+su,%+fds}] # users which ARE members of BOTH "su" and "fds" +# [/demo/${g%-su}] # all groups except su +# [/demo/${g%-su,%-fds}] # all groups except su and fds diff --git a/tests/res/idp/7.conf b/tests/res/idp/7.conf new file mode 100644 index 00000000..bc5fa33a --- /dev/null +++ b/tests/res/idp/7.conf @@ -0,0 +1,46 @@ +# -*- mode: yaml -*- +# vim: ft=yaml: + +[global] + idp-h-usr: x-idp-user + idp-h-grp: x-idp-group + +[/u/${u}] + /u/${u} + accs: + r: * + +[/uya/${u%+ga}] + /uya/${u} + accs: + r: * + +[/uyab/${u%+ga,%+gb}] + /uyab/${u} + accs: + r: * + +[/una/${u%-ga}] + /una/${u} + accs: + r: * + +[/unab/${u%-ga,%-gb}] + /unab/${u} + accs: + r: * + +[/gya/${g%+ga}] + /gya/${g} + accs: + r: * + +[/gna/${g%-ga}] + /gna/${g} + accs: + r: * + +[/gnab/${g%-ga,%-gb}] + /gnab/${g} + accs: + r: * diff --git a/tests/test_idp.py b/tests/test_idp.py index 8c12b569..80500c15 100644 --- a/tests/test_idp.py +++ b/tests/test_idp.py @@ -234,3 +234,42 @@ class TestVFS(unittest.TestCase): au.idp_checkin(None, "iud", "su") self.assertAxsAt(au, "team/su/iuc", [["iuc", "iud"]]) self.assertAxsAt(au, "team/su/iud", [["iuc", "iud"]]) + + def test_7(self): + """ + conditional idp-vols + """ + _, cfgdir, xcfg = self.prep() + au = AuthSrv(Cfg(c=[cfgdir + "/7.conf"], **xcfg), self.log) + au.idp_checkin(None, "iua", "ga") + au.idp_checkin(None, "iuab", "ga,gb") + au.idp_checkin(None, "iuabc", "ga,gb,gc") + au.idp_checkin(None, "iub", "gb") + au.idp_checkin(None, "iubc", "gb,gc") + au.idp_checkin(None, "iuc", "gc") + zs = """ +u/iua +u/iuab +u/iuabc +u/iub +u/iubc +u/iuc +uya/iua +uya/iuab +uya/iuabc +uyab/iuab +uyab/iuabc +una/iub +una/iubc +una/iuc +unab/iuc +gya/ga +gna/gb +gna/gc +gnab/gc +""" + zl1 = sorted(zs.strip().split("\n"))[:] + zl2 = sorted(list(au.vfs.all_vols))[:] + # print(" ".join(zl1)) + # print(" ".join(zl2)) + self.assertListEqual(zl1, zl2)