diff --git a/appinfo/info.xml b/appinfo/info.xml
index 7eb0b1411b..79b7fe75a3 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -34,7 +34,7 @@ The rating depends on the installed text processing backend. See [the rating ove
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]>
- 5.7.0-rc.1
+ 5.7.0-rc.2
agpl
Christoph Wurst
GretaD
diff --git a/lib/Db/Provisioning.php b/lib/Db/Provisioning.php
index 8d85c6d153..f7cae5121b 100644
--- a/lib/Db/Provisioning.php
+++ b/lib/Db/Provisioning.php
@@ -38,7 +38,11 @@
* @method bool|null getMasterPasswordEnabled()
* @method void setMasterPasswordEnabled(bool $masterPasswordEnabled)
* @method string|null getMasterPassword()
- * @method void setMasterPassword(string $masterPassword)
+ * @method void setMasterPassword(?string $masterPassword)
+ * @method string|null getMasterUser()
+ * @method void setMasterUser(?string $masterUser)
+ * @method string|null getMasterUserSeparator()
+ * @method void setMasterUserSeparator(?string $masterUserSeparator)
* @method bool|null getSieveEnabled()
* @method void setSieveEnabled(bool $sieveEnabled)
* @method string|null getSieveHost()
@@ -72,6 +76,8 @@ class Provisioning extends Entity implements JsonSerializable {
protected $smtpSslMode;
protected $masterPasswordEnabled;
protected $masterPassword;
+ protected $masterUser;
+ protected $masterUserSeparator;
protected $sieveEnabled;
protected $sieveUser;
protected $sieveHost;
@@ -86,6 +92,8 @@ public function __construct() {
$this->addType('smtpPort', 'integer');
$this->addType('masterPasswordEnabled', 'boolean');
$this->addType('masterPassword', 'string');
+ $this->addType('masterUser', 'string');
+ $this->addType('masterUserSeparator', 'string');
$this->addType('sieveEnabled', 'boolean');
$this->addType('sievePort', 'integer');
$this->addType('ldapAliasesProvisioning', 'boolean');
@@ -108,6 +116,8 @@ public function jsonSerialize() {
'smtpSslMode' => $this->getSmtpSslMode(),
'masterPasswordEnabled' => $this->getMasterPasswordEnabled(),
'masterPassword' => !empty($this->getMasterPassword()) ? self::MASTER_PASSWORD_PLACEHOLDER : null,
+ 'masterUser' => $this->getMasterUser(),
+ 'masterUserSeparator' => $this->getMasterUserSeparator(),
'sieveEnabled' => $this->getSieveEnabled(),
'sieveUser' => $this->getSieveUser(),
'sieveHost' => $this->getSieveHost(),
diff --git a/lib/Db/ProvisioningMapper.php b/lib/Db/ProvisioningMapper.php
index 0486b84a30..f47a90cd66 100644
--- a/lib/Db/ProvisioningMapper.php
+++ b/lib/Db/ProvisioningMapper.php
@@ -67,7 +67,7 @@ public function validate(array $data): Provisioning {
$exception->setField('imapHost', false);
}
if (!isset($data['imapPort']) || (int)$data['imapPort'] === 0) {
- $exception->setField('imapHost', false);
+ $exception->setField('imapPort', false);
}
if (!isset($data['imapSslMode']) || $data['imapSslMode'] === '') {
$exception->setField('imapSslMode', false);
@@ -92,6 +92,20 @@ public function validate(array $data): Provisioning {
$exception->setField('ldapAliasesAttribute', false);
}
+ $masterPasswordEnabled = (bool)($data['masterPasswordEnabled'] ?? false);
+ $masterPassword = $data['masterPassword'] ?? '';
+ $masterUser = $data['masterUser'] ?? '';
+ $masterUserSeparator = $data['masterUserSeparator'] ?? '';
+
+ if ($masterPasswordEnabled) {
+ if ($masterPassword === '') {
+ $exception->setField('masterPassword', false);
+ }
+ if ($masterUser !== '' && $masterUserSeparator === '') {
+ $exception->setField('masterUserSeparator', false);
+ }
+ }
+
if (!empty($exception->getFields())) {
throw $exception;
}
@@ -108,12 +122,6 @@ public function validate(array $data): Provisioning {
$provisioning->setSmtpHost($data['smtpHost']);
$provisioning->setSmtpPort((int)$data['smtpPort']);
$provisioning->setSmtpSslMode($data['smtpSslMode']);
-
- $provisioning->setMasterPasswordEnabled((bool)($data['masterPasswordEnabled'] ?? false));
- if (isset($data['masterPassword']) && $data['masterPassword'] !== Provisioning::MASTER_PASSWORD_PLACEHOLDER) {
- $provisioning->setMasterPassword($data['masterPassword']);
- }
-
$provisioning->setSieveEnabled((bool)$data['sieveEnabled']);
$provisioning->setSieveHost($data['sieveHost'] ?? '');
$provisioning->setSieveUser($data['sieveUser'] ?? '');
@@ -123,6 +131,20 @@ public function validate(array $data): Provisioning {
$provisioning->setLdapAliasesProvisioning($ldapAliasesProvisioning);
$provisioning->setLdapAliasesAttribute($ldapAliasesAttribute);
+ if ($masterPasswordEnabled) {
+ $provisioning->setMasterPasswordEnabled(true);
+ if ($masterPassword !== Provisioning::MASTER_PASSWORD_PLACEHOLDER) {
+ $provisioning->setMasterPassword($masterPassword);
+ }
+ $provisioning->setMasterUser($masterUser);
+ $provisioning->setMasterUserSeparator($masterUserSeparator);
+ } else {
+ $provisioning->setMasterPasswordEnabled(false);
+ $provisioning->setMasterPassword(null);
+ $provisioning->setMasterUser(null);
+ $provisioning->setMasterUserSeparator(null);
+ }
+
return $provisioning;
}
diff --git a/lib/IMAP/IMAPClientFactory.php b/lib/IMAP/IMAPClientFactory.php
index 229d09b641..476dbeca8e 100644
--- a/lib/IMAP/IMAPClientFactory.php
+++ b/lib/IMAP/IMAPClientFactory.php
@@ -14,6 +14,7 @@
use Horde_Imap_Client_Socket;
use OCA\Mail\Account;
use OCA\Mail\Cache\HordeCacheFactory;
+use OCA\Mail\Db\ProvisioningMapper;
use OCA\Mail\Events\BeforeImapClientCreated;
use OCA\Mail\Exception\ServiceException;
use OCP\AppFramework\Utility\ITimeFactory;
@@ -42,12 +43,15 @@ class IMAPClientFactory {
private ITimeFactory $timeFactory;
private HordeCacheFactory $hordeCacheFactory;
- public function __construct(ICrypto $crypto,
+ public function __construct(
+ ICrypto $crypto,
IConfig $config,
ICacheFactory $cacheFactory,
IEventDispatcher $eventDispatcher,
ITimeFactory $timeFactory,
- HordeCacheFactory $hordeCacheFactory) {
+ HordeCacheFactory $hordeCacheFactory,
+ private ProvisioningMapper $provisioningMapper
+ ) {
$this->crypto = $crypto;
$this->config = $config;
$this->cacheFactory = $cacheFactory;
@@ -85,6 +89,16 @@ public function getClient(Account $account, bool $useCache = true): Horde_Imap_C
$sslMode = false;
}
+ // Check for Dovecot master user authentication
+ $provisioningId = $account->getMailAccount()->getProvisioningId();
+ if ($provisioningId !== null) {
+ $provisioning = $this->provisioningMapper->get($provisioningId);
+ if ($provisioning !== null && !empty($provisioning->getMasterUser())) {
+ $separator = $provisioning->getMasterUserSeparator() ?? '*';
+ $user = $user . $separator . $provisioning->getMasterUser();
+ }
+ }
+
$params = [
'username' => $user,
'password' => $decryptedPassword,
diff --git a/lib/Migration/Version5007Date20260124120000.php b/lib/Migration/Version5007Date20260124120000.php
new file mode 100644
index 0000000000..f005b7e09a
--- /dev/null
+++ b/lib/Migration/Version5007Date20260124120000.php
@@ -0,0 +1,51 @@
+getTable('mail_provisionings');
+ if (!$provisioningTable->hasColumn('master_user')) {
+ $provisioningTable->addColumn('master_user', Types::STRING, [
+ 'notnull' => false,
+ 'length' => 256,
+ ]);
+ }
+ if (!$provisioningTable->hasColumn('master_user_separator')) {
+ $provisioningTable->addColumn('master_user_separator', Types::STRING, [
+ 'notnull' => false,
+ 'length' => 8,
+ ]);
+ }
+
+ return $schema;
+ }
+}
diff --git a/lib/SMTP/SmtpClientFactory.php b/lib/SMTP/SmtpClientFactory.php
index 0315414a12..072c6059cf 100644
--- a/lib/SMTP/SmtpClientFactory.php
+++ b/lib/SMTP/SmtpClientFactory.php
@@ -14,6 +14,7 @@
use Horde_Mail_Transport_Smtphorde;
use Horde_Smtp_Password_Xoauth2;
use OCA\Mail\Account;
+use OCA\Mail\Db\ProvisioningMapper;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Support\HostNameFactory;
use OCP\IConfig;
@@ -29,12 +30,16 @@ class SmtpClientFactory {
/** @var HostNameFactory */
private $hostNameFactory;
+ private ProvisioningMapper $provisioningMapper;
+
public function __construct(IConfig $config,
ICrypto $crypto,
- HostNameFactory $hostNameFactory) {
+ HostNameFactory $hostNameFactory,
+ ProvisioningMapper $provisioningMapper) {
$this->config = $config;
$this->crypto = $crypto;
$this->hostNameFactory = $hostNameFactory;
+ $this->provisioningMapper = $provisioningMapper;
}
/**
@@ -50,12 +55,25 @@ public function create(Account $account): Horde_Mail_Transport {
$decryptedPassword = $this->crypto->decrypt($mailAccount->getOutboundPassword());
}
$security = $mailAccount->getOutboundSslMode();
+
+ $username = $mailAccount->getOutboundUser();
+
+ // Check for Dovecot master user authentication
+ $provisioningId = $mailAccount->getProvisioningId();
+ if ($provisioningId !== null) {
+ $provisioning = $this->provisioningMapper->get($provisioningId);
+ if ($provisioning !== null && !empty($provisioning->getMasterUser())) {
+ $separator = $provisioning->getMasterUserSeparator() ?? '*';
+ $username = $username . $separator . $provisioning->getMasterUser();
+ }
+ }
+
$params = [
'localhost' => $this->hostNameFactory->getHostName(),
'host' => $mailAccount->getOutboundHost(),
'password' => $decryptedPassword,
'port' => $mailAccount->getOutboundPort(),
- 'username' => $mailAccount->getOutboundUser(),
+ 'username' => $username,
'secure' => $security === 'none' ? false : $security,
'timeout' => (int)$this->config->getSystemValue('app.mail.smtp.timeout', 20),
'context' => [
diff --git a/lib/Sieve/SieveClientFactory.php b/lib/Sieve/SieveClientFactory.php
index fb9264ae6b..d1c209a74e 100644
--- a/lib/Sieve/SieveClientFactory.php
+++ b/lib/Sieve/SieveClientFactory.php
@@ -11,17 +11,20 @@
use Horde\ManageSieve;
use OCA\Mail\Account;
+use OCA\Mail\Db\ProvisioningMapper;
use OCP\IConfig;
use OCP\Security\ICrypto;
class SieveClientFactory {
private ICrypto $crypto;
private IConfig $config;
+ private ProvisioningMapper $provisioningMapper;
private array $cache = [];
- public function __construct(ICrypto $crypto, IConfig $config) {
+ public function __construct(ICrypto $crypto, IConfig $config, ProvisioningMapper $provisioningMapper) {
$this->crypto = $crypto;
$this->config = $config;
+ $this->provisioningMapper = $provisioningMapper;
}
/**
@@ -38,6 +41,16 @@ public function getClient(Account $account): ManageSieve {
$password = $account->getMailAccount()->getInboundPassword();
}
+ // Check for Dovecot master user authentication
+ $provisioningId = $account->getMailAccount()->getProvisioningId();
+ if ($provisioningId !== null) {
+ $provisioning = $this->provisioningMapper->get($provisioningId);
+ if ($provisioning !== null && !empty($provisioning->getMasterUser())) {
+ $separator = $provisioning->getMasterUserSeparator() ?? '*';
+ $user = $user . $separator . $provisioning->getMasterUser();
+ }
+ }
+
if ($account->getMailAccount()->getDebug() || $this->config->getSystemValueBool('app.mail.debug')) {
$logFile = $this->config->getSystemValue('datadirectory') . '/mail-' . $account->getUserId() . '-' . $account->getId() . '-sieve.log';
} else {
diff --git a/src/components/settings/ProvisionPreview.vue b/src/components/settings/ProvisionPreview.vue
index 715f7edf61..149bbc0e24 100644
--- a/src/components/settings/ProvisionPreview.vue
+++ b/src/components/settings/ProvisionPreview.vue
@@ -15,7 +15,7 @@
{{ t('mail', 'Email: {email}', { email }) }}
{{
t('mail', 'IMAP: {user} on {host}:{port} ({ssl} encryption)', {
- user: imapUser,
+ user: imapLoginUser,
host: imapHost,
port: imapPort,
ssl: imapSslMode,
@@ -23,7 +23,7 @@
}}
{{
t('mail', 'SMTP: {user} on {host}:{port} ({ssl} encryption)', {
- user: smtpUser,
+ user: smtpLoginUser,
host: smtpHost,
port: smtpPort,
ssl: smtpSslMode,
@@ -32,13 +32,21 @@
{{
t('mail', 'Sieve: {user} on {host}:{port} ({ssl} encryption)', {
- user: sieveUser,
+ user: sieveLoginUser,
host: sieveHost,
port: sievePort,
ssl: sieveSslMode,
})
}}
+
+
+ {{ t('mail', 'Using Dovecot master user authentication') }}
+
+
+
+ {{ t('mail', 'Using static password for all users') }}
+
@@ -117,6 +125,46 @@ export default {
sieveUser() {
return this.templates.sieveUser.replace('%USERID%', this.data.uid).replace('%EMAIL%', this.data.email)
},
+
+ masterPasswordEnabled() {
+ return this.templates.masterPasswordEnabled
+ },
+
+ masterUser() {
+ return this.templates.masterUser || ''
+ },
+
+ masterUserSeparator() {
+ return this.templates.masterUserSeparator || '*'
+ },
+
+ hasMasterUser() {
+ return this.masterUser.length > 0
+ },
+
+ imapLoginUser() {
+ const baseUser = this.imapUser
+ if (this.hasMasterUser) {
+ return baseUser + this.masterUserSeparator + this.masterUser
+ }
+ return baseUser
+ },
+
+ smtpLoginUser() {
+ const baseUser = this.smtpUser
+ if (this.hasMasterUser) {
+ return baseUser + this.masterUserSeparator + this.masterUser
+ }
+ return baseUser
+ },
+
+ sieveLoginUser() {
+ const baseUser = this.sieveUser
+ if (this.hasMasterUser) {
+ return baseUser + this.masterUserSeparator + this.masterUser
+ }
+ return baseUser
+ },
},
}
diff --git a/src/components/settings/ProvisioningSettings.vue b/src/components/settings/ProvisioningSettings.vue
index ac7be23921..f445b6ee8b 100644
--- a/src/components/settings/ProvisioningSettings.vue
+++ b/src/components/settings/ProvisioningSettings.vue
@@ -196,14 +196,46 @@
{{ t('mail', 'Use master password') }}
-
-
-
{{ t('mail', 'Master password') }}
+
+
+
+ {{ t('mail', 'Master password') }}
+
+
+ {{ t('mail', 'When only master password is set, all users will authenticate with their normal username and this static password.') }}
+
+
+
+ {{ t('mail', 'Master user (Dovecot master user)') }}
+
+
+
+
{{ t('mail', 'When master user is set, authentication will use the Dovecot master user format: user{separator}masteruser with the master password.') }}
+
+
+
+ {{ t('mail', 'Master user separator') }}
+
+
+
+
{{ t('mail', 'The separator between the user and master user') }}
+
@@ -426,6 +458,8 @@ export default {
smtpSslMode: this.setting.smtpSslMode || 'tls',
masterPasswordEnabled: this.setting.masterPasswordEnabled === true,
masterPassword: this.setting.masterPassword || '',
+ masterUser: this.setting.masterUser || '',
+ masterUserSeparator: this.setting.masterUserSeparator || '',
sieveEnabled: this.setting.sieveEnabled || '',
sieveHost: this.setting.sieveHost || '',
sievePort: this.setting.sievePort || '',
@@ -448,6 +482,10 @@ export default {
},
computed: {
+ needsMasterUserSeparator() {
+ return this.masterPasswordEnabled && this.masterUser.length > 0
+ },
+
previewTemplates() {
return {
email: this.emailTemplate,
@@ -462,6 +500,8 @@ export default {
smtpSslMode: this.smtpSslMode,
masterPasswordEnabled: this.masterPasswordEnabled,
masterPassword: this.masterPassword,
+ masterUser: this.masterUser,
+ masterUserSeparator: this.masterUserSeparator,
sieveEnabled: this.sieveEnabled,
sieveUser: this.sieveUser,
sieveHost: this.sieveHost,
@@ -497,6 +537,8 @@ export default {
smtpSslMode: this.smtpSslMode,
masterPasswordEnabled: this.masterPasswordEnabled,
masterPassword: this.masterPassword,
+ masterUser: this.masterUser,
+ masterUserSeparator: this.masterUserSeparator,
sieveEnabled: this.sieveEnabled,
sieveUser: this.sieveUser,
sieveHost: this.sieveHost,
diff --git a/tests/Integration/Framework/Caching.php b/tests/Integration/Framework/Caching.php
index d54d74714f..2c966fc35d 100644
--- a/tests/Integration/Framework/Caching.php
+++ b/tests/Integration/Framework/Caching.php
@@ -11,6 +11,7 @@
use OC\Memcache\Factory;
use OCA\Mail\Cache\HordeCacheFactory;
+use OCA\Mail\Db\ProvisioningMapper;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
@@ -60,6 +61,7 @@ public static function getImapClientFactoryAndConfiguredCacheFactory(?ICrypto $c
Server::get(IEventDispatcher::class),
Server::get(ITimeFactory::class),
Server::get(HordeCacheFactory::class),
+ Server::get(ProvisioningMapper::class),
);
return [$imapClient, $cacheFactory];
}
diff --git a/tests/Integration/IMAP/IMAPClientFactoryTest.php b/tests/Integration/IMAP/IMAPClientFactoryTest.php
index 0d60515db8..75ed255580 100644
--- a/tests/Integration/IMAP/IMAPClientFactoryTest.php
+++ b/tests/Integration/IMAP/IMAPClientFactoryTest.php
@@ -17,6 +17,7 @@
use OCA\Mail\Account;
use OCA\Mail\Cache\HordeCacheFactory;
use OCA\Mail\Db\MailAccount;
+use OCA\Mail\Db\ProvisioningMapper;
use OCA\Mail\IMAP\HordeImapClient;
use OCA\Mail\IMAP\IMAPClientFactory;
use OCA\Mail\Tests\Integration\Framework\Caching;
@@ -44,6 +45,7 @@ class IMAPClientFactoryTest extends TestCase {
private IEventDispatcher|MockObject $eventDispatcher;
private ITimeFactory|MockObject $timeFactory;
private HordeCacheFactory|MockObject $hordeCacheFactory;
+ private ProvisioningMapper|MockObject $provisioningMapper;
protected function setUp(): void {
parent::setUp();
@@ -54,6 +56,7 @@ protected function setUp(): void {
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->hordeCacheFactory = $this->createMock(HordeCacheFactory::class);
+ $this->provisioningMapper = $this->createMock(ProvisioningMapper::class);
$this->factory = new IMAPClientFactory(
$this->crypto,
@@ -62,6 +65,7 @@ protected function setUp(): void {
$this->eventDispatcher,
$this->timeFactory,
$this->hordeCacheFactory,
+ $this->provisioningMapper,
);
}
diff --git a/tests/Integration/Sieve/SieveClientFactoryTest.php b/tests/Integration/Sieve/SieveClientFactoryTest.php
index 8218228251..25daf0afbc 100644
--- a/tests/Integration/Sieve/SieveClientFactoryTest.php
+++ b/tests/Integration/Sieve/SieveClientFactoryTest.php
@@ -13,6 +13,7 @@
use Horde\ManageSieve;
use OCA\Mail\Account;
use OCA\Mail\Db\MailAccount;
+use OCA\Mail\Db\ProvisioningMapper;
use OCA\Mail\Sieve\SieveClientFactory;
use OCP\IConfig;
use OCP\Security\ICrypto;
@@ -26,6 +27,9 @@ class SieveClientFactoryTest extends TestCase {
/** @var IConfig|MockObject */
private $config;
+ /** @var ProvisioningMapper|MockObject */
+ private $provisioningMapper;
+
/** @var SieveClientFactory */
private $factory;
@@ -34,6 +38,7 @@ protected function setUp(): void {
$this->crypto = $this->createMock(ICrypto::class);
$this->config = $this->createMock(IConfig::class);
+ $this->provisioningMapper = $this->createMock(ProvisioningMapper::class);
$this->config->method('getSystemValueInt')
->willReturnMap([
@@ -46,7 +51,7 @@ protected function setUp(): void {
['app.mail.debug', false, false],
]);
- $this->factory = new SieveClientFactory($this->crypto, $this->config);
+ $this->factory = new SieveClientFactory($this->crypto, $this->config, $this->provisioningMapper);
}
/**
diff --git a/tests/Unit/Db/ProvisioningMapperTest.php b/tests/Unit/Db/ProvisioningMapperTest.php
new file mode 100644
index 0000000000..f28fba44c3
--- /dev/null
+++ b/tests/Unit/Db/ProvisioningMapperTest.php
@@ -0,0 +1,177 @@
+mapper = new ProvisioningMapper(
+ $this->createMock(IDBConnection::class),
+ new NullLogger(),
+ );
+ }
+
+ public function testValidateEmptyHost(): void {
+ $data = [
+ 'provisioningDomain' => 'static.test',
+ 'emailTemplate' => '%USERID%@static.test',
+ 'imapUser' => '%EMAIL%',
+ 'imapHost' => '',
+ 'imapPort' => '143',
+ 'imapSslMode' => 'none',
+ 'smtpUser' => '%EMAIL%',
+ 'smtpHost' => '',
+ 'smtpPort' => '25',
+ 'smtpSslMode' => 'none',
+ ];
+
+ $exceptionWasThrown = false;
+
+ try {
+ $this->mapper->validate($data);
+ } catch (ValidationException $e) {
+ $exceptionWasThrown = true;
+ $fields = $e->getFields();
+ $this->assertCount(2, $fields);
+ $this->assertArrayHasKey('imapHost', $fields);
+ $this->assertArrayHasKey('smtpHost', $fields);
+ }
+
+ $this->assertTrue($exceptionWasThrown);
+ }
+
+ public function testValidateLdapAliasesProvisioningNeedsAttribute(): void {
+ $data = [
+ 'provisioningDomain' => 'static.test',
+ 'emailTemplate' => '%USERID%@static.test',
+ 'imapUser' => '%EMAIL%',
+ 'imapHost' => 'static.test',
+ 'imapPort' => '143',
+ 'imapSslMode' => 'none',
+ 'smtpUser' => '%EMAIL%',
+ 'smtpHost' => 'static.test',
+ 'smtpPort' => '25',
+ 'smtpSslMode' => 'none',
+ 'ldapAliasesProvisioning' => true,
+ 'ldapAliasesAttribute' => ''
+ ];
+
+ $exceptionWasThrown = false;
+
+ try {
+ $this->mapper->validate($data);
+ } catch (ValidationException $e) {
+ $exceptionWasThrown = true;
+ $fields = $e->getFields();
+ $this->assertCount(1, $fields);
+ $this->assertArrayHasKey('ldapAliasesAttribute', $fields);
+ }
+
+ $this->assertTrue($exceptionWasThrown);
+ }
+
+ public function testValidateMasterPasswordNeedsPassword(): void {
+ $data = [
+ 'provisioningDomain' => 'static.test',
+ 'emailTemplate' => '%USERID%@static.test',
+ 'imapUser' => '%EMAIL%',
+ 'imapHost' => 'static.test',
+ 'imapPort' => '143',
+ 'imapSslMode' => 'none',
+ 'smtpUser' => '%EMAIL%',
+ 'smtpHost' => 'static.test',
+ 'smtpPort' => '25',
+ 'smtpSslMode' => 'none',
+ 'masterPasswordEnabled' => true,
+ 'masterPassword' => '',
+ ];
+
+ $exceptionWasThrown = false;
+
+ try {
+ $this->mapper->validate($data);
+ } catch (ValidationException $e) {
+ $exceptionWasThrown = true;
+ $fields = $e->getFields();
+ $this->assertCount(1, $fields);
+ $this->assertArrayHasKey('masterPassword', $fields);
+ }
+
+ $this->assertTrue($exceptionWasThrown);
+ }
+
+ public function testValidateMasterPasswordWitUserNeedsPasswordAndSeparator(): void {
+ $data = [
+ 'provisioningDomain' => 'static.test',
+ 'emailTemplate' => '%USERID%@static.test',
+ 'imapUser' => '%EMAIL%',
+ 'imapHost' => 'static.test',
+ 'imapPort' => '143',
+ 'imapSslMode' => 'none',
+ 'smtpUser' => '%EMAIL%',
+ 'smtpHost' => 'static.test',
+ 'smtpPort' => '25',
+ 'smtpSslMode' => 'none',
+ 'masterPasswordEnabled' => true,
+ 'masterPassword' => '',
+ 'masterUser' => 'master',
+ 'masterUserSeparator' => '',
+ ];
+
+ $exceptionWasThrown = false;
+
+ try {
+ $this->mapper->validate($data);
+ } catch (ValidationException $e) {
+ $exceptionWasThrown = true;
+ $fields = $e->getFields();
+ $this->assertCount(2, $fields);
+ $this->assertArrayHasKey('masterPassword', $fields);
+ $this->assertArrayHasKey('masterUserSeparator', $fields);
+ }
+
+ $this->assertTrue($exceptionWasThrown);
+ }
+
+ public function testValidateKeepMasterPasswordSkipPlaceholder(): void {
+ $data = [
+ 'provisioningDomain' => 'static.test',
+ 'emailTemplate' => '%USERID%@static.test',
+ 'imapUser' => '%EMAIL%',
+ 'imapHost' => 'static.test',
+ 'imapPort' => '143',
+ 'imapSslMode' => 'none',
+ 'smtpUser' => '%EMAIL%',
+ 'smtpHost' => 'static.test',
+ 'smtpPort' => '25',
+ 'smtpSslMode' => 'none',
+ 'sieveEnabled' => false,
+ 'masterPasswordEnabled' => true,
+ 'masterPassword' => Provisioning::MASTER_PASSWORD_PLACEHOLDER,
+ ];
+
+ $provisioning = $this->mapper->validate($data);
+
+ $this->assertTrue($provisioning->getMasterPasswordEnabled());
+ $this->assertNull($provisioning->getMasterPassword());
+ }
+
+}
diff --git a/tests/Unit/SMTP/SmtpClientFactoryTest.php b/tests/Unit/SMTP/SmtpClientFactoryTest.php
index 1e9e918874..f778debd5d 100644
--- a/tests/Unit/SMTP/SmtpClientFactoryTest.php
+++ b/tests/Unit/SMTP/SmtpClientFactoryTest.php
@@ -13,6 +13,7 @@
use Horde_Mail_Transport_Smtphorde;
use OCA\Mail\Account;
use OCA\Mail\Db\MailAccount;
+use OCA\Mail\Db\ProvisioningMapper;
use OCA\Mail\SMTP\SmtpClientFactory;
use OCA\Mail\Support\HostNameFactory;
use OCP\IConfig;
@@ -29,6 +30,9 @@ class SmtpClientFactoryTest extends TestCase {
/** @var HostNameFactory|MockObject */
private $hostNameFactory;
+ /** @var ProvisioningMapper|MockObject */
+ private $provisioningMapper;
+
/** @var SmtpClientFactory */
private $factory;
@@ -38,8 +42,9 @@ protected function setUp(): void {
$this->config = $this->createMock(IConfig::class);
$this->crypto = $this->createMock(ICrypto::class);
$this->hostNameFactory = $this->createMock(HostNameFactory::class);
+ $this->provisioningMapper = $this->createMock(ProvisioningMapper::class);
- $this->factory = new SmtpClientFactory($this->config, $this->crypto, $this->hostNameFactory);
+ $this->factory = new SmtpClientFactory($this->config, $this->crypto, $this->hostNameFactory, $this->provisioningMapper);
}
public function testSmtpTransport() {