Add IMAP OAUTH Mail Box Type

This commit is contained in:
bruce.gibbins 2023-04-15 21:52:44 +10:00
parent 26628db7e0
commit d57f11f40a
5 changed files with 146 additions and 2 deletions

View File

@ -21,6 +21,11 @@ import email
from email.message import Message from email.message import Message
from email.utils import getaddresses from email.utils import getaddresses
from email_reply_parser import EmailReplyParser from email_reply_parser import EmailReplyParser
# Add OAUTH Libraries
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
from helpdesk import settings from helpdesk import settings
from helpdesk.exceptions import DeleteIgnoredTicketException, IgnoreTicketException from helpdesk.exceptions import DeleteIgnoredTicketException, IgnoreTicketException
from helpdesk.lib import process_attachments, safe_template_context from helpdesk.lib import process_attachments, safe_template_context
@ -225,6 +230,101 @@ def imap_sync(q, logger, server):
server.logout() server.logout()
def imap_oauth_sync(q, logger, server):
"""
IMAP eMail server with OAUTH authentication.
Only tested against O365 implementation
Uses OAUTH Dict in Settings.
"""
try:
logger.debug("Start Mailbox polling via IMAP OAUTH")
client = BackendApplicationClient(
client_id=django_settings.HELPDESK_OAUTH["client_id"],
scope=django_settings.HELPDESK_OAUTH["scope"],
)
oauth = OAuth2Session(client=client)
token = oauth.fetch_token(
token_url=django_settings.HELPDESK_OAUTH["token_url"],
client_id=django_settings.HELPDESK_OAUTH["client_id"],
client_secret=django_settings.HELPDESK_OAUTH["secret"],
include_client_id=True,
)
# TODO: Somehow link this to the debug level set within Django settings logging
server.debug = 4
# TODO: Perhaps store the authentication string template externally? Settings? Queue Table?
server.authenticate(
"XOAUTH2",
lambda x: f"user={q.email_box_user}\x01auth=Bearer {token['access_token']}\x01\x01".encode(),
)
# Select the Inbound Mailbox folder
server.select(q.email_box_imap_folder)
except imaplib.IMAP4.abort:
logger.error("IMAP authentication failed.")
server.logout()
sys.exit()
except ssl.SSLError:
logger.error(
"IMAP login failed due to SSL error. This is often due to a timeout. "
"Please check your connection and try again."
)
server.logout()
sys.exit()
try:
data = server.search(None, 'NOT', 'DELETED')[1]
if data:
msgnums = data[0].split()
logger.info(f"Found {len(msgnums)} message(s) on IMAP server" )
for num in msgnums:
logger.info(f"Processing message {num}")
data = server.fetch(num, '(RFC822)')[1]
full_message = encoding.force_str(data[0][1], errors='replace')
try:
ticket = object_from_message(message=full_message, queue=q, logger=logger)
except IgnoreTicketException as itex:
logger.warn(f"Message {num} was ignored. {itex}")
except DeleteIgnoredTicketException:
server.store(num, '+FLAGS', '\\Deleted')
logger.warn("Message %s was ignored and deleted from IMAP server" % num)
except TypeError as te:
# Log the error with stacktrace to help identify what went wrong
logger.error(f"Unexpected error processing message: {te}", exc_info=True)
else:
if ticket:
server.store(num, '+FLAGS', '\\Deleted')
logger.info(
"Successfully processed message %s, deleted from IMAP server" % num)
else:
logger.warn(
"Message %s was not successfully processed, and will be left on IMAP server" % num)
except imaplib.IMAP4.error:
logger.error(
"IMAP retrieve failed. Is the folder '%s' spelled correctly, and does it exist on the server?",
q.email_box_imap_folder
)
# Purged Flagged Messages & Logout
server.expunge()
server.close()
server.logout()
def process_queue(q, logger): def process_queue(q, logger):
logger.info("***** %s: Begin processing mail for django-helpdesk" % ctime()) logger.info("***** %s: Begin processing mail for django-helpdesk" % ctime())
@ -272,7 +372,18 @@ def process_queue(q, logger):
'init': imaplib.IMAP4, 'init': imaplib.IMAP4,
}, },
'sync': imap_sync 'sync': imap_sync
} },
'oauth': {
'ssl': {
'port': 993,
'init': imaplib.IMAP4_SSL,
},
'insecure': {
'port': 143,
'init': imaplib.IMAP4,
},
'sync': imap_oauth_sync
},
} }
if email_box_type in mail_defaults: if email_box_type in mail_defaults:
encryption = 'insecure' encryption = 'insecure'

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2023-03-25 15:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('helpdesk', '0036_add_attachment_validator'),
]
operations = [
migrations.AlterField(
model_name='queue',
name='email_box_type',
field=models.CharField(blank=True, choices=[('pop3', 'POP 3'), ('imap', 'IMAP'), ('oauth', 'IMAP OAUTH'), ('local', 'Local Directory')], help_text='E-Mail server type for creating tickets automatically from a mailbox - both POP3 and IMAP are supported, as well as reading from a local directory.', max_length=5, null=True, verbose_name='E-Mail Box Type'),
),
]

View File

@ -175,7 +175,9 @@ class Queue(models.Model):
email_box_type = models.CharField( email_box_type = models.CharField(
_('E-Mail Box Type'), _('E-Mail Box Type'),
max_length=5, max_length=5,
choices=(('pop3', _('POP 3')), ('imap', _('IMAP')), choices=(('pop3', _('POP 3')),
('imap', _('IMAP')),
('oauth', _('IMAP OAUTH')),
('local', _('Local Directory'))), ('local', _('Local Directory'))),
blank=True, blank=True,
null=True, null=True,

View File

@ -249,3 +249,14 @@ HELPDESK_FULL_FIRST_MESSAGE_FROM_EMAIL = getattr(
# (which gets stripped/corrupted otherwise) # (which gets stripped/corrupted otherwise)
HELPDESK_ALWAYS_SAVE_INCOMING_EMAIL_MESSAGE = getattr( HELPDESK_ALWAYS_SAVE_INCOMING_EMAIL_MESSAGE = getattr(
settings, "HELPDESK_ALWAYS_SAVE_INCOMING_EMAIL_MESSAGE", False) settings, "HELPDESK_ALWAYS_SAVE_INCOMING_EMAIL_MESSAGE", False)
#######################
# email OAUTH #
#######################
HELPDESK_OAUTH = {
"token_url": "",
"client_id": "",
"secret": "",
"scope": [""]
}

View File

@ -12,3 +12,5 @@ pinax_teams
djangorestframework djangorestframework
django-model-utils django-model-utils
django-cleanup django-cleanup
requests
requests_oauthlib