Skip to content

Commit fad8704

Browse files
committed
perf: prevent fetching a principal's user account if the data is not needed
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
1 parent 92e71d2 commit fad8704

7 files changed

Lines changed: 73 additions & 17 deletions

File tree

apps/dav/lib/CalDAV/CalDavBackend.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3565,7 +3565,9 @@ private function addOwnerPrincipalToCalendar(array $calendarInfo): array {
35653565
$uri = $calendarInfo['principaluri'];
35663566
}
35673567

3568-
$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
3568+
$principalInformation = $this->principalBackend->getPrincipalPropertiesByPath($uri, [
3569+
'{DAV:}displayname',
3570+
]);
35693571
if (isset($principalInformation['{DAV:}displayname'])) {
35703572
$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
35713573
}

apps/dav/lib/CalDAV/CalendarRoot.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
*/
88
namespace OCA\DAV\CalDAV;
99

10+
use OCA\DAV\Connector\Sabre\Principal;
1011
use Psr\Log\LoggerInterface;
1112
use Sabre\CalDAV\Backend;
13+
use Sabre\DAV\Exception\NotFound;
1214
use Sabre\DAVACL\PrincipalBackend;
1315

1416
class CalendarRoot extends \Sabre\CalDAV\CalendarRoot {
@@ -46,4 +48,25 @@ public function getName() {
4648
public function enableReturnCachedSubscriptions(string $principalUri): void {
4749
$this->returnCachedSubscriptions['principals/users/' . $principalUri] = true;
4850
}
51+
52+
public function childExists($name) {
53+
if (!($this->principalBackend instanceof Principal)) {
54+
return parent::childExists($name);
55+
}
56+
57+
// Fetch the most shallow version of the principal just to determine if it exists
58+
$principalInfo = $this->principalBackend->getPrincipalPropertiesByPath(
59+
$this->principalPrefix . '/' . $name,
60+
[],
61+
);
62+
if ($principalInfo === null) {
63+
return false;
64+
}
65+
66+
try {
67+
return $this->getChildForPrincipal($principalInfo) !== null;
68+
} catch (NotFound $e) {
69+
return false;
70+
}
71+
}
4972
}

apps/dav/lib/Connector/Sabre/Principal.php

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,21 @@ public function getPrincipalsByPrefix($prefixPath) {
101101
* @return array
102102
*/
103103
public function getPrincipalByPath($path) {
104+
return $this->getPrincipalPropertiesByPath($path);
105+
}
106+
107+
/**
108+
* Returns a specific principal, specified by its path.
109+
* The returned structure should be the exact same as from
110+
* getPrincipalsByPrefix.
111+
*
112+
* It is possible to optionally filter retrieved properties in case only a limited set is
113+
* required. Note that the implementation might return more properties than requested.
114+
*
115+
* @param string $path The path of the principal
116+
* @param string[]|null $propertyFilter A list of properties to be retrieved or all if null. An empty array will cause a very shallow principal to be retrieved.
117+
*/
118+
public function getPrincipalPropertiesByPath($path, ?array $propertyFilter = null): ?array {
104119
[$prefix, $name] = \Sabre\Uri\split($path);
105120
$decodedName = urldecode($name);
106121

@@ -127,7 +142,7 @@ public function getPrincipalByPath($path) {
127142
$user = $this->userManager->get($decodedName);
128143

129144
if ($user !== null) {
130-
return $this->userToPrincipal($user);
145+
return $this->userToPrincipal($user, $propertyFilter);
131146
}
132147
} elseif ($prefix === 'principals/circles') {
133148
if ($this->userSession->getUser() !== null) {
@@ -466,29 +481,44 @@ public function findByUri($uri, $principalPrefix) {
466481

467482
/**
468483
* @param IUser $user
484+
* @param string[]|null $propertyFilter
469485
* @return array
470486
* @throws PropertyDoesNotExistException
471487
*/
472-
protected function userToPrincipal($user) {
488+
protected function userToPrincipal($user, ?array $propertyFilter = null) {
489+
$wantsProperty = static function (string $name) use ($propertyFilter) {
490+
if ($propertyFilter === null) {
491+
return true;
492+
}
493+
494+
return in_array($name, $propertyFilter, true);
495+
};
496+
473497
$userId = $user->getUID();
474498
$displayName = $user->getDisplayName();
475499
$principal = [
476500
'uri' => $this->principalPrefix . '/' . $userId,
477501
'{DAV:}displayname' => is_null($displayName) ? $userId : $displayName,
478502
'{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'INDIVIDUAL',
479-
'{http://nextcloud.com/ns}language' => $this->languageFactory->getUserLanguage($user),
480503
];
481504

482-
$account = $this->accountManager->getAccount($user);
483-
$alternativeEmails = array_map(fn (IAccountProperty $property) => 'mailto:' . $property->getValue(), $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties());
505+
if ($wantsProperty('{http://nextcloud.com/ns}language')) {
506+
$principal['{http://nextcloud.com/ns}language'] = $this->languageFactory->getUserLanguage($user);
507+
}
484508

485-
$email = $user->getSystemEMailAddress();
486-
if (!empty($email)) {
487-
$principal['{http://sabredav.org/ns}email-address'] = $email;
509+
if ($wantsProperty('{http://sabredav.org/ns}email-address')) {
510+
$email = $user->getSystemEMailAddress();
511+
if (!empty($email)) {
512+
$principal['{http://sabredav.org/ns}email-address'] = $email;
513+
}
488514
}
489515

490-
if (!empty($alternativeEmails)) {
491-
$principal['{DAV:}alternate-URI-set'] = $alternativeEmails;
516+
if ($wantsProperty('{DAV:}alternate-URI-set')) {
517+
$account = $this->accountManager->getAccount($user);
518+
$alternativeEmails = array_map(static fn (IAccountProperty $property) => 'mailto:' . $property->getValue(), $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties());
519+
if (!empty($alternativeEmails)) {
520+
$principal['{DAV:}alternate-URI-set'] = $alternativeEmails;
521+
}
492522
}
493523

494524
return $principal;

apps/dav/lib/DAV/Sharing/Backend.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ public function preloadShares(array $resourceIds): void {
150150
$sharesByResource = array_fill_keys($resourceIds, []);
151151
foreach ($rows as $row) {
152152
$resourceId = (int)$row['resourceid'];
153-
$p = $this->principalBackend->getPrincipalByPath($row['principaluri']);
153+
$p = $this->principalBackend->getPrincipalPropertiesByPath($row['principaluri'], [
154+
'uri',
155+
'{DAV:}displayname',
156+
]);
154157
$sharesByResource[$resourceId][] = [
155158
'href' => "principal:{$row['principaluri']}",
156159
'commonName' => isset($p['{DAV:}displayname']) ? (string)$p['{DAV:}displayname'] : '',

apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ protected function setUp(): void {
7777
$this->createMock(IConfig::class),
7878
$this->createMock(IFactory::class)
7979
])
80-
->onlyMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri'])
80+
->onlyMethods(['getPrincipalPropertiesByPath', 'getGroupMembership', 'findByUri'])
8181
->getMock();
82-
$this->principal->expects($this->any())->method('getPrincipalByPath')
82+
$this->principal->expects($this->any())->method('getPrincipalPropertiesByPath')
8383
->willReturn([
8484
'uri' => 'principals/best-friend',
8585
'{DAV:}displayname' => 'User\'s displayname',

apps/dav/tests/unit/DAV/Sharing/BackendTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ public function testPreloadShares(): void {
382382
->with($resourceIds)
383383
->willReturn($rows);
384384
$this->principalBackend->expects(self::exactly(2))
385-
->method('getPrincipalByPath')
385+
->method('getPrincipalPropertiesByPath')
386386
->willReturnCallback(function (string $principal) use ($principalResults) {
387387
switch ($principal) {
388388
case 'principals/groups/bob':

build/psalm-baseline.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -741,8 +741,6 @@
741741
<code><![CDATA[$results]]></code>
742742
</InvalidScalarArgument>
743743
<NullableReturnStatement>
744-
<code><![CDATA[$this->circleToPrincipal($decodedName)
745-
?: $this->circleToPrincipal($name)]]></code>
746744
<code><![CDATA[null]]></code>
747745
<code><![CDATA[null]]></code>
748746
<code><![CDATA[null]]></code>

0 commit comments

Comments
 (0)