import pytest from cryptography.fernet import Fernet from django.core.exceptions import ValidationError from django.test import override_settings from apps.sources.models import MailboxConnection from apps.sources.services.imap_import import poll_imap_mailbox from apps.sources.services.mailbox_connections import ( MailboxCredentialError, decrypt_mailbox_password, encrypt_mailbox_password, rotate_mailbox_credentials, ) from apps.sources.services.url_security import UnsafeUrlError def test_mailbox_password_is_encrypted_and_supports_key_rotation(): old_key = Fernet.generate_key().decode("ascii") new_key = Fernet.generate_key().decode("ascii") with override_settings(MAILBOX_CREDENTIAL_KEYS=[old_key]): ciphertext = encrypt_mailbox_password("app-password-value") connection = MailboxConnection(encrypted_password=ciphertext) assert "app-password-value" not in ciphertext with override_settings(MAILBOX_CREDENTIAL_KEYS=[new_key, old_key]): assert decrypt_mailbox_password(connection) == "app-password-value" rotated = encrypt_mailbox_password("replacement-value") assert Fernet(new_key.encode("ascii")).decrypt(rotated.encode("ascii")) == b"replacement-value" def test_mailbox_password_fails_closed_without_valid_key(): with ( override_settings(MAILBOX_CREDENTIAL_KEYS=[]), pytest.raises(MailboxCredentialError, match="niet geconfigureerd"), ): encrypt_mailbox_password("never-stored") with ( override_settings(MAILBOX_CREDENTIAL_KEYS=["invalid"]), pytest.raises(MailboxCredentialError, match="ongeldige sleutel"), ): encrypt_mailbox_password("never-stored") @pytest.mark.parametrize("host", ["", "https://imap.example.org", "imap.example.org/path", "a b"]) def test_custom_mailbox_host_rejects_non_hostname_values(host): connection = MailboxConnection( platform=MailboxConnection.Platform.VDAB, provider=MailboxConnection.Provider.CUSTOM, custom_host=host, port=993, username="mailbox@example.invalid", encrypted_password="ciphertext", ) with pytest.raises(ValidationError): connection.clean() @pytest.mark.parametrize("port", [0, 65536]) def test_mailbox_rejects_invalid_port(port): connection = MailboxConnection( platform=MailboxConnection.Platform.VDAB, provider=MailboxConnection.Provider.GMAIL, port=port, username="mailbox@example.invalid", encrypted_password="ciphertext", ) with pytest.raises(ValidationError): connection.clean() @pytest.mark.django_db def test_rotation_reencrypts_existing_credentials_with_primary_key(user): old_key = Fernet.generate_key().decode("ascii") new_key = Fernet.generate_key().decode("ascii") with override_settings(MAILBOX_CREDENTIAL_KEYS=[old_key]): ciphertext = encrypt_mailbox_password("rotate-me") connection = MailboxConnection.objects.create( user=user, platform=MailboxConnection.Platform.VDAB, provider=MailboxConnection.Provider.GMAIL, username="rotate@example.invalid", encrypted_password=ciphertext, ) with override_settings(MAILBOX_CREDENTIAL_KEYS=[new_key, old_key]): assert rotate_mailbox_credentials() == 1 connection.refresh_from_db() with override_settings(MAILBOX_CREDENTIAL_KEYS=[new_key]): assert decrypt_mailbox_password(connection) == "rotate-me" @pytest.mark.django_db def test_rotation_skips_outlook_oauth_connections(user): MailboxConnection.objects.create( user=user, platform=MailboxConnection.Platform.INDEED, provider=MailboxConnection.Provider.OUTLOOK, username="vacatureradar@example.invalid", encrypted_password="", ) assert rotate_mailbox_credentials() == 0 @pytest.mark.django_db def test_private_imap_host_is_blocked_before_connection(user): key = Fernet.generate_key().decode("ascii") with override_settings(MAILBOX_CREDENTIAL_KEYS=[key]): connection = MailboxConnection.objects.create( user=user, platform=MailboxConnection.Platform.VDAB, provider=MailboxConnection.Provider.CUSTOM, custom_host="127.0.0.1", username="private@example.invalid", encrypted_password=encrypt_mailbox_password("never-sent"), ) with pytest.raises(UnsafeUrlError, match="Niet-publiek"): poll_imap_mailbox( connection, client_factory=lambda *args, **kwargs: pytest.fail( "Private IMAP-host had niet geconnecteerd mogen worden" ), )