"""Credential handling for the mail module. Both checks guard fixes for real defects: the account file used to be written at the umask and chmodded afterwards, and the IMAP/SMTP connections used to take Python's stdlib SSL context, which verifies nothing. """ import json import os import ssl import stat from modules.mail import backend as mail def test_account_file_is_never_group_or_world_readable(tmp_path, monkeypatch): monkeypatch.setattr(mail, "_ACCOUNT_FILE", tmp_path / "mail_accounts.json") mail._write_accounts([{**mail._DEFAULTS, "id": "abc", "username": "u", "password": "secret"}]) mode = stat.S_IMODE((tmp_path / "mail_accounts.json").stat().st_mode) assert mode == 0o600, f"account file is {oct(mode)}, expected 0o600" assert not list(tmp_path.glob("*.tmp")), "temp file left behind" # The password round-trips to disk but never to the API. assert mail.load_accounts()[0]["password"] == "secret" assert "password" not in mail.public_account(mail.get_account("abc")) assert mail.public_account(mail.get_account("abc"))["has_password"] is True assert json.loads((tmp_path / "mail_accounts.json").read_text())["accounts"] def test_account_file_is_0600_while_it_is_being_written(tmp_path, monkeypatch): """The old code wrote at the umask and chmodded afterwards, so the file sat world-readable for the length of the write. Assert the handle it is written through is already 0600 -- the pre-fix version never wrote through a handle at all (json.dumps to a string, then write_text), so this fails against it.""" monkeypatch.setattr(mail, "_ACCOUNT_FILE", tmp_path / "mail_accounts.json") seen = {} real_dump = mail.json.dump def spy(obj, fh, **kw): seen["mode"] = stat.S_IMODE(os.fstat(fh.fileno()).st_mode) return real_dump(obj, fh, **kw) monkeypatch.setattr(mail.json, "dump", spy) mail._write_accounts([{**mail._DEFAULTS, "id": "abc", "username": "u", "password": "secret"}]) assert seen["mode"] == 0o600, f"written through a {oct(seen['mode'])} handle" def test_tls_context_verifies_certificate_and_hostname(): # ssl._create_stdlib_context(), the imaplib/smtplib fallback, gives # CERT_NONE + check_hostname False -- which is what this replaced. assert mail._TLS.verify_mode is ssl.CERT_REQUIRED assert mail._TLS.check_hostname is True