-
-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WPK-8, #339 - tweak IP rate limit checks
* use a more Symfony-standard way to determine forwarded IPs * move request table check/update logic to a repository helper * use QB + ORM there as appropriate for cleaner code & fewer DB vendor hacks
- Loading branch information
Showing
4 changed files
with
107 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php | ||
|
||
namespace Outlandish\Wpackagist\Entity; | ||
|
||
use Doctrine\ORM\EntityRepository; | ||
use Doctrine\ORM\QueryBuilder; | ||
|
||
class RequestRepository extends EntityRepository | ||
{ | ||
/** | ||
* Get a count of sensitive requests for an IP, incrementing the counter as a side effect. | ||
* | ||
* @param string $ip | ||
* @return int The number of requests within the past 24 hours | ||
* @throws \Doctrine\DBAL\DBALException | ||
*/ | ||
public function getRequestCountByIp(string $ip): int | ||
{ | ||
$em = $this->getEntityManager(); | ||
$qb = new QueryBuilder($em); | ||
|
||
$oneHourAgo = (new \DateTime())->sub(new \DateInterval('PT1H')); | ||
|
||
$qb->select('r') | ||
->from(Request::class, 'r') | ||
->where('r.ipAddress = :ip') | ||
->andWhere('r.lastRequest >= :cutoff') | ||
->setParameter('ip', $ip) | ||
->setParameter('cutoff', $oneHourAgo); | ||
$requestHistory = $qb->getQuery()->getResult(); | ||
|
||
if (empty($requestHistory)) { | ||
$this->resetRequestCount($ip, $oneHourAgo); | ||
return 1; | ||
} | ||
|
||
/** @var Request $requestItem */ | ||
$requestItem = $requestHistory[0]; | ||
$requestItem->addRequest(); | ||
$em->persist($requestItem); | ||
|
||
return $requestItem->getRequestCount(); | ||
} | ||
|
||
/** | ||
* Add an entry to the requests table for the provided IP address. | ||
* Has the side effect of removing all expired entries. | ||
* | ||
* @param string $ip | ||
* @param \DateTime $cutoff | ||
*/ | ||
private function resetRequestCount(string $ip, \DateTime $cutoff) | ||
{ | ||
// Prune any old records. | ||
$em = $this->getEntityManager(); | ||
$qb = new QueryBuilder($em); | ||
$qb->delete(Request::class, 'r') | ||
->where('r.ipAddress = :ip') | ||
->andWhere('r.lastRequest < :cutoff') | ||
->setParameter('ip', $ip) | ||
->setParameter('cutoff', $cutoff) | ||
->getQuery() | ||
->execute(); | ||
|
||
// Ensure the old record is deleted at DB level before the insert coming up, so we don't | ||
// fall foul of the IP uniqueness constraint. | ||
$em->flush(); | ||
|
||
// Add a new Request record and set it up with `requestCount` 1 and `lastRequest` now. | ||
$requestItem = new Request(); | ||
$requestItem->setIpAddress($ip); | ||
$requestItem->addRequest(); | ||
$em->persist($requestItem); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters