BackupCodeStorageTest.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\TwoFactorBackupCodes\Tests\Unit\Service;
  8. use OCA\TwoFactorBackupCodes\Db\BackupCode;
  9. use OCA\TwoFactorBackupCodes\Db\BackupCodeMapper;
  10. use OCA\TwoFactorBackupCodes\Event\CodesGenerated;
  11. use OCA\TwoFactorBackupCodes\Service\BackupCodeStorage;
  12. use OCP\EventDispatcher\IEventDispatcher;
  13. use OCP\IUser;
  14. use OCP\Security\IHasher;
  15. use OCP\Security\ISecureRandom;
  16. use Test\TestCase;
  17. class BackupCodeStorageTest extends TestCase {
  18. /** @var BackupCodeMapper|\PHPUnit\Framework\MockObject\MockObject */
  19. private $mapper;
  20. /** @var ISecureRandom|\PHPUnit\Framework\MockObject\MockObject */
  21. private $random;
  22. /** @var IHasher|\PHPUnit\Framework\MockObject\MockObject */
  23. private $hasher;
  24. /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
  25. private $eventDispatcher;
  26. /** @var BackupCodeStorage */
  27. private $storage;
  28. protected function setUp(): void {
  29. parent::setUp();
  30. $this->mapper = $this->createMock(BackupCodeMapper::class);
  31. $this->random = $this->createMock(ISecureRandom::class);
  32. $this->hasher = $this->createMock(IHasher::class);
  33. $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
  34. $this->storage = new BackupCodeStorage($this->mapper, $this->random, $this->hasher, $this->eventDispatcher);
  35. }
  36. public function testCreateCodes() {
  37. $user = $this->createMock(IUser::class);
  38. $number = 5;
  39. $user->method('getUID')->willReturn('fritz');
  40. $this->random->expects($this->exactly($number))
  41. ->method('generate')
  42. ->with(16, ISecureRandom::CHAR_HUMAN_READABLE)
  43. ->willReturn('CODEABCDEF');
  44. $this->hasher->expects($this->exactly($number))
  45. ->method('hash')
  46. ->with('CODEABCDEF')
  47. ->willReturn('HASHEDCODE');
  48. $row = new BackupCode();
  49. $row->setUserId('fritz');
  50. $row->setCode('HASHEDCODE');
  51. $row->setUsed(0);
  52. $this->mapper->expects($this->exactly($number))
  53. ->method('insert')
  54. ->with($this->equalTo($row));
  55. $this->eventDispatcher->expects($this->once())
  56. ->method('dispatchTyped')
  57. ->with(
  58. $this->equalTo(new CodesGenerated($user))
  59. );
  60. $codes = $this->storage->createCodes($user, $number);
  61. $this->assertCount($number, $codes);
  62. foreach ($codes as $code) {
  63. $this->assertEquals('CODEABCDEF', $code);
  64. }
  65. }
  66. public function testHasBackupCodes() {
  67. $user = $this->createMock(IUser::class);
  68. $codes = [
  69. new BackupCode(),
  70. new BackupCode(),
  71. ];
  72. $this->mapper->expects($this->once())
  73. ->method('getBackupCodes')
  74. ->with($user)
  75. ->willReturn($codes);
  76. $this->assertTrue($this->storage->hasBackupCodes($user));
  77. }
  78. public function testHasBackupCodesNoCodes() {
  79. $user = $this->createMock(IUser::class);
  80. $codes = [];
  81. $this->mapper->expects($this->once())
  82. ->method('getBackupCodes')
  83. ->with($user)
  84. ->willReturn($codes);
  85. $this->assertFalse($this->storage->hasBackupCodes($user));
  86. }
  87. public function testGetBackupCodeState() {
  88. $user = $this->createMock(IUser::class);
  89. $code1 = new BackupCode();
  90. $code1->setUsed(1);
  91. $code2 = new BackupCode();
  92. $code2->setUsed('0');
  93. $codes = [
  94. $code1,
  95. $code2,
  96. ];
  97. $this->mapper->expects($this->once())
  98. ->method('getBackupCodes')
  99. ->with($user)
  100. ->willReturn($codes);
  101. $expected = [
  102. 'enabled' => true,
  103. 'total' => 2,
  104. 'used' => 1,
  105. ];
  106. $this->assertEquals($expected, $this->storage->getBackupCodesState($user));
  107. }
  108. public function testGetBackupCodeDisabled() {
  109. $user = $this->createMock(IUser::class);
  110. $codes = [];
  111. $this->mapper->expects($this->once())
  112. ->method('getBackupCodes')
  113. ->with($user)
  114. ->willReturn($codes);
  115. $expected = [
  116. 'enabled' => false,
  117. 'total' => 0,
  118. 'used' => 0,
  119. ];
  120. $this->assertEquals($expected, $this->storage->getBackupCodesState($user));
  121. }
  122. public function testValidateCode() {
  123. $user = $this->createMock(IUser::class);
  124. $code = new BackupCode();
  125. $code->setUsed(0);
  126. $code->setCode('HASHEDVALUE');
  127. $codes = [
  128. $code,
  129. ];
  130. $this->mapper->expects($this->once())
  131. ->method('getBackupCodes')
  132. ->with($user)
  133. ->willReturn($codes);
  134. $this->hasher->expects($this->once())
  135. ->method('verify')
  136. ->with('CHALLENGE', 'HASHEDVALUE', $this->anything())
  137. ->willReturn(true);
  138. $this->mapper->expects($this->once())
  139. ->method('update')
  140. ->with($code);
  141. $this->assertTrue($this->storage->validateCode($user, 'CHALLENGE'));
  142. $this->assertEquals(1, $code->getUsed());
  143. }
  144. public function testValidateUsedCode() {
  145. $user = $this->createMock(IUser::class);
  146. $code = new BackupCode();
  147. $code->setUsed('1');
  148. $code->setCode('HASHEDVALUE');
  149. $codes = [
  150. $code,
  151. ];
  152. $this->mapper->expects($this->once())
  153. ->method('getBackupCodes')
  154. ->with($user)
  155. ->willReturn($codes);
  156. $this->hasher->expects($this->never())
  157. ->method('verify');
  158. $this->mapper->expects($this->never())
  159. ->method('update');
  160. $this->assertFalse($this->storage->validateCode($user, 'CHALLENGE'));
  161. }
  162. public function testValidateCodeWithWrongHash() {
  163. $user = $this->createMock(IUser::class);
  164. $code = new BackupCode();
  165. $code->setUsed(0);
  166. $code->setCode('HASHEDVALUE');
  167. $codes = [
  168. $code,
  169. ];
  170. $this->mapper->expects($this->once())
  171. ->method('getBackupCodes')
  172. ->with($user)
  173. ->willReturn($codes);
  174. $this->hasher->expects($this->once())
  175. ->method('verify')
  176. ->with('CHALLENGE', 'HASHEDVALUE')
  177. ->willReturn(false);
  178. $this->mapper->expects($this->never())
  179. ->method('update');
  180. $this->assertFalse($this->storage->validateCode($user, 'CHALLENGE'));
  181. }
  182. public function testDeleteCodes(): void {
  183. $user = $this->createMock(IUser::class);
  184. $this->mapper->expects($this->once())
  185. ->method('deleteCodes')
  186. ->with($user);
  187. $this->storage->deleteCodes($user);
  188. }
  189. }