diff options
author | Christoph Wurst <ChristophWurst@users.noreply.github.com> | 2021-07-14 16:48:24 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-14 16:48:24 +0300 |
commit | a32f165f201c666490bd3c40a8b12539b262f7f7 (patch) | |
tree | 8516547fb3c7350a468505cd637faa91be505ccd /lib/Service | |
parent | a59a5fbdbb82eb6403c6d3e1694b90695d8c1638 (diff) | |
parent | 8fb14b5ffa3450b2c2201ff9bb42c589e4a4afb7 (diff) |
Merge pull request #5198 from nextcloud/enh/5173/ldap-aliases-provisioning
LDAP alias provisioning
Diffstat (limited to 'lib/Service')
-rw-r--r-- | lib/Service/AliasesService.php | 44 | ||||
-rw-r--r-- | lib/Service/Provisioning/Manager.php | 167 |
2 files changed, 161 insertions, 50 deletions
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); } |