mirror of
https://github.com/nextcloud/server.git
synced 2026-02-20 00:12:30 -05:00
Merge pull request #37734 from nextcloud/enh/expose-system-address-book
feat(dav): expose system address book
This commit is contained in:
commit
d5c2e75db6
9 changed files with 158 additions and 27 deletions
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
@ -24,11 +25,15 @@
|
|||
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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@
|
|||
<?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') {
|
||||
|
|
@ -219,7 +219,7 @@
|
|||
<?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') {
|
||||
|
|
|
|||
Loading…
Reference in a new issue