imap ref
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Espo\Core\Mail\Account;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Mail\Account\Storage\Handler;
|
||||
use Espo\Core\Mail\Account\Storage\LaminasStorage;
|
||||
use Espo\Core\Mail\Account\Storage\Params;
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Mail\Storage\Imap;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Laminas\Mail\Protocol\Exception\ExceptionInterface;
|
||||
use Laminas\Mail\Protocol\Exception\RuntimeException as ProtocolRuntimeException;
|
||||
use Laminas\Mail\Protocol\Imap as ImapProtocol;
|
||||
use Laminas\Mail\Storage\Exception\InvalidArgumentException;
|
||||
use Laminas\Mail\Storage\Exception\RuntimeException as LaminasRuntimeException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
*/
|
||||
class CommonStorageFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Log $log,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws ImapError
|
||||
*/
|
||||
public function create(Params $params): LaminasStorage
|
||||
{
|
||||
$handlerClassName = $params->getImapHandlerClassName();
|
||||
$handler = null;
|
||||
$isHandled = false;
|
||||
|
||||
if ($handlerClassName && $params->getId()) {
|
||||
$handler = $this->injectableFactory->create($handlerClassName);
|
||||
|
||||
if ($handler instanceof Handler || method_exists($handler, 'handle')) {
|
||||
$params = $handler->handle($params, $params->getId());
|
||||
|
||||
$isHandled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($params->getAuthMechanism() === Params::AUTH_MECHANISM_XOAUTH) {
|
||||
$imapParams = $this->prepareXoauthProtocol($params);
|
||||
} else {
|
||||
$rawParams = [
|
||||
'host' => $params->getHost(),
|
||||
'port' => $params->getPort(),
|
||||
'username' => $params->getUsername(),
|
||||
'password' => $params->getPassword(),
|
||||
'id' => $params->getId(),
|
||||
];
|
||||
|
||||
if ($params->getSecurity()) {
|
||||
$rawParams['security'] = $params->getSecurity();
|
||||
}
|
||||
|
||||
$imapParams = null;
|
||||
|
||||
// For bc.
|
||||
if (!$isHandled && $handler && $params->getId() && method_exists($handler, 'prepareProtocol')) {
|
||||
$imapParams = $handler->prepareProtocol($params->getId(), $rawParams);
|
||||
}
|
||||
|
||||
if (!$imapParams) {
|
||||
$imapParams = [
|
||||
'host' => $rawParams['host'],
|
||||
'port' => $rawParams['port'],
|
||||
'user' => $rawParams['username'],
|
||||
'password' => $rawParams['password'],
|
||||
];
|
||||
|
||||
if (!empty($rawParams['security'])) {
|
||||
$imapParams['ssl'] = $rawParams['security'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$storage = new Imap($imapParams);
|
||||
} catch (LaminasRuntimeException|InvalidArgumentException|ProtocolRuntimeException $e) {
|
||||
throw new ImapError($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return new LaminasStorage($storage);
|
||||
}
|
||||
|
||||
private function prepareXoauthProtocol(Params $params): ?ImapProtocol
|
||||
{
|
||||
$username = $params->getUsername();
|
||||
$accessToken = $params->getPassword();
|
||||
$host = $params->getHost() ?? throw new RuntimeException("No IMAP host.");
|
||||
$port = $params->getPort();
|
||||
$ssl = $params->getSecurity() ?: false;
|
||||
|
||||
try {
|
||||
$protocol = new ImapProtocol($host, $port, $ssl);
|
||||
} catch (ExceptionInterface $e) {
|
||||
throw new RuntimeException($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
$authString = base64_encode("user=$username\1auth=Bearer $accessToken\1\1");
|
||||
|
||||
$authenticateParams = ['XOAUTH2', $authString];
|
||||
$protocol->sendRequest('AUTHENTICATE', $authenticateParams);
|
||||
|
||||
$i = 0;
|
||||
|
||||
while (true) {
|
||||
if ($i === 10) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$response = '';
|
||||
$isPlus = $protocol->readLine($response, '+', true);
|
||||
|
||||
if ($isPlus) {
|
||||
$this->log->warning("Imap XOauth: Extra server challenge: " . var_export($response, true));
|
||||
|
||||
$protocol->sendRequest('');
|
||||
} else {
|
||||
if (
|
||||
is_string($response) &&
|
||||
(preg_match('/^NO /i', $response) || preg_match('/^BAD /i', $response))
|
||||
) {
|
||||
$this->log->error("Imap XOauth: Failure: " . var_export($response, true));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_string($response) && preg_match("/^OK /i", $response)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
|
||||
return $protocol;
|
||||
}
|
||||
}
|
||||
@@ -29,28 +29,17 @@
|
||||
|
||||
namespace Espo\Core\Mail\Account\GroupAccount;
|
||||
|
||||
use Espo\Core\Mail\Account\CommonStorageFactory;
|
||||
use Espo\Core\Mail\Account\Storage\Params;
|
||||
use Espo\Core\Mail\Account\Account;
|
||||
use Espo\Core\Mail\Account\StorageFactory as StorageFactoryInterface;
|
||||
use Espo\Core\Mail\Account\Storage\LaminasStorage;
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Exceptions\NoImap;
|
||||
use Espo\Core\Mail\Mail\Storage\Imap;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\InjectableFactory;
|
||||
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Laminas\Mail\Storage\Exception\RuntimeException;
|
||||
use Laminas\Mail\Storage\Exception\InvalidArgumentException;
|
||||
use Laminas\Mail\Protocol\Exception\RuntimeException as ProtocolRuntimeException;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class StorageFactory implements StorageFactoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Log $log,
|
||||
private InjectableFactory $injectableFactory
|
||||
private CommonStorageFactory $commonStorageFactory,
|
||||
) {}
|
||||
|
||||
public function create(Account $account): LaminasStorage
|
||||
@@ -76,60 +65,6 @@ class StorageFactory implements StorageFactoryInterface
|
||||
|
||||
public function createWithParams(Params $params): LaminasStorage
|
||||
{
|
||||
$rawParams = [
|
||||
'host' => $params->getHost(),
|
||||
'port' => $params->getPort(),
|
||||
'username' => $params->getUsername(),
|
||||
'password' => $params->getPassword(),
|
||||
'imapHandler' => $params->getImapHandlerClassName(),
|
||||
Attribute::ID => $params->getId(),
|
||||
];
|
||||
|
||||
if ($params->getSecurity()) {
|
||||
$rawParams['security'] = $params->getSecurity();
|
||||
}
|
||||
|
||||
$imapParams = null;
|
||||
|
||||
$handlerClassName = $rawParams['imapHandler'] ?? null;
|
||||
|
||||
$handler = null;
|
||||
|
||||
if ($handlerClassName && !empty($rawParams['id'])) {
|
||||
try {
|
||||
$handler = $this->injectableFactory->create($handlerClassName);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->error("InboundEmail: Could not create Imap Handler. Error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($handler && method_exists($handler, 'prepareProtocol')) {
|
||||
// for backward compatibility
|
||||
$rawParams['ssl'] = $rawParams['security'] ?? null;
|
||||
|
||||
// @todo Incorporate an interface `LaminasProtocolPreparator`.
|
||||
$imapParams = $handler->prepareProtocol($rawParams['id'], $rawParams);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$imapParams) {
|
||||
$imapParams = [
|
||||
'host' => $rawParams['host'],
|
||||
'port' => $rawParams['port'],
|
||||
'user' => $rawParams['username'],
|
||||
'password' => $rawParams['password'],
|
||||
];
|
||||
|
||||
if (!empty($rawParams['security'])) {
|
||||
$imapParams['ssl'] = $rawParams['security'];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$storage = new Imap($imapParams);
|
||||
} catch (RuntimeException|InvalidArgumentException|ProtocolRuntimeException $e) {
|
||||
throw new ImapError($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return new LaminasStorage($storage);
|
||||
return $this->commonStorageFactory->create($params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,29 +29,19 @@
|
||||
|
||||
namespace Espo\Core\Mail\Account\PersonalAccount;
|
||||
|
||||
use Espo\Core\Mail\Account\CommonStorageFactory;
|
||||
use Espo\Core\Mail\Account\Storage\Params;
|
||||
use Espo\Core\Mail\Account\StorageFactory as StorageFactoryInterface;
|
||||
use Espo\Core\Mail\Account\Account;
|
||||
use Espo\Core\Mail\Exceptions\ImapError;
|
||||
use Espo\Core\Mail\Exceptions\NoImap;
|
||||
use Espo\Core\Mail\Mail\Storage\Imap;
|
||||
use Espo\Core\Mail\Account\Storage\LaminasStorage;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
|
||||
use Laminas\Mail\Protocol\Exception\RuntimeException as ProtocolRuntimeException;
|
||||
use Laminas\Mail\Storage\Exception\InvalidArgumentException;
|
||||
use Laminas\Mail\Storage\Exception\RuntimeException;
|
||||
|
||||
use LogicException;
|
||||
use Throwable;
|
||||
|
||||
class StorageFactory implements StorageFactoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Log $log,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private CommonStorageFactory $commonStorageFactory,
|
||||
) {}
|
||||
|
||||
public function create(Account $account): LaminasStorage
|
||||
@@ -87,63 +77,6 @@ class StorageFactory implements StorageFactoryInterface
|
||||
|
||||
public function createWithParams(Params $params): LaminasStorage
|
||||
{
|
||||
$rawParams = [
|
||||
'host' => $params->getHost(),
|
||||
'port' => $params->getPort(),
|
||||
'username' => $params->getUsername(),
|
||||
'password' => $params->getPassword(),
|
||||
'emailAddress' => $params->getEmailAddress(),
|
||||
'userId' => $params->getUserId(),
|
||||
'imapHandler' => $params->getImapHandlerClassName(),
|
||||
Attribute::ID => $params->getId(),
|
||||
];
|
||||
|
||||
if ($params->getSecurity()) {
|
||||
$rawParams['security'] = $params->getSecurity();
|
||||
}
|
||||
|
||||
/** @var ?class-string $handlerClassName */
|
||||
$handlerClassName = $rawParams['imapHandler'] ?? null;
|
||||
|
||||
$handler = null;
|
||||
$imapParams = null;
|
||||
|
||||
if ($handlerClassName && !empty($rawParams['id'])) {
|
||||
try {
|
||||
$handler = $this->injectableFactory->create($handlerClassName);
|
||||
} catch (Throwable $e) {
|
||||
$this->log->error(
|
||||
"EmailAccount: Could not create Imap Handler. Error: " . $e->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
if ($handler && method_exists($handler, 'prepareProtocol')) {
|
||||
// for backward compatibility
|
||||
$rawParams['ssl'] = $rawParams['security'] ?? null;
|
||||
|
||||
$imapParams = $handler->prepareProtocol($rawParams['id'], $rawParams);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$imapParams) {
|
||||
$imapParams = [
|
||||
'host' => $rawParams['host'],
|
||||
'port' => $rawParams['port'],
|
||||
'user' => $rawParams['username'],
|
||||
'password' => $rawParams['password'],
|
||||
];
|
||||
|
||||
if (!empty($rawParams['security'])) {
|
||||
$imapParams['ssl'] = $rawParams['security'];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$storage = new Imap($imapParams);
|
||||
} catch (RuntimeException|InvalidArgumentException|ProtocolRuntimeException $e) {
|
||||
throw new ImapError($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return new LaminasStorage($storage);
|
||||
return $this->commonStorageFactory->create($params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Espo\Core\Mail\Account\Storage;
|
||||
|
||||
/**
|
||||
* Handle storage parameters.
|
||||
* To be used by extensions.
|
||||
*
|
||||
* @since 9.3.0
|
||||
*/
|
||||
interface Handler
|
||||
{
|
||||
public function handle(Params $params, string $id): Params;
|
||||
}
|
||||
@@ -39,8 +39,19 @@ class Params
|
||||
/** @var ?class-string<object> */
|
||||
private ?string $imapHandlerClassName;
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public const string AUTH_MECHANISM_PLAIN = 'plain';
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public const string AUTH_MECHANISM_XOAUTH = 'xoauth';
|
||||
|
||||
/**
|
||||
* @param ?class-string<object> $imapHandlerClassName
|
||||
* @param self::AUTH_MECHANISM_* $authMechanism
|
||||
*/
|
||||
public function __construct(
|
||||
private ?string $host,
|
||||
@@ -51,7 +62,8 @@ class Params
|
||||
?string $imapHandlerClassName,
|
||||
private ?string $id,
|
||||
private ?string $userId,
|
||||
private ?string $emailAddress
|
||||
private ?string $emailAddress,
|
||||
private string $authMechanism = self::AUTH_MECHANISM_PLAIN,
|
||||
) {
|
||||
$this->imapHandlerClassName = $imapHandlerClassName;
|
||||
}
|
||||
@@ -109,6 +121,38 @@ class Params
|
||||
return $this->emailAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self::AUTH_MECHANISM_*
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public function getAuthMechanism(): string
|
||||
{
|
||||
return $this->authMechanism;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self::AUTH_MECHANISM_* $authMechanism
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public function withAuthMechanism(string $authMechanism): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->authMechanism = $authMechanism;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
*/
|
||||
public function withUsername(?string $username): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->username = $username;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withPassword(#[SensitiveParameter] ?string $password): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
Reference in New Issue
Block a user