Skip to content

Commit 3339d05

Browse files
authored
Merge pull request #107 from utopia-php/copilot/add-reset-method-to-abuse-interface
Add reset() method to Abuse interface and adapters
2 parents 36edca6 + 7bece2f commit 3339d05

11 files changed

Lines changed: 481 additions & 202 deletions

File tree

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"ext-curl": "*",
2424
"ext-redis": "*",
2525
"utopia-php/database": "3.*.*",
26-
"appwrite/appwrite": "18.*.*"
26+
"appwrite/appwrite": "19.*.*"
2727
},
2828
"require-dev": {
2929
"phpunit/phpunit": "9.*",

composer.lock

Lines changed: 241 additions & 201 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Abuse/Abuse.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,16 @@ public function cleanup(int $timestamp): bool
5353
{
5454
return $this->adapter->cleanup($timestamp);
5555
}
56+
57+
/**
58+
* Reset
59+
*
60+
* Reset the count to 0 for the current adapter
61+
*
62+
* @return void
63+
*/
64+
public function reset(): void
65+
{
66+
$this->adapter->reset();
67+
}
5668
}

src/Abuse/Adapter.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,13 @@ abstract public function getLogs(?int $offset = null, ?int $limit = 25): array;
8383
* @return bool
8484
*/
8585
abstract public function cleanup(int $timestamp): bool;
86+
87+
/**
88+
* Reset
89+
*
90+
* Reset the count to 0
91+
*
92+
* @return void
93+
*/
94+
abstract public function reset(): void;
8695
}

src/Abuse/Adapters/ReCaptcha.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,18 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array
117117
{
118118
throw new Exception('Method not supported');
119119
}
120+
121+
/**
122+
* Reset
123+
*
124+
* Reset is not applicable for ReCaptcha adapter
125+
*
126+
* @return void
127+
*
128+
* @throws Exception
129+
*/
130+
public function reset(): void
131+
{
132+
throw new Exception('Method not supported');
133+
}
120134
}

src/Abuse/Adapters/TimeLimit.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ abstract protected function count(string $key, int $timestamp): int;
3737

3838
abstract protected function hit(string $key, int $timestamp): void;
3939

40+
abstract protected function set(string $key, int $timestamp, int $value): void;
41+
4042
/**
4143
* Check
4244
*
@@ -102,4 +104,18 @@ public function time(): int
102104
{
103105
return $this->timestamp;
104106
}
107+
108+
/**
109+
* Reset
110+
*
111+
* Reset the count to 0 for the current key and timestamp
112+
*
113+
* @return void
114+
*
115+
* @throws \Exception
116+
*/
117+
public function reset(): void
118+
{
119+
$this->set($this->parseKey(), $this->timestamp, 0);
120+
}
105121
}

src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,60 @@ protected function hit(string $key, int $timestamp): void
250250
$this->count++;
251251
}
252252

253+
/**
254+
* @param string $key
255+
* @param int $timestamp
256+
* @param int $value
257+
* @return void
258+
*
259+
* @throws \Throwable
260+
*/
261+
protected function set(string $key, int $timestamp, int $value): void
262+
{
263+
$timestamp = $this->toDateTime($timestamp);
264+
265+
$response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [
266+
Query::equal('key', [$key]),
267+
Query::equal('time', [$timestamp]),
268+
]);
269+
$rows = $response['rows'];
270+
$data = $rows[0] ?? null;
271+
272+
if (\is_null($data)) {
273+
$data = [
274+
'key' => $key,
275+
'time' => $timestamp,
276+
'count' => $value,
277+
];
278+
279+
try {
280+
$this->tablesDB->createRow($this->databaseId, self::TABLE_ID, ID::unique(), $data);
281+
} catch (AppwriteException $err) {
282+
if ($err->getType() !== 'row_already_exists') {
283+
throw $err;
284+
}
285+
286+
$response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [
287+
Query::equal('key', [$key]),
288+
Query::equal('time', [$timestamp]),
289+
]);
290+
$rows = $response['rows'];
291+
292+
$data = $rows[0] ?? null;
293+
294+
if (!is_null($data)) {
295+
$this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]);
296+
} else {
297+
throw new \Exception('Unable to find abuse tracking row after race condition handling');
298+
}
299+
}
300+
} else {
301+
$this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]);
302+
}
303+
304+
$this->count = $value;
305+
}
306+
253307
/**
254308
* Get abuse logs
255309
*

src/Abuse/Adapters/TimeLimit/Database.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,61 @@ protected function hit(string $key, int $timestamp): void
215215
$this->count++;
216216
}
217217

218+
/**
219+
* @param string $key
220+
* @param int $timestamp
221+
* @param int $value
222+
* @return void
223+
*
224+
* @throws AuthorizationException|Structure|\Exception|\Throwable
225+
*/
226+
protected function set(string $key, int $timestamp, int $value): void
227+
{
228+
$timestamp = $this->toDateTime($timestamp);
229+
Authorization::skip(function () use ($timestamp, $key, $value) {
230+
$data = $this->db->findOne(self::COLLECTION, [
231+
Query::equal('key', [$key]),
232+
Query::equal('time', [$timestamp]),
233+
]);
234+
235+
if ($data->isEmpty()) {
236+
$data = [
237+
'$permissions' => [],
238+
'key' => $key,
239+
'time' => $timestamp,
240+
'count' => $value,
241+
'$collection' => self::COLLECTION,
242+
];
243+
244+
try {
245+
$this->db->createDocument(self::COLLECTION, new Document($data));
246+
} catch (Duplicate $e) {
247+
// Duplicate in case of race condition - update existing document
248+
$data = $this->db->findOne(self::COLLECTION, [
249+
Query::equal('key', [$key]),
250+
Query::equal('time', [$timestamp]),
251+
]);
252+
253+
if (!$data->isEmpty()) {
254+
/** @var Document $data */
255+
$this->db->updateDocument(self::COLLECTION, $data->getId(), new Document([
256+
'count' => $value,
257+
]));
258+
} else {
259+
throw new \Exception('Unable to find abuse tracking document after race condition handling');
260+
}
261+
}
262+
} else {
263+
/** @var Document $data */
264+
$this->db->updateDocument(self::COLLECTION, $data->getId(), new Document([
265+
'count' => $value,
266+
]));
267+
}
268+
});
269+
270+
$this->count = $value;
271+
}
272+
218273
/**
219274
* Get abuse logs
220275
*

src/Abuse/Adapters/TimeLimit/Redis.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ protected function hit(string $key, int $timestamp): void
7777
$this->count = ($this->count ?? 0) + 1;
7878
}
7979

80+
/**
81+
* Set count for a key at specific timestamp
82+
*
83+
* @param string $key
84+
* @param int $timestamp
85+
* @param int $value
86+
* @return void
87+
*/
88+
protected function set(string $key, int $timestamp, int $value): void
89+
{
90+
$key = self::NAMESPACE . '__' . $key . '__' . $timestamp;
91+
$this->redis->multi()
92+
->set($key, (string)$value)
93+
->expire($key, $this->ttl)
94+
->exec();
95+
96+
$this->count = $value;
97+
}
98+
8099
/**
81100
* Get abuse logs
82101
*

src/Abuse/Adapters/TimeLimit/RedisCluster.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@ protected function hit(string $key, int $timestamp): void
7979
$this->count = ($this->count ?? 0) + 1;
8080
}
8181

82+
/**
83+
* Set count for a key at specific timestamp
84+
*
85+
* @param string $key
86+
* @param int $timestamp
87+
* @param int $value
88+
* @return void
89+
*/
90+
protected function set(string $key, int $timestamp, int $value): void
91+
{
92+
93+
$key = self::NAMESPACE . '__' . $key . '__' . $timestamp;
94+
95+
$this->redis->multi();
96+
$this->redis->set($key, (string)$value);
97+
$this->redis->expire($key, $this->ttl);
98+
$this->redis->exec();
99+
100+
$this->count = $value;
101+
}
102+
82103
/**
83104
* Get abuse logs with proper cursor-based pagination
84105
*

0 commit comments

Comments
 (0)