Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable23] Allow to disable AuthToken v1 #30949

Merged
merged 2 commits into from
Feb 16, 2022
Merged
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
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