fix: retry mailu sync when bcrypt rejects password
This commit is contained in:
parent
04e740d3b9
commit
f332549a2d
@ -39,6 +39,10 @@ class MailuUserSyncResult:
|
||||
updated: int = 0
|
||||
skipped: int = 0
|
||||
failures: int = 0
|
||||
|
||||
|
||||
class PasswordTooLongError(RuntimeError):
|
||||
pass
|
||||
mailboxes: int = 0
|
||||
|
||||
|
||||
@ -240,13 +244,34 @@ class MailuService:
|
||||
|
||||
mailbox_ok = False
|
||||
failed = False
|
||||
rotated = False
|
||||
try:
|
||||
mailbox_ok = self._ensure_mailbox(conn, mailu_email, app_password, _display_name(user))
|
||||
except PasswordTooLongError as exc:
|
||||
rotated = True
|
||||
app_password = random_password(24)
|
||||
try:
|
||||
keycloak_admin.set_user_attribute(username, MAILU_APP_PASSWORD_ATTR, app_password)
|
||||
logger.info(
|
||||
"mailu app password rotated",
|
||||
extra={
|
||||
"event": "mailu_sync",
|
||||
"status": "updated",
|
||||
"detail": "app password exceeded bcrypt limit",
|
||||
"username": username,
|
||||
},
|
||||
)
|
||||
mailbox_ok = self._ensure_mailbox(conn, mailu_email, app_password, _display_name(user))
|
||||
except Exception as retry_exc:
|
||||
self._log_sync_error(username, str(retry_exc))
|
||||
failed = True
|
||||
except Exception as exc:
|
||||
self._log_sync_error(username, str(exc))
|
||||
failed = True
|
||||
|
||||
result = MailuUserSyncResult(skipped=1, updated=updated)
|
||||
if rotated:
|
||||
result = MailuUserSyncResult(skipped=1, updated=max(updated, 1))
|
||||
if failed:
|
||||
result = MailuUserSyncResult(failures=1, updated=updated)
|
||||
elif mailbox_ok:
|
||||
@ -266,10 +291,15 @@ class MailuService:
|
||||
if not _domain_matches(email):
|
||||
return False
|
||||
if _password_too_long(password):
|
||||
raise ValueError("mailu password exceeds bcrypt limit")
|
||||
raise PasswordTooLongError("mailu password exceeds bcrypt limit")
|
||||
|
||||
localpart, domain = email.split("@", 1)
|
||||
try:
|
||||
hashed = bcrypt_sha256.hash(password)
|
||||
except ValueError as exc:
|
||||
if "password cannot be longer than 72 bytes" in str(exc):
|
||||
raise PasswordTooLongError(str(exc)) from exc
|
||||
raise
|
||||
now = datetime.now(timezone.utc)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
|
||||
@ -5,6 +5,7 @@ import types
|
||||
|
||||
import pytest
|
||||
|
||||
from ariadne.services import mailu as mailu_module
|
||||
from ariadne.services.firefly import FireflyService
|
||||
from ariadne.services.mailu import MailuService
|
||||
from ariadne.services.nextcloud import NextcloudService
|
||||
@ -451,6 +452,69 @@ def test_mailu_sync_rotates_long_password(monkeypatch) -> None:
|
||||
assert mailbox_calls
|
||||
assert mailbox_calls[0][1] == "short-pass-123"
|
||||
|
||||
|
||||
def test_mailu_sync_retries_on_password_limit(monkeypatch) -> None:
|
||||
dummy_settings = types.SimpleNamespace(
|
||||
mailu_domain="bstein.dev",
|
||||
mailu_db_host="localhost",
|
||||
mailu_db_port=5432,
|
||||
mailu_db_name="mailu",
|
||||
mailu_db_user="mailu",
|
||||
mailu_db_password="secret",
|
||||
mailu_default_quota=20000000000,
|
||||
mailu_system_users=[],
|
||||
mailu_system_password="",
|
||||
)
|
||||
monkeypatch.setattr("ariadne.services.mailu.settings", dummy_settings)
|
||||
monkeypatch.setattr("ariadne.services.mailu.keycloak_admin.ready", lambda: True)
|
||||
monkeypatch.setattr(
|
||||
"ariadne.services.mailu.keycloak_admin.iter_users",
|
||||
lambda *args, **kwargs: [
|
||||
{
|
||||
"id": "1",
|
||||
"username": "alice",
|
||||
"enabled": True,
|
||||
"email": "alice@example.com",
|
||||
"attributes": {"mailu_app_password": ["short-pass"]},
|
||||
"firstName": "Alice",
|
||||
"lastName": "Example",
|
||||
}
|
||||
],
|
||||
)
|
||||
monkeypatch.setattr("ariadne.services.mailu.random_password", lambda *_args, **_kwargs: "retry-pass-1")
|
||||
|
||||
set_calls: list[tuple[str, str, str]] = []
|
||||
monkeypatch.setattr(
|
||||
"ariadne.services.mailu.keycloak_admin.set_user_attribute",
|
||||
lambda username, key, value: set_calls.append((username, key, value)),
|
||||
)
|
||||
|
||||
call_count = {"count": 0}
|
||||
|
||||
def fake_ensure(self, _conn, _email, password, _display):
|
||||
call_count["count"] += 1
|
||||
if call_count["count"] == 1:
|
||||
raise mailu_module.PasswordTooLongError("password cannot be longer than 72 bytes")
|
||||
assert password == "retry-pass-1"
|
||||
return True
|
||||
|
||||
monkeypatch.setattr("ariadne.services.mailu.MailuService._ensure_mailbox", fake_ensure)
|
||||
|
||||
class DummyConn:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
|
||||
monkeypatch.setattr("ariadne.services.mailu.psycopg.connect", lambda *args, **kwargs: DummyConn())
|
||||
|
||||
svc = MailuService()
|
||||
summary = svc.sync("provision", force=True)
|
||||
|
||||
assert summary.processed == 1
|
||||
assert call_count["count"] == 2
|
||||
assert set_calls
|
||||
def test_mailu_sync_skips_disabled(monkeypatch) -> None:
|
||||
dummy_settings = types.SimpleNamespace(
|
||||
mailu_domain="bstein.dev",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user