Skip to content

Commit

Permalink
Merge pull request #27426 from nextcloud/fix/dav-custom-props-no-scope
Browse files Browse the repository at this point in the history
Allow certain custom DAV props to be readable by everyone
  • Loading branch information
ChristophWurst authored Jun 10, 2021
2 parents 6cf174f + baf4ad1 commit 2d1d939
Showing 1 changed file with 57 additions and 14 deletions.
71 changes: 57 additions & 14 deletions apps/dav/lib/DAV/CustomPropertiesBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,19 @@
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Tree;
use function array_intersect;

class CustomPropertiesBackend implements BackendInterface {

/** @var string */
private const TABLE_NAME = 'properties';

/**
* Ignored properties
*
* @var array
* @var string[]
*/
private $ignoredProperties = [
private const IGNORED_PROPERTIES = [
'{DAV:}getcontentlength',
'{DAV:}getcontenttype',
'{DAV:}getetag',
Expand All @@ -52,6 +56,15 @@ class CustomPropertiesBackend implements BackendInterface {
'{http://nextcloud.org/ns}is-encrypted',
];

/**
* Properties set by one user, readable by all others
*
* @var array[]
*/
private const PUBLISHED_READ_ONLY_PROPERTIES = [
'{urn:ietf:params:xml:ns:caldav}calendar-availability',
];

/**
* @var Tree
*/
Expand All @@ -72,7 +85,7 @@ class CustomPropertiesBackend implements BackendInterface {
*
* @var array
*/
private $cache = [];
private $userCache = [];

/**
* @param Tree $tree node tree
Expand Down Expand Up @@ -101,7 +114,7 @@ public function propFind($path, PropFind $propFind) {
// these might appear
$requestedProps = array_diff(
$requestedProps,
$this->ignoredProperties
self::IGNORED_PROPERTIES
);

// substr of calendars/ => path is inside the CalDAV component
Expand All @@ -128,8 +141,12 @@ public function propFind($path, PropFind $propFind) {
return;
}

$props = $this->getProperties($path, $requestedProps);
foreach ($props as $propName => $propValue) {
// First fetch the published properties (set by another user), then get the ones set by
// the current user. If both are set then the latter as priority.
foreach ($this->getPublishedProperties($path, $requestedProps) as $propName => $propValue) {
$propFind->set($propName, $propValue);
}
foreach ($this->getUserProperties($path, $requestedProps) as $propName => $propValue) {
$propFind->set($propName, $propValue);
}
}
Expand Down Expand Up @@ -160,7 +177,7 @@ public function delete($path) {
$statement->execute([$this->user->getUID(), $this->formatPath($path)]);
$statement->closeCursor();

unset($this->cache[$path]);
unset($this->userCache[$path]);
}

/**
Expand All @@ -181,7 +198,33 @@ public function move($source, $destination) {
}

/**
* Returns a list of properties for this nodes.;
* @param string $path
* @param string[] $requestedProperties
*
* @return array
*/
private function getPublishedProperties(string $path, array $requestedProperties): array {
$allowedProps = array_intersect(self::PUBLISHED_READ_ONLY_PROPERTIES, $requestedProperties);

if (empty($allowedProps)) {
return [];
}

$qb = $this->connection->getQueryBuilder();
$qb->select('*')
->from(self::TABLE_NAME)
->where($qb->expr()->eq('propertypath', $qb->createNamedParameter($path)));
$result = $qb->executeQuery();
$props = [];
while ($row = $result->fetch()) {
$props[$row['propertyname']] = $row['propertyvalue'];
}
$result->closeCursor();
return $props;
}

/**
* Returns a list of properties for the given path and current user
*
* @param string $path
* @param array $requestedProperties requested properties or empty array for "all"
Expand All @@ -191,9 +234,9 @@ public function move($source, $destination) {
* http://www.example.org/namespace#author If the array is empty, all
* properties should be returned
*/
private function getProperties(string $path, array $requestedProperties) {
if (isset($this->cache[$path])) {
return $this->cache[$path];
private function getUserProperties(string $path, array $requestedProperties) {
if (isset($this->userCache[$path])) {
return $this->userCache[$path];
}

// TODO: chunking if more than 1000 properties
Expand Down Expand Up @@ -222,7 +265,7 @@ private function getProperties(string $path, array $requestedProperties) {

$result->closeCursor();

$this->cache[$path] = $props;
$this->userCache[$path] = $props;
return $props;
}

Expand All @@ -245,7 +288,7 @@ private function updateProperties(string $path, array $properties) {
' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';

// TODO: use "insert or update" strategy ?
$existing = $this->getProperties($path, []);
$existing = $this->getUserProperties($path, []);
$this->connection->beginTransaction();
foreach ($properties as $propertyName => $propertyValue) {
// If it was null, we need to delete the property
Expand Down Expand Up @@ -283,7 +326,7 @@ private function updateProperties(string $path, array $properties) {
}

$this->connection->commit();
unset($this->cache[$path]);
unset($this->userCache[$path]);

return true;
}
Expand Down

0 comments on commit 2d1d939

Please sign in to comment.