diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
index c52805c95488d..8c467f11d0cb0 100644
--- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
@@ -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 @@ 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) {
@@ -540,55 +565,96 @@ 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;
+ }
+
+ $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
diff --git a/apps/settings/img/settings.svg b/apps/settings/img/settings.svg
new file mode 100644
index 0000000000000..66e5eb23393b7
--- /dev/null
+++ b/apps/settings/img/settings.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/settings/img/settings_apps.svg b/apps/settings/img/settings_apps.svg
new file mode 100644
index 0000000000000..af35d41f41c39
--- /dev/null
+++ b/apps/settings/img/settings_apps.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/settings/img/users-white.svg b/apps/settings/img/users-white.svg
new file mode 100644
index 0000000000000..91d2a38c1a350
--- /dev/null
+++ b/apps/settings/img/users-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/settings/img/users.svg b/apps/settings/img/users.svg
index c961c86207e23..247f595bdbe92 100644
--- a/apps/settings/img/users.svg
+++ b/apps/settings/img/users.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/lib/private/FilesMetadata/FilesMetadataManager.php b/lib/private/FilesMetadata/FilesMetadataManager.php
index e9d6e677bafda..f90693359950e 100644
--- a/lib/private/FilesMetadata/FilesMetadataManager.php
+++ b/lib/private/FilesMetadata/FilesMetadataManager.php
@@ -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
@@ -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;
}
/**
diff --git a/lib/private/FilesMetadata/MetadataQuery.php b/lib/private/FilesMetadata/MetadataQuery.php
index 56ea90465b26b..9c3e63e5e22df 100644
--- a/lib/private/FilesMetadata/MetadataQuery.php
+++ b/lib/private/FilesMetadata/MetadataQuery.php
@@ -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,
@@ -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.');
}
diff --git a/lib/private/FilesMetadata/Model/FilesMetadata.php b/lib/private/FilesMetadata/Model/FilesMetadata.php
index b10de55579c75..629b537dabee9 100644
--- a/lib/private/FilesMetadata/Model/FilesMetadata.php
+++ b/lib/private/FilesMetadata/Model/FilesMetadata.php
@@ -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
*
@@ -582,7 +614,7 @@ public function importFromDatabase(array $data, string $prefix = ''): IFilesMeta
JSON_THROW_ON_ERROR
)
);
- } catch (JsonException $e) {
+ } catch (JsonException) {
throw new FilesMetadataNotFoundException();
}
}
diff --git a/lib/private/FilesMetadata/Model/MetadataValueWrapper.php b/lib/private/FilesMetadata/Model/MetadataValueWrapper.php
index 159cd1e6fd11f..90f1554180dd7 100644
--- a/lib/private/FilesMetadata/Model/MetadataValueWrapper.php
+++ b/lib/private/FilesMetadata/Model/MetadataValueWrapper.php
@@ -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 @@ 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
*
@@ -383,7 +406,7 @@ 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;
}
@@ -391,7 +414,8 @@ 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()
];
}
}
diff --git a/lib/private/Search/SearchComposer.php b/lib/private/Search/SearchComposer.php
index 0c465385a8ce8..b92fdd250d894 100644
--- a/lib/private/Search/SearchComposer.php
+++ b/lib/private/Search/SearchComposer.php
@@ -223,6 +223,12 @@ private function fetchIcon(string $appId, string $providerId): string {
[$appId, 'app.svg'],
['core', 'places/default-app-icon.svg'],
];
+ if ($appId === 'settings' && $providerId === 'users') {
+ // Conflict:
+ // the file /apps/settings/users.svg is already used in black version by top right user menu
+ // Override icon name here
+ $icons = [['settings', 'users-white.svg']];
+ }
foreach ($icons as $i => $icon) {
try {
return $this->urlGenerator->imagePath(... $icon);
diff --git a/lib/public/FilesMetadata/IFilesMetadataManager.php b/lib/public/FilesMetadata/IFilesMetadataManager.php
index 78ea020e96734..6664c049d917c 100644
--- a/lib/public/FilesMetadata/IFilesMetadataManager.php
+++ b/lib/public/FilesMetadata/IFilesMetadataManager.php
@@ -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.
@@ -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;
}
diff --git a/lib/public/FilesMetadata/Model/IFilesMetadata.php b/lib/public/FilesMetadata/Model/IFilesMetadata.php
index e5b6ff1bce03d..7697a2f37adea 100644
--- a/lib/public/FilesMetadata/Model/IFilesMetadata.php
+++ b/lib/public/FilesMetadata/Model/IFilesMetadata.php
@@ -37,12 +37,14 @@
* "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 @@ public function getIndexes(): array;
*/
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
*
diff --git a/lib/public/FilesMetadata/Model/IMetadataValueWrapper.php b/lib/public/FilesMetadata/Model/IMetadataValueWrapper.php
index 577ff87433002..d34cd070c8b2c 100644
--- a/lib/public/FilesMetadata/Model/IMetadataValueWrapper.php
+++ b/lib/public/FilesMetadata/Model/IMetadataValueWrapper.php
@@ -37,17 +37,31 @@
* @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 @@ public function setIndexed(bool $indexed): self;
*/
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
*