1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684 |
- <?php
- declare(strict_types=1);
- namespace OC;
- use InvalidArgumentException;
- use JsonException;
- use NCU\Config\Lexicon\ConfigLexiconEntry;
- use NCU\Config\Lexicon\ConfigLexiconStrictness;
- use NCU\Config\Lexicon\IConfigLexicon;
- use OC\AppFramework\Bootstrap\Coordinator;
- use OCP\DB\Exception as DBException;
- use OCP\DB\QueryBuilder\IQueryBuilder;
- use OCP\Exceptions\AppConfigIncorrectTypeException;
- use OCP\Exceptions\AppConfigTypeConflictException;
- use OCP\Exceptions\AppConfigUnknownKeyException;
- use OCP\IAppConfig;
- use OCP\IConfig;
- use OCP\IDBConnection;
- use OCP\Security\ICrypto;
- use Psr\Log\LoggerInterface;
- class AppConfig implements IAppConfig {
- private const APP_MAX_LENGTH = 32;
- private const KEY_MAX_LENGTH = 64;
- private const ENCRYPTION_PREFIX = '$AppConfigEncryption$';
- private const ENCRYPTION_PREFIX_LENGTH = 21;
-
- private array $fastCache = [];
-
- private array $lazyCache = [];
-
- private array $valueTypes = [];
- private bool $fastLoaded = false;
- private bool $lazyLoaded = false;
-
- private array $configLexiconDetails = [];
-
- private bool $migrationCompleted = true;
- public function __construct(
- protected IDBConnection $connection,
- protected LoggerInterface $logger,
- protected ICrypto $crypto,
- ) {
- }
-
- public function getApps(): array {
- $this->loadConfigAll();
- $apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
- sort($apps);
- return array_values(array_unique($apps));
- }
-
- public function getKeys(string $app): array {
- $this->assertParams($app);
- $this->loadConfigAll($app);
- $keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
- sort($keys);
- return array_values(array_unique($keys));
- }
-
- public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
- $this->assertParams($app, $key);
- $this->loadConfig($app, $lazy);
- if ($lazy === null) {
- $appCache = $this->getAllValues($app);
- return isset($appCache[$key]);
- }
- if ($lazy) {
- return isset($this->lazyCache[$app][$key]);
- }
- return isset($this->fastCache[$app][$key]);
- }
-
- public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
- $this->assertParams($app, $key);
- $this->loadConfig(null, $lazy);
- if (!isset($this->valueTypes[$app][$key])) {
- throw new AppConfigUnknownKeyException('unknown config key');
- }
- return $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key]);
- }
-
- public function isLazy(string $app, string $key): bool {
-
- if ($this->hasKey($app, $key, false)) {
- return false;
- }
-
- if ($this->hasKey($app, $key, true)) {
- return true;
- }
- throw new AppConfigUnknownKeyException('unknown config key');
- }
-
- public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
- $this->assertParams($app, $prefix);
-
- $this->loadConfigAll($app);
-
- $values = $this->formatAppValues($app, ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []));
- $values = array_filter(
- $values,
- function (string $key) use ($prefix): bool {
- return str_starts_with($key, $prefix);
- }, ARRAY_FILTER_USE_KEY
- );
- if (!$filtered) {
- return $values;
- }
-
- foreach ($this->getSensitiveKeys($app) as $sensitiveKeyExp) {
- $sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
- foreach ($sensitiveKeys as $sensitiveKey) {
- $this->valueTypes[$app][$sensitiveKey] = ($this->valueTypes[$app][$sensitiveKey] ?? 0) | self::VALUE_SENSITIVE;
- }
- }
- $result = [];
- foreach ($values as $key => $value) {
- $result[$key] = $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key] ?? 0) ? IConfig::SENSITIVE_VALUE : $value;
- }
- return $result;
- }
-
- public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array {
- $this->assertParams('', $key, true);
- $this->loadConfig(null, $lazy);
-
- if ($lazy) {
- $cache = $this->lazyCache;
- } else {
- $cache = $this->fastCache;
- }
- $values = [];
- foreach (array_keys($cache) as $app) {
- if (isset($cache[$app][$key])) {
- $values[$app] = $this->convertTypedValue($cache[$app][$key], $typedAs ?? $this->getValueType((string)$app, $key, $lazy));
- }
- }
- return $values;
- }
-
- public function getValueMixed(
- string $app,
- string $key,
- string $default = '',
- ?bool $lazy = false,
- ): string {
- try {
- $lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
- } catch (AppConfigUnknownKeyException $e) {
- return $default;
- }
- return $this->getTypedValue(
- $app,
- $key,
- $default,
- $lazy,
- self::VALUE_MIXED
- );
- }
-
- public function getValueString(
- string $app,
- string $key,
- string $default = '',
- bool $lazy = false,
- ): string {
- return $this->getTypedValue($app, $key, $default, $lazy, self::VALUE_STRING);
- }
-
- public function getValueInt(
- string $app,
- string $key,
- int $default = 0,
- bool $lazy = false,
- ): int {
- return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
- }
-
- public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
- return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
- }
-
- public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
- $b = strtolower($this->getTypedValue($app, $key, $default ? 'true' : 'false', $lazy, self::VALUE_BOOL));
- return in_array($b, ['1', 'true', 'yes', 'on']);
- }
-
- public function getValueArray(
- string $app,
- string $key,
- array $default = [],
- bool $lazy = false,
- ): array {
- try {
- $defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
- $value = json_decode($this->getTypedValue($app, $key, $defaultJson, $lazy, self::VALUE_ARRAY), true, flags: JSON_THROW_ON_ERROR);
- return is_array($value) ? $value : [];
- } catch (JsonException) {
- return [];
- }
- }
-
- private function getTypedValue(
- string $app,
- string $key,
- string $default,
- bool $lazy,
- int $type,
- ): string {
- $this->assertParams($app, $key, valueType: $type);
- if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default)) {
- return $default;
- }
- $this->loadConfig($app, $lazy);
-
- $knownType = $this->valueTypes[$app][$key] ?? 0;
- if (!$this->isTyped(self::VALUE_MIXED, $type)
- && $knownType > 0
- && !$this->isTyped(self::VALUE_MIXED, $knownType)
- && !$this->isTyped($type, $knownType)) {
- $this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
- throw new AppConfigTypeConflictException('conflict with value type from database');
- }
-
- if (isset($this->lazyCache[$app][$key])) {
- $value = $this->lazyCache[$app][$key];
- } elseif (isset($this->fastCache[$app][$key])) {
- $value = $this->fastCache[$app][$key];
- } else {
- return $default;
- }
- $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $knownType);
- if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
-
- $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
- }
- return $value;
- }
-
- public function getValueType(string $app, string $key, ?bool $lazy = null): int {
- $this->assertParams($app, $key);
- $this->loadConfig($app, $lazy);
- if (!isset($this->valueTypes[$app][$key])) {
- throw new AppConfigUnknownKeyException('unknown config key');
- }
- $type = $this->valueTypes[$app][$key];
- $type &= ~self::VALUE_SENSITIVE;
- return $type;
- }
-
- public function setValueMixed(
- string $app,
- string $key,
- string $value,
- bool $lazy = false,
- bool $sensitive = false,
- ): bool {
- return $this->setTypedValue(
- $app,
- $key,
- $value,
- $lazy,
- self::VALUE_MIXED | ($sensitive ? self::VALUE_SENSITIVE : 0)
- );
- }
-
- public function setValueString(
- string $app,
- string $key,
- string $value,
- bool $lazy = false,
- bool $sensitive = false,
- ): bool {
- return $this->setTypedValue(
- $app,
- $key,
- $value,
- $lazy,
- self::VALUE_STRING | ($sensitive ? self::VALUE_SENSITIVE : 0)
- );
- }
-
- public function setValueInt(
- string $app,
- string $key,
- int $value,
- bool $lazy = false,
- bool $sensitive = false,
- ): bool {
- if ($value > 2000000000) {
- $this->logger->debug('You are trying to store an integer value around/above 2,147,483,647. This is a reminder that reaching this theoretical limit on 32 bits system will throw an exception.');
- }
- return $this->setTypedValue(
- $app,
- $key,
- (string)$value,
- $lazy,
- self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
- );
- }
-
- public function setValueFloat(
- string $app,
- string $key,
- float $value,
- bool $lazy = false,
- bool $sensitive = false,
- ): bool {
- return $this->setTypedValue(
- $app,
- $key,
- (string)$value,
- $lazy,
- self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
- );
- }
-
- public function setValueBool(
- string $app,
- string $key,
- bool $value,
- bool $lazy = false,
- ): bool {
- return $this->setTypedValue(
- $app,
- $key,
- ($value) ? '1' : '0',
- $lazy,
- self::VALUE_BOOL
- );
- }
-
- public function setValueArray(
- string $app,
- string $key,
- array $value,
- bool $lazy = false,
- bool $sensitive = false,
- ): bool {
- try {
- return $this->setTypedValue(
- $app,
- $key,
- json_encode($value, JSON_THROW_ON_ERROR),
- $lazy,
- self::VALUE_ARRAY | ($sensitive ? self::VALUE_SENSITIVE : 0)
- );
- } catch (JsonException $e) {
- $this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
- throw $e;
- }
- }
-
- private function setTypedValue(
- string $app,
- string $key,
- string $value,
- bool $lazy,
- int $type,
- ): bool {
- $this->assertParams($app, $key);
- if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
- return false;
- }
- $this->loadConfig(null, $lazy);
- $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
- $inserted = $refreshCache = false;
- $origValue = $value;
- if ($sensitive || ($this->hasKey($app, $key, $lazy) && $this->isSensitive($app, $key, $lazy))) {
- $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
- }
- if ($this->hasKey($app, $key, $lazy)) {
-
- if ($origValue === $this->getTypedValue($app, $key, $value, $lazy, $type)
- && (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
- return false;
- }
- } else {
-
- try {
- $insert = $this->connection->getQueryBuilder();
- $insert->insert('appconfig')
- ->setValue('appid', $insert->createNamedParameter($app))
- ->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
- ->setValue('type', $insert->createNamedParameter($type, IQueryBuilder::PARAM_INT))
- ->setValue('configkey', $insert->createNamedParameter($key))
- ->setValue('configvalue', $insert->createNamedParameter($value));
- $insert->executeStatement();
- $inserted = true;
- } catch (DBException $e) {
- if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
- throw $e;
- }
- }
- }
-
- if (!$inserted) {
- $currType = $this->valueTypes[$app][$key] ?? 0;
- if ($currType === 0) {
- $this->loadConfigAll();
- $currType = $this->valueTypes[$app][$key] ?? 0;
- }
-
- if ($currType === 0) {
- $this->logger->warning('Value type is set to zero (0) in database. This is fine only during the upgrade process from 28 to 29.', ['app' => $app, 'key' => $key]);
- $currType = self::VALUE_MIXED;
- }
-
- if (!$this->isTyped(self::VALUE_MIXED, $currType) &&
- ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
- try {
- $currType = $this->convertTypeToString($currType);
- $type = $this->convertTypeToString($type);
- } catch (AppConfigIncorrectTypeException) {
-
- }
- throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
- }
-
- if ($sensitive || $this->isTyped(self::VALUE_SENSITIVE, $currType)) {
- $type |= self::VALUE_SENSITIVE;
- }
- if ($lazy !== $this->isLazy($app, $key)) {
- $refreshCache = true;
- }
- $update = $this->connection->getQueryBuilder();
- $update->update('appconfig')
- ->set('configvalue', $update->createNamedParameter($value))
- ->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
- ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
- ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
- ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
- $update->executeStatement();
- }
- if ($refreshCache) {
- $this->clearCache();
- return true;
- }
-
- if ($lazy) {
- $this->lazyCache[$app][$key] = $value;
- } else {
- $this->fastCache[$app][$key] = $value;
- }
- $this->valueTypes[$app][$key] = $type;
- return true;
- }
-
- public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
- $this->assertParams($app, $key);
- $this->loadConfigAll();
- $lazy = $this->isLazy($app, $key);
-
- if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
- throw new AppConfigIncorrectTypeException('Unknown value type');
- }
- $currType = $this->valueTypes[$app][$key];
- if (($type | self::VALUE_SENSITIVE) === ($currType | self::VALUE_SENSITIVE)) {
- return false;
- }
-
- if ($this->isTyped(self::VALUE_SENSITIVE, $currType)) {
- $type = $type | self::VALUE_SENSITIVE;
- }
- $update = $this->connection->getQueryBuilder();
- $update->update('appconfig')
- ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
- ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
- ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
- $update->executeStatement();
- $this->valueTypes[$app][$key] = $type;
- return true;
- }
-
- public function updateSensitive(string $app, string $key, bool $sensitive): bool {
- $this->assertParams($app, $key);
- $this->loadConfigAll();
- try {
- if ($sensitive === $this->isSensitive($app, $key, null)) {
- return false;
- }
- } catch (AppConfigUnknownKeyException $e) {
- return false;
- }
- $lazy = $this->isLazy($app, $key);
- if ($lazy) {
- $cache = $this->lazyCache;
- } else {
- $cache = $this->fastCache;
- }
- if (!isset($cache[$app][$key])) {
- throw new AppConfigUnknownKeyException('unknown config key');
- }
-
- $type = $this->getValueType($app, $key);
- $value = $cache[$app][$key];
- if ($sensitive) {
- $type |= self::VALUE_SENSITIVE;
- $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
- } else {
- $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
- }
- $update = $this->connection->getQueryBuilder();
- $update->update('appconfig')
- ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
- ->set('configvalue', $update->createNamedParameter($value))
- ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
- ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
- $update->executeStatement();
- $this->valueTypes[$app][$key] = $type;
- return true;
- }
-
- public function updateLazy(string $app, string $key, bool $lazy): bool {
- $this->assertParams($app, $key);
- $this->loadConfigAll();
- try {
- if ($lazy === $this->isLazy($app, $key)) {
- return false;
- }
- } catch (AppConfigUnknownKeyException $e) {
- return false;
- }
- $update = $this->connection->getQueryBuilder();
- $update->update('appconfig')
- ->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
- ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
- ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
- $update->executeStatement();
-
- $this->clearCache();
- return true;
- }
-
- public function getDetails(string $app, string $key): array {
- $this->assertParams($app, $key);
- $this->loadConfigAll();
- $lazy = $this->isLazy($app, $key);
- if ($lazy) {
- $cache = $this->lazyCache;
- } else {
- $cache = $this->fastCache;
- }
- $type = $this->getValueType($app, $key);
- try {
- $typeString = $this->convertTypeToString($type);
- } catch (AppConfigIncorrectTypeException $e) {
- $this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
- $typeString = (string)$type;
- }
- if (!isset($cache[$app][$key])) {
- throw new AppConfigUnknownKeyException('unknown config key');
- }
- $value = $cache[$app][$key];
- $sensitive = $this->isSensitive($app, $key, null);
- if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
- $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
- }
- return [
- 'app' => $app,
- 'key' => $key,
- 'value' => $value,
- 'type' => $type,
- 'lazy' => $lazy,
- 'typeString' => $typeString,
- 'sensitive' => $sensitive
- ];
- }
-
- public function convertTypeToInt(string $type): int {
- return match (strtolower($type)) {
- 'mixed' => IAppConfig::VALUE_MIXED,
- 'string' => IAppConfig::VALUE_STRING,
- 'integer' => IAppConfig::VALUE_INT,
- 'float' => IAppConfig::VALUE_FLOAT,
- 'boolean' => IAppConfig::VALUE_BOOL,
- 'array' => IAppConfig::VALUE_ARRAY,
- default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
- };
- }
-
- public function convertTypeToString(int $type): string {
- $type &= ~self::VALUE_SENSITIVE;
- return match ($type) {
- IAppConfig::VALUE_MIXED => 'mixed',
- IAppConfig::VALUE_STRING => 'string',
- IAppConfig::VALUE_INT => 'integer',
- IAppConfig::VALUE_FLOAT => 'float',
- IAppConfig::VALUE_BOOL => 'boolean',
- IAppConfig::VALUE_ARRAY => 'array',
- default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
- };
- }
-
- public function deleteKey(string $app, string $key): void {
- $this->assertParams($app, $key);
- $qb = $this->connection->getQueryBuilder();
- $qb->delete('appconfig')
- ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
- ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
- $qb->executeStatement();
- unset($this->lazyCache[$app][$key]);
- unset($this->fastCache[$app][$key]);
- }
-
- public function deleteApp(string $app): void {
- $this->assertParams($app);
- $qb = $this->connection->getQueryBuilder();
- $qb->delete('appconfig')
- ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
- $qb->executeStatement();
- $this->clearCache();
- }
-
- public function clearCache(bool $reload = false): void {
- $this->lazyLoaded = $this->fastLoaded = false;
- $this->lazyCache = $this->fastCache = $this->valueTypes = [];
- if (!$reload) {
- return;
- }
- $this->loadConfigAll();
- }
-
- public function statusCache(): array {
- return [
- 'fastLoaded' => $this->fastLoaded,
- 'fastCache' => $this->fastCache,
- 'lazyLoaded' => $this->lazyLoaded,
- 'lazyCache' => $this->lazyCache,
- ];
- }
-
- private function isTyped(int $needle, int $type): bool {
- return (($needle & $type) !== 0);
- }
-
- private function assertParams(string $app = '', string $configKey = '', bool $allowEmptyApp = false, int $valueType = -1): void {
- if (!$allowEmptyApp && $app === '') {
- throw new InvalidArgumentException('app cannot be an empty string');
- }
- if (strlen($app) > self::APP_MAX_LENGTH) {
- throw new InvalidArgumentException(
- 'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
- );
- }
- if (strlen($configKey) > self::KEY_MAX_LENGTH) {
- throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
- }
- if ($valueType > -1) {
- $valueType &= ~self::VALUE_SENSITIVE;
- if (!in_array($valueType, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
- throw new InvalidArgumentException('Unknown value type');
- }
- }
- }
- private function loadConfigAll(?string $app = null): void {
- $this->loadConfig($app, null);
- }
-
- private function loadConfig(?string $app = null, ?bool $lazy = false): void {
- if ($this->isLoaded($lazy)) {
- return;
- }
-
- if (($lazy ?? true) !== false && $app !== null) {
- $exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "' . $app . '"');
- $this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
- }
- $qb = $this->connection->getQueryBuilder();
- $qb->from('appconfig');
-
- if (!$this->migrationCompleted) {
- $qb->select('appid', 'configkey', 'configvalue');
- } else {
-
- $qb->select('appid', 'configkey', 'configvalue', 'type');
- if ($lazy !== null) {
- $qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
- } else {
- $qb->addSelect('lazy');
- }
- }
- try {
- $result = $qb->executeQuery();
- } catch (DBException $e) {
-
- if ($e->getReason() !== DBException::REASON_INVALID_FIELD_NAME) {
- throw $e;
- }
- $this->migrationCompleted = false;
- $this->loadConfig($app, $lazy);
- return;
- }
- $rows = $result->fetchAll();
- foreach ($rows as $row) {
-
- if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
- $this->lazyCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
- } else {
- $this->fastCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
- }
- $this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
- }
- $result->closeCursor();
- $this->setAsLoaded($lazy);
- }
-
- private function isLoaded(?bool $lazy): bool {
- if ($lazy === null) {
- return $this->lazyLoaded && $this->fastLoaded;
- }
- return $lazy ? $this->lazyLoaded : $this->fastLoaded;
- }
-
- private function setAsLoaded(?bool $lazy): void {
- if ($lazy === null) {
- $this->fastLoaded = true;
- $this->lazyLoaded = true;
- return;
- }
- if ($lazy) {
- $this->lazyLoaded = true;
- } else {
- $this->fastLoaded = true;
- }
- }
-
- public function getValue($app, $key, $default = null) {
- $this->loadConfig($app);
- return $this->fastCache[$app][$key] ?? $default;
- }
-
- public function setValue($app, $key, $value) {
-
- return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
- }
-
- public function getValues($app, $key) {
- if (($app !== false) === ($key !== false)) {
- return false;
- }
- $key = ($key === false) ? '' : $key;
- if (!$app) {
- return $this->searchValues($key, false, self::VALUE_MIXED);
- } else {
- return $this->getAllValues($app, $key);
- }
- }
-
- public function getFilteredValues($app) {
- return $this->getAllValues($app, filtered: true);
- }
-
- private function formatAppValues(string $app, array $values, ?bool $lazy = null): array {
- foreach ($values as $key => $value) {
- try {
- $type = $this->getValueType($app, $key, $lazy);
- } catch (AppConfigUnknownKeyException $e) {
- continue;
- }
- $values[$key] = $this->convertTypedValue($value, $type);
- }
- return $values;
- }
-
- private function convertTypedValue(string $value, int $type): string|int|float|bool|array {
- switch ($type) {
- case self::VALUE_INT:
- return (int)$value;
- case self::VALUE_FLOAT:
- return (float)$value;
- case self::VALUE_BOOL:
- return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
- case self::VALUE_ARRAY:
- try {
- return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
- } catch (JsonException $e) {
-
- }
- break;
- }
- return $value;
- }
-
- private function getSensitiveKeys(string $app): array {
- $sensitiveValues = [
- 'circles' => [
- '/^key_pairs$/',
- '/^local_gskey$/',
- ],
- 'call_summary_bot' => [
- '/^secret_(.*)$/',
- ],
- 'external' => [
- '/^sites$/',
- '/^jwt_token_privkey_(.*)$/',
- ],
- 'globalsiteselector' => [
- '/^gss\.jwt\.key$/',
- ],
- 'integration_discourse' => [
- '/^private_key$/',
- '/^public_key$/',
- ],
- 'integration_dropbox' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_github' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_gitlab' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^oauth_instance_url$/',
- ],
- 'integration_google' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_jira' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^forced_instance_url$/',
- ],
- 'integration_onedrive' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_openproject' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^oauth_instance_url$/',
- ],
- 'integration_reddit' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_suitecrm' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^oauth_instance_url$/',
- ],
- 'integration_twitter' => [
- '/^consumer_key$/',
- '/^consumer_secret$/',
- '/^followed_user$/',
- ],
- 'integration_zammad' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^oauth_instance_url$/',
- ],
- 'notify_push' => [
- '/^cookie$/',
- ],
- 'onlyoffice' => [
- '/^jwt_secret$/',
- ],
- 'passwords' => [
- '/^SSEv1ServerKey$/',
- ],
- 'serverinfo' => [
- '/^token$/',
- ],
- 'spreed' => [
- '/^bridge_bot_password$/',
- '/^hosted-signaling-server-(.*)$/',
- '/^recording_servers$/',
- '/^signaling_servers$/',
- '/^signaling_ticket_secret$/',
- '/^signaling_token_privkey_(.*)$/',
- '/^signaling_token_pubkey_(.*)$/',
- '/^sip_bridge_dialin_info$/',
- '/^sip_bridge_shared_secret$/',
- '/^stun_servers$/',
- '/^turn_servers$/',
- '/^turn_server_secret$/',
- ],
- 'support' => [
- '/^last_response$/',
- '/^potential_subscription_key$/',
- '/^subscription_key$/',
- ],
- 'theming' => [
- '/^imprintUrl$/',
- '/^privacyUrl$/',
- '/^slogan$/',
- '/^url$/',
- ],
- 'user_ldap' => [
- '/^(s..)?ldap_agent_password$/',
- ],
- 'twofactor_gateway' => [
- '/^.*token$/',
- ],
- 'user_saml' => [
- '/^idp-x509cert$/',
- ],
- 'whiteboard' => [
- '/^jwt_secret_key$/',
- ],
- ];
- return $sensitiveValues[$app] ?? [];
- }
-
- public function clearCachedConfig(): void {
- $this->clearCache();
- }
-
- private function matchAndApplyLexiconDefinition(
- string $app,
- string $key,
- bool &$lazy,
- int &$type,
- string &$default = '',
- ): bool {
- if (in_array($key,
- [
- 'enabled',
- 'installed_version',
- 'types',
- ])) {
- return true;
- }
- $configDetails = $this->getConfigDetailsFromLexicon($app);
- if (!array_key_exists($key, $configDetails['entries'])) {
- return $this->applyLexiconStrictness(
- $configDetails['strictness'],
- 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon'
- );
- }
-
- $configValue = $configDetails['entries'][$key];
- $type &= ~self::VALUE_SENSITIVE;
- $appConfigValueType = $configValue->getValueType()->toAppConfigFlag();
- if ($type === self::VALUE_MIXED) {
- $type = $appConfigValueType;
- } elseif ($appConfigValueType !== $type) {
- throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
- }
- $lazy = $configValue->isLazy();
- $default = $configValue->getDefault() ?? $default;
- if ($configValue->isFlagged(self::FLAG_SENSITIVE)) {
- $type |= self::VALUE_SENSITIVE;
- }
- if ($configValue->isDeprecated()) {
- $this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
- }
- return true;
- }
-
- private function applyLexiconStrictness(
- ?ConfigLexiconStrictness $strictness,
- string $line = '',
- ): bool {
- if ($strictness === null) {
- return true;
- }
- switch ($strictness) {
- case ConfigLexiconStrictness::IGNORE:
- return true;
- case ConfigLexiconStrictness::NOTICE:
- $this->logger->notice($line);
- return true;
- case ConfigLexiconStrictness::WARNING:
- $this->logger->warning($line);
- return false;
- }
- throw new AppConfigUnknownKeyException($line);
- }
-
- private function getConfigDetailsFromLexicon(string $appId): array {
- if (!array_key_exists($appId, $this->configLexiconDetails)) {
- $entries = [];
- $bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
- $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
- foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
- $entries[$configEntry->getKey()] = $configEntry;
- }
- $this->configLexiconDetails[$appId] = [
- 'entries' => $entries,
- 'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE
- ];
- }
- return $this->configLexiconDetails[$appId];
- }
- }
|