diff --git a/src/middlewared/middlewared/plugins/mail.py b/src/middlewared/middlewared/plugins/mail.py index e09be97ad0d0..8ec96feb7c65 100644 --- a/src/middlewared/middlewared/plugins/mail.py +++ b/src/middlewared/middlewared/plugins/mail.py @@ -28,13 +28,15 @@ class DenyNetworkActivity(Exception): pass -class QueueItem: +class QueueItem(object): + def __init__(self, message): self.attempts = 0 self.message = message -class MailQueue: +class MailQueue(object): + MAX_ATTEMPTS = 3 MAX_QUEUE_LIMIT = 20 @@ -71,7 +73,10 @@ class MailModel(sa.Model): class MailService(ConfigService): + mail_queue = MailQueue() + oauth_access_token = None + oauth_access_token_expires_at = None class Config: datastore = 'system.email' @@ -91,7 +96,6 @@ class Config: Password('pass', null=True, required=True), Dict( 'oauth', - Str('provider'), Str('client_id'), Str('client_secret'), Password('refresh_token'), @@ -115,7 +119,6 @@ async def mail_extend(self, cfg): ( 'replace', Dict( 'oauth', - Str('provider'), Str('client_id', required=True), Str('client_secret', required=True), Password('refresh_token', required=True), @@ -367,7 +370,7 @@ def read_json(): msg[key] = val try: - if config['oauth'] and config['oauth']['provider'] == 'gmail': + if config['oauth']: self.middleware.call_sync('mail.gmail_send', msg, config) else: server = self._get_smtp_server(config, message['timeout'], local_hostname=local_hostname) @@ -426,9 +429,7 @@ def _get_smtp_server(self, config, timeout=300, local_hostname=None): local_hostname=local_hostname) if config['security'] == 'TLS': server.starttls() - if config['oauth'] and config['oauth']['provider'] == 'outlook': - self.middleware.call_sync('mail.outlook_xoauth2', server, config) - elif config['smtp']: + if config['smtp']: server.login(config['user'], config['pass']) return server @@ -440,7 +441,7 @@ def send_mail_queue(self): for queue in list(mq.queue): try: config = self.middleware.call_sync('mail.config') - if config['oauth'] and config['oauth']['provider'] == 'gmail': + if config['oauth']: self.middleware.call_sync('mail.gmail_send', queue.message, config) else: server = self._get_smtp_server(config) diff --git a/src/middlewared/middlewared/plugins/mail_/gmail.py b/src/middlewared/middlewared/plugins/mail_/gmail.py index 549fccf26000..e211742fe454 100644 --- a/src/middlewared/middlewared/plugins/mail_/gmail.py +++ b/src/middlewared/middlewared/plugins/mail_/gmail.py @@ -59,7 +59,7 @@ def gmail_initialize(self): @private def gmail_build_service(self, config): - if config["oauth"] and config["oauth"]["provider"] == "gmail": + if config["oauth"]: return GmailService(config) return None diff --git a/src/middlewared/middlewared/plugins/mail_/outlook.py b/src/middlewared/middlewared/plugins/mail_/outlook.py deleted file mode 100644 index 40c97e951c9a..000000000000 --- a/src/middlewared/middlewared/plugins/mail_/outlook.py +++ /dev/null @@ -1,67 +0,0 @@ -import base64 -from dataclasses import dataclass -from smtplib import SMTP -import time - -import requests - -from middlewared.service import CallError, private, Service - - -@dataclass -class OutlookToken: - token: str - expires_at: float - - -class MailService(Service): - outlook_tokens: dict[str, OutlookToken] = {} - - @private - def outlook_xoauth2(self, server: SMTP, config: dict): - server.ehlo() - - if token := self._get_outlook_token(config["fromemail"], config["oauth"]["refresh_token"]): - code, response = self._do_xoauth2(server, config["fromemail"], token) - if 200 <= code <= 299: - return - - self.logger.warning("Outlook XOAUTH2 failed: %r %r. Refreshing access token", code, response) - - self.logger.debug("Requesting Outlook access token") - r = requests.post( - "https://login.microsoftonline.com/common/oauth2/v2.0/token", - data={ - "grant_type": "refresh_token", - "client_id": config["oauth"]["client_id"], - "client_secret": config["oauth"]["client_secret"], - "refresh_token": config["oauth"]["refresh_token"], - "scope": "https://outlook.office.com/SMTP.Send openid offline_access", - } - ) - r.raise_for_status() - response = r.json() - - token = response["access_token"] - self._set_outlook_token(config["fromemail"], config["oauth"]["refresh_token"], token, response["expires_in"]) - - code, response = self._do_xoauth2(server, config["fromemail"], token) - if 200 <= code <= 299: - return - - raise CallError("Outlook XOAUTH2 failed: %r %r" % (code, response)) - - def _get_outlook_token(self, email: str, refresh_token: str) -> str | None: - for key, token in list(self.outlook_tokens.items()): - if token.expires_at < time.monotonic() - 5: - self.outlook_tokens.pop(key) - - if token := self.outlook_tokens.get(email + refresh_token): - return token.token - - def _set_outlook_token(self, email: str, refresh_token: str, token: str, expires_in: int): - self.outlook_tokens[email + refresh_token] = OutlookToken(token, time.monotonic() + expires_in) - - def _do_xoauth2(self, server: SMTP, email: str, access_token: str): - auth_string = f"user={email}\1auth=Bearer {access_token}\1\1" - return server.docmd("AUTH XOAUTH2", base64.b64encode(auth_string.encode()).decode())