aboutsummaryrefslogtreecommitdiffstats
path: root/lib/unstable
diff options
context:
space:
mode:
Diffstat (limited to 'lib/unstable')
-rw-r--r--lib/unstable/.gitkeep0
-rw-r--r--lib/unstable/Config/Exceptions/IncorrectTypeException.php19
-rw-r--r--lib/unstable/Config/Exceptions/TypeConflictException.php19
-rw-r--r--lib/unstable/Config/Exceptions/UnknownKeyException.php19
-rw-r--r--lib/unstable/Config/IUserConfig.php831
-rw-r--r--lib/unstable/Config/Lexicon/ConfigLexiconEntry.php297
-rw-r--r--lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php48
-rw-r--r--lib/unstable/Config/Lexicon/IConfigLexicon.php55
-rw-r--r--lib/unstable/Config/Lexicon/Preset.php75
-rw-r--r--lib/unstable/Config/ValueType.php143
-rw-r--r--lib/unstable/Federation/ISignedCloudFederationProvider.php33
-rw-r--r--lib/unstable/Security/Signature/Enum/DigestAlgorithm.php34
-rw-r--r--lib/unstable/Security/Signature/Enum/SignatoryStatus.php24
-rw-r--r--lib/unstable/Security/Signature/Enum/SignatoryType.php30
-rw-r--r--lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php21
-rw-r--r--lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php15
-rw-r--r--lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php15
-rw-r--r--lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php15
-rw-r--r--lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php15
-rw-r--r--lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php15
-rw-r--r--lib/unstable/Security/Signature/Exceptions/SignatoryException.php15
-rw-r--r--lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php15
-rw-r--r--lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php15
-rw-r--r--lib/unstable/Security/Signature/Exceptions/SignatureException.php17
-rw-r--r--lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php15
-rw-r--r--lib/unstable/Security/Signature/IIncomingSignedRequest.php66
-rw-r--r--lib/unstable/Security/Signature/IOutgoingSignedRequest.php112
-rw-r--r--lib/unstable/Security/Signature/ISignatoryManager.php73
-rw-r--r--lib/unstable/Security/Signature/ISignatureManager.php136
-rw-r--r--lib/unstable/Security/Signature/ISignedRequest.php121
-rw-r--r--lib/unstable/Security/Signature/Model/Signatory.php200
31 files changed, 2508 insertions, 0 deletions
diff --git a/lib/unstable/.gitkeep b/lib/unstable/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/lib/unstable/.gitkeep
diff --git a/lib/unstable/Config/Exceptions/IncorrectTypeException.php b/lib/unstable/Config/Exceptions/IncorrectTypeException.php
new file mode 100644
index 00000000000..6b91959071a
--- /dev/null
+++ b/lib/unstable/Config/Exceptions/IncorrectTypeException.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Config\Exceptions;
+
+use Exception;
+
+/**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Exceptions\IncorrectTypeException
+ * @see \OCP\Config\Exceptions\IncorrectTypeException
+ */
+class IncorrectTypeException extends Exception {
+}
diff --git a/lib/unstable/Config/Exceptions/TypeConflictException.php b/lib/unstable/Config/Exceptions/TypeConflictException.php
new file mode 100644
index 00000000000..808679ed873
--- /dev/null
+++ b/lib/unstable/Config/Exceptions/TypeConflictException.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Config\Exceptions;
+
+use Exception;
+
+/**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Exceptions\TypeConflictException
+ * @see \OCP\Config\Exceptions\TypeConflictException
+ */
+class TypeConflictException extends Exception {
+}
diff --git a/lib/unstable/Config/Exceptions/UnknownKeyException.php b/lib/unstable/Config/Exceptions/UnknownKeyException.php
new file mode 100644
index 00000000000..744ce25e48d
--- /dev/null
+++ b/lib/unstable/Config/Exceptions/UnknownKeyException.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Config\Exceptions;
+
+use Exception;
+
+/**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Exceptions\UnknownKeyException
+ * @see \OCP\Config\Exceptions\UnknownKeyException
+ */
+class UnknownKeyException extends Exception {
+}
diff --git a/lib/unstable/Config/IUserConfig.php b/lib/unstable/Config/IUserConfig.php
new file mode 100644
index 00000000000..bffd6a96ebd
--- /dev/null
+++ b/lib/unstable/Config/IUserConfig.php
@@ -0,0 +1,831 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Config;
+
+use Generator;
+use NCU\Config\Exceptions\IncorrectTypeException;
+use NCU\Config\Exceptions\UnknownKeyException;
+
+/**
+ * This class provides an easy way for apps to store user config in the
+ * database.
+ * Supports **lazy loading**
+ *
+ * ### What is lazy loading ?
+ * In order to avoid loading useless user config into memory for each request,
+ * only non-lazy values are now loaded.
+ *
+ * Once a value that is lazy is requested, all lazy values will be loaded.
+ *
+ * Similarly, some methods from this class are marked with a warning about ignoring
+ * lazy loading. Use them wisely and only on parts of the code that are called
+ * during specific requests or actions to avoid loading the lazy values all the time.
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+interface IUserConfig {
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public const FLAG_SENSITIVE = 1; // value is sensitive
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public const FLAG_INDEXED = 2; // value should be indexed
+
+ /**
+ * Get list of all userIds with config stored in database.
+ * If $appId is specified, will only limit the search to this value
+ *
+ * **WARNING:** ignore any cache and get data directly from database.
+ *
+ * @param string $appId optional id of app
+ *
+ * @return list<string> list of userIds
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function getUserIds(string $appId = ''): array;
+
+ /**
+ * Get list of all apps that have at least one config
+ * value related to $userId stored in database
+ *
+ * **WARNING:** ignore lazy filtering, all user config are loaded from database
+ *
+ * @param string $userId id of the user
+ *
+ * @return list<string> list of app ids
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function getApps(string $userId): array;
+
+ /**
+ * Returns all keys stored in database, related to user+app.
+ * Please note that the values are not returned.
+ *
+ * **WARNING:** ignore lazy filtering, all user config are loaded from database
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ *
+ * @return list<string> list of stored config keys
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function getKeys(string $userId, string $app): array;
+
+ /**
+ * Check if a key exists in the list of stored config values.
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return bool TRUE if key exists
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function hasKey(string $userId, string $app, string $key, ?bool $lazy = false): bool;
+
+ /**
+ * best way to see if a value is set as sensitive (not displayed in report)
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool|null $lazy search within lazy loaded config
+ *
+ * @return bool TRUE if value is sensitive
+ * @throws UnknownKeyException if config key is not known
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function isSensitive(string $userId, string $app, string $key, ?bool $lazy = false): bool;
+
+ /**
+ * best way to see if a value is set as indexed (so it can be search)
+ *
+ * @see self::searchUsersByValueString()
+ * @see self::searchUsersByValueInt()
+ * @see self::searchUsersByValueBool()
+ * @see self::searchUsersByValues()
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool|null $lazy search within lazy loaded config
+ *
+ * @return bool TRUE if value is sensitive
+ * @throws UnknownKeyException if config key is not known
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function isIndexed(string $userId, string $app, string $key, ?bool $lazy = false): bool;
+
+ /**
+ * Returns if the config key stored in database is lazy loaded
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @return bool TRUE if config is lazy loaded
+ * @throws UnknownKeyException if config key is not known
+ * @see IUserConfig for details about lazy loading
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function isLazy(string $userId, string $app, string $key): bool;
+
+ /**
+ * List all config values from an app with config key starting with $key.
+ * Returns an array with config key as key, stored value as value.
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $prefix config keys prefix to search, can be empty.
+ * @param bool $filtered filter sensitive config values
+ *
+ * @return array<string, string|int|float|bool|array> [key => value]
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function getValues(string $userId, string $app, string $prefix = '', bool $filtered = false): array;
+
+ /**
+ * List all config values of a user.
+ * Returns an array with config key as key, stored value as value.
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $userId id of the user
+ * @param bool $filtered filter sensitive config values
+ *
+ * @return array<string, string|int|float|bool|array> [key => value]
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function getAllValues(string $userId, bool $filtered = false): array;
+
+ /**
+ * List all apps storing a specific config key and its stored value.
+ * Returns an array with appId as key, stored value as value.
+ *
+ * @param string $userId id of the user
+ * @param string $key config key
+ * @param bool $lazy search within lazy loaded config
+ * @param ValueType|null $typedAs enforce type for the returned values
+ *
+ * @return array<string, string|int|float|bool|array> [appId => value]
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ * @psalm-suppress DeprecatedClass
+ */
+ public function getValuesByApps(string $userId, string $key, bool $lazy = false, ?ValueType $typedAs = null): array;
+
+ /**
+ * List all users storing a specific config key and its stored value.
+ * Returns an array with userId as key, stored value as value.
+ *
+ * **WARNING:** no caching, generate a fresh request
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param ValueType|null $typedAs enforce type for the returned values
+ * @param array|null $userIds limit the search to a list of user ids
+ *
+ * @return array<string, string|int|float|bool|array> [userId => value]
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ * @psalm-suppress DeprecatedClass
+ */
+ public function getValuesByUsers(string $app, string $key, ?ValueType $typedAs = null, ?array $userIds = null): array;
+
+ /**
+ * List all users storing a specific config key/value pair.
+ * Returns a list of user ids.
+ *
+ * **WARNING:** no caching, generate a fresh request
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $value config value
+ * @param bool $caseInsensitive non-case-sensitive search, only works if $value is a string
+ *
+ * @return Generator<string>
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function searchUsersByValueString(string $app, string $key, string $value, bool $caseInsensitive = false): Generator;
+
+ /**
+ * List all users storing a specific config key/value pair.
+ * Returns a list of user ids.
+ *
+ * **WARNING:** no caching, generate a fresh request
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param int $value config value
+ *
+ * @return Generator<string>
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function searchUsersByValueInt(string $app, string $key, int $value): Generator;
+
+ /**
+ * List all users storing a specific config key/value pair.
+ * Returns a list of user ids.
+ *
+ * **WARNING:** no caching, generate a fresh request
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param array $values list of possible config values
+ *
+ * @return Generator<string>
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function searchUsersByValues(string $app, string $key, array $values): Generator;
+
+ /**
+ * List all users storing a specific config key/value pair.
+ * Returns a list of user ids.
+ *
+ * **WARNING:** no caching, generate a fresh request
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $value config value
+ *
+ * @return Generator<string>
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function searchUsersByValueBool(string $app, string $key, bool $value): Generator;
+
+ /**
+ * Get user config assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return string stored config value or $default if not set in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserConfig for explanation about lazy loading
+ * @see getValueInt()
+ * @see getValueFloat()
+ * @see getValueBool()
+ * @see getValueArray()
+ */
+ public function getValueString(string $userId, string $app, string $key, string $default = '', bool $lazy = false): string;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param int $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return int stored config value or $default if not set in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserConfig for explanation about lazy loading
+ * @see getValueString()
+ * @see getValueFloat()
+ * @see getValueBool()
+ * @see getValueArray()
+ */
+ public function getValueInt(string $userId, string $app, string $key, int $default = 0, bool $lazy = false): int;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param float $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return float stored config value or $default if not set in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserConfig for explanation about lazy loading
+ * @see getValueString()
+ * @see getValueInt()
+ * @see getValueBool()
+ * @see getValueArray()
+ */
+ public function getValueFloat(string $userId, string $app, string $key, float $default = 0, bool $lazy = false): float;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return bool stored config value or $default if not set in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserPrefences for explanation about lazy loading
+ * @see getValueString()
+ * @see getValueInt()
+ * @see getValueFloat()
+ * @see getValueArray()
+ */
+ public function getValueBool(string $userId, string $app, string $key, bool $default = false, bool $lazy = false): bool;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param array $default default value`
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return array stored config value or $default if not set in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserConfig for explanation about lazy loading
+ * @see getValueString()
+ * @see getValueInt()
+ * @see getValueFloat()
+ * @see getValueBool()
+ */
+ public function getValueArray(string $userId, string $app, string $key, array $default = [], bool $lazy = false): array;
+
+ /**
+ * returns the type of config value
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ * unless lazy is set to false
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool|null $lazy
+ *
+ * @return ValueType type of the value
+ * @throws UnknownKeyException if config key is not known
+ * @throws IncorrectTypeException if config value type is not known
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ * @psalm-suppress DeprecatedClass
+ */
+ public function getValueType(string $userId, string $app, string $key, ?bool $lazy = null): ValueType;
+
+ /**
+ * returns a bitflag related to config value
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ * unless lazy is set to false
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $lazy lazy loading
+ *
+ * @return int a bitflag in relation to the config value
+ * @throws UnknownKeyException if config key is not known
+ * @throws IncorrectTypeException if config value type is not known
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function getValueFlags(string $userId, string $app, string $key, bool $lazy = false): int;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserConfig for explanation about lazy loading
+ * @see setValueInt()
+ * @see setValueFloat()
+ * @see setValueBool()
+ * @see setValueArray()
+ */
+ public function setValueString(string $userId, string $app, string $key, string $value, bool $lazy = false, int $flags = 0): bool;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * When handling huge value around and/or above 2,147,483,647, a debug log will be generated
+ * on 64bits system, as php int type reach its limit (and throw an exception) on 32bits when using huge numbers.
+ *
+ * When using huge numbers, it is advised to use {@see \OCP\Util::numericToNumber()} and {@see setValueString()}
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param int $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserConfig for explanation about lazy loading
+ * @see setValueString()
+ * @see setValueFloat()
+ * @see setValueBool()
+ * @see setValueArray()
+ */
+ public function setValueInt(string $userId, string $app, string $key, int $value, bool $lazy = false, int $flags = 0): bool;
+
+ /**
+ * Store a config key and its value in database.
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param float $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserConfig for explanation about lazy loading
+ * @see setValueString()
+ * @see setValueInt()
+ * @see setValueBool()
+ * @see setValueArray()
+ */
+ public function setValueFloat(string $userId, string $app, string $key, float $value, bool $lazy = false, int $flags = 0): bool;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $value config value
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserConfig for explanation about lazy loading
+ * @see setValueString()
+ * @see setValueInt()
+ * @see setValueFloat()
+ * @see setValueArray()
+ */
+ public function setValueBool(string $userId, string $app, string $key, bool $value, bool $lazy = false): bool;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param array $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ *
+ * @see IUserConfig for explanation about lazy loading
+ * @see setValueString()
+ * @see setValueInt()
+ * @see setValueFloat()
+ * @see setValueBool()
+ */
+ public function setValueArray(string $userId, string $app, string $key, array $value, bool $lazy = false, int $flags = 0): bool;
+
+ /**
+ * switch sensitive status of a config value
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
+ *
+ * @return bool TRUE if database update were necessary
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool;
+
+ /**
+ * switch sensitive loading status of a config key for all users
+ *
+ * **Warning:** heavy on resources, MUST only be used on occ command or migrations
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function updateGlobalSensitive(string $app, string $key, bool $sensitive): void;
+
+
+ /**
+ * switch indexed status of a config value
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $indexed TRUE to set as indexed, FALSE to unset
+ *
+ * @return bool TRUE if database update were necessary
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool;
+
+ /**
+ * switch sensitive loading status of a config key for all users
+ *
+ * **Warning:** heavy on resources, MUST only be used on occ command or migrations
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $indexed TRUE to set as indexed, FALSE to unset
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function updateGlobalIndexed(string $app, string $key, bool $indexed): void;
+
+ /**
+ * switch lazy loading status of a config value
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
+ *
+ * @return bool TRUE if database update was necessary
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool;
+
+ /**
+ * switch lazy loading status of a config key for all users
+ *
+ * **Warning:** heavy on resources, MUST only be used on occ command or migrations
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function updateGlobalLazy(string $app, string $key, bool $lazy): void;
+
+ /**
+ * returns an array contains details about a config value
+ *
+ * ```
+ * [
+ * "app" => "myapp",
+ * "key" => "mykey",
+ * "value" => "its_value",
+ * "lazy" => false,
+ * "type" => 4,
+ * "typeString" => "string",
+ * 'sensitive' => true
+ * ]
+ * ```
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @return array
+ * @throws UnknownKeyException if config key is not known in database
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function getDetails(string $userId, string $app, string $key): array;
+
+ /**
+ * Delete single config key from database.
+ *
+ * @param string $userId id of the user
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function deleteUserConfig(string $userId, string $app, string $key): void;
+
+ /**
+ * Delete config values from all users linked to a specific config keys
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function deleteKey(string $app, string $key): void;
+
+ /**
+ * delete all config keys linked to an app
+ *
+ * @param string $app id of the app
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function deleteApp(string $app): void;
+
+ /**
+ * delete all config keys linked to a user
+ *
+ * @param string $userId id of the user
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function deleteAllUserConfig(string $userId): void;
+
+ /**
+ * Clear the cache for a single user
+ *
+ * The cache will be rebuilt only the next time a user config is requested.
+ *
+ * @param string $userId id of the user
+ * @param bool $reload set to TRUE to refill cache instantly after clearing it
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function clearCache(string $userId, bool $reload = false): void;
+
+ /**
+ * Clear the cache for all users.
+ * The cache will be rebuilt only the next time a user config is requested.
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\IUserConfig
+ * @see \OCP\Config\IUserConfig
+ */
+ public function clearCacheAll(): void;
+}
diff --git a/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php b/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php
new file mode 100644
index 00000000000..2587cd52c01
--- /dev/null
+++ b/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php
@@ -0,0 +1,297 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Config\Lexicon;
+
+use Closure;
+use NCU\Config\ValueType;
+
+/**
+ * Model that represent config values within an app config lexicon.
+ *
+ * @see IConfigLexicon
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ * @psalm-suppress DeprecatedClass
+ */
+class ConfigLexiconEntry {
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ public const RENAME_INVERT_BOOLEAN = 1;
+
+ private string $definition = '';
+ private ?string $default = null;
+
+ /**
+ * @param string $key config key, can only contain alphanumerical chars and -._
+ * @param ValueType $type type of config value
+ * @param string $definition optional description of config key available when using occ command
+ * @param bool $lazy set config value as lazy
+ * @param int $flags set flags
+ * @param string|null $rename previous config key to migrate config value from
+ * @param bool $deprecated set config key as deprecated
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ * @psalm-suppress PossiblyInvalidCast
+ * @psalm-suppress RiskyCast
+ */
+ public function __construct(
+ private readonly string $key,
+ private readonly ValueType $type,
+ private null|string|int|float|bool|array|Closure $defaultRaw = null,
+ string $definition = '',
+ private readonly bool $lazy = false,
+ private readonly int $flags = 0,
+ private readonly bool $deprecated = false,
+ private readonly ?string $rename = null,
+ private readonly int $options = 0,
+ ) {
+ // key can only contain alphanumeric chars and underscore "_"
+ if (preg_match('/[^[:alnum:]_]/', $key)) {
+ throw new \Exception('invalid config key');
+ }
+
+ /** @psalm-suppress UndefinedClass */
+ if (\OC::$CLI) { // only store definition if ran from CLI
+ $this->definition = $definition;
+ }
+ }
+
+ /**
+ * returns the config key
+ *
+ * @return string config key
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ public function getKey(): string {
+ return $this->key;
+ }
+
+ /**
+ * get expected type for config value
+ *
+ * @return ValueType
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ * @psalm-suppress DeprecatedClass
+ */
+ public function getValueType(): ValueType {
+ return $this->type;
+ }
+
+ /**
+ * @param string $default
+ * @return string
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ private function convertFromString(string $default): string {
+ return $default;
+ }
+
+ /**
+ * @param int $default
+ * @return string
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ private function convertFromInt(int $default): string {
+ return (string)$default;
+ }
+
+ /**
+ * @param float $default
+ * @return string
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ private function convertFromFloat(float $default): string {
+ return (string)$default;
+ }
+
+ /**
+ * @param bool $default
+ * @return string
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ private function convertFromBool(bool $default): string {
+ return ($default) ? '1' : '0';
+ }
+
+ /**
+ * @param array $default
+ * @return string
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ private function convertFromArray(array $default): string {
+ return json_encode($default);
+ }
+
+ /**
+ * returns default value
+ *
+ * @return string|null NULL if no default is set
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ * @psalm-suppress DeprecatedClass
+ * @psalm-suppress DeprecatedMethod
+ */
+ public function getDefault(Preset $preset): ?string {
+ if ($this->default !== null) {
+ return $this->default;
+ }
+
+ if ($this->defaultRaw === null) {
+ return null;
+ }
+
+ if ($this->defaultRaw instanceof Closure) {
+ /** @psalm-suppress MixedAssignment we expect closure to returns string|int|float|bool|array */
+ $this->defaultRaw = ($this->defaultRaw)($preset);
+ }
+
+ /** @psalm-suppress MixedArgument closure should be managed previously */
+ $this->default = $this->convertToString($this->defaultRaw);
+
+ return $this->default;
+ }
+
+ /**
+ * convert $entry into string, based on the expected type for config value
+ *
+ * @param string|int|float|bool|array $entry
+ *
+ * @return string
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ * @psalm-suppress PossiblyInvalidCast arrays are managed pre-cast
+ * @psalm-suppress RiskyCast
+ * @psalm-suppress DeprecatedClass
+ * @psalm-suppress DeprecatedMethod
+ * @psalm-suppress DeprecatedConstant
+ */
+ public function convertToString(string|int|float|bool|array $entry): string {
+ // in case $default is array but is not expected to be an array...
+ if ($this->getValueType() !== ValueType::ARRAY && is_array($entry)) {
+ $entry = json_encode($entry, JSON_THROW_ON_ERROR);
+ }
+
+ return match ($this->getValueType()) {
+ ValueType::MIXED => (string)$entry,
+ ValueType::STRING => $this->convertFromString((string)$entry),
+ ValueType::INT => $this->convertFromInt((int)$entry),
+ ValueType::FLOAT => $this->convertFromFloat((float)$entry),
+ ValueType::BOOL => $this->convertFromBool((bool)$entry),
+ ValueType::ARRAY => $this->convertFromArray((array)$entry)
+ };
+ }
+
+ /**
+ * returns definition
+ *
+ * @return string
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ public function getDefinition(): string {
+ return $this->definition;
+ }
+
+ /**
+ * returns if config key is set as lazy
+ *
+ * @see IAppConfig for details on lazy config values
+ * @return bool TRUE if config value is lazy
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ public function isLazy(): bool {
+ return $this->lazy;
+ }
+
+ /**
+ * returns flags
+ *
+ * @see IAppConfig for details on sensitive config values
+ * @return int bitflag about the config value
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ public function getFlags(): int {
+ return $this->flags;
+ }
+
+ /**
+ * @param int $flag
+ *
+ * @return bool TRUE is config value bitflag contains $flag
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ * @psalm-suppress DeprecatedMethod
+ */
+ public function isFlagged(int $flag): bool {
+ return (($flag & $this->getFlags()) === $flag);
+ }
+
+ /**
+ * should be called/used only during migration/upgrade.
+ * link to an old config key.
+ *
+ * @return string|null not NULL if value can be imported from a previous key
+ * @experimental 32.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ public function getRename(): ?string {
+ return $this->rename;
+ }
+
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ * @return bool TRUE if $option was set during the creation of the entry.
+ */
+ public function hasOption(int $option): bool {
+ return (($option & $this->options) !== 0);
+ }
+
+ /**
+ * returns if config key is set as deprecated
+ *
+ * @return bool TRUE if config si deprecated
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Entry
+ * @see \OCP\Config\Lexicon\Entry
+ */
+ public function isDeprecated(): bool {
+ return $this->deprecated;
+ }
+}
diff --git a/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php b/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php
new file mode 100644
index 00000000000..2ccd7bd265c
--- /dev/null
+++ b/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace NCU\Config\Lexicon;
+
+/**
+ * Strictness regarding using not-listed config keys
+ *
+ * - **ConfigLexiconStrictness::IGNORE** - fully ignore
+ * - **ConfigLexiconStrictness::NOTICE** - ignore and report
+ * - **ConfigLexiconStrictness::WARNING** - silently block (returns $default) and report
+ * - **ConfigLexiconStrictness::EXCEPTION** - block (throws exception) and report
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Strictness
+ * @see \OCP\Config\Lexicon\Strictness
+ */
+enum ConfigLexiconStrictness {
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Strictness
+ * @see \OCP\Config\Lexicon\Strictness
+ */
+ case IGNORE; // fully ignore
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Strictness
+ * @see \OCP\Config\Lexicon\Strictness
+ */
+ case NOTICE; // ignore and report
+ /**
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Strictness
+ * @see \OCP\Config\Lexicon\Strictness
+ * @experimental 31.0.0
+ */
+ case WARNING; // silently block (returns $default) and report
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Strictness
+ * @see \OCP\Config\Lexicon\Strictness
+ */
+ case EXCEPTION; // block (throws exception) and report
+}
diff --git a/lib/unstable/Config/Lexicon/IConfigLexicon.php b/lib/unstable/Config/Lexicon/IConfigLexicon.php
new file mode 100644
index 00000000000..74dc19e7728
--- /dev/null
+++ b/lib/unstable/Config/Lexicon/IConfigLexicon.php
@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace NCU\Config\Lexicon;
+
+/**
+ * This interface needs to be implemented if you want to define a config lexicon for your application
+ * The config lexicon is used to avoid conflicts and problems when storing/retrieving config values
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\ILexicon
+ * @see \OCP\Config\Lexicon\ILexicon
+ */
+interface IConfigLexicon {
+
+ /**
+ * Define the expected behavior when using config
+ * keys not set within your application config lexicon.
+ *
+ * @return ConfigLexiconStrictness
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\ILexicon
+ * @see \OCP\Config\Lexicon\ILexicon
+ * @psalm-suppress DeprecatedClass
+ *
+ */
+ public function getStrictness(): ConfigLexiconStrictness;
+
+ /**
+ * define the list of entries of your application config lexicon, related to AppConfig.
+ *
+ * @return ConfigLexiconEntry[]
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\ILexicon
+ * @see \OCP\Config\Lexicon\ILexicon
+ * @psalm-suppress DeprecatedClass
+ */
+ public function getAppConfigs(): array;
+
+ /**
+ * define the list of entries of your application config lexicon, related to UserPreferences.
+ *
+ * @return ConfigLexiconEntry[]
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\ILexicon
+ * @see \OCP\Config\Lexicon\ILexicon
+ * @psalm-suppress DeprecatedClass
+ */
+ public function getUserConfigs(): array;
+}
diff --git a/lib/unstable/Config/Lexicon/Preset.php b/lib/unstable/Config/Lexicon/Preset.php
new file mode 100644
index 00000000000..b2a98ed5647
--- /dev/null
+++ b/lib/unstable/Config/Lexicon/Preset.php
@@ -0,0 +1,75 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace NCU\Config\Lexicon;
+
+/**
+ * list of preset to handle the default behavior of the instance
+ *
+ * @see ConfigLexiconEntry::preset
+ *
+ * - **Preset::LARGE** - Large size organisation (> 50k accounts)
+ * - **Preset::MEDIUM** - Medium size organisation (> 100 accounts)
+ * - **Preset::SMALL** - Small size organisation (< 100 accounts)
+ * - **Preset::SHARED** - Shared hosting
+ * - **Preset::EDUCATION** - School/University
+ * - **Preset::CLUB** - Club/Association
+ * - **Preset::FAMILY** - Family
+ * - **Preset::PRIVATE** - Private
+ *
+ * @experimental 32.0.0
+ * @deprecated 32.0.0 use \OCP\Config\Lexicon\Preset
+ * @see \OCP\Config\Lexicon\Preset
+ */
+enum Preset: int {
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0
+ */
+ case LARGE = 8;
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0
+ */
+ case MEDIUM = 7;
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0
+ */
+ case SMALL = 6;
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0
+ */
+ case SHARED = 5;
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0
+ */
+ case EDUCATION = 4;
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0
+ */
+ case CLUB = 3;
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0
+ */
+ case FAMILY = 2;
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0
+ */
+ case PRIVATE = 1;
+ /**
+ * @experimental 32.0.0
+ * @deprecated 32.0.0
+ */
+ case NONE = 0;
+}
diff --git a/lib/unstable/Config/ValueType.php b/lib/unstable/Config/ValueType.php
new file mode 100644
index 00000000000..73714640445
--- /dev/null
+++ b/lib/unstable/Config/ValueType.php
@@ -0,0 +1,143 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Config;
+
+use NCU\Config\Exceptions\IncorrectTypeException;
+use OCP\IAppConfig;
+use UnhandledMatchError;
+
+/**
+ * Listing of available value type for typed config value
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ */
+enum ValueType: int {
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ */
+ case MIXED = 0;
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ */
+ case STRING = 1;
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ */
+ case INT = 2;
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ */
+ case FLOAT = 3;
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ */
+ case BOOL = 4;
+ /**
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ */
+ case ARRAY = 5;
+
+ /**
+ * get ValueType from string
+ *
+ * @param string $definition
+ *
+ * @return self
+ * @throws IncorrectTypeException
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ * @psalm-suppress DeprecatedConstant
+ * @psalm-suppress DeprecatedClass
+ */
+ public static function fromStringDefinition(string $definition): self {
+ try {
+ return match ($definition) {
+ 'mixed' => self::MIXED,
+ 'string' => self::STRING,
+ 'int' => self::INT,
+ 'float' => self::FLOAT,
+ 'bool' => self::BOOL,
+ 'array' => self::ARRAY
+ };
+ } catch (\UnhandledMatchError) {
+ throw new IncorrectTypeException('unknown string definition');
+ }
+ }
+
+ /**
+ * get string definition for current enum value
+ *
+ * @return string
+ * @throws IncorrectTypeException
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ * @psalm-suppress DeprecatedConstant
+ * @psalm-suppress DeprecatedClass
+ */
+ public function getDefinition(): string {
+ try {
+ return match ($this) {
+ self::MIXED => 'mixed',
+ self::STRING => 'string',
+ self::INT => 'int',
+ self::FLOAT => 'float',
+ self::BOOL => 'bool',
+ self::ARRAY => 'array',
+ };
+ } catch (UnhandledMatchError) {
+ throw new IncorrectTypeException('unknown type definition ' . $this->value);
+ }
+ }
+
+ /**
+ * get corresponding AppConfig flag value
+ *
+ * @return int
+ * @throws IncorrectTypeException
+ *
+ * @experimental 31.0.0
+ * @deprecated 32.0.0 use \OCP\Config\ValueType
+ * @see \OCP\Config\ValueType
+ * @psalm-suppress DeprecatedConstant
+ * @psalm-suppress DeprecatedClass
+ */
+ public function toAppConfigFlag(): int {
+ try {
+ return match ($this) {
+ self::MIXED => IAppConfig::VALUE_MIXED,
+ self::STRING => IAppConfig::VALUE_STRING,
+ self::INT => IAppConfig::VALUE_INT,
+ self::FLOAT => IAppConfig::VALUE_FLOAT,
+ self::BOOL => IAppConfig::VALUE_BOOL,
+ self::ARRAY => IAppConfig::VALUE_ARRAY,
+ };
+ } catch (UnhandledMatchError) {
+ throw new IncorrectTypeException('unknown type definition ' . $this->value);
+ }
+ }
+
+}
diff --git a/lib/unstable/Federation/ISignedCloudFederationProvider.php b/lib/unstable/Federation/ISignedCloudFederationProvider.php
new file mode 100644
index 00000000000..1ec50f606ae
--- /dev/null
+++ b/lib/unstable/Federation/ISignedCloudFederationProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Federation;
+
+use OCP\Federation\ICloudFederationProvider;
+
+/**
+ * Interface ICloudFederationProvider
+ *
+ * Enable apps to create their own cloud federation provider
+ *
+ * @experimental 31.0.0
+ */
+interface ISignedCloudFederationProvider extends ICloudFederationProvider {
+
+ /**
+ * returns federationId in direct relation (as recipient or as author) of a sharedSecret
+ * the federationId must be the one at the remote end
+ *
+ * @param string $sharedSecret
+ * @param array $payload
+ *
+ * @experimental 31.0.0
+ * @return string
+ */
+ public function getFederationIdFromSharedSecret(string $sharedSecret, array $payload): string;
+}
diff --git a/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php b/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php
new file mode 100644
index 00000000000..465f33fd2c3
--- /dev/null
+++ b/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Enum;
+
+/**
+ * list of available algorithm when generating digest from body
+ *
+ * @experimental 31.0.0
+ */
+enum DigestAlgorithm: string {
+ /** @experimental 31.0.0 */
+ case SHA256 = 'SHA-256';
+ /** @experimental 31.0.0 */
+ case SHA512 = 'SHA-512';
+
+ /**
+ * returns hashing algorithm to be used when generating digest
+ *
+ * @return string
+ * @experimental 31.0.0
+ */
+ public function getHashingAlgorithm(): string {
+ return match($this) {
+ self::SHA256 => 'sha256',
+ self::SHA512 => 'sha512',
+ };
+ }
+}
diff --git a/lib/unstable/Security/Signature/Enum/SignatoryStatus.php b/lib/unstable/Security/Signature/Enum/SignatoryStatus.php
new file mode 100644
index 00000000000..1e460aed449
--- /dev/null
+++ b/lib/unstable/Security/Signature/Enum/SignatoryStatus.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Enum;
+
+/**
+ * current status of signatory. is it trustable or not ?
+ *
+ * - SYNCED = the remote instance is trustable.
+ * - BROKEN = the remote instance does not use the same key pairs than previously
+ *
+ * @experimental 31.0.0
+ */
+enum SignatoryStatus: int {
+ /** @experimental 31.0.0 */
+ case SYNCED = 1;
+ /** @experimental 31.0.0 */
+ case BROKEN = 9;
+}
diff --git a/lib/unstable/Security/Signature/Enum/SignatoryType.php b/lib/unstable/Security/Signature/Enum/SignatoryType.php
new file mode 100644
index 00000000000..de3e5568479
--- /dev/null
+++ b/lib/unstable/Security/Signature/Enum/SignatoryType.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Enum;
+
+/**
+ * type of link between local and remote instance
+ *
+ * - FORGIVABLE = the keypair can be deleted and refreshed anytime; silently
+ * - REFRESHABLE = the keypair can be refreshed but a notice will be generated
+ * - TRUSTED = any changes of keypair will require human interaction, warning will be issued
+ * - STATIC = error will be issued on conflict, assume keypair cannot be reset.
+ *
+ * @experimental 31.0.0
+ */
+enum SignatoryType: int {
+ /** @experimental 31.0.0 */
+ case FORGIVABLE = 1; // no notice on refresh
+ /** @experimental 31.0.0 */
+ case REFRESHABLE = 4; // notice on refresh
+ /** @experimental 31.0.0 */
+ case TRUSTED = 8; // warning on refresh
+ /** @experimental 31.0.0 */
+ case STATIC = 9; // error on refresh
+}
diff --git a/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php b/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php
new file mode 100644
index 00000000000..5afa8a3f810
--- /dev/null
+++ b/lib/unstable/Security/Signature/Enum/SignatureAlgorithm.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Enum;
+
+/**
+ * list of available algorithm when signing payload
+ *
+ * @experimental 31.0.0
+ */
+enum SignatureAlgorithm: string {
+ /** @experimental 31.0.0 */
+ case RSA_SHA256 = 'rsa-sha256';
+ /** @experimental 31.0.0 */
+ case RSA_SHA512 = 'rsa-sha512';
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php
new file mode 100644
index 00000000000..c8c700033e6
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/IdentityNotFoundException.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @experimental 31.0.0
+ */
+class IdentityNotFoundException extends SignatureException {
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php b/lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php
new file mode 100644
index 00000000000..c334090fdc3
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/IncomingRequestException.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @experimental 31.0.0
+ */
+class IncomingRequestException extends SignatureException {
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php b/lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php
new file mode 100644
index 00000000000..3d8fa78077f
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/InvalidKeyOriginException.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @experimental 31.0.0
+ */
+class InvalidKeyOriginException extends SignatureException {
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php b/lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php
new file mode 100644
index 00000000000..351637ef201
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/InvalidSignatureException.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @experimental 31.0.0
+ */
+class InvalidSignatureException extends SignatureException {
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php b/lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php
new file mode 100644
index 00000000000..e078071e970
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/SignatoryConflictException.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @experimental 31.0.0
+ */
+class SignatoryConflictException extends SignatoryException {
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/SignatoryException.php b/lib/unstable/Security/Signature/Exceptions/SignatoryException.php
new file mode 100644
index 00000000000..92409ab3d98
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/SignatoryException.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @experimental 31.0.0
+ */
+class SignatoryException extends SignatureException {
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php
new file mode 100644
index 00000000000..0234b3e7d5c
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/SignatoryNotFoundException.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @experimental 31.0.0
+ */
+class SignatoryNotFoundException extends SignatoryException {
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php
new file mode 100644
index 00000000000..ca0fa1c2194
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/SignatureElementNotFoundException.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @experimental 31.0.0
+ */
+class SignatureElementNotFoundException extends SignatureException {
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/SignatureException.php b/lib/unstable/Security/Signature/Exceptions/SignatureException.php
new file mode 100644
index 00000000000..12353a8e61b
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/SignatureException.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace NCU\Security\Signature\Exceptions;
+
+use Exception;
+
+/**
+ * @experimental 31.0.0
+ */
+class SignatureException extends Exception {
+}
diff --git a/lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php b/lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php
new file mode 100644
index 00000000000..f015b07673b
--- /dev/null
+++ b/lib/unstable/Security/Signature/Exceptions/SignatureNotFoundException.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Exceptions;
+
+/**
+ * @experimental 31.0.0
+ */
+class SignatureNotFoundException extends SignatureException {
+}
diff --git a/lib/unstable/Security/Signature/IIncomingSignedRequest.php b/lib/unstable/Security/Signature/IIncomingSignedRequest.php
new file mode 100644
index 00000000000..5c06c41c394
--- /dev/null
+++ b/lib/unstable/Security/Signature/IIncomingSignedRequest.php
@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature;
+
+use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
+use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException;
+use NCU\Security\Signature\Exceptions\SignatureException;
+use OCP\IRequest;
+
+/**
+ * model wrapping an actual incoming request, adding details about the signature and the
+ * authenticity of the origin of the request.
+ *
+ * This interface must not be implemented in your application but
+ * instead obtained from {@see ISignatureManager::getIncomingSignedRequest}.
+ *
+ * ```php
+ * $signedRequest = $this->signatureManager->getIncomingSignedRequest($mySignatoryManager);
+ * ```
+ *
+ * @see ISignatureManager for details on signature
+ * @experimental 31.0.0
+ */
+interface IIncomingSignedRequest extends ISignedRequest {
+ /**
+ * returns the base IRequest
+ *
+ * @return IRequest
+ * @experimental 31.0.0
+ */
+ public function getRequest(): IRequest;
+
+ /**
+ * get the hostname at the source of the base request.
+ * based on the keyId defined in the signature header.
+ *
+ * @return string
+ * @experimental 31.0.0
+ */
+ public function getOrigin(): string;
+
+ /**
+ * returns the keyId extracted from the signature headers.
+ * keyId is a mandatory entry in the headers of a signed request.
+ *
+ * @return string
+ * @throws SignatureElementNotFoundException
+ * @experimental 31.0.0
+ */
+ public function getKeyId(): string;
+
+ /**
+ * confirm the current signed request's identity is correct
+ *
+ * @throws SignatureException
+ * @throws SignatoryNotFoundException
+ * @experimental 31.0.0
+ */
+ public function verify(): void;
+}
diff --git a/lib/unstable/Security/Signature/IOutgoingSignedRequest.php b/lib/unstable/Security/Signature/IOutgoingSignedRequest.php
new file mode 100644
index 00000000000..e9af12ea4b4
--- /dev/null
+++ b/lib/unstable/Security/Signature/IOutgoingSignedRequest.php
@@ -0,0 +1,112 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature;
+
+use NCU\Security\Signature\Enum\SignatureAlgorithm;
+use NCU\Security\Signature\Exceptions\SignatoryException;
+use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
+
+/**
+ * extends ISignedRequest to add info requested at the generation of the signature
+ *
+ * This interface must not be implemented in your application but
+ * instead obtained from {@see ISignatureManager::getIncomingSignedRequest}.
+ *
+ * ```php
+ * $signedRequest = $this->signatureManager->getIncomingSignedRequest($mySignatoryManager);
+ * ```
+ *
+ * @see ISignatureManager for details on signature
+ * @experimental 31.0.0
+ */
+interface IOutgoingSignedRequest extends ISignedRequest {
+ /**
+ * set the host of the recipient of the request.
+ *
+ * @param string $host
+ * @return self
+ * @experimental 31.0.0
+ */
+ public function setHost(string $host): self;
+
+ /**
+ * get the host of the recipient of the request.
+ * - on incoming request, this is the local hostname of current instance.
+ * - on outgoing request, this is the remote instance.
+ *
+ * @return string
+ * @experimental 31.0.0
+ */
+ public function getHost(): string;
+
+ /**
+ * add a key/value pair to the headers of the request
+ *
+ * @param string $key
+ * @param string|int|float $value
+ *
+ * @return self
+ * @experimental 31.0.0
+ */
+ public function addHeader(string $key, string|int|float $value): self;
+
+ /**
+ * returns list of headers value that will be added to the base request
+ *
+ * @return array
+ * @experimental 31.0.0
+ */
+ public function getHeaders(): array;
+
+ /**
+ * set the ordered list of used headers in the Signature
+ *
+ * @param list<string> $list
+ *
+ * @return self
+ * @experimental 31.0.0
+ */
+ public function setHeaderList(array $list): self;
+
+ /**
+ * returns ordered list of used headers in the Signature
+ *
+ * @return list<string>
+ * @experimental 31.0.0
+ */
+ public function getHeaderList(): array;
+
+ /**
+ * set algorithm to be used to sign the signature
+ *
+ * @param SignatureAlgorithm $algorithm
+ *
+ * @return self
+ * @experimental 31.0.0
+ */
+ public function setAlgorithm(SignatureAlgorithm $algorithm): self;
+
+ /**
+ * returns the algorithm set to sign the signature
+ *
+ * @return SignatureAlgorithm
+ * @experimental 31.0.0
+ */
+ public function getAlgorithm(): SignatureAlgorithm;
+
+ /**
+ * sign outgoing request providing a certificate that it emanate from this instance
+ *
+ * @return self
+ * @throws SignatoryException
+ * @throws SignatoryNotFoundException
+ * @experimental 31.0.0
+ */
+ public function sign(): self;
+}
diff --git a/lib/unstable/Security/Signature/ISignatoryManager.php b/lib/unstable/Security/Signature/ISignatoryManager.php
new file mode 100644
index 00000000000..c16dace1bde
--- /dev/null
+++ b/lib/unstable/Security/Signature/ISignatoryManager.php
@@ -0,0 +1,73 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature;
+
+use NCU\Security\Signature\Model\Signatory;
+
+/**
+ * ISignatoryManager contains a group of method that will help
+ * - signing outgoing request
+ * - confirm the authenticity of incoming signed request.
+ *
+ * This interface must be implemented to generate a `SignatoryManager` to
+ * be used with {@see ISignatureManager}
+ *
+ * @experimental 31.0.0
+ */
+interface ISignatoryManager {
+ /**
+ * id of the signatory manager.
+ * This is used to store, confirm uniqueness and avoid conflict of the remote key pairs.
+ *
+ * Must be unique.
+ *
+ * @return string
+ * @experimental 31.0.0
+ */
+ public function getProviderId(): string;
+
+ /**
+ * options that might affect the way the whole process is handled:
+ * [
+ * 'bodyMaxSize' => 10000,
+ * 'ttl' => 300,
+ * 'ttlSignatory' => 86400*3,
+ * 'extraSignatureHeaders' => [],
+ * 'algorithm' => 'sha256',
+ * 'dateHeader' => "D, d M Y H:i:s T",
+ * ]
+ *
+ * @return array
+ * @experimental 31.0.0
+ */
+ public function getOptions(): array;
+
+ /**
+ * generate and returns local signatory including private and public key pair.
+ *
+ * Used to sign outgoing request
+ *
+ * @return Signatory
+ * @experimental 31.0.0
+ */
+ public function getLocalSignatory(): Signatory;
+
+ /**
+ * retrieve details and generate signatory from remote instance.
+ * If signatory cannot be found, returns NULL.
+ *
+ * Used to confirm authenticity of incoming request.
+ *
+ * @param string $remote
+ *
+ * @return Signatory|null must be NULL if no signatory is found
+ * @experimental 31.0.0
+ */
+ public function getRemoteSignatory(string $remote): ?Signatory;
+}
diff --git a/lib/unstable/Security/Signature/ISignatureManager.php b/lib/unstable/Security/Signature/ISignatureManager.php
new file mode 100644
index 00000000000..655454f67e7
--- /dev/null
+++ b/lib/unstable/Security/Signature/ISignatureManager.php
@@ -0,0 +1,136 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature;
+
+use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
+use NCU\Security\Signature\Exceptions\IncomingRequestException;
+use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
+use NCU\Security\Signature\Exceptions\SignatureException;
+use NCU\Security\Signature\Exceptions\SignatureNotFoundException;
+use NCU\Security\Signature\Model\Signatory;
+
+/**
+ * ISignatureManager is a service integrated to core that provide tools
+ * to set/get authenticity of/from outgoing/incoming request.
+ *
+ * Quick description of the signature, added to the headers
+ * {
+ * "(request-target)": "post /path",
+ * "content-length": 385,
+ * "date": "Mon, 08 Jul 2024 14:16:20 GMT",
+ * "digest": "SHA-256=U7gNVUQiixe5BRbp4Tg0xCZMTcSWXXUZI2\\/xtHM40S0=",
+ * "host": "hostname.of.the.recipient",
+ * "Signature": "keyId=\"https://author.hostname/key\",algorithm=\"sha256\",headers=\"content-length date digest host\",signature=\"DzN12OCS1rsA[...]o0VmxjQooRo6HHabg==\""
+ * }
+ *
+ * 'content-length' is the total length of the data/content
+ * 'date' is the datetime the request have been initiated
+ * 'digest' is a checksum of the data/content
+ * 'host' is the hostname of the recipient of the request (remote when signing outgoing request, local on incoming request)
+ * 'Signature' contains the signature generated using the private key, and metadata:
+ * - 'keyId' is a unique id, formatted as an url. hostname is used to retrieve the public key via custom discovery
+ * - 'algorithm' define the algorithm used to generate signature
+ * - 'headers' contains a list of element used during the generation of the signature
+ * - 'signature' is the encrypted string, using local private key, of an array containing elements
+ * listed in 'headers' and their value. Some elements (content-length date digest host) are mandatory
+ * to ensure authenticity override protection.
+ *
+ * This interface can be used to inject {@see SignatureManager} in your code:
+ *
+ * ```php
+ * public function __construct(
+ * private ISignatureManager $signatureManager,
+ * ) {}
+ * ```
+ *
+ * instead obtained from {@see ISignatureManager::getIncomingSignedRequest}.
+ *
+ * @experimental 31.0.0
+ */
+interface ISignatureManager {
+ /**
+ * Extracting data from headers and body from the incoming request.
+ * Compare headers and body to confirm authenticity of remote instance.
+ * Returns details about the signed request or throws exception.
+ *
+ * Should be called from Controller.
+ *
+ * @param ISignatoryManager $signatoryManager used to get details about remote instance
+ * @param string|null $body if NULL, body will be extracted from php://input
+ *
+ * @return IIncomingSignedRequest
+ * @throws IncomingRequestException if anything looks wrong with the incoming request
+ * @throws SignatureNotFoundException if incoming request is not signed
+ * @throws SignatureException if signature could not be confirmed
+ * @experimental 31.0.0
+ */
+ public function getIncomingSignedRequest(ISignatoryManager $signatoryManager, ?string $body = null): IIncomingSignedRequest;
+
+ /**
+ * Preparing signature (and headers) to sign an outgoing request.
+ * Returns a IOutgoingSignedRequest containing all details to finalise the packaging of the whole payload
+ *
+ * @param ISignatoryManager $signatoryManager
+ * @param string $content body to be signed
+ * @param string $method needed in the signature
+ * @param string $uri needed in the signature
+ *
+ * @return IOutgoingSignedRequest
+ * @experimental 31.0.0
+ */
+ public function getOutgoingSignedRequest(ISignatoryManager $signatoryManager, string $content, string $method, string $uri): IOutgoingSignedRequest;
+
+ /**
+ * Complete the full process of signing and filling headers from payload when generating
+ * an outgoing request with IClient
+ *
+ * @param ISignatoryManager $signatoryManager
+ * @param array $payload original payload, will be used to sign and completed with new headers with signature elements
+ * @param string $method needed in the signature
+ * @param string $uri needed in the signature
+ *
+ * @return array new payload to be sent, including original payload and signature elements in headers
+ * @experimental 31.0.0
+ */
+ public function signOutgoingRequestIClientPayload(ISignatoryManager $signatoryManager, array $payload, string $method, string $uri): array;
+
+ /**
+ * returns remote signatory stored in local database, based on the remote host.
+ *
+ * @param string $host remote host
+ * @param string $account linked account, should be used when multiple signature can exist for the same host
+ *
+ * @return Signatory
+ * @throws SignatoryNotFoundException if entry does not exist in local database
+ * @experimental 31.0.0
+ */
+ public function getSignatory(string $host, string $account = ''): Signatory;
+
+ /**
+ * returns a fully formatted keyId, based on a fix hostname and path
+ *
+ * @param string $path
+ *
+ * @return string
+ * @throws IdentityNotFoundException if hostname is not set
+ * @experimental 31.0.0
+ */
+ public function generateKeyIdFromConfig(string $path): string;
+
+ /**
+ * returns hostname:port extracted from an uri
+ *
+ * @param string $uri
+ *
+ * @return string
+ * @throws IdentityNotFoundException if identity cannot be extracted
+ * @experimental 31.0.0
+ */
+ public function extractIdentityFromUri(string $uri): string;
+}
diff --git a/lib/unstable/Security/Signature/ISignedRequest.php b/lib/unstable/Security/Signature/ISignedRequest.php
new file mode 100644
index 00000000000..6bf5e7e7dbc
--- /dev/null
+++ b/lib/unstable/Security/Signature/ISignedRequest.php
@@ -0,0 +1,121 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature;
+
+use NCU\Security\Signature\Enum\DigestAlgorithm;
+use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
+use NCU\Security\Signature\Exceptions\SignatureElementNotFoundException;
+use NCU\Security\Signature\Model\Signatory;
+
+/**
+ * model that store data related to a possible signature.
+ * those details will be used:
+ * - to confirm authenticity of a signed incoming request
+ * - to sign an outgoing request
+ *
+ * This interface must not be implemented in your application:
+ * @see IIncomingSignedRequest
+ * @see IOutgoingSignedRequest
+ *
+ * @experimental 31.0.0
+ */
+interface ISignedRequest {
+ /**
+ * payload of the request
+ *
+ * @return string
+ * @experimental 31.0.0
+ */
+ public function getBody(): string;
+
+ /**
+ * get algorithm used to generate digest
+ *
+ * @return DigestAlgorithm
+ * @experimental 31.0.0
+ */
+ public function getDigestAlgorithm(): DigestAlgorithm;
+
+ /**
+ * checksum of the payload of the request
+ *
+ * @return string
+ * @experimental 31.0.0
+ */
+ public function getDigest(): string;
+
+ /**
+ * set the list of headers related to the signature of the request
+ *
+ * @param array $elements
+ *
+ * @return self
+ * @experimental 31.0.0
+ */
+ public function setSigningElements(array $elements): self;
+
+ /**
+ * get the list of elements in the Signature header of the request
+ *
+ * @return array
+ * @experimental 31.0.0
+ */
+ public function getSigningElements(): array;
+
+ /**
+ * @param string $key
+ *
+ * @return string
+ * @throws SignatureElementNotFoundException
+ * @experimental 31.0.0
+ */
+ public function getSigningElement(string $key): string;
+
+ /**
+ * returns data used to generate signature
+ *
+ * @return array
+ * @experimental 31.0.0
+ */
+ public function getSignatureData(): array;
+
+ /**
+ * get the signed version of the signature
+ *
+ * @return string
+ * @experimental 31.0.0
+ */
+ public function getSignature(): string;
+
+ /**
+ * set the signatory, containing keys and details, related to this request
+ *
+ * @param Signatory $signatory
+ * @return self
+ * @experimental 31.0.0
+ */
+ public function setSignatory(Signatory $signatory): self;
+
+ /**
+ * get the signatory, containing keys and details, related to this request
+ *
+ * @return Signatory
+ * @throws SignatoryNotFoundException
+ * @experimental 31.0.0
+ */
+ public function getSignatory(): Signatory;
+
+ /**
+ * returns if a signatory related to this request have been found and defined
+ *
+ * @return bool
+ * @experimental 31.0.0
+ */
+ public function hasSignatory(): bool;
+}
diff --git a/lib/unstable/Security/Signature/Model/Signatory.php b/lib/unstable/Security/Signature/Model/Signatory.php
new file mode 100644
index 00000000000..6bd50bb1098
--- /dev/null
+++ b/lib/unstable/Security/Signature/Model/Signatory.php
@@ -0,0 +1,200 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace NCU\Security\Signature\Model;
+
+use JsonSerializable;
+use NCU\Security\Signature\Enum\SignatoryStatus;
+use NCU\Security\Signature\Enum\SignatoryType;
+use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * model that store keys and details related to host and in use protocol
+ * mandatory details are providerId, host, keyId and public key.
+ * private key is only used for local signatory, used to sign outgoing request
+ *
+ * the pair providerId+host is unique, meaning only one signatory can exist for each host
+ * and protocol
+ *
+ * @experimental 31.0.0
+ *
+ * @method void setProviderId(string $providerId)
+ * @method string getProviderId()
+ * @method string getKeyId()
+ * @method void setKeyIdSum(string $keyIdSum)
+ * @method string getKeyIdSum()
+ * @method void setPublicKey(string $publicKey)
+ * @method string getPublicKey()
+ * @method void setPrivateKey(string $privateKey)
+ * @method string getPrivateKey()
+ * @method void setHost(string $host)
+ * @method string getHost()
+ * @method int getType()
+ * @method void setType(int $type)
+ * @method int getStatus()
+ * @method void setStatus(int $status)
+ * @method void setAccount(?string $account)
+ * @method void setMetadata(array $metadata)
+ * @method ?array getMetadata()
+ * @method void setCreation(int $creation)
+ * @method int getCreation()
+ * @method void setLastUpdated(int $creation)
+ * @method int getLastUpdated()
+ * @psalm-suppress PropertyNotSetInConstructor
+ */
+class Signatory extends Entity implements JsonSerializable {
+ protected string $keyId = '';
+ protected string $keyIdSum = '';
+ protected string $providerId = '';
+ protected string $host = '';
+ protected string $publicKey = '';
+ protected string $privateKey = '';
+ protected ?string $account = '';
+ protected int $type = 9;
+ protected int $status = 1;
+ protected ?array $metadata = null;
+ protected int $creation = 0;
+ protected int $lastUpdated = 0;
+
+ /**
+ * @param bool $local only set to TRUE when managing local signatory
+ *
+ * @experimental 31.0.0
+ */
+ public function __construct(
+ private readonly bool $local = false,
+ ) {
+ $this->addType('providerId', 'string');
+ $this->addType('host', 'string');
+ $this->addType('account', 'string');
+ $this->addType('keyId', 'string');
+ $this->addType('keyIdSum', 'string');
+ $this->addType('publicKey', 'string');
+ $this->addType('metadata', 'json');
+ $this->addType('type', 'integer');
+ $this->addType('status', 'integer');
+ $this->addType('creation', 'integer');
+ $this->addType('lastUpdated', 'integer');
+ }
+
+ /**
+ * @param string $keyId
+ *
+ * @experimental 31.0.0
+ * @throws IdentityNotFoundException if identity cannot be extracted from keyId
+ */
+ public function setKeyId(string $keyId): void {
+ // if set as local (for current instance), we apply some filters.
+ if ($this->local) {
+ // to avoid conflict with duplicate key pairs (ie generated url from the occ command), we enforce https as prefix
+ if (str_starts_with($keyId, 'http://')) {
+ $keyId = 'https://' . substr($keyId, 7);
+ }
+
+ // removing /index.php from generated url
+ $path = parse_url($keyId, PHP_URL_PATH);
+ if (str_starts_with($path, '/index.php/')) {
+ $pos = strpos($keyId, '/index.php');
+ if ($pos !== false) {
+ $keyId = substr_replace($keyId, '', $pos, 10);
+ }
+ }
+ }
+ $this->setter('keyId', [$keyId]); // needed to trigger the update in database
+ $this->setKeyIdSum(hash('sha256', $keyId));
+
+ $this->setHost(self::extractIdentityFromUri($this->getKeyId()));
+ }
+
+ /**
+ * @param SignatoryType $type
+ * @experimental 31.0.0
+ */
+ public function setSignatoryType(SignatoryType $type): void {
+ $this->setType($type->value);
+ }
+
+ /**
+ * @return SignatoryType
+ * @experimental 31.0.0
+ */
+ public function getSignatoryType(): SignatoryType {
+ return SignatoryType::from($this->getType());
+ }
+
+ /**
+ * @param SignatoryStatus $status
+ * @experimental 31.0.0
+ */
+ public function setSignatoryStatus(SignatoryStatus $status): void {
+ $this->setStatus($status->value);
+ }
+
+ /**
+ * @return SignatoryStatus
+ * @experimental 31.0.0
+ */
+ public function getSignatoryStatus(): SignatoryStatus {
+ return SignatoryStatus::from($this->getStatus());
+ }
+
+ /**
+ * @experimental 31.0.0
+ */
+ public function getAccount(): string {
+ return $this->account ?? '';
+ }
+
+ /**
+ * update an entry in metadata
+ *
+ * @param string $key
+ * @param string|int|float|bool|array $value
+ * @experimental 31.0.0
+ */
+ public function setMetaValue(string $key, string|int|float|bool|array $value): void {
+ $this->metadata[$key] = $value;
+ $this->setter('metadata', [$this->metadata]);
+ }
+
+ /**
+ * @return array
+ * @experimental 31.0.0
+ */
+ public function jsonSerialize(): array {
+ return [
+ 'keyId' => $this->getKeyId(),
+ 'publicKeyPem' => $this->getPublicKey()
+ ];
+ }
+
+ /**
+ * static is needed to make this easily callable from outside the model
+ *
+ * @param string $uri
+ *
+ * @return string
+ * @throws IdentityNotFoundException if identity cannot be extracted
+ * @experimental 31.0.0
+ */
+ public static function extractIdentityFromUri(string $uri): string {
+ $identity = parse_url($uri, PHP_URL_HOST);
+ $port = parse_url($uri, PHP_URL_PORT);
+ if ($identity === null || $identity === false) {
+ throw new IdentityNotFoundException('cannot extract identity from ' . $uri);
+ }
+
+ if ($port !== null && $port !== false) {
+ $identity .= ':' . $port;
+ }
+
+ return $identity;
+ }
+
+}