Skip to content

Commit

Permalink
metadata must be set a editable for PROPPATCH
Browse files Browse the repository at this point in the history
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
  • Loading branch information
ArtificialOwl committed Nov 13, 2023
1 parent 88fda7d commit 8e4f8e9
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 58 deletions.
135 changes: 92 additions & 43 deletions apps/dav/lib/Connector/Sabre/FilesPlugin.php
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,8 +530,35 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
return true;
});

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

/** @var IFilesMetadataManager */
/**
* Disable modification of the displayname property for files and
* folders via PROPPATCH. See PROPFIND for more information.
*/
$propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function ($displayName) {

Check notice

Code scanning / Psalm

MissingClosureParamType Note

Parameter $displayName has no provided type
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);

/** @var IFilesMetadataManager $filesMetadataManager */
$filesMetadataManager = \OCP\Server::get(IFilesMetadataManager::class);
$knownMetadata = $filesMetadataManager->getKnownMetadata();

Expand All @@ -540,55 +567,77 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
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;
}

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);

return true;
}
);
}
}

$filesMetadataManager->saveMetadata($metadata);
return true;
});

private function getMetadataFileAccessRight(Node $node, string $userId): int {
$isOwner = ($node->getOwner()?->getUID() === $userId);
if ($isOwner) {
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
18 changes: 15 additions & 3 deletions lib/private/FilesMetadata/FilesMetadataManager.php
Expand Up @@ -251,6 +251,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 @@ -261,18 +262,29 @@ public function getKnownMetadata(): IFilesMetadata {
* @see IMetadataValueWrapper::TYPE_STRING_LIST
* @see IMetadataValueWrapper::TYPE_INT_LIST
* @see IMetadataValueWrapper::TYPE_STRING
* @see IMetadataValueWrapper::EDIT_LOCK
* @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_LOCK
): 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));
}

Expand Down
34 changes: 33 additions & 1 deletion lib/private/FilesMetadata/Model/FilesMetadata.php
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
17 changes: 15 additions & 2 deletions lib/private/FilesMetadata/Model/MetadataQuery.php
Expand Up @@ -85,7 +85,7 @@ public function extractMetadata(array $row): IFilesMetadata {

/**
* @param string $metadataKey metadata key
* @param bool $enforce limit the request only to existing metadata
* @param bool $enforce limit the request only to existing metadata (<b>not supported by SQLite</b>)
*
* @inheritDoc
* @since 28.0.0
Expand Down Expand Up @@ -121,10 +121,23 @@ public function joinIndex(string $metadataKey, bool $enforce = false): string {
return $aliasIndex;
}

/**
* @param string $metadataKey
*
* @inheritDoc
* @throws FilesMetadataNotFoundException
* @throws FilesMetadataTypeException
* @since 28.0.0
*/
public function enforceMetadataKey(string $metadataKey): void {
$expr = $this->queryBuilder->expr();
$this->queryBuilder->andWhere($expr->isNotNull($this->getMetadataValueField($metadataKey)));
}

/**
* @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
28 changes: 26 additions & 2 deletions lib/private/FilesMetadata/Model/MetadataValueWrapper.php
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_LOCK;

/**
* @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_LOCK);
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
Expand Up @@ -31,6 +31,7 @@
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
use OCP\FilesMetadata\Model\IFilesMetadata;
use OCP\FilesMetadata\Model\IMetadataQuery;
use OCP\FilesMetadata\Model\IMetadataValueWrapper;

/**
* Manager for FilesMetadata; manage files' metadata.
Expand Down Expand Up @@ -134,7 +135,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_LOCK
* @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;
}

0 comments on commit 8e4f8e9

Please sign in to comment.