From 055ebb5606dddfc69cce7def1700993dd170136d Mon Sep 17 00:00:00 2001 From: Junxiang Huang Date: Fri, 13 Sep 2024 17:12:05 +0800 Subject: [PATCH] opt: email account support oauth --- dtable_events/dtable_io/__init__.py | 132 ++++++++++++++++++ dtable_events/dtable_io/request_handler.py | 34 +++-- .../dtable_io/task_message_manager.py | 9 +- 3 files changed, 163 insertions(+), 12 deletions(-) diff --git a/dtable_events/dtable_io/__init__.py b/dtable_events/dtable_io/__init__.py index 3ae6de13..aee3c34e 100644 --- a/dtable_events/dtable_io/__init__.py +++ b/dtable_events/dtable_io/__init__.py @@ -7,6 +7,7 @@ import requests import smtplib +import msal from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import formataddr, parseaddr @@ -720,6 +721,137 @@ def send_email_msg(auth_info, send_info, username, config=None, db_session=None) session.close() return result +def _get_oauth_smtp_headers(client_id, client_secret, authority, scopes): + app = msal.ConfidentialClientApplication( + client_id, + authority=authority, + client_credential=client_secret, + ) + result = app.acquire_token_for_client(scopes=scopes.replace(' ','').split(',')) + if "access_token" in result: + headers = { + 'Authorization': f'Bearer {result["access_token"]}', + 'Content-Type': 'application/json' + } + return headers + + else: + raise Exception('Failed to acquire token', result.get('error'), result.get('error_description')) + +def send_email_msg_by_oauth_auth(auth_info, send_info, username, config=None, db_session=None): + # auth info + host_user = auth_info.get('host_user') + client_id = auth_info.get('client_id', '') + client_secret = auth_info.get('client_secret', '') + authority = auth_info.get('authority', '') + scopes = auth_info.get('scopes', '') + endpoint = auth_info.get('endpoint', '') + sender_name = auth_info.get('sender_name', '') + sender_email = auth_info.get('sender_email', '') + + # send info + msg = send_info.get('message', '') + html_msg = send_info.get('html_message', '') + send_to = send_info.get('send_to', []) + subject = send_info.get('subject', '') + copy_to = send_info.get('copy_to', []) + reply_to = send_info.get('reply_to', '') + file_download_urls = send_info.get('file_download_urls', None) + message_id = send_info.get('message_id', '') + + result = {} + if not msg and not html_msg: + result['err_msg'] = 'Email message invalid' + return result + + try: + headers = _get_oauth_smtp_headers(client_id, client_secret, authority, scopes) + except Exception as e: + dtable_message_logger.warning(f'SMTP auth failure: {e}') + result['err_msg'] = 'SMTP auth failure' + return result + + email_data = { + 'message':{ + 'subject': subject, + }, + 'body': { + 'contentType': 'html' if html_msg else 'text', + 'content': html_msg if html_msg else msg + }, + 'from':{ + 'emailAddress': { + 'address': sender_email if sender_name else host_user + } + }, + 'toRecipients': [ + { + 'emailAddress':{ + 'address': to + } + } + for to in send_to + ], + 'ccRecipients': [ + { + 'emailAddress':{ + 'address': to + } + } + for to in copy_to + ], + 'replyTo':[ + { + 'emailAddress':{ + 'address': to + } + } + for to in reply_to + ] + } + + if sender_name: + email_data['from']['emailAddress']['name'] = sender_name + + if message_id: + email_data.update({'internetMessageId': message_id}) + + if file_download_urls: + email_data.update({'attachments':[ + { + '@odata.type': '#microsoft.graph.fileAttachment', + 'name': 'UTF-8\'\'' + parse.quote(file_name), + 'contentType': 'application/octet-stream', + 'contentBytes': requests.get(file_url).content + } + for file_name, file_url in file_download_urls.items() + ]}) + + try: + response = requests.post( + endpoint, + headers=headers, + data=json.dumps(email_data) + ) + if response.status_code != 202: + raise Exception(f'Error sending email: {response.status_code} - {response.text}') + success = True + except Exception as e: + dtable_message_logger.warning(e) + result['err_msg'] = e + else: + dtable_message_logger.info('Email sending success!') + + session = db_session or init_db_session_class(config)() + try: + save_email_sending_records(session, username, endpoint, success) + except Exception as e: + dtable_message_logger.error( + 'Email sending log record error: %s' % e) + finally: + session.close() + return result + def batch_send_email_msg(auth_info, send_info_list, username, config=None, db_session=None): """ diff --git a/dtable_events/dtable_io/request_handler.py b/dtable_events/dtable_io/request_handler.py index 53c6bbd3..11b53910 100644 --- a/dtable_events/dtable_io/request_handler.py +++ b/dtable_events/dtable_io/request_handler.py @@ -585,14 +585,30 @@ def add_email_sending_task(): if image_cid_url_map and not isinstance(image_cid_url_map, dict): image_cid_url_map = json.loads(image_cid_url_map) - auth_info = { - 'email_host': data.get('email_host'), - 'email_port': data.get('email_port'), - 'host_user': data.get('host_user'), - 'password': data.get('password'), - 'sender_name': data.get('sender_name'), - 'sender_email': data.get('sender_email') - } + auth_type = data.get('auth_type') + + if auth_type == 'LOGIN': + auth_info = { + 'email_host': data.get('email_host'), + 'email_port': data.get('email_port'), + 'host_user': data.get('host_user'), + 'password': data.get('password'), + 'sender_name': data.get('sender_name'), + 'sender_email': data.get('sender_email') + } + elif auth_type == 'OAUTH': + auth_info = { + 'host_user': data.get('host_user'), + 'client_id': data.get('client_id'), + 'client_secret': data.get('client_secret'), + 'authority': data.get('authority'), + 'scopes': data.get('scopes'), + 'endpoint': data.get('endpoint'), + 'sender_name': data.get('sender_name'), + 'sender_email': data.get('sender_email') + } + else: + return make_response(('Bad request', 400)) send_info = { 'message': data.get('message'), @@ -610,7 +626,7 @@ def add_email_sending_task(): try: task_id = message_task_manager.add_email_sending_task( - auth_info, send_info, username) + auth_type, auth_info, send_info, username) except Exception as e: logger.error(e) return make_response((e, 500)) diff --git a/dtable_events/dtable_io/task_message_manager.py b/dtable_events/dtable_io/task_message_manager.py index 1b06d02e..56dc3e76 100644 --- a/dtable_events/dtable_io/task_message_manager.py +++ b/dtable_events/dtable_io/task_message_manager.py @@ -30,10 +30,13 @@ def init(self, workers, file_server_port, io_task_timeout, config): def is_valid_task_id(self, task_id): return task_id in self.tasks_map.keys() - def add_email_sending_task(self, auth_info, send_info, username): - from dtable_events.dtable_io import send_email_msg + def add_email_sending_task(self, auth_type, auth_info, send_info, username): + from dtable_events.dtable_io import send_email_msg, send_email_msg_by_oauth_auth task_id = str(uuid.uuid4()) - task = (send_email_msg,(auth_info, send_info, username, self.config)) + if auth_type == 'LOGIN': + task = (send_email_msg,(auth_info, send_info, username, self.config)) + elif auth_type == 'OAUTH': + task = (send_email_msg_by_oauth_auth,(auth_info, send_info, username, self.config)) self.tasks_queue.put(task_id) self.tasks_map[task_id] = task return task_id