Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Christoph Wurst 2022-11-16 18:42:25 +01:00
Родитель 6a3ac8d507
Коммит b70e090503
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CC42AC2A7F0E56D8
13 изменённых файлов: 610 добавлений и 8 удалений

Просмотреть файл

@ -7,6 +7,15 @@ Then open the Mail app from the app menu.
## Configuration
### Local IMAP and SMTP servers
By default, Nextcloud does not allow local hostnames and IP addresses as remote servers. This includes IMAP, SMTP and Sieve servers
like `localhost`, `mx.local` and `10.0.0.3`. This check can be disabled with via config/config.php
```php
'allow_local_remote_servers' => true,
```
### Attachment size limit
Admins can prevent users from attaching large attachments to their emails. Users will be asked to use link shares instead.

Просмотреть файл

@ -43,6 +43,7 @@ use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
use OCA\Mail\Service\SetupService;
use OCA\Mail\Service\Sync\SyncService;
use OCA\Mail\Validation\RemoteHostValidator;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
@ -62,6 +63,7 @@ class AccountsController extends Controller {
private IMailManager $mailManager;
private SyncService $syncService;
private IConfig $config;
private RemoteHostValidator $hostValidator;
public function __construct(string $appName,
IRequest $request,
@ -74,7 +76,8 @@ class AccountsController extends Controller {
SetupService $setup,
IMailManager $mailManager,
SyncService $syncService,
IConfig $config
IConfig $config,
RemoteHostValidator $hostValidator
) {
parent::__construct($appName, $request);
$this->accountService = $accountService;
@ -87,6 +90,7 @@ class AccountsController extends Controller {
$this->mailManager = $mailManager;
$this->syncService = $syncService;
$this->config = $config;
$this->hostValidator = $hostValidator;
}
/**
@ -162,6 +166,26 @@ class AccountsController extends Controller {
} catch (ClientException $e) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if (!$this->hostValidator->isValid($imapHost)) {
return MailJsonResponse::fail(
[
'error' => 'CONNECTION_ERROR',
'service' => 'IMAP',
'host' => $imapHost,
'port' => $imapPort,
],
);
}
if (!$this->hostValidator->isValid($smtpHost)) {
return MailJsonResponse::fail(
[
'error' => 'CONNECTION_ERROR',
'service' => 'SMTP',
'host' => $smtpHost,
'port' => $smtpPort,
],
);
}
try {
return MailJsonResponse::success(
@ -318,6 +342,26 @@ class AccountsController extends Controller {
$this->logger->info('Creating account disabled by admin.');
return MailJsonResponse::error('Could not create account');
}
if (!$this->hostValidator->isValid($imapHost)) {
return MailJsonResponse::fail(
[
'error' => 'CONNECTION_ERROR',
'service' => 'IMAP',
'host' => $imapHost,
'port' => $imapPort,
],
);
}
if (!$this->hostValidator->isValid($smtpHost)) {
return MailJsonResponse::fail(
[
'error' => 'CONNECTION_ERROR',
'service' => 'SMTP',
'host' => $smtpHost,
'port' => $smtpPort,
],
);
}
try {
return MailJsonResponse::success(
$this->setup->createNewAccount($accountName, $emailAddress, $imapHost, $imapPort, $imapSslMode, $imapUser, $imapPassword, $smtpHost, $smtpPort, $smtpSslMode, $smtpUser, $smtpPassword, $this->currentUserId, $authMethod), Http::STATUS_CREATED

Просмотреть файл

@ -31,6 +31,7 @@ use OCA\Mail\Http\JsonResponse;
use OCA\Mail\Service\AutoConfig\ConnectivityTester;
use OCA\Mail\Service\AutoConfig\IspDb;
use OCA\Mail\Service\AutoConfig\MxRecord;
use OCA\Mail\Validation\RemoteHostValidator;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\IRequest;
@ -40,15 +41,18 @@ class AutoConfigController extends Controller {
private IspDb $ispDb;
private MxRecord $mxRecord;
private ConnectivityTester $connectivityTester;
private RemoteHostValidator $hostValidator;
public function __construct(IRequest $request,
IspDb $ispDb,
MxRecord $mxRecord,
ConnectivityTester $connectivityTester) {
ConnectivityTester $connectivityTester,
RemoteHostValidator $hostValidator) {
parent::__construct(Application::APP_ID, $request);
$this->ispDb = $ispDb;
$this->mxRecord = $mxRecord;
$this->connectivityTester = $connectivityTester;
$this->hostValidator = $hostValidator;
}
/**
@ -61,7 +65,7 @@ class AutoConfigController extends Controller {
*/
public function queryIspdb(string $email): JsonResponse {
$rfc822Address = new Horde_Mail_Rfc822_Address($email);
if (!$rfc822Address->valid) {
if (!$rfc822Address->valid || !$this->hostValidator->isValid($rfc822Address->host)) {
return JsonResponse::fail('Invalid email address', Http::STATUS_UNPROCESSABLE_ENTITY)
->cacheFor(60 * 60, false, true);
}
@ -79,7 +83,7 @@ class AutoConfigController extends Controller {
*/
public function queryMx(string $email): JsonResponse {
$rfc822Address = new Horde_Mail_Rfc822_Address($email);
if (!$rfc822Address->valid) {
if (!$rfc822Address->valid || !$this->hostValidator->isValid($rfc822Address->host)) {
return JsonResponse::fail('Invalid email address', Http::STATUS_UNPROCESSABLE_ENTITY)
->cacheFor(60 * 60, false, true);
}
@ -101,6 +105,9 @@ class AutoConfigController extends Controller {
if (!in_array($port, [143, 993, 465, 587])) {
return JsonResponse::fail('Port not allowed');
}
if (!$this->hostValidator->isValid($host)) {
return JsonResponse::success(false);
}
return JsonResponse::success(
$this->connectivityTester->canConnect($host, $port),
);

Просмотреть файл

@ -28,10 +28,13 @@ use OCA\Mail\AppInfo\Application;
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\CouldNotConnectException;
use OCA\Mail\Http\JsonResponse as MailJsonResponse;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Sieve\SieveClientFactory;
use OCA\Mail\Validation\RemoteHostValidator;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\Security\ICrypto;
@ -42,13 +45,15 @@ class SieveController extends Controller {
private SieveClientFactory $sieveClientFactory;
private string $currentUserId;
private ICrypto $crypto;
private RemoteHostValidator $hostValidator;
public function __construct(IRequest $request,
string $UserId,
AccountService $accountService,
MailAccountMapper $mailAccountMapper,
SieveClientFactory $sieveClientFactory,
ICrypto $crypto
ICrypto $crypto,
RemoteHostValidator $hostValidator
) {
parent::__construct(Application::APP_ID, $request);
$this->currentUserId = $UserId;
@ -56,6 +61,7 @@ class SieveController extends Controller {
$this->mailAccountMapper = $mailAccountMapper;
$this->sieveClientFactory = $sieveClientFactory;
$this->crypto = $crypto;
$this->hostValidator = $hostValidator;
}
/**
@ -132,6 +138,17 @@ class SieveController extends Controller {
string $sievePassword,
string $sieveSslMode
): JSONResponse {
if (!$this->hostValidator->isValid($sieveHost)) {
return MailJsonResponse::fail(
[
'error' => 'CONNECTION_ERROR',
'service' => 'ManageSieve',
'host' => $sieveHost,
'port' => $sievePort,
],
Http::STATUS_UNPROCESSABLE_ENTITY
);
}
$mailAccount = $this->mailAccountMapper->find($this->currentUserId, $id);
if ($sieveEnabled === false) {

Просмотреть файл

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/*
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\Mail\Net;
use function filter_var;
use function in_array;
use function strrchr;
use function substr;
use function substr_count;
/**
* Classifier for network hostnames
*/
class HostnameClassifier {
private const LOCAL_TOPLEVEL_DOMAINS = [
'local',
'localhost',
'intranet',
'internal',
'private',
'corp',
'home',
'lan',
];
/**
* Check host identifier for local hostname
*
* IP addresses are not considered local. Use the IpAddressClassifier for those.
*
* @param string $hostname
*
* @return bool
*/
public function isLocalHostname(string $hostname): bool {
// Disallow local network top-level domains from RFC 6762
$topLevelDomain = substr((strrchr($hostname, '.') ?: ''), 1);
if (in_array($topLevelDomain, self::LOCAL_TOPLEVEL_DOMAINS)) {
return true;
}
// Disallow hostname only
if (substr_count($hostname, '.') === 0 && !filter_var($hostname, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return true;
}
return false;
}
}

Просмотреть файл

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/*
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\Mail\Net;
use IPLib\Address\IPv6;
use IPLib\Factory;
use IPLib\ParseStringFlag;
use Symfony\Component\HttpFoundation\IpUtils;
use function filter_var;
/**
* Classifier for IP addresses
*/
class IpAddressClassifier {
private const LOCAL_ADDRESS_RANGES = [
'100.64.0.0/10', // See RFC 6598
'192.0.0.0/24', // See RFC 6890
];
/**
* Check host identifier for local IPv4 and IPv6 address ranges
*
* Hostnames are not considered local. Use the HostnameClassifier for those.
*
* @param string $ip
*
* @return bool
*/
public function isLocalAddress(string $ip): bool {
$parsedIp = Factory::parseAddressString(
$ip,
ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
);
if ($parsedIp === null) {
/* Not an IP */
return false;
}
/* Replace by normalized form */
if ($parsedIp instanceof IPv6) {
$ip = (string)($parsedIp->toIPv4() ?? $parsedIp);
} else {
$ip = (string)$parsedIp;
}
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
/* Range address */
return true;
}
if (IpUtils::checkIp($ip, self::LOCAL_ADDRESS_RANGES)) {
/* Within local range */
return true;
}
return false;
}
}

Просмотреть файл

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/*
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\Mail\Validation;
use OCA\Mail\Net\HostnameClassifier;
use OCA\Mail\Net\IpAddressClassifier;
use OCP\IConfig;
use Psr\Log\LoggerInterface;
use function strpos;
use function strtolower;
use function substr;
use function urldecode;
/**
* @internal
*/
class RemoteHostValidator {
private IConfig $config;
private HostnameClassifier $hostnameClassifier;
private IpAddressClassifier $ipAddressClassifier;
private LoggerInterface $logger;
public function __construct(IConfig $config,
HostnameClassifier $hostnameClassifier,
IpAddressClassifier $ipAddressClassifier,
LoggerInterface $logger) {
$this->config = $config;
$this->hostnameClassifier = $hostnameClassifier;
$this->ipAddressClassifier = $ipAddressClassifier;
$this->logger = $logger;
}
public function isValid(string $host): bool {
if ($this->config->getSystemValueBool('allow_local_remote_servers', false)) {
return true;
}
$host = idn_to_utf8(strtolower(urldecode($host)));
// Remove brackets from IPv6 addresses
if (strpos($host, '[') === 0 && substr($host, -1) === ']') {
$host = substr($host, 1, -1);
}
if ($this->hostnameClassifier->isLocalHostname($host)
|| $this->ipAddressClassifier->isLocalAddress($host)) {
$this->logger->warning("Host $host was not connected to because it violates local access rules");
return false;
}
return true;
}
}

Просмотреть файл

@ -26,9 +26,12 @@
<referencedClass name="Doctrine\DBAL\Platforms\SqlitePlatform" />
<referencedClass name="Doctrine\DBAL\Types\Type" />
<referencedClass name="Doctrine\DBAL\Types\Types" />
<referencedClass name="IPLib\Factory" />
<referencedClass name="IPLib\Address\IPv6" />
<referencedClass name="OC" />
<referencedClass name="OC\Security\CSP\ContentSecurityPolicyNonceManager" />
<referencedClass name="Psr\Http\Client\ClientExceptionInterface" />
<referencedClass name="Symfony\Component\HttpFoundation\IpUtils" />
</errorLevel>
</UndefinedClass>
<UndefinedDocblockClass>

Просмотреть файл

@ -35,6 +35,7 @@ use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
use OCA\Mail\Service\SetupService;
use OCA\Mail\Service\Sync\SyncService;
use OCA\Mail\Validation\RemoteHostValidator;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
@ -86,6 +87,8 @@ class AccountsControllerTest extends TestCase {
/** @var SyncService|MockObject */
private $syncService;
/** @var RemoteHostValidator|MockObject */
private $hostValidator;
protected function setUp(): void {
parent::setUp();
@ -102,6 +105,8 @@ class AccountsControllerTest extends TestCase {
$this->mailManager = $this->createMock(IMailManager::class);
$this->syncService = $this->createMock(SyncService::class);
$this->config = $this->createMock(IConfig::class);
$this->hostValidator = $this->createMock(RemoteHostValidator::class);
$this->hostValidator->method('isValid')->willReturn(true);
$this->controller = new AccountsController(
$this->appName,
@ -115,7 +120,8 @@ class AccountsControllerTest extends TestCase {
$this->setupService,
$this->mailManager,
$this->syncService,
$this->config
$this->config,
$this->hostValidator,
);
$this->account = $this->createMock(Account::class);
$this->accountId = 123;

Просмотреть файл

@ -31,8 +31,13 @@ use OCA\Mail\Db\MailAccount;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\CouldNotConnectException;
use OCA\Mail\Tests\Integration\TestCase;
use OCA\Mail\Validation\RemoteHostValidator;
use PHPUnit\Framework\MockObject\MockObject;
class SieveControllerTest extends TestCase {
/** @var RemoteHostValidator|MockObject */
private $remoteHostValidator;
/** @var ServiceMockObject */
private $serviceMock;
@ -42,9 +47,16 @@ class SieveControllerTest extends TestCase {
protected function setUp(): void {
parent::setUp();
$this->remoteHostValidator = $this->createMock(RemoteHostValidator::class);
$this->remoteHostValidator->method('isValid')->willReturn(true);
$this->serviceMock = $this->createServiceMock(
SieveController::class,
['UserId' => '1']
[
'hostValidator' => $this->remoteHostValidator,
'UserId' => '1',
]
);
$this->sieveController = $this->serviceMock->getService();
}
@ -59,6 +71,8 @@ class SieveControllerTest extends TestCase {
->method('save');
$response = $this->sieveController->updateAccount(2, false, '', 0, '', '', '');
$this->assertEquals(200, $response->getStatus());
$this->assertEquals(false, $response->getData()['sieveEnabled']);
}
@ -72,6 +86,8 @@ class SieveControllerTest extends TestCase {
->method('save');
$response = $this->sieveController->updateAccount(2, true, 'localhost', 4190, 'user', 'password', '');
$this->assertEquals(200, $response->getStatus());
$this->assertEquals(true, $response->getData()['sieveEnabled']);
}
@ -89,6 +105,8 @@ class SieveControllerTest extends TestCase {
->method('save');
$response = $this->sieveController->updateAccount(2, true, 'localhost', 4190, '', '', '');
$this->assertEquals(200, $response->getStatus());
$this->assertEquals(true, $response->getData()['sieveEnabled']);
}
@ -107,7 +125,7 @@ class SieveControllerTest extends TestCase {
->method('createClient')
->willThrowException(new Exception('Computer says no'));
$this->sieveController->updateAccount(2, true, 'localhost', 4190, 'user', 'password', '');
$response = $this->sieveController->updateAccount(2, true, 'localhost', 4190, 'user', 'password', '');
}
public function testGetActiveScript(): void {
@ -126,6 +144,8 @@ class SieveControllerTest extends TestCase {
->willReturn(new Account($mailAccount));
$response = $this->sieveController->getActiveScript(2);
$this->assertEquals(200, $response->getStatus());
$this->assertEquals(['scriptName' => '', 'script' => ''], $response->getData());
}
@ -161,6 +181,8 @@ class SieveControllerTest extends TestCase {
->willReturn(new Account($mailAccount));
$response = $this->sieveController->updateActiveScript(2, 'sieve script');
$this->assertEquals(200, $response->getStatus());
$this->assertEquals([], $response->getData());
}
}

Просмотреть файл

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/*
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\Mail\Tests\Unit\Net;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Net\HostnameClassifier;
class HostnameClassifierTest extends TestCase {
private HostnameClassifier $classifier;
protected function setUp(): void {
parent::setUp();
$this->classifier = new HostnameClassifier();
}
public function localHostnamesData():array {
return [
['localhost'],
['localHost'],
['random-host'],
['another-host.local'],
['service.localhost'],
['randomdomain.internal'],
];
}
/**
* @dataProvider localHostnamesData
*/
public function testLocalHostname(string $host): void {
$isLocal = $this->classifier->isLocalHostname($host);
self::assertTrue($isLocal);
}
public function publicHostnamesData(): array {
return [
['example.com'],
['example.net'],
['example.org'],
['host.domain'],
['cloud.domain.tld'],
];
}
/**
* @dataProvider publicHostnamesData
*/
public function testPublicHostname(string $host): void {
$isLocal = $this->classifier->isLocalHostname($host);
self::assertFalse($isLocal);
}
}

Просмотреть файл

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/*
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\Mail\Tests\Unit\Net;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Net\IpAddressClassifier;
class IpAddressClassifierTest extends TestCase {
private IpAddressClassifier $classifier;
protected function setUp(): void {
parent::setUp();
$this->classifier = new IpAddressClassifier();
}
public function publicIpAddressData(): array {
return [
['8.8.8.8'],
['8.8.4.4'],
['2001:4860:4860::8888'],
['2001:4860:4860::8844'],
];
}
/**
* @dataProvider publicIpAddressData
*/
public function testPublicAddress(string $ip): void {
$isLocal = $this->classifier->isLocalAddress($ip);
self::assertFalse($isLocal);
}
public function localIpAddressData(): array {
return [
['192.168.0.1'],
['fe80::200:5aee:feaa:20a2'],
['0:0:0:0:0:ffff:10.0.0.1'],
['0:0:0:0:0:ffff:127.0.0.0'],
['10.0.0.1'],
['::'],
['::1'],
['100.100.100.200'],
['192.0.0.1'],
];
}
/**
* @dataProvider localIpAddressData
*/
public function testLocalAddress(string $ip): void {
$isLocal = $this->classifier->isLocalAddress($ip);
self::assertTrue($isLocal);
}
}

Просмотреть файл

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
/*
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\Mail\Tests\Unit;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Net\HostnameClassifier;
use OCA\Mail\Net\IpAddressClassifier;
use OCA\Mail\Validation\RemoteHostValidator;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
class RemoteHostValidatorTest extends TestCase {
/** @var IConfig|IConfig&MockObject|MockObject */
private IConfig $config;
/** @var HostnameClassifier|HostnameClassifier&MockObject|MockObject */
private HostnameClassifier $hostnameClassifier;
/** @var IpAddressClassifier|IpAddressClassifier&MockObject|MockObject */
private IpAddressClassifier $ipAddressClassifier;
/** @var MockObject|LoggerInterface|LoggerInterface&MockObject */
private LoggerInterface $logger;
private RemoteHostValidator $validator;
protected function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->hostnameClassifier = $this->createMock(HostnameClassifier::class);
$this->ipAddressClassifier = $this->createMock(IpAddressClassifier::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->validator = new RemoteHostValidator(
$this->config,
$this->hostnameClassifier,
$this->ipAddressClassifier,
$this->logger,
);
}
public function testValid(): void {
$host = 'nextcloud.com';
$this->hostnameClassifier
->method('isLocalHostname')
->with($host)
->willReturn(false);
$this->ipAddressClassifier
->method('isLocalAddress')
->with($host)
->willReturn(false);
$valid = $this->validator->isValid($host);
self::assertTrue($valid);
}
public function testLocalHostname(): void {
$host = 'localhost';
$this->hostnameClassifier
->method('isLocalHostname')
->with($host)
->willReturn(true);
$this->ipAddressClassifier
->method('isLocalAddress')
->with($host)
->willReturn(false);
$valid = $this->validator->isValid($host);
self::assertFalse($valid);
}
public function testLocalAddress(): void {
$host = '10.0.0.10';
$this->hostnameClassifier
->method('isLocalHostname')
->with($host)
->willReturn(false);
$this->ipAddressClassifier
->method('isLocalAddress')
->with($host)
->willReturn(true);
$valid = $this->validator->isValid($host);
self::assertFalse($valid);
}
}