BackupCodeStorage.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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 J0WI <J0WI@users.noreply.github.com>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Roeland Jago Douma <roeland@famdouma.nl>
  10. *
  11. * @license GNU AGPL version 3 or any later version
  12. *
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU Affero General Public License as
  15. * published by the Free Software Foundation, either version 3 of the
  16. * License, or (at your option) any later version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU Affero General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Affero General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. *
  26. */
  27. namespace OCA\TwoFactorBackupCodes\Service;
  28. use OCA\TwoFactorBackupCodes\Db\BackupCode;
  29. use OCA\TwoFactorBackupCodes\Db\BackupCodeMapper;
  30. use OCA\TwoFactorBackupCodes\Event\CodesGenerated;
  31. use OCP\EventDispatcher\IEventDispatcher;
  32. use OCP\IUser;
  33. use OCP\Security\IHasher;
  34. use OCP\Security\ISecureRandom;
  35. class BackupCodeStorage {
  36. private static $CODE_LENGTH = 16;
  37. /** @var BackupCodeMapper */
  38. private $mapper;
  39. /** @var IHasher */
  40. private $hasher;
  41. /** @var ISecureRandom */
  42. private $random;
  43. /** @var IEventDispatcher */
  44. private $eventDispatcher;
  45. public function __construct(BackupCodeMapper $mapper,
  46. ISecureRandom $random,
  47. IHasher $hasher,
  48. IEventDispatcher $eventDispatcher) {
  49. $this->mapper = $mapper;
  50. $this->hasher = $hasher;
  51. $this->random = $random;
  52. $this->eventDispatcher = $eventDispatcher;
  53. }
  54. /**
  55. * @param IUser $user
  56. * @param int $number
  57. * @return string[]
  58. */
  59. public function createCodes(IUser $user, int $number = 10): array {
  60. $result = [];
  61. // Delete existing ones
  62. $this->mapper->deleteCodes($user);
  63. $uid = $user->getUID();
  64. foreach (range(1, min([$number, 20])) as $i) {
  65. $code = $this->random->generate(self::$CODE_LENGTH, ISecureRandom::CHAR_HUMAN_READABLE);
  66. $dbCode = new BackupCode();
  67. $dbCode->setUserId($uid);
  68. $dbCode->setCode($this->hasher->hash($code));
  69. $dbCode->setUsed(0);
  70. $this->mapper->insert($dbCode);
  71. $result[] = $code;
  72. }
  73. $this->eventDispatcher->dispatchTyped(new CodesGenerated($user));
  74. return $result;
  75. }
  76. /**
  77. * @param IUser $user
  78. * @return bool
  79. */
  80. public function hasBackupCodes(IUser $user): bool {
  81. $codes = $this->mapper->getBackupCodes($user);
  82. return count($codes) > 0;
  83. }
  84. /**
  85. * @param IUser $user
  86. * @return array
  87. */
  88. public function getBackupCodesState(IUser $user): array {
  89. $codes = $this->mapper->getBackupCodes($user);
  90. $total = count($codes);
  91. $used = 0;
  92. array_walk($codes, function (BackupCode $code) use (&$used) {
  93. if (1 === (int)$code->getUsed()) {
  94. $used++;
  95. }
  96. });
  97. return [
  98. 'enabled' => $total > 0,
  99. 'total' => $total,
  100. 'used' => $used,
  101. ];
  102. }
  103. /**
  104. * @param IUser $user
  105. * @param string $code
  106. * @return bool
  107. */
  108. public function validateCode(IUser $user, string $code): bool {
  109. $dbCodes = $this->mapper->getBackupCodes($user);
  110. foreach ($dbCodes as $dbCode) {
  111. if (0 === (int)$dbCode->getUsed() && $this->hasher->verify($code, $dbCode->getCode())) {
  112. $dbCode->setUsed(1);
  113. $this->mapper->update($dbCode);
  114. return true;
  115. }
  116. }
  117. return false;
  118. }
  119. public function deleteCodes(IUser $user): void {
  120. $this->mapper->deleteCodes($user);
  121. }
  122. }