diff options
author | Vincent Petry <vincent@nextcloud.com> | 2022-02-16 11:24:33 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-16 11:24:33 +0100 |
commit | 287d058a57b0853ff6e79fd0dd78c3754facc2ae (patch) | |
tree | 4fe7c743c833ac22f70cdda37c883430d3ae3a1d | |
parent | b9a56ee753ce96a6ed9cd3176e3e5cd891db26cd (diff) | |
parent | a429f02a6249287f1819d5455afdef635ad92a7a (diff) | |
download | nextcloud-server-287d058a57b0853ff6e79fd0dd78c3754facc2ae.tar.gz nextcloud-server-287d058a57b0853ff6e79fd0dd78c3754facc2ae.zip |
Merge pull request #30949 from nextcloud/bugfix/noid/allow-to-disable-v1-authtokens
[stable23] Allow to disable AuthToken v1
6 files changed, 124 insertions, 3 deletions
diff --git a/config/config.sample.php b/config/config.sample.php index 9f81cf3d1c1..99019c45b82 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -304,6 +304,16 @@ $CONFIG = [ 'auth.bruteforce.protection.enabled' => true, /** + * Whether the authtoken v1 provider should be skipped + * + * The v1 provider is deprecated and removed in Nextcloud 24 onwards. It can be + * disabled already when the instance was installed after Nextcloud 14. + * + * Defaults to ``false`` + */ +'auth.authtoken.v1.disabled' => false, + +/** * By default WebAuthn is available but it can be explicitly disabled by admins */ 'auth.webauthn.enabled' => true, diff --git a/lib/private/Authentication/Token/DefaultTokenCleanupJob.php b/lib/private/Authentication/Token/DefaultTokenCleanupJob.php index c3d80beac69..3f9aa490108 100644 --- a/lib/private/Authentication/Token/DefaultTokenCleanupJob.php +++ b/lib/private/Authentication/Token/DefaultTokenCleanupJob.php @@ -24,9 +24,22 @@ namespace OC\Authentication\Token; use OC; use OC\BackgroundJob\Job; +use OCP\IConfig; class DefaultTokenCleanupJob extends Job { + + /** @var IConfig */ + protected $config; + + public function __construct(IConfig $config) { + $this->config = $config; + } + protected function run($argument) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return; + } + /* @var $provider IProvider */ $provider = OC::$server->query(IProvider::class); $provider->invalidateOldTokens(); diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index 6ceb777c30f..34e3aa30ab6 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -32,13 +32,19 @@ namespace OC\Authentication\Token; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; use OCP\IDBConnection; /** * @template-extends QBMapper<DefaultToken> */ class DefaultTokenMapper extends QBMapper { - public function __construct(IDBConnection $db) { + + /** @var IConfig */ + protected $config; + + public function __construct(IDBConnection $db, IConfig $config) { + $this->config = $config; parent::__construct($db, 'authtoken'); } @@ -48,6 +54,10 @@ class DefaultTokenMapper extends QBMapper { * @param string $token */ public function invalidate(string $token) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return; + } + /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $qb->delete('authtoken') @@ -61,6 +71,10 @@ class DefaultTokenMapper extends QBMapper { * @param int $remember */ public function invalidateOld(int $olderThan, int $remember = IToken::DO_NOT_REMEMBER) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return; + } + /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $qb->delete('authtoken') @@ -79,6 +93,10 @@ class DefaultTokenMapper extends QBMapper { * @return DefaultToken */ public function getToken(string $token): DefaultToken { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new DoesNotExistException('Authtoken v1 disabled'); + } + /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'token', 'type', 'remember', 'last_activity', 'last_check', 'scope', 'expires', 'version') @@ -103,6 +121,10 @@ class DefaultTokenMapper extends QBMapper { * @return DefaultToken */ public function getTokenById(int $id): DefaultToken { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new DoesNotExistException('Authtoken v1 disabled'); + } + /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'token', 'type', 'remember', 'last_activity', 'last_check', 'scope', 'expires', 'version') @@ -129,6 +151,10 @@ class DefaultTokenMapper extends QBMapper { * @return DefaultToken[] */ public function getTokenByUser(string $uid): array { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return []; + } + /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $qb->select('id', 'uid', 'login_name', 'password', 'name', 'token', 'type', 'remember', 'last_activity', 'last_check', 'scope', 'expires', 'version') @@ -148,6 +174,10 @@ class DefaultTokenMapper extends QBMapper { } public function deleteById(string $uid, int $id) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return; + } + /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $qb->delete('authtoken') @@ -163,6 +193,10 @@ class DefaultTokenMapper extends QBMapper { * @param string $name */ public function deleteByName(string $name) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return; + } + $qb = $this->db->getQueryBuilder(); $qb->delete('authtoken') ->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR)) diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index c10d7f17bc2..c55a194501b 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -80,6 +80,10 @@ class DefaultTokenProvider implements IProvider { string $name, int $type = IToken::TEMPORARY_TOKEN, int $remember = IToken::DO_NOT_REMEMBER): IToken { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + $dbToken = new DefaultToken(); $dbToken->setUid($uid); $dbToken->setLoginName($loginName); @@ -106,6 +110,10 @@ class DefaultTokenProvider implements IProvider { * @throws InvalidTokenException */ public function updateToken(IToken $token) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + if (!($token instanceof DefaultToken)) { throw new InvalidTokenException("Invalid token type"); } @@ -119,6 +127,10 @@ class DefaultTokenProvider implements IProvider { * @param IToken $token */ public function updateTokenActivity(IToken $token) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + if (!($token instanceof DefaultToken)) { throw new InvalidTokenException("Invalid token type"); } @@ -132,6 +144,10 @@ class DefaultTokenProvider implements IProvider { } public function getTokenByUser(string $uid): array { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return []; + } + return $this->mapper->getTokenByUser($uid); } @@ -144,6 +160,10 @@ class DefaultTokenProvider implements IProvider { * @return IToken */ public function getToken(string $tokenId): IToken { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + try { $token = $this->mapper->getToken($this->hashToken($tokenId)); } catch (DoesNotExistException $ex) { @@ -166,6 +186,10 @@ class DefaultTokenProvider implements IProvider { * @return IToken */ public function getTokenById(int $tokenId): IToken { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + try { $token = $this->mapper->getTokenById($tokenId); } catch (DoesNotExistException $ex) { @@ -186,6 +210,10 @@ class DefaultTokenProvider implements IProvider { * @return IToken */ public function renewSessionToken(string $oldSessionId, string $sessionId): IToken { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + $token = $this->getToken($oldSessionId); $newToken = new DefaultToken(); @@ -214,6 +242,10 @@ class DefaultTokenProvider implements IProvider { * @return string */ public function getPassword(IToken $savedToken, string $tokenId): string { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + $password = $savedToken->getPassword(); if ($password === null || $password === '') { throw new PasswordlessTokenException(); @@ -230,6 +262,10 @@ class DefaultTokenProvider implements IProvider { * @throws InvalidTokenException */ public function setPassword(IToken $token, string $tokenId, string $password) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + if (!($token instanceof DefaultToken)) { throw new InvalidTokenException("Invalid token type"); } @@ -244,10 +280,18 @@ class DefaultTokenProvider implements IProvider { * @param string $token */ public function invalidateToken(string $token) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return; + } + $this->mapper->invalidate($this->hashToken($token)); } public function invalidateTokenById(string $uid, int $id) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return; + } + $this->mapper->deleteById($uid, $id); } @@ -255,6 +299,10 @@ class DefaultTokenProvider implements IProvider { * Invalidate (delete) old session tokens */ public function invalidateOldTokens() { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + return; + } + $olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24); $this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']); $this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER); @@ -272,6 +320,10 @@ class DefaultTokenProvider implements IProvider { * @return IToken */ public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + try { $password = $this->getPassword($token, $oldTokenId); $token->setPassword($this->encryptPassword($password, $newTokenId)); @@ -329,6 +381,10 @@ class DefaultTokenProvider implements IProvider { } public function markPasswordInvalid(IToken $token, string $tokenId) { + if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) { + throw new InvalidTokenException('Authtokens v1 disabled'); + } + if (!($token instanceof DefaultToken)) { throw new InvalidTokenException("Invalid token type"); } diff --git a/tests/lib/Authentication/Token/DefaultTokenCleanupJobTest.php b/tests/lib/Authentication/Token/DefaultTokenCleanupJobTest.php index 0991c8b1fc8..a464dc4b5bb 100644 --- a/tests/lib/Authentication/Token/DefaultTokenCleanupJobTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenCleanupJobTest.php @@ -25,6 +25,7 @@ namespace Test\Authentication\Token; use OC\Authentication\Token\DefaultTokenCleanupJob; use OC\Authentication\Token\IProvider; use OC\Authentication\Token\Manager; +use OCP\IConfig; use Test\TestCase; class DefaultTokenCleanupJobTest extends TestCase { @@ -36,11 +37,13 @@ class DefaultTokenCleanupJobTest extends TestCase { protected function setUp(): void { parent::setUp(); + $this->config = $this->createMock(IConfig::class); + $this->tokenProvider = $this->getMockBuilder(Manager::class) ->disableOriginalConstructor() ->getMock(); $this->overwriteService(IProvider::class, $this->tokenProvider); - $this->job = new DefaultTokenCleanupJob(); + $this->job = new DefaultTokenCleanupJob($this->config); } public function testRun() { diff --git a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php index da779be0807..9b9bfaf4821 100644 --- a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php @@ -27,8 +27,10 @@ use OC\Authentication\Token\DefaultToken; use OC\Authentication\Token\DefaultTokenMapper; use OC\Authentication\Token\IToken; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; use OCP\IDBConnection; use OCP\IUser; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; /** @@ -44,6 +46,8 @@ class DefaultTokenMapperTest extends TestCase { /** @var IDBConnection */ private $dbConnection; + /** @var IConfig|MockObject */ + private $config; private $time; protected function setUp(): void { @@ -51,9 +55,10 @@ class DefaultTokenMapperTest extends TestCase { $this->dbConnection = OC::$server->getDatabaseConnection(); $this->time = time(); + $this->config = $this->createMock(IConfig::class); $this->resetDatabase(); - $this->mapper = new DefaultTokenMapper($this->dbConnection); + $this->mapper = new DefaultTokenMapper($this->dbConnection, $this->config); } private function resetDatabase() { |