diff options
Diffstat (limited to 'lib/unstable')
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; + } + +} |