mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge pull request #725 from paperless-ngx/bugfix-imap-utf8-login
Fixes IMAP UTF8 Authenication
This commit is contained in:
commit
0f1e31643d
@ -3,6 +3,7 @@ import tempfile
|
||||
from datetime import date
|
||||
from datetime import timedelta
|
||||
from fnmatch import fnmatch
|
||||
from imaplib import IMAP4
|
||||
|
||||
import magic
|
||||
import pathvalidate
|
||||
@ -145,7 +146,7 @@ class MailAccountHandler(LoggingMixin):
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Unknwown correspondent selector",
|
||||
"Unknown correspondent selector",
|
||||
) # pragma: nocover
|
||||
|
||||
def handle_mail_account(self, account):
|
||||
@ -164,7 +165,33 @@ class MailAccountHandler(LoggingMixin):
|
||||
|
||||
try:
|
||||
M.login(account.username, account.password)
|
||||
|
||||
except UnicodeEncodeError:
|
||||
self.log("debug", "Falling back to AUTH=PLAIN")
|
||||
try:
|
||||
# rfc2595 section 6 - PLAIN SASL mechanism
|
||||
client: IMAP4 = M.client
|
||||
encoded = (
|
||||
b"\0"
|
||||
+ account.username.encode("utf8")
|
||||
+ b"\0"
|
||||
+ account.password.encode("utf8")
|
||||
)
|
||||
# Assumption is the server supports AUTH=PLAIN capability
|
||||
# Could check the list with client.capability(), but then what?
|
||||
# We're failing anyway then
|
||||
client.authenticate("PLAIN", lambda x: encoded)
|
||||
|
||||
# Need to transition out of AUTH state to SELECTED
|
||||
M.folder.set("INBOX")
|
||||
except Exception:
|
||||
self.log(
|
||||
"error",
|
||||
"Unable to authenticate with mail server using AUTH=PLAIN",
|
||||
)
|
||||
raise MailError(f"Error while authenticating account {account}")
|
||||
except Exception:
|
||||
self.log("error", "Unable to authenticate with mail server")
|
||||
raise MailError(f"Error while authenticating account {account}")
|
||||
|
||||
self.log(
|
||||
@ -184,7 +211,7 @@ class MailAccountHandler(LoggingMixin):
|
||||
|
||||
return total_processed_files
|
||||
|
||||
def handle_mail_rule(self, M, rule):
|
||||
def handle_mail_rule(self, M: MailBox, rule):
|
||||
|
||||
self.log("debug", f"Rule {rule}: Selecting folder {rule.folder}")
|
||||
|
||||
|
@ -17,6 +17,7 @@ from documents.tests.utils import DirectoriesMixin
|
||||
from imap_tools import EmailAddress
|
||||
from imap_tools import FolderInfo
|
||||
from imap_tools import MailboxFolderSelectError
|
||||
from imap_tools import MailboxLoginError
|
||||
from imap_tools import MailMessage
|
||||
from imap_tools import MailMessageFlags
|
||||
from paperless_mail import tasks
|
||||
@ -44,6 +45,14 @@ class BogusFolderManager:
|
||||
self.current_folder = new_folder
|
||||
|
||||
|
||||
class BogusClient(object):
|
||||
def authenticate(self, mechanism, authobject):
|
||||
# authobject must be a callable object
|
||||
auth_bytes = authobject(None)
|
||||
if auth_bytes != b"\x00admin\x00w57\xc3\xa4\xc3\xb6\xc3\xbcw4b6huwb6nhu":
|
||||
raise MailboxLoginError("BAD", "OK")
|
||||
|
||||
|
||||
class BogusMailBox(ContextManager):
|
||||
def __enter__(self):
|
||||
return self
|
||||
@ -55,10 +64,14 @@ class BogusMailBox(ContextManager):
|
||||
self.messages: List[MailMessage] = []
|
||||
self.messages_spam: List[MailMessage] = []
|
||||
self.folder = BogusFolderManager()
|
||||
self.client = BogusClient()
|
||||
|
||||
def login(self, username, password):
|
||||
if not (username == "admin" and password == "secret"):
|
||||
raise Exception()
|
||||
# This will raise a UnicodeEncodeError if the password is not ASCII only
|
||||
password.encode("ascii")
|
||||
# Otherwise, check for correct values
|
||||
if username != "admin" or password not in {"secret"}:
|
||||
raise MailboxLoginError("BAD", "OK")
|
||||
|
||||
def fetch(self, criteria, mark_seen, charset=""):
|
||||
msg = self.messages
|
||||
@ -819,6 +832,66 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 2)
|
||||
self.assertEqual(self.async_task.call_count, 5)
|
||||
|
||||
def test_auth_plain_fallback(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Mail account with password containing non-ASCII characters
|
||||
THEN:
|
||||
- Should still authenticate to the mail account
|
||||
"""
|
||||
account = MailAccount.objects.create(
|
||||
name="test",
|
||||
imap_server="",
|
||||
username="admin",
|
||||
# Note the non-ascii characters here
|
||||
password="w57äöüw4b6huwb6nhu",
|
||||
)
|
||||
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.MailAction.MARK_READ,
|
||||
)
|
||||
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
||||
self.assertEqual(self.async_task.call_count, 0)
|
||||
self.assertEqual(len(self.bogus_mailbox.fetch("UNSEEN", False)), 2)
|
||||
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
self.assertEqual(self.async_task.call_count, 2)
|
||||
self.assertEqual(len(self.bogus_mailbox.fetch("UNSEEN", False)), 0)
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
||||
|
||||
def test_auth_plain_fallback_fails_still(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Mail account with password containing non-ASCII characters
|
||||
- Incorrect password value
|
||||
THEN:
|
||||
- Should raise a MailError for the account
|
||||
"""
|
||||
account = MailAccount.objects.create(
|
||||
name="test",
|
||||
imap_server="",
|
||||
username="admin",
|
||||
# Note the non-ascii characters here
|
||||
# Passes the check in login, not in authenticate
|
||||
password="réception",
|
||||
)
|
||||
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.MailAction.MARK_READ,
|
||||
)
|
||||
|
||||
self.assertRaises(
|
||||
MailError,
|
||||
self.mail_account_handler.handle_mail_account,
|
||||
account,
|
||||
)
|
||||
|
||||
|
||||
class TestManagementCommand(TestCase):
|
||||
@mock.patch(
|
||||
|
Loading…
x
Reference in New Issue
Block a user