Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Wurst <ChristophWurst@users.noreply.github.com>2021-07-14 16:48:24 +0300
committerGitHub <noreply@github.com>2021-07-14 16:48:24 +0300
commita32f165f201c666490bd3c40a8b12539b262f7f7 (patch)
tree8516547fb3c7350a468505cd637faa91be505ccd
parenta59a5fbdbb82eb6403c6d3e1694b90695d8c1638 (diff)
parent8fb14b5ffa3450b2c2201ff9bb42c589e4a4afb7 (diff)
Merge pull request #5198 from nextcloud/enh/5173/ldap-aliases-provisioning
LDAP alias provisioning
-rw-r--r--lib/Controller/AliasesController.php9
-rw-r--r--lib/Controller/SettingsController.php9
-rw-r--r--lib/Db/Alias.php12
-rw-r--r--lib/Db/AliasMapper.php45
-rw-r--r--lib/Db/Provisioning.php14
-rw-r--r--lib/Db/ProvisioningMapper.php23
-rw-r--r--lib/Migration/Version1101Date20210616141806.php34
-rw-r--r--lib/Service/AliasesService.php44
-rw-r--r--lib/Service/Provisioning/Manager.php167
-rw-r--r--lib/Settings/AdminSettings.php9
-rw-r--r--src/components/AccountSettings.vue21
-rw-r--r--src/components/AliasForm.vue158
-rw-r--r--src/components/AliasSettings.vue123
-rw-r--r--src/components/settings/ProvisioningSettings.vue41
-rw-r--r--src/service/AliasService.js63
-rw-r--r--src/store/actions.js35
-rw-r--r--src/store/mutations.js13
-rw-r--r--tests/Unit/Controller/AliasesControllerTest.php77
-rw-r--r--tests/Unit/Service/AliasesServiceTest.php117
19 files changed, 806 insertions, 208 deletions
diff --git a/lib/Controller/AliasesController.php b/lib/Controller/AliasesController.php
index 8c1b49ddc..add3efb5f 100644
--- a/lib/Controller/AliasesController.php
+++ b/lib/Controller/AliasesController.php
@@ -72,8 +72,8 @@ class AliasesController extends Controller {
* @NoAdminRequired
* @TrapError
*/
- public function update() {
- throw new NotImplemented();
+ public function update(int $id, string $alias, string $aliasName): JSONResponse {
+ return new JSONResponse($this->aliasService->update($this->currentUserId, $id, $alias, $aliasName));
}
/**
@@ -84,7 +84,7 @@ class AliasesController extends Controller {
* @return JSONResponse
*/
public function destroy(int $id): JSONResponse {
- return new JSONResponse($this->aliasService->delete($id, $this->currentUserId));
+ return new JSONResponse($this->aliasService->delete($this->currentUserId, $id));
}
/**
@@ -116,7 +116,6 @@ class AliasesController extends Controller {
* @throws DoesNotExistException
*/
public function updateSignature(int $id, string $signature = null): JSONResponse {
- $this->aliasService->updateSignature($this->currentUserId, $id, $signature);
- return new JSONResponse();
+ return new JSONResponse($this->aliasService->updateSignature($this->currentUserId, $id, $signature));
}
}
diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php
index 2081ea5bb..dd1a19dc4 100644
--- a/lib/Controller/SettingsController.php
+++ b/lib/Controller/SettingsController.php
@@ -60,6 +60,8 @@ class SettingsController extends Controller {
$this->provisioningManager->newProvisioning($data);
} catch (ValidationException $e) {
return HttpJsonResponse::fail([$e->getFields()]);
+ } catch (\Exception $e) {
+ return HttpJsonResponse::fail([$e->getMessage()]);
}
return new JSONResponse([]);
@@ -67,12 +69,11 @@ class SettingsController extends Controller {
public function updateProvisioning(int $id, array $data): JSONResponse {
try {
- $this->provisioningManager->updateProvisioning(array_merge(
- $data,
- ['id' => $id]
- ));
+ $this->provisioningManager->updateProvisioning(array_merge($data, ['id' => $id]));
} catch (ValidationException $e) {
return HttpJsonResponse::fail([$e->getFields()]);
+ } catch (\Exception $e) {
+ return HttpJsonResponse::fail([$e->getMessage()]);
}
return new JSONResponse([]);
diff --git a/lib/Db/Alias.php b/lib/Db/Alias.php
index e1727a83c..812682678 100644
--- a/lib/Db/Alias.php
+++ b/lib/Db/Alias.php
@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace OCA\Mail\Db;
use JsonSerializable;
-
use OCP\AppFramework\Db\Entity;
/**
@@ -36,6 +35,8 @@ use OCP\AppFramework\Db\Entity;
* @method string getAlias()
* @method void setSignature(string|null $signature)
* @method string|null getSignature()
+ * @method void setProvisioningId(int $provisioningId)
+ * @method int|null getProvisioningId()
*/
class Alias extends Entity implements JsonSerializable {
@@ -51,10 +52,18 @@ class Alias extends Entity implements JsonSerializable {
/** @var string|null */
protected $signature;
+ /** @var int|null */
+ protected $provisioningId;
+
public function __construct() {
$this->addType('accountId', 'int');
$this->addType('name', 'string');
$this->addType('alias', 'string');
+ $this->addType('provisioningId', 'int');
+ }
+
+ public function isProvisioned(): bool {
+ return $this->getProvisioningId() !== null;
}
public function jsonSerialize(): array {
@@ -63,6 +72,7 @@ class Alias extends Entity implements JsonSerializable {
'name' => $this->getName(),
'alias' => $this->getAlias(),
'signature' => $this->getSignature(),
+ 'provisioned' => $this->isProvisioned(),
];
}
}
diff --git a/lib/Db/AliasMapper.php b/lib/Db/AliasMapper.php
index bf2e17b5d..ad95fc5b6 100644
--- a/lib/Db/AliasMapper.php
+++ b/lib/Db/AliasMapper.php
@@ -41,7 +41,7 @@ class AliasMapper extends QBMapper {
*/
public function find(int $aliasId, string $currentUserId): Alias {
$qb = $this->db->getQueryBuilder();
- $qb->select('aliases.*')
+ $qb->select('aliases.*', 'accounts.provisioning_id')
->from($this->getTableName(), 'aliases')
->join('aliases', 'mail_accounts', 'accounts', $qb->expr()->eq('aliases.account_id', 'accounts.id'))
->where(
@@ -55,6 +55,24 @@ class AliasMapper extends QBMapper {
}
/**
+ * @throws DoesNotExistException
+ */
+ public function findByAlias(string $alias, string $currentUserId): Alias {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('aliases.*', 'accounts.provisioning_id')
+ ->from($this->getTableName(), 'aliases')
+ ->join('aliases', 'mail_accounts', 'accounts', $qb->expr()->eq('aliases.account_id', 'accounts.id'))
+ ->where(
+ $qb->expr()->andX(
+ $qb->expr()->eq('accounts.user_id', $qb->createNamedParameter($currentUserId)),
+ $qb->expr()->eq('aliases.alias', $qb->createNamedParameter($alias))
+ )
+ );
+
+ return $this->findEntity($qb);
+ }
+
+ /**
* @param int $accountId
* @param string $currentUserId
*
@@ -62,7 +80,7 @@ class AliasMapper extends QBMapper {
*/
public function findAll(int $accountId, string $currentUserId): array {
$qb = $this->db->getQueryBuilder();
- $qb->select('aliases.*')
+ $qb->select('aliases.*', 'accounts.provisioning_id')
->from($this->getTableName(), 'aliases')
->join('aliases', 'mail_accounts', 'accounts', $qb->expr()->eq('aliases.account_id', 'accounts.id'))
->where(
@@ -89,6 +107,29 @@ class AliasMapper extends QBMapper {
$query->execute();
}
+ /**
+ * Delete all provisioned aliases for the given uid
+ *
+ * Exception for Nextcloud 20: \Doctrine\DBAL\DBALException
+ * Exception for Nextcloud 21 and newer: \OCP\DB\Exception
+ *
+ * @TODO: Change throws to \OCP\DB\Exception once Mail does not support Nextcloud 20.
+ *
+ * @throws \Exception
+ */
+ public function deleteProvisionedAliasesByUid(string $uid): void {
+ $qb = $this->db->getQueryBuilder();
+
+ $qb->delete($this->getTableName(), 'aliases')
+ ->join('aliases', 'mail_accounts', 'accounts', 'accounts.id = aliases.account_id')
+ ->where(
+ $qb->expr()->eq('accounts.user_id', $qb->createNamedParameter($uid)),
+ $qb->expr()->isNotNull('provisioning_id')
+ );
+
+ $qb->execute();
+ }
+
public function deleteOrphans(): void {
$qb1 = $this->db->getQueryBuilder();
$idsQuery = $qb1->select('a.id')
diff --git a/lib/Db/Provisioning.php b/lib/Db/Provisioning.php
index 97d3c7a81..b29afa91c 100644
--- a/lib/Db/Provisioning.php
+++ b/lib/Db/Provisioning.php
@@ -60,6 +60,12 @@ use OCP\IUser;
* @method void setSieveSslMode(?string $sieveSslMode)
* @method string|null getSieveUser()
* @method void setSieveUser(?string $sieveUser)
+ * @method array getAliases()
+ * @method void setAliases(array $aliases)
+ * @method bool getLdapAliasesProvisioning()
+ * @method void setLdapAliasesProvisioning(bool $ldapAliasesProvisioning)
+ * @method string|null getLdapAliasesAttribute()
+ * @method void setLdapAliasesAttribute(?string $ldapAliasesAttribute)
*/
class Provisioning extends Entity implements JsonSerializable {
public const WILDCARD = '*';
@@ -79,13 +85,18 @@ class Provisioning extends Entity implements JsonSerializable {
protected $sieveHost;
protected $sievePort;
protected $sieveSslMode;
+ protected $aliases = [];
+ protected $ldapAliasesProvisioning;
+ protected $ldapAliasesAttribute;
public function __construct() {
$this->addType('imapPort', 'integer');
$this->addType('smtpPort', 'integer');
$this->addType('sieveEnabled', 'boolean');
$this->addType('sievePort', 'integer');
+ $this->addType('ldapAliasesProvisioning', 'boolean');
}
+
/**
* @return array
*/
@@ -107,6 +118,9 @@ class Provisioning extends Entity implements JsonSerializable {
'sieveHost' => $this->getSieveHost(),
'sievePort' => $this->getSievePort(),
'sieveSslMode' => $this->getSieveSslMode(),
+ 'aliases' => $this->getAliases(),
+ 'ldapAliasesProvisioning' => $this->getLdapAliasesProvisioning(),
+ 'ldapAliasesAttribute' => $this->getLdapAliasesAttribute(),
];
}
diff --git a/lib/Db/ProvisioningMapper.php b/lib/Db/ProvisioningMapper.php
index 09eec6281..0daca261f 100644
--- a/lib/Db/ProvisioningMapper.php
+++ b/lib/Db/ProvisioningMapper.php
@@ -51,11 +51,11 @@ class ProvisioningMapper extends QBMapper {
*
* @return Provisioning[]
*/
- public function getAll() : array {
+ public function getAll(): array {
$qb = $this->db->getQueryBuilder();
$qb = $qb->select('*')
- ->from($this->getTableName())
- ->orderBy('provisioning_domain', 'desc');
+ ->from($this->getTableName())
+ ->orderBy('provisioning_domain', 'desc');
try {
return $this->findEntities($qb);
} catch (DoesNotExistException $e) {
@@ -69,7 +69,7 @@ class ProvisioningMapper extends QBMapper {
* @return Provisioning
* @throws ValidationException
*/
- public function validate(array $data) : Provisioning {
+ public function validate(array $data): Provisioning {
$exception = new ValidationException();
if (!isset($data['provisioningDomain']) || $data['provisioningDomain'] === '') {
@@ -103,11 +103,17 @@ class ProvisioningMapper extends QBMapper {
$exception->setField('smtpSslMode', false);
}
+ $ldapAliasesProvisioning = (bool)($data['ldapAliasesProvisioning'] ?? false);
+ $ldapAliasesAttribute = $data['ldapAliasesAttribute'] ?? '';
+
+ if ($ldapAliasesProvisioning && empty($ldapAliasesAttribute)) {
+ $exception->setField('ldapAliasesAttribute', false);
+ }
+
if (!empty($exception->getFields())) {
throw $exception;
}
-
$provisioning = new Provisioning();
$provisioning->setId($data['id'] ?? null);
$provisioning->setProvisioningDomain($data['provisioningDomain']);
@@ -127,14 +133,17 @@ class ProvisioningMapper extends QBMapper {
$provisioning->setSievePort($data['sievePort'] ?? null);
$provisioning->setSieveSslMode($data['sieveSslMode'] ?? '');
+ $provisioning->setLdapAliasesProvisioning($ldapAliasesProvisioning);
+ $provisioning->setLdapAliasesAttribute($ldapAliasesAttribute);
+
return $provisioning;
}
public function get(int $id): ?Provisioning {
$qb = $this->db->getQueryBuilder();
$qb = $qb->select('*')
- ->from($this->getTableName())
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($id), IQueryBuilder::PARAM_INT));
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id), IQueryBuilder::PARAM_INT));
try {
return $this->findEntity($qb);
} catch (DoesNotExistException $e) {
diff --git a/lib/Migration/Version1101Date20210616141806.php b/lib/Migration/Version1101Date20210616141806.php
new file mode 100644
index 000000000..0687b9091
--- /dev/null
+++ b/lib/Migration/Version1101Date20210616141806.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\Mail\Migration;
+
+use Closure;
+use Doctrine\DBAL\Schema\SchemaException;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version1101Date20210616141806 extends SimpleMigrationStep {
+ /**
+ * @throws SchemaException
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ $provisioningTable = $schema->getTable('mail_provisionings');
+ $provisioningTable->addColumn('ldap_aliases_provisioning', 'boolean', [
+ 'notnull' => false,
+ 'default' => false
+ ]);
+ $provisioningTable->addColumn('ldap_aliases_attribute', 'string', [
+ 'notnull' => false,
+ 'length' => 255,
+ 'default' => '',
+ ]);
+
+ return $schema;
+ }
+}
diff --git a/lib/Service/AliasesService.php b/lib/Service/AliasesService.php
index db21e1388..7d308b97c 100644
--- a/lib/Service/AliasesService.php
+++ b/lib/Service/AliasesService.php
@@ -26,6 +26,7 @@ namespace OCA\Mail\Service;
use OCA\Mail\Db\Alias;
use OCA\Mail\Db\AliasMapper;
use OCA\Mail\Db\MailAccountMapper;
+use OCA\Mail\Exception\ClientException;
use OCP\AppFramework\Db\DoesNotExistException;
class AliasesService {
@@ -81,15 +82,17 @@ class AliasesService {
}
/**
- * @param int $aliasId
- * @param String $currentUserId
- * @return Alias
+ * @throws ClientException
* @throws DoesNotExistException
*/
- public function delete(int $aliasId, string $currentUserId): Alias {
- $alias = $this->aliasMapper->find($aliasId, $currentUserId);
- $this->aliasMapper->delete($alias);
- return $alias;
+ public function delete(string $userId, int $aliasId): Alias {
+ $entity = $this->aliasMapper->find($aliasId, $userId);
+
+ if ($entity->isProvisioned()) {
+ throw new ClientException('Deleting a provisioned alias is not allowed.');
+ }
+
+ return $this->aliasMapper->delete($entity);
}
/**
@@ -105,16 +108,29 @@ class AliasesService {
}
/**
+ * Update alias and name
+ *
+ * @throws DoesNotExistException
+ */
+ public function update(string $userId, int $aliasId, string $alias, string $aliasName): Alias {
+ $entity = $this->aliasMapper->find($aliasId, $userId);
+
+ if (!$entity->isProvisioned()) {
+ $entity->setAlias($alias);
+ }
+ $entity->setName($aliasName);
+
+ return $this->aliasMapper->update($entity);
+ }
+
+ /**
* Update signature for alias
*
- * @param string $userId
- * @param int $aliasId
- * @param string|null $signature
* @throws DoesNotExistException
*/
- public function updateSignature(string $userId, int $aliasId, string $signature = null): void {
- $alias = $this->find($aliasId, $userId);
- $alias->setSignature($signature);
- $this->aliasMapper->update($alias);
+ public function updateSignature(string $userId, int $aliasId, string $signature = null): Alias {
+ $entity = $this->find($aliasId, $userId);
+ $entity->setSignature($signature);
+ return $this->aliasMapper->update($entity);
}
}
diff --git a/lib/Service/Provisioning/Manager.php b/lib/Service/Provisioning/Manager.php
index 8cc9c2e06..6f06c330e 100644
--- a/lib/Service/Provisioning/Manager.php
+++ b/lib/Service/Provisioning/Manager.php
@@ -24,6 +24,8 @@ declare(strict_types=1);
namespace OCA\Mail\Service\Provisioning;
use Horde_Mail_Rfc822_Address;
+use OCA\Mail\Db\Alias;
+use OCA\Mail\Db\AliasMapper;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\MailAccountMapper;
use OCA\Mail\Db\Provisioning;
@@ -33,6 +35,8 @@ use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\IUser;
use OCP\IUserManager;
+use OCP\LDAP\ILDAPProvider;
+use OCP\LDAP\ILDAPProviderFactory;
use OCP\Security\ICrypto;
use Psr\Log\LoggerInterface;
@@ -50,6 +54,12 @@ class Manager {
/** @var ICrypto */
private $crypto;
+ /** @var ILDAPProviderFactory */
+ private $ldapProviderFactory;
+
+ /** @var AliasMapper */
+ private $aliasMapper;
+
/** @var LoggerInterface */
private $logger;
@@ -57,11 +67,15 @@ class Manager {
ProvisioningMapper $provisioningMapper,
MailAccountMapper $mailAccountMapper,
ICrypto $crypto,
+ ILDAPProviderFactory $ldapProviderFactory,
+ AliasMapper $aliasMapper,
LoggerInterface $logger) {
$this->userManager = $userManager;
$this->provisioningMapper = $provisioningMapper;
$this->mailAccountMapper = $mailAccountMapper;
$this->crypto = $crypto;
+ $this->ldapProviderFactory = $ldapProviderFactory;
+ $this->aliasMapper = $aliasMapper;
$this->logger = $logger;
}
@@ -85,6 +99,75 @@ class Manager {
}
/**
+ * Delete orphaned aliases for the given account.
+ *
+ * A alias is orphaned if not listed in newAliases anymore
+ * (=> the provisioning configuration does contain it anymore)
+ *
+ * Exception for Nextcloud 20: \Doctrine\DBAL\DBALException
+ * Exception for Nextcloud 21 and newer: \OCP\DB\Exception
+ *
+ * @TODO: Change throws to \OCP\DB\Exception once Mail requires Nextcloud 21 or above
+ *
+ * @throws \Exception
+ */
+ private function deleteOrphanedAliases(string $userId, int $accountId, array $newAliases): void {
+ $existingAliases = $this->aliasMapper->findAll($accountId, $userId);
+ foreach ($existingAliases as $existingAlias) {
+ if (!in_array($existingAlias->getAlias(), $newAliases, true)) {
+ $this->aliasMapper->delete($existingAlias);
+ }
+ }
+ }
+
+ /**
+ * Create new aliases for the given account.
+ *
+ * Exception for Nextcloud 20: \Doctrine\DBAL\DBALException
+ * Exception for Nextcloud 21 and newer: \OCP\DB\Exception
+ *
+ * @TODO: Change throws to \OCP\DB\Exception once Mail requires Nextcloud 21 or above
+ *
+ * @throws \Exception
+ */
+ private function createNewAliases(string $userId, int $accountId, array $newAliases, string $displayName): void {
+ foreach ($newAliases as $newAlias) {
+ try {
+ $this->aliasMapper->findByAlias($newAlias, $userId);
+ } catch (DoesNotExistException $e) {
+ $alias = new Alias();
+ $alias->setAccountId($accountId);
+ $alias->setName($displayName);
+ $alias->setAlias($newAlias);
+ $this->aliasMapper->insert($alias);
+ }
+ }
+ }
+
+ /**
+ * @throws \Exception if user id was not found in LDAP
+ *
+ * @TODO: Remove psalm-suppress once Mail requires Nextcloud 22 or above
+ */
+ public function ldapAliasesIntegration(Provisioning $provisioning, IUser $user): Provisioning {
+ if ($user->getBackendClassName() !== 'LDAP' || $provisioning->getLdapAliasesProvisioning() === false || empty($provisioning->getLdapAliasesAttribute())) {
+ return $provisioning;
+ }
+
+ /** @psalm-suppress UndefinedInterfaceMethod */
+ if ($this->ldapProviderFactory->isAvailable() === false) {
+ $this->logger->debug('Request to provision mail aliases but ldap not available');
+ return $provisioning;
+ }
+
+ $ldapProvider = $this->ldapProviderFactory->getLDAPProvider();
+ /** @psalm-suppress UndefinedInterfaceMethod */
+ $provisioning->setAliases($ldapProvider->getMultiValueUserAttribute($user->getUID(), $provisioning->getLdapAliasesAttribute()));
+
+ return $provisioning;
+ }
+
+ /**
* @param Provisioning[] $provisionings
*/
public function provisionSingleUser(array $provisionings, IUser $user): bool {
@@ -96,57 +179,69 @@ class Manager {
try {
// TODO: match by UID only, catch multiple objects returned below and delete all those accounts
- $existing = $this->mailAccountMapper->findProvisionedAccount($user);
+ $mailAccount = $this->mailAccountMapper->findProvisionedAccount($user);
- $this->mailAccountMapper->update(
- $this->updateAccount($user, $existing, $provisioning)
+ $mailAccount = $this->mailAccountMapper->update(
+ $this->updateAccount($user, $mailAccount, $provisioning)
);
- return true;
- } catch (DoesNotExistException $e) {
+ } catch (DoesNotExistException | MultipleObjectsReturnedException $e) {
+ if ($e instanceof MultipleObjectsReturnedException) {
+ // This is unlikely to happen but not impossible.
+ // Let's wipe any existing accounts and start fresh
+ $this->aliasMapper->deleteProvisionedAliasesByUid($user->getUID());
+ $this->mailAccountMapper->deleteProvisionedAccountsByUid($user->getUID());
+ }
+
// Fine, then we create a new one
- $new = new MailAccount();
- $new->setUserId($user->getUID());
+ $mailAccount = new MailAccount();
+ $mailAccount->setUserId($user->getUID());
- $this->mailAccountMapper->insert(
- $this->updateAccount($user, $new, $provisioning)
+ $mailAccount = $this->mailAccountMapper->insert(
+ $this->updateAccount($user, $mailAccount, $provisioning)
);
- return true;
- } catch (MultipleObjectsReturnedException $e) {
- // This is unlikely to happen but not impossible.
- // Let's wipe any existing accounts and start fresh
- $this->mailAccountMapper->deleteProvisionedAccountsByUid($user->getUID());
+ }
- $new = new MailAccount();
- $new->setUserId($user->getUID());
+ // @TODO: Remove method_exists once Mail requires Nextcloud 22 or above
+ if (method_exists(ILDAPProvider::class, 'getMultiValueUserAttribute')) {
+ try {
+ $provisioning = $this->ldapAliasesIntegration($provisioning, $user);
+ } catch (\Throwable $e) {
+ $this->logger->warning('Request to provision mail aliases failed', ['exception' => $e]);
+ // return here to avoid provisioning of aliases.
+ return true;
+ }
- $this->mailAccountMapper->insert(
- $this->updateAccount($user, $new, $provisioning)
- );
- return true;
+ try {
+ $this->deleteOrphanedAliases($user->getUID(), $mailAccount->getId(), $provisioning->getAliases());
+ } catch (\Throwable $e) {
+ $this->logger->warning('Deleting orphaned aliases failed', ['exception' => $e]);
+ }
+
+ try {
+ $this->createNewAliases($user->getUID(), $mailAccount->getId(), $provisioning->getAliases(), $user->getDisplayName());
+ } catch (\Throwable $e) {
+ $this->logger->warning('Creating new aliases failed', ['exception' => $e]);
+ }
}
- return false;
+
+ return true;
}
+ /**
+ * @throws ValidationException
+ * @throws \Exception
+ */
public function newProvisioning(array $data): void {
- try {
- $provisioning = $this->provisioningMapper->validate(
- $data
- );
- } catch (ValidationException $e) {
- throw $e;
- }
+ $provisioning = $this->provisioningMapper->validate($data);
$this->provisioningMapper->insert($provisioning);
}
+ /**
+ * @throws ValidationException
+ * @throws \Exception
+ */
public function updateProvisioning(array $data): void {
- try {
- $provisioning = $this->provisioningMapper->validate(
- $data
- );
- } catch (ValidationException $e) {
- throw $e;
- }
-
+ $provisioning = $this->provisioningMapper->validate($data);
$this->provisioningMapper->update($provisioning);
}
diff --git a/lib/Settings/AdminSettings.php b/lib/Settings/AdminSettings.php
index 2a8d0fd4e..428201244 100644
--- a/lib/Settings/AdminSettings.php
+++ b/lib/Settings/AdminSettings.php
@@ -29,6 +29,7 @@ use OCA\Mail\AppInfo\Application;
use OCA\Mail\Service\Provisioning\Manager as ProvisioningManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IInitialStateService;
+use OCP\LDAP\ILDAPProvider;
use OCP\Settings\ISettings;
class AdminSettings implements ISettings {
@@ -52,6 +53,14 @@ class AdminSettings implements ISettings {
$this->provisioningManager->getConfigs()
);
+ $this->initialStateService->provideLazyInitialState(
+ Application::APP_ID,
+ 'ldap_aliases_integration',
+ function () {
+ return method_exists(ILDAPProvider::class, 'getMultiValueUserAttribute');
+ }
+ );
+
return new TemplateResponse(Application::APP_ID, 'settings-admin');
}
diff --git a/src/components/AccountSettings.vue b/src/components/AccountSettings.vue
index c6503a03e..0af5beb70 100644
--- a/src/components/AccountSettings.vue
+++ b/src/components/AccountSettings.vue
@@ -28,13 +28,15 @@
:show-navigation="true">
<AppSettingsSection
:title="t('mail', 'Account settings')">
- <strong>{{ displayName }}</strong> &lt;{{ email }}&gt;
- <a
- v-if="!account.provisioningId"
- class="button icon-rename"
- :title="t('mail', 'Change name')"
- @click="handleClick" />
- <AliasSettings v-if="!account.provisioningId" :account="account" />
+ <div class="alias-item">
+ <p><strong>{{ displayName }}</strong> &lt;{{ email }}&gt;</p>
+ <a
+ v-if="!account.provisioningId"
+ class="button icon-rename"
+ :title="t('mail', 'Change name')"
+ @click="handleClick" />
+ </div>
+ <AliasSettings :account="account" />
</AppSettingsSection>
<AppSettingsSection :title="t('mail', 'Signature')">
<p class="settings-hint">
@@ -178,6 +180,11 @@ export default {
</script>
<style lang="scss" scoped>
+.alias-item {
+ display: flex;
+ justify-content: space-between;
+}
+
::v-deep .modal-container {
display: block;
overflow: scroll;
diff --git a/src/components/AliasForm.vue b/src/components/AliasForm.vue
new file mode 100644
index 000000000..a88df8803
--- /dev/null
+++ b/src/components/AliasForm.vue
@@ -0,0 +1,158 @@
+<!--
+ - @copyright 2021 Daniel Kesselberg <mail@danielkesselberg.de>
+ -
+ - @author 2021 Daniel Kesselberg <mail@danielkesselberg.de>
+ -
+ - @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>
+ <form v-if="showForm" class="alias-form" @submit.prevent="updateAlias">
+ <div>
+ <input v-model="changeName"
+ type="text"
+ required>
+ <input v-model="changeAlias"
+ type="email"
+ :disabled="alias.provisioned"
+ required>
+ </div>
+ <div class="button-group">
+ <button
+ class="icon"
+ type="submit"
+ :class="loading ? 'icon-loading-small-dark' : 'icon-checkmark'"
+ :title="t('mail', 'Update alias')" />
+ </div>
+ </form>
+ <div v-else class="alias-item">
+ <p><strong>{{ alias.name }}</strong> &lt;{{ alias.alias }}&gt;</p>
+ <div class="button-group">
+ <button
+ class="icon icon-rename"
+ :title="t('mail', 'Show update alias form')"
+ @click="showForm = true" />
+ <button v-if="!alias.provisioned"
+ class="icon"
+ :title="t('mail', 'Delete alias')"
+ :class="loading ? 'icon-loading-small-dark' : 'icon-delete'"
+ @click="deleteAlias" />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+import logger from '../logger'
+
+export default {
+ name: 'AliasForm',
+ props: {
+ account: {
+ type: Object,
+ required: true,
+ },
+ alias: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ changeAlias: this.alias.alias,
+ changeName: this.alias.name,
+ showForm: false,
+ loading: false,
+ }
+ },
+ methods: {
+ async updateAlias(e) {
+ this.loading = true
+
+ await this.$store.dispatch('updateAlias', {
+ account: this.account,
+ aliasId: this.alias.id,
+ alias: this.changeAlias,
+ name: this.changeName,
+ })
+
+ logger.debug('updated alias', {
+ accountId: this.account.id,
+ aliasId: this.alias.id,
+ alias: this.changeAlias,
+ name: this.changeName,
+ })
+
+ this.showForm = false
+ this.loading = false
+ },
+ async deleteAlias() {
+ this.loading = true
+
+ await this.$store.dispatch('deleteAlias', {
+ account: this.account,
+ aliasId: this.alias.id,
+ })
+
+ logger.debug('deleted alias', {
+ accountId: this.account.id,
+ aliasId: this.alias.id,
+ alias: this.alias.alias,
+ name: this.alias.name,
+ })
+
+ this.showForm = false
+ this.loading = false
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.alias-form, .alias-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.button-group {
+ display: flex;
+ align-items: center;
+}
+
+.icon {
+ background-color: var(--color-main-background);
+ border: none;
+ opacity: 0.7;
+
+ &:hover, &:focus {
+ opacity: 1;
+ }
+}
+
+.icon-checkmark {
+ background-image: var(--icon-checkmark-000);
+}
+
+.icon-delete {
+ background-image: var(--icon-delete-000);
+}
+
+.icon-rename {
+ background-image: var(--icon-rename-000);
+}
+</style>
diff --git a/src/components/AliasSettings.vue b/src/components/AliasSettings.vue
index c7fd79025..7f579d010 100644
--- a/src/components/AliasSettings.vue
+++ b/src/components/AliasSettings.vue
@@ -20,44 +20,40 @@
<template>
<div>
<ul class="aliases-list">
- <li v-for="curAlias in aliases" :key="curAlias.id">
- <strong>{{ curAlias.name }}</strong> &lt;{{ curAlias.alias }}&gt;
-
- <button class="icon-delete" @click="deleteAlias(curAlias)" />
+ <li v-for="alias in aliases" :key="alias.id">
+ <AliasForm :account="account" :alias="alias" />
+ </li>
+ <li v-if="showForm">
+ <form id="createAliasForm" @submit.prevent="createAlias">
+ <input v-model="newName"
+ type="text"
+ :placeholder="t('mail', 'Name')"
+ required>
+ <input v-model="newAlias"
+ type="email"
+ :placeholder="t('mail', 'Email-Address')"
+ required>
+ </form>
</li>
</ul>
- <div>
- <input
- v-if="addMode"
- id="alias-name"
- v-model="alias.aliasName"
- type="text"
- :placeholder="t('mail', 'Name')"
- :disabled="loading">
-
- <input
- v-if="addMode"
- id="alias"
- ref="email"
- v-model="alias.alias"
- type="email"
- :placeholder="t('mail', 'Mail Address')"
- :disabled="loading">
- </div>
-
- <div>
- <button v-if="!addMode" class="primary icon-add" @click="enabledAddMode">
+ <div v-if="!account.provisioningId">
+ <button v-if="!showForm" class="primary icon-add" @click="showForm = true">
{{ t('mail', 'Add alias') }}
</button>
- <button
- v-if="addMode"
+ <button v-if="showForm"
class="primary"
:class="loading ? 'icon-loading-small-dark' : 'icon-checkmark-white'"
- :disabled="loading"
- @click="saveAlias">
- {{ t('mail', 'Save') }}
+ type="submit"
+ form="createAliasForm"
+ :disabled="loading">
+ {{ t('mail', 'Create alias') }}
+ </button>
+ <button v-if="showForm"
+ class="button-text"
+ @click="resetCreate">
+ {{ t("mail", "Cancel") }}
</button>
</div>
</div>
@@ -65,10 +61,11 @@
<script>
import logger from '../logger'
-import Vue from 'vue'
+import AliasForm from './AliasForm'
export default {
name: 'AliasSettings',
+ components: { AliasForm },
props: {
account: {
type: Object,
@@ -77,9 +74,10 @@ export default {
},
data() {
return {
- addMode: false,
+ newAlias: '',
+ newName: this.account.name,
+ showForm: false,
loading: false,
- alias: { aliasName: this.account.name, alias: '' },
}
},
computed: {
@@ -88,25 +86,28 @@ export default {
},
},
methods: {
- enabledAddMode() {
- this.addMode = true
- Vue.nextTick(this.focusEmailField)
- },
- focusEmailField() {
- this.$refs.email.focus()
- },
- async deleteAlias(alias) {
+ async createAlias() {
this.loading = true
- await this.$store.dispatch('deleteAlias', { account: this.account, aliasToDelete: alias })
- logger.info('alias deleted')
+
+ await this.$store.dispatch('createAlias', {
+ account: this.account,
+ alias: this.newAlias,
+ name: this.newName,
+ })
+
+ logger.debug('created alias', {
+ accountId: this.account.id,
+ alias: this.newAlias,
+ name: this.newName,
+ })
+
+ this.resetCreate()
this.loading = false
},
- async saveAlias() {
- this.loading = true
- await this.$store.dispatch('createAlias', { account: this.account, aliasToAdd: this.alias })
- logger.info('alias added')
- this.alias = { aliasName: this.account.name, alias: '' }
- this.loading = false
+ resetCreate() {
+ this.newAlias = ''
+ this.newName = this.account.name
+ this.showForm = false
},
},
}
@@ -122,23 +123,23 @@ export default {
left: 14px;
}
}
-input {
- width: 195px;
-}
-.aliases-list {
- margin: 0.5rem 0rem;
-}
-.icon-delete {
- vertical-align: bottom;
- background-image: var(--icon-delete-000);
- background-color: var(--color-main-background);
+
+.button-text {
+ background-color: transparent;
border: none;
- opacity: 0.7;
+ color: var(--color-text-maxcontrast);
+ font-weight: normal;
+
&:hover,
&:focus {
- opacity: 1;
+ color: var(--color-main-text);
}
}
+
+input {
+ width: 195px;
+}
+
.icon-add {
background-image: var(--icon-add-fff);
}
diff --git a/src/components/settings/ProvisioningSettings.vue b/src/components/settings/ProvisioningSettings.vue
index 9b963940d..276d8a306 100644
--- a/src/components/settings/ProvisioningSettings.vue
+++ b/src/components/settings/ProvisioningSettings.vue
@@ -281,6 +281,36 @@
</div>
</div>
</div>
+ <div v-if="ldapAliasesIntegration" class="settings-group">
+ <div class="group-title">
+ {{ t('mail', 'LDAP aliases integration') }}
+ </div>
+ <div class="group-inputs">
+ <div>
+ <input
+ :id="'mail-provision-ldap-aliases-provisioning' + setting.id"
+ v-model="ldapAliasesProvisioning"
+ type="checkbox"
+ class="checkbox">
+ <label :for="'mail-provision-ldap-aliases-provisioning' + setting.id">
+ {{ t('mail', 'Enable ldap aliases integration') }}
+ </label>
+ <p>{{ t('mail', 'The ldap aliases integration reads a attribute from the configured ldap directory to provision email aliases.') }}</p>
+ </div>
+ <div>
+ <label :for="'mail-provision-ldap-aliases-attribute' + setting.id">
+ {{ t('mail', 'LDAP attribute for aliases') }}*
+ <br>
+ <input :id="'mail-provision-ldap-aliases-attribute' + setting.id"
+ v-model="ldapAliasesAttribute"
+ :disabled="loading"
+ :required="ldapAliasesProvisioning"
+ type="text">
+ </label>
+ <p>{{ t('mail', 'A multi value attribute to provision email aliases. For each value an alias is created. Alias existing in Nextcloud but not in the ldap directory are deleted.') }}</p>
+ </div>
+ </div>
+ </div>
<div class="settings-group">
<div class="group-title" />
<div class="group-inputs">
@@ -321,6 +351,9 @@
<script>
import logger from '../../logger'
import ProvisionPreview from './ProvisionPreview'
+import { loadState } from '@nextcloud/initial-state'
+
+const ldapAliasesIntegration = loadState('mail', 'ldap_aliases_integration', false)
export default {
name: 'ProvisioningSettings',
@@ -373,6 +406,9 @@ export default {
uid: 'user321',
email: 'user@domain.com',
},
+ ldapAliasesIntegration,
+ ldapAliasesProvisioning: this.setting.ldapAliasesProvisioning || false,
+ ldapAliasesAttribute: this.setting.ldapAliasesAttribute || '',
loading: false,
}
},
@@ -394,6 +430,8 @@ export default {
sieveHost: this.sieveHost,
sievePort: this.sievePort,
sieveSslMode: this.sieveSslMode,
+ ldapAliasesProvisioning: this.ldapAliasesProvisioning,
+ ldapAliasesAttribute: this.ldapAliasesAttribute,
}
},
},
@@ -423,6 +461,8 @@ export default {
sieveHost: this.sieveHost,
sievePort: this.sievePort,
sieveSslMode: this.sieveSslMode,
+ ldapAliasesProvisioning: this.ldapAliasesProvisioning,
+ ldapAliasesAttribute: this.ldapAliasesAttribute,
})
logger.info('provisioning setting updated')
@@ -462,6 +502,7 @@ export default {
.group-title {
min-width: 100px;
+ max-width: 100px;
text-align: right;
margin: 10px;
font-weight: bold;
diff --git a/src/service/AliasService.js b/src/service/AliasService.js
index 6b0cc15b3..0f3fcb0b5 100644
--- a/src/service/AliasService.js
+++ b/src/service/AliasService.js
@@ -1,29 +1,66 @@
import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
-export const createAlias = async(account, data) => {
+/**
+ * @typedef {Object} Alias
+ * @property {number} id
+ * @property {string} alias
+ * @property {string} name
+ * @property {string} signature
+ * @property {boolean} provisioned
+ */
+
+/**
+ * @param {number} accountId id of account
+ * @param {string} alias new alias
+ * @param {string} aliasName new alias name
+ * @returns {Promise<Alias>}
+ */
+export const createAlias = async(accountId, alias, aliasName) => {
const url = generateUrl('/apps/mail/api/accounts/{id}/aliases', {
- id: account.accountId,
+ id: accountId,
})
- return axios.post(url, data).then((resp) => resp.data).catch((e) => {
- if (e.response && e.response.status === 400) {
- throw e.response.data
- }
-
- throw e
- })
+ return axios.post(url, { alias, aliasName }).then(resp => resp.data)
}
-export const deleteAlias = async(account, alias) => {
+/**
+ * @param {number} accountId id of account
+ * @param {number} aliasId if of alias
+ * @returns {Promise<Alias>}
+ */
+export const deleteAlias = async(accountId, aliasId) => {
const url = generateUrl('/apps/mail/api/accounts/{id}/aliases/{aliasId}', {
- id: account.accountId,
- aliasId: alias.id,
+ id: accountId,
+ aliasId,
})
return axios.delete(url).then((resp) => resp.data)
}
+/**
+ * @param {number} accountId id of account
+ * @param {number} aliasId if of alias
+ * @param {string} alias new alias
+ * @param {string} aliasName new alias name
+ * @returns {Promise<Alias>}
+ */
+export const updateAlias = async(accountId, aliasId, alias, aliasName) => {
+ const url = generateUrl(
+ '/apps/mail/api/accounts/{id}/aliases/{aliasId}', {
+ id: accountId,
+ aliasId,
+ })
+
+ return axios.put(url, { alias, aliasName }).then(resp => resp.data)
+}
+
+/**
+ * @param {number} accountId id of account
+ * @param {number} aliasId id of alias
+ * @param {string} signature new signature
+ * @returns {Promise<Alias>}
+ */
export const updateSignature = async(accountId, aliasId, signature) => {
const url = generateUrl(
'/apps/mail/api/accounts/{id}/aliases/{aliasId}/signature', {
@@ -31,5 +68,5 @@ export const updateSignature = async(accountId, aliasId, signature) => {
aliasId,
})
- return axios.put(url, { signature })
+ return axios.put(url, { signature }).then(resp => resp.data)
}
diff --git a/src/store/actions.js b/src/store/actions.js
index 6e42c0402..5f00bdf0e 100644
--- a/src/store/actions.js
+++ b/src/store/actions.js
@@ -739,17 +739,36 @@ export default {
throw err
}
},
- async createAlias({ commit }, { account, aliasToAdd }) {
- const alias = await AliasService.createAlias(account, aliasToAdd)
- commit('createAlias', { account, alias })
+ async createAlias({ commit }, { account, alias, name }) {
+ const entity = await AliasService.createAlias(account.id, alias, name)
+ commit('createAlias', {
+ account,
+ alias: entity,
+ })
+ },
+ async deleteAlias({ commit }, { account, aliasId }) {
+ const entity = await AliasService.deleteAlias(account.id, aliasId)
+ commit('deleteAlias', {
+ account,
+ aliasId: entity.id,
+ })
},
- async deleteAlias({ commit }, { account, aliasToDelete }) {
- await AliasService.deleteAlias(account, aliasToDelete)
- commit('deleteAlias', { account, alias: aliasToDelete })
+ async updateAlias({ commit }, { account, aliasId, alias, name }) {
+ const entity = await AliasService.updateAlias(account.id, aliasId, alias, name)
+ commit('patchAlias', {
+ account,
+ aliasId: entity.id,
+ data: { alias: entity.alias, name: entity.name },
+ })
+ commit('editAccount', account)
},
async updateAliasSignature({ commit }, { account, aliasId, signature }) {
- await AliasService.updateSignature(account.id, aliasId, signature)
- commit('patchAlias', { account, aliasId, data: { signature } })
+ const entity = await AliasService.updateSignature(account.id, aliasId, signature)
+ commit('patchAlias', {
+ account,
+ aliasId: entity.id,
+ data: { signature: entity.signature },
+ })
commit('editAccount', account)
},
async renameMailbox({ commit }, { account, mailbox, newName }) {
diff --git a/src/store/mutations.js b/src/store/mutations.js
index b9f10a823..b2efd6492 100644
--- a/src/store/mutations.js
+++ b/src/store/mutations.js
@@ -307,12 +307,17 @@ export default {
createAlias(state, { account, alias }) {
account.aliases.push(alias)
},
- deleteAlias(state, { account, alias }) {
- account.aliases.splice(account.aliases.indexOf(alias), 1)
+ deleteAlias(state, { account, aliasId }) {
+ const index = account.aliases.findIndex(temp => aliasId === temp.id)
+ if (index !== -1) {
+ account.aliases.splice(index, 1)
+ }
},
patchAlias(state, { account, aliasId, data }) {
- const index = account.aliases.findIndex((temp) => aliasId === temp.id)
- account.aliases[index] = Object.assign({}, account.aliases[index], data)
+ const index = account.aliases.findIndex(temp => aliasId === temp.id)
+ if (index !== -1) {
+ account.aliases[index] = Object.assign({}, account.aliases[index], data)
+ }
},
setMailboxUnreadCount(state, { id, unread }) {
Vue.set(state.mailboxes[id], 'unread', unread ?? 0)
diff --git a/tests/Unit/Controller/AliasesControllerTest.php b/tests/Unit/Controller/AliasesControllerTest.php
index fa44ea4f0..81869237d 100644
--- a/tests/Unit/Controller/AliasesControllerTest.php
+++ b/tests/Unit/Controller/AliasesControllerTest.php
@@ -26,6 +26,7 @@ use OCA\Mail\Controller\AliasesController;
use OCA\Mail\Db\Alias;
use OCA\Mail\Db\AliasMapper;
use OCA\Mail\Db\MailAccountMapper;
+use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\NotImplemented;
use OCA\Mail\Service\AliasesService;
use OCP\AppFramework\Db\DoesNotExistException;
@@ -88,8 +89,54 @@ class AliasesControllerTest extends TestCase {
}
public function testUpdate(): void {
- $this->expectException(NotImplemented::class);
- $this->controller->update();
+ $alias = new Alias();
+ $alias->setId(101);
+ $alias->setAccountId(200);
+ $alias->setName('Jane Doe');
+ $alias->setAlias('jane@doe.com');
+
+ $this->aliasMapper->expects($this->once())
+ ->method('find')
+ ->with($alias->getId(), $this->userId)
+ ->willReturn($alias);
+
+ $this->aliasMapper->expects($this->once())
+ ->method('update')
+ ->willReturnArgument(0);
+
+ $response = $this->controller->update($alias->getId(), 'john@doe.com', 'John Doe');
+ /** @var Alias $data */
+ $data = $response->getData();
+
+ $this->assertInstanceOf(Alias::class, $response->getData());
+ $this->assertEquals('john@doe.com', $data->getAlias());
+ $this->assertEquals('John Doe', $data->getName());
+ }
+
+ public function testUpdateProvisioned(): void {
+ $alias = new Alias();
+ $alias->setId(201);
+ $alias->setAccountId(300);
+ $alias->setName('Jane Doe');
+ $alias->setAlias('jane@doe.com');
+ $alias->setProvisioningId(100);
+
+ $this->aliasMapper->expects($this->once())
+ ->method('find')
+ ->with($alias->getId(), $this->userId)
+ ->willReturn($alias);
+
+ $this->aliasMapper->expects($this->once())
+ ->method('update')
+ ->willReturnArgument(0);
+
+ $response = $this->controller->update($alias->getId(), 'john@doe.com', 'John Doe');
+ /** @var Alias $data */
+ $data = $response->getData();
+
+ $this->assertInstanceOf(Alias::class, $data);
+ $this->assertEquals('jane@doe.com', $data->getAlias());
+ $this->assertEquals('John Doe', $data->getName());
}
public function testDestroy(): void {
@@ -106,7 +153,7 @@ class AliasesControllerTest extends TestCase {
$this->aliasMapper->expects($this->once())
->method('delete')
- ->with($alias);
+ ->willReturnArgument(0);
$expectedResponse = new JSONResponse($alias);
$response = $this->controller->destroy($alias->getId());
@@ -114,6 +161,25 @@ class AliasesControllerTest extends TestCase {
$this->assertEquals($expectedResponse, $response);
}
+ public function testDestroyProvisioned(): void {
+ $this->expectException(ClientException::class);
+ $this->expectExceptionMessage('Deleting a provisioned alias is not allowed.');
+
+ $alias = new Alias();
+ $alias->setId(201);
+ $alias->setAccountId(300);
+ $alias->setName('Jane Doe');
+ $alias->setAlias('jane@doe.com');
+ $alias->setProvisioningId(100);
+
+ $this->aliasMapper->expects($this->once())
+ ->method('find')
+ ->with($alias->getId(), $this->userId)
+ ->willReturn($alias);
+
+ $this->controller->destroy($alias->getId());
+ }
+
public function testCreate(): void {
$alias = new Alias();
$alias->setId(102);
@@ -172,10 +238,11 @@ class AliasesControllerTest extends TestCase {
->method('find')
->willReturn($alias);
$this->aliasMapper->expects($this->once())
- ->method('update');
+ ->method('update')
+ ->willReturnArgument(0);
$expectedResponse = new JSONResponse(
- [],
+ $alias,
Http::STATUS_OK
);
$response = $this->controller->updateSignature(
diff --git a/tests/Unit/Service/AliasesServiceTest.php b/tests/Unit/Service/AliasesServiceTest.php
index ddbe4ed04..17c5d8671 100644
--- a/tests/Unit/Service/AliasesServiceTest.php
+++ b/tests/Unit/Service/AliasesServiceTest.php
@@ -25,6 +25,7 @@ use ChristophWurst\Nextcloud\Testing\TestCase;
use OCA\Mail\Db\Alias;
use OCA\Mail\Db\AliasMapper;
use OCA\Mail\Db\MailAccountMapper;
+use OCA\Mail\Exception\ClientException;
use OCA\Mail\Service\AliasesService;
use OCP\AppFramework\Db\DoesNotExistException;
@@ -42,15 +43,11 @@ class AliasesServiceTest extends TestCase {
/** @var MailAccountMapper */
private $mailAccountMapper;
- /** @var Alias */
- private $alias;
-
protected function setUp(): void {
parent::setUp();
$this->aliasMapper = $this->createMock(AliasMapper::class);
$this->mailAccountMapper = $this->createMock(MailAccountMapper::class);
- $this->alias = $this->createMock(Alias::class);
$this->service = new AliasesService(
$this->aliasMapper,
@@ -58,36 +55,42 @@ class AliasesServiceTest extends TestCase {
);
}
- public function testFindAll() {
- $accountId = 123;
- $this->aliasMapper->expects($this->once())
+ public function testFindAll(): void {
+ $entity = new Alias();
+ $entity->setAccountId(200);
+ $entity->setAlias('jane@doe.com');
+ $entity->setName('Jane Doe');
+
+ $this->aliasMapper->expects(self::once())
->method('findAll')
- ->with($accountId, $this->user)
- ->will($this->returnValue([$this->alias]));
+ ->with($entity->getAccountId(), $this->user)
+ ->willReturn([$entity]);
- $actual = $this->service->findAll($accountId, $this->user);
+ $aliases = $this->service->findAll($entity->getAccountId(), $this->user);
- $expected = [
- $this->alias
- ];
- $this->assertEquals($expected, $actual);
+ $this->assertEquals([$entity], $aliases);
}
- public function testFind() {
- $aliasId = 123;
- $this->aliasMapper->expects($this->once())
+ public function testFind(): void {
+ $entity = new Alias();
+ $entity->setId(101);
+ $entity->setAccountId(200);
+ $entity->setAlias('jane@doe.com');
+ $entity->setName('Jane Doe');
+
+ $this->aliasMapper->expects(self::once())
->method('find')
- ->with($aliasId, $this->user)
- ->will($this->returnValue($this->alias));
+ ->with($entity->getId(), $this->user)
+ ->willReturn($entity);
- $actual = $this->service->find($aliasId, $this->user);
+ $alias = $this->service->find($entity->getId(), $this->user);
- $expected = $this->alias;
- $this->assertEquals($expected, $actual);
+ $this->assertEquals($entity, $alias);
}
public function testCreate(): void {
$entity = new Alias();
+ $entity->setId(101);
$entity->setAccountId(200);
$entity->setAlias('jane@doe.com');
$entity->setName('Jane Doe');
@@ -95,7 +98,7 @@ class AliasesServiceTest extends TestCase {
$this->mailAccountMapper->expects($this->once())
->method('find');
- $this->aliasMapper->expects($this->once())
+ $this->aliasMapper->expects(self::once())
->method('insert')
->willReturnCallback(static function (Alias $alias) {
$alias->setId(100);
@@ -123,7 +126,7 @@ class AliasesServiceTest extends TestCase {
$entity->setAlias('jane@doe.com');
$entity->setName('Jane Doe');
- $this->mailAccountMapper->expects($this->once())
+ $this->mailAccountMapper->expects(self::once())
->method('find')
->willThrowException(new DoesNotExistException('Account does not exist'));
@@ -135,38 +138,70 @@ class AliasesServiceTest extends TestCase {
);
}
- public function testDelete() {
- $aliasId = 123;
- $this->aliasMapper->expects($this->once())
+ public function testDelete(): void {
+ $entity = new Alias();
+ $entity->setId(101);
+ $entity->setAccountId(200);
+ $entity->setName('Jane Doe');
+ $entity->setAlias('jane@doe.com');
+
+ $this->aliasMapper->expects(self::once())
->method('find')
- ->with($aliasId, $this->user)
- ->will($this->returnValue($this->alias));
- $this->aliasMapper->expects($this->once())
+ ->with($entity->getId(), $this->user)
+ ->willReturn($entity);
+ $this->aliasMapper->expects(self::once())
->method('delete')
- ->with($this->alias);
+ ->willReturnArgument(0);
- $this->service->delete($aliasId, $this->user);
+ $alias = $this->service->delete($this->user, $entity->getId());
+
+ $this->assertEquals($entity, $alias);
+ }
+
+ public function testDeleteProvisioned(): void {
+ $this->expectException(ClientException::class);
+ $this->expectExceptionMessage('Deleting a provisioned alias is not allowed.');
+
+ $entity = new Alias();
+ $entity->setId(201);
+ $entity->setAccountId(300);
+ $entity->setName('Jane Doe');
+ $entity->setAlias('jane@doe.com');
+ $entity->setProvisioningId(100);
+
+ $this->aliasMapper->expects(self::once())
+ ->method('find')
+ ->with($entity->getId(), $this->user)
+ ->willReturn($entity);
+
+ $this->service->delete($this->user, $entity->getId());
}
public function testUpdateSignature(): void {
- $aliasId = 400;
- $this->aliasMapper->expects($this->once())
+ $entity = new Alias();
+ $entity->setId(101);
+ $entity->setAccountId(200);
+ $entity->setName('Jane Doe');
+ $entity->setAlias('jane@doe.com');
+
+ $this->aliasMapper->expects(self::once())
->method('find')
- ->with($aliasId, $this->user)
- ->willReturn($this->alias);
- $this->aliasMapper->expects($this->once())
- ->method('update');
+ ->with($entity->getId(), $this->user)
+ ->willReturn($entity);
+ $this->aliasMapper->expects(self::once())
+ ->method('update')
+ ->willReturnArgument(0);
- $this->service->updateSignature($this->user, $aliasId, 'Kind regards<br>Herbert');
+ $this->service->updateSignature($this->user, $entity->getId(), 'Kind regards<br>Herbert');
}
public function testUpateSignatureInvalidAliasId(): void {
$this->expectException(DoesNotExistException::class);
- $this->aliasMapper->expects($this->once())
+ $this->aliasMapper->expects(self::once())
->method('find')
->willThrowException(new DoesNotExistException('Alias does not exist'));
- $this->aliasMapper->expects($this->never())
+ $this->aliasMapper->expects(self::never())
->method('update');
$this->service->updateSignature($this->user, '999999', 'Kind regards<br>Herbert');