Validate remote hosts
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Родитель
6a3ac8d507
Коммит
b70e090503
|
@ -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);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче