]> source.dussan.org Git - nextcloud-server.git/commitdiff
fix(carddav): expose system address book 37734/head
authorAnna Larch <anna@nextcloud.com>
Thu, 11 May 2023 16:59:30 +0000 (18:59 +0200)
committerAnna Larch <anna@nextcloud.com>
Thu, 11 May 2023 16:59:30 +0000 (18:59 +0200)
Signed-off-by: Anna Larch <anna@nextcloud.com>
apps/dav/appinfo/v1/carddav.php
apps/dav/lib/CardDAV/AddressBookRoot.php
apps/dav/lib/CardDAV/CardDavBackend.php
apps/dav/lib/CardDAV/SyncService.php
apps/dav/lib/CardDAV/SystemAddressbook.php
apps/dav/lib/CardDAV/UserAddressBooks.php
apps/dav/lib/RootCollection.php
apps/dav/tests/unit/CardDAV/SystemAddressBookTest.php
apps/settings/templates/settings/admin/sharing.php

index 2b3001e80bdfada7215c9270b62673e3b6380610..e7faa9314e2a69e96a8ab67ede9efaa10bf2fe11 100644 (file)
@@ -10,6 +10,7 @@
  * @author Morris Jobke <hey@morrisjobke.de>
  * @author Thomas Citharel <nextcloud@tcit.fr>
  * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Anna Larch <anna.larch@gmx.net>
  *
  * @license AGPL-3.0
  *
@@ -72,7 +73,7 @@ $principalCollection = new \Sabre\CalDAV\Principal\Collection($principalBackend)
 $principalCollection->disableListing = !$debugging; // Disable listing
 
 $pluginManager = new PluginManager(\OC::$server, \OC::$server->query(IAppManager::class));
-$addressBookRoot = new AddressBookRoot($principalBackend, $cardDavBackend, $pluginManager);
+$addressBookRoot = new AddressBookRoot($principalBackend, $cardDavBackend, $pluginManager, \OC::$server->getUserSession()->getUser(), \OC::$server->get(\OCP\IGroupManager::class));
 $addressBookRoot->disableListing = !$debugging; // Disable listing
 
 $nodes = [
index 897ed8190714dc5750b206fc66c330da156fd965..c82943d2879ed5674ee0d41c809c4cc6b4fb63f6 100644 (file)
@@ -5,6 +5,7 @@
  * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  * @author Joas Schilling <coding@schilljs.com>
  * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Anna Larch <anna.larch@gmx.net>
  *
  * @license AGPL-3.0
  *
 namespace OCA\DAV\CardDAV;
 
 use OCA\DAV\AppInfo\PluginManager;
+use OCP\IGroupManager;
+use OCP\IUser;
 
 class AddressBookRoot extends \Sabre\CardDAV\AddressBookRoot {
 
        /** @var PluginManager */
        private $pluginManager;
+       private ?IUser $user;
+       private ?IGroupManager $groupManager;
 
        /**
         * @param \Sabre\DAVACL\PrincipalBackend\BackendInterface $principalBackend
@@ -38,9 +43,13 @@ class AddressBookRoot extends \Sabre\CardDAV\AddressBookRoot {
        public function __construct(\Sabre\DAVACL\PrincipalBackend\BackendInterface $principalBackend,
                                                                \Sabre\CardDAV\Backend\BackendInterface $carddavBackend,
                                                                PluginManager $pluginManager,
-                                                               $principalPrefix = 'principals') {
+                                                               ?IUser $user,
+                                                               ?IGroupManager $groupManager,
+                                                               string $principalPrefix = 'principals') {
                parent::__construct($principalBackend, $carddavBackend, $principalPrefix);
                $this->pluginManager = $pluginManager;
+               $this->user = $user;
+               $this->groupManager = $groupManager;
        }
 
        /**
@@ -55,7 +64,7 @@ class AddressBookRoot extends \Sabre\CardDAV\AddressBookRoot {
         * @return \Sabre\DAV\INode
         */
        public function getChildForPrincipal(array $principal) {
-               return new UserAddressBooks($this->carddavBackend, $principal['uri'], $this->pluginManager);
+               return new UserAddressBooks($this->carddavBackend, $principal['uri'], $this->pluginManager, $this->user, $this->groupManager);
        }
 
        public function getName() {
index 577d7282eaeed5a97a776f5027a32124d3eb42bc..5f5b8f1e65f2fc3c920f2194b39f963be029c5c0 100644 (file)
@@ -311,6 +311,11 @@ class CardDavBackend implements BackendInterface, SyncSupport {
                        '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
                ];
 
+               // system address books are always read only
+               if ($principal === 'principals/system/system') {
+                       $addressBook['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only'] = true;
+               }
+
                $this->addOwnerPrincipal($addressBook);
 
                return $addressBook;
index da798c5768e5629ea25ee8b0cd0c6c8f957c3402..b228d45f0670720d9f571f7ffc1d2344e0346939 100644 (file)
@@ -10,6 +10,7 @@
  * @author Morris Jobke <hey@morrisjobke.de>
  * @author Thomas Citharel <nextcloud@tcit.fr>
  * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Anna Larch <anna.larch@gmx.net>
  *
  * @license AGPL-3.0
  *
@@ -209,10 +210,8 @@ class SyncService {
        public function updateUser(IUser $user) {
                $systemAddressBook = $this->getLocalSystemAddressBook();
                $addressBookId = $systemAddressBook['id'];
-               $name = $user->getBackendClassName();
-               $userId = $user->getUID();
 
-               $cardId = "$name:$userId.vcf";
+               $cardId = self::getCardUri($user);
                if ($user->isEnabled()) {
                        $card = $this->backend->getCard($addressBookId, $cardId);
                        if ($card === false) {
@@ -239,10 +238,7 @@ class SyncService {
        public function deleteUser($userOrCardId) {
                $systemAddressBook = $this->getLocalSystemAddressBook();
                if ($userOrCardId instanceof IUser) {
-                       $name = $userOrCardId->getBackendClassName();
-                       $userId = $userOrCardId->getUID();
-
-                       $userOrCardId = "$name:$userId.vcf";
+                       $userOrCardId = self::getCardUri($userOrCardId);
                }
                $this->backend->deleteCard($systemAddressBook['id'], $userOrCardId);
        }
@@ -281,4 +277,12 @@ class SyncService {
                        }
                }
        }
+
+       /**
+        * @param IUser $user
+        * @return string
+        */
+       public static function getCardUri(IUser $user): string {
+               return $user->getBackendClassName() . ':' . $user->getUID() . '.vcf';
+       }
 }
index a803a1e6b244b03ab9cbc2611d772f20decb0ad7..17900fd033e5fa342b3e191e21447d068f171424 100644 (file)
@@ -8,6 +8,7 @@ declare(strict_types=1);
  * @author Joas Schilling <coding@schilljs.com>
  * @author Julius Härtl <jus@bitgrid.net>
  * @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Anna Larch <anna.larch@gmx.net>
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -31,38 +32,97 @@ use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
 use OCA\Federation\TrustedServers;
 use OCP\Accounts\IAccountManager;
 use OCP\IConfig;
+use OCP\IGroupManager;
 use OCP\IL10N;
 use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserSession;
 use Sabre\CardDAV\Backend\SyncSupport;
 use Sabre\CardDAV\Backend\BackendInterface;
 use Sabre\CardDAV\Card;
 use Sabre\DAV\Exception\Forbidden;
 use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\ICollection;
 use Sabre\VObject\Component\VCard;
 use Sabre\VObject\Reader;
+use function array_unique;
 
 class SystemAddressbook extends AddressBook {
+       public const URI_SHARED = 'z-server-generated--system';
        /** @var IConfig */
        private $config;
+       private IUserSession $userSession;
        private ?TrustedServers $trustedServers;
        private ?IRequest $request;
+       private ?IGroupManager $groupManager;
 
-       public function __construct(BackendInterface $carddavBackend, array $addressBookInfo, IL10N $l10n, IConfig $config, ?IRequest $request = null, ?TrustedServers $trustedServers = null) {
+       public function __construct(BackendInterface $carddavBackend,
+               array $addressBookInfo,
+               IL10N $l10n,
+               IConfig $config,
+               IUserSession $userSession,
+               ?IRequest $request = null,
+               ?TrustedServers $trustedServers = null,
+               ?IGroupManager $groupManager) {
                parent::__construct($carddavBackend, $addressBookInfo, $l10n);
                $this->config = $config;
+               $this->userSession = $userSession;
                $this->request = $request;
                $this->trustedServers = $trustedServers;
+               $this->groupManager = $groupManager;
+
+               $this->addressBookInfo['{DAV:}displayname'] = $l10n->t('Accounts');
+               $this->addressBookInfo['{' . Plugin::NS_CARDDAV . '}addressbook-description'] = $l10n->t('System address book which holds all accounts');
        }
 
-       public function getChildren(): array {
+       /**
+        * No checkbox checked -> Show only the same user
+        * 'Allow username autocompletion in share dialog' -> show everyone
+        * 'Allow username autocompletion in share dialog' + 'Allow username autocompletion to users within the same groups' -> show only users in intersecting groups
+        * 'Allow username autocompletion in share dialog' + 'Allow username autocompletion to users based on phone number integration' -> show only the same user
+        * 'Allow username autocompletion in share dialog' + 'Allow username autocompletion to users within the same groups' + 'Allow username autocompletion to users based on phone number integration' -> show only users in intersecting groups
+        */
+       public function getChildren() {
                $shareEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
                $shareEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
                $shareEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
-               if (!$shareEnumeration || $shareEnumerationGroup || $shareEnumerationPhone) {
+               $user = $this->userSession->getUser();
+               if (!$user) {
+                       // Should never happen because we don't allow anonymous access
                        return [];
                }
+               if (!$shareEnumeration || !$shareEnumerationGroup && $shareEnumerationPhone) {
+                       $name = SyncService::getCardUri($user);
+                       try {
+                               return [parent::getChild($name)];
+                       } catch (NotFound $e) {
+                               return [];
+                       }
+               }
+               if ($shareEnumerationGroup) {
+                       if ($this->groupManager === null) {
+                               // Group manager is not available, so we can't determine which data is safe
+                               return [];
+                       }
+                       $groups = $this->groupManager->getUserGroups($user);
+                       $names = [];
+                       foreach ($groups as $group) {
+                               $users = $group->getUsers();
+                               foreach ($users as $groupUser) {
+                                       if ($groupUser->getBackendClassName() === 'Guests') {
+                                               continue;
+                                       }
+                                       $names[] = SyncService::getCardUri($groupUser);
+                               }
+                       }
+                       return parent::getMultipleChildren(array_unique($names));
+               }
 
-               return parent::getChildren();
+               $children = parent::getChildren();
+               return array_filter($children, function (Card $child) {
+                       // check only for URIs that begin with Guests:
+                       return strpos($child->getName(), 'Guests:') !== 0;
+               });
        }
 
        /**
@@ -225,4 +285,15 @@ class SystemAddressbook extends AddressBook {
 
                return $vCard->serialize();
        }
+
+       /**
+        * @return mixed
+        * @throws Forbidden
+        */
+       public function delete() {
+               if ($this->isFederation()) {
+                       parent::delete();
+               }
+               throw new Forbidden();
+       }
 }
index 85795604f28ff070d1504af01ec2351ae4d62ddd..938575bd1a775e5daba8c364307c1ac2f7747309 100644 (file)
@@ -9,6 +9,7 @@ declare(strict_types=1);
  * @author Joas Schilling <coding@schilljs.com>
  * @author Roeland Jago Douma <roeland@famdouma.nl>
  * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Anna Larch <anna.larch@gmx.net>
  *
  * @license AGPL-3.0
  *
@@ -33,8 +34,11 @@ use OCA\DAV\CardDAV\Integration\ExternalAddressBook;
 use OCA\Federation\TrustedServers;
 use OCP\AppFramework\QueryException;
 use OCP\IConfig;
+use OCP\IGroupManager;
 use OCP\IL10N;
 use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserSession;
 use Psr\Container\ContainerExceptionInterface;
 use Psr\Container\NotFoundExceptionInterface;
 use Sabre\CardDAV\Backend;
@@ -44,7 +48,6 @@ use function array_map;
 use Sabre\DAV\MkCol;
 
 class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome {
-
        /** @var IL10N */
        protected $l10n;
 
@@ -53,12 +56,18 @@ class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome {
 
        /** @var PluginManager */
        private $pluginManager;
+       private ?IUser $user;
+       private ?IGroupManager $groupManager;
 
        public function __construct(Backend\BackendInterface $carddavBackend,
                                                                string $principalUri,
-                                                               PluginManager $pluginManager) {
+                                                               PluginManager $pluginManager,
+                                                               ?IUser $user,
+                                                               ?IGroupManager $groupManager) {
                parent::__construct($carddavBackend, $principalUri);
                $this->pluginManager = $pluginManager;
+               $this->user = $user;
+               $this->groupManager = $groupManager;
        }
 
        /**
@@ -74,10 +83,25 @@ class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome {
                        $this->config = \OC::$server->getConfig();
                }
 
+               /** @var string|array $principal */
+               $principal = $this->principalUri;
                $addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
-               /** @var IAddressBook[] $objects */
-               $objects = array_map(function (array $addressBook) {
-                       if ($addressBook['principaluri'] === 'principals/system/system') {
+               // add the system address book
+               $systemAddressBook = null;
+               if (is_string($principal) && $principal !== 'principals/system/system' && $this->carddavBackend instanceof CardDavBackend) {
+                       $systemAddressBook = $this->carddavBackend->getAddressBooksByUri('principals/system/system', 'system');
+                       if ($systemAddressBook !== null) {
+                               $systemAddressBook['uri'] = SystemAddressbook::URI_SHARED;
+                       }
+               }
+               if (!is_null($systemAddressBook)) {
+                       $addressBooks[] = $systemAddressBook;
+               }
+
+               $objects = [];
+               if (!empty($addressBooks)) {
+                       /** @var IAddressBook[] $objects */
+                       $objects = array_map(function (array $addressBook) {
                                $trustedServers = null;
                                $request = null;
                                try {
@@ -86,11 +110,22 @@ class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome {
                                } catch (NotFoundExceptionInterface | ContainerExceptionInterface $e) {
                                        // nothing to do, the request / trusted servers don't exist
                                }
-                               return new SystemAddressbook($this->carddavBackend, $addressBook, $this->l10n, $this->config, $request, $trustedServers);
-                       }
+                               if ($addressBook['principaluri'] === 'principals/system/system') {
+                                       return new SystemAddressbook(
+                                               $this->carddavBackend,
+                                               $addressBook,
+                                               $this->l10n,
+                                               $this->config,
+                                               \OCP\Server::get(IUserSession::class),
+                                               $request,
+                                               $trustedServers,
+                                               $this->groupManager
+                                       );
+                               }
 
-                       return new AddressBook($this->carddavBackend, $addressBook, $this->l10n);
-               }, $addressBooks);
+                               return new AddressBook($this->carddavBackend, $addressBook, $this->l10n);
+                       }, $addressBooks);
+               }
                /** @var IAddressBook[][] $objectsFromPlugins */
                $objectsFromPlugins = array_map(function (IAddressBookProvider $plugin): array {
                        return $plugin->fetchAllForAddressBookHome($this->principalUri);
index 6e009875ddc59f3ab3db8cd20bf922063a52596f..80d96f0d7483f5f6dde0a290755e39b5941c942f 100644 (file)
@@ -50,6 +50,7 @@ use OCP\AppFramework\Utility\ITimeFactory;
 use OCP\EventDispatcher\IEventDispatcher;
 use OCP\Files\IRootFolder;
 use OCP\IConfig;
+use OCP\IGroupManager;
 use Psr\Log\LoggerInterface;
 use Sabre\DAV\SimpleCollection;
 
@@ -144,11 +145,11 @@ class RootCollection extends SimpleCollection {
 
                $pluginManager = new PluginManager(\OC::$server, \OC::$server->query(IAppManager::class));
                $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher);
-               $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, $pluginManager, 'principals/users');
+               $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/users');
                $usersAddressBookRoot->disableListing = $disableListing;
 
                $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher);
-               $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, $pluginManager, 'principals/system');
+               $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/system');
                $systemAddressBookRoot->disableListing = $disableListing;
 
                $uploadCollection = new Upload\RootCollection(
index 73393d756159e40b448280a76fd50acc3887c35d..a753a1c5a7342647880b4bcdb841f9e2a86bfd05 100644 (file)
@@ -32,6 +32,7 @@ use OCP\Accounts\IAccountManager;
 use OCP\IConfig;
 use OCP\IL10N;
 use OCP\IRequest;
+use OCP\IUserSession;
 use PHPUnit\Framework\MockObject\MockObject;
 use Sabre\CardDAV\Backend\BackendInterface;
 use Sabre\VObject\Component\VCard;
@@ -44,6 +45,7 @@ class SystemAddressBookTest extends TestCase {
        private array $addressBookInfo;
        private IL10N|MockObject $l10n;
        private IConfig|MockObject $config;
+       private IUserSession $userSession;
        private IRequest|MockObject $request;
        private array $server;
        private TrustedServers|MockObject $trustedServers;
@@ -60,6 +62,7 @@ class SystemAddressBookTest extends TestCase {
                ];
                $this->l10n = $this->createMock(IL10N::class);
                $this->config = $this->createMock(IConfig::class);
+               $this->userSession = $this->createMock(IUserSession::class);
                $this->request = $this->createMock(Request::class);
                $this->server = [
                        'PHP_AUTH_USER' => 'system',
@@ -73,8 +76,10 @@ class SystemAddressBookTest extends TestCase {
                        $this->addressBookInfo,
                        $this->l10n,
                        $this->config,
+                       $this->userSession,
                        $this->request,
                        $this->trustedServers,
+                       null,
                );
        }
 
index 781fdf3a49da65925806be4ddb5f306f99b5212b..709c36a7b7b93fea2c38d930340083f4b95f93c5 100644 (file)
                                <?php if ($_['allowShareDialogUserEnumeration'] === 'yes') {
        print_unescaped('checked="checked"');
 } ?> />
-                       <label for="shareapi_allow_share_dialog_user_enumeration"><?php p($l->t('Allow username autocompletion in share dialog'));?></label><br />
+                       <label for="shareapi_allow_share_dialog_user_enumeration"><?php p($l->t('Allow username autocompletion in share dialog and allow access to the system address book'));?></label><br />
                </p>
 
                <p id="shareapi_restrict_user_enumeration_to_group_setting" class="indent <?php if ($_['shareAPIEnabled'] === 'no' || $_['allowShareDialogUserEnumeration'] === 'no') {
                                <?php if ($_['restrictUserEnumerationToGroup'] === 'yes') {
        print_unescaped('checked="checked"');
 } ?> />
-                       <label for="shareapi_restrict_user_enumeration_to_group"><?php p($l->t('Allow username autocompletion to users within the same groups'));?></label><br />
+                       <label for="shareapi_restrict_user_enumeration_to_group"><?php p($l->t('Allow username autocompletion to users within the same groups and limit system address books to users in the same groups'));?></label><br />
                </p>
 
                <p id="shareapi_restrict_user_enumeration_to_phone_setting" class="indent <?php if ($_['shareAPIEnabled'] === 'no' || $_['allowShareDialogUserEnumeration'] === 'no') {