Harden live mail OAuth and immutable releases
deploy / deploy (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-30 02:00:09 +02:00
parent 1397f085bd
commit 67be350283
23 changed files with 429 additions and 20 deletions
+7 -1
View File
@@ -74,14 +74,19 @@ def _validate_connected_peer(
*,
before: ValidatedUrl,
after: ValidatedUrl,
require_peer: bool,
) -> None:
peer_ip = _connected_peer_ip(response)
if peer_ip is not None:
if not peer_ip.is_global:
raise FetchError(f"Niet-publiek verbonden IP-adres geblokkeerd: {peer_ip}")
return
if require_peer:
raise FetchError(
"Verbonden IP-adres kon niet veilig worden vastgesteld; aanvraag geblokkeerd."
)
# Mock/custom transports do not always expose the peer socket. Keep the conservative
# DNS-overlap fallback for those transports.
# DNS-overlap fallback only for explicitly injected test/custom clients.
if not set(before.addresses).intersection(after.addresses):
raise FetchError("DNS-rebindcontrole faalde bij het benaderen van bron.")
@@ -175,6 +180,7 @@ def fetch_url(
response,
before=validation,
after=post_validation,
require_peer=own_client,
)
if response.status_code in {301, 302, 303, 307, 308}:
location = response.headers.get("location")
+13 -2
View File
@@ -7,6 +7,7 @@ from typing import Any
from django.conf import settings
from apps.core.services.microsoft365 import acquire_exchange_access_token, xoauth2_bytes
from apps.sources.models import EmailMessageRecord, MailboxConnection
from apps.sources.services.email_import import ingest_email, message_identity
from apps.sources.services.mailbox_connections import decrypt_mailbox_password
@@ -30,7 +31,11 @@ def poll_imap_mailbox(
if client_factory is None:
client_factory = imaplib.IMAP4_SSL
host_validator(f"https://{connection.imap_host}")
password = decrypt_mailbox_password(connection)
password = (
""
if connection.provider == MailboxConnection.Provider.OUTLOOK
else decrypt_mailbox_password(connection)
)
client_options = {"timeout": settings.IMAP_CONNECT_TIMEOUT_SECONDS}
if client_factory is imaplib.IMAP4_SSL:
client_options["ssl_context"] = trusted_tls_context()
@@ -42,7 +47,13 @@ def poll_imap_mailbox(
"failed": 0,
}
try:
client.login(connection.username, password)
if connection.provider == MailboxConnection.Provider.OUTLOOK:
access_token = acquire_exchange_access_token()
client.authenticate(
"XOAUTH2", lambda _challenge: xoauth2_bytes(connection.username, access_token)
)
else:
client.login(connection.username, password)
status, _ = client.select(connection.mailbox, readonly=True)
if status != "OK":
raise RuntimeError("IMAP-mailbox kon niet worden geopend")
+7 -2
View File
@@ -48,7 +48,9 @@ def decrypt_mailbox_password(connection: MailboxConnection) -> str:
def rotate_mailbox_credentials() -> int:
"""Re-encrypt every mailbox password with the first configured key."""
rotated = 0
for connection in MailboxConnection.objects.select_for_update().all():
for connection in MailboxConnection.objects.select_for_update().exclude(
provider=MailboxConnection.Provider.OUTLOOK
):
password = decrypt_mailbox_password(connection)
connection.encrypted_password = encrypt_mailbox_password(password)
connection.save(update_fields=["encrypted_password", "updated_at"])
@@ -79,7 +81,10 @@ def save_mailbox_connection(
"poll_interval_minutes",
):
setattr(connection, field, cleaned_data[field])
if password:
uses_oauth = connection.provider == MailboxConnection.Provider.OUTLOOK
if uses_oauth:
connection.encrypted_password = ""
elif password:
connection.encrypted_password = encrypt_mailbox_password(password)
elif not connection.encrypted_password:
raise MailboxCredentialError("Een app-wachtwoord is verplicht.")