You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BackupCodeStorage.php 3.4KB

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