diff --git a/copyparty/__init__.py b/copyparty/__init__.py index ed9f8021..86b7fe1e 100644 --- a/copyparty/__init__.py +++ b/copyparty/__init__.py @@ -80,6 +80,7 @@ web/deps/prismd.css web/deps/scp.woff2 web/deps/sha512.ac.js web/deps/sha512.hw.js +web/idp.html web/iiam.gif web/md.css web/md.html diff --git a/copyparty/__main__.py b/copyparty/__main__.py index a92770d7..aa6744d8 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1102,6 +1102,7 @@ def add_auth(ap): ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m") ap2.add_argument("--idp-db", metavar="PATH", type=u, default=idp_db, help="where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs)") ap2.add_argument("--idp-store", metavar="N", type=int, default=1, help="how to use \033[33m--idp-db\033[0m; [\033[32m0\033[0m] = entirely disable, [\033[32m1\033[0m] = write-only (effectively disabled), [\033[32m2\033[0m] = remember users, [\033[32m3\033[0m] = remember users and groups.\nNOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP") + ap2.add_argument("--idp-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to use /?idp (the cache management UI)") ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app") ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins") ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)") diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 91e84051..0ad7b380 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -956,7 +956,7 @@ class AuthSrv(object): broker.ask("reload", False, True).get() return True - def _update_idp_db(self, uname, gname): + def _update_idp_db(self, uname: str, gname: str) -> None: if not self.args.idp_store: return @@ -2657,10 +2657,16 @@ class AuthSrv(object): db = sqlite3.connect(self.args.idp_db) cur = db.cursor() + from_cache = cur.execute("select un, gs from us").fetchall() + cur.close() + db.close() + + self.idp_accs.clear() + self.idp_usr_gh.clear() gsep = self.args.idp_gsep n = [] - for uname, gname in cur.execute("select un, gs from us"): + for uname, gname in from_cache: if level < 3: if uname in self.idp_accs: continue @@ -2672,9 +2678,6 @@ class AuthSrv(object): self.idp_accs[uname] = gnames n.append(uname) - cur.close() - db.close() - if n and not quiet: t = ", ".join(n[:9]) if len(n) > 9: diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 733f2eb4..f3d933fc 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -1299,6 +1299,9 @@ class HttpCli(object): if "ru" in self.uparam: return self.tx_rups() + if "idp" in self.uparam: + return self.tx_idp() + if "h" in self.uparam: return self.tx_mounts() @@ -5537,6 +5540,32 @@ class HttpCli(object): self.reply(html.encode("utf-8"), status=200) return True + def tx_idp(self) -> bool: + if self.uname.lower() not in self.args.idp_adm_set: + raise Pebkac(403, "'idp' not allowed for user " + self.uname) + + cmd = self.uparam["idp"] + if cmd.startswith("rm="): + import sqlite3 + + db = sqlite3.connect(self.args.idp_db) + db.execute("delete from us where un=?", (cmd[3:],)) + db.commit() + db.close() + + self.conn.hsrv.broker.ask("reload", False, False).get() + + self.redirect("", "?idp") + return True + + rows = [ + [k, "[%s]" % ("], [".join(v))] + for k, v in sorted(self.asrv.idp_accs.items()) + ] + html = self.j2s("idp", this=self, rows=rows, now=int(time.time())) + self.reply(html.encode("utf-8"), status=200) + return True + def tx_shares(self) -> bool: if self.uname == "*": self.loud_reply("you're not logged in") @@ -5611,7 +5640,7 @@ class HttpCli(object): self.conn.hsrv.broker.ask("reload", False, False).get() self.conn.hsrv.broker.ask("up2k.wake_rescanner").get() - self.redirect(self.args.SRS + "?shares") + self.redirect("", "?shares") return True def handle_share(self, req: dict[str, str]) -> bool: diff --git a/copyparty/httpsrv.py b/copyparty/httpsrv.py index 75139cc1..2e5cc69e 100644 --- a/copyparty/httpsrv.py +++ b/copyparty/httpsrv.py @@ -175,6 +175,7 @@ class HttpSrv(object): "browser", "browser2", "cf", + "idp", "md", "mde", "msg", diff --git a/copyparty/svchub.py b/copyparty/svchub.py index e7e3b86c..b498afe2 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -949,6 +949,12 @@ class SvcHub(object): vs = os.path.expandvars(os.path.expanduser(vs)) setattr(al, k, vs) + for k in "idp_adm".split(" "): + vs = getattr(al, k) + vsa = [x.strip() for x in vs.split(",")] + vsa = [x.lower() for x in vsa if x] + setattr(al, k + "_set", set(vsa)) + zs = "dav_ua1 sus_urls nonsus_urls ua_nodoc ua_nozip" for k in zs.split(" "): vs = getattr(al, k) diff --git a/copyparty/web/idp.html b/copyparty/web/idp.html new file mode 100644 index 00000000..b67dd2f1 --- /dev/null +++ b/copyparty/web/idp.html @@ -0,0 +1,55 @@ + + + +
+ +forget | +user | +groups | +
---|---|---|
forget | +{{ un|e }} | +{{ gn|e }} | +