Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Christoph Wurst 2019-11-15 14:26:56 +01:00
Родитель 26c13bc035
Коммит ad29c8a46c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CC42AC2A7F0E56D8
41 изменённых файлов: 2216 добавлений и 398 удалений

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

@ -12,7 +12,7 @@
- **🙈 Were not reinventing the wheel!** Based on the great [Horde](http://horde.org) libraries.
- **📬 Want to host your own mail server?** We dont have to reimplement this as you could set up [Mail-in-a-Box](https://mailinabox.email)!
]]></description>
<version>0.19.1</version>
<version>0.20.0</version>
<licence>agpl</licence>
<author>Christoph Wurst</author>
<author>Jan-Christoph Borchardt</author>
@ -34,10 +34,15 @@
<repair-steps>
<post-migration>
<step>OCA\Mail\Migration\FixCollectedAddresses</step>
<step>OCA\Mail\Migration\MigrateProvisioningConfig</step>
<step>OCA\Mail\Migration\ProvisionAccounts</step>
</post-migration>
</repair-steps>
<commands>
<command>OCA\Mail\Command\CreateAccount</command>
<command>OCA\Mail\Command\ExportAccount</command>
</commands>
<settings>
<admin>OCA\Mail\Settings\AdminSettings</admin>
</settings>
</info>

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

@ -113,6 +113,16 @@ return [
'url' => '/proxy',
'verb' => 'GET'
],
[
'name' => 'settings#provisioning',
'url' => '/api/settings/provisioning',
'verb' => 'POST'
],
[
'name' => 'settings#deprovision',
'url' => '/api/settings/provisioning',
'verb' => 'DELETE'
],
],
'resources' => [
'accounts' => ['url' => '/api/accounts'],

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

@ -9,52 +9,6 @@ Then open the Mail app from the app menu. Put in your mail account credentials a
Certain advanced or experimental features need to be specifically enabled in your `config.php`:
### Automatic account creation
In cases where an external user back-end is used for both your Nextcloud and your mail server you may want to have imap accounts set up automatically for your users.
### Available patterns
Two patterns are available to automatically construct credentials:
* `%USERID%`, e.g. `jan`
* `%EMAIL%`, e.g. `jan@domain.tld`
### Minimal configuration
The following minimal configuration will add such an account as soon as the user logs in. The login password is used for the IMAP and SMTP authentication.
Note: Valid values for SSL are `'none'`, `'ssl'` and `'tls'`.
```
'app.mail.accounts.default' => [
'email' => '%USERID%@domain.tld',
'imapHost' => 'imap.domain.tld',
'imapPort' => 993,
'imapSslMode' => 'ssl',
'smtpHost' => 'smtp.domain.tld',
'smtpPort' => 486,
'smtpSslMode' => 'tls',
],
```
### Advanced configuration
In case you have to tweak IMAP and SMTP username, you can do that too.
```
'app.mail.accounts.default' => [
'email' => '%USERID%@domain.tld',
'imapHost' => 'imap.domain.tld',
'imapPort' => 993,
'imapUser' => '%USERID%@domain.tld',
'imapSslMode' => 'ssl',
'smtpHost' => 'smtp.domain.tld',
'smtpPort' => 486,
'smtpUser' => '%USERID%@domain.tld',
'smtpSslMode' => 'tls',
],
```
### Timeouts
Depending on your mail host, it may be necessary to increase your IMAP and/or SMTP timeout threshold. Currently IMAP defaults to 20 seconds and SMTP defaults to 2 seconds. They can be changed as follows:

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

@ -23,6 +23,7 @@
namespace OCA\Mail\AppInfo;
use OC\Hooks\PublicEmitter;
use OCA\Mail\Contracts\IAttachmentService;
use OCA\Mail\Contracts\IAvatarService;
use OCA\Mail\Contracts\IMailManager;
@ -34,6 +35,7 @@ use OCA\Mail\Events\DraftSavedEvent;
use OCA\Mail\Events\MessageSentEvent;
use OCA\Mail\Events\SaveDraftEvent;
use OCA\Mail\Http\Middleware\ErrorMiddleware;
use OCA\Mail\Http\Middleware\ProvisioningMiddleware;
use OCA\Mail\Listener\AddressCollectionListener;
use OCA\Mail\Listener\DeleteDraftListener;
use OCA\Mail\Listener\DraftMailboxCreatorListener;
@ -50,7 +52,8 @@ use OCA\Mail\Service\MailTransmission;
use OCA\Mail\Service\UserPreferenceSevice;
use OCP\AppFramework\IAppContainer;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IContainer;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Util;
class BootstrapSingleton {
@ -108,6 +111,7 @@ class BootstrapSingleton {
$container->registerAlias('ErrorMiddleware', ErrorMiddleware::class);
$container->registerMiddleWare('ErrorMiddleware');
$container->registerMiddleWare(ProvisioningMiddleware::class);
$container->registerAlias(IGroupService::class, NextcloudGroupService::class);
}

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

@ -0,0 +1,73 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Controller;
use OCA\Mail\AppInfo\Application;
use OCA\Mail\Service\Provisioning\Manager as ProvisioningManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
class SettingsController extends Controller {
/** @var ProvisioningManager */
private $provisioningManager;
public function __construct(IRequest $request,
ProvisioningManager $provisioningManager) {
parent::__construct(Application::APP_ID, $request);
$this->provisioningManager = $provisioningManager;
}
public function provisioning(string $emailTemplate,
string $imapUser,
string $imapHost,
int $imapPort,
string $imapSslMode,
string $smtpUser,
string $smtpHost,
int $smtpPort,
string $smtpSslMode): JSONResponse {
$this->provisioningManager->newProvisioning(
$emailTemplate,
$imapUser,
$imapHost,
$imapPort,
$imapSslMode,
$smtpUser,
$smtpHost,
$smtpPort,
$smtpSslMode
);
return new JSONResponse(null);
}
public function deprovision(): JSONResponse {
$this->provisioningManager->deprovision();
return new JSONResponse(null);
}
}

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

@ -45,7 +45,7 @@ use OCP\AppFramework\Db\Entity;
* @method void setInboundSslMode(string $inboundSslMode)
* @method string getInboundUser()
* @method void setInboundUser(string $inboundUser)
* @method string getInboundPassword()
* @method string|null getInboundPassword()
* @method void setInboundPassword(string $inboundPassword)
* @method string getOutboundHost()
* @method void setOutboundHost(string $outboundHost)
@ -55,7 +55,7 @@ use OCP\AppFramework\Db\Entity;
* @method void setOutboundSslMode(string $outboundSslMode)
* @method string getOutboundUser()
* @method void setOutboundUser(string $outboundUser)
* @method string getOutboundPassword()
* @method string|null getOutboundPassword()
* @method void setOutboundPassword(string $outboundPassword)
* @method string|null getSignature()
* @method void setSignature(string|null $signature)
@ -63,6 +63,8 @@ use OCP\AppFramework\Db\Entity;
* @method void setLastMailboxSync(int $time)
* @method string getEditorMode()
* @method void setEditorMode(string $editorMode)
* @method bool getProvisioned()
* @method void setProvisioned(bool $provisioned)
*/
class MailAccount extends Entity {
@ -82,12 +84,12 @@ class MailAccount extends Entity {
protected $signature;
protected $lastMailboxSync;
protected $editorMode;
protected $provisioned;
/**
* @param array $params
*/
public function __construct(array $params=[]) {
if (isset($params['accountId'])) {
$this->setId($params['accountId']);
}
@ -131,6 +133,7 @@ class MailAccount extends Entity {
}
$this->addType('lastMailboxSync', 'integer');
$this->addType('provisioned', 'bool');
}
/**
@ -147,6 +150,7 @@ class MailAccount extends Entity {
'imapSslMode' => $this->getInboundSslMode(),
'signature' => $this->getSignature(),
'editorMode' => $this->getEditorMode(),
'provisioned' => $this->getProvisioned(),
];
if (!is_null($this->getOutboundHost())) {

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

@ -25,8 +25,12 @@ declare(strict_types=1);
namespace OCA\Mail\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUser;
class MailAccountMapper extends QBMapper {
@ -72,6 +76,24 @@ class MailAccountMapper extends QBMapper {
return $this->findEntities($query);
}
/**
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function findProvisionedAccount(IUser $user): MailAccount {
$qb = $this->db->getQueryBuilder();
$query = $qb
->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('user_id', $qb->createNamedParameter($user->getUID())),
$qb->expr()->eq('provisioned', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL))
);
return $this->findEntity($query);
}
/**
* Saves an User Account into the database
*
@ -80,12 +102,20 @@ class MailAccountMapper extends QBMapper {
* @return MailAccount
*/
public function save(MailAccount $account): MailAccount {
if (is_null($account->getId())) {
if ($account->getId() === null) {
return $this->insert($account);
} else {
$this->update($account);
return $account;
}
return $this->update($account);
}
public function deleteProvisionedAccounts(): void {
$qb = $this->db->getQueryBuilder();
$delete = $qb->delete($this->getTableName())
->where($qb->expr()->eq('provisioned', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)));
$delete->execute();
}
}

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

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Http\Middleware;
use OCA\Mail\Service\Provisioning\Manager as ProvisioningManager;
use OCP\AppFramework\Middleware;
use OCP\Authentication\Exceptions\CredentialsUnavailableException;
use OCP\Authentication\Exceptions\PasswordUnavailableException;
use OCP\Authentication\LoginCredentials\ICredentials;
use OCP\Authentication\LoginCredentials\IStore as ICredentialStore;
use OCP\ILogger;
use OCP\IUserSession;
class ProvisioningMiddleware extends Middleware {
/** @var IUserSession */
private $userSession;
/** @var ICredentialStore */
private $credentialStore;
/** @var ProvisioningManager */
private $provisioningManager;
/** @var ILogger */
private $logger;
public function __construct(IUserSession $userSession,
ICredentialStore $credentialStore,
ProvisioningManager $provisioningManager,
ILogger $logger) {
$this->userSession = $userSession;
$this->credentialStore = $credentialStore;
$this->provisioningManager = $provisioningManager;
$this->logger = $logger;
}
public function beforeController($controller, $methodName) {
$user = $this->userSession->getUser();
if ($user === null) {
// Nothing to update
return;
}
try {
$this->provisioningManager->updatePassword(
$user,
$this->credentialStore->getLoginCredentials()->getPassword()
);
} catch (CredentialsUnavailableException|PasswordUnavailableException $e) {
// Nothing to update
return;
}
}
}

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

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* Mail
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Mail\Migration;
use OCA\Mail\Service\Provisioning\Manager as ProvisioningManager;
use OCP\IConfig;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class MigrateProvisioningConfig implements IRepairStep {
/** @var ProvisioningManager */
private $provisioningManager;
/** @var IConfig */
private $config;
public function __construct(ProvisioningManager $provisioningManager,
IConfig $config) {
$this->provisioningManager = $provisioningManager;
$this->config = $config;
}
public function getName(): string {
return 'Migrate Mail provisioning config from config.php to the database';
}
public function run(IOutput $output) {
$fromConfigRaw = $this->config->getSystemValue('app.mail.accounts.default');
if ($fromConfigRaw === '') {
$output->info("No old config found");
return;
}
if ($this->provisioningManager->getConfig() !== null) {
$output->info("Mail provisioning config already set, ignoring old config");
return;
}
$this->provisioningManager->importConfig($fromConfigRaw);
$this->config->deleteSystemValue('app.mail.accounts.default');
$output->info("Config migrated. Accounts not updated yet");
}
}

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

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Migration;
use OCA\Mail\Service\Provisioning\Manager as ProvisioningManager;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class ProvisionAccounts implements IRepairStep {
/** @var ProvisioningManager */
private $provisioningManager;
public function __construct(ProvisioningManager $provisioningManager) {
$this->provisioningManager = $provisioningManager;
}
public function getName(): string {
return 'Create or update provisioned Mail accounts';
}
public function run(IOutput $output) {
$config = $this->provisioningManager->getConfig();
if ($config === null) {
$output->info("No Mail provisioning config set");
return;
}
$cnt = $this->provisioningManager->provision($config);
$output->info("$cnt accounts provisioned");
}
}

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

@ -54,7 +54,6 @@ class Version0100Date20180825194217 extends SimpleMigrationStep {
$table->addColumn('user_id', 'string', [
'notnull' => true,
'length' => 64,
'default' => '',
]);
$table->addColumn('name', 'string', [
'notnull' => false,

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

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\IDBConnection;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
class Version0190Date20191118160843 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*
* @return ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$accountsTable = $schema->getTable('mail_accounts');
$accountsTable->addColumn('provisioned', 'boolean', [
'notnull' => true,
'default' => false,
]);
$accountsTable->changeColumn('inbound_password', [
'notnull' => false,
'default' => null,
]);
return $schema;
}
}

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

@ -24,13 +24,12 @@ declare(strict_types=1);
namespace OCA\Mail\Service;
use Exception;
use OCA\Mail\Account;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Service\DefaultAccount\Manager;
use OCP\AppFramework\Db\DoesNotExistException;
use function array_map;
class AccountService {
@ -44,17 +43,12 @@ class AccountService {
*/
private $accounts;
/** @var Manager */
private $defaultAccountManager;
/** @var AliasesService */
private $aliasesService;
public function __construct(MailAccountMapper $mapper,
Manager $defaultAccountManager,
AliasesService $aliasesService) {
$this->mapper = $mapper;
$this->defaultAccountManager = $defaultAccountManager;
$this->aliasesService = $aliasesService;
}
@ -64,16 +58,9 @@ class AccountService {
*/
public function findByUserId(string $currentUserId): array {
if ($this->accounts === null) {
$accounts = array_map(function ($a) {
return $this->accounts = array_map(function ($a) {
return new Account($a);
}, $this->mapper->findByUserId($currentUserId));
$defaultAccount = $this->defaultAccountManager->getDefaultAccount();
if (!is_null($defaultAccount)) {
$accounts[] = new Account($defaultAccount);
}
$this->accounts = $accounts;
}, $this->mapper->findByUserId($currentUserId));;
}
return $this->accounts;
@ -96,13 +83,6 @@ class AccountService {
throw new DoesNotExistException("Invalid account id <$accountId>");
}
if ($accountId === Manager::ACCOUNT_ID) {
$defaultAccount = $this->defaultAccountManager->getDefaultAccount();
if (is_null($defaultAccount)) {
throw new DoesNotExistException('Default account config missing');
}
return new Account($defaultAccount);
}
return new Account($this->mapper->find($uid, $accountId));
}
@ -110,10 +90,6 @@ class AccountService {
* @param int $accountId
*/
public function delete(string $currentUserId, int $accountId): void {
if ($accountId === Manager::ACCOUNT_ID) {
return;
}
$mailAccount = $this->mapper->find($currentUserId, $accountId);
$this->aliasesService->deleteAll($accountId);
$this->mapper->delete($mailAccount);

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

@ -1,127 +0,0 @@
<?php
declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* Mail
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Mail\Service\DefaultAccount;
use OCA\Mail\Db\MailAccount;
use OCP\Authentication\Exceptions\CredentialsUnavailableException;
use OCP\Authentication\LoginCredentials\IStore;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUserSession;
use OCP\Security\ICrypto;
class Manager {
const ACCOUNT_ID = -2;
/** @var IConfig */
private $config;
/** @var IStore */
private $credentialStore;
/** @var ILogger */
private $logger;
/** @var IUserSession */
private $userSession;
/** @var ICrypto */
private $crypto;
/**
* @param IConfig $config
* @param IStore $credentialStore
* @param ILogger $logger
* @param IUserSession $userSession
* @param ICrypto $crypto
*/
public function __construct(IConfig $config,
IStore $credentialStore,
ILogger $logger,
IUserSession $userSession,
ICrypto $crypto) {
$this->config = $config;
$this->logger = $logger;
$this->userSession = $userSession;
$this->crypto = $crypto;
$this->credentialStore = $credentialStore;
}
/**
* @return Config|null
*/
private function getConfig() {
$config = $this->config->getSystemValue('app.mail.accounts.default', null);
if (is_null($config)) {
$this->logger->debug('no default config found');
return null;
} else {
$this->logger->debug('default config to create a default account found');
// TODO: check if config is complete
return new Config($config);
}
}
/**
* @return MailAccount|null
*/
public function getDefaultAccount() {
$config = $this->getConfig();
if (is_null($config)) {
return null;
}
try {
$credentials = $this->credentialStore->getLoginCredentials();
} catch (CredentialsUnavailableException $ex) {
$this->logger->debug('login credentials not available for default account');
return null;
}
$user = $this->userSession->getUser();
$password = $this->crypto->encrypt($credentials->getPassword());
$this->logger->info('building default account for user ' . $user->getUID());
$account = new MailAccount();
$account->setId(self::ACCOUNT_ID);
$account->setUserId($user->getUID());
$account->setEmail($config->buildEmail($user));
$account->setName($user->getDisplayName());
$account->setInboundUser($config->buildImapUser($user));
$account->setInboundHost($config->getImapHost());
$account->setInboundPort($config->getImapPort());
$account->setInboundSslMode($config->getImapSslMode());
$account->setInboundPassword($password);
$account->setOutboundUser($config->buildSmtpUser($user));
$account->setOutboundHost($config->getSmtpHost());
$account->setOutboundPort($config->getSmtpPort());
$account->setOutboundSslMode($config->getSmtpSslMode());
$account->setOutboundPassword($password);
return $account;
}
}

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

@ -21,12 +21,16 @@ declare(strict_types=1);
*
*/
namespace OCA\Mail\Service\DefaultAccount;
namespace OCA\Mail\Service\Provisioning;
use JsonSerializable;
use OCP\IUser;
class Config {
class Config implements JsonSerializable {
private const VERSION = 1;
/** @var string[] */
private $data;
/**
@ -115,13 +119,28 @@ class Config {
* @return string
*/
private function buildUserEmail(string $original, IUser $user) {
if (!is_null($user->getUID())) {
if ($user->getUID() !== null) {
$original = str_replace('%USERID%', $user->getUID(), $original);
}
if (!is_null($user->getEMailAddress())) {
if ($user->getEMailAddress() !== null) {
$original = str_replace('%EMAIL%', $user->getEMailAddress(), $original);
}
return $original;
}
public function setActive(bool $state): self {
$this->data['active'] = $state;
return $this;
}
public function jsonSerialize() {
return array_merge(
[
'active' => false,
'version' => self::VERSION,
],
$this->data
);
}
}

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

@ -0,0 +1,62 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Service\Provisioning;
use OCA\Mail\AppInfo\Application;
use OCP\IConfig;
class ConfigMapper {
private const CONFIG_KEY = 'provisioning_settings';
/** @var IConfig */
private $config;
public function __construct(IConfig $config) {
$this->config = $config;
}
public function load(): ?Config {
$raw = $this->config->getAppValue(
Application::APP_ID,
self::CONFIG_KEY
);
if ($raw === '') {
// Not config set yet
return null;
}
return new Config(json_decode($raw, true));
}
public function save(Config $config): Config {
$this->config->setAppValue(
Application::APP_ID,
self::CONFIG_KEY,
json_encode($config)
);
return $config;
}
}

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

@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* Mail
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Mail\Service\Provisioning;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\MailAccountMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\IDBConnection;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Security\ICrypto;
class Manager {
/** @var IUserManager */
private $userManager;
/** @var ConfigMapper */
private $configMapper;
/** @var MailAccountMapper */
private $mailAccountMapper;
/** @var ICrypto */
private $crypto;
/** @var ILogger */
private $logger;
public function __construct(IUserManager $userManager,
ConfigMapper $configMapper,
MailAccountMapper $mailAccountMapper,
ICrypto $crypto,
ILogger $logger) {
$this->userManager = $userManager;
$this->configMapper = $configMapper;
$this->mailAccountMapper = $mailAccountMapper;
$this->crypto = $crypto;
$this->logger = $logger;
}
public function getConfig(): ?Config {
return $this->configMapper->load();
}
public function provision(Config $config): int {
$cnt = 0;
$this->userManager->callForAllUsers(function (IUser $user) use ($config, &$cnt) {
$this->provisionSingleUser($config, $user);
$cnt++;
});
return $cnt;
}
public function provisionSingleUser(Config $config, IUser $user): void {
try {
$existing = $this->mailAccountMapper->findProvisionedAccount($user);
$this->mailAccountMapper->update(
$this->updateAccount($user, $existing, $config)
);
} catch (DoesNotExistException $e) {
// Fine, then we create a new one
$new = new MailAccount();
$new->setUserId($user->getUID());
if ($user->getDisplayName() !== $user->getUID()) {
// Only set if it's something meaningful
$new->setName($user->getDisplayName());
}
$new->setProvisioned(true);
$this->mailAccountMapper->insert(
$this->updateAccount($user, $new, $config)
);
}
}
public function newProvisioning(string $email,
string $imapUser,
string $imapHost,
int $imapPort,
string $imapSslMode,
string $smtpUser,
string $smtpHost,
int $smtpPort,
string $smtpSslMode): void {
$config = $this->configMapper->save(new Config([
'active' => true,
'email' => $email,
'imapUser' => $imapUser,
'imapHost' => $imapHost,
'imapPort' => $imapPort,
'imapSslMode' => $imapSslMode,
'smtpUser' => $smtpUser,
'smtpHost' => $smtpHost,
'smtpPort' => $smtpPort,
'smtpSslMode' => $smtpSslMode,
]));
$this->provision($config);
}
private function updateAccount(IUser $user, MailAccount $account, Config $config): MailAccount {
$account->setEmail($config->buildEmail($user));
$account->setInboundUser($config->buildImapUser($user));
$account->setInboundHost($config->getImapHost());
$account->setInboundPort($config->getImapPort());
$account->setInboundSslMode($config->getImapSslMode());
$account->setOutboundUser($config->buildSmtpUser($user));
$account->setOutboundHost($config->getSmtpHost());
$account->setOutboundPort($config->getSmtpPort());
$account->setOutboundSslMode($config->getSmtpSslMode());
return $account;
}
public function deprovision(): void {
$this->mailAccountMapper->deleteProvisionedAccounts();
$config = $this->configMapper->load();
if ($config !== null) {
$config->setActive(false);
$this->configMapper->save($config);
}
}
public function importConfig(array $data): Config {
if (!isset($data['imapUser'])) {
$data['imapUser'] = $data['email'];
}
if (!isset($data['smtpUser'])) {
$data['smtpUser'] = $data['email'];
}
return $this->configMapper->save(new Config($data));
}
public function updatePassword(IUser $user, string $password): void {
try {
$account = $this->mailAccountMapper->findProvisionedAccount($user);
if ($account->getInboundPassword() !== null
&& $this->crypto->decrypt($account->getInboundPassword()) === $password
&& $account->getOutboundPassword() !== null
&& $this->crypto->decrypt($account->getOutboundPassword()) === $password) {
$this->logger->debug('Password of provisioned account is up to date');
return;
}
$account->setInboundPassword($this->crypto->encrypt($password));
$account->setOutboundPassword($this->crypto->encrypt($password));
$this->mailAccountMapper->update($account);
$this->logger->debug('Provisioned account password udpated');
} catch (DoesNotExistException $e) {
// Nothing to update
}
}
}

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

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Settings;
use OCA\Mail\AppInfo\Application;
use OCA\Mail\Service\Provisioning\Config;
use OCA\Mail\Service\Provisioning\Manager as ProvisioningManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IInitialStateService;
use OCP\Settings\ISettings;
class AdminSettings implements ISettings {
/** @var IInitialStateService */
private $initialStateService;
/** @var ProvisioningManager */
private $provisioningManager;
public function __construct(IInitialStateService $initialStateService,
ProvisioningManager $provisioningManager) {
$this->initialStateService = $initialStateService;
$this->provisioningManager = $provisioningManager;
}
public function getForm() {
$this->initialStateService->provideInitialState(
Application::APP_ID,
'provisioning_settings',
$this->provisioningManager->getConfig() ?? new Config([
'active' => false,
'email' => '%USERID%@domain.com',
'imapUser' => '%USERID%@domain.com',
'imapHost' => 'imap.domain.com',
'imapPort' => 993,
'imapSslMode' => 'ssl',
'smtpUser' => '%USERID%@domain.com',
'smtpHost' => 'smtp.domain.com',
'smtpPort' => 587,
'smtpSslMode' => 'tls',
])
);
return new TemplateResponse(Application::APP_ID, 'settings-admin');
}
public function getSection() {
return 'groupware';
}
public function getPriority() {
return 90;
}
}

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

@ -37,7 +37,7 @@
<ActionRouter :to="settingsRoute" icon="icon-settings">
{{ t('mail', 'Edit account') }}
</ActionRouter>
<ActionButton icon="icon-delete" @click="deleteAccount">
<ActionButton v-if="!account.provisioned" icon="icon-delete" @click="deleteAccount">
{{ t('mail', 'Delete account') }}
</ActionButton>
<ActionInput icon="icon-add" @submit="createFolder">

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

@ -0,0 +1,53 @@
<!--
- @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
-
- @author 2019 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/>.
-->
<template>
<div id="mail-admin-settings" class="section">
<h2>{{ t('mail', 'Mail app') }}</h2>
<h3>{{ t('mail', 'The mail app allows users to read mails on their IMAP accounts.') }}</h3>
<h3>
{{
t(
'mail',
'Here you can find instance-wide settings. User specific settings are found in the app itself (bottom-left corner).'
)
}}
</h3>
<ProvisioningSettings :settings="provisioningSettings" />
</div>
</template>
<script>
import ProvisioningSettings from './ProvisioningSettings'
export default {
name: 'AdminSettings',
components: {
ProvisioningSettings,
},
props: {
provisioningSettings: {
type: Object,
required: true,
},
},
}
</script>

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

@ -0,0 +1,99 @@
<!--
- @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
-
- @author 2019 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/>.
-->
<template>
<div class="provision-preview">
<b>
<span v-if="data.uid">uid={{ data.uid }}</span>
<span v-if="data.email">email={{ email }}</span>
</b>
<br />
{{ t('mail', 'Email: {email}', {email}) }}<br />
{{
t('mail', 'IMAP: {user} on {host}:{port} ({ssl} encryption)', {
user: imapUser,
host: imapHost,
port: imapPort,
ssl: imapSslMode,
})
}}<br />
{{
t('mail', 'SMTP: {user} on {host}:{port} ({ssl} encryption)', {
user: smtpUser,
host: smtpHost,
port: smtpPort,
ssl: smtpSslMode,
})
}}<br />
</div>
</template>
<script>
export default {
name: 'ProvisionPreview',
props: {
data: {
type: Object,
required: true,
},
templates: {
type: Object,
required: true,
},
},
computed: {
email() {
return this.templates.email.replace('%USERID%', this.data.uid).replace('%EMAIL%', this.data.email)
},
imapHost() {
return this.templates.imapHost
},
imapPort() {
return this.templates.imapPort
},
imapSslMode() {
return this.templates.imapSslMode
},
imapUser() {
return this.templates.imapUser.replace('%USERID%', this.data.uid).replace('%EMAIL%', this.data.email)
},
smtpHost() {
return this.templates.smtpHost
},
smtpPort() {
return this.templates.smtpPort
},
smtpSslMode() {
return this.templates.smtpSslMode
},
smtpUser() {
return this.templates.smtpUser.replace('%USERID%', this.data.uid).replace('%EMAIL%', this.data.email)
},
},
}
</script>
<style lang="scss" scoped>
.provision-preview {
border: 1px solid var(--color-border-dark);
border-radius: var(--border-radius);
}
</style>

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

@ -0,0 +1,428 @@
<!--
- @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
-
- @author 2019 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/>.
-->
<template>
<div>
<h3>Account provisioning</h3>
<p>
{{
t(
'mail',
'You can configure a template for account settings, from which all users will get an account provisioned from.'
)
}}
{{
t(
'mail',
"This setting only makes most sense if you use the same user back-end for your organization's Nextcloud and mail server."
)
}}
</p>
<div>
<input id="mail-provision-toggle" v-model="active" type="checkbox" class="checkbox" />
<label for="mail-provision-toggle">
{{ t('mail', 'Provision an account for every user') }}
</label>
</div>
<div v-if="active" class="form-preview-row">
<form @submit.prevent="submit">
<div class="settings-group">
<div class="group-title">{{ t('mail', 'General') }}</div>
<div class="group-inputs">
<label for="mail-provision-email"> {{ t('mail', 'Email address') }}* </label>
<br />
<input
id="mail-provision-email"
v-model="emailTemplate"
:disabled="loading"
name="email"
type="text"
/>
</div>
</div>
<div class="settings-group">
<div class="group-title">{{ t('mail', 'IMAP') }}</div>
<div class="group-inputs">
<label for="mail-provision-imap-user">
{{ t('mail', 'User') }}*
<br />
<input
id="mail-provision-imap-user"
v-model="imapUser"
:disabled="loading"
name="email"
type="text"
/>
</label>
<div class="flex-row">
<label for="mail-provision-imap-host">
{{ t('mail', 'Host') }}
<br />
<input
id="mail-provision-imap-host"
v-model="imapHost"
:disabled="loading"
name="email"
type="text"
/>
</label>
<label for="mail-provision-imap-port">
{{ t('mail', 'Port') }}
<br />
<input
id="mail-provision-imap-port"
v-model="imapPort"
:disabled="loading"
name="email"
type="number"
/>
</label>
</div>
<div class="flex-row">
<input
id="mail-provision-imap-user-none"
v-model="imapSslMode"
type="radio"
name="man-imap-sec"
:disabled="loading"
value="none"
/>
<label
class="button"
for="mail-provision-imap-user-none"
:class="{primary: imapSslMode === 'none'}"
>{{ t('mail', 'None') }}</label
>
<input
id="mail-provision-imap-user-ssl"
v-model="imapSslMode"
type="radio"
name="man-imap-sec"
:disabled="loading"
value="ssl"
/>
<label
class="button"
for="mail-provision-imap-user-ssl"
:class="{primary: imapSslMode === 'ssl'}"
>{{ t('mail', 'SSL/TLS') }}</label
>
<input
id="mail-provision-imap-user-tls"
v-model="imapSslMode"
type="radio"
name="man-imap-sec"
:disabled="loading"
value="tls"
/>
<label
class="button"
for="mail-provision-imap-user-tls"
:class="{primary: imapSslMode === 'tls'}"
>{{ t('mail', 'STARTTLS') }}</label
>
</div>
</div>
</div>
<div class="settings-group">
<div class="group-title">{{ t('mail', 'SMTP') }}</div>
<div class="group-inputs">
<label for="mail-provision-smtp-user">
{{ t('mail', 'User') }}*
<br />
<input
id="mail-provision-smtp-user"
v-model="smtpUser"
:disabled="loading"
name="email"
type="text"
/>
</label>
<div class="flex-row">
<label for="mail-provision-imap-host">
{{ t('mail', 'Host') }}
<br />
<input
id="mail-provision-smtp-host"
v-model="smtpHost"
:disabled="loading"
name="email"
type="text"
/>
</label>
<label for="mail-provision-smtp-port">
{{ t('mail', 'Port') }}
<br />
<input
id="mail-provision-smtp-port"
v-model="smtpPort"
:disabled="loading"
name="email"
type="number"
/>
</label>
</div>
<div class="flex-row">
<input
id="mail-provision-smtp-user-none"
v-model="smtpSslMode"
type="radio"
name="man-smtp-sec"
:disabled="loading"
value="none"
/>
<label
class="button"
for="mail-provision-smtp-user-none"
:class="{primary: smtpSslMode === 'none'}"
>{{ t('mail', 'None') }}</label
>
<input
id="mail-provision-smtp-user-ssl"
v-model="smtpSslMode"
type="radio"
name="man-smtp-sec"
:disabled="loading"
value="ssl"
/>
<label
class="button"
for="mail-provision-smtp-user-ssl"
:class="{primary: smtpSslMode === 'ssl'}"
>{{ t('mail', 'SSL/TLS') }}</label
>
<input
id="mail-provision-smtp-user-tls"
v-model="smtpSslMode"
type="radio"
name="man-smtp-sec"
:disabled="loading"
value="tls"
/>
<label
class="button"
for="mail-provision-smtp-user-tls"
:class="{primary: smtpSslMode === 'tls'}"
>{{ t('mail', 'STARTTLS') }}</label
>
</div>
</div>
</div>
<div class="settings-group">
<div class="group-title"></div>
<div class="group-inputs">
<input
type="submit"
class="primary"
:disabled="loading"
:value="t('mail', 'Apply and create/update for all users')"
/>
<input
type="button"
:disabled="loading"
:value="t('mail', 'Disable and un-provision existing accounts')"
@click="disable"
/>
<br />
<small>{{
t('mail', "* %USERID% and %EMAIL% will be replaced with the user's UID and email")
}}</small>
</div>
</div>
</form>
<div>
<h4>Preview</h4>
<p>
{{
t('mail', 'With the settings above, the app will create account settings in the following way:')
}}
</p>
<div class="previews">
<ProvisionPreview class="preview-item" :templates="previewTemplates" :data="previewData1" />
<ProvisionPreview class="preview-item" :templates="previewTemplates" :data="previewData2" />
</div>
</div>
</div>
</div>
</template>
<script>
import logger from '../../logger'
import ProvisionPreview from './ProvisionPreview'
import {disableProvisioning, saveProvisioningSettings} from '../../service/SettingsService'
export default {
name: 'ProvisioningSettings',
components: {ProvisionPreview},
props: {
settings: {
type: Object,
required: true,
},
},
data() {
return {
active: !!this.settings.active,
emailTemplate: this.settings.email || '',
imapHost: this.settings.imapHost || 'mx.domain.com',
imapPort: this.settings.imapPort || 993,
imapUser: this.settings.imapUser || '%USERID%domain.com',
imapSslMode: this.settings.imapSslMode || 'ssl',
smtpHost: this.settings.smtpHost || 'mx.domain.com',
smtpPort: this.settings.smtpPort || 587,
smtpUser: this.settings.smtpUser || '%USERID%domain.com',
smtpSslMode: this.settings.smtpSslMode || 'tls',
previewData1: {
uid: 'user123',
email: '',
},
previewData2: {
uid: 'user321',
email: 'user@domain.com',
},
loading: false,
}
},
computed: {
previewTemplates() {
return {
email: this.emailTemplate,
imapUser: this.imapUser,
imapHost: this.imapHost,
imapPort: this.imapPort,
imapSslMode: this.imapSslMode,
smtpUser: this.smtpUser,
smtpHost: this.smtpHost,
smtpPort: this.smtpPort,
smtpSslMode: this.smtpSslMode,
}
},
},
beforeMount() {
logger.debug('provisioning settings loaded', {settings: this.settings})
},
methods: {
submit() {
this.loading = true
return saveProvisioningSettings({
emailTemplate: this.emailTemplate,
imapUser: this.imapUser,
imapHost: this.imapHost,
imapPort: this.imapPort,
imapSslMode: this.imapSslMode,
smtpUser: this.smtpUser,
smtpHost: this.smtpHost,
smtpPort: this.smtpPort,
smtpSslMode: this.smtpSslMode,
})
.then(() => {
logger.info('provisioning settings updated')
})
.catch(error => {
// TODO: show user feedback
logger.error('Could not save provisioning settings', {error})
})
.then(() => {
this.loading = false
})
},
disable() {
this.loading = true
return disableProvisioning()
.then(() => {
logger.info('deprovisioned successfully')
})
.catch(error => {
logger.error('could not deprovision accounts', {error})
})
.then(() => {
this.active = false
this.loading = false
})
},
},
}
</script>
<style lang="scss" scoped>
.form-preview-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
div:last-child {
margin-top: 10px;
}
}
.settings-group {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
.group-title {
min-width: 100px;
text-align: right;
margin: 10px;
font-weight: bold;
}
.group-inputs {
margin: 10px;
flex-grow: 1;
input[type='text'] {
min-width: 200px;
}
}
}
h4 {
font-weight: bold;
}
.previews {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 0 -10px;
.preview-item {
flex-grow: 1;
margin: 10px;
padding: 25px;
}
}
input[type='radio'] {
display: none;
}
.flex-row {
display: flex;
}
form {
label {
color: var(--color-text-maxcontrast);
}
}
</style>

40
src/main-settings.js Normal file
Просмотреть файл

@ -0,0 +1,40 @@
/*
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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/>.
*/
import {generateFilePath} from '@nextcloud/router'
import {getRequestToken} from '@nextcloud/auth'
import {loadState} from '@nextcloud/initial-state'
import Vue from 'vue'
import AdminSettings from './components/settings/AdminSettings'
import Nextcloud from './mixins/Nextcloud'
__webpack_nonce__ = btoa(getRequestToken())
__webpack_public_path__ = generateFilePath('mail', '', 'js/')
Vue.mixin(Nextcloud)
const View = Vue.extend(AdminSettings)
new View({
propsData: {
provisioningSettings: loadState('mail', 'provisioning_settings') || {},
},
}).$mount('#mail-admin-settings')

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

@ -21,16 +21,16 @@
*/
import Vue from 'vue'
import App from './App'
import {getRequestToken} from '@nextcloud/auth'
import router from './router'
import store from './store'
import {sync} from 'vuex-router-sync'
import {translate, translatePlural} from '@nextcloud/l10n'
import {generateFilePath} from '@nextcloud/router'
import VueShortKey from 'vue-shortkey'
import VTooltip from 'v-tooltip'
import App from './App'
import Nextcloud from './mixins/Nextcloud'
import router from './router'
import store from './store'
import {fixAccountId} from './service/AccountService'
__webpack_nonce__ = btoa(getRequestToken())
@ -38,12 +38,7 @@ __webpack_public_path__ = generateFilePath('mail', '', 'js/')
sync(store, router)
Vue.mixin({
methods: {
t: translate,
n: translatePlural,
},
})
Vue.mixin(Nextcloud)
Vue.use(VueShortKey, {prevent: ['input', 'div']})
Vue.use(VTooltip)

29
src/mixins/Nextcloud.js Normal file
Просмотреть файл

@ -0,0 +1,29 @@
/*
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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/>.
*/
import {translate as t, translatePlural as n} from '@nextcloud/l10n'
export default {
methods: {
t,
n,
},
}

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

@ -0,0 +1,35 @@
/*
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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/>.
*/
import axios from '@nextcloud/axios'
import {generateUrl} from '@nextcloud/router'
export const saveProvisioningSettings = config => {
const url = generateUrl('/apps/mail/api/settings/provisioning')
return axios.post(url, config).then(resp => resp.data)
}
export const disableProvisioning = () => {
const url = generateUrl('/apps/mail/api/settings/provisioning')
return axios.delete(url).then(resp => resp.data)
}

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

@ -6,12 +6,17 @@
<h2>{{ t('mail', 'Account settings') }}</h2>
<p>
<strong>{{ displayName }}</strong> &lt;{{ email }}&gt;
<a class="button icon-rename" href="#account-form" :title="t('mail', 'Change name')"></a>
<a
v-if="!account.provisioned"
class="button icon-rename"
href="#account-form"
:title="t('mail', 'Change name')"
></a>
</p>
</div>
<SignatureSettings :account="account" />
<EditorSettings :account="account" />
<div class="section">
<div v-if="!account.provisioned" class="section">
<h2>{{ t('mail', 'Mail server') }}</h2>
<div id="mail-settings">
<AccountForm

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

@ -0,0 +1,28 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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/>.
*/
script(\OCA\Mail\AppInfo\Application::APP_ID, 'settings');
?>
<div id="mail-admin-settings">
</div>

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

@ -64,12 +64,14 @@ class MailTransmissionIntegrationTest extends TestCase {
parent::setUp();
$this->resetImapAccount();
$this->user = $this->createTestUser();
/** @var ICrypto $crypo */
$crypo = OC::$server->getCrypto();
/** @var MailAccountMapper $mapper */
$mapper = OC::$server->query(MailAccountMapper::class);
$mailAccount = MailAccount::fromParams([
'userId' => $this->user->getUID(),
'email' => 'user@domain.tld',
'inboundHost' => 'localhost',
'inboundPort' => '993',
@ -86,7 +88,6 @@ class MailTransmissionIntegrationTest extends TestCase {
$this->account = new Account($mailAccount);
$this->attachmentService = OC::$server->query(IAttachmentService::class);
$this->user = $this->createTestUser();
$userFolder = OC::$server->getUserFolder($this->user->getUID());
$this->transmission = new MailTransmission(
$userFolder,

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

@ -0,0 +1,86 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Controller;
use ChristophWurst\Nextcloud\Testing\ServiceMockObject;
use OCA\Mail\Controller\SettingsController;
use OCA\Mail\Tests\Integration\TestCase;
use OCP\AppFramework\Http\JSONResponse;
class SettingsControllerTest extends TestCase {
/** @var ServiceMockObject */
private $mock;
/** @var SettingsController */
private $controller;
protected function setUp(): void {
parent::setUp();
$this->mock = $this->createServiceMock(SettingsController::class);
$this->controller = $this->mock->getService();
}
public function testProvisioning() {
$this->mock->getParameter('provisioningManager')
->expects($this->once())
->method('newProvisioning')
->with(
'%USERID%@domain.com',
'%USERID%@domain.com',
'mx.domain.com',
993,
'ssl',
'%USERID%@domain.com',
'mx.domain.com',
567,
'tls'
);
$response = $this->controller->provisioning(
'%USERID%@domain.com',
'%USERID%@domain.com',
'mx.domain.com',
993,
'ssl',
'%USERID%@domain.com',
'mx.domain.com',
567,
'tls'
);
$this->assertInstanceOf(JSONResponse::class, $response);
}
public function testDeprovision() {
$this->mock->getParameter('provisioningManager')
->expects($this->once())
->method('deprovision');
$response = $this->controller->deprovision();
$this->assertInstanceOf(JSONResponse::class, $response);
}
}

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

@ -42,6 +42,7 @@ class MailAccountTest extends TestCase {
$a->setOutboundPassword('xxxx');
$a->setOutboundSslMode('ssl');
$a->setEditorMode('html');
$a->setProvisioned(false);
$this->assertEquals(array(
'accountId' => 12345,
@ -57,6 +58,7 @@ class MailAccountTest extends TestCase {
'smtpSslMode' => 'ssl',
'signature' => null,
'editorMode' => 'html',
'provisioned' => false,
), $a->toJson());
}
@ -75,6 +77,7 @@ class MailAccountTest extends TestCase {
'smtpSslMode' => 'ssl',
'signature' => null,
'editorMode' => null,
'provisioned' => false,
];
$a = new MailAccount($expected);
// TODO: fix inconsistency

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

@ -0,0 +1,147 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Http\Middleware;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Controller\PageController;
use OCA\Mail\Http\Middleware\ProvisioningMiddleware;
use OCA\Mail\Service\Provisioning\Manager;
use OCP\Authentication\Exceptions\CredentialsUnavailableException;
use OCP\Authentication\Exceptions\PasswordUnavailableException;
use OCP\Authentication\LoginCredentials\ICredentials;
use OCP\Authentication\LoginCredentials\IStore;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserSession;
use PHPUnit\Framework\MockObject\MockObject;
class ProvisioningMiddlewareTest extends TestCase {
/** @var IUserSession|MockObject */
private $userSession;
/** @var IStore|MockObject */
private $credentialStore;
/** @var Manager|MockObject */
private $provisioningManager;
/** @var ILogger|MockObject */
private $logger;
/** @var ProvisioningMiddleware */
private $middleware;
protected function setUp(): void {
parent::setUp();
$this->userSession = $this->createMock(IUserSession::class);
$this->credentialStore = $this->createMock(IStore::class);
$this->provisioningManager = $this->createMock(Manager::class);
$this->logger = $this->createMock(ILogger::class);
$this->middleware = new ProvisioningMiddleware(
$this->userSession,
$this->credentialStore,
$this->provisioningManager,
$this->logger
);
}
public function testBeforeControllerNotLoggedIn() {
$this->credentialStore->expects($this->never())
->method('getLoginCredentials');
$this->provisioningManager->expects($this->never())
->method('updatePassword');
$this->middleware->beforeController(
$this->createMock(PageController::class),
'index'
);
}
public function testBeforeControllerNoCredentialsAvailable() {
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->once())
->method('getUser')
->willReturn($user);
$this->credentialStore->expects($this->once())
->method('getLoginCredentials')
->willThrowException($this->createMock(CredentialsUnavailableException::class));
$this->provisioningManager->expects($this->never())
->method('updatePassword');
$this->middleware->beforeController(
$this->createMock(PageController::class),
'index'
);
}
public function testBeforeControllerNoPasswordAvailable() {
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->once())
->method('getUser')
->willReturn($user);
$credentials = $this->createMock(ICredentials::class);
$this->credentialStore->expects($this->once())
->method('getLoginCredentials')
->willReturn($credentials);
$credentials->expects($this->once())
->method('getPassword')
->willThrowException($this->createMock(PasswordUnavailableException::class));
$this->provisioningManager->expects($this->never())
->method('updatePassword');
$this->middleware->beforeController(
$this->createMock(PageController::class),
'index'
);
}
public function testBeforeController() {
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->once())
->method('getUser')
->willReturn($user);
$credentials = $this->createMock(ICredentials::class);
$this->credentialStore->expects($this->once())
->method('getLoginCredentials')
->willReturn($credentials);
$credentials->expects($this->once())
->method('getPassword')
->willReturn('123456');
$this->provisioningManager->expects($this->once())
->method('updatePassword')
->with(
$user,
'123456'
);
$this->middleware->beforeController(
$this->createMock(PageController::class),
'index'
);
}
}

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

@ -0,0 +1,106 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Migration;
use ChristophWurst\Nextcloud\Testing\ServiceMockObject;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Migration\MigrateProvisioningConfig;
use OCA\Mail\Service\Provisioning\Config;
use OCP\Migration\IOutput;
use PHPUnit\Framework\MockObject\MockObject;
class MigrateProvisioningConfigTest extends TestCase {
/** @var ServiceMockObject */
private $mock;
/** @var MigrateProvisioningConfig */
private $repairStep;
protected function setUp(): void {
parent::setUp();
$this->mock = $this->createServiceMock(MigrateProvisioningConfig::class);
$this->repairStep = $this->mock->getService();
}
public function testRunNoConfigToMigrate() {
/** @var IOutput|MockObject $output */
$output = $this->createMock(IOutput::class);
$this->mock->getParameter('config')
->expects($this->once())
->method('getSystemValue')
->with('app.mail.accounts.default')
->willReturn('');
$this->repairStep->run($output);
}
public function testRunAlreadyMigrated() {
/** @var IOutput|MockObject $output */
$output = $this->createMock(IOutput::class);
$this->mock->getParameter('config')
->expects($this->once())
->method('getSystemValue')
->with('app.mail.accounts.default')
->willReturn([]);
$this->mock->getParameter('provisioningManager')
->expects($this->once())
->method('getConfig')
->willReturn($this->createMock(Config::class));
$this->repairStep->run($output);
}
public function testRun() {
/** @var IOutput|MockObject $output */
$output = $this->createMock(IOutput::class);
$this->mock->getParameter('config')
->expects($this->once())
->method('getSystemValue')
->with('app.mail.accounts.default')
->willReturn([]);
$this->mock->getParameter('provisioningManager')
->expects($this->once())
->method('getConfig')
->willReturn(null);
$this->mock->getParameter('provisioningManager')
->expects($this->once())
->method('importConfig');
$this->mock->getParameter('config')
->expects($this->once())
->method('deleteSystemValue')
->with('app.mail.accounts.default');
$this->repairStep->run($output);
}
public function testGetName() {
$name = $this->repairStep->getName();
$this->assertEquals('Migrate Mail provisioning config from config.php to the database', $name);
}
}

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

@ -27,44 +27,42 @@ use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Service\AccountService;
use OCA\Mail\Service\AliasesService;
use OCA\Mail\Service\DefaultAccount\Manager;
use OCP\IL10N;
use PHPUnit_Framework_MockObject_MockObject;
use PHPUnit\Framework\MockObject\MockObject;
class AccountServiceTest extends TestCase {
/** @var string */
private $user = 'herbert';
/** @var MailAccountMapper|PHPUnit_Framework_MockObject_MockObject */
/** @var MailAccountMapper|MockObject */
private $mapper;
/** @var IL10N|PHPUnit_Framework_MockObject_MockObject */
/** @var IL10N|MockObject */
private $l10n;
/** @var AccountService|PHPUnit_Framework_MockObject_MockObject */
/** @var AccountService|MockObject */
private $accountService;
/** @var AliasesService|PHPUnit_Framework_MockObject_MockObject */
/** @var AliasesService|MockObject */
private $aliasesService;
/** @var MailAccount|PHPUnit_Framework_MockObject_MockObject */
/** @var MailAccount|MockObject */
private $account1;
/** @var MailAccount|PHPUnit_Framework_MockObject_MockObject */
/** @var MailAccount|MockObject */
private $account2;
/** @var Manager|PHPUnit_Framework_MockObject_MockObject */
private $defaultAccountManager;
protected function setUp(): void {
parent::setUp();
$this->mapper = $this->createMock(MailAccountMapper::class);
$this->l10n = $this->createMock(IL10N::class);
$this->defaultAccountManager = $this->createMock(Manager::class);
$this->aliasesService = $this->createMock(AliasesService::class);
$this->accountService = new AccountService($this->mapper, $this->defaultAccountManager, $this->aliasesService);
$this->accountService = new AccountService(
$this->mapper,
$this->aliasesService
);
$this->account1 = $this->createMock(MailAccount::class);
$this->account2 = $this->createMock(MailAccount::class);

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

@ -1,154 +0,0 @@
<?php
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* Mail
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Mail\Tests\Unit\Service\DefaultAccount;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Service\DefaultAccount\Manager;
use OCP\Authentication\Exceptions\CredentialsUnavailableException;
use OCP\Authentication\LoginCredentials\ICredentials;
use OCP\Authentication\LoginCredentials\IStore;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Security\ICrypto;
use PHPUnit_Framework_MockObject_MockObject;
class ManagerTest extends TestCase {
/** @var IConfig|PHPUnit_Framework_MockObject_MockObject */
private $config;
/** @var IStore|PHPUnit_Framework_MockObject_MockObject */
private $credentialStore;
/** @var ILogger|PHPUnit_Framework_MockObject_MockObject */
private $logger;
/** @var IUserSession|PHPUnit_Framework_MockObject_MockObject */
private $userSession;
/** @var ICrypto|PHPUnit_Framework_MockObject_MockObject */
private $crypto;
/** @var Manager|PHPUnit_Framework_MockObject_MockObject */
private $manager;
protected function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->credentialStore = $this->createMock(IStore::class);
$this->logger = $this->createMock(ILogger::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->crypto = $this->createMock(ICrypto::class);
$this->manager = new Manager($this->config, $this->credentialStore, $this->logger, $this->userSession, $this->crypto);
}
public function testGetDefaultAccountWithoutConfigAvailble() {
$this->config->expects($this->once())
->method('getSystemValue')
->with($this->equalTo('app.mail.accounts.default'), $this->equalTo(null))
->willReturn(null);
$account = $this->manager->getDefaultAccount();
$this->assertSame(null, $account);
}
public function testGetDefaultAccountWithCredentialsUnavailable() {
$this->config->expects($this->once())
->method('getSystemValue')
->with($this->equalTo('app.mail.accounts.default'), $this->equalTo(null))
->willReturn([
'email' => '%EMAIL%',
]);
$this->credentialStore->expects($this->once())
->method('getLoginCredentials')
->willThrowException(new CredentialsUnavailableException());
$account = $this->manager->getDefaultAccount();
$this->assertSame(null, $account);
}
public function testGetDefaultAccount() {
$this->config->expects($this->once())
->method('getSystemValue')
->with($this->equalTo('app.mail.accounts.default'), $this->equalTo(null))
->willReturn([
'email' => '%EMAIL%',
'imapHost' => 'imap.domain.tld',
'imapPort' => 993,
'imapSslMode' => 'ssl',
'smtpHost' => 'smtp.domain.tld',
'smtpPort' => 465,
'smtpSslMode' => 'tls',
]);
$credentials = $this->createMock(ICredentials::class);
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->once())
->method('getUser')
->willReturn($user);
$this->credentialStore->expects($this->once())
->method('getLoginCredentials')
->willReturn($credentials);
$credentials->expects($this->once())
->method('getPassword')
->willReturn('123456');
$this->crypto->expects($this->once())
->method('encrypt')
->with($this->equalTo('123456'))
->willReturn('encrypted');
$expected = new MailAccount();
$expected->setId(Manager::ACCOUNT_ID);
$user->expects($this->any())
->method('getUID')
->willReturn('user123');
$user->expects($this->any())
->method('getEMailAddress')
->willReturn('user@domain.tld');
$user->expects($this->once())
->method('getDisplayName')
->willReturn('Test User');
$expected->setUserId('user123');
$expected->setEmail('user@domain.tld');
$expected->setName('Test User');
$expected->setInboundUser('user@domain.tld');
$expected->setInboundHost('imap.domain.tld');
$expected->setInboundPort(993);
$expected->setInboundSslMode('ssl');
$expected->setInboundPassword('encrypted');
$expected->setOutboundUser('user@domain.tld');
$expected->setOutboundHost('smtp.domain.tld');
$expected->setOutboundPort(465);
$expected->setOutboundSslMode('tls');
$expected->setOutboundPassword('encrypted');
$account = $this->manager->getDefaultAccount();
$this->assertEquals($expected, $account);
}
}

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

@ -0,0 +1,85 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Service\Provisioning;
use ChristophWurst\Nextcloud\Testing\ServiceMockObject;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Service\Provisioning\Config;
use OCA\Mail\Service\Provisioning\ConfigMapper;
use PHPUnit\Framework\MockObject\MockObject;
class ConfigMapperTest extends TestCase {
/** @var ServiceMockObject */
private $mock;
/** @var ConfigMapper */
private $mapper;
protected function setUp(): void {
parent::setUp();
$this->mock = $this->createServiceMock(ConfigMapper::class);
$this->mapper = $this->mock->getService();
}
public function testSave() {
/** @var Config|MockObject $config */
$config = $this->createMock(Config::class);
$config->expects($this->once())
->method('jsonSerialize')
->willReturn([]);
$this->mock->getParameter('config')
->expects($this->once())
->method('setAppValue')
->with('mail', 'provisioning_settings', '[]');
$this->mapper->save($config);
}
public function testLoadNoConfig() {
$this->mock->getParameter('config')
->expects($this->once())
->method('getAppValue')
->with('mail', 'provisioning_settings')
->willReturn('');
$config = $this->mapper->load();
$this->assertNull($config);
}
public function testLoad() {
$this->mock->getParameter('config')
->expects($this->once())
->method('getAppValue')
->with('mail', 'provisioning_settings')
->willReturn('[]');
$config = $this->mapper->load();
$this->assertInstanceOf(Config::class, $config);
}
}

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

@ -19,10 +19,10 @@
*
*/
namespace OCA\Mail\Tests\Unit\Service\DefaultAccount;
namespace OCA\Mail\Tests\Unit\Service\Provisioning;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Service\DefaultAccount\Config;
use OCA\Mail\Service\Provisioning\Config;
use OCP\IUser;
class ConfigTest extends TestCase {

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

@ -0,0 +1,184 @@
<?php
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Service\Provisioning;
use ChristophWurst\Nextcloud\Testing\ServiceMockObject;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Service\Provisioning\Config;
use OCA\Mail\Service\Provisioning\Manager;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IUser;
use PHPUnit\Framework\MockObject\MockObject;
class ManagerTest extends TestCase {
/** @var ServiceMockObject */
private $mock;
/** @var Manager */
private $manager;
protected function setUp(): void {
parent::setUp();
$this->mock = $this->createServiceMock(Manager::class);
$this->manager = $this->mock->getService();
}
public function testProvision() {
$config = new TestConfig();
$this->mock->getParameter('userManager')
->expects($this->once())
->method('callForAllUsers');
$cnt = $this->manager->provision($config);
$this->assertEquals(0, $cnt);
}
public function testUpdateProvisionSingleUser() {
/** @var IUser|MockObject $user */
$user = $this->createMock(IUser::class);
$config = new TestConfig();
$account = $this->createMock(MailAccount::class);
$this->mock->getParameter('mailAccountMapper')
->expects($this->once())
->method('findProvisionedAccount')
->willReturn($account);
$this->mock->getParameter('mailAccountMapper')
->expects($this->once())
->method('update')
->with($account);
$this->manager->provisionSingleUser($config, $user);
}
public function testProvisionSingleUser() {
/** @var IUser|MockObject $user */
$user = $this->createMock(IUser::class);
$config = new TestConfig();
$this->mock->getParameter('mailAccountMapper')
->expects($this->once())
->method('findProvisionedAccount')
->willThrowException($this->createMock(DoesNotExistException::class));
$this->mock->getParameter('mailAccountMapper')
->expects($this->once())
->method('insert');
$this->manager->provisionSingleUser($config, $user);
}
public function testGetNoConfig() {
$config = $this->manager->getConfig();
$this->assertNull($config);
}
public function testGetConfig() {
$config = $this->createMock(Config::class);
$this->mock->getParameter('configMapper')
->expects($this->once())
->method('load')
->willReturn($config);
$cfg = $this->manager->getConfig();
$this->assertSame($config, $cfg);
}
public function testDeprovision() {
$config = new TestConfig();
$this->mock->getParameter('mailAccountMapper')
->expects($this->once())
->method('deleteProvisionedAccounts');
$this->mock->getParameter('configMapper')
->expects($this->once())
->method('load')
->willReturn($config);
$this->mock->getParameter('configMapper')
->expects($this->once())
->method('save')
->willReturn($config);
$this->manager->deprovision();
$this->assertEquals(false, $config->jsonSerialize()['active']);
}
public function testImportConfig() {
$this->mock->getParameter('configMapper')
->expects($this->once())
->method('save');
$this->manager->importConfig([
'email' => '%USERID%@domain.com',
]);
}
public function testUpdatePasswordNotProvisioned() {
/** @var IUser|MockObject $user */
$user = $this->createMock(IUser::class);
$this->mock->getParameter('mailAccountMapper')
->expects($this->once())
->method('findProvisionedAccount')
->with($user)
->willThrowException($this->createMock(DoesNotExistException::class));
$this->manager->updatePassword($user, '123456');
}
public function testUpdatePassword() {
/** @var IUser|MockObject $user */
$user = $this->createMock(IUser::class);
$account = $this->createMock(MailAccount::class);
$this->mock->getParameter('mailAccountMapper')
->expects($this->once())
->method('findProvisionedAccount')
->willReturn($account);
$this->mock->getParameter('mailAccountMapper')
->expects($this->once())
->method('update')
->with($account);
$this->manager->updatePassword($user, '123456');
}
public function testNewProvisioning() {
$this->mock->getParameter('configMapper')
->expects($this->once())
->method('save');
$this->manager->newProvisioning(
'%USERID%@domain.com',
'%USERID%@domain.com',
'mx.domain.com',
993,
'ssl',
'%USERID%@domain.com',
'mx.domain.com',
567,
'tls'
);
}
}

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

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Service\Provisioning;
use OCA\Mail\Service\Provisioning\Config;
class TestConfig extends Config {
public function __construct() {
parent::__construct([
'email' => '%USERID%@domain.com',
'imapUser' => '%USERID%@domain.com',
'imapHost' => 'mx.domain.com',
'imapPort' => 993,
'imapSslMode' => 'ssl',
'smtpUser' => '%USERID%@domain.com',
'smtpHost' => 'mx.domain.com',
'smtpPort' => 567,
'smtpSslMode' => 'tls',
]);
}
}

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

@ -0,0 +1,74 @@
<?php declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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\Settings;
use ChristophWurst\Nextcloud\Testing\ServiceMockObject;
use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\AppInfo\Application;
use OCA\Mail\Settings\AdminSettings;
use OCP\AppFramework\Http\TemplateResponse;
class AdminSettingsTest extends TestCase {
/** @var ServiceMockObject */
private $serviceMock;
/** @var AdminSettings */
private $settings;
protected function setUp(): void {
parent::setUp();
$this->serviceMock = $this->createServiceMock(AdminSettings::class);
$this->settings = $this->serviceMock->getService();
}
public function testGetSection() {
$section = $this->settings->getSection();
$this->assertSame('groupware', $section);
}
public function testGetForm() {
$this->serviceMock->getParameter('initialStateService')->expects($this->once())
->method('provideInitialState')
->with(
Application::APP_ID,
'provisioning_settings',
$this->anything()
);
$expected = new TemplateResponse(Application::APP_ID, 'settings-admin');
$form = $this->settings->getForm();
$this->assertEquals($expected, $form);
}
public function testGetPriority() {
$priority = $this->settings->getPriority();
$this->assertIsInt($priority);
}
}

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

@ -20,7 +20,8 @@ if (process.env.BUNDLE_ANALYZER_TOKEN) {
module.exports = {
entry: {
autoredirect: path.join(__dirname, 'src/autoredirect.js'),
mail: path.join(__dirname, 'src/main.js')
mail: path.join(__dirname, 'src/main.js'),
settings: path.join(__dirname, 'src/main-settings')
},
output: {
path: path.resolve(__dirname, 'js'),