aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoas Schilling <coding@schilljs.com>2024-09-13 09:16:35 +0200
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>2024-09-13 11:40:13 +0000
commit66a6b442b5543a571ba405d328651677774b699a (patch)
treed4961e99c22994665735208c3e34e6a147525f25
parent91533fb6f7ac0d61f8016aafe5e06bed75fb7498 (diff)
downloadnextcloud-server-66a6b442b5543a571ba405d328651677774b699a.tar.gz
nextcloud-server-66a6b442b5543a571ba405d328651677774b699a.zip
fix(config): Throw PreconditionException always when it didn't matchbackport/47933/stable30
Previously even when the precondition did not match, the call "passed" when the after value was the expected one. This however can lead to race conditions, duplicate code excutions and other things. Signed-off-by: Joas Schilling <coding@schilljs.com>
-rw-r--r--lib/private/AllConfig.php6
-rw-r--r--tests/lib/AllConfigTest.php36
2 files changed, 39 insertions, 3 deletions
diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php
index 58eee772fbf..d48de298fad 100644
--- a/lib/private/AllConfig.php
+++ b/lib/private/AllConfig.php
@@ -242,10 +242,10 @@ class AllConfig implements IConfig {
$prevValue = $this->getUserValue($userId, $appName, $key, null);
if ($prevValue !== null) {
- if ($prevValue === (string)$value) {
- return;
- } elseif ($preCondition !== null && $prevValue !== (string)$preCondition) {
+ if ($preCondition !== null && $prevValue !== (string)$preCondition) {
throw new PreConditionNotMetException();
+ } elseif ($prevValue === (string)$value) {
+ return;
} else {
$qb = $this->connection->getQueryBuilder();
$qb->update('preferences')
diff --git a/tests/lib/AllConfigTest.php b/tests/lib/AllConfigTest.php
index c703c47c94e..48df3416df1 100644
--- a/tests/lib/AllConfigTest.php
+++ b/tests/lib/AllConfigTest.php
@@ -182,6 +182,42 @@ class AllConfigTest extends \Test\TestCase {
$config->deleteUserValue('userPreCond1', 'appPreCond', 'keyPreCond');
}
+ public function testSetUserValueWithPreConditionFailureWhenResultStillMatches(): void {
+ $this->expectException(\OCP\PreConditionNotMetException::class);
+
+ $config = $this->getConfig();
+
+ $selectAllSQL = 'SELECT `userid`, `appid`, `configkey`, `configvalue` FROM `*PREFIX*preferences` WHERE `userid` = ?';
+
+ $config->setUserValue('userPreCond1', 'appPreCond', 'keyPreCond', 'valuePreCond');
+
+ $result = $this->connection->executeQuery($selectAllSQL, ['userPreCond1'])->fetchAll();
+
+ $this->assertCount(1, $result);
+ $this->assertEquals([
+ 'userid' => 'userPreCond1',
+ 'appid' => 'appPreCond',
+ 'configkey' => 'keyPreCond',
+ 'configvalue' => 'valuePreCond'
+ ], $result[0]);
+
+ // test if the method throws with invalid precondition when the value is the same
+ $config->setUserValue('userPreCond1', 'appPreCond', 'keyPreCond', 'valuePreCond', 'valuePreCond3');
+
+ $result = $this->connection->executeQuery($selectAllSQL, ['userPreCond1'])->fetchAll();
+
+ $this->assertCount(1, $result);
+ $this->assertEquals([
+ 'userid' => 'userPreCond1',
+ 'appid' => 'appPreCond',
+ 'configkey' => 'keyPreCond',
+ 'configvalue' => 'valuePreCond'
+ ], $result[0]);
+
+ // cleanup
+ $config->deleteUserValue('userPreCond1', 'appPreCond', 'keyPreCond');
+ }
+
public function testSetUserValueUnchanged() {
// TODO - FIXME until the dependency injection is handled properly (in AllConfig)
$this->markTestSkipped('Skipped because this is just testable if database connection can be injected');