1
0
Fork 0

Office365 API mailbox support (#251)

* Update models.py, __init__.py, and office365.py

* Update setup.py

* Office365Transport properties

* Update .gitignore

* wrong folder stage

* Update models.py

* Update office365.py and models.py

* office365 implementation

* Update office365.py

* clear code

* fix import

* Update readme.rst

* archive and delete

* fix and documentation

* removed build folder

* missing archive

Co-authored-by: Pietro Mingo <p.mingo@intac.it>
Co-authored-by: Serafim Bordei <s.bordei@intac.it>
This commit is contained in:
Pietro Mingo 2023-01-16 22:23:38 +01:00 committed by GitHub
parent 40263b6670
commit cefbcdebd2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 129 additions and 3 deletions

1
.gitignore vendored
View file

@ -13,3 +13,4 @@ dummy_project/*
.tox/
messages
*.sqlite3
.idea/

View file

@ -31,7 +31,7 @@ from django_mailbox import utils
from django_mailbox.signals import message_received
from django_mailbox.transports import Pop3Transport, ImapTransport, \
MaildirTransport, MboxTransport, BabylTransport, MHTransport, \
MMDFTransport, GmailImapTransport
MMDFTransport, GmailImapTransport, Office365Transport
logger = logging.getLogger(__name__)
@ -189,6 +189,30 @@ class Mailbox(models.Model):
return None
return folder[0]
@property
def client_id(self):
"""Returns (if specified) the client id for Office365."""
client_id = self._query_string.get('client_id', None)
if not client_id:
return None
return client_id[0]
@property
def client_secret(self):
"""Returns (if specified) the client secret for Office365."""
client_secret = self._query_string.get('client_secret', None)
if not client_secret:
return None
return client_secret[0]
@property
def tenant_id(self):
"""Returns (if specified) the tenant id for Office365."""
tenant_id = self._query_string.get('tenant_id', None)
if not tenant_id:
return None
return tenant_id[0]
def get_connection(self):
"""Returns the transport instance for this mailbox.
@ -223,6 +247,14 @@ class Mailbox(models.Model):
ssl=self.use_ssl
)
conn.connect(self.username, self.password)
elif self.type == 'office365':
conn = Office365Transport(
self.location,
self.username,
folder=self.folder,
archive=self.archive
)
conn.connect(self.client_id, self.client_secret, self.tenant_id)
elif self.type == 'maildir':
conn = MaildirTransport(self.location)
elif self.type == 'mbox':

View file

@ -8,3 +8,4 @@ from django_mailbox.transports.babyl import BabylTransport
from django_mailbox.transports.mh import MHTransport
from django_mailbox.transports.mmdf import MMDFTransport
from django_mailbox.transports.gmail import GmailImapTransport
from django_mailbox.transports.office365 import Office365Transport

View file

@ -9,3 +9,4 @@ class EmailTransport:
message = email.message_from_bytes(contents)
return message

View file

@ -0,0 +1,66 @@
import logging
from django.conf import settings
from .base import EmailTransport, MessageParseError
logger = logging.getLogger(__name__)
class Office365Transport(EmailTransport):
def __init__(
self, hostname, username, archive='', folder=None
):
self.integration_testing_subject = getattr(
settings,
'DJANGO_MAILBOX_INTEGRATION_TESTING_SUBJECT',
None
)
self.hostname = hostname
self.username = username
self.archive = archive
self.folder = folder
def connect(self, client_id, client_secret, tenant_id):
try:
import O365
except ImportError:
raise ValueError(
"Install o365 to use oauth2 auth for office365"
)
credentials = (client_id, client_secret)
self.account = O365.Account(credentials, auth_flow_type='credentials', tenant_id=tenant_id)
self.account.authenticate()
self.mailbox = self.account.mailbox(resource=self.username)
self.mailbox_folder = self.mailbox.inbox_folder()
if self.folder:
self.mailbox_folder = self.mailbox.get_folder(folder_name=self.folder)
def get_message(self, condition=None):
archive_folder = None
if self.archive:
archive_folder = self.mailbox.get_folder(folder_name=self.archive)
if not archive_folder:
archive_folder = self.mailbox.create_child_folder(self.archive)
for o365message in self.mailbox_folder.get_messages(order_by='receivedDateTime'):
try:
mime_content = o365message.get_mime_content()
message = self.get_email_from_bytes(mime_content)
if condition and not condition(message):
continue
yield message
except MessageParseError:
continue
if self.archive and archive_folder:
o365message.copy(archive_folder)
o365message.delete()
return

View file

@ -13,6 +13,7 @@ POP3 and IMAP as well as local file-based mailboxes.
POP3 ``pop3://`` Can also specify SSL with ``pop3+ssl://``
IMAP ``imap://`` Can also specify SSL with ``imap+ssl://`` or STARTTLS with ``imap+tls``; additional configuration is also possible: see :ref:`pop3-and-imap-mailboxes` for details.
Gmail IMAP ``gmail+ssl://`` Uses OAuth authentication for Gmail's IMAP transport. See :ref:`gmail-oauth` for details.
Office365 API``office365://`` Uses OAuth authentication for Office365 API transport. See :ref:`office365-oauth` for details.
Maildir ``maildir://``
Mbox ``mbox://``
Babyl ``babyl://``
@ -115,6 +116,25 @@ Build your URI accordingly::
gmail+ssl://youremailaddress%40gmail.com:oauth2@imap.gmail.com?archive=Archived
.. _office365-oauth:
Office 365 API
-------------------------------------
Office 365 allows through the API to read a mailbox with Oauth.
The O365_ library is used.
.. _O365: https://github.com/O365/python-o365
.. _configuration: https://github.com/O365/python-o365#authentication
For the configuration_ you need to register an application and get a client_id, client_secret and tenant_id.
This implementation uses the client credentials grant flow and the password you specify will be ignored.
Build your URI accordingly::
office365://youremailaddress%40yourdomain.com:oauth2@outlook.office365.com?client_id=client_id&client_secret=client_secret&tenant_id=tenant_id&archive=Archived
Local File-based Mailboxes
--------------------------

View file

@ -8,7 +8,7 @@
:target: https://pypi.python.org/pypi/django-mailbox
Easily ingest messages from POP3, IMAP, or local mailboxes into your Django application.
Easily ingest messages from POP3, IMAP, Office365 API or local mailboxes into your Django application.
This app allows you to either ingest e-mail content from common e-mail services (as long as the service provides POP3 or IMAP support),
or directly receive e-mail messages from ``stdin`` (for locally processing messages from Postfix or Exim4).

View file

@ -13,6 +13,10 @@ gmail_oauth2_require = [
'python-social-auth',
]
office365_oauth2_require = [
'O365',
]
setup(
name='django-mailbox',
version=version_string,
@ -25,7 +29,8 @@ setup(
author='Adam Coddington',
author_email='me@adamcoddington.net',
extras_require={
'gmail-oauth2': gmail_oauth2_require
'gmail-oauth2': gmail_oauth2_require,
'office365-oauth2': office365_oauth2_require
},
python_requires=">=3",
classifiers=[