feat: Use a class to materialize user config entries

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
pull/61413/head
Côme Chilliet 2 months ago
parent 00d58abd0b
commit 7f4e722c6b
No known key found for this signature in database
GPG Key ID: A3E2F658B28C760A
  1. 1
      lib/composer/composer/autoload_classmap.php
  2. 1
      lib/composer/composer/autoload_static.php
  3. 179
      lib/private/Config/UserConfig.php
  4. 106
      lib/private/Config/UserConfigEntry.php
  5. 24
      tests/lib/Config/UserConfigTest.php

@ -1366,6 +1366,7 @@ return array(
'OC\\Config\\ConfigManager' => $baseDir . '/lib/private/Config/ConfigManager.php',
'OC\\Config\\PresetManager' => $baseDir . '/lib/private/Config/PresetManager.php',
'OC\\Config\\UserConfig' => $baseDir . '/lib/private/Config/UserConfig.php',
'OC\\Config\\UserConfigEntry' => $baseDir . '/lib/private/Config/UserConfigEntry.php',
'OC\\Console\\Application' => $baseDir . '/lib/private/Console/Application.php',
'OC\\Console\\TimestampFormatter' => $baseDir . '/lib/private/Console/TimestampFormatter.php',
'OC\\ContactsManager' => $baseDir . '/lib/private/ContactsManager.php',

@ -1407,6 +1407,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Config\\ConfigManager' => __DIR__ . '/../../..' . '/lib/private/Config/ConfigManager.php',
'OC\\Config\\PresetManager' => __DIR__ . '/../../..' . '/lib/private/Config/PresetManager.php',
'OC\\Config\\UserConfig' => __DIR__ . '/../../..' . '/lib/private/Config/UserConfig.php',
'OC\\Config\\UserConfigEntry' => __DIR__ . '/../../..' . '/lib/private/Config/UserConfigEntry.php',
'OC\\Console\\Application' => __DIR__ . '/../../..' . '/lib/private/Console/Application.php',
'OC\\Console\\TimestampFormatter' => __DIR__ . '/../../..' . '/lib/private/Console/TimestampFormatter.php',
'OC\\ContactsManager' => __DIR__ . '/../../..' . '/lib/private/ContactsManager.php',

@ -1,6 +1,7 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@ -54,12 +55,10 @@ class UserConfig implements IUserConfig {
private const APP_MAX_LENGTH = 32;
private const KEY_MAX_LENGTH = 64;
private const INDEX_MAX_LENGTH = 64;
private const ENCRYPTION_PREFIX = '$UserConfigEncryption$';
private const ENCRYPTION_PREFIX_LENGTH = 22; // strlen(self::ENCRYPTION_PREFIX)
/** @var CappedMemoryCache<array<string, array<string, array{type: ValueType, flags: int, value: string}>>> cache for normal config keys */
/** @var CappedMemoryCache<array<string, array<string, UserConfigEntry>>> cache for normal config keys */
private CappedMemoryCache $fastCache;
/** @var CappedMemoryCache<array<string, array<string, array{type: ValueType, flags: int, value: string}>>> cache for lazy config keys */
/** @var CappedMemoryCache<array<string, array<string, UserConfigEntry>>> cache for lazy config keys */
private CappedMemoryCache $lazyCache;
/** @var array<string, array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
private array $configLexiconDetails = [];
@ -203,9 +202,9 @@ class UserConfig implements IUserConfig {
$this->matchAndApplyLexiconDefinition($userId, $app, $key);
if (isset($this->fastCache[$userId][$app][$key])) {
return $this->isFlagged(self::FLAG_SENSITIVE, $this->fastCache[$userId][$app][$key]['flags']);
return $this->fastCache[$userId][$app][$key]->isSensitive();
} elseif (isset($this->lazyCache[$userId][$app][$key])) {
return $this->isFlagged(self::FLAG_SENSITIVE, $this->lazyCache[$userId][$app][$key]['flags']);
return $this->lazyCache[$userId][$app][$key]->isSensitive();
} else {
throw new UnknownKeyException('Unknown config key ' . $app . '/' . $key);
}
@ -230,9 +229,9 @@ class UserConfig implements IUserConfig {
$this->matchAndApplyLexiconDefinition($userId, $app, $key);
if (isset($this->fastCache[$userId][$app][$key])) {
return $this->isFlagged(self::FLAG_INDEXED, $this->fastCache[$userId][$app][$key]['flags']);
return $this->fastCache[$userId][$app][$key]->isIndexed();
} elseif (isset($this->lazyCache[$userId][$app][$key])) {
return $this->isFlagged(self::FLAG_INDEXED, $this->lazyCache[$userId][$app][$key]['flags']);
return $this->lazyCache[$userId][$app][$key]->isIndexed();
} else {
throw new UnknownKeyException('Unknown config key ' . $app . '/' . $key);
}
@ -352,12 +351,12 @@ class UserConfig implements IUserConfig {
$values = [];
foreach (array_keys($cache) as $app) {
if (isset($cache[$app][$key])) {
$valueDetail = $cache[$app][$key];
$entry = $cache[$app][$key];
try {
$this->decryptSensitiveValue($userId, $app, $key, $valueDetail);
$value = $this->convertTypedValue($valueDetail['value'], $typedAs ?? $this->getValueType($userId, $app, $key, $lazy));
$value = $this->getDecryptedSensitiveValue($userId, $app, $key, $entry);
$value = $this->convertTypedValue($value, $typedAs ?? $entry->getType());
} catch (IncorrectTypeException|UnknownKeyException) {
$value = $valueDetail['value'];
$value = $entry->getRawValue();
}
$values[$app] = $value;
}
@ -803,9 +802,9 @@ class UserConfig implements IUserConfig {
* This way, lazyCache will be empty until the load for lazy config value is requested.
*/
if (isset($this->lazyCache[$userId][$app][$key])) {
$valueDetail = $this->lazyCache[$userId][$app][$key];
$entry = $this->lazyCache[$userId][$app][$key];
} elseif (isset($this->fastCache[$userId][$app][$key])) {
$valueDetail = $this->fastCache[$userId][$app][$key];
$entry = $this->fastCache[$userId][$app][$key];
} else {
return $default;
}
@ -815,18 +814,15 @@ class UserConfig implements IUserConfig {
* If type of stored value is set as mixed, we don't filter.
* If type of stored value is defined, we compare with the one requested.
*/
$knownType = $valueDetail['type'] ?? null;
$knownType = $entry->getType();
if ($type !== ValueType::MIXED
&& $knownType !== null
&& $knownType !== ValueType::MIXED
&& $type !== $knownType) {
$this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
throw new TypeConflictException('conflict with value type from database');
}
$this->decryptSensitiveValue($userId, $app, $key, $valueDetail);
$value = $valueDetail['value'];
$value = $this->getDecryptedSensitiveValue($userId, $app, $key, $entry);
// in case the key was modified while running matchAndApplyLexiconDefinition() we are
// interested to check options in case a modification of the value is needed
@ -857,9 +853,9 @@ class UserConfig implements IUserConfig {
$this->matchAndApplyLexiconDefinition($userId, $app, $key);
if (isset($this->fastCache[$userId][$app][$key])) {
return $this->fastCache[$userId][$app][$key]['type'];
return $this->fastCache[$userId][$app][$key]->getType();
} elseif (isset($this->lazyCache[$userId][$app][$key])) {
return $this->lazyCache[$userId][$app][$key]['type'];
return $this->lazyCache[$userId][$app][$key]->getType();
} else {
throw new UnknownKeyException('Unknown config key ' . $app . '/' . $key);
}
@ -885,9 +881,9 @@ class UserConfig implements IUserConfig {
$this->matchAndApplyLexiconDefinition($userId, $app, $key);
if (isset($this->fastCache[$userId][$app][$key])) {
return $this->fastCache[$userId][$app][$key]['flags'];
return $this->fastCache[$userId][$app][$key]->getFlags();
} elseif (isset($this->lazyCache[$userId][$app][$key])) {
return $this->lazyCache[$userId][$app][$key]['flags'];
return $this->lazyCache[$userId][$app][$key]->getFlags();
} else {
throw new UnknownKeyException('Unknown config key ' . $app . '/' . $key);
}
@ -1160,16 +1156,23 @@ class UserConfig implements IUserConfig {
$inserted = $refreshCache = false;
$origValue = $value;
$sensitive = $this->isFlagged(self::FLAG_SENSITIVE, $flags);
if ($sensitive || ($this->hasKey($userId, $app, $key, $lazy) && $this->isSensitive($userId, $app, $key, $lazy))) {
$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
$flags |= self::FLAG_SENSITIVE;
$newEntry = new UserConfigEntry(
type:$type,
flags:$flags,
value:$value,
crypto:$this->crypto,
);
- $sensitive = $newEntry->isSensitive();
if ($newEntry->isSensitive() || ($this->hasKey($userId, $app, $key, $lazy) && $this->isSensitive($userId, $app, $key, $lazy))) {
$newEntry->setSensitive(true);
}
$value = $newEntry->getDecryptedSensitiveValue();
$flags = $newEntry->getFlags();
// if requested, we fill the 'indexed' field with current value
$indexed = '';
if ($type !== ValueType::ARRAY && $this->isFlagged(self::FLAG_INDEXED, $flags)) {
if ($this->isFlagged(self::FLAG_SENSITIVE, $flags)) {
if ($type !== ValueType::ARRAY && $newEntry->isIndexed()) {
if ($newEntry->isSensitive()) {
$this->logger->warning('sensitive value are not to be indexed');
} elseif (strlen($value) > self::USER_MAX_LENGTH) {
$this->logger->warning('value is too lengthy to be indexed');
@ -1225,14 +1228,14 @@ class UserConfig implements IUserConfig {
* We cannot insert a new row, meaning we need to update an already existing one
*/
if (!$inserted) {
$currType = $this->fastCache[$userId][$app][$key]['type'] ?? $this->lazyCache[$userId][$app][$key]['type'] ?? null;
$currType = ($this->fastCache[$userId][$app][$key] ?? $this->lazyCache[$userId][$app][$key] ?? null)?->getType();
if ($currType === null) { // this might happen when switching lazy loading status
$this->loadConfigAll($userId);
if (!isset($this->fastCache[$userId][$app][$key]) && !isset($this->lazyCache[$userId][$app][$key])) {
throw new UnknownKeyException("unknown key $app $key for $userId even though $updateReason");
}
$currType = $this->fastCache[$userId][$app][$key]['type'] ?? $this->lazyCache[$userId][$app][$key]['type'] ?? null;
$currType = ($this->fastCache[$userId][$app][$key] ?? $this->lazyCache[$userId][$app][$key] ?? null)?->getType();
}
/**
@ -1287,15 +1290,16 @@ class UserConfig implements IUserConfig {
}
// update local cache
$valueDetail = [
'value' => $value,
'type' => $type,
'flags' => $flags
];
$entry = new UserConfigEntry(
value: $value,
type: $type,
flags: $flags,
crypto:$this->crypto,
);
if ($lazy) {
$this->lazyCache[$userId][$app][$key] = $valueDetail;
$this->lazyCache[$userId][$app][$key] = $entry;
} else {
$this->fastCache[$userId][$app][$key] = $valueDetail;
$this->fastCache[$userId][$app][$key] = $entry;
}
return true;
@ -1332,9 +1336,9 @@ class UserConfig implements IUserConfig {
$update->executeStatement();
if ($lazy) {
$this->lazyCache[$userId][$app][$key]['type'] = $type;
$this->lazyCache[$userId][$app][$key]->setType($type);
} else {
$this->fastCache[$userId][$app][$key]['type'] = $type;
$this->fastCache[$userId][$app][$key]->setType($type);
}
return true;
@ -1376,28 +1380,18 @@ class UserConfig implements IUserConfig {
throw new UnknownKeyException('unknown config key');
}
$valueDetail = $cache[$userId][$app][$key];
$flags = $this->getValueFlags($userId, $app, $key);
if ($sensitive) {
$flags |= self::FLAG_SENSITIVE;
$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($valueDetail['value']);
} else {
$flags &= ~self::FLAG_SENSITIVE;
$this->decryptSensitiveValue($userId, $app, $key, $valueDetail);
$value = $valueDetail['value'];
}
$entry = $cache[$userId][$app][$key];
$entry->setSensitive($sensitive);
$update = $this->connection->getQueryBuilder();
$update->update('preferences')
->set('flags', $update->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
->set('configvalue', $update->createNamedParameter($value))
->set('flags', $update->createNamedParameter($entry->getFlags(), IQueryBuilder::PARAM_INT))
->set('configvalue', $update->createNamedParameter($entry->getRawValue()))
->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
$update->executeStatement();
$cache[$userId][$app][$key]['flags'] = $flags;
return true;
}
@ -1466,26 +1460,23 @@ class UserConfig implements IUserConfig {
throw new UnknownKeyException('unknown config key');
}
$value = $cache[$userId][$app][$key]['value'];
$flags = $this->getValueFlags($userId, $app, $key);
$entry = $cache[$userId][$app][$key];
$entry->setIndexed($indexed);
if ($indexed) {
$indexed = $value;
$indexed = $entry->getRawValue();
} else {
$flags &= ~self::FLAG_INDEXED;
$indexed = '';
}
$update = $this->connection->getQueryBuilder();
$update->update('preferences')
->set('flags', $update->createNamedParameter($flags, IQueryBuilder::PARAM_INT))
->set('flags', $update->createNamedParameter($entry->getFlags(), IQueryBuilder::PARAM_INT))
->set('indexed', $update->createNamedParameter($indexed))
->where($update->expr()->eq('userid', $update->createNamedParameter($userId)))
->andWhere($update->expr()->eq('appid', $update->createNamedParameter($app)))
->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
$update->executeStatement();
$cache[$userId][$app][$key]['flags'] = $flags;
return true;
}
@ -1634,19 +1625,18 @@ class UserConfig implements IUserConfig {
throw new UnknownKeyException('unknown config key');
}
$valueDetail = $cache[$app][$key];
$sensitive = $this->isSensitive($userId, $app, $key, null);
$this->decryptSensitiveValue($userId, $app, $key, $valueDetail);
$entry = $cache[$app][$key];
$value = $this->getDecryptedSensitiveValue($userId, $app, $key, $entry);
return [
'userId' => $userId,
'app' => $app,
'key' => $key,
'value' => $valueDetail['value'],
'value' => $value,
'type' => $type->value,
'lazy' => $lazy,
'typeString' => $typeString,
'sensitive' => $sensitive
'sensitive' => $entry->isSensitive(),
];
}
@ -1775,16 +1765,6 @@ class UserConfig implements IUserConfig {
];
}
/**
* @param int $needle bitflag to search
* @param int $flags all flags
*
* @return bool TRUE if bitflag $needle is set in $flags
*/
private function isFlagged(int $needle, int $flags): bool {
return (($needle & $flags) !== 0);
}
/**
* Confirm the string set for app and key fit the database description
*
@ -1869,11 +1849,12 @@ class UserConfig implements IUserConfig {
$rows = $result->fetchAllAssociative();
foreach ($rows as $row) {
$valueDetail = [
'value' => $row['configvalue'] ?? '',
'type' => ValueType::from((int)($row['type'] ?? 0)),
'flags' => (int)($row['flags'] ?? 0),
];
$valueDetail = new UserConfigEntry(
value: $row['configvalue'] ?? '',
type: ValueType::from((int)($row['type'] ?? 0)),
flags: (int)($row['flags'] ?? 0),
crypto:$this->crypto,
);
if ($this->migrationCompleted && (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1)) {
$this->lazyCache[$userId][$row['appid']][$row['configkey']] = $valueDetail;
} else {
@ -1912,22 +1893,16 @@ class UserConfig implements IUserConfig {
* @return array<string, string|int|float|bool|array>
*/
private function formatAppValues(string $userId, string $app, array $values, bool $filtered = false): array {
foreach ($values as $key => $valueDetail) {
$value = $valueDetail['value'];
//$key = (string)$key;
try {
$type = $this->getValueType($userId, $app, (string)$key);
} catch (UnknownKeyException) {
continue;
}
foreach ($values as $key => $entry) {
$value = $entry->getRawValue();
$type = $entry->getType();
if ($this->isFlagged(self::FLAG_SENSITIVE, $valueDetail['flags'] ?? 0)) {
if ($entry->isSensitive()) {
if ($filtered) {
$value = IConfig::SENSITIVE_VALUE;
$type = ValueType::STRING;
} else {
$this->decryptSensitiveValue($userId, $app, (string)$key, $valueDetail);
$value = $valueDetail['value'];
$value = $this->getDecryptedSensitiveValue($userId, $app, (string)$key, $entry);
}
}
@ -1964,30 +1939,18 @@ class UserConfig implements IUserConfig {
return $value;
}
/**
* will change referenced $value with the decrypted value in case of encrypted (sensitive value)
*
* @param array{type: ValueType, flags: int, value: string} $valueDetail
*/
private function decryptSensitiveValue(string $userId, string $app, string $key, array &$valueDetail): void {
if (!$this->isFlagged(self::FLAG_SENSITIVE, $valueDetail['flags'] ?? 0)) {
return;
}
if (!str_starts_with($valueDetail['value'], self::ENCRYPTION_PREFIX)) {
return;
}
private function getDecryptedSensitiveValue(string $userId, string $app, string $key, UserConfigEntry $entry): string {
try {
$valueDetail['value'] = $this->crypto->decrypt(substr($valueDetail['value'], self::ENCRYPTION_PREFIX_LENGTH));
return $entry->getDecryptedSensitiveValue();
} catch (\Exception $e) {
$this->logger->warning('could not decrypt sensitive value', [
'userId' => $userId,
'app' => $app,
'key' => $key,
'value' => $valueDetail['value'],
'value' => $entry->getRawValue(),
'exception' => $e
]);
return $entry->getRawValue();
}
}

@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Config;
use OCP\Config\IUserConfig;
use OCP\Config\ValueType;
use OCP\Security\ICrypto;
class UserConfigEntry {
private const ENCRYPTION_PREFIX = '$UserConfigEncryption$';
private const ENCRYPTION_PREFIX_LENGTH = 22; // strlen(self::ENCRYPTION_PREFIX)
/** @var string $decryptedValue Cached decrypted value, if any */
private ?string $decryptedValue = null;
public function __construct(
private ValueType $type,
private int $flags,
private string $value,
private ICrypto $crypto,
) {
}
public function getRawValue(): string {
return $this->value;
}
public function getType(): ValueType {
return $this->type;
}
public function setType(ValueType $type): void {
$this->type = $type;
}
public function getFlags(): int {
return $this->flags;
}
public function isFlagged(int $mask): bool {
return (($mask & $this->flags) === $mask);
}
public function isSensitive(): bool {
return $this->isFlagged(IUserConfig::FLAG_SENSITIVE);
}
public function isIndexed(): bool {
return $this->isFlagged(IUserConfig::FLAG_INDEXED);
}
/**
* will change referenced $value with the decrypted value in case of encrypted (sensitive value)
*
* @param array{type: ValueType, flags: int, value: string} $valueDetail
*/
public function getDecryptedSensitiveValue(): string {
if (!$this->isFlagged(IUserConfig::FLAG_SENSITIVE)) {
return $this->value;
}
if ($this->decryptedValue !== null) {
return $this->decryptedValue;
}
if (!str_starts_with($this->value, self::ENCRYPTION_PREFIX)) {
return $this->value;
}
$this->decryptedValue = $this->crypto->decrypt(substr($this->value, self::ENCRYPTION_PREFIX_LENGTH));
return $this->decryptedValue;
}
public function setSensitive(bool $sensitive): void {
if ($sensitive) {
$this->flags |= IUserConfig::FLAG_SENSITIVE;
$this->decryptedValue = $this->value;
$this->value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($this->value);
} else {
$clearValue = $this->getDecryptedSensitiveValue();
$this->flags &= ~IUserConfig::FLAG_SENSITIVE;
$this->value = $clearValue;
$this->decryptedValue = null;
}
}
public function setIndexed(bool $indexed): void {
if ($indexed) {
$this->flags |= IUserConfig::FLAG_INDEXED;
$this->decryptedValue = $this->value;
$this->value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($this->value);
} else {
$clearValue = $this->getDecryptedSensitiveValue();
$this->flags &= ~IUserConfig::FLAG_INDEXED;
$this->value = $clearValue;
$this->decryptedValue = null;
}
}
}

@ -11,6 +11,7 @@ namespace Test\lib\Config;
use OC\Config\ConfigManager;
use OC\Config\PresetManager;
use OC\Config\UserConfig;
use OC\Config\UserConfigEntry;
use OCP\Config\Exceptions\TypeConflictException;
use OCP\Config\Exceptions\UnknownKeyException;
use OCP\Config\IUserConfig;
@ -230,7 +231,7 @@ class UserConfigTest extends TestCase {
$flags = $row[4] ?? 0;
if ((UserConfig::FLAG_SENSITIVE & $flags) !== 0) {
if (!isset($this->basePreferences[$userId][$appId][$key]['encrypted'])) {
$value = self::invokePrivate(UserConfig::class, 'ENCRYPTION_PREFIX')
$value = self::invokePrivate(UserConfigEntry::class, 'ENCRYPTION_PREFIX')
. $this->crypto->encrypt((string)$value);
$this->basePreferences[$userId][$appId][$key]['encrypted'] = $value;
} else {
@ -1422,10 +1423,13 @@ class UserConfigTest extends TestCase {
$userConfig = $this->generateUserConfig($preload ?? []);
$this->assertEquals($sensitive, $userConfig->isSensitive($userId, $app, $key));
if ($sensitive) {
$this->assertEquals(true, str_starts_with(
$userConfig->statusCache()['fastCache'][$userId][$app][$key]['value']
?? $userConfig->statusCache()['lazyCache'][$userId][$app][$key]['value'],
'$UserConfigEncryption$')
$statusCache = $userConfig->statusCache();
$this->assertEquals(
true,
str_starts_with(
($statusCache['fastCache'][$userId][$app][$key]
?? $statusCache['lazyCache'][$userId][$app][$key])->getRawValue(),
'$UserConfigEncryption$')
);
}
}
@ -1450,10 +1454,11 @@ class UserConfigTest extends TestCase {
$this->assertEquals($value, $userConfig->getValueString('user1', $app, $key));
foreach (['user1', 'user2', 'user3', 'user4'] as $userId) {
$userConfig->getValueString($userId, $app, $key); // cache loading for userId
$statusCache = $userConfig->statusCache();
$this->assertEquals(
!$sensitive, str_starts_with(
$userConfig->statusCache()['fastCache'][$userId][$app][$key]['value']
?? $userConfig->statusCache()['lazyCache'][$userId][$app][$key]['value'],
($statusCache['fastCache'][$userId][$app][$key]
?? $statusCache['lazyCache'][$userId][$app][$key])->getRawValue(),
'$UserConfigEncryption$'
)
);
@ -1464,10 +1469,11 @@ class UserConfigTest extends TestCase {
$this->assertEquals($value, $userConfig->getValueString('user1', $app, $key));
foreach (['user1', 'user2', 'user3', 'user4'] as $userId) {
$this->assertEquals($sensitive, $userConfig->isSensitive($userId, $app, $key));
$statusCache = $userConfig->statusCache();
// should only work if updateGlobalSensitive drop cache
$this->assertEquals($sensitive, str_starts_with(
$userConfig->statusCache()['fastCache'][$userId][$app][$key]['value']
?? $userConfig->statusCache()['lazyCache'][$userId][$app][$key]['value'],
($statusCache['fastCache'][$userId][$app][$key]
?? $statusCache['lazyCache'][$userId][$app][$key])->getRawValue(),
'$UserConfigEncryption$')
);
}

Loading…
Cancel
Save