123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- <?php
- declare(strict_types=1);
- /**
- * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
- namespace OC\Authentication\Token;
- use OCP\AppFramework\Db\DoesNotExistException;
- use OCP\AppFramework\Db\QBMapper;
- use OCP\Authentication\Token\IToken;
- use OCP\DB\QueryBuilder\IQueryBuilder;
- use OCP\IDBConnection;
- /**
- * @template-extends QBMapper<PublicKeyToken>
- */
- class PublicKeyTokenMapper extends QBMapper {
- public function __construct(IDBConnection $db) {
- parent::__construct($db, 'authtoken');
- }
- /**
- * Invalidate (delete) a given token
- */
- public function invalidate(string $token) {
- /* @var $qb IQueryBuilder */
- $qb = $this->db->getQueryBuilder();
- $qb->delete($this->tableName)
- ->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
- ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
- ->execute();
- }
- /**
- * @param int $olderThan
- * @param int $remember
- */
- public function invalidateOld(int $olderThan, int $remember = IToken::DO_NOT_REMEMBER) {
- /* @var $qb IQueryBuilder */
- $qb = $this->db->getQueryBuilder();
- $qb->delete($this->tableName)
- ->where($qb->expr()->lt('last_activity', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('remember', $qb->createNamedParameter($remember, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
- ->execute();
- }
- public function invalidateLastUsedBefore(string $uid, int $before): int {
- $qb = $this->db->getQueryBuilder();
- $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)));
- return $qb->executeStatement();
- }
- /**
- * Get the user UID for the given token
- *
- * @throws DoesNotExistException
- */
- public function getToken(string $token): PublicKeyToken {
- /* @var $qb IQueryBuilder */
- $qb = $this->db->getQueryBuilder();
- $result = $qb->select('*')
- ->from($this->tableName)
- ->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
- ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
- ->execute();
- $data = $result->fetch();
- $result->closeCursor();
- if ($data === false) {
- throw new DoesNotExistException('token does not exist');
- }
- return PublicKeyToken::fromRow($data);
- }
- /**
- * Get the token for $id
- *
- * @throws DoesNotExistException
- */
- public function getTokenById(int $id): PublicKeyToken {
- /* @var $qb IQueryBuilder */
- $qb = $this->db->getQueryBuilder();
- $result = $qb->select('*')
- ->from($this->tableName)
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
- ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
- ->execute();
- $data = $result->fetch();
- $result->closeCursor();
- if ($data === false) {
- throw new DoesNotExistException('token does not exist');
- }
- return PublicKeyToken::fromRow($data);
- }
- /**
- * Get all tokens of a user
- *
- * The provider may limit the number of result rows in case of an abuse
- * where a high number of (session) tokens is generated
- *
- * @param string $uid
- * @return PublicKeyToken[]
- */
- public function getTokenByUser(string $uid): array {
- /* @var $qb IQueryBuilder */
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->tableName)
- ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
- ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
- ->setMaxResults(1000);
- $result = $qb->execute();
- $data = $result->fetchAll();
- $result->closeCursor();
- $entities = array_map(function ($row) {
- return PublicKeyToken::fromRow($row);
- }, $data);
- return $entities;
- }
- public function getTokenByUserAndId(string $uid, int $id): ?string {
- /* @var $qb IQueryBuilder */
- $qb = $this->db->getQueryBuilder();
- $qb->select('token')
- ->from($this->tableName)
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
- ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
- ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
- return $qb->executeQuery()->fetchOne() ?: null;
- }
- /**
- * delete all auth token which belong to a specific client if the client was deleted
- *
- * @param string $name
- */
- public function deleteByName(string $name) {
- $qb = $this->db->getQueryBuilder();
- $qb->delete($this->tableName)
- ->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR))
- ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
- $qb->execute();
- }
- public function deleteTempToken(PublicKeyToken $except) {
- $qb = $this->db->getQueryBuilder();
- $qb->delete($this->tableName)
- ->where($qb->expr()->eq('uid', $qb->createNamedParameter($except->getUID())))
- ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN)))
- ->andWhere($qb->expr()->neq('id', $qb->createNamedParameter($except->getId())))
- ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
- $qb->execute();
- }
- public function hasExpiredTokens(string $uid): bool {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->tableName)
- ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
- ->andWhere($qb->expr()->eq('password_invalid', $qb->createNamedParameter(true), IQueryBuilder::PARAM_BOOL))
- ->setMaxResults(1);
- $cursor = $qb->execute();
- $data = $cursor->fetchAll();
- $cursor->closeCursor();
- return count($data) === 1;
- }
- /**
- * 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
- * not necessary to hit the database with two actual updates. Therefore the
- * target last activity is included in the WHERE clause with a few seconds
- * of tolerance.
- *
- * Example:
- * - process 1 (P1) reads the token at timestamp 1500
- * - process 2 (P2) reads the token at timestamp 1501
- * - activity update interval is 100
- *
- * This means
- *
- * - P1 will see a last_activity smaller than the current time and update
- * the token row
- * - If P2 reads after P1 had written, it will see 1600 as last activity
- * and the comparison on last_activity won't be truthy. This means no rows
- * need to be updated a second time
- * - If P2 reads before P1 had written, it will see 1501 as last activity,
- * but the comparison on last_activity will still not be truthy and the
- * token row is not updated a second time
- *
- * @param PublicKeyToken $token
- * @param int $now
- */
- 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();
- }
- public function updateHashesForUser(string $userId, string $passwordHash): void {
- $qb = $this->db->getQueryBuilder();
- $update = $qb->update($this->getTableName())
- ->set('password_hash', $qb->createNamedParameter($passwordHash))
- ->where(
- $qb->expr()->eq('uid', $qb->createNamedParameter($userId))
- );
- $update->executeStatement();
- }
- public function getFirstTokenForUser(string $userId): ?PublicKeyToken {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->getTableName())
- ->where($qb->expr()->eq('uid', $qb->createNamedParameter($userId)))
- ->setMaxResults(1)
- ->orderBy('id');
- $result = $qb->executeQuery();
- $data = $result->fetch();
- $result->closeCursor();
- if ($data === false) {
- return null;
- }
- return PublicKeyToken::fromRow($data);
- }
- }
|