mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-19 10:19:27 -05:00
Allows users to use OAuth tokens instead of passwords
This commit is contained in:
parent
14b997fe2c
commit
09b1413748
@ -15,6 +15,7 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<app-input-text i18n-title title="Username" formControlName="username" [error]="error?.username"></app-input-text>
|
<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-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>
|
<app-input-text i18n-title title="Character Set" formControlName="character_set" [error]="error?.character_set"></app-input-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,6 +45,7 @@ export class MailAccountEditDialogComponent extends EditDialogComponent<Paperles
|
|||||||
imap_security: new FormControl(IMAPSecurity.SSL),
|
imap_security: new FormControl(IMAPSecurity.SSL),
|
||||||
username: new FormControl(null),
|
username: new FormControl(null),
|
||||||
password: new FormControl(null),
|
password: new FormControl(null),
|
||||||
|
is_token: new FormControl(false),
|
||||||
character_set: new FormControl('UTF-8'),
|
character_set: new FormControl('UTF-8'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -20,4 +20,6 @@ export interface PaperlessMailAccount extends ObjectWithId {
|
|||||||
password: string
|
password: string
|
||||||
|
|
||||||
character_set?: string
|
character_set?: string
|
||||||
|
|
||||||
|
is_token: boolean
|
||||||
}
|
}
|
||||||
|
@ -202,20 +202,21 @@ def mailbox_login(mailbox: MailBox, account: MailAccount):
|
|||||||
|
|
||||||
try:
|
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:
|
if use_ascii_login:
|
||||||
logger.debug("Falling back to AUTH=PLAIN")
|
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:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Error while authenticating account {account}: {e}",
|
f"Error while authenticating account {account}: {e}",
|
||||||
|
19
src/paperless_mail/migrations/0020_mailaccount_is_token.py
Normal file
19
src/paperless_mail/migrations/0020_mailaccount_is_token.py
Normal 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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -38,6 +38,8 @@ class MailAccount(document_models.ModelWithOwner):
|
|||||||
|
|
||||||
password = models.CharField(_("password"), max_length=256)
|
password = models.CharField(_("password"), max_length=256)
|
||||||
|
|
||||||
|
is_token = models.BooleanField(_("Is token authentication"), default=False)
|
||||||
|
|
||||||
character_set = models.CharField(
|
character_set = models.CharField(
|
||||||
_("character set"),
|
_("character set"),
|
||||||
max_length=256,
|
max_length=256,
|
||||||
|
@ -34,6 +34,7 @@ class MailAccountSerializer(OwnedObjectSerializer):
|
|||||||
"username",
|
"username",
|
||||||
"password",
|
"password",
|
||||||
"character_set",
|
"character_set",
|
||||||
|
"is_token",
|
||||||
]
|
]
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
|
@ -83,6 +83,8 @@ class BogusMailBox(ContextManager):
|
|||||||
ASCII_PASSWORD: str = "secret"
|
ASCII_PASSWORD: str = "secret"
|
||||||
# Note the non-ascii characters here
|
# Note the non-ascii characters here
|
||||||
UTF_PASSWORD: str = "w57äöüw4b6huwb6nhu"
|
UTF_PASSWORD: str = "w57äöüw4b6huwb6nhu"
|
||||||
|
# A dummy access token
|
||||||
|
ACCESS_TOKEN = "ea7e075cd3acf2c54c48e600398d5d5a"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.messages: List[MailMessage] = []
|
self.messages: List[MailMessage] = []
|
||||||
@ -112,6 +114,10 @@ class BogusMailBox(ContextManager):
|
|||||||
if username != self.USERNAME or password != self.UTF_PASSWORD:
|
if username != self.USERNAME or password != self.UTF_PASSWORD:
|
||||||
raise MailboxLoginError("BAD", "OK")
|
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=""):
|
def fetch(self, criteria, mark_seen, charset=""):
|
||||||
msg = self.messages
|
msg = self.messages
|
||||||
|
|
||||||
@ -737,6 +743,14 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
||||||
|
|
||||||
def test_error_login(self):
|
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(
|
account = MailAccount.objects.create(
|
||||||
name="test",
|
name="test",
|
||||||
imap_server="",
|
imap_server="",
|
||||||
@ -1007,6 +1021,8 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- Mail account with password containing non-ASCII characters
|
- Mail account with password containing non-ASCII characters
|
||||||
|
WHEN:
|
||||||
|
- Mail account is handled
|
||||||
THEN:
|
THEN:
|
||||||
- Should still authenticate to the mail account
|
- Should still authenticate to the mail account
|
||||||
"""
|
"""
|
||||||
@ -1040,6 +1056,8 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
GIVEN:
|
GIVEN:
|
||||||
- Mail account with password containing non-ASCII characters
|
- Mail account with password containing non-ASCII characters
|
||||||
- Incorrect password value
|
- Incorrect password value
|
||||||
|
WHEN:
|
||||||
|
- Mail account is handled
|
||||||
THEN:
|
THEN:
|
||||||
- Should raise a MailError for the account
|
- Should raise a MailError for the account
|
||||||
"""
|
"""
|
||||||
@ -1064,6 +1082,41 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
account,
|
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):
|
def assert_queue_consumption_tasks_call_args(self, expected_call_args: List):
|
||||||
"""
|
"""
|
||||||
Verifies that queue_consumption_tasks has been called with the expected arguments.
|
Verifies that queue_consumption_tasks has been called with the expected arguments.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user