mirror of
https://github.com/nextcloud/server.git
synced 2026-06-08 16:26:59 -04:00
Merge pull request #41390 from nextcloud/enh/noid/limit-proppatch-metadata
Metadata must be set a editable for PROPPATCH
This commit is contained in:
commit
93ea634de6
8 changed files with 271 additions and 64 deletions
|
|
@ -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,55 +530,7 @@ class FilesPlugin extends ServerPlugin {
|
|||
return true;
|
||||
});
|
||||
|
||||
|
||||
/** @var IFilesMetadataManager */
|
||||
$filesMetadataManager = \OCP\Server::get(IFilesMetadataManager::class);
|
||||
$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));
|
||||
|
||||
// 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);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
$this->handleUpdatePropertiesMetadata($propPatch, $node);
|
||||
|
||||
/**
|
||||
* Disable modification of the displayname property for files and
|
||||
|
|
@ -589,6 +541,120 @@ class FilesPlugin extends ServerPlugin {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 ($accessRight, $knownMetadata, $node, $mutation, $filesMetadataManager): bool {
|
||||
$metadata = $filesMetadataManager->getMetadata((int)$node->getFileId(), true);
|
||||
$metadataKey = substr($mutation, strlen(self::FILE_METADATA_PREFIX));
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
* @param \Sabre\DAV\INode $node
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,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…
Reference in a new issue