loomio: sanitize usernames instead of falling back to another invalid one
The username fix rejected a dot-containing OAuth uid and then substituted the
email local part, which carries the same dots — so Loomio answered 422
("Usernames must contain lower case letters, numbers, underscores, and internal
hyphens only") on every sweep, for the one member whose Cloudron username has a
dot. Normalize instead: disallowed characters become underscores, and leading
and trailing hyphens/underscores are stripped. Also log the Loomio user id
rather than the member's email.
This commit is contained in:
1 parent
e24c272e2e
commit
178a4aa360
1 file changed
+21
-11
+21
-11
@@ -995,6 +995,18 @@ def set_member_matrix_mxid(email: str, mxid: str) -> None:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _loomio_safe_username(raw) -> str | None:
|
||||
"""Sanitize a candidate into a Loomio-legal username.
|
||||
|
||||
Loomio accepts lowercase letters, numbers, underscores and INTERNAL hyphens
|
||||
only, so a dot-containing candidate — an OAuth uid or an email local part
|
||||
such as "joshua.z.tan" — must be normalized rather than passed through.
|
||||
Passing it through is what made this fix fail with HTTP 422 on every sweep.
|
||||
"""
|
||||
u = re.sub(r"[^a-z0-9_-]", "_", (raw or "").lower()).strip("-_")
|
||||
return u if len(u) >= 2 else None
|
||||
|
||||
|
||||
async def loomio_fix_usernames(client: httpx.AsyncClient) -> dict:
|
||||
"""Rename Loomio users with auto-generated handles to their Cloudron username.
|
||||
|
||||
@@ -1014,16 +1026,13 @@ async def loomio_fix_usernames(client: httpx.AsyncClient) -> dict:
|
||||
target = None
|
||||
for ident in u.get("identities") or []:
|
||||
if ident.get("identity_type") == "oauth" and ident.get("uid"):
|
||||
uid = ident["uid"].lower()
|
||||
# Loomio usernames allow lowercase letters, numbers, underscores and hyphens.
|
||||
if uid.replace("_", "").replace("-", "").isalnum() and len(uid) >= 2:
|
||||
target = uid
|
||||
target = _loomio_safe_username(ident["uid"])
|
||||
if not target:
|
||||
# uid unusable even after sanitizing — fall back to the email local part
|
||||
target = _loomio_safe_username((u.get("email") or "").split("@")[0])
|
||||
break
|
||||
# uid unusable (dots etc) — use email local-part instead
|
||||
target = (u.get("email") or "").split("@")[0].lower() or None
|
||||
break
|
||||
if not target and u.get("email") and "@" in u["email"]:
|
||||
target = u["email"].split("@")[0].lower()
|
||||
if not target:
|
||||
target = _loomio_safe_username((u.get("email") or "").split("@")[0])
|
||||
if not target or u.get("username") == target:
|
||||
skipped += 1
|
||||
continue
|
||||
@@ -1034,8 +1043,9 @@ async def loomio_fix_usernames(client: httpx.AsyncClient) -> dict:
|
||||
if rr.status_code == 200:
|
||||
fixed += 1
|
||||
else:
|
||||
logger.warning("Loomio username fix for %s failed: %s %s",
|
||||
u.get("email"), rr.status_code, rr.text[:120])
|
||||
# log the Loomio user id, never the member's email
|
||||
logger.warning("Loomio username fix for user %s failed: %s %s",
|
||||
u.get("id"), rr.status_code, rr.text[:120])
|
||||
if fixed:
|
||||
logger.info("Loomio usernames fixed: %s", fixed)
|
||||
return {"fixed": fixed, "skipped": skipped}
|
||||
|
||||
Reference in new issue
Block a user