diff --git a/core/Command/User/AuthTokens/Delete.php b/core/Command/User/AuthTokens/Delete.php index 928387f1cc6..830050c1bb9 100644 --- a/core/Command/User/AuthTokens/Delete.php +++ b/core/Command/User/AuthTokens/Delete.php @@ -22,10 +22,14 @@ */ namespace OC\Core\Command\User\AuthTokens; +use DateTimeImmutable; use OC\Core\Command\Base; use OC\Authentication\Token\IProvider; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class Delete extends Base { @@ -40,17 +44,77 @@ class Delete extends Base { ->setName('user:auth-tokens:delete') ->setDescription('Deletes an authentication token') ->addArgument( - 'id', + 'uid', InputArgument::REQUIRED, + 'ID of the user to delete tokens for' + ) + ->addArgument( + 'id', + InputArgument::OPTIONAL, 'ID of the auth token to delete' + ) + ->addOption( + 'last-used-before', + null, + InputOption::VALUE_REQUIRED, + 'Delete tokens last used before a given date.' ); } protected function execute(InputInterface $input, OutputInterface $output): int { - $token = $this->tokenProvider->getTokenById($input->getArgument('id')); + $uid = $input->getArgument('uid'); + $id = $input->getArgument('id'); + $before = $input->getOption('last-used-before'); - $this->tokenProvider->invalidateTokenById($token->getUID(), $token->getId()); + if ($before) { + if ($id) { + throw new RuntimeException('Option --last-used-before cannot be used with []'); + } - return 0; + return $this->deleteLastUsedBefore($uid, $before); + } + + if (!$id) { + throw new RuntimeException('Not enough arguments. Specify the token or use the --last-used-before option.'); + } + return $this->deleteById($uid, $id); + } + + protected function deleteById(string $uid, string $id) { + $this->tokenProvider->invalidateTokenById($uid, $id); + + return Command::SUCCESS; + } + + protected function deleteLastUsedBefore(string $uid, string $before) { + $date = $this->parseDateOption($before); + if (!$date) { + throw new RuntimeException('Invalid date format. Acceptable formats are: ISO8601 (w/o fractions), "YYYY-MM-DD" and Unix time in seconds.'); + } + + $this->tokenProvider->invalidateLastUsedBefore($uid, $date->getTimestamp()); + + return Command::SUCCESS; + } + + /** + * @return \DateTimeImmutable|false + */ + protected function parseDateOption(string $input) { + $date = false; + + // Handle Unix timestamp + if (filter_var($input, FILTER_VALIDATE_INT)) { + return new DateTimeImmutable('@' . $input); + } + + // ISO8601 + $date = DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $input); + if ($date) { + return $date; + } + + // YYYY-MM-DD + return DateTimeImmutable::createFromFormat('!Y-m-d', $input); } } diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index b5af3f3a5ee..a12d3ba34d9 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -109,6 +109,11 @@ interface IProvider { */ public function invalidateOldTokens(); + /** + * Invalidate (delete) tokens last used before a given date + */ + public function invalidateLastUsedBefore(string $uid, int $before): void; + /** * Save the updated token * diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php index 761e799d298..6a1c7d4c1e7 100644 --- a/lib/private/Authentication/Token/Manager.php +++ b/lib/private/Authentication/Token/Manager.php @@ -204,6 +204,10 @@ class Manager implements IProvider, OCPIProvider { $this->publicKeyTokenProvider->invalidateOldTokens(); } + public function invalidateLastUsedBefore(string $uid, int $before): void { + $this->publicKeyTokenProvider->invalidateLastUsedBefore($uid, $before); + } + /** * @param IToken $token * @param string $oldTokenId diff --git a/lib/private/Authentication/Token/PublicKeyTokenMapper.php b/lib/private/Authentication/Token/PublicKeyTokenMapper.php index 8feb275b3b7..f150576a623 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenMapper.php +++ b/lib/private/Authentication/Token/PublicKeyTokenMapper.php @@ -69,6 +69,15 @@ class PublicKeyTokenMapper extends QBMapper { ->execute(); } + public function invalidateLastUsedBefore(string $uid, int $before): int { + $qb = $this->db->getQueryBuilder(); + return $qb->delete($this->tableName) + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) + ->andWhere($qb->expr()->lt('last_activity', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT))) + ->executeStatement(); + } + /** * Get the user UID for the given token * diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index f5fcd4dcef2..3fb11611076 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -273,6 +273,12 @@ class PublicKeyTokenProvider implements IProvider { $this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER); } + public function invalidateLastUsedBefore(string $uid, int $before): void { + $this->cache->clear(); + + $this->mapper->invalidateLastUsedBefore($uid, $before); + } + public function updateToken(IToken $token) { $this->cache->clear();