1
0
mirror of https://github.com/freescout-helpdesk/freescout.git synced 2024-11-23 10:52:31 +01:00

Microsoft Exchange oAuth for SMTP - closes #4072

This commit is contained in:
FreeScout 2024-06-12 11:45:08 -07:00
parent fb3b0d3a49
commit 7e8e514e7b
8 changed files with 114 additions and 25 deletions

View File

@ -853,6 +853,7 @@ class MailboxesController extends Controller
{
$mailbox_id = $request->id ?? '';
$provider = $request->provider ?? '';
$in_out = $request->in_out ?? 'in';
$state_data = [];
if (!empty($request->state)) {
@ -863,6 +864,9 @@ class MailboxesController extends Controller
if (!empty($state_data['provider'])) {
$provider = $state_data['provider'];
}
if (!empty($state_data['in_out'])) {
$in_out = $state_data['in_out'];
}
}
// MS Exchange.
@ -880,10 +884,17 @@ class MailboxesController extends Controller
if (empty($mailbox)) {
return __('Mailbox not found').': '.$mailbox_id;
}
if (empty($mailbox->in_username)) {
if ($in_out == 'in') {
$username = $mailbox->in_username;
$password = $mailbox->in_password;
} else {
$username = $mailbox->out_username;
$password = $mailbox->out_password;
}
if (empty($username)) {
return 'Enter oAuth Client ID as Username and save mailbox settings';
}
if (empty($mailbox->in_password)) {
if (empty($password)) {
return 'Enter oAuth Client Secret as Password and save mailbox settings';
}
@ -893,14 +904,16 @@ class MailboxesController extends Controller
}
if (empty($request->code)) {
// Start.
$state = [
'provider' => $provider,
'mailbox_id' => $mailbox_id,
'state' => crc32($mailbox->in_username.$mailbox->in_password),
'in_out' => $in_out,
'state' => crc32($username.$password),
];
$url = \MailHelper::oauthGetAuthorizationUrl(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [
'state' => json_encode($state),
'client_id' => $mailbox->in_username,
'client_id' => $username,
]);
if ($url) {
\Session::put('mailbox_oauth_'.$provider.'_'.$mailbox_id, $state);
@ -921,21 +934,35 @@ class MailboxesController extends Controller
return 'Invalid oAuth state';
} else {
// state is set.
// Try to get an access token (using the authorization code grant)
$token_data = \MailHelper::oauthGetAccessToken(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [
'client_id' => $mailbox->in_username,
'client_secret' => $mailbox->in_password,
'client_id' => $username,
'client_secret' => $password,
'code' => $request->code,
]);
if (!empty($token_data['a_token'])) {
// Set username and password for the oppozite in_out.
if ($in_out == 'in') {
$mailbox->out_username = $username;
$mailbox->out_password = $password;
//$mailbox->out_method = Mailbox::OUT_METHOD_SMTP;
} else {
$mailbox->in_username = $username;
$mailbox->in_password = $password;
}
$mailbox->setMetaParam('oauth', $token_data, true);
} elseif (!empty($token_data['error'])) {
return __('Error occurred').': '.htmlspecialchars($token_data['error']);
}
return redirect()->route('mailboxes.connection.incoming', ['id' => $mailbox_id]);
if ($in_out == 'in') {
$route = 'mailboxes.connection.incoming';
} else {
$route = 'mailboxes.connection';
}
return redirect()->route($route, ['id' => $mailbox_id]);
}
}
@ -943,12 +970,20 @@ class MailboxesController extends Controller
{
$mailbox_id = $request->id ?? '';
$provider = $request->provider ?? '';
$in_out = $request->in_out ?? 'in';
$mailbox = Mailbox::findOrFail($mailbox_id);
$this->authorize('admin', $mailbox);
// oAuth Disconnect.
$mailbox->removeMetaParam('oauth', true);
return \MailHelper::oauthDisconnect($provider, route('mailboxes.connection.incoming', ['id' => $mailbox_id]));
if ($in_out == 'in') {
$route = 'mailboxes.connection.incoming';
} else {
$route = 'mailboxes.connection';
}
return \MailHelper::oauthDisconnect($provider, route($route, ['id' => $mailbox_id]));
}
}

View File

@ -100,20 +100,52 @@ class Mail
public static function setMailDriver($mailbox = null, $user_from = null, $conversation = null)
{
if ($mailbox) {
// Configure mail driver according to Mailbox settings
// Configure mail driver according to Mailbox settings.
$oauth = $mailbox->oauthEnabled();
// Refresh Access Token.
if ($oauth) {
if ((strtotime($mailbox->oauthGetParam('issued_on')) + (int)$mailbox->oauthGetParam('expires_in')) < time()) {
// Try to get an access token (using the authorization code grant)
$token_data = \MailHelper::oauthGetAccessToken(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [
'client_id' => $mailbox->out_username,
'client_secret' => $mailbox->out_password,
'refresh_token' => $mailbox->oauthGetParam('r_token'),
]);
if (!empty($token_data['a_token'])) {
$mailbox->setMetaParam('oauth', $token_data, true);
} elseif (!empty($token_data['error'])) {
$error_message = 'Error occurred refreshing oAuth Access Token: '.$token_data['error'];
\Helper::log(\App\ActivityLog::NAME_EMAILS_SENDING,
\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER, [
'error' => $error_message,
'mailbox' => $mailbox->name,
]);
//throw new \Exception($error_message, 1);
}
}
}
\Config::set('mail.driver', $mailbox->getMailDriverName());
\Config::set('mail.from', $mailbox->getMailFrom($user_from, $conversation));
// SMTP
// SMTP.
if ($mailbox->out_method == Mailbox::OUT_METHOD_SMTP) {
\Config::set('mail.host', $mailbox->out_server);
\Config::set('mail.port', $mailbox->out_port);
if (!$mailbox->out_username) {
\Config::set('mail.username', null);
\Config::set('mail.password', null);
if ($oauth) {
\Config::set('mail.auth_mode', 'XOAUTH2');
\Config::set('mail.username', $mailbox->email);
\Config::set('mail.password', $mailbox->oauthGetParam('a_token'));
} else {
\Config::set('mail.username', $mailbox->out_username);
\Config::set('mail.password', $mailbox->out_password);
if (!$mailbox->out_username) {
\Config::set('mail.username', null);
\Config::set('mail.password', null);
} else {
\Config::set('mail.username', $mailbox->out_username);
\Config::set('mail.password', $mailbox->out_password);
}
}
\Config::set('mail.encryption', $mailbox->getOutEncryptionName());
}

View File

@ -88,6 +88,17 @@ return [
'password' => env('MAIL_PASSWORD'),
/*
|--------------------------------------------------------------------------
| Auth mode
|--------------------------------------------------------------------------
|
| 'login' or 'XOAUTH2'
|
*/
'auth_mode' => env('MAIL_AUTH_MODE', 'login'),
/*
|--------------------------------------------------------------------------
| Sendmail System Path

View File

@ -31,7 +31,7 @@ class TransportManager extends Manager
// The Swift SMTP transport instance will allow us to use any SMTP backend
// for delivering mail such as Sendgrid, Amazon SES, or a custom server
// a developer has available. We will just pass this configured host.
$transport = new SmtpTransport($config['host'], $config['port']);
$transport = new SmtpTransport($config['host'], $config['port'], null, $config['auth_mode']);
if (isset($config['encryption'])) {
$transport->setEncryption($config['encryption']);

View File

@ -27,7 +27,7 @@ class Swift_SmtpTransport extends Swift_Transport_EsmtpTransport
* @param int $port
* @param string $encryption
*/
public function __construct($host = 'localhost', $port = 25, $encryption = null)
public function __construct($host = 'localhost', $port = 25, $encryption = null, $auth_mode = 'login')
{
parent::__construct(...Swift_DependencyContainer::getInstance()->createDependenciesFor('transport.smtp'));
// call_user_func_array(
@ -39,5 +39,6 @@ class Swift_SmtpTransport extends Swift_Transport_EsmtpTransport
$this->setHost($host);
$this->setPort($port);
$this->setEncryption($encryption);
$this->setAuthMode($auth_mode);
}
}

View File

@ -88,7 +88,7 @@
<label for="out_username" class="col-sm-2 control-label">{{ __('Username') }}</label>
<div class="col-sm-6">
<input id="out_username" type="text" class="form-control input-sized" name="out_username" value="{{ old('out_username', $mailbox->out_username) }}" maxlength="100" @if ($mailbox->out_method == App\Mailbox::OUT_METHOD_SMTP) @endif autofocus {{-- This added to prevent autocomplete in Chrome --}}autocomplete="new-password">
<input id="out_username" type="text" class="form-control input-sized @if ($mailbox->oauthEnabled()) disabled @endif" name="out_username" value="{{ old('out_username', $mailbox->out_username) }}" maxlength="100" @if ($mailbox->out_method == App\Mailbox::OUT_METHOD_SMTP) @endif autofocus {{-- This added to prevent autocomplete in Chrome --}}autocomplete="new-password" @if ($mailbox->oauthEnabled()) readonly @endif>
@include('partials/field_error', ['field'=>'out_username'])
</div>
@ -97,9 +97,19 @@
<label for="out_password" class="col-sm-2 control-label">{{ __('Password') }}</label>
<div class="col-sm-6">
<input id="out_password" type="password" class="form-control input-sized" name="out_password" value="{{ old('out_password', $mailbox->outPasswordSafe()) }}" maxlength="255" @if ($mailbox->out_method == App\Mailbox::OUT_METHOD_SMTP) @endif autofocus {{-- This added to prevent autocomplete in Chrome --}}autocomplete="new-password">
<input id="out_password" type="password" class="form-control input-sized @if ($mailbox->oauthEnabled()) disabled @endif" name="out_password" value="{{ old('out_password', $mailbox->outPasswordSafe()) }}" maxlength="255" @if ($mailbox->out_method == App\Mailbox::OUT_METHOD_SMTP) @endif autofocus {{-- This added to prevent autocomplete in Chrome --}}autocomplete="new-password" @if ($mailbox->oauthEnabled()) readonly @endif>
@include('partials/field_error', ['field'=>'out_password'])
<p class="form-help">
<small @if ($mailbox->oauthGetParam('provider') == \MailHelper::OAUTH_PROVIDER_MICROSOFT) class="text-success" @endif>Microsoft Exchange</small>
@if (!$mailbox->oauthEnabled())
@if ($mailbox->out_username && $mailbox->out_password && !strstr($mailbox->out_username, '@'))
<a href="{{ route('mailboxes.oauth', ['id' => $mailbox->id, 'provider' => \MailHelper::OAUTH_PROVIDER_MICROSOFT, 'in_out' => 'out']) }}" target="_blank">{{ __('Connect') }}</a>
@endif
@elseif ($mailbox->oauthGetParam('provider') == \MailHelper::OAUTH_PROVIDER_MICROSOFT)
<a href="{{ route('mailboxes.oauth_disconnect', ['id' => $mailbox->id, 'provider' => \MailHelper::OAUTH_PROVIDER_MICROSOFT, 'in_out' => 'out']) }}">{{ __('Disconnect') }}</a>
@endif
<small>(<a href="https://github.com/freescout-helpdesk/freescout/wiki/Connect-FreeScout-to-Microsoft-365-Exchange-via-oAuth" target="_blank">{{ __('Help') }}</a>)</small>
</p>
</div>
</div>
<div class="form-group{{ $errors->has('out_encryption') ? ' has-error' : '' }}">

View File

@ -109,10 +109,10 @@
<small @if ($mailbox->oauthGetParam('provider') == \MailHelper::OAUTH_PROVIDER_MICROSOFT) class="text-success" @endif>Microsoft Exchange</small>
@if (!$mailbox->oauthEnabled())
@if ($mailbox->in_username && $mailbox->in_password && !strstr($mailbox->in_username, '@'))
<a href="{{ route('mailboxes.oauth', ['id' => $mailbox->id, 'provider' => \MailHelper::OAUTH_PROVIDER_MICROSOFT]) }}" target="_blank">{{ __('Connect') }}</a>
<a href="{{ route('mailboxes.oauth', ['id' => $mailbox->id, 'provider' => \MailHelper::OAUTH_PROVIDER_MICROSOFT, 'in_out' => 'in']) }}" target="_blank">{{ __('Connect') }}</a>
@endif
@elseif ($mailbox->oauthGetParam('provider') == \MailHelper::OAUTH_PROVIDER_MICROSOFT)
<a href="{{ route('mailboxes.oauth_disconnect', ['id' => $mailbox->id, 'provider' => \MailHelper::OAUTH_PROVIDER_MICROSOFT]) }}">{{ __('Disconnect') }}</a>
<a href="{{ route('mailboxes.oauth_disconnect', ['id' => $mailbox->id, 'provider' => \MailHelper::OAUTH_PROVIDER_MICROSOFT, 'in_out' => 'in']) }}">{{ __('Disconnect') }}</a>
@endif
<small>(<a href="https://github.com/freescout-helpdesk/freescout/wiki/Connect-FreeScout-to-Microsoft-365-Exchange-via-oAuth" target="_blank">{{ __('Help') }}</a>)</small>
</p>

View File

@ -89,9 +89,9 @@ Route::post('/mailbox/connection-settings/{id}/incoming', 'MailboxesController@c
Route::get('/mailbox/settings/{id}/auto-reply', 'MailboxesController@autoReply')->name('mailboxes.auto_reply');
Route::post('/mailbox/settings/{id}/auto-reply', 'MailboxesController@autoReplySave')->name('mailboxes.auto_reply.save');
Route::post('/mailbox/ajax', ['uses' => 'MailboxesController@ajax', 'laroute' => true])->name('mailboxes.ajax');
Route::get('/mailbox/oauth/{id}/{provider}', ['uses' => 'MailboxesController@oauth'])->name('mailboxes.oauth');
Route::get('/mailbox/oauth/{id}/{in_out}/{provider}', ['uses' => 'MailboxesController@oauth'])->name('mailboxes.oauth');
Route::get('/mailbox/oauth', ['uses' => 'MailboxesController@oauth'])->name('mailboxes.oauth_callback');
Route::get('/mailbox/oauth-disconnect/{id}/{provider}', ['uses' => 'MailboxesController@oauthDisconnect'])->name('mailboxes.oauth_disconnect');
Route::get('/mailbox/oauth-disconnect/{id}/{in_out}/{provider}', ['uses' => 'MailboxesController@oauthDisconnect'])->name('mailboxes.oauth_disconnect');
// Customers
Route::get('/customers/{id}/edit', 'CustomersController@update')->name('customers.update');