BackupCodeStorageTest.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at>
  5. *
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Morris Jobke <hey@morrisjobke.de>
  8. * @author Roeland Jago Douma <roeland@famdouma.nl>
  9. *
  10. * @license GNU AGPL version 3 or any later version
  11. *
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License as
  14. * published by the Free Software Foundation, either version 3 of the
  15. * License, or (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. *
  25. */
  26. namespace OCA\TwoFactorBackupCodes\Tests\Unit\Service;
  27. use OCA\TwoFactorBackupCodes\Db\BackupCode;
  28. use OCA\TwoFactorBackupCodes\Db\BackupCodeMapper;
  29. use OCA\TwoFactorBackupCodes\Event\CodesGenerated;
  30. use OCA\TwoFactorBackupCodes\Service\BackupCodeStorage;
  31. use OCP\EventDispatcher\IEventDispatcher;
  32. use OCP\IUser;
  33. use OCP\Security\IHasher;
  34. use OCP\Security\ISecureRandom;
  35. use Test\TestCase;
  36. class BackupCodeStorageTest extends TestCase {
  37. /** @var BackupCodeMapper|\PHPUnit\Framework\MockObject\MockObject */
  38. private $mapper;
  39. /** @var ISecureRandom|\PHPUnit\Framework\MockObject\MockObject */
  40. private $random;
  41. /** @var IHasher|\PHPUnit\Framework\MockObject\MockObject */
  42. private $hasher;
  43. /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
  44. private $eventDispatcher;
  45. /** @var BackupCodeStorage */
  46. private $storage;
  47. protected function setUp(): void {
  48. parent::setUp();
  49. $this->mapper = $this->createMock(BackupCodeMapper::class);
  50. $this->random = $this->createMock(ISecureRandom::class);
  51. $this->hasher = $this->createMock(IHasher::class);
  52. $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
  53. $this->storage = new BackupCodeStorage($this->mapper, $this->random, $this->hasher, $this->eventDispatcher);
  54. }
  55. public function testCreateCodes() {
  56. $user = $this->createMock(IUser::class);
  57. $number = 5;
  58. $user->method('getUID')->willReturn('fritz');
  59. $this->random->expects($this->exactly($number))
  60. ->method('generate')
  61. ->with(16, ISecureRandom::CHAR_HUMAN_READABLE)
  62. ->willReturn('CODEABCDEF');
  63. $this->hasher->expects($this->exactly($number))
  64. ->method('hash')
  65. ->with('CODEABCDEF')
  66. ->willReturn('HASHEDCODE');
  67. $row = new BackupCode();
  68. $row->setUserId('fritz');
  69. $row->setCode('HASHEDCODE');
  70. $row->setUsed(0);
  71. $this->mapper->expects($this->exactly($number))
  72. ->method('insert')
  73. ->with($this->equalTo($row));
  74. $this->eventDispatcher->expects($this->once())
  75. ->method('dispatchTyped')
  76. ->with(
  77. $this->equalTo(new CodesGenerated($user))
  78. );
  79. $codes = $this->storage->createCodes($user, $number);
  80. $this->assertCount($number, $codes);
  81. foreach ($codes as $code) {
  82. $this->assertEquals('CODEABCDEF', $code);
  83. }
  84. }
  85. public function testHasBackupCodes() {
  86. $user = $this->createMock(IUser::class);
  87. $codes = [
  88. new BackupCode(),
  89. new BackupCode(),
  90. ];
  91. $this->mapper->expects($this->once())
  92. ->method('getBackupCodes')
  93. ->with($user)
  94. ->willReturn($codes);
  95. $this->assertTrue($this->storage->hasBackupCodes($user));
  96. }
  97. public function testHasBackupCodesNoCodes() {
  98. $user = $this->createMock(IUser::class);
  99. $codes = [];
  100. $this->mapper->expects($this->once())
  101. ->method('getBackupCodes')
  102. ->with($user)
  103. ->willReturn($codes);
  104. $this->assertFalse($this->storage->hasBackupCodes($user));
  105. }
  106. public function testGetBackupCodeState() {
  107. $user = $this->createMock(IUser::class);
  108. $code1 = new BackupCode();
  109. $code1->setUsed(1);
  110. $code2 = new BackupCode();
  111. $code2->setUsed('0');
  112. $codes = [
  113. $code1,
  114. $code2,
  115. ];
  116. $this->mapper->expects($this->once())
  117. ->method('getBackupCodes')
  118. ->with($user)
  119. ->willReturn($codes);
  120. $expected = [
  121. 'enabled' => true,
  122. 'total' => 2,
  123. 'used' => 1,
  124. ];
  125. $this->assertEquals($expected, $this->storage->getBackupCodesState($user));
  126. }
  127. public function testGetBackupCodeDisabled() {
  128. $user = $this->createMock(IUser::class);
  129. $codes = [];
  130. $this->mapper->expects($this->once())
  131. ->method('getBackupCodes')
  132. ->with($user)
  133. ->willReturn($codes);
  134. $expected = [
  135. 'enabled' => false,
  136. 'total' => 0,
  137. 'used' => 0,
  138. ];
  139. $this->assertEquals($expected, $this->storage->getBackupCodesState($user));
  140. }
  141. public function testValidateCode() {
  142. $user = $this->createMock(IUser::class);
  143. $code = new BackupCode();
  144. $code->setUsed(0);
  145. $code->setCode('HASHEDVALUE');
  146. $codes = [
  147. $code,
  148. ];
  149. $this->mapper->expects($this->once())
  150. ->method('getBackupCodes')
  151. ->with($user)
  152. ->willReturn($codes);
  153. $this->hasher->expects($this->once())
  154. ->method('verify')
  155. ->with('CHALLENGE', 'HASHEDVALUE', $this->anything())
  156. ->willReturn(true);
  157. $this->mapper->expects($this->once())
  158. ->method('update')
  159. ->with($code);
  160. $this->assertTrue($this->storage->validateCode($user, 'CHALLENGE'));
  161. $this->assertEquals(1, $code->getUsed());
  162. }
  163. public function testValidateUsedCode() {
  164. $user = $this->createMock(IUser::class);
  165. $code = new BackupCode();
  166. $code->setUsed('1');
  167. $code->setCode('HASHEDVALUE');
  168. $codes = [
  169. $code,
  170. ];
  171. $this->mapper->expects($this->once())
  172. ->method('getBackupCodes')
  173. ->with($user)
  174. ->willReturn($codes);
  175. $this->hasher->expects($this->never())
  176. ->method('verify');
  177. $this->mapper->expects($this->never())
  178. ->method('update');
  179. $this->assertFalse($this->storage->validateCode($user, 'CHALLENGE'));
  180. }
  181. public function testValidateCodeWithWrongHash() {
  182. $user = $this->createMock(IUser::class);
  183. $code = new BackupCode();
  184. $code->setUsed(0);
  185. $code->setCode('HASHEDVALUE');
  186. $codes = [
  187. $code,
  188. ];
  189. $this->mapper->expects($this->once())
  190. ->method('getBackupCodes')
  191. ->with($user)
  192. ->willReturn($codes);
  193. $this->hasher->expects($this->once())
  194. ->method('verify')
  195. ->with('CHALLENGE', 'HASHEDVALUE')
  196. ->willReturn(false);
  197. $this->mapper->expects($this->never())
  198. ->method('update');
  199. $this->assertFalse($this->storage->validateCode($user, 'CHALLENGE'));
  200. }
  201. public function testDeleteCodes(): void {
  202. $user = $this->createMock(IUser::class);
  203. $this->mapper->expects($this->once())
  204. ->method('deleteCodes')
  205. ->with($user);
  206. $this->storage->deleteCodes($user);
  207. }
  208. }