What I am looking to accomplish:
- I want to be able to connect my app to the outlook 365 email account
- Parse data in the email, such as headers/body of email
- Send that data and store it in my db
Problem: I cannot seem to get connected to my outlook account using 2 factor. I have all of the proper azure credentials. I have php-ews installed and am using that library. I had my code working with a default mail client but that was without 2 factor with using only username/password. (not as secure)
Here is the current code, Its all in one file i am using for testing, i edited the NTLMSoapClient class to try to add the necessary details. Any help would greatly be appreciated.
<?php
require 'vendor/autoload.php';
require 'class.connect.php';
require 'monitor.php';
use garethpewsAPI;
use garethpewsAPINTLMSoapClient;
class OAuthSoapClient extends NTLMSoapClient
{
private $accessToken;
public function __construct($location, $auth, $wsdl, array $options = [], $accessToken)
{
$this->accessToken = $accessToken;
parent::__construct($location, $auth, $wsdl, $options);
}
protected function setupHeaders($location, $action)
{
$headers = parent::setupHeaders($location, $action);
$headers[] = new SoapHeader('http://schemas.xmlsoap.org/ws/2002/12/secext',
'Authorization', 'Bearer ' . $this->accessToken);
return $headers;
}
}
Here is the function that handles the email credentials:
public function processEmailData() {
$server = 'https://outlook.office365.com/EWS/Exchange.asmx';
$tenant = 'Tenant';
$clientId = 'clientID';
$clientSecret = 'secret';
$tokenUrl = "https://login.microsoftonline.com/{$tenant}/oauth2/v2.0/token";
$client = new GuzzleHttpClient();
try {
$response = $client->post($tokenUrl, [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => $clientSecret,
'scope' => 'https://outlook.office365.com/.default'
]
]);
$tokenData = json_decode($response->getBody()->getContents(), true);
$accessToken = $tokenData['access_token'];
$this->logger->log('INFO', 'Access token retrieved successfully');
} catch (Exception $ex) {
$this->logger->log('ERROR', "Failed to retrieve access token: " . $ex->getMessage());
die('Cannot retrieve access token: ' . $ex->getMessage());
}
$authOptions = [];
$wsdl = null;
$options = [
'version' => SOAP_1_1,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
'trace' => 1,
'exceptions' => 1,
];
// Initialize OAuthSoapClient with appropriate parameters
$client = new OAuthSoapClient($server, $authOptions, $wsdl, $options, $accessToken);
$api = new API($client);
try {
$this->logger->log('INFO', 'Connected to Microsoft Exchange server successfully');
// Define the folder to search (e.g., inbox)
$folder = new garethpewsAPITypeDistinguishedFolderIdType();
$folder->setId(garethpewsAPIEnumerationDistinguishedFolderIdNameType::INBOX);
// Wrap the folder ID in a NonEmptyArrayOfBaseFolderIdsType
$folderArray = new garethpewsAPITypeNonEmptyArrayOfBaseFolderIdsType();
$folderArray->setDistinguishedFolderId([$folder]);
// Define the item response shape
$itemShape = new garethpewsAPITypeItemResponseShapeType();
$itemShape->setBaseShape('AllProperties');
// Create the find item request
$request = new garethpewsAPIMessageFindItemType();
$request->setParentFolderIds($folderArray);
$request->setItemShape($itemShape);
$request->setTraversal(garethpewsAPIEnumerationItemQueryTraversalType::SHALLOW);
// Fetch unread emails
$this->logger->log('INFO', 'Attempting to fetch unread emails');
$response = $api->getClient()->findItem($request);
$this->logger->log('INFO', 'Fetch unread emails request sent successfully');
// Check if response contains items
$responseMessages = $response->getResponseMessages();
if (!empty($responseMessages)) {
$findItemResponse = $responseMessages[0]->getFindItemResponseMessage();
$items = $findItemResponse->getRootFolder()->getItems()->getMessage();
$this->logger->log('INFO', 'Fetched unread emails');
} else {
$this->logger->log('INFO', 'No unread emails found');
return;
}
// Processing emails in batches of 10
$batchSize = 10;
$batches = array_chunk($items, $batchSize);
foreach ($batches as $batch) {
foreach ($batch as $item) {
$mailId = $item->getItemId()->getId();
$this->logger->log('DEBUG', "Fetched email with ID: $mailId");
if ($this->isEmailProcessed($mailId)) {
$this->logger->log('INFO', "Email already processed for UID: $mailId. Skipping.");
continue;
}
$sender = $item->getFrom()->getMailbox()->getEmailAddress();
$this->logger->log('DEBUG', "Sender email extracted: $sender");
// Verify the sender email address
if (!$this->isValidSender($sender)) {
$this->logger->log('WARN', "Invalid sender email: $sender");
continue;
}
// Mark the email as read
$api->getClient()->updateItem($item, ['IsRead' => true]);
// debugging
//$this->logger->log('INFO', "Processing email from: $sender");
// Extract the actual sender email address
$replyTo = $this->getReplyTo($item);
// Mark this email as processed
$this->markEmailAsProcessed($mailId);
$this->logger->log('INFO', "Marked email as processed for UID: $mailId");
// Add a separator line to visually show where the next transaction starts
$this->logger->log('INFO', str_repeat('-', 50));
}
// Sleep for a short duration to avoid overloading the server
usleep(500000); // 0.5 second
}
} catch (Exception $ex) {
$this->logger->log('ERROR', "Microsoft Exchange connection failed: " . $ex->getMessage());
die('Cannot connect to Microsoft Exchange: ' . $ex->getMessage());
}
}