diff --git a/appinfo/routes.php b/appinfo/routes.php index 58bbfe3..cced935 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -36,5 +36,16 @@ 'url' => '/settings/verifyNewSecret', 'verb' => 'POST' ], + ], + 'ocs' => [ + [ + 'name' => 'totp_api#validateKey', + 'url' => '/api/v1/validate/{uid}/{key}', + 'verb' => 'GET', + 'requirements' => [ + 'uid' => '.+', + 'key' => '.+' + ] + ] ] ]; diff --git a/lib/Controller/TotpApiController.php b/lib/Controller/TotpApiController.php new file mode 100644 index 0000000..7e32f26 --- /dev/null +++ b/lib/Controller/TotpApiController.php @@ -0,0 +1,82 @@ + + * + * Two-factor TOTP + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\TwoFactor_Totp\Controller; + +use OCA\TwoFactor_Totp\Exception\NoTotpSecretFoundException; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCSController; +use OCP\ILogger; +use \OCP\IRequest; +use \OCP\IUserManager; +use OCA\TwoFactor_Totp\Service\ITotp; + +class TotpApiController extends OCSController { + + /** @var ITotp */ + private $totp; + + /** @var IUserManager */ + private $userManager; + + /** @var ILogger */ + private $logger; + + public function __construct( + $appName, + IRequest $request, + ITotp $totp, + IUserManager $userManager, + ILogger $logger + ) { + parent::__construct($appName, $request); + $this->totp = $totp; + $this->userManager = $userManager; + $this->logger = $logger; + } + + /** + * @CORS + * @NoCSRFRequired + * + * @param string $uid + * @param string $key 6 digits numeric time-based one time password. + * @return DataResponse + */ + public function validateKey($uid, $key) { + $user = $this->userManager->get($uid); + if ($user !== null) { + try { + return new DataResponse(['data' => ['result' => $this->totp->validateKey($user, $key)]]); + } catch (NoTotpSecretFoundException $e) { + $this->logger->logException($e); + } + } + return new DataResponse( + [ + 'statuscode' => 404, + 'data' => ['result' => false] + ], + Http::STATUS_NOT_FOUND + ); + } +} diff --git a/lib/Provider/TotpProvider.php b/lib/Provider/TotpProvider.php index 42a8e7c..47d45f9 100644 --- a/lib/Provider/TotpProvider.php +++ b/lib/Provider/TotpProvider.php @@ -90,7 +90,7 @@ public function getTemplate(IUser $user) { * @param string $challenge */ public function verifyChallenge(IUser $user, $challenge) { - return $this->totp->validateSecret($user, $challenge); + return $this->totp->validateKey($user, $challenge); } /** diff --git a/lib/Service/ITotp.php b/lib/Service/ITotp.php index e714a4d..582d0c5 100644 --- a/lib/Service/ITotp.php +++ b/lib/Service/ITotp.php @@ -22,6 +22,7 @@ namespace OCA\TwoFactor_Totp\Service; +use OCA\TwoFactor_Totp\Exception\NoTotpSecretFoundException; use OCA\TwoFactor_Totp\Exception\TotpSecretAlreadySet; use OCP\IUser; @@ -46,9 +47,11 @@ public function deleteSecret(IUser $user); /** * @param IUser $user - * @param string $key + * @param string $key 6 digits numeric time-based one time password. + * @return boolean If key is correct + * @throws NoTotpSecretFoundException */ - public function validateSecret(IUser $user, $key); + public function validateKey(IUser $user, $key); /** * @param IUser $user diff --git a/lib/Service/Totp.php b/lib/Service/Totp.php index 7b97678..e9f97bd 100644 --- a/lib/Service/Totp.php +++ b/lib/Service/Totp.php @@ -92,7 +92,7 @@ public function deleteSecret(IUser $user) { * @param string $key */ public function verifySecret(IUser $user, $key) { - if ($this->validateSecret($user, $key) === true) { + if ($this->validateKey($user, $key) === true) { $dbSecret = $this->secretMapper->getSecret($user); $dbSecret->setVerified(true); $this->secretMapper->update($dbSecret); @@ -104,8 +104,10 @@ public function verifySecret(IUser $user, $key) { /** * @param IUser $user * @param string $key + * @return boolean + * @throws NoTotpSecretFoundException */ - public function validateSecret(IUser $user, $key) { + public function validateKey(IUser $user, $key) { try { $dbSecret = $this->secretMapper->getSecret($user); } catch (DoesNotExistException $ex) { diff --git a/tests/unit/Controller/TotpApiControllerTest.php b/tests/unit/Controller/TotpApiControllerTest.php new file mode 100644 index 0000000..386d49c --- /dev/null +++ b/tests/unit/Controller/TotpApiControllerTest.php @@ -0,0 +1,138 @@ + + * + * Two-factor TOTP + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\TwoFactor_Totp\Unit\Controller; + +use OC\OCS\Result; +use OCA\TwoFactor_Totp\Controller\TotpApiController; +use OCA\TwoFactor_Totp\Exception\NoTotpSecretFoundException; +use OCA\TwoFactor_Totp\Service\ITotp; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\ILogger; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; +use Test\TestCase; +use PHPUnit_Framework_MockObject_MockObject; + +class TotpApiControllerTest extends TestCase { + + /** @var IRequest | PHPUnit_Framework_MockObject_MockObject */ + private $request; + + /** @var IUserManager | PHPUnit_Framework_MockObject_MockObject */ + private $userManager; + + /** @var IUser | PHPUnit_Framework_MockObject_MockObject */ + private $user; + + /** @var ITotp | PHPUnit_Framework_MockObject_MockObject */ + private $totp; + + /** @var ILogger | PHPUnit_Framework_MockObject_MockObject */ + private $logger; + + /** @var TotpApiController */ + private $controller; + + protected function setUp() { + parent::setUp(); + $this->user = $this->createMock(IUser::class); + $this->request = $this->createMock(IRequest::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->totp = $this->createMock(ITotp::class); + $this->logger = $this->createMock(ILogger::class); + + $this->controller = new TotpApiController( + 'twofactor_totp', + $this->request, $this->totp, + $this->userManager, + $this->logger + ); + } + + /** + * @dataProvider dataTestValidateKey + * + * @param string $uid + * @param boolean $result + */ + public function testValidateKey($uid, $result) { + $this->userManager->expects($this->once()) + ->method('get') + ->with($uid) + ->will($this->returnValue($this->user)); + $this->totp->expects($this->once()) + ->method('validateKey') + ->with($this->user, '111111') + ->will($this->returnValue($result)); + + $expected = new DataResponse(['data' => ['result' => $result]]); + $this->assertEquals($expected, $this->controller->validateKey($uid, '111111')); + } + + public function dataTestValidateKey() { + return [ + ['testuser', false], + ['testuser', true], + ]; + } + + public function testValidateKeyUserNotExist() { + $this->userManager->expects($this->once()) + ->method('get') + ->with('notexist') + ->will($this->returnValue(null)); + $expected = new DataResponse( + [ + 'statuscode' => 404, + 'data' => ['result' => false] + ], + Http::STATUS_NOT_FOUND + ); + $this->assertEquals($expected, $this->controller->validateKey('notexist', '111111')); + } + + public function testValidateKeySecretNotExist() { + $exception = new NoTotpSecretFoundException(); + $this->user = $this->createMock(IUser::class); + $this->userManager->expects($this->once()) + ->method('get') + ->with('testuser') + ->will($this->returnValue($this->user)); + $this->totp->expects($this->once()) + ->method('validateKey') + ->with($this->user, '111111') + ->will($this->throwException($exception)); + $this->logger->expects($this->once()) + ->method('logException') + ->with($exception); + $expected = new DataResponse( + [ + 'statuscode' => 404, + 'data' => ['result' => false] + ], + Http::STATUS_NOT_FOUND + ); + $this->assertEquals($expected, $this->controller->validateKey('testuser', '111111')); + } +} diff --git a/tests/unit/Provider/TotpProviderTest.php b/tests/unit/Provider/TotpProviderTest.php new file mode 100644 index 0000000..7d3a115 --- /dev/null +++ b/tests/unit/Provider/TotpProviderTest.php @@ -0,0 +1,62 @@ + + * + * Two-factor TOTP + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\TwoFactor_Totp\Tests\Provider; + +use OCA\TwoFactor_Totp\Provider\TotpProvider; +use OCA\TwoFactor_Totp\Service\ITotp; +use OCP\IL10N; +use OCP\IUser; +use Test\TestCase; + +/** + * Class TotpTest + */ +class TotpProviderTest extends TestCase { + + /** @var ITotp | \PHPUnit_Framework_MockObject_MockObject $totp */ + private $totp; + + /** @var IL10N | \PHPUnit_Framework_MockObject_MockObject */ + private $l; + + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject */ + private $user; + + /** @var TotpProvider $totpProvider */ + private $totpProvider; + + protected function setUp() { + parent::setUp(); + + $this->totp = $this->createMock(ITotp::class); + $this->l = $this->createMock(IL10N::class); + $this->user = $this->createMock(IUser::class); + + $this->totpProvider = new TotpProvider($this->totp, $this->l); + } + + public function testVerifyChallange() { + $this->totp->expects($this->once()) + ->method('validateKey') + ->with($this->user, '111111'); + $this->totpProvider->verifyChallenge($this->user, '111111'); + } +} diff --git a/tests/unit/Service/TotpTest.php b/tests/unit/Service/TotpTest.php index 381ecca..3b1b2c7 100644 --- a/tests/unit/Service/TotpTest.php +++ b/tests/unit/Service/TotpTest.php @@ -64,7 +64,7 @@ protected function setUp() { * @param boolean $validationResult * @param boolean $expectedResult */ - public function testValidateSecret($lastKey, $key, $validationResult, $expectedResult) { + public function testValidateKey($lastKey, $key, $validationResult, $expectedResult) { /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ $user = $this->createMock(IUser::class); $dbSecret = $this @@ -96,7 +96,7 @@ public function testValidateSecret($lastKey, $key, $validationResult, $expectedR ->method('update') ->with($dbSecret); } - $this->assertEquals($this->totp->validateSecret($user, $key), $expectedResult); + $this->assertEquals($this->totp->validateKey($user, $key), $expectedResult); } public function validationProvider() { @@ -117,6 +117,6 @@ public function testValidateSecretNoSecret() { ->with($user) ->will($this->throwException(new DoesNotExistException(''))); $this->expectException(NoTotpSecretFoundException::class); - $this->totp->validateSecret($user, 'testkey'); + $this->totp->validateKey($user, 'testkey'); } }