feat(settings): add option to allow self-signed mail certificates

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2026-01-14 15:43:40 +01:00
parent 265d2c3f87
commit 7d21350040
5 changed files with 87 additions and 19 deletions

View file

@ -59,6 +59,7 @@ class MailSettingsController extends Controller {
?bool $mail_smtpauth,
string $mail_smtpport,
string $mail_sendmailmode,
?bool $mail_noverify = null,
): DataResponse {
$mail_smtpauth = $mail_smtpauth == '1';
@ -76,6 +77,15 @@ class MailSettingsController extends Controller {
$configs[$key] = empty($value) ? null : $value;
}
if ($mail_noverify !== null) {
$options = $this->config->getSystemValue('mail_smtpstreamoptions', []);
$options['ssl'] ??= [];
$options['ssl']['allow_self_signed'] = $mail_noverify;
$options['ssl']['verify_peer'] = !$mail_noverify;
$options['ssl']['verify_peer_name'] = !$mail_noverify;
$configs['mail_smtpstreamoptions'] = $options;
}
// Delete passwords from config in case no auth is specified
if (!$mail_smtpauth) {
$configs['mail_smtpname'] = null;

View file

@ -17,7 +17,7 @@ use OCP\Settings\IDelegatedSettings;
use OCP\Util;
class Mail implements IDelegatedSettings {
public function __construct(
private IConfig $config,
private IL10N $l,
@ -67,6 +67,7 @@ class Mail implements IDelegatedSettings {
$smtpMode = 'smtp';
}
$smtpOptions = $this->config->getSystemValue('mail_smtpstreamoptions', []);
$this->initialState->provideInitialState('settingsAdminMailConfig', [
'mail_domain' => $this->config->getSystemValue('mail_domain', ''),
'mail_from_address' => $this->config->getSystemValue('mail_from_address', ''),
@ -78,6 +79,8 @@ class Mail implements IDelegatedSettings {
'mail_smtpname' => $this->config->getSystemValue('mail_smtpname', ''),
'mail_smtppassword' => $smtpPassword,
'mail_sendmailmode' => $this->config->getSystemValue('mail_sendmailmode', 'smtp'),
'mail_noverify' => $smtpOptions['ssl']['allow_self_signed'] ?? false,
]);
Util::addScript('settings', 'vue-settings-admin-mail');

View file

@ -39,6 +39,8 @@ const initialConfig = loadState<{
mail_smtpname: string
mail_smtppassword: string
mail_sendmailmode: string
mail_noverify: boolean
}>('settings', 'settingsAdminMailConfig')
const mailConfig = ref({ ...initialConfig })
@ -159,6 +161,10 @@ async function onSubmit() {
:input-label="t('settings', 'Sendmail mode')"
:options="settingsAdminMail.smtpSendmailModeOptions"
required />
<NcCheckboxRadioSwitch v-model="mailConfig.mail_noverify" type="switch">
{{ t('settings', 'Disable certificate verification (insecure)') }}
</NcCheckboxRadioSwitch>
</NcFormBox>
<NcFormGroup :label="t('settings', 'From address')">

View file

@ -95,7 +95,7 @@ class MailSettingsControllerTest extends \Test\TestCase {
'smtp',
'ssl',
'mx.nextcloud.org',
'1',
true,
'25',
'smtp'
);
@ -108,7 +108,7 @@ class MailSettingsControllerTest extends \Test\TestCase {
'smtp',
'ssl',
'mx.nextcloud.org',
'0',
false,
'25',
'smtp'
);
@ -116,16 +116,30 @@ class MailSettingsControllerTest extends \Test\TestCase {
}
public function testStoreCredentials(): void {
$calls = [];
$this->config
->expects($this->once())
->method('setSystemValues')
->with([
'mail_smtpname' => 'UsernameToStore',
'mail_smtppassword' => 'PasswordToStore',
]);
->expects($this->exactly(2))
->method('setSystemValue')
->willReturnCallback(function (string $key, ?string $value) use (&$calls): void {
$calls[] = [$key, $value];
});
$response = $this->mailController->storeCredentials('UsernameToStore', 'PasswordToStore');
$this->assertSame(Http::STATUS_OK, $response->getStatus());
self::assertEqualsCanonicalizing([
['mail_smtpname', 'UsernameToStore'],
['mail_smtppassword', 'PasswordToStore'],
], $calls);
}
public function testStoreCredentialsWithoutPassword(): void {
$this->config
->expects($this->exactly(1))
->method('setSystemValue')
->with('mail_smtpname', 'UsernameToStore');
$response = $this->mailController->storeCredentials('UsernameToStore', null);
$this->assertSame(Http::STATUS_OK, $response->getStatus());
}
public function testSendTestMail(): void {

View file

@ -8,9 +8,11 @@ namespace OCA\Settings\Tests\Settings\Admin;
use OCA\Settings\Settings\Admin\Mail;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IBinaryFinder;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
@ -19,15 +21,22 @@ class MailTest extends TestCase {
private Mail $admin;
private IConfig&MockObject $config;
private IL10N&MockObject $l10n;
private IURLGenerator&MockObject $urlGenerator;
private IInitialState&MockObject $initialState;
protected function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->l10n = $this->createMock(IL10N::class);
$this->l10n->method('t')->willReturnArgument(0);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->initialState = $this->createMock(IInitialState::class);
$this->admin = new Mail(
$this->config,
$this->l10n
$this->l10n,
$this->initialState,
$this->urlGenerator,
);
}
@ -41,10 +50,12 @@ class MailTest extends TestCase {
#[\PHPUnit\Framework\Attributes\DataProvider('dataGetForm')]
public function testGetForm(bool $sendmail) {
$finder = $this->createMock(IBinaryFinder::class);
$finder->expects(self::once())
$finder->expects(self::atLeastOnce())
->method('findBinaryPath')
->with('sendmail')
->willReturn($sendmail ? '/usr/bin/sendmail': false);
->willReturnMap([
['qmail', false],
['sendmail', $sendmail ? '/usr/bin/sendmail' : false],
]);
$this->overwriteService(IBinaryFinder::class, $finder);
$this->config
@ -63,11 +74,37 @@ class MailTest extends TestCase {
['mail_sendmailmode', 'smtp', 'smtp'],
]);
$initialState = [];
$this->initialState->method('provideInitialState')
->willReturnCallback(function (string $key, array $data) use (&$initialState): void {
$initialState[$key] = $data;
});
$expected = new TemplateResponse(
'settings',
'settings/admin/additional-mail',
[
'sendmail_is_available' => $sendmail,
renderAs: ''
);
$this->assertEquals($expected, $this->admin->getForm());
self::assertEquals([
'settingsAdminMail' => [
'configIsReadonly' => false,
'docUrl' => '',
'smtpModeOptions' => [
['label' => 'SMTP', 'id' => 'smtp'],
...($sendmail ? [['label' => 'Sendmail', 'id' => 'sendmail']] : [])
],
'smtpEncryptionOptions' => [
['label' => 'None / STARTTLS', 'id' => ''],
['label' => 'SSL/TLS', 'id' => 'ssl'],
],
'smtpSendmailModeOptions' => [
['label' => 'smtp (-bs)', 'id' => 'smtp'],
['label' => 'pipe (-t -i)', 'id' => 'pipe'],
],
],
'settingsAdminMailConfig' => [
'mail_domain' => 'mx.nextcloud.com',
'mail_from_address' => 'no-reply@nextcloud.com',
'mail_smtpmode' => 'smtp',
@ -78,11 +115,9 @@ class MailTest extends TestCase {
'mail_smtpname' => 'smtp.sender.com',
'mail_smtppassword' => '********',
'mail_sendmailmode' => 'smtp',
'mail_noverify' => false,
],
''
);
$this->assertEquals($expected, $this->admin->getForm());
], $initialState);
}
public function testGetSection(): void {