Allows users to use OAuth tokens instead of passwords

This commit is contained in:
Trenton H 2023-03-22 11:54:20 -07:00
parent 14b997fe2c
commit 09b1413748
8 changed files with 92 additions and 12 deletions

View File

@ -15,6 +15,7 @@
<div class="col">
<app-input-text i18n-title title="Username" formControlName="username" [error]="error?.username"></app-input-text>
<app-input-password i18n-title title="Password" formControlName="password" [error]="error?.password"></app-input-password>
<app-input-check i18n-title title="Is Token?" formControlName="is_token" [error]="error?.is_token"></app-input-check>
<app-input-text i18n-title title="Character Set" formControlName="character_set" [error]="error?.character_set"></app-input-text>
</div>
</div>

View File

@ -45,6 +45,7 @@ export class MailAccountEditDialogComponent extends EditDialogComponent<Paperles
imap_security: new FormControl(IMAPSecurity.SSL),
username: new FormControl(null),
password: new FormControl(null),
is_token: new FormControl(false),
character_set: new FormControl('UTF-8'),
})
}

View File

@ -20,4 +20,6 @@ export interface PaperlessMailAccount extends ObjectWithId {
password: string
character_set?: string
is_token: boolean
}

View File

@ -202,20 +202,21 @@ def mailbox_login(mailbox: MailBox, account: MailAccount):
try:
mailbox.login(account.username, account.password)
if account.is_token:
mailbox.xoauth2(account.username, account.password)
else:
try:
_ = account.password.encode("ascii")
use_ascii_login = True
except UnicodeEncodeError:
use_ascii_login = False
except UnicodeEncodeError:
logger.debug("Falling back to AUTH=PLAIN")
if use_ascii_login:
mailbox.login(account.username, account.password)
else:
logger.debug("Falling back to AUTH=PLAIN")
mailbox.login_utf8(account.username, account.password)
try:
mailbox.login_utf8(account.username, account.password)
except Exception as e:
logger.error(
"Unable to authenticate with mail server using AUTH=PLAIN",
)
raise MailError(
f"Error while authenticating account {account}",
) from e
except Exception as e:
logger.error(
f"Error while authenticating account {account}: {e}",

View File

@ -0,0 +1,19 @@
# Generated by Django 4.1.7 on 2023-03-22 17:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("paperless_mail", "0019_mailrule_filter_to"),
]
operations = [
migrations.AddField(
model_name="mailaccount",
name="is_token",
field=models.BooleanField(
default=False, verbose_name="Is token authentication"
),
),
]

View File

@ -38,6 +38,8 @@ class MailAccount(document_models.ModelWithOwner):
password = models.CharField(_("password"), max_length=256)
is_token = models.BooleanField(_("Is token authentication"), default=False)
character_set = models.CharField(
_("character set"),
max_length=256,

View File

@ -34,6 +34,7 @@ class MailAccountSerializer(OwnedObjectSerializer):
"username",
"password",
"character_set",
"is_token",
]
def update(self, instance, validated_data):

View File

@ -83,6 +83,8 @@ class BogusMailBox(ContextManager):
ASCII_PASSWORD: str = "secret"
# Note the non-ascii characters here
UTF_PASSWORD: str = "w57äöüw4b6huwb6nhu"
# A dummy access token
ACCESS_TOKEN = "ea7e075cd3acf2c54c48e600398d5d5a"
def __init__(self):
self.messages: List[MailMessage] = []
@ -112,6 +114,10 @@ class BogusMailBox(ContextManager):
if username != self.USERNAME or password != self.UTF_PASSWORD:
raise MailboxLoginError("BAD", "OK")
def xoauth2(self, username: str, access_token: str):
if username != self.USERNAME or access_token != self.ACCESS_TOKEN:
raise MailboxLoginError("BAD", "OK")
def fetch(self, criteria, mark_seen, charset=""):
msg = self.messages
@ -737,6 +743,14 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertEqual(len(self.bogus_mailbox.messages), 3)
def test_error_login(self):
"""
GIVEN:
- Account configured with incorrect password
WHEN:
- Account tried to login
THEN:
- MailError with correct message raised
"""
account = MailAccount.objects.create(
name="test",
imap_server="",
@ -1007,6 +1021,8 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
GIVEN:
- Mail account with password containing non-ASCII characters
WHEN:
- Mail account is handled
THEN:
- Should still authenticate to the mail account
"""
@ -1040,6 +1056,8 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
GIVEN:
- Mail account with password containing non-ASCII characters
- Incorrect password value
WHEN:
- Mail account is handled
THEN:
- Should raise a MailError for the account
"""
@ -1064,6 +1082,41 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
account,
)
def test_auth_with_valid_token(self):
"""
GIVEN:
- Mail account configured with access token
WHEN:
- Mail account is handled
THEN:
- Should still authenticate to the mail account
"""
account = MailAccount.objects.create(
name="test",
imap_server="",
username=BogusMailBox.USERNAME,
# Note the non-ascii characters here
password=BogusMailBox.ACCESS_TOKEN,
is_token=True,
)
_ = MailRule.objects.create(
name="testrule",
account=account,
action=MailRule.MailAction.MARK_READ,
)
self.assertEqual(len(self.bogus_mailbox.messages), 3)
self.assertEqual(self._queue_consumption_tasks_mock.call_count, 0)
self.assertEqual(len(self.bogus_mailbox.fetch("UNSEEN", False)), 2)
self.mail_account_handler.handle_mail_account(account)
self.apply_mail_actions()
self.assertEqual(self._queue_consumption_tasks_mock.call_count, 2)
self.assertEqual(len(self.bogus_mailbox.fetch("UNSEEN", False)), 0)
self.assertEqual(len(self.bogus_mailbox.messages), 3)
def assert_queue_consumption_tasks_call_args(self, expected_call_args: List):
"""
Verifies that queue_consumption_tasks has been called with the expected arguments.