Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions lib/private/Authentication/Token/PublicKeyTokenMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ public function invalidateOld(int $olderThan, int $type = IToken::TEMPORARY_TOKE
$delete->executeStatement();
}

/**
* Delete tokens whose explicit expiration date lies before the given time.
* Remote wipe tokens are kept, they have their own retention.
*
* @return int number of deleted tokens
*/
public function invalidateExpired(int $expiredBefore): int {
$qb = $this->db->getQueryBuilder();
return $qb->delete($this->tableName)
->where($qb->expr()->isNotNull('expires'))
->andWhere($qb->expr()->gt('expires', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->lt('expires', $qb->createNamedParameter($expiredBefore, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->neq('type', $qb->createNamedParameter(IToken::WIPE_TOKEN, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
->executeStatement();
}

public function invalidateLastUsedBefore(string $uid, int $before): int {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->tableName)
Expand Down
6 changes: 6 additions & 0 deletions lib/private/Authentication/Token/PublicKeyTokenProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ public function invalidateOldTokens() {
$authTokenThreshold = $this->time->getTime() - $this->config->getSystemValueInt('token_auth_token_retention', 60 * 60 * 24 * 365);
$this->logger->debug('Invalidating auth tokens older than ' . date('c', $authTokenThreshold), ['app' => 'cron']);
$this->mapper->invalidateOld($authTokenThreshold, OCPIToken::PERMANENT_TOKEN);

// Tokens past their explicit expiration date can't be used anymore. They are kept for a grace period,
// so a client still sending one is rejected quietly instead of counting as a failed login.
$expiredThreshold = $this->time->getTime() - $this->config->getSystemValueInt('token_auth_expired_token_retention', 60 * 60 * 24 * 7);
$this->logger->debug('Deleting auth tokens that expired before ' . date('c', $expiredThreshold), ['app' => 'cron']);
$this->mapper->invalidateExpired($expiredThreshold);
}

#[\Override]
Expand Down
31 changes: 31 additions & 0 deletions tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,37 @@ public function testInvalidateOld(): void {
$this->assertSame(4, $this->getNumberOfTokens());
}

private function insertTokenWithExpiry(string $token, int $type, int $expires): void {
$qb = $this->dbConnection->getQueryBuilder();
$qb->insert('authtoken')->values([
'uid' => $qb->createNamedParameter('user4'),
'login_name' => $qb->createNamedParameter('User4'),
'password' => $qb->createNamedParameter(''),
'name' => $qb->createNamedParameter('App session'),
'token' => $qb->createNamedParameter($token),
'type' => $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT),
'last_activity' => $qb->createNamedParameter($this->time - 60 * 60 * 24 * 30, IQueryBuilder::PARAM_INT),
'last_check' => $this->time - 60 * 60 * 24 * 30,
'public_key' => $qb->createNamedParameter('public key'),
'private_key' => $qb->createNamedParameter('private key'),
'version' => $qb->createNamedParameter(2),
'expires' => $qb->createNamedParameter($expires, IQueryBuilder::PARAM_INT),
])->executeStatement();
}

public function testInvalidateExpired(): void {
$this->insertTokenWithExpiry('expired-long-ago', IToken::PERMANENT_TOKEN, $this->time - 60 * 60 * 24 * 10);
$this->insertTokenWithExpiry('expired-recently', IToken::PERMANENT_TOKEN, $this->time - 60 * 60);
$this->insertTokenWithExpiry('still-valid', IToken::PERMANENT_TOKEN, $this->time + 60 * 60);
$this->insertTokenWithExpiry('wiped', IToken::WIPE_TOKEN, $this->time - 60 * 60 * 24 * 10);

$deleted = $this->mapper->invalidateExpired($this->time - 60 * 60 * 24 * 7);

// only the token that expired before the threshold goes; tokens without an expiry are untouched
$this->assertSame(1, $deleted);
$this->assertSame(8, $this->getNumberOfTokens());
}

public function testInvalidateLastUsedBefore(): void {
$before = $this->time - 60 * 2; // Two minutes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,13 +329,14 @@ public function testInvalidateOldTokens(): void {
$defaultSessionLifetime = 60 * 60 * 24;
$defaultRememberMeLifetime = 60 * 60 * 24 * 15;
$wipeTokenLifetime = 60 * 60 * 24 * 60;
$this->config->expects($this->exactly(4))
$this->config->expects($this->exactly(5))
->method('getSystemValueInt')
->willReturnMap([
['session_lifetime', $defaultSessionLifetime, 150],
['remember_login_cookie_lifetime', $defaultRememberMeLifetime, 300],
['token_auth_wipe_token_retention', $wipeTokenLifetime, 500],
['token_auth_token_retention', 60 * 60 * 24 * 365, 800],
['token_auth_expired_token_retention', 60 * 60 * 24 * 7, 900],
]);

$calls = [
Expand All @@ -351,6 +352,10 @@ public function testInvalidateOldTokens(): void {
$this->assertEquals($expected, func_get_args());
});

$this->mapper->expects($this->once())
->method('invalidateExpired')
->with($this->time - 900);

$this->tokenProvider->invalidateOldTokens();
}

Expand Down