+import os
+from datetime import datetime
+from datetime import timezone
+from typing import Any, Optional
+
+import msal # type: ignore
+import requests
+from requests.exceptions import RequestException
+
+from onyx.configs.app_configs import INDEX_BATCH_SIZE
+from onyx.configs.constants import DocumentSource
+from onyx.connectors.cross_connector_utils.miscellaneous_utils import time_str_to_utc
+from onyx.connectors.exceptions import ConnectorValidationError
+from onyx.connectors.exceptions import CredentialExpiredError
+from onyx.connectors.exceptions import InsufficientPermissionsError
+from onyx.connectors.exceptions import UnexpectedValidationError
+from onyx.connectors.interfaces import GenerateDocumentsOutput
+from onyx.connectors.interfaces import LoadConnector
+from onyx.connectors.interfaces import PollConnector
+from onyx.connectors.interfaces import SecondsSinceUnixEpoch
+from onyx.connectors.models import BasicExpertInfo
+from onyx.connectors.models import ConnectorMissingCredentialError
+from onyx.connectors.models import Document
+from onyx.connectors.models import TextSection
+from onyx.utils.logger import setup_logger
+
+logger = setup_logger()
+
+
+class OutlookConnector(LoadConnector, PollConnector):
+ def __init__(
+ self,
+ batch_size: int = INDEX_BATCH_SIZE,
+ indexing_scope: str = "everything",
+ folders: list[str] = [],
+ email_addresses: list[str] = [],
+ include_attachments: bool = True,
+ start_date: str | None = None,
+ include_metadata: bool = False,
+ max_emails: int | None = None,
+ ) -> None:
+ self.batch_size = batch_size
+ self.access_token: Optional[str] = None
+ self.indexing_scope = indexing_scope
+ self.folders = folders
+ self.email_addresses = email_addresses
+ self.include_attachments = include_attachments
+ self.start_date = start_date
+ self.include_metadata = include_metadata
+ self.max_emails = max_emails
+ self.msal_app: Optional[msal.ConfidentialClientApplication] = None
+ self.base_url = "https://graph.microsoft.com/v1.0"
+
+ def load_credentials(self, credentials: dict[str, Any]) -> dict[str, Any] | None:
+ outlook_client_id = credentials["outlook_client_id"]
+ outlook_client_secret = credentials["outlook_client_secret"]
+ outlook_directory_id = credentials["outlook_directory_id"]
+
+ authority_url = f"https://login.microsoftonline.com/{outlook_directory_id}"
+ self.msal_app = msal.ConfidentialClientApplication(
+ authority=authority_url,
+ client_id=outlook_client_id,
+ client_credential=outlook_client_secret,
+ )
+ return None
+
+ def _get_access_token(self) -> str:
+ if self.msal_app is None:
+ raise ConnectorMissingCredentialError("Outlook credentials not loaded.")
+
+ token = self.msal_app.acquire_token_for_client(
+ scopes=["https://graph.microsoft.com/.default"]
+ )
+ if "access_token" not in token:
+ raise CredentialExpiredError("Failed to acquire access token")
+ return token["access_token"]
+
+ def _make_request(self, endpoint: str, params: Optional[dict] = None) -> dict:
+ if not self.access_token:
+ self.access_token = self._get_access_token()
+
+ headers = {
+ "Authorization": f"Bearer {self.access_token}",
+ "Content-Type": "application/json",
+ }
+
+ try:
+ response = requests.get(
+ f"{self.base_url}/{endpoint}",
+ headers=headers,
+ params=params,
+ timeout=30
+ )
+ response.raise_for_status()
+ return response.json()
+ except RequestException as e:
+ if e.response is not None:
+ status_code = e.response.status_code
+ if status_code == 401:
+ # Token might be expired, try to get a new one
+ self.access_token = None
+ return self._make_request(endpoint, params)