diff --git a/app/Helpers/Mail/GmailTransportManager.php b/app/Helpers/Mail/GmailTransportManager.php index 3db322048b13..b6f574427f50 100644 --- a/app/Helpers/Mail/GmailTransportManager.php +++ b/app/Helpers/Mail/GmailTransportManager.php @@ -10,9 +10,10 @@ */ namespace App\Helpers\Mail; -use Illuminate\Mail\MailManager; use App\CustomMailDriver\CustomTransport; +use App\Helpers\Mail\Office365MailTransport; use Dacastro4\LaravelGmail\Services\Message\Mail; +use Illuminate\Mail\MailManager; use Illuminate\Support\Facades\Config; @@ -22,4 +23,9 @@ class GmailTransportManager extends MailManager { return new GmailTransport(new Mail); } + + protected function createOffice365Transport() + { + return new Office365MailTransport(); + } } \ No newline at end of file diff --git a/app/Helpers/Mail/Office365MailTransport.php b/app/Helpers/Mail/Office365MailTransport.php new file mode 100644 index 000000000000..22c7c4bef4bc --- /dev/null +++ b/app/Helpers/Mail/Office365MailTransport.php @@ -0,0 +1,307 @@ +beforeSendPerformed($message); + + $graph = new Graph(); + + $graph->setAccessToken($this->getAccessToken()); + + // Special treatment if the message has too large attachments + $messageBody = $this->getBody($message, true); + $messageBodySizeMb = json_encode($messageBody); + $messageBodySizeMb = strlen($messageBodySizeMb); + $messageBodySizeMb = $messageBodySizeMb / 1048576; //byte -> mb + + if ($messageBodySizeMb >= 4) { + unset($messageBody); + $graphMessage = $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/messages") + ->attachBody($this->getBody($message)) + ->setReturnType(\Microsoft\Graph\Model\Message::class) + ->execute(); + + foreach ($message->getChildren() as $attachment) { + if ($attachment instanceof \Swift_Mime_SimpleMimeEntity) { + $fileName = $attachment->getHeaders()->get('Content-Type')->getParameter('name'); + $content = $attachment->getBody(); + $fileSize = strlen($content); + $size = $fileSize / 1048576; //byte -> mb + $id = $attachment->getId(); + $attachmentMessage = [ + 'AttachmentItem' => [ + 'attachmentType' => 'file', + 'name' => $fileName, + 'size' => strlen($content) + ] + ]; + + if ($size <= 3) { //ErrorAttachmentSizeShouldNotBeLessThanMinimumSize if attachment <= 3mb, then we need to add this + $attachmentBody = [ + "@odata.type" => "#microsoft.graph.fileAttachment", + "name" => $attachment->getHeaders()->get('Content-Type')->getParameter('name'), + "contentType" => $attachment->getBodyContentType(), + "contentBytes" => base64_encode($attachment->getBody()), + 'contentId' => $id + ]; + + $addAttachment = $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/messages/" . $graphMessage->getId() . "/attachments") + ->attachBody($attachmentBody) + ->setReturnType(UploadSession::class) + ->execute(); + } else { + //upload the files in chunks of 4mb.... + $uploadSession = $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/messages/" . $graphMessage->getId() . "/attachments/createUploadSession") + ->attachBody($attachmentMessage) + ->setReturnType(UploadSession::class) + ->execute(); + + $fragSize = 1024 * 1024 * 4; //4mb at once... + $numFragments = ceil($fileSize / $fragSize); + $contentChunked = str_split($content, $fragSize); + $bytesRemaining = $fileSize; + + $i = 0; + while ($i < $numFragments) { + $chunkSize = $numBytes = $fragSize; + $start = $i * $fragSize; + $end = $i * $fragSize + $chunkSize - 1; + if ($bytesRemaining < $chunkSize) { + $chunkSize = $numBytes = $bytesRemaining; + $end = $fileSize - 1; + } + $data = $contentChunked[$i]; + $content_range = "bytes " . $start . "-" . $end . "/" . $fileSize; + $headers = [ + "Content-Length" => $numBytes, + "Content-Range" => $content_range + ]; + $client = new \GuzzleHttp\Client(); + $tmp = $client->put($uploadSession->getUploadUrl(), [ + 'headers' => $headers, + 'body' => $data, + 'allow_redirects' => false, + 'timeout' => 1000 + ]); + $result = $tmp->getBody() . ''; + $result = json_decode($result); //if body == empty, then the file was successfully uploaded + $bytesRemaining = $bytesRemaining - $chunkSize; + $i++; + } + } + } + } + + //definetly send the message + $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/messages/" . $graphMessage->getId() . "/send")->execute(); + } else { + $graphMessage = $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/sendmail") + ->attachBody($messageBody) + ->setReturnType(\Microsoft\Graph\Model\Message::class) + ->execute(); + } + + $this->sendPerformed($message); + + return $this->numberOfRecipients($message); + } + + /** + * Get body for the message. + * + * @param \Swift_Mime_SimpleMessage $message + * @param bool $withAttachments + * @return array + */ + + protected function getBody(Swift_Mime_SimpleMessage $message, $withAttachments = false) + { + $messageData = [ + 'from' => [ + 'emailAddress' => [ + 'address' => key($message->getFrom()), + 'name' => current($message->getFrom()) + ] + ], + 'toRecipients' => $this->getTo($message), + 'ccRecipients' => $this->getCc($message), + 'bccRecipients' => $this->getBcc($message), + 'replyTo' => $this->getReplyTo($message), + 'subject' => $message->getSubject(), + 'body' => [ + 'contentType' => $message->getBodyContentType() == "text/html" ? 'html' : 'text', + 'content' => $message->getBody() + ] + ]; + + if ($withAttachments) { + $messageData = ['message' => $messageData]; + //add attachments if any + $attachments = []; + foreach ($message->getChildren() as $attachment) { + if ($attachment instanceof \Swift_Mime_SimpleMimeEntity) { + $attachments[] = [ + "@odata.type" => "#microsoft.graph.fileAttachment", + "name" => $attachment->getHeaders()->get('Content-Type')->getParameter('name'), + "contentType" => $attachment->getBodyContentType(), + "contentBytes" => base64_encode($attachment->getBody()), + 'contentId' => $attachment->getId() + ]; + } + } + if (count($attachments) > 0) { + $messageData['message']['attachments'] = $attachments; + } + } + + return $messageData; + } + + /** + * Get the "to" payload field for the API request. + * + * @param \Swift_Mime_SimpleMessage $message + * @return string + */ + protected function getTo(Swift_Mime_SimpleMessage $message) + { + return collect((array) $message->getTo())->map(function ($display, $address) { + return $display ? [ + 'emailAddress' => [ + 'address' => $address, + 'name' => $display + ] + ] : [ + 'emailAddress' => [ + 'address' => $address + ] + ]; + })->values()->toArray(); + } + + /** + * Get the "Cc" payload field for the API request. + * + * @param \Swift_Mime_SimpleMessage $message + * @return string + */ + protected function getCc(Swift_Mime_SimpleMessage $message) + { + return collect((array) $message->getCc())->map(function ($display, $address) { + return $display ? [ + 'emailAddress' => [ + 'address' => $address, + 'name' => $display + ] + ] : [ + 'emailAddress' => [ + 'address' => $address + ] + ]; + })->values()->toArray(); + } + + /** + * Get the "replyTo" payload field for the API request. + * + * @param \Swift_Mime_SimpleMessage $message + * @return string + */ + protected function getReplyTo(Swift_Mime_SimpleMessage $message) + { + return collect((array) $message->getReplyTo())->map(function ($display, $address) { + return $display ? [ + 'emailAddress' => [ + 'address' => $address, + 'name' => $display + ] + ] : [ + 'emailAddress' => [ + 'address' => $address + ] + ]; + })->values()->toArray(); + } + + /** + * Get the "Bcc" payload field for the API request. + * + * @param \Swift_Mime_SimpleMessage $message + * @return string + */ + protected function getBcc(Swift_Mime_SimpleMessage $message) + { + return collect((array) $message->getBcc())->map(function ($display, $address) { + return $display ? [ + 'emailAddress' => [ + 'address' => $address, + 'name' => $display + ] + ] : [ + 'emailAddress' => [ + 'address' => $address + ] + ]; + })->values()->toArray(); + } + + /** + * Get all of the contacts for the message. + * + * @param \Swift_Mime_SimpleMessage $message + * @return array + */ + protected function allContacts(Swift_Mime_SimpleMessage $message) + { + return array_merge( + (array) $message->getTo(), + (array) $message->getCc(), + (array) $message->getBcc(), + (array) $message->getReplyTo() + ); + } + + protected function getAccessToken() + { + $guzzle = new \GuzzleHttp\Client(); + $url = 'https://login.microsoftonline.com/' . config('ninja.o365.tenant_id') . '/oauth2/v2.0/token'; + $token = json_decode($guzzle->post($url, [ + 'form_params' => [ + 'client_id' => config('ninja.o365.client_id'), + 'client_secret' => config('ninja.o365.client_secret'), + 'scope' => 'https://graph.microsoft.com/.default', + 'grant_type' => 'client_credentials', + ], + ])->getBody()->getContents()); + + nlog($token); + + nlog($token->access_token); + + return $token->access_token; + } +} \ No newline at end of file diff --git a/app/Helpers/Mail/Office365TransportManager.php b/app/Helpers/Mail/Office365TransportManager.php new file mode 100644 index 000000000000..3c88383820e4 --- /dev/null +++ b/app/Helpers/Mail/Office365TransportManager.php @@ -0,0 +1,23 @@ + 'offline', "prompt" => "consent select_account", 'redirect_uri' => config('ninja.app_url')."/auth/google"]; } + if($provider == 'microsoft'){ + $scopes = ['email', 'Mail.ReadWrite', 'Mail.Send', 'offline_access', 'profile', 'User.Read openid']; + $parameters = ['access_type' => 'offline', "prompt" => "consent select_account", 'redirect_uri' => config('ninja.app_url')."/auth/microsoft"]; + } + if (request()->has('code')) { return $this->handleProviderCallback($provider); } else { @@ -710,6 +715,10 @@ class LoginController extends BaseController public function handleProviderCallback(string $provider) { + + if($provider == 'microsoft') + return $this->handleMicrosoftProviderCallback(); + $socialite_user = Socialite::driver($provider)->user(); $oauth_user_token = ''; @@ -749,4 +758,40 @@ class LoginController extends BaseController return redirect('/#/'); } + + public function handleMicrosoftProviderCallback($provider = 'microsoft') + { + + $socialite_user = Socialite::driver($provider)->user(); + nlog($socialite_user); + + $oauth_user_token = ''; + + if($user = OAuth::handleAuth($socialite_user, $provider)) + { + + nlog('found user and updating their user record'); + $name = OAuth::splitName($socialite_user->getName()); + + $update_user = [ + 'first_name' => $name[0], + 'last_name' => $name[1], + 'email' => $socialite_user->getEmail(), + 'oauth_user_id' => $socialite_user->getId(), + 'oauth_provider_id' => $provider, + 'oauth_user_token' => $oauth_user_token, + 'oauth_user_refresh_token' => $socialite_user->refreshToken + ]; + + $user->update($update_user); + + } + else { + nlog("user not found for oauth"); + } + + return redirect('/#/'); + + } + } diff --git a/app/Providers/MailServiceProvider.php b/app/Providers/MailServiceProvider.php index 3389f5a4cc08..b9489888d62b 100644 --- a/app/Providers/MailServiceProvider.php +++ b/app/Providers/MailServiceProvider.php @@ -39,7 +39,6 @@ class MailServiceProvider extends MailProvider return new GmailTransportManager($app); }); - //this is octane ready - but is untested // $this->app->bind('mail.manager', function ($app){ // return new GmailTransportManager($app); diff --git a/config/mail.php b/config/mail.php index 88b35739aa46..97ab1b8c182e 100644 --- a/config/mail.php +++ b/config/mail.php @@ -73,6 +73,9 @@ return [ 'gmail' => [ 'transport' => 'gmail', ], + 'office365' => [ + 'transport' => 'office365', + ], 'cocopostmark' => [ 'transport' => 'cocopostmark', ],