From a64d78cd864a9a5df890077c4d799a1e269c5479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Fern=C3=A1ndez?= Date: Wed, 9 Oct 2024 13:47:13 +0200 Subject: [PATCH] Tarea #3602 - Oauth email --- Core/Controller/ConfigEmail.php | 64 +++++++++++++++--- Core/Lib/Email/NewMail.php | 115 ++++++++++++++++++++++++++++++++ composer.json | 5 +- 3 files changed, 174 insertions(+), 10 deletions(-) diff --git a/Core/Controller/ConfigEmail.php b/Core/Controller/ConfigEmail.php index 7996e4d92d..a1b42d2631 100644 --- a/Core/Controller/ConfigEmail.php +++ b/Core/Controller/ConfigEmail.php @@ -19,6 +19,7 @@ namespace FacturaScripts\Core\Controller; +use FacturaScripts\Core\Cache; use FacturaScripts\Core\Lib\ExtendedController\PanelController; use FacturaScripts\Core\Tools; use FacturaScripts\Dinamic\Lib\Email\NewMail; @@ -110,18 +111,18 @@ protected function createViewsEmailSent(string $viewName = 'ListEmailSent'): voi $this->tab($viewName)->setSettings('btnNew', false); } - protected function enableNotificationAction(bool $value): void + protected function enableNotificationAction(bool $value): bool { if (false === $this->validateFormToken()) { - return; + return true; } elseif (false === $this->user->can('EditEmailNotification', 'update')) { Tools::log()->warning('not-allowed-modify'); - return; + return true; } $codes = $this->request->request->get('code', []); if (false === is_array($codes)) { - return; + return true; } foreach ($codes as $code) { @@ -133,11 +134,12 @@ protected function enableNotificationAction(bool $value): void $notification->enabled = $value; if (false === $notification->save()) { Tools::log()->warning('record-save-error'); - return; + return true; } } Tools::log()->notice('record-updated-correctly'); + return true; } /** @@ -160,14 +162,16 @@ protected function execPreviousAction($action) { switch ($action) { case 'disable-notification': - $this->enableNotificationAction(false); - break; + return $this->enableNotificationAction(false); case 'enable-notification': - $this->enableNotificationAction(true); - break; + return $this->enableNotificationAction(true); + + case 'autenticate-oauth-mail': + return $this->oauth2AutenticateAction(); } + $this->oauth2Token(); return parent::execPreviousAction($action); } @@ -188,6 +192,15 @@ protected function loadData($viewName, $view) 'label' => 'test' ]); } + if ($view->model->authtype === 'XOAUTH2') { + // añadimos el botón test + $this->addButton($viewName, [ + 'action' => 'autenticate-oauth-mail', + 'color' => 'warning', + 'icon' => 'fa-solid fa-key', + 'label' => 'oauth' + ]); + } break; case 'ListEmailNotification': @@ -197,6 +210,39 @@ protected function loadData($viewName, $view) } } + protected function oauth2AutenticateAction(): bool + { + // guardamos los datos del formulario primero + if (false === $this->editAction()) { + return true; + } + + $email = new NewMail(); + $url = $email->oauth2Autenticate(); + if (empty($url)) { + return true; + } + + $this->redirect($url); + return true; + } + + protected function oauth2Token() + { + $code = $this->request->get('code', ''); + $state = $this->request->get('state', ''); + $sessionState = $this->request->get('session_state', ''); + + if (empty($code) || empty($state) || empty($sessionState) || $state !== Cache::get('oauth2state')) { + Cache::delete('oauth2state'); + return; + } + + $email = new NewMail(); + $email->oauth2Token($code); + Cache::delete('oauth2state'); + } + protected function testMailAction(): void { // guardamos los datos del formulario primero diff --git a/Core/Lib/Email/NewMail.php b/Core/Lib/Email/NewMail.php index 0078a5832f..42813ed573 100644 --- a/Core/Lib/Email/NewMail.php +++ b/Core/Lib/Email/NewMail.php @@ -19,6 +19,7 @@ namespace FacturaScripts\Core\Lib\Email; +use FacturaScripts\Core\Cache; use FacturaScripts\Core\DataSrc\Empresas; use FacturaScripts\Core\Html; use FacturaScripts\Core\Model\User; @@ -28,8 +29,13 @@ use FacturaScripts\Dinamic\Model\EmailNotification; use FacturaScripts\Dinamic\Model\EmailSent; use FacturaScripts\Dinamic\Model\Empresa; +use League\OAuth2\Client\Provider\GenericProvider; +use League\OAuth2\Client\Provider\Google; +use League\OAuth2\Client\Token\AccessToken; use PHPMailer\PHPMailer\Exception; +use PHPMailer\PHPMailer\OAuth; use PHPMailer\PHPMailer\PHPMailer; +use TheNetworg\OAuth2\Client\Provider\Azure; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; @@ -71,6 +77,8 @@ class NewMail /** @var BaseBlock[] */ protected $mainBlocks = []; + protected $provider; + /** @var string */ public $signature; @@ -121,6 +129,7 @@ public function __construct() $this->signature = Tools::settings('email', 'signature', ''); $this->verificode = Tools::randomString(20); + $this->setOauth2(); } /** @@ -301,6 +310,33 @@ public function replyTo(string $address, string $name = ''): NewMail return $this; } + public function oauth2Autenticate(): string + { + if (empty($this->provider) || $this->mail->AuthType !== 'XOAUTH2') { + return ''; + } + + $authorizationUrl = $this->provider->getAuthorizationUrl([ + 'prompt' => 'consent', // muestra siempre la pantalla de consentimiento + 'access_type' => 'offline', + //'access_type' => 'offline.access,smtp.send,user.read,offline,offline_access,SMTP.Send,https://outlook.office.com/SMTP.Send', + //'scope' => ['https://outlook.office.com/SMTP.Send', 'https://outlook.office.com/IMAP.AccessAsUser.All', 'offline_access'], + ]); + Cache::set('oauth2state', $this->provider->getState()); + return $authorizationUrl; + } + + public function oauth2Token(string $code) + { + $accessToken = $this->provider->getAccessToken('authorization_code', ['code' => $code]); + Tools::settingsSet('email', 'oauth2_access_token', $accessToken->getToken()); + Tools::settingsSet('email', 'oauth2_refresh_token', $accessToken->getRefreshToken()); + Tools::settingsSet('email', 'oauth2_expires', $accessToken->getExpires()); + if (Tools::settingsSave()) { + $this->setOauth2(); + } + } + /** * Envía el correo. * @@ -449,6 +485,28 @@ protected function getFooterBlocks(): array : array_merge($this->footerBlocks, [new TextBlock($signature, 'text-footer')]); } + protected function getOauthAccessToken(): AccessToken + { + $accessToken = Tools::settings('email', 'oauth2_access_token'); + $refreshToken = Tools::settings('email', 'oauth2_refresh_token'); + $expires = Tools::settings('email', 'oauth2_expires'); + + if ($expires && $expires < time()) { + $newAccessToken = $this->provider->getAccessToken('refresh_token', [ + 'refresh_token' => $refreshToken, + ]); + $refreshToken = $newAccessToken->getRefreshToken(); + $expires = $newAccessToken->getExpires(); + } + + return new AccessToken([ + 'access_token' => $accessToken, + 'refresh_token' => $refreshToken, + //'expires' => time() + 3600, + 'expires' => $expires, + ]); + } + /** * Devuelve los bloques del cuerpo del correo. */ @@ -542,6 +600,63 @@ protected function saveMailSent(): void } } + protected function setOauth2() + { + if ($this->mail->AuthType !== 'XOAUTH2') { + return; + } + + $params = ['redirectUri' => Tools::settings('default', 'site_url') . '/ConfigEmail',]; + + // si el host contiene live, outlook o hotmail, usamos el proveedor de Microsoft + if (strpos($this->mail->Host, 'live') !== false + || strpos($this->mail->Host, 'outlook') !== false + || strpos($this->mail->Host, 'hotmail') !== false) { + + $params['clientId'] = Tools::config('email_azure_oauth2_client_id'); + $params['clientSecret'] = Tools::config('email_azure_oauth2_client_secret'); + $params['tenantId'] = Tools::config('email_azure_oauth2_tenant_id'); + if (empty($params['tenantId'])) { + Tools::log()->warning('email-oauth2-tenant-id-not-configured'); + return; + } + + $this->provider = new Azure($params); + } elseif (strpos($this->mail->Host, 'gmail') !== false) { + // si el host contiene gmail, usamos el proveedor de Google + $params['clientId'] = Tools::config('email_google_oauth2_client_id'); + $params['clientSecret'] = Tools::config('email_google_oauth2_client_secret'); + $this->provider = new Google($params); + } else { + // en otro caso, usamos el proveedor genérico + $this->provider = new GenericProvider($params); + } + + if (empty($params['clientId']) || empty($params['clientSecret'])) { + Tools::log()->warning('email-oauth2-not-configured'); + return; + } + + // configurar el token de acceso + $accessToken = $this->getOauthAccessToken(); + if (empty($accessToken->getToken()) || empty($accessToken->getRefreshToken())) { + Tools::log()->warning('email-oauth2-access-token-or-refresh-token-not-configured'); + return; + } + + // configurar el proveedor de OAuth2 para PHPMailer + $this->mail->setOAuth( + new OAuth([ + 'provider' => $this->provider, + 'clientId' => $params['clientId'], + 'clientSecret' => $params['clientSecret'], + 'refreshToken' => $accessToken->getRefreshToken(), + 'userName' => $this->mail->Username, + 'accessToken' => $accessToken->getToken(), + ]) + ); + } + /** * Devuelve las opciones SMTP. */ diff --git a/composer.json b/composer.json index 7dc888394c..a065a1988a 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,10 @@ "ext-mysqli": "*", "ext-bcmath": "*", "ext-gd": "*", - "ext-curl": "*" + "ext-curl": "*", + "league/oauth2-client": "^2.7", + "league/oauth2-google": "^4.0", + "thenetworg/oauth2-azure": "^2.2" }, "require-dev": { "phpunit/phpunit": "9.*",