MemoryCacheBackend.php 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2017 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\ICache;
  10. use OCP\ICacheFactory;
  11. use OCP\IConfig;
  12. /**
  13. * Class MemoryCacheBackend uses the configured distributed memory cache for storing
  14. * rate limiting data.
  15. *
  16. * @package OC\Security\RateLimiting\Backend
  17. */
  18. class MemoryCacheBackend implements IBackend {
  19. private ICache $cache;
  20. public function __construct(
  21. private IConfig $config,
  22. ICacheFactory $cacheFactory,
  23. private ITimeFactory $timeFactory,
  24. ) {
  25. $this->cache = $cacheFactory->createDistributed(__CLASS__);
  26. }
  27. private function hash(
  28. string $methodIdentifier,
  29. string $userIdentifier,
  30. ): string {
  31. return hash('sha512', $methodIdentifier . $userIdentifier);
  32. }
  33. private function getExistingAttempts(string $identifier): array {
  34. $cachedAttempts = $this->cache->get($identifier);
  35. if ($cachedAttempts === null) {
  36. return [];
  37. }
  38. $cachedAttempts = json_decode($cachedAttempts, true);
  39. if (\is_array($cachedAttempts)) {
  40. return $cachedAttempts;
  41. }
  42. return [];
  43. }
  44. /**
  45. * {@inheritDoc}
  46. */
  47. public function getAttempts(
  48. string $methodIdentifier,
  49. string $userIdentifier,
  50. ): int {
  51. $identifier = $this->hash($methodIdentifier, $userIdentifier);
  52. $existingAttempts = $this->getExistingAttempts($identifier);
  53. $count = 0;
  54. $currentTime = $this->timeFactory->getTime();
  55. foreach ($existingAttempts as $expirationTime) {
  56. if ($expirationTime > $currentTime) {
  57. $count++;
  58. }
  59. }
  60. return $count;
  61. }
  62. /**
  63. * {@inheritDoc}
  64. */
  65. public function registerAttempt(
  66. string $methodIdentifier,
  67. string $userIdentifier,
  68. int $period,
  69. ): void {
  70. $identifier = $this->hash($methodIdentifier, $userIdentifier);
  71. $existingAttempts = $this->getExistingAttempts($identifier);
  72. $currentTime = $this->timeFactory->getTime();
  73. // Unset all attempts that are already expired
  74. foreach ($existingAttempts as $key => $expirationTime) {
  75. if ($expirationTime < $currentTime) {
  76. unset($existingAttempts[$key]);
  77. }
  78. }
  79. $existingAttempts = array_values($existingAttempts);
  80. // Store the new attempt
  81. $existingAttempts[] = (string)($currentTime + $period);
  82. if (!$this->config->getSystemValueBool('ratelimit.protection.enabled', true)) {
  83. return;
  84. }
  85. $this->cache->set($identifier, json_encode($existingAttempts));
  86. }
  87. }