Skip to content

Commit

Permalink
Merge pull request #41390 from nextcloud/enh/noid/limit-proppatch-met…
Browse files Browse the repository at this point in the history
…adata

Metadata must be set a editable for PROPPATCH
  • Loading branch information
AndyScherzinger authored Nov 14, 2023
2 parents 8b5d85a + 847c687 commit 93ea634
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 58 deletions.
154 changes: 110 additions & 44 deletions apps/dav/lib/Connector/Sabre/FilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@
namespace OCA\DAV\Connector\Sabre;

use OC\AppFramework\Http\Request;
use OC\FilesMetadata\Model\MetadataValueWrapper;
use OCP\Constants;
use OCP\Files\ForbiddenException;
use OCP\Files\StorageNotAvailableException;
use OCP\FilesMetadata\Exceptions\FilesMetadataException;
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\FilesMetadata\Model\IMetadataValueWrapper;
Expand Down Expand Up @@ -530,65 +530,131 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
return true;
});

$this->handleUpdatePropertiesMetadata($propPatch, $node);

/** @var IFilesMetadataManager */
$filesMetadataManager = \OCP\Server::get(IFilesMetadataManager::class);
/**
* Disable modification of the displayname property for files and
* folders via PROPPATCH. See PROPFIND for more information.
*/
$propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function ($displayName) {
return 403;
});
}


/**
* handle the update of metadata from PROPPATCH requests
*
* @param PropPatch $propPatch
* @param Node $node
*
* @throws FilesMetadataException
*/
private function handleUpdatePropertiesMetadata(PropPatch $propPatch, Node $node): void {
$userId = $this->userSession->getUser()?->getUID();
if (null === $userId) {
return;
}

$accessRight = $this->getMetadataFileAccessRight($node, $userId);
$filesMetadataManager = $this->initFilesMetadataManager();
$knownMetadata = $filesMetadataManager->getKnownMetadata();

foreach ($propPatch->getRemainingMutations() as $mutation) {
if (!str_starts_with($mutation, self::FILE_METADATA_PREFIX)) {
continue;
}

$propPatch->handle($mutation, function (mixed $value) use ($knownMetadata, $node, $mutation, $filesMetadataManager): bool {
$metadata = $filesMetadataManager->getMetadata((int)$node->getFileId(), true);
$metadataKey = substr($mutation, strlen(self::FILE_METADATA_PREFIX));
$propPatch->handle(
$mutation,
function (mixed $value) use ($accessRight, $knownMetadata, $node, $mutation, $filesMetadataManager): bool {
$metadata = $filesMetadataManager->getMetadata((int)$node->getFileId(), true);
$metadataKey = substr($mutation, strlen(self::FILE_METADATA_PREFIX));

// If the metadata is unknown, it defaults to string.
try {
$type = $knownMetadata->getType($metadataKey);
} catch (FilesMetadataNotFoundException) {
$type = IMetadataValueWrapper::TYPE_STRING;
}
// confirm metadata key is editable via PROPPATCH
if ($knownMetadata->getEditPermission($metadataKey) < $accessRight) {
throw new FilesMetadataException('you do not have enough rights to update \'' . $metadataKey . '\' on this node');
}

// If the metadata is unknown, it defaults to string.
try {
$type = $knownMetadata->getType($metadataKey);
} catch (FilesMetadataNotFoundException) {
$type = IMetadataValueWrapper::TYPE_STRING;
}

switch ($type) {
case IMetadataValueWrapper::TYPE_STRING:
$metadata->setString($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
break;
case IMetadataValueWrapper::TYPE_INT:
$metadata->setInt($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
break;
case IMetadataValueWrapper::TYPE_FLOAT:
$metadata->setFloat($metadataKey, $value);
break;
case IMetadataValueWrapper::TYPE_BOOL:
$metadata->setBool($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
break;
case IMetadataValueWrapper::TYPE_ARRAY:
$metadata->setArray($metadataKey, $value);
break;
case IMetadataValueWrapper::TYPE_STRING_LIST:
$metadata->setStringList(
$metadataKey, $value, $knownMetadata->isIndex($metadataKey)
);
break;
case IMetadataValueWrapper::TYPE_INT_LIST:
$metadata->setIntList(
$metadataKey, $value, $knownMetadata->isIndex($metadataKey)
);
break;
}

$filesMetadataManager->saveMetadata($metadata);

switch ($type) {
case IMetadataValueWrapper::TYPE_STRING:
$metadata->setString($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
break;
case IMetadataValueWrapper::TYPE_INT:
$metadata->setInt($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
break;
case IMetadataValueWrapper::TYPE_FLOAT:
$metadata->setFloat($metadataKey, $value);
break;
case IMetadataValueWrapper::TYPE_BOOL:
$metadata->setBool($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
break;
case IMetadataValueWrapper::TYPE_ARRAY:
$metadata->setArray($metadataKey, $value);
break;
case IMetadataValueWrapper::TYPE_STRING_LIST:
$metadata->setStringList($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
break;
case IMetadataValueWrapper::TYPE_INT_LIST:
$metadata->setIntList($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
break;
return true;
}
);
}
}

$filesMetadataManager->saveMetadata($metadata);
return true;
});
/**
* init default internal metadata
*
* @return IFilesMetadataManager
*/
private function initFilesMetadataManager(): IFilesMetadataManager {
/** @var IFilesMetadataManager $manager */
$manager = \OCP\Server::get(IFilesMetadataManager::class);
$manager->initMetadata('files-live-photo', IMetadataValueWrapper::TYPE_STRING, false, IMetadataValueWrapper::EDIT_REQ_OWNERSHIP);

return $manager;
}

/**
* based on owner and shares, returns the bottom limit to update related metadata
*
* @param Node $node
* @param string $userId
*
* @return int
*/
private function getMetadataFileAccessRight(Node $node, string $userId): int {
if ($node->getOwner()?->getUID() === $userId) {
return IMetadataValueWrapper::EDIT_REQ_OWNERSHIP;
} else {
$filePermissions = $node->getSharePermissions($userId);
if ($filePermissions & Constants::PERMISSION_UPDATE) {
return IMetadataValueWrapper::EDIT_REQ_WRITE_PERMISSION;
}
}

/**
* Disable modification of the displayname property for files and
* folders via PROPPATCH. See PROPFIND for more information.
*/
$propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function ($displayName) {
return 403;
});
return IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION;
}



/**
* @param string $filePath
* @param \Sabre\DAV\INode $node
Expand Down
19 changes: 16 additions & 3 deletions lib/private/FilesMetadata/FilesMetadataManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ public function getKnownMetadata(): IFilesMetadata {
* @param string $key metadata key
* @param string $type metadata type
* @param bool $indexed TRUE if metadata can be search
* @param int $editPermission remote edit permission via Webdav PROPPATCH
*
* @inheritDoc
* @since 28.0.0
Expand All @@ -264,19 +265,31 @@ public function getKnownMetadata(): IFilesMetadata {
* @see IMetadataValueWrapper::TYPE_STRING_LIST
* @see IMetadataValueWrapper::TYPE_INT_LIST
* @see IMetadataValueWrapper::TYPE_STRING
* @see IMetadataValueWrapper::EDIT_FORBIDDEN
* @see IMetadataValueWrapper::EDIT_REQ_OWNERSHIP
* @see IMetadataValueWrapper::EDIT_REQ_WRITE_PERMISSION
* @see IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION
*/
public function initMetadata(string $key, string $type, bool $indexed): void {
public function initMetadata(
string $key,
string $type,
bool $indexed = false,
int $editPermission = IMetadataValueWrapper::EDIT_FORBIDDEN
): void {
$current = $this->getKnownMetadata();
try {
if ($current->getType($key) === $type && $indexed === $current->isIndex($key)) {
if ($current->getType($key) === $type
&& $indexed === $current->isIndex($key)
&& $editPermission === $current->getEditPermission($key)) {
return; // if key exists, with same type and indexed, we do nothing.
}
} catch (FilesMetadataNotFoundException) {
// if value does not exist, we keep on the writing of course
}

$current->import([$key => ['type' => $type, 'indexed' => $indexed]]);
$current->import([$key => ['type' => $type, 'indexed' => $indexed, 'editPermission' => $editPermission]]);
$this->config->setAppValue('core', self::CONFIG_KEY, json_encode($current));
$this->all = $current;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions lib/private/FilesMetadata/MetadataQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public function joinIndex(string $metadataKey, bool $enforce = false): string {
$andX->add($expr->eq($this->getMetadataKeyField($metadataKey), $this->queryBuilder->createNamedParameter($metadataKey)));

if ($enforce) {
$this->queryBuilder->rightJoin(
$this->queryBuilder->innerJoin(
$this->fileTableAlias,
IndexRequestService::TABLE_METADATA_INDEX,
$aliasIndex,
Expand All @@ -125,7 +125,7 @@ public function joinIndex(string $metadataKey, bool $enforce = false): string {
/**
* @throws FilesMetadataNotFoundException
*/
public function joinedTableAlias(string $metadataKey): string {
private function joinedTableAlias(string $metadataKey): string {
if (!array_key_exists($metadataKey, $this->knownJoinedIndex)) {
throw new FilesMetadataNotFoundException('table related to ' . $metadataKey . ' not initiated, you need to use leftJoin() first.');
}
Expand Down
34 changes: 33 additions & 1 deletion lib/private/FilesMetadata/Model/FilesMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,38 @@ public function isIndex(string $key): bool {
return $this->metadata[$key]?->isIndexed() ?? false;
}

/**
* @param string $key metadata key
*
* @inheritDoc
* @return int edit permission
* @throws FilesMetadataNotFoundException
* @since 28.0.0
*/
public function getEditPermission(string $key): int {
if (!array_key_exists($key, $this->metadata)) {
throw new FilesMetadataNotFoundException();
}

return $this->metadata[$key]->getEditPermission();
}

/**
* @param string $key metadata key
* @param int $permission edit permission
*
* @inheritDoc
* @throws FilesMetadataNotFoundException
* @since 28.0.0
*/
public function setEditPermission(string $key, int $permission): void {
if (!array_key_exists($key, $this->metadata)) {
throw new FilesMetadataNotFoundException();
}

$this->metadata[$key]->setEditPermission($permission);
}

/**
* @param string $key metadata key
*
Expand Down Expand Up @@ -582,7 +614,7 @@ public function importFromDatabase(array $data, string $prefix = ''): IFilesMeta
JSON_THROW_ON_ERROR
)
);
} catch (JsonException $e) {
} catch (JsonException) {
throw new FilesMetadataNotFoundException();
}
}
Expand Down
28 changes: 26 additions & 2 deletions lib/private/FilesMetadata/Model/MetadataValueWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class MetadataValueWrapper implements IMetadataValueWrapper {
/** @var string|int|float|bool|array|string[]|int[] */
private mixed $value = null;
private bool $indexed = false;
private int $editPermission = self::EDIT_FORBIDDEN;

/**
* @param string $type value type
Expand Down Expand Up @@ -371,6 +372,28 @@ public function isIndexed(): bool {
return $this->indexed;
}

/**
* @param int $permission edit permission
*
* @inheritDoc
* @return self
* @since 28.0.0
*/
public function setEditPermission(int $permission): self {
$this->editPermission = $permission;

return $this;
}

/**
* @inheritDoc
* @return int edit permission
* @since 28.0.0
*/
public function getEditPermission(): int {
return $this->editPermission;
}

/**
* @param array $data serialized version of the object
*
Expand All @@ -383,15 +406,16 @@ public function import(array $data): self {
$this->value = $data['value'] ?? null;
$this->type = $data['type'] ?? '';
$this->setIndexed($data['indexed'] ?? false);

$this->setEditPermission($data['editPermission'] ?? self::EDIT_FORBIDDEN);
return $this;
}

public function jsonSerialize(bool $emptyValues = false): array {
return [
'value' => ($emptyValues) ? null : $this->value,
'type' => $this->getType(),
'indexed' => $this->isIndexed()
'indexed' => $this->isIndexed(),
'editPermission' => $this->getEditPermission()
];
}
}
16 changes: 15 additions & 1 deletion lib/public/FilesMetadata/IFilesMetadataManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use OCP\FilesMetadata\Exceptions\FilesMetadataException;
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
use OCP\FilesMetadata\Model\IFilesMetadata;
use OCP\FilesMetadata\Model\IMetadataValueWrapper;

/**
* Manager for FilesMetadata; manage files' metadata.
Expand Down Expand Up @@ -133,7 +134,20 @@ public function getKnownMetadata(): IFilesMetadata;
* @param string $key metadata key
* @param string $type metadata type
* @param bool $indexed TRUE if metadata can be search
* @param int $editPermission remote edit permission via Webdav PROPPATCH
*
* @see IMetadataValueWrapper::TYPE_INT
* @see IMetadataValueWrapper::TYPE_FLOAT
* @see IMetadataValueWrapper::TYPE_BOOL
* @see IMetadataValueWrapper::TYPE_ARRAY
* @see IMetadataValueWrapper::TYPE_STRING_LIST
* @see IMetadataValueWrapper::TYPE_INT_LIST
* @see IMetadataValueWrapper::TYPE_STRING
* @see IMetadataValueWrapper::EDIT_FORBIDDEN
* @see IMetadataValueWrapper::EDIT_REQ_OWNERSHIP
* @see IMetadataValueWrapper::EDIT_REQ_WRITE_PERMISSION
* @see IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION
* @since 28.0.0
*/
public function initMetadata(string $key, string $type, bool $indexed): void;
public function initMetadata(string $key, string $type, bool $indexed, int $editPermission): void;
}
Loading

0 comments on commit 93ea634

Please sign in to comment.