aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobin Appelman <robin@icewind.nl>2022-09-13 16:52:44 +0200
committerRobin Appelman <robin@icewind.nl>2024-06-21 11:01:46 +0200
commitf6d18f8303882a43510d67c787410aec7c8e28e8 (patch)
treeaefae7e31c4d41882f903ef720c5516198c055fe
parentf56bca3385243f5f996b47b811a218c00816a215 (diff)
downloadnextcloud-server-merge-token-updates.tar.gz
nextcloud-server-merge-token-updates.zip
perf: merge last_activity and last_check updatesmerge-token-updates
the debounce for updating last_activity is changed so it always updates if another field of the token has been updated, this ensures that last_check if updated even if last_activity is still in the debounce period. Signed-off-by: Robin Appelman <robin@icewind.nl>
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenMapper.php26
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenProvider.php6
-rw-r--r--lib/private/User/Session.php1
-rw-r--r--lib/public/AppFramework/Db/QBMapper.php42
-rw-r--r--tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php49
-rw-r--r--tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php18
6 files changed, 113 insertions, 29 deletions
diff --git a/lib/private/Authentication/Token/PublicKeyTokenMapper.php b/lib/private/Authentication/Token/PublicKeyTokenMapper.php
index 0db5c4f53e7..5b97e557219 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenMapper.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenMapper.php
@@ -182,7 +182,7 @@ class PublicKeyTokenMapper extends QBMapper {
}
/**
- * Update the last activity timestamp
+ * Update the last activity timestamp and save all saved fields
*
* In highly concurrent setups it can happen that two parallel processes
* trigger the update at (nearly) the same time. In that special case it's
@@ -192,7 +192,7 @@ class PublicKeyTokenMapper extends QBMapper {
*
* Example:
* - process 1 (P1) reads the token at timestamp 1500
- * - process 1 (P2) reads the token at timestamp 1501
+ * - process 2 (P2) reads the token at timestamp 1501
* - activity update interval is 100
*
* This means
@@ -206,17 +206,21 @@ class PublicKeyTokenMapper extends QBMapper {
* but the comparison on last_activity will still not be truthy and the
* token row is not updated a second time
*
- * @param IToken $token
+ * @param PublicKeyToken $token
* @param int $now
*/
- public function updateActivity(IToken $token, int $now): void {
- $qb = $this->db->getQueryBuilder();
- $update = $qb->update($this->getTableName())
- ->set('last_activity', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
- ->where(
- $qb->expr()->eq('id', $qb->createNamedParameter($token->getId(), IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
- $qb->expr()->lt('last_activity', $qb->createNamedParameter($now - 15, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)
- );
+ public function updateActivity(PublicKeyToken $token, int $now): void {
+ $token->setLastActivity($now);
+ $update = $this->createUpdateQuery($token);
+
+ $updatedFields = $token->getUpdatedFields();
+ unset($updatedFields['lastActivity']);
+
+ // if no other fields are updated, we add the extra filter to prevent duplicate updates
+ if (count($updatedFields) === 0) {
+ $update->andWhere($update->expr()->lt('last_activity', $update->createNamedParameter($now - 15, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
+ }
+
$update->executeStatement();
}
diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
index 767ece1e551..809c39474e2 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
@@ -299,10 +299,12 @@ class PublicKeyTokenProvider implements IProvider {
$activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60);
$activityInterval = min(max($activityInterval, 0), 300);
+ $updatedFields = $token->getUpdatedFields();
+ unset($updatedFields['lastActivity']);
+
/** @var PublicKeyToken $token */
$now = $this->time->getTime();
- if ($token->getLastActivity() < ($now - $activityInterval)) {
- $token->setLastActivity($now);
+ if ($token->getLastActivity() < ($now - $activityInterval) || count($updatedFields)) {
$this->mapper->updateActivity($token, $now);
$this->cacheToken($token);
}
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index e5d27172519..8226e3c95f5 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -757,7 +757,6 @@ class Session implements IUserSession, Emitter {
if ($dbToken instanceof PublicKeyToken) {
$dbToken->setLastActivity($now);
}
- $this->tokenProvider->updateToken($dbToken);
return true;
}
diff --git a/lib/public/AppFramework/Db/QBMapper.php b/lib/public/AppFramework/Db/QBMapper.php
index badd2483b58..ad9f772095a 100644
--- a/lib/public/AppFramework/Db/QBMapper.php
+++ b/lib/public/AppFramework/Db/QBMapper.php
@@ -146,22 +146,15 @@ abstract class QBMapper {
}
/**
- * Updates an entry in the db from an entity
+ * Create an update query that saves all updated fields
*
- * @param Entity $entity the entity that should be created
- * @psalm-param T $entity the entity that should be created
- * @return Entity the saved entity with the set id
- * @psalm-return T the saved entity with the set id
- * @throws Exception
- * @throws \InvalidArgumentException if entity has no id
- * @since 14.0.0
+ * @param Entity $entity the entity that should be updated
+ * @psalm-param T $entity the entity that should be updated
+ * @return IQueryBuilder
+ * @since 25.0.0
*/
- public function update(Entity $entity): Entity {
- // if entity wasn't changed it makes no sense to run a db query
+ protected function createUpdateQuery(Entity $entity): IQueryBuilder {
$properties = $entity->getUpdatedFields();
- if (\count($properties) === 0) {
- return $entity;
- }
// entity needs an id
$id = $entity->getId();
@@ -193,6 +186,29 @@ abstract class QBMapper {
$qb->where(
$qb->expr()->eq('id', $qb->createNamedParameter($id, $idType))
);
+
+ return $qb;
+ }
+
+ /**
+ * Updates an entry in the db from an entity
+ *
+ * @param Entity $entity the entity that should be created
+ * @psalm-param T $entity the entity that should be created
+ * @return Entity the saved entity with the set id
+ * @psalm-return T the saved entity with the set id
+ * @throws Exception
+ * @throws \InvalidArgumentException if entity has no id
+ * @since 14.0.0
+ */
+ public function update(Entity $entity): Entity {
+ // if entity wasn't changed it makes no sense to run a db query
+ $properties = $entity->getUpdatedFields();
+ if (\count($properties) === 0) {
+ return $entity;
+ }
+
+ $qb = $this->createUpdateQuery($entity);
$qb->executeStatement();
return $entity;
diff --git a/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php
index 4b87f7101b5..8ff7ec70bf7 100644
--- a/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php
+++ b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php
@@ -261,4 +261,53 @@ class PublicKeyTokenMapperTest extends TestCase {
$this->assertFalse($this->mapper->hasExpiredTokens('user1'));
$this->assertTrue($this->mapper->hasExpiredTokens('user3'));
}
+
+ public function testUpdateTokenActivity() {
+ $token = '6d9a290d239d09f2cc33a03cc54cccd46f7dc71630dcc27d39214824bd3e093f1feb4e2b55eb159d204caa15dee9556c202a5aa0b9d67806c3f4ec2cde11af67';
+ $dbToken = $this->mapper->getToken($token);
+
+ $this->assertEquals($dbToken->getLastActivity(), $this->time - 120);
+ $this->assertEquals($this->time - 60 * 10, $dbToken->getLastCheck());
+
+ $this->mapper->updateActivity($dbToken, $this->time);
+
+ $updatedDbToken = $this->mapper->getToken($token);
+
+ $this->assertEquals($this->time, $updatedDbToken->getLastActivity());
+ $this->assertEquals($this->time - 60 * 10, $dbToken->getLastCheck());
+ $this->assertEquals($this->time, $dbToken->getLastActivity());
+ }
+
+ public function testUpdateTokenActivityDebounce() {
+ $token = '6d9a290d239d09f2cc33a03cc54cccd46f7dc71630dcc27d39214824bd3e093f1feb4e2b55eb159d204caa15dee9556c202a5aa0b9d67806c3f4ec2cde11af67';
+ $dbToken = $this->mapper->getToken($token);
+
+ $this->assertEquals($dbToken->getLastActivity(), $this->time - 120);
+ $this->assertEquals($this->time - 60 * 10, $dbToken->getLastCheck());
+
+ $this->mapper->updateActivity($dbToken, $this->time - 110);
+
+ $updatedDbToken = $this->mapper->getToken($token);
+
+ $this->assertEquals($this->time - 120, $updatedDbToken->getLastActivity());
+ $this->assertEquals($this->time - 60 * 10, $dbToken->getLastCheck());
+ $this->assertEquals($this->time - 110, $dbToken->getLastActivity());
+ }
+
+ public function testUpdateTokenActivityDebounceUpdate() {
+ $token = '6d9a290d239d09f2cc33a03cc54cccd46f7dc71630dcc27d39214824bd3e093f1feb4e2b55eb159d204caa15dee9556c202a5aa0b9d67806c3f4ec2cde11af67';
+ $dbToken = $this->mapper->getToken($token);
+
+ $this->assertEquals($this->time - 120, $dbToken->getLastActivity());
+ $this->assertEquals($this->time - 60 * 10, $dbToken->getLastCheck());
+
+ $dbToken->setLastCheck($this->time - 100);
+ $this->mapper->updateActivity($dbToken, $this->time - 110);
+
+ $updatedDbToken = $this->mapper->getToken($token);
+
+ $this->assertEquals($this->time - 110, $updatedDbToken->getLastActivity());
+ $this->assertEquals($this->time - 100, $dbToken->getLastCheck());
+ $this->assertEquals($this->time - 110, $dbToken->getLastActivity());
+ }
}
diff --git a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php
index 3c81eade700..5f0f65e2cc9 100644
--- a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php
+++ b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php
@@ -191,8 +191,6 @@ class PublicKeyTokenProviderTest extends TestCase {
]);
$this->tokenProvider->updateTokenActivity($tk);
-
- $this->assertEquals($this->time, $tk->getLastActivity());
}
public function testUpdateTokenDebounce() {
@@ -210,6 +208,22 @@ class PublicKeyTokenProviderTest extends TestCase {
$this->tokenProvider->updateTokenActivity($tk);
}
+ public function testUpdateTokenDebounceWithUpdatedFields() {
+ $tk = new PublicKeyToken();
+ $this->config->method('getSystemValueInt')
+ ->willReturnCallback(function ($value, $default) {
+ return $default;
+ });
+ $tk->setLastActivity($this->time - 30);
+ $tk->setLastCheck($this->time - 30);
+
+ $this->mapper->expects($this->once())
+ ->method('updateActivity')
+ ->with($tk, $this->time);
+
+ $this->tokenProvider->updateTokenActivity($tk);
+ }
+
public function testGetTokenByUser() {
$this->mapper->expects($this->once())
->method('getTokenByUser')