123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- <?php
- declare(strict_types=1);
- /**
- * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
- namespace OC\Security\Bruteforce\Backend;
- use OCP\AppFramework\Utility\ITimeFactory;
- use OCP\ICache;
- use OCP\ICacheFactory;
- class MemoryCacheBackend implements IBackend {
- private ICache $cache;
- public function __construct(
- ICacheFactory $cacheFactory,
- private ITimeFactory $timeFactory,
- ) {
- $this->cache = $cacheFactory->createDistributed(self::class);
- }
- private function hash(
- null|string|array $data,
- ): ?string {
- if ($data === null) {
- return null;
- }
- if (!is_string($data)) {
- $data = json_encode($data);
- }
- return hash('sha1', $data);
- }
- private function getExistingAttempts(string $identifier): array {
- $cachedAttempts = $this->cache->get($identifier);
- if ($cachedAttempts === null) {
- return [];
- }
- $cachedAttempts = json_decode($cachedAttempts, true);
- if (\is_array($cachedAttempts)) {
- return $cachedAttempts;
- }
- return [];
- }
- /**
- * {@inheritDoc}
- */
- public function getAttempts(
- string $ipSubnet,
- int $maxAgeTimestamp,
- ?string $action = null,
- ?array $metadata = null,
- ): int {
- $identifier = $this->hash($ipSubnet);
- $actionHash = $this->hash($action);
- $metadataHash = $this->hash($metadata);
- $existingAttempts = $this->getExistingAttempts($identifier);
- $count = 0;
- foreach ($existingAttempts as $info) {
- [$occurredTime, $attemptAction, $attemptMetadata] = explode('#', $info, 3);
- if ($action === null || $attemptAction === $actionHash) {
- if ($metadata === null || $attemptMetadata === $metadataHash) {
- if ($occurredTime > $maxAgeTimestamp) {
- $count++;
- }
- }
- }
- }
- return $count;
- }
- /**
- * {@inheritDoc}
- */
- public function resetAttempts(
- string $ipSubnet,
- ?string $action = null,
- ?array $metadata = null,
- ): void {
- $identifier = $this->hash($ipSubnet);
- if ($action === null) {
- $this->cache->remove($identifier);
- } else {
- $actionHash = $this->hash($action);
- $metadataHash = $this->hash($metadata);
- $existingAttempts = $this->getExistingAttempts($identifier);
- $maxAgeTimestamp = $this->timeFactory->getTime() - 12 * 3600;
- foreach ($existingAttempts as $key => $info) {
- [$occurredTime, $attemptAction, $attemptMetadata] = explode('#', $info, 3);
- if ($attemptAction === $actionHash) {
- if ($metadata === null || $attemptMetadata === $metadataHash) {
- unset($existingAttempts[$key]);
- } elseif ($occurredTime < $maxAgeTimestamp) {
- unset($existingAttempts[$key]);
- }
- }
- }
- if (!empty($existingAttempts)) {
- $this->cache->set($identifier, json_encode($existingAttempts), 12 * 3600);
- } else {
- $this->cache->remove($identifier);
- }
- }
- }
- /**
- * {@inheritDoc}
- */
- public function registerAttempt(
- string $ip,
- string $ipSubnet,
- int $timestamp,
- string $action,
- array $metadata = [],
- ): void {
- $identifier = $this->hash($ipSubnet);
- $existingAttempts = $this->getExistingAttempts($identifier);
- $maxAgeTimestamp = $this->timeFactory->getTime() - 12 * 3600;
- // Unset all attempts that are already expired
- foreach ($existingAttempts as $key => $info) {
- [$occurredTime,] = explode('#', $info, 3);
- if ($occurredTime < $maxAgeTimestamp) {
- unset($existingAttempts[$key]);
- }
- }
- $existingAttempts = array_values($existingAttempts);
- // Store the new attempt
- $existingAttempts[] = $timestamp . '#' . $this->hash($action) . '#' . $this->hash($metadata);
- $this->cache->set($identifier, json_encode($existingAttempts), 12 * 3600);
- }
- }
|