Browse Source

metadata must be set as editable for PROPPATCH

Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
tags/v28.0.0beta3
Maxence Lange 7 months ago
parent
commit
847c687a06

+ 110
- 44
apps/dav/lib/Connector/Sabre/FilesPlugin.php View File

@@ -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;
@@ -530,9 +530,34 @@ class FilesPlugin extends ServerPlugin {
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) {
@@ -540,55 +565,96 @@ class FilesPlugin extends ServerPlugin {
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

+ 16
- 3
lib/private/FilesMetadata/FilesMetadataManager.php View File

@@ -254,6 +254,7 @@ class FilesMetadataManager implements IFilesMetadataManager {
* @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
@@ -264,19 +265,31 @@ class FilesMetadataManager implements IFilesMetadataManager {
* @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;
}

/**

+ 2
- 2
lib/private/FilesMetadata/MetadataQuery.php View File

@@ -104,7 +104,7 @@ class MetadataQuery implements IMetadataQuery {
$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,
@@ -125,7 +125,7 @@ class MetadataQuery implements IMetadataQuery {
/**
* @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.');
}

+ 33
- 1
lib/private/FilesMetadata/Model/FilesMetadata.php View File

@@ -124,6 +124,38 @@ class FilesMetadata implements IFilesMetadata {
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
*
@@ -582,7 +614,7 @@ class FilesMetadata implements IFilesMetadata {
JSON_THROW_ON_ERROR
)
);
} catch (JsonException $e) {
} catch (JsonException) {
throw new FilesMetadataNotFoundException();
}
}

+ 26
- 2
lib/private/FilesMetadata/Model/MetadataValueWrapper.php View File

@@ -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
@@ -371,6 +372,28 @@ class MetadataValueWrapper implements IMetadataValueWrapper {
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
*
@@ -383,7 +406,7 @@ class MetadataValueWrapper implements IMetadataValueWrapper {
$this->value = $data['value'] ?? null;
$this->type = $data['type'] ?? '';
$this->setIndexed($data['indexed'] ?? false);
$this->setEditPermission($data['editPermission'] ?? self::EDIT_FORBIDDEN);
return $this;
}

@@ -391,7 +414,8 @@ class MetadataValueWrapper implements IMetadataValueWrapper {
return [
'value' => ($emptyValues) ? null : $this->value,
'type' => $this->getType(),
'indexed' => $this->isIndexed()
'indexed' => $this->isIndexed(),
'editPermission' => $this->getEditPermission()
];
}
}

+ 15
- 1
lib/public/FilesMetadata/IFilesMetadataManager.php View File

@@ -30,6 +30,7 @@ use OCP\Files\Node;
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.
@@ -133,7 +134,20 @@ interface IFilesMetadataManager {
* @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;
}

+ 26
- 2
lib/public/FilesMetadata/Model/IFilesMetadata.php View File

@@ -37,12 +37,14 @@ use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
* "mymeta": {
* "value": "this is a test",
* "type": "string",
* "indexed": false
* "indexed": false,
* "editPermission": 1
* },
* "myapp-anothermeta": {
* "value": 42,
* "type": "int",
* "indexed": true
* "indexed": true,
* "editPermission": 0
* }
* }
*
@@ -110,6 +112,28 @@ interface IFilesMetadata extends JsonSerializable {
*/
public function isIndex(string $key): bool;

/**
* set remote edit permission
* (Webdav PROPPATCH)
*
* @param string $key metadata key
* @param int $permission remote edit permission
*
* @since 28.0.0
*/
public function setEditPermission(string $key, int $permission): void;

/**
* returns remote edit permission
* (Webdav PROPPATCH)
*
* @param string $key metadata key
*
* @return int
* @since 28.0.0
*/
public function getEditPermission(string $key): int;

/**
* returns string value for a metadata key
*

+ 37
- 3
lib/public/FilesMetadata/Model/IMetadataValueWrapper.php View File

@@ -37,17 +37,31 @@ use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
* @since 28.0.0
*/
interface IMetadataValueWrapper extends JsonSerializable {
/**
* @since 28.0.0
*/
/** @since 28.0.0 */
public const TYPE_STRING = 'string';
/** @since 28.0.0 */
public const TYPE_INT = 'int';
/** @since 28.0.0 */
public const TYPE_FLOAT = 'float';
/** @since 28.0.0 */
public const TYPE_BOOL = 'bool';
/** @since 28.0.0 */
public const TYPE_ARRAY = 'array';
/** @since 28.0.0 */
public const TYPE_STRING_LIST = 'string[]';
/** @since 28.0.0 */
public const TYPE_INT_LIST = 'int[]';

/** @since 28.0.0 */
public const EDIT_FORBIDDEN = 0;
/** @since 28.0.0 */
public const EDIT_REQ_OWNERSHIP = 1;
/** @since 28.0.0 */
public const EDIT_REQ_WRITE_PERMISSION = 2;
/** @since 28.0.0 */
public const EDIT_REQ_READ_PERMISSION = 3;


/**
* Unless a call of import() to deserialize an object is expected, a valid value type is needed here.
*
@@ -287,6 +301,26 @@ interface IMetadataValueWrapper extends JsonSerializable {
*/
public function isIndexed(): bool;

/**
* set remote edit permission
* (Webdav PROPPATCH)
*
* @param int $permission edit permission
*
* @return self
* @since 28.0.0
*/
public function setEditPermission(int $permission): self;

/**
* get remote edit permission
* (Webdav PROPPATCH)
*
* @return int edit permission
* @since 28.0.0
*/
public function getEditPermission(): int;

/**
* deserialize the object from a json
*

Loading…
Cancel
Save