Skip to content

Commit

Permalink
Merge pull request #30949 from nextcloud/bugfix/noid/allow-to-disable…
Browse files Browse the repository at this point in the history
…-v1-authtokens

[stable23] Allow to disable AuthToken v1
  • Loading branch information
PVince81 authored Feb 16, 2022
2 parents b9a56ee + a429f02 commit 287d058
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 3 deletions.
10 changes: 10 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,16 @@
*/
'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
*/
Expand Down
13 changes: 13 additions & 0 deletions lib/private/Authentication/Token/DefaultTokenCleanupJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,22 @@

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();
Expand Down
36 changes: 35 additions & 1 deletion lib/private/Authentication/Token/DefaultTokenMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@
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');
}

Expand All @@ -48,6 +54,10 @@ public function __construct(IDBConnection $db) {
* @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')
Expand All @@ -61,6 +71,10 @@ public function invalidate(string $token) {
* @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')
Expand All @@ -79,6 +93,10 @@ public function invalidateOld(int $olderThan, int $remember = IToken::DO_NOT_REM
* @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')
Expand All @@ -103,6 +121,10 @@ public function getToken(string $token): DefaultToken {
* @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')
Expand All @@ -129,6 +151,10 @@ public function getTokenById(int $id): DefaultToken {
* @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')
Expand All @@ -148,6 +174,10 @@ public function getTokenByUser(string $uid): array {
}

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')
Expand All @@ -163,6 +193,10 @@ public function deleteById(string $uid, int $id) {
* @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))
Expand Down
56 changes: 56 additions & 0 deletions lib/private/Authentication/Token/DefaultTokenProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public function generateToken(string $token,
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);
Expand All @@ -106,6 +110,10 @@ public function generateToken(string $token,
* @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");
}
Expand All @@ -119,6 +127,10 @@ public function updateToken(IToken $token) {
* @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");
}
Expand All @@ -132,6 +144,10 @@ public function updateTokenActivity(IToken $token) {
}

public function getTokenByUser(string $uid): array {
if ($this->config->getSystemValueBool('auth.authtoken.v1.disabled')) {
return [];
}

return $this->mapper->getTokenByUser($uid);
}

Expand All @@ -144,6 +160,10 @@ public function getTokenByUser(string $uid): array {
* @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) {
Expand All @@ -166,6 +186,10 @@ public function getToken(string $tokenId): IToken {
* @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) {
Expand All @@ -186,6 +210,10 @@ public function getTokenById(int $tokenId): IToken {
* @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();
Expand Down Expand Up @@ -214,6 +242,10 @@ public function renewSessionToken(string $oldSessionId, string $sessionId): ITok
* @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();
Expand All @@ -230,6 +262,10 @@ public function getPassword(IToken $savedToken, string $tokenId): string {
* @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");
}
Expand All @@ -244,17 +280,29 @@ public function setPassword(IToken $token, string $tokenId, string $password) {
* @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);
}

/**
* 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);
Expand All @@ -272,6 +320,10 @@ public function invalidateOldTokens() {
* @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));
Expand Down Expand Up @@ -329,6 +381,10 @@ private function decryptPassword(string $password, string $token): string {
}

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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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 {
Expand All @@ -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() {
Expand Down
7 changes: 6 additions & 1 deletion tests/lib/Authentication/Token/DefaultTokenMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
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;

/**
Expand All @@ -44,16 +46,19 @@ class DefaultTokenMapperTest extends TestCase {

/** @var IDBConnection */
private $dbConnection;
/** @var IConfig|MockObject */
private $config;
private $time;

protected function setUp(): void {
parent::setUp();

$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() {
Expand Down

0 comments on commit 287d058

Please sign in to comment.