diff --git a/copyparty/__main__.py b/copyparty/__main__.py
index 26158879..37427fb1 100644
--- a/copyparty/__main__.py
+++ b/copyparty/__main__.py
@@ -676,6 +676,42 @@ def get_sects():
"""
),
],
+ [
+ "auth-ord",
+ "authentication precedence",
+ dedent(
+ """
+ \033[33m--auth-ord\033[0m is a comma-separated list of auth options
+ (one or more of the [\033[35moptions\033[0m] below); first one wins
+
+ [\033[35mpw\033[0m] is conventional login, for example the "\033[36mPW\033[0m" header,
+ or the \033[36m?pw=\033[0m[...] URL-suffix, or a valid session cookie
+ (see \033[33m--help-auth\033[0m)
+
+ [\033[35midp\033[0m] is a username provided in the http-request-header
+ defined by \033[33m--idp-h-usr\033[0m and/or \033[33m--idp-hm-usr\033[0m, which is
+ provided by an authentication middleware such as
+ authentik, authelia, tailscale, ... (see \033[33m--help-idp\033[0m)
+
+ [\033[35midp-h\033[0m] is specifically the \033[33m--idp-h-usr\033[0m header,
+ [\033[35midp-hm\033[0m] is specifically an \033[33m--idp-hm-usr\033[0m header;
+ [\033[35midp\033[0m] is the same as [\033[35midp-hm,idp-h\033[0m]
+
+ [\033[35mipu\033[0m] is a mapping from an IP-address to a username,
+ auto-authing that client-IP to that account
+ (see the description of \033[36m--ipu\033[0m in \033[33m--help\033[0m)
+
+ NOTE: even if an option (\033[35mpw\033[0m/\033[35mipu\033[0m/...) is not in the list,
+ it may still be enabled and can still take effect if
+ none of the other alternatives identify the user
+
+ NOTE: if [\033[35mipu\033[0m] is in the list, it must be FIRST or LAST
+
+ NOTE: if [\033[35mpw\033[0m] is not in the list, the logout-button
+ will be hidden when any idp feature is enabled
+ """
+ ),
+ ],
[
"flags",
"list of volflags",
@@ -1254,6 +1290,7 @@ def add_auth(ap):
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("--idp-cookie", metavar="S", type=int, default=0, help="generate a session-token for IdP users which is written to cookie \033[33mcppws\033[0m (or \033[33mcppwd\033[0m if plaintext), to reduce the load on the IdP server, lifetime \033[33mS\033[0m seconds.\n └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with \033[33m--ses-db\033[0m wiped)")
+ ap2.add_argument("--auth-ord", metavar="TXT", type=u, default="idp,ipu", help="controls auth precedence; examples: [\033[32mpw,idp,ipu\033[0m], [\033[32mipu,pw,idp\033[0m], see --help-auth-ord")
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)")
@@ -1264,6 +1301,10 @@ def add_auth(ap):
ap2.add_argument("--ipr", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m username \033[33mUSR\033[0m can only connect from an IP matching one or more \033[33mCIDR\033[0m (comma-sep.); example: [\033[32m192.168.123.0/24,172.16.0.0/16=dave]")
ap2.add_argument("--have-idp-hdrs", type=u, default="", help=argparse.SUPPRESS)
ap2.add_argument("--have-ipu-or-ipr", type=u, default="", help=argparse.SUPPRESS)
+ ap2.add_argument("--ao-idp-before-pw", type=u, default="", help=argparse.SUPPRESS)
+ ap2.add_argument("--ao-h-before-hm", type=u, default="", help=argparse.SUPPRESS)
+ ap2.add_argument("--ao-ipu-wins", type=u, default="", help=argparse.SUPPRESS)
+ ap2.add_argument("--ao-has-pw", type=u, default="", help=argparse.SUPPRESS)
def add_chpw(ap):
diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py
index 4309ba2c..ff73f19a 100644
--- a/copyparty/authsrv.py
+++ b/copyparty/authsrv.py
@@ -1713,6 +1713,7 @@ class AuthSrv(object):
self.args.have_idp_hdrs = bool(self.args.idp_h_usr or self.args.idp_hm_usr)
self.args.have_ipu_or_ipr = bool(self.args.ipu or self.args.ipr)
+ self.setup_auth_ord()
self.setup_pwhash(acct)
defpw = acct.copy()
@@ -2864,6 +2865,18 @@ class AuthSrv(object):
zs = str(vol.flags.get("tcolor") or self.args.tcolor)
vol.flags["tcolor"] = zs.lstrip("#")
+ def setup_auth_ord(self) -> None:
+ ao = [x.strip() for x in self.args.auth_ord.split(",")]
+ if "idp" in ao:
+ zi = ao.index("idp")
+ ao = ao[:zi] + ["idp-hm", "idp-h"] + ao[zi:]
+ zsl = "pw idp-h idp-hm ipu".split()
+ pw, h, hm, ipu = [ao.index(x) if x in ao else 99 for x in zsl]
+ self.args.ao_idp_before_pw = min(h, hm) < pw
+ self.args.ao_h_before_hm = h < hm
+ self.args.ao_ipu_wins = ipu == 0
+ self.args.ao_have_pw = pw < 99
+
def load_idp_db(self, quiet=False) -> None:
# mutex me
level = self.args.idp_store
diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py
index 88a83105..21676f33 100644
--- a/copyparty/httpcli.py
+++ b/copyparty/httpcli.py
@@ -624,7 +624,9 @@ class HttpCli(object):
or "*"
)
- if self.args.have_idp_hdrs:
+ if self.args.have_idp_hdrs and (
+ self.uname == "*" or self.args.ao_idp_before_pw
+ ):
idp_usr = ""
if self.args.idp_hm_usr:
for hn, hmv in self.args.idp_hm_usr_p.items():
@@ -637,9 +639,9 @@ class HttpCli(object):
if idp_usr:
break
for hn in self.args.idp_h_usr:
- if idp_usr:
+ if idp_usr and not self.args.ao_h_before_hm:
break
- idp_usr = self.headers.get(hn)
+ idp_usr = self.headers.get(hn) or idp_usr
if idp_usr:
idp_grp = (
self.headers.get(self.args.idp_h_grp) or ""
@@ -688,7 +690,10 @@ class HttpCli(object):
if idp_usr in self.asrv.vfs.aread:
self.pw = ""
self.uname = idp_usr
- self.html_head += "\n"
+ if self.args.ao_have_pw:
+ self.html_head += "\n"
+ else:
+ self.html_head += "\n"
zs = self.asrv.ases.get(idp_usr)
if zs:
self.set_idp_cookie(zs)
@@ -696,7 +701,7 @@ class HttpCli(object):
self.log("unknown username: %r" % (idp_usr,), 1)
if self.args.have_ipu_or_ipr:
- if self.args.ipu and self.uname == "*":
+ if self.args.ipu and (self.uname == "*" or self.args.ao_ipu_wins):
self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
ipr = self.conn.hsrv.ipr
if ipr and self.uname in ipr:
diff --git a/copyparty/web/splash.js b/copyparty/web/splash.js
index 4ff09fd1..150b7dac 100644
--- a/copyparty/web/splash.js
+++ b/copyparty/web/splash.js
@@ -834,7 +834,7 @@ if (o1 && o2 && d.lo3)
o1.setAttribute("value", d.lo3.format(o2.textContent));
try {
- if (is_idp) {
+ if (is_idp > 1) {
var z = ['#l+div', '#l', '#c'];
for (var a = 0; a < z.length; a++)
QS(z[a]).style.display = 'none';
diff --git a/tests/util.py b/tests/util.py
index 9b1a0e27..06f5fd50 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -183,6 +183,7 @@ class Cfg(Namespace):
v=v or [],
c=c,
E=E,
+ auth_ord="idp,ipu",
bup_ck="sha512",
chmod_d="755",
cookie_cmax=8192,