DatabaseBackend.php 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\Security\RateLimiting\Backend;
  8. use OCP\AppFramework\Utility\ITimeFactory;
  9. use OCP\DB\Exception;
  10. use OCP\DB\QueryBuilder\IQueryBuilder;
  11. use OCP\IConfig;
  12. use OCP\IDBConnection;
  13. class DatabaseBackend implements IBackend {
  14. private const TABLE_NAME = 'ratelimit_entries';
  15. public function __construct(
  16. private IConfig $config,
  17. private IDBConnection $dbConnection,
  18. private ITimeFactory $timeFactory,
  19. ) {
  20. }
  21. private function hash(
  22. string $methodIdentifier,
  23. string $userIdentifier,
  24. ): string {
  25. return hash('sha512', $methodIdentifier . $userIdentifier);
  26. }
  27. /**
  28. * @throws Exception
  29. */
  30. private function getExistingAttemptCount(
  31. string $identifier,
  32. ): int {
  33. $currentTime = $this->timeFactory->getDateTime();
  34. $qb = $this->dbConnection->getQueryBuilder();
  35. $qb->delete(self::TABLE_NAME)
  36. ->where(
  37. $qb->expr()->lte('delete_after', $qb->createNamedParameter($currentTime, IQueryBuilder::PARAM_DATETIME_MUTABLE))
  38. )
  39. ->executeStatement();
  40. $qb = $this->dbConnection->getQueryBuilder();
  41. $qb->select($qb->func()->count())
  42. ->from(self::TABLE_NAME)
  43. ->where(
  44. $qb->expr()->eq('hash', $qb->createNamedParameter($identifier, IQueryBuilder::PARAM_STR))
  45. );
  46. $cursor = $qb->executeQuery();
  47. $row = $cursor->fetchOne();
  48. $cursor->closeCursor();
  49. return (int)$row;
  50. }
  51. /**
  52. * {@inheritDoc}
  53. */
  54. public function getAttempts(
  55. string $methodIdentifier,
  56. string $userIdentifier,
  57. ): int {
  58. $identifier = $this->hash($methodIdentifier, $userIdentifier);
  59. return $this->getExistingAttemptCount($identifier);
  60. }
  61. /**
  62. * {@inheritDoc}
  63. */
  64. public function registerAttempt(
  65. string $methodIdentifier,
  66. string $userIdentifier,
  67. int $period,
  68. ): void {
  69. $identifier = $this->hash($methodIdentifier, $userIdentifier);
  70. $deleteAfter = $this->timeFactory->getDateTime()->add(new \DateInterval("PT{$period}S"));
  71. $qb = $this->dbConnection->getQueryBuilder();
  72. $qb->insert(self::TABLE_NAME)
  73. ->values([
  74. 'hash' => $qb->createNamedParameter($identifier, IQueryBuilder::PARAM_STR),
  75. 'delete_after' => $qb->createNamedParameter($deleteAfter, IQueryBuilder::PARAM_DATETIME_MUTABLE),
  76. ]);
  77. if (!$this->config->getSystemValueBool('ratelimit.protection.enabled', true)) {
  78. return;
  79. }
  80. $qb->executeStatement();
  81. }
  82. }