* @copyright 2017 * @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 . * */ namespace OCA\Circles\Service; use Exception; use OCA\Circles\Circles\FileSharingBroadcaster; use OCA\Circles\Db\DeprecatedCirclesRequest; use OCA\Circles\Db\DeprecatedMembersRequest; use OCA\Circles\Exceptions\CircleAlreadyExistsException; use OCA\Circles\Exceptions\CircleDoesNotExistException; use OCA\Circles\Exceptions\MemberAlreadyExistsException; use OCA\Circles\Exceptions\MemberDoesNotExistException; use OCA\Circles\Exceptions\NotLocalMemberException; use OCA\Circles\Model\DeprecatedCircle; use OCA\Circles\Model\DavCard; use OCA\Circles\Model\DeprecatedMember; use OCA\DAV\CardDAV\CardDavBackend; use OCP\App\ManagerEvent; use OCP\Federation\ICloudIdManager; use OCP\IUserManager; use Symfony\Component\EventDispatcher\GenericEvent; /** * Class DavService * @deprecated * @package OCA\Circles\Service */ class DavService { /** @var string */ private $userId; /** @var IUserManager */ private $userManager; /** @var CardDavBackend */ private $cardDavBackend; /** @var ICloudIdManager */ private $cloudManager; /** @var FileSharingBroadcaster */ private $fileSharingBroadcaster; /** @var MembersService */ private $membersService; /** @var DeprecatedCirclesRequest */ private $circlesRequest; /** @var DeprecatedMembersRequest */ private $membersRequest; /** @var ConfigService */ private $configService; /** @var MiscService */ private $miscService; /** @var array */ private $migratedBooks = []; /** * TimezoneService constructor. * * @param string $userId * @param IUserManager $userManager * @param CardDavBackend $cardDavBackend * @param ICloudIdManager $cloudManager * @param FileSharingBroadcaster $fileSharingBroadcaster * @param MembersService $membersService * @param DeprecatedCirclesRequest $circlesRequest * @param DeprecatedMembersRequest $membersRequest * @param ConfigService $configService * @param MiscService $miscService */ public function __construct( $userId, IUserManager $userManager, CardDavBackend $cardDavBackend, ICloudIdManager $cloudManager, FileSharingBroadcaster $fileSharingBroadcaster, MembersService $membersService, DeprecatedCirclesRequest $circlesRequest, DeprecatedMembersRequest $membersRequest, ConfigService $configService, MiscService $miscService ) { $this->userId = $userId; $this->userManager = $userManager; $this->cardDavBackend = $cardDavBackend; $this->cloudManager = $cloudManager; $this->fileSharingBroadcaster = $fileSharingBroadcaster; $this->membersService = $membersService; $this->circlesRequest = $circlesRequest; $this->membersRequest = $membersRequest; $this->configService = $configService; $this->miscService = $miscService; } /** * @param ManagerEvent $event */ public function onAppEnabled(ManagerEvent $event) { if ($event->getAppID() !== 'circles') { return; } try { // $this->migration(); } catch (Exception $e) { } } /** * @param GenericEvent $event */ public function onCreateCard(GenericEvent $event) { $davCard = $this->generateDavCard($event); $this->manageDavCard($davCard); } /** * @param GenericEvent $event */ public function onUpdateCard(GenericEvent $event) { $davCard = $this->generateDavCard($event); $this->manageDavCard($davCard); } /** * @param GenericEvent $event */ public function onDeleteCard(GenericEvent $event) { $davCard = $this->generateDavCard($event, true); $this->miscService->log('Deleting Card: ' . json_encode($davCard), 1); $this->membersRequest->removeMembersByContactId($davCard->getUniqueId(), DeprecatedMember::TYPE_USER); $this->manageDeprecatedCircles($davCard->getAddressBookId()); $this->manageDeprecatedMembers($davCard); } /** * @param GenericEvent $event * @param bool $tiny * * @return DavCard */ private function generateDavCard(GenericEvent $event, bool $tiny = false): DavCard { $addressBookId = $event->getArgument('addressBookId'); $cardUri = $event->getArgument('cardUri'); $davCard = new DavCard(); $davCard->setAddressBookId($addressBookId); $davCard->setCardUri($cardUri); if ($tiny) { return $davCard; } $cardData = $event->getArgument('cardData'); $davCard->setOwner($this->getOwnerFromAddressBook($addressBookId)); $davCard->importFromDav($cardData); return $davCard; } /** * @param int $bookId * @param array $card * * @return DavCard */ private function generateDavCardFromCard(int $bookId, array $card): DavCard { $this->miscService->log( 'generating DavCard Model from book=' . $bookId . ' from ' . json_encode($card), 0 ); $davCard = new DavCard(); $davCard->setAddressBookId($bookId); $davCard->setCardUri($card['uri']); $davCard->setOwner($this->getOwnerFromAddressBook($bookId)); $davCard->importFromDav($card['carddata']); $this->miscService->log('generated DavCard Model: ' . json_encode($davCard), 0); return $davCard; } /** * @param DavCard $davCard * */ private function manageDavCard(DavCard $davCard) { $this->miscService->log('Updating Card: ' . json_encode($davCard), 1); $this->manageCircles($davCard); $this->manageContact($davCard); } /** * @param DavCard $davCard */ private function manageContact(DavCard $davCard) { $this->manageDeprecatedMembers($davCard); switch ($this->getMemberType($davCard)) { case DavCard::TYPE_CONTACT: $this->manageRemoteContact($davCard); break; case DavCard::TYPE_LOCAL: $this->manageLocalContact($davCard); break; // case DavCard::TYPE_FEDERATED: // $this->manageFederatedContact($davCard); // break; } } /** * @param DavCard $davCard */ private function manageDeprecatedMembers(DavCard $davCard) { $this->miscService->log( 'Managing deprecated circles memberships from DavCard Model: ' . json_encode($davCard), 0 ); $circles = array_map( function (DeprecatedCircle $circle) { return $circle->getUniqueId(); }, $davCard->getCircles() ); $members = $this->membersRequest->getMembersByContactId($davCard->getUniqueId()); $this->miscService->log( 'Found ' . sizeof($members) . ' memberships with contactId=' . $davCard->getUniqueId(), 0 ); foreach ($members as $member) { if (!in_array($member->getCircleId(), $circles)) { $this->miscService->log( 'Removing membership ' . $member->getMemberId() . ' from ' . $member->getCircleId(), 0 ); $this->membersRequest->removeMember($member); } } } /** * @param DavCard $davCard */ private function manageLocalContact(DavCard $davCard) { foreach ($davCard->getCircles() as $circle) { $this->manageMember($circle, $davCard, DeprecatedMember::TYPE_USER); } } /** * @param DavCard $davCard */ private function manageRemoteContact(DavCard $davCard) { foreach ($davCard->getCircles() as $circle) { $this->manageMember($circle, $davCard, DeprecatedMember::TYPE_CONTACT); } } /** * @param DeprecatedCircle $circle * @param DavCard $davCard * @param int $type */ private function manageMember(DeprecatedCircle $circle, DavCard $davCard, int $type) { try { $member = $this->membersRequest->getContactMember($circle->getUniqueId(), $davCard->getUniqueId()); if ($member->getType() !== $type) { $this->membersRequest->removeMember($member); throw new MemberDoesNotExistException(); } } catch (MemberDoesNotExistException $e) { $member = new DeprecatedMember(); $member->setLevel(DeprecatedMember::LEVEL_MEMBER); $member->setStatus(DeprecatedMember::STATUS_MEMBER); $member->setContactId($davCard->getUniqueId()); $member->setType($type); $member->setCircleId($circle->getUniqueId()); $member->setUserId($davCard->getUserId()); $this->membersService->updateCachedName($member); try { $this->membersRequest->createMember($member); // if ($type === Member::TYPE_CONTACT) { // $this->fileSharingBroadcaster->sendMailAboutExistingShares($circle, $member); // } } catch (MemberAlreadyExistsException $e) { $this->membersRequest->checkMember($member, false); } } } /** * @param DavCard $davCard * * @return int */ private function getMemberType(DavCard $davCard): int { foreach (array_merge($davCard->getEmails(), $davCard->getClouds()) as $address) { try { $davCard->setUserId($this->isLocalMember($address)); return DavCard::TYPE_LOCAL; } catch (NotLocalMemberException $e) { } } $davCard->setUserId($davCard->getOwner() . ':' . $davCard->getContactId()); return DavCard::TYPE_CONTACT; } /** * @param string $address * * @return string * @throws NotLocalMemberException */ private function isLocalMember(string $address): string { if (strpos($address, '@') === false) { $user = $this->userManager->get($address); if ($user !== null) { return $user->getUID(); } throw new NotLocalMemberException(); } [$username, $domain] = explode('@', $address); if (in_array($domain, $this->configService->getAvailableHosts())) { $user = $this->userManager->get($username); if ($user !== null) { return $user->getUID(); } } throw new NotLocalMemberException(); } /** * @param DavCard $davCard */ private function manageCircles(DavCard $davCard) { $fromCard = $davCard->getGroups(); $current = array_map( function (DeprecatedCircle $circle) { return $circle->getContactGroupName(); }, $this->getCirclesFromBook($davCard->getAddressBookId()) ); $this->miscService->log( 'manage Circles from DavCard: ' . json_encode($fromCard) . ' - current: ' . json_encode($current) ); $this->manageNewCircles($davCard, $fromCard, $current); $this->manageDeprecatedCircles($davCard->getAddressBookId()); $this->assignCirclesToCard($davCard); } /** * @param DavCard $davCard * @param array $fromCard * @param array $current */ private function manageNewCircles(DavCard $davCard, array $fromCard, array $current) { foreach ($fromCard as $group) { if (in_array($group, $current)) { continue; } $user = $this->userManager->get($davCard->getOwner()); $circle = new DeprecatedCircle( $this->configService->contactsBackendType(), $group . ' - ' . $user->getDisplayName() ); $circle->setAltName($group); $circle->generateUniqueId(); $circle->setContactAddressBook($davCard->getAddressBookId()); $circle->setContactGroupName($group); $this->miscService->log( 'creating new Circle: ' . json_encode($circle) . ', with owner=' . $davCard->getOwner(), 0 ); try { $this->circlesRequest->createCircle($circle); $owner = new DeprecatedMember($davCard->getOwner(), DeprecatedMember::TYPE_USER, $circle->getUniqueId()); $owner->setLevel(DeprecatedMember::LEVEL_OWNER); $owner->setStatus(DeprecatedMember::STATUS_MEMBER); $this->membersService->updateCachedName($owner); $this->miscService->log('creating new Member: ' . json_encode($owner), 0); try { $this->membersRequest->createMember($owner); } catch (MemberAlreadyExistsException $e) { } } catch (CircleAlreadyExistsException $e) { } } } /** * @param DavCard $davCard */ private function assignCirclesToCard(DavCard $davCard) { $this->miscService->log('assigning Circles to DavCard Model: ' . json_encode($davCard), 0); foreach ($davCard->getGroups() as $group) { try { $davCard->addCircle( $this->circlesRequest->getFromContactGroup($davCard->getAddressBookId(), $group) ); } catch (CircleDoesNotExistException $e) { } } $this->miscService->log('assigned Circles to DavCard Model: ' . json_encode($davCard), 0); } /** * @param int $addressBookId * * @return DeprecatedCircle[] */ private function getCirclesFromBook(int $addressBookId): array { return $this->circlesRequest->getFromBook($addressBookId); } /** * @param int $length * * @return string * @deprecated */ protected function uuid(int $length = 0): string { $uuid = sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) ); if ($length > 0) { if ($length <= 16) { $uuid = str_replace('-', '', $uuid); } $uuid = substr($uuid, 0, $length); } return $uuid; } /** * @param int $bookId * * @return string */ public function getOwnerFromAddressBook(int $bookId): string { $this->miscService->log('Retrieving Owner from book:' . $bookId, 0); $data = $this->cardDavBackend->getAddressBookById($bookId); // let's assume the format is principals/users/OWNER $owner = substr($data['principaluri'], 17); $this->miscService->log('Retrieved Owner:' . $owner, 0); return $owner; } /** * @throws Exception */ public function migration() { if (!$this->configService->isContactsBackend()) { throw new Exception('Circles needs to be set as Contacts App Backend first'); } $this->manageDeprecatedContacts(); $this->manageDeprecatedCircles(); $users = $this->userManager->search(''); $this->miscService->log('initiating migration for ' . sizeof($users) . ' users', 0); foreach ($users as $user) { $this->miscService->log('retrieving books for user=' . $user->getUID(), 0); $books = $this->cardDavBackend->getAddressBooksForUser('principals/users/' . $user->getUID()); $this->miscService->log('initiating migration for user=' . $user->getUID(), 0); foreach ($books as $book) { $this->migrateBook($book['id']); } } } /** */ private function manageDeprecatedContacts() { $contacts = $this->membersRequest->getMembersByContactId(); $this->miscService->log( 'Managing Deprecated Contacts, checking ' . sizeof($contacts) . ' known contacts in database', 0 ); foreach ($contacts as $contact) { try { $this->getDavCardFromMember($contact); $this->miscService->log('Contact is not deprecated: ' . json_encode($contact)); } catch (MemberDoesNotExistException $e) { $this->miscService->log( 'Contact is deprecated and will be removed: ' . json_encode($contact) ); $this->membersRequest->removeMember($contact); } } $this->miscService->log('Deprecated Contacts managed', 0); } /** * @param int $bookId */ private function manageDeprecatedCircles(int $bookId = 0) { $knownBooks = [$bookId]; $this->miscService->log('Managing Deprecated Circles, using bookId: ' . $bookId, 0); if ($bookId > 0) { $knownBooks = []; $contacts = $this->membersRequest->getMembersByContactId(); $this->miscService->log(sizeof($contacts) . ' known members as contacts in Circles DB', 0); foreach ($contacts as $contact) { [$bookId,] = explode('/', $contact->getContactId(), 2); if (in_array($bookId, $knownBooks)) { continue; } $knownBooks[] = $bookId; } } $this->miscService->log('Known books: ' . json_encode($knownBooks), 0); foreach ($knownBooks as $bookId) { $this->miscService->log('retrieving local Circles data for bookId=' . $bookId, 0); $circles = $this->circlesRequest->getFromContactBook($bookId); $this->miscService->log( 'Known circles for bookId=' . $bookId . ': ' . json_encode($circles), 0 ); $fromBook = $this->getExistingCirclesFromBook($bookId); $this->miscService->log( 'Generated circles from bookId=' . $bookId . ': ' . json_encode($fromBook), 0 ); foreach ($circles as $circle) { if (in_array($circle->getContactGroupName(), $fromBook)) { continue; } $this->miscService->log( $circle->getUniqueId() . ' is a deprecated Circle and will be destroyed', 0 ); $this->membersRequest->removeAllFromCircle($circle->getUniqueId()); $this->circlesRequest->destroyCircle($circle->getUniqueId()); } } $this->miscService->log('Deprecated Circles managed', 0); } /** * @param int $bookId * * @return DeprecatedCircle[] */ private function getExistingCirclesFromBook(int $bookId): array { $circles = []; $cards = $this->cardDavBackend->getCards($bookId); $this->miscService->log( 'retrieving existing circles from bookId=' . $bookId . ' in ' . sizeof($cards) . ' cards', 0 ); foreach ($cards as $card) { $davCard = $this->generateDavCardFromCard($bookId, $card); $this->assignCirclesToCard($davCard); $circles = array_merge($circles, $davCard->getCircles()); } $this->miscService->log('Found ' . sizeof($circles) . ' Circles from book=' . $bookId, 0); $existing = array_unique( array_map( function (DeprecatedCircle $circle) { return $circle->getContactGroupName(); }, $circles ) ); $this->miscService->log( 'retrieved existing circles from book=' . $bookId . ': ' . json_encode($existing), 0 ); return $existing; } /** * @param DeprecatedMember $contact * * @return DavCard * @throws MemberDoesNotExistException */ public function getDavCardFromMember(DeprecatedMember $contact): DavCard { [$bookId, $cardUri] = explode('/', $contact->getContactId(), 2); $this->miscService->log('Retrieving DavCard from book:' . $bookId . ', uri:' . $cardUri, 0); $cards = $this->cardDavBackend->getCards($bookId); $this->miscService->log('Book contains ' . sizeof($cards) . ' cards', 0); foreach ($cards as $card) { if ($card['uri'] === $cardUri) { $davCard = $this->generateDavCardFromCard($bookId, $card); $this->miscService->log('Retrieved DavCard: ' . json_encode($card)); return $davCard; } } throw new MemberDoesNotExistException(); } /** * @param int $bookId */ private function migrateBook(int $bookId) { if (in_array($bookId, $this->migratedBooks)) { return; } $this->miscService->log('migrating book: ' . $bookId, 0); $owner = $this->getOwnerFromAddressBook($bookId); $cards = $this->cardDavBackend->getCards($bookId); $this->miscService->log('found ' . sizeof($cards) . 'cards from book=' . $bookId, 0); foreach ($cards as $card) { $davCard = new DavCard(); $davCard->setOwner($owner); $davCard->importFromDav($card['carddata']); $davCard->setAddressBookId($bookId); $davCard->setCardUri($card['uri']); $this->manageDavCard($davCard); } $this->migratedBooks[] = $bookId; } }