From efef05396034eaf34614b39aef36056a65f6f452 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 26 Sep 2018 12:20:26 +0200 Subject: [PATCH 1/4] Add column to DB to store expired passwords Signed-off-by: Roeland Jago Douma --- .../Version15000Date20180926101451.php | 53 +++++++++++++++++++ lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + version.php | 2 +- 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 core/Migrations/Version15000Date20180926101451.php diff --git a/core/Migrations/Version15000Date20180926101451.php b/core/Migrations/Version15000Date20180926101451.php new file mode 100644 index 0000000000..00a5545576 --- /dev/null +++ b/core/Migrations/Version15000Date20180926101451.php @@ -0,0 +1,53 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Core\Migrations; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +class Version15000Date20180926101451 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable('authtoken'); + $table->addColumn('password_invalid','boolean', [ + 'notnull' => true, + 'default' => false, + ]); + + return $schema; + } + +} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 2522be9ec8..7de8d3a442 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -626,6 +626,7 @@ return array( 'OC\\Core\\Migrations\\Version14000Date20180626223656' => $baseDir . '/core/Migrations/Version14000Date20180626223656.php', 'OC\\Core\\Migrations\\Version14000Date20180710092004' => $baseDir . '/core/Migrations/Version14000Date20180710092004.php', 'OC\\Core\\Migrations\\Version14000Date20180712153140' => $baseDir . '/core/Migrations/Version14000Date20180712153140.php', + 'OC\\Core\\Migrations\\Version15000Date20180926101451' => $baseDir . '/core/Migrations/Version15000Date20180926101451.php', 'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php', 'OC\\DB\\AdapterMySQL' => $baseDir . '/lib/private/DB/AdapterMySQL.php', 'OC\\DB\\AdapterOCI8' => $baseDir . '/lib/private/DB/AdapterOCI8.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index e4e64ac1d5..8d92f623a0 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -656,6 +656,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Migrations\\Version14000Date20180626223656' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180626223656.php', 'OC\\Core\\Migrations\\Version14000Date20180710092004' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180710092004.php', 'OC\\Core\\Migrations\\Version14000Date20180712153140' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180712153140.php', + 'OC\\Core\\Migrations\\Version15000Date20180926101451' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20180926101451.php', 'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php', 'OC\\DB\\AdapterMySQL' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterMySQL.php', 'OC\\DB\\AdapterOCI8' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterOCI8.php', diff --git a/version.php b/version.php index eebed2ce7f..c3740f01fa 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = array(15, 0, 0, 0); +$OC_Version = array(15, 0, 0, 1); // The human readable string $OC_VersionString = '15.0.0 alpha'; From 00e99af5863e40e89c012f3ce642802c891def4e Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 26 Sep 2018 13:10:17 +0200 Subject: [PATCH 2/4] Mark token as invalid if the password doesn't match Signed-off-by: Roeland Jago Douma --- .../Authentication/Token/DefaultTokenProvider.php | 10 ++++++++++ lib/private/Authentication/Token/IProvider.php | 8 ++++++++ lib/private/Authentication/Token/Manager.php | 5 +++++ lib/private/Authentication/Token/PublicKeyToken.php | 6 ++++++ .../Authentication/Token/PublicKeyTokenProvider.php | 11 +++++++++++ lib/private/User/Session.php | 13 ++++++++++--- tests/lib/User/SessionTest.php | 6 ++---- 7 files changed, 52 insertions(+), 7 deletions(-) diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index ad45303fa7..19aba58b05 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -338,4 +338,14 @@ class DefaultTokenProvider implements IProvider { } } + public function markPasswordInvalid(IToken $token, string $tokenId) { + if (!($token instanceof DefaultToken)) { + throw new InvalidTokenException(); + } + + //No need to mark as invalid. We just invalide default tokens + $this->invalidateToken($tokenId); + } + + } diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index ab46bd1212..d1b067868b 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -156,4 +156,12 @@ interface IProvider { * @return IToken */ public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken; + + /** + * Marks a token as having an invalid password. + * + * @param IToken $token + * @param string $tokenId + */ + public function markPasswordInvalid(IToken $token, string $tokenId); } diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php index 254a159894..711d211039 100644 --- a/lib/private/Authentication/Token/Manager.php +++ b/lib/private/Authentication/Token/Manager.php @@ -227,4 +227,9 @@ class Manager implements IProvider { } throw new InvalidTokenException(); } + + + public function markPasswordInvalid(IToken $token, string $tokenId) { + $this->getProvider($token)->markPasswordInvalid($token, $tokenId); + } } diff --git a/lib/private/Authentication/Token/PublicKeyToken.php b/lib/private/Authentication/Token/PublicKeyToken.php index 0e793ce8c7..9896915ca3 100644 --- a/lib/private/Authentication/Token/PublicKeyToken.php +++ b/lib/private/Authentication/Token/PublicKeyToken.php @@ -43,6 +43,8 @@ use OCP\AppFramework\Db\Entity; * @method string getPublicKey() * @method void setPublicKey(string $key) * @method void setVersion(int $version) + * @method bool getPasswordInvalid() + * @method void setPasswordInvalid(bool $invalid); */ class PublicKeyToken extends Entity implements IToken { @@ -90,6 +92,9 @@ class PublicKeyToken extends Entity implements IToken { /** @var int */ protected $version; + /** @var bool */ + protected $passwordInvalid; + public function __construct() { $this->addType('uid', 'string'); $this->addType('loginName', 'string'); @@ -105,6 +110,7 @@ class PublicKeyToken extends Entity implements IToken { $this->addType('publicKey', 'string'); $this->addType('privateKey', 'string'); $this->addType('version', 'int'); + $this->addType('passwordInvalid', 'bool'); } public function getId(): int { diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 7e98ee939c..9afdb5a8ff 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -317,4 +317,15 @@ class PublicKeyTokenProvider implements IProvider { return $dbToken; } + + public function markPasswordInvalid(IToken $token, string $tokenId) { + if (!($token instanceof PublicKeyToken)) { + throw new InvalidTokenException(); + } + + $token->setPasswordInvalid(true); + $this->mapper->update($token); + } + + } diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 5593e178ca..8ac42eac4e 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -694,12 +694,19 @@ class Session implements IUserSession, Emitter { return true; } - if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false - || (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) { + // Invalidate token if the user is no longer active + if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) { $this->tokenProvider->invalidateToken($token); - // Password has changed or user was disabled -> log user out return false; } + + // If the token password is no longer valid mark it as such + if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false) { + $this->tokenProvider->markPasswordInvalid($dbToken, $token); + // User is logged out + return false; + } + $dbToken->setLastCheck($now); return true; } diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index 24677b57dd..81ceade9e0 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -1017,10 +1017,8 @@ class SessionTest extends \Test\TestCase { ->method('getPassword') ->with($token, 'APP-PASSWORD') ->will($this->returnValue('123456')); - $userManager->expects($this->once()) - ->method('checkPassword') - ->with('susan', '123456') - ->will($this->returnValue(true)); + $userManager->expects($this->never()) + ->method('checkPassword'); $user->expects($this->once()) ->method('isEnabled') ->will($this->returnValue(false)); From d9febae5b2cbe2147c892030ca9d1b5db7304e9f Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 26 Sep 2018 13:36:04 +0200 Subject: [PATCH 3/4] Update all the publickey tokens if needed on web login * On weblogin check if we have invalid public key tokens * If so update them all with the new token This ensures that your marked as invalid tokens work again if you once login on the web. Signed-off-by: Roeland Jago Douma --- core/Controller/LoginController.php | 1 + .../Authentication/Token/DefaultTokenProvider.php | 4 +++- lib/private/Authentication/Token/IProvider.php | 8 ++++++++ lib/private/Authentication/Token/Manager.php | 7 +++++++ .../Authentication/Token/PublicKeyTokenMapper.php | 15 +++++++++++++++ .../Token/PublicKeyTokenProvider.php | 15 +++++++++++++++ lib/private/User/Session.php | 4 ++++ 7 files changed, 53 insertions(+), 1 deletion(-) diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php index 09b6fe5438..14e3b4c40b 100644 --- a/core/Controller/LoginController.php +++ b/core/Controller/LoginController.php @@ -320,6 +320,7 @@ class LoginController extends Controller { // requires https://github.com/owncloud/core/pull/24616 $this->userSession->completeLogin($loginResult, ['loginName' => $user, 'password' => $password]); $this->userSession->createSessionToken($this->request, $loginResult->getUID(), $user, $password, IToken::REMEMBER); + $this->userSession->updateTokens($loginResult->getUID(), $password); // User has successfully logged in, now remove the password reset link, when it is available $this->config->deleteUserValue($loginResult->getUID(), 'core', 'lostpassword'); diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index 19aba58b05..a27a875a27 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -347,5 +347,7 @@ class DefaultTokenProvider implements IProvider { $this->invalidateToken($tokenId); } - + public function updatePasswords(string $uid, string $password) { + // Nothing to do here + } } diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index d1b067868b..7ee76b7b38 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -164,4 +164,12 @@ interface IProvider { * @param string $tokenId */ public function markPasswordInvalid(IToken $token, string $tokenId); + + /** + * Update all the passwords of $uid if required + * + * @param string $uid + * @param string $password + */ + public function updatePasswords(string $uid, string $password); } diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php index 711d211039..7c991eadea 100644 --- a/lib/private/Authentication/Token/Manager.php +++ b/lib/private/Authentication/Token/Manager.php @@ -232,4 +232,11 @@ class Manager implements IProvider { public function markPasswordInvalid(IToken $token, string $tokenId) { $this->getProvider($token)->markPasswordInvalid($token, $tokenId); } + + public function updatePasswords(string $uid, string $password) { + $this->defaultTokenProvider->updatePasswords($uid, $password); + $this->publicKeyTokenProvider->updatePasswords($uid, $password); + } + + } diff --git a/lib/private/Authentication/Token/PublicKeyTokenMapper.php b/lib/private/Authentication/Token/PublicKeyTokenMapper.php index 5e5c69dbc4..df91066c44 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenMapper.php +++ b/lib/private/Authentication/Token/PublicKeyTokenMapper.php @@ -169,4 +169,19 @@ class PublicKeyTokenMapper extends QBMapper { $qb->execute(); } + + public function hasExpiredTokens(string $uid): bool { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('authtoken') + ->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; + } } diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 9afdb5a8ff..33c0b1d59e 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -327,5 +327,20 @@ class PublicKeyTokenProvider implements IProvider { $this->mapper->update($token); } + public function updatePasswords(string $uid, string $password) { + if (!$this->mapper->hasExpiredTokens($uid)) { + // Nothing to do here + return; + } + + // Update the password for all tokens + $tokens = $this->mapper->getTokenByUser($uid); + foreach ($tokens as $t) { + $publicKey = $t->getPublicKey(); + $t->setPassword($this->encryptPassword($password, $publicKey)); + $this->updateToken($t); + } + } + } diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 8ac42eac4e..a9c638dca9 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -950,5 +950,9 @@ class Session implements IUserSession, Emitter { } } + public function updateTokens(string $uid, string $password) { + $this->tokenProvider->updatePasswords($uid, $password); + } + } From 19f84f7b5485e204014671d0439e8af5693acbb1 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 2 Oct 2018 11:50:41 +0200 Subject: [PATCH 4/4] Add tests Signed-off-by: Roeland Jago Douma --- .../Authentication/Token/PublicKeyToken.php | 5 +- .../Token/DefaultTokenProviderTest.php | 19 +++++ .../lib/Authentication/Token/ManagerTest.php | 41 +++++++++++ .../Token/PublicKeyTokenMapperTest.php | 31 ++++++-- .../Token/PublicKeyTokenProviderTest.php | 72 +++++++++++++++++++ tests/lib/User/SessionTest.php | 57 +++++++++++++++ 6 files changed, 218 insertions(+), 7 deletions(-) diff --git a/lib/private/Authentication/Token/PublicKeyToken.php b/lib/private/Authentication/Token/PublicKeyToken.php index 9896915ca3..b6f5514670 100644 --- a/lib/private/Authentication/Token/PublicKeyToken.php +++ b/lib/private/Authentication/Token/PublicKeyToken.php @@ -44,7 +44,6 @@ use OCP\AppFramework\Db\Entity; * @method void setPublicKey(string $key) * @method void setVersion(int $version) * @method bool getPasswordInvalid() - * @method void setPasswordInvalid(bool $invalid); */ class PublicKeyToken extends Entity implements IToken { @@ -220,4 +219,8 @@ class PublicKeyToken extends Entity implements IToken { public function getExpires() { return parent::getExpires(); } + + public function setPasswordInvalid(bool $invalid) { + parent::setPasswordInvalid($invalid); + } } diff --git a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php index 3fb11f410b..8b005bd8bd 100644 --- a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php @@ -28,6 +28,7 @@ use OC\Authentication\Token\DefaultTokenMapper; use OC\Authentication\Token\DefaultTokenProvider; use OC\Authentication\Token\ExpiredTokenException; use OC\Authentication\Token\IToken; +use OC\Authentication\Token\PublicKeyToken; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; @@ -532,4 +533,22 @@ class DefaultTokenProviderTest extends TestCase { $this->tokenProvider->rotate($token, 'oldtoken', 'newtoken'); } + + public function testMarkPasswordInvalidInvalidToken() { + $token = $this->createMock(PublicKeyToken::class); + + $this->expectException(InvalidTokenException::class); + + $this->tokenProvider->markPasswordInvalid($token, 'tokenId'); + } + + public function testMarkPasswordInvalid() { + $token = $this->createMock(DefaultToken::class); + + $this->mapper->expects($this->once()) + ->method('invalidate') + ->with('0c7db0098fe8ddba6032b22719ec18867c69a1820fa36d71c28bf96d52843bdc44a112bd24093b049be5bb54769bcb72d67190a4a9690e51aac263cba38186fb'); + + $this->tokenProvider->markPasswordInvalid($token, 'tokenId'); + } } diff --git a/tests/lib/Authentication/Token/ManagerTest.php b/tests/lib/Authentication/Token/ManagerTest.php index 8b77bfc499..1b7f3a637c 100644 --- a/tests/lib/Authentication/Token/ManagerTest.php +++ b/tests/lib/Authentication/Token/ManagerTest.php @@ -448,4 +448,45 @@ class ManagerTest extends TestCase { $this->assertSame($newToken, $this->manager->rotate($oldToken, 'oldId', 'newId')); } + + public function testMarkPasswordInvalidDefault() { + $token = $this->createMock(DefaultToken::class); + + $this->defaultTokenProvider->expects($this->once()) + ->method('markPasswordInvalid') + ->with($token, 'tokenId'); + $this->publicKeyTokenProvider->expects($this->never()) + ->method($this->anything()); + + $this->manager->markPasswordInvalid($token, 'tokenId'); + } + + public function testMarkPasswordInvalidPublicKey() { + $token = $this->createMock(PublicKeyToken::class); + + $this->publicKeyTokenProvider->expects($this->once()) + ->method('markPasswordInvalid') + ->with($token, 'tokenId'); + $this->defaultTokenProvider->expects($this->never()) + ->method($this->anything()); + + $this->manager->markPasswordInvalid($token, 'tokenId'); + } + + public function testMarkPasswordInvalidInvalidToken() { + $this->expectException(InvalidTokenException::class); + + $this->manager->markPasswordInvalid($this->createMock(IToken::class), 'tokenId'); + } + + public function testUpdatePasswords() { + $this->defaultTokenProvider->expects($this->once()) + ->method('updatePasswords') + ->with('uid', 'pass'); + $this->publicKeyTokenProvider->expects($this->once()) + ->method('updatePasswords') + ->with('uid', 'pass'); + + $this->manager->updatePasswords('uid', 'pass'); + } } diff --git a/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php index 5a98747ab0..89adef5bb8 100644 --- a/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php +++ b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php @@ -99,6 +99,20 @@ class PublicKeyTokenMapperTest extends TestCase { 'private_key' => $qb->createNamedParameter('private key'), 'version' => $qb->createNamedParameter(2), ])->execute(); + $qb->insert('authtoken')->values([ + 'uid' => $qb->createNamedParameter('user3'), + 'login_name' => $qb->createNamedParameter('User3'), + 'password' => $qb->createNamedParameter('063de945d6f6b26862d9b6f40652f2d5|DZ/z520tfdXPtd0T|395f6b89be8d9d605e409e20b9d9abe477fde1be38a3223f9e508f979bf906e50d9eaa4dca983ca4fb22a241eb696c3f98654e7775f78c4caf13108f98642b53'), + 'name' => $qb->createNamedParameter('Iceweasel on Linux'), + 'token' => $qb->createNamedParameter('6d9a290d239d09f2cc33a03cc54cccd46f7dc71630dcc27d39214824bd3e093f1feb4e2b55eb159d204caa15dee9556c202a5aa0b9d67806c3f4ec2cde11af67'), + 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN), + 'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago + 'last_check' => $this->time - 60 * 10, // 10mins ago + 'public_key' => $qb->createNamedParameter('public key'), + 'private_key' => $qb->createNamedParameter('private key'), + 'version' => $qb->createNamedParameter(2), + 'password_invalid' => $qb->createNamedParameter(1), + ])->execute(); } private function getNumberOfTokens() { @@ -115,7 +129,7 @@ class PublicKeyTokenMapperTest extends TestCase { $this->mapper->invalidate($token); - $this->assertSame(2, $this->getNumberOfTokens()); + $this->assertSame(3, $this->getNumberOfTokens()); } public function testInvalidateInvalid() { @@ -123,7 +137,7 @@ class PublicKeyTokenMapperTest extends TestCase { $this->mapper->invalidate($token); - $this->assertSame(3, $this->getNumberOfTokens()); + $this->assertSame(4, $this->getNumberOfTokens()); } public function testInvalidateOld() { @@ -131,7 +145,7 @@ class PublicKeyTokenMapperTest extends TestCase { $this->mapper->invalidateOld($olderThan); - $this->assertSame(2, $this->getNumberOfTokens()); + $this->assertSame(3, $this->getNumberOfTokens()); } public function testGetToken() { @@ -224,7 +238,7 @@ class PublicKeyTokenMapperTest extends TestCase { $id = $result->fetch()['id']; $this->mapper->deleteById('user1', (int)$id); - $this->assertEquals(2, $this->getNumberOfTokens()); + $this->assertEquals(3, $this->getNumberOfTokens()); } public function testDeleteByIdWrongUser() { @@ -233,7 +247,7 @@ class PublicKeyTokenMapperTest extends TestCase { $id = 33; $this->mapper->deleteById('user1000', $id); - $this->assertEquals(3, $this->getNumberOfTokens()); + $this->assertEquals(4, $this->getNumberOfTokens()); } public function testDeleteByName() { @@ -244,7 +258,12 @@ class PublicKeyTokenMapperTest extends TestCase { $result = $qb->execute(); $name = $result->fetch()['name']; $this->mapper->deleteByName($name); - $this->assertEquals(2, $this->getNumberOfTokens()); + $this->assertEquals(3, $this->getNumberOfTokens()); + } + + public function testHasExpiredTokens() { + $this->assertFalse($this->mapper->hasExpiredTokens('user1')); + $this->assertTrue($this->mapper->hasExpiredTokens('user3')); } } diff --git a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php index 681ad35c64..02ec62d3d7 100644 --- a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php +++ b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php @@ -504,4 +504,76 @@ class PublicKeyTokenProviderTest extends TestCase { $this->assertSame(IToken::REMEMBER, $newToken->getRemember()); $this->assertSame(IToken::PERMANENT_TOKEN, $newToken->getType()); } + + public function testMarkPasswordInvalidInvalidToken() { + $token = $this->createMock(DefaultToken::class); + + $this->expectException(InvalidTokenException::class); + + $this->tokenProvider->markPasswordInvalid($token, 'tokenId'); + } + + public function testMarkPasswordInvalid() { + $token = $this->createMock(PublicKeyToken::class); + + $token->expects($this->once()) + ->method('setPasswordInvalid') + ->with(true); + $this->mapper->expects($this->once()) + ->method('update') + ->with($token); + + $this->tokenProvider->markPasswordInvalid($token, 'tokenId'); + } + + public function testUpdatePasswords() { + $uid = 'myUID'; + $token1 = $this->tokenProvider->generateToken( + 'foo', + $uid, + $uid, + 'bar', + 'random1', + IToken::PERMANENT_TOKEN, + IToken::REMEMBER); + $token2 = $this->tokenProvider->generateToken( + 'foobar', + $uid, + $uid, + 'bar', + 'random2', + IToken::PERMANENT_TOKEN, + IToken::REMEMBER); + + $this->mapper->expects($this->once()) + ->method('hasExpiredTokens') + ->with($uid) + ->willReturn(true); + $this->mapper->expects($this->once()) + ->method('getTokenByUser') + ->with($uid) + ->willReturn([$token1, $token2]); + $this->mapper->expects($this->exactly(2)) + ->method('update') + ->with($this->callback(function (PublicKeyToken $t) use ($token1, $token2) { + return $t === $token1 || $t === $token2; + })); + + $this->tokenProvider->updatePasswords($uid, 'bar2'); + } + + public function testUpdatePasswordsNotRequired() { + $uid = 'myUID'; + + $this->mapper->expects($this->once()) + ->method('hasExpiredTokens') + ->with($uid) + ->willReturn(false); + $this->mapper->expects($this->never()) + ->method('getTokenByUser'); + $this->mapper->expects($this->never()) + ->method('update'); + + $this->tokenProvider->updatePasswords($uid, 'bar2'); + } } diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index 81ceade9e0..114b2eb559 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -1067,6 +1067,55 @@ class SessionTest extends \Test\TestCase { $this->assertEquals(1000, $token->getLastCheck()); } + public function testValidateSessionInvalidPassword() { + $userManager = $this->createMock(Manager::class); + $session = $this->createMock(ISession::class); + $timeFactory = $this->createMock(ITimeFactory::class); + $tokenProvider = $this->createMock(IProvider::class); + $userSession = $this->getMockBuilder(Session::class) + ->setConstructorArgs([$userManager, $session, $timeFactory, $tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger]) + ->setMethods(['logout']) + ->getMock(); + + $user = $this->createMock(IUser::class); + $token = new \OC\Authentication\Token\DefaultToken(); + $token->setLoginName('susan'); + $token->setLastCheck(20); + + $session->expects($this->once()) + ->method('get') + ->with('app_password') + ->will($this->returnValue('APP-PASSWORD')); + $tokenProvider->expects($this->once()) + ->method('getToken') + ->with('APP-PASSWORD') + ->will($this->returnValue($token)); + $timeFactory->expects($this->once()) + ->method('getTime') + ->will($this->returnValue(1000)); // more than 5min since last check + $tokenProvider->expects($this->once()) + ->method('getPassword') + ->with($token, 'APP-PASSWORD') + ->will($this->returnValue('123456')); + $userManager->expects($this->once()) + ->method('checkPassword') + ->with('susan', '123456') + ->willReturn(false); + $user->expects($this->once()) + ->method('isEnabled') + ->will($this->returnValue(true)); + $tokenProvider->expects($this->never()) + ->method('invalidateToken'); + $tokenProvider->expects($this->once()) + ->method('markPasswordInvalid') + ->with($token, 'APP-PASSWORD'); + $userSession->expects($this->once()) + ->method('logout'); + + $userSession->setUser($user); + $this->invokePrivate($userSession, 'validateSession'); + } + public function testUpdateSessionTokenPassword() { $userManager = $this->createMock(Manager::class); $session = $this->createMock(ISession::class); @@ -1362,4 +1411,12 @@ class SessionTest extends \Test\TestCase { $this->assertFalse($userSession->tryBasicAuthLogin($request, $this->throttler)); } + + public function testUpdateTokens() { + $this->tokenProvider->expects($this->once()) + ->method('updatePasswords') + ->with('uid', 'pass'); + + $this->userSession->updateTokens('uid', 'pass'); + } }