Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow certain custom DAV props to be readable by everyone #27426

Merged
merged 1 commit into from
Jun 10, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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