Skip to content

Commit c1b6e14

Browse files
author
Timo Nieminen
committed
feat(provisioning): integrate ProvisioningMapper into IMAP and Sieve client factories and tests
1 parent d07eb4a commit c1b6e14

5 files changed

Lines changed: 147 additions & 131 deletions

File tree

lib/IMAP/IMAPClientFactory.php

Lines changed: 129 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -28,133 +28,133 @@
2828
use function json_encode;
2929

3030
class IMAPClientFactory {
31-
/** @var ICrypto */
32-
private $crypto;
33-
34-
/** @var IConfig */
35-
private $config;
36-
37-
/** @var ICacheFactory */
38-
private $cacheFactory;
39-
40-
/** @var IEventDispatcher */
41-
private $eventDispatcher;
42-
43-
private ITimeFactory $timeFactory;
44-
private HordeCacheFactory $hordeCacheFactory;
45-
private ProvisioningMapper $provisioningMapper;
46-
47-
public function __construct(ICrypto $crypto,
48-
IConfig $config,
49-
ICacheFactory $cacheFactory,
50-
IEventDispatcher $eventDispatcher,
51-
ITimeFactory $timeFactory,
52-
HordeCacheFactory $hordeCacheFactory,
53-
ProvisioningMapper $provisioningMapper) {
54-
$this->crypto = $crypto;
55-
$this->config = $config;
56-
$this->cacheFactory = $cacheFactory;
57-
$this->eventDispatcher = $eventDispatcher;
58-
$this->timeFactory = $timeFactory;
59-
$this->hordeCacheFactory = $hordeCacheFactory;
60-
$this->provisioningMapper = $provisioningMapper;
61-
}
62-
63-
/**
64-
* Get the connection object for the given account
65-
*
66-
* Connections are not closed until destruction, so the caller site is
67-
* responsible to log out as soon as possible to keep the number of open
68-
* (and stale) connections at a minimum.
69-
*
70-
* @param Account $account
71-
* @param bool $useCache
72-
*
73-
* @return Horde_Imap_Client_Socket
74-
* @throws ServiceException
75-
*/
76-
public function getClient(Account $account, bool $useCache = true): Horde_Imap_Client_Socket {
77-
$this->eventDispatcher->dispatchTyped(
78-
new BeforeImapClientCreated($account)
79-
);
80-
$host = $account->getMailAccount()->getInboundHost();
81-
$user = $account->getMailAccount()->getInboundUser();
82-
$decryptedPassword = null;
83-
if ($account->getMailAccount()->getInboundPassword() !== null) {
84-
$decryptedPassword = $this->crypto->decrypt($account->getMailAccount()->getInboundPassword());
85-
}
86-
$port = $account->getMailAccount()->getInboundPort();
87-
$sslMode = $account->getMailAccount()->getInboundSslMode();
88-
if ($sslMode === 'none') {
89-
$sslMode = false;
90-
}
91-
92-
// Check for Dovecot master user authentication
93-
$provisioningId = $account->getMailAccount()->getProvisioningId();
94-
if ($provisioningId !== null) {
95-
$provisioning = $this->provisioningMapper->get($provisioningId);
96-
if ($provisioning !== null && !empty($provisioning->getMasterUser())) {
97-
$separator = $provisioning->getMasterUserSeparator() ?? '*';
98-
$user = $user . $separator . $provisioning->getMasterUser();
99-
}
100-
}
101-
102-
$params = [
103-
'username' => $user,
104-
'password' => $decryptedPassword,
105-
'hostspec' => $host,
106-
'port' => $port,
107-
'secure' => $sslMode,
108-
'timeout' => (int)$this->config->getSystemValue('app.mail.imap.timeout', 5),
109-
'context' => [
110-
'ssl' => [
111-
'verify_peer' => $this->config->getSystemValueBool('app.mail.verify-tls-peer', true),
112-
'verify_peer_name' => $this->config->getSystemValueBool('app.mail.verify-tls-peer', true),
113-
],
114-
],
115-
];
116-
if ($account->getMailAccount()->getAuthMethod() === 'xoauth2') {
117-
try {
118-
$oauthAccessToken = $account->getMailAccount()->getOauthAccessToken();
119-
if ($oauthAccessToken === null) {
120-
throw new ServiceException('Missing access token for xoauth2 account');
121-
}
122-
$decryptedAccessToken = $this->crypto->decrypt($oauthAccessToken);
123-
} catch (Exception $e) {
124-
throw new ServiceException('Could not decrypt account access token: ' . $e->getMessage(), 0, $e);
125-
}
126-
127-
$params['password'] = $decryptedAccessToken; // Not used, but Horde wants this
128-
$params['xoauth2_token'] = new Horde_Imap_Client_Password_Xoauth2(
129-
$account->getEmail(),
130-
$decryptedAccessToken,
131-
);
132-
}
133-
$paramHash = hash(
134-
'sha512',
135-
implode('-', [
136-
$this->config->getSystemValueString('secret'),
137-
$account->getId(),
138-
json_encode($params)
139-
]),
140-
);
141-
if ($useCache) {
142-
$params['cache'] = [
143-
'backend' => $this->hordeCacheFactory->newCache($account),
144-
];
145-
}
146-
if ($account->getDebug() || $this->config->getSystemValueBool('app.mail.debug')) {
147-
$fn = 'mail-' . $account->getUserId() . '-' . $account->getId() . '-imap.log';
148-
$params['debug'] = $this->config->getSystemValue('datadirectory') . '/' . $fn;
149-
}
150-
151-
$client = new HordeImapClient($params);
152-
153-
$rateLimitingCache = $this->cacheFactory->createDistributed('mail_imap_ratelimit');
154-
if ($rateLimitingCache instanceof IMemcache) {
155-
$client->enableRateLimiter($rateLimitingCache, $paramHash, $this->timeFactory);
156-
}
157-
158-
return $client;
159-
}
31+
/** @var ICrypto */
32+
private $crypto;
33+
34+
/** @var IConfig */
35+
private $config;
36+
37+
/** @var ICacheFactory */
38+
private $cacheFactory;
39+
40+
/** @var IEventDispatcher */
41+
private $eventDispatcher;
42+
43+
private ITimeFactory $timeFactory;
44+
private HordeCacheFactory $hordeCacheFactory;
45+
private ProvisioningMapper $provisioningMapper;
46+
47+
public function __construct(ICrypto $crypto,
48+
IConfig $config,
49+
ICacheFactory $cacheFactory,
50+
IEventDispatcher $eventDispatcher,
51+
ITimeFactory $timeFactory,
52+
HordeCacheFactory $hordeCacheFactory,
53+
ProvisioningMapper $provisioningMapper) {
54+
$this->crypto = $crypto;
55+
$this->config = $config;
56+
$this->cacheFactory = $cacheFactory;
57+
$this->eventDispatcher = $eventDispatcher;
58+
$this->timeFactory = $timeFactory;
59+
$this->hordeCacheFactory = $hordeCacheFactory;
60+
$this->provisioningMapper = $provisioningMapper;
61+
}
62+
63+
/**
64+
* Get the connection object for the given account
65+
*
66+
* Connections are not closed until destruction, so the caller site is
67+
* responsible to log out as soon as possible to keep the number of open
68+
* (and stale) connections at a minimum.
69+
*
70+
* @param Account $account
71+
* @param bool $useCache
72+
*
73+
* @return Horde_Imap_Client_Socket
74+
* @throws ServiceException
75+
*/
76+
public function getClient(Account $account, bool $useCache = true): Horde_Imap_Client_Socket {
77+
$this->eventDispatcher->dispatchTyped(
78+
new BeforeImapClientCreated($account)
79+
);
80+
$host = $account->getMailAccount()->getInboundHost();
81+
$user = $account->getMailAccount()->getInboundUser();
82+
$decryptedPassword = null;
83+
if ($account->getMailAccount()->getInboundPassword() !== null) {
84+
$decryptedPassword = $this->crypto->decrypt($account->getMailAccount()->getInboundPassword());
85+
}
86+
$port = $account->getMailAccount()->getInboundPort();
87+
$sslMode = $account->getMailAccount()->getInboundSslMode();
88+
if ($sslMode === 'none') {
89+
$sslMode = false;
90+
}
91+
92+
// Check for Dovecot master user authentication
93+
$provisioningId = $account->getMailAccount()->getProvisioningId();
94+
if ($provisioningId !== null) {
95+
$provisioning = $this->provisioningMapper->get($provisioningId);
96+
if ($provisioning !== null && !empty($provisioning->getMasterUser())) {
97+
$separator = $provisioning->getMasterUserSeparator() ?? '*';
98+
$user = $user . $separator . $provisioning->getMasterUser();
99+
}
100+
}
101+
102+
$params = [
103+
'username' => $user,
104+
'password' => $decryptedPassword,
105+
'hostspec' => $host,
106+
'port' => $port,
107+
'secure' => $sslMode,
108+
'timeout' => (int)$this->config->getSystemValue('app.mail.imap.timeout', 5),
109+
'context' => [
110+
'ssl' => [
111+
'verify_peer' => $this->config->getSystemValueBool('app.mail.verify-tls-peer', true),
112+
'verify_peer_name' => $this->config->getSystemValueBool('app.mail.verify-tls-peer', true),
113+
],
114+
],
115+
];
116+
if ($account->getMailAccount()->getAuthMethod() === 'xoauth2') {
117+
try {
118+
$oauthAccessToken = $account->getMailAccount()->getOauthAccessToken();
119+
if ($oauthAccessToken === null) {
120+
throw new ServiceException('Missing access token for xoauth2 account');
121+
}
122+
$decryptedAccessToken = $this->crypto->decrypt($oauthAccessToken);
123+
} catch (Exception $e) {
124+
throw new ServiceException('Could not decrypt account access token: ' . $e->getMessage(), 0, $e);
125+
}
126+
127+
$params['password'] = $decryptedAccessToken; // Not used, but Horde wants this
128+
$params['xoauth2_token'] = new Horde_Imap_Client_Password_Xoauth2(
129+
$account->getEmail(),
130+
$decryptedAccessToken,
131+
);
132+
}
133+
$paramHash = hash(
134+
'sha512',
135+
implode('-', [
136+
$this->config->getSystemValueString('secret'),
137+
$account->getId(),
138+
json_encode($params)
139+
]),
140+
);
141+
if ($useCache) {
142+
$params['cache'] = [
143+
'backend' => $this->hordeCacheFactory->newCache($account),
144+
];
145+
}
146+
if ($account->getDebug() || $this->config->getSystemValueBool('app.mail.debug')) {
147+
$fn = 'mail-' . $account->getUserId() . '-' . $account->getId() . '-imap.log';
148+
$params['debug'] = $this->config->getSystemValue('datadirectory') . '/' . $fn;
149+
}
150+
151+
$client = new HordeImapClient($params);
152+
153+
$rateLimitingCache = $this->cacheFactory->createDistributed('mail_imap_ratelimit');
154+
if ($rateLimitingCache instanceof IMemcache) {
155+
$client->enableRateLimiter($rateLimitingCache, $paramHash, $this->timeFactory);
156+
}
157+
158+
return $client;
159+
}
160160
}

tests/Integration/Framework/Caching.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use OC\Memcache\Factory;
1313
use OCA\Mail\Cache\HordeCacheFactory;
14+
use OCA\Mail\Db\ProvisioningMapper;
1415
use OCA\Mail\IMAP\IMAPClientFactory;
1516
use OCP\AppFramework\Utility\ITimeFactory;
1617
use OCP\EventDispatcher\IEventDispatcher;
@@ -60,6 +61,7 @@ public static function getImapClientFactoryAndConfiguredCacheFactory(?ICrypto $c
6061
Server::get(IEventDispatcher::class),
6162
Server::get(ITimeFactory::class),
6263
Server::get(HordeCacheFactory::class),
64+
Server::get(ProvisioningMapper::class),
6365
);
6466
return [$imapClient, $cacheFactory];
6567
}

tests/Integration/IMAP/IMAPClientFactoryTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use OCA\Mail\Account;
1818
use OCA\Mail\Cache\HordeCacheFactory;
1919
use OCA\Mail\Db\MailAccount;
20+
use OCA\Mail\Db\ProvisioningMapper;
2021
use OCA\Mail\IMAP\HordeImapClient;
2122
use OCA\Mail\IMAP\IMAPClientFactory;
2223
use OCA\Mail\Tests\Integration\Framework\Caching;
@@ -44,6 +45,7 @@ class IMAPClientFactoryTest extends TestCase {
4445
private IEventDispatcher|MockObject $eventDispatcher;
4546
private ITimeFactory|MockObject $timeFactory;
4647
private HordeCacheFactory|MockObject $hordeCacheFactory;
48+
private ProvisioningMapper|MockObject $provisioningMapper;
4749

4850
protected function setUp(): void {
4951
parent::setUp();
@@ -54,6 +56,7 @@ protected function setUp(): void {
5456
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
5557
$this->timeFactory = $this->createMock(ITimeFactory::class);
5658
$this->hordeCacheFactory = $this->createMock(HordeCacheFactory::class);
59+
$this->provisioningMapper = $this->createMock(ProvisioningMapper::class);
5760

5861
$this->factory = new IMAPClientFactory(
5962
$this->crypto,
@@ -62,6 +65,7 @@ protected function setUp(): void {
6265
$this->eventDispatcher,
6366
$this->timeFactory,
6467
$this->hordeCacheFactory,
68+
$this->provisioningMapper,
6569
);
6670
}
6771

tests/Integration/Sieve/SieveClientFactoryTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Horde\ManageSieve;
1414
use OCA\Mail\Account;
1515
use OCA\Mail\Db\MailAccount;
16+
use OCA\Mail\Db\ProvisioningMapper;
1617
use OCA\Mail\Sieve\SieveClientFactory;
1718
use OCP\IConfig;
1819
use OCP\Security\ICrypto;
@@ -26,6 +27,9 @@ class SieveClientFactoryTest extends TestCase {
2627
/** @var IConfig|MockObject */
2728
private $config;
2829

30+
/** @var ProvisioningMapper|MockObject */
31+
private $provisioningMapper;
32+
2933
/** @var SieveClientFactory */
3034
private $factory;
3135

@@ -34,6 +38,7 @@ protected function setUp(): void {
3438

3539
$this->crypto = $this->createMock(ICrypto::class);
3640
$this->config = $this->createMock(IConfig::class);
41+
$this->provisioningMapper = $this->createMock(ProvisioningMapper::class);
3742

3843
$this->config->method('getSystemValueInt')
3944
->willReturnMap([
@@ -46,7 +51,7 @@ protected function setUp(): void {
4651
['app.mail.debug', false, false],
4752
]);
4853

49-
$this->factory = new SieveClientFactory($this->crypto, $this->config);
54+
$this->factory = new SieveClientFactory($this->crypto, $this->config, $this->provisioningMapper);
5055
}
5156

5257
/**

tests/Unit/SMTP/SmtpClientFactoryTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Horde_Mail_Transport_Smtphorde;
1414
use OCA\Mail\Account;
1515
use OCA\Mail\Db\MailAccount;
16+
use OCA\Mail\Db\ProvisioningMapper;
1617
use OCA\Mail\SMTP\SmtpClientFactory;
1718
use OCA\Mail\Support\HostNameFactory;
1819
use OCP\IConfig;
@@ -29,6 +30,9 @@ class SmtpClientFactoryTest extends TestCase {
2930
/** @var HostNameFactory|MockObject */
3031
private $hostNameFactory;
3132

33+
/** @var ProvisioningMapper|MockObject */
34+
private $provisioningMapper;
35+
3236
/** @var SmtpClientFactory */
3337
private $factory;
3438

@@ -38,8 +42,9 @@ protected function setUp(): void {
3842
$this->config = $this->createMock(IConfig::class);
3943
$this->crypto = $this->createMock(ICrypto::class);
4044
$this->hostNameFactory = $this->createMock(HostNameFactory::class);
45+
$this->provisioningMapper = $this->createMock(ProvisioningMapper::class);
4146

42-
$this->factory = new SmtpClientFactory($this->config, $this->crypto, $this->hostNameFactory);
47+
$this->factory = new SmtpClientFactory($this->config, $this->crypto, $this->hostNameFactory, $this->provisioningMapper);
4348
}
4449

4550
public function testSmtpTransport() {

0 commit comments

Comments
 (0)