aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoas Schilling <coding@schilljs.com>2024-09-13 09:16:35 +0200
committerJoas Schilling <coding@schilljs.com>2024-09-13 09:20:08 +0200
commitdcd97e12346125faf6b3b8e9dd8a93e695745f11 (patch)
tree531029e96352dbc4a0b53af2b79d4558628a4723
parentc9e4598360335d7eab0f4a5153dbf16a1f161351 (diff)
downloadnextcloud-server-dcd97e12346125faf6b3b8e9dd8a93e695745f11.tar.gz
nextcloud-server-dcd97e12346125faf6b3b8e9dd8a93e695745f11.zip
fix(config): Throw PreconditionException always when it didn't match
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 263b5283133..f08e5125a47 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 92793a75ae6..73a62c6b8f2 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');