diff options
author | marvin-borner@live.com | 2018-04-16 21:09:05 +0200 |
---|---|---|
committer | marvin-borner@live.com | 2018-04-16 21:09:05 +0200 |
commit | cf14306c2b3f82a81f8d56669a71633b4d4b5fce (patch) | |
tree | 86700651aa180026e89a66064b0364b1e4346f3f /main/app/sprinkles/core/src/Throttle | |
parent | 619b01b3615458c4ed78bfaeabb6b1a47cc8ad8b (diff) |
Main merge to user management system - files are now at /main/public/
Diffstat (limited to 'main/app/sprinkles/core/src/Throttle')
3 files changed, 336 insertions, 0 deletions
diff --git a/main/app/sprinkles/core/src/Throttle/ThrottleRule.php b/main/app/sprinkles/core/src/Throttle/ThrottleRule.php new file mode 100755 index 0000000..b71f296 --- /dev/null +++ b/main/app/sprinkles/core/src/Throttle/ThrottleRule.php @@ -0,0 +1,140 @@ +<?php +/** + * UserFrosting (http://www.userfrosting.com) + * + * @link https://github.com/userfrosting/UserFrosting + * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + */ +namespace UserFrosting\Sprinkle\Core\Throttle; + +/** + * ThrottleRule Class + * + * Represents a request throttling rule. + * @author Alex Weissman (https://alexanderweissman.com) + */ +class ThrottleRule +{ + /** @var string Set to 'ip' for ip-based throttling, 'data' for request-data-based throttling. */ + protected $method; + + /** @var int The amount of time, in seconds, to look back in determining attempts to consider. */ + protected $interval; + + /** + * @var int[] A mapping of minimum observation counts (x) to delays (y), in seconds. + * Any throttleable event that has occurred more than x times in this rule's interval, + * must wait y seconds after the last occurrence before another attempt is permitted. + */ + protected $delays; + + /** + * Create a new ThrottleRule object. + * + * @param string $method Set to 'ip' for ip-based throttling, 'data' for request-data-based throttling. + * @param int $interval The amount of time, in seconds, to look back in determining attempts to consider. + * @param int[] $delays A mapping of minimum observation counts (x) to delays (y), in seconds. + */ + public function __construct($method, $interval, $delays) + { + $this->setMethod($method); + $this->setInterval($interval); + $this->setDelays($delays); + } + + /** + * Get the current delay on this rule for a particular number of event counts. + * + * @param Carbon\Carbon $lastEventTime The timestamp for the last countable event. + * @param int $count The total number of events which have occurred in an interval. + */ + public function getDelay($lastEventTime, $count) + { + // Zero occurrences always maps to a delay of 0 seconds. + if ($count == 0) { + return 0; + } + + foreach ($this->delays as $observations => $delay) { + // Skip any delay rules for which we haven't met the requisite number of observations + if ($count < $observations) { + continue; + } + + // If this rule meets the observed number of events, and violates the required delay, then return the remaining time left + if ($lastEventTime->diffInSeconds() < $delay) { + return $lastEventTime->addSeconds($delay)->diffInSeconds(); + } + } + + return 0; + } + + /** + * Gets the current mapping of attempts (int) to delays (seconds). + * + * @return int[] + */ + public function getDelays() + { + return $this->delays; + } + + /** + * Gets the current throttling interval (seconds). + * + * @return int + */ + public function getInterval() + { + return $this->interval; + } + + /** + * Gets the current throttling method ('ip' or 'data'). + * + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Sets the current mapping of attempts (int) to delays (seconds). + * + * @param int[] A mapping of minimum observation counts (x) to delays (y), in seconds. + */ + public function setDelays($delays) + { + // Sort the array by key, from highest to lowest value + $this->delays = $delays; + krsort($this->delays); + + return $this; + } + + /** + * Sets the current throttling interval (seconds). + * + * @param int The amount of time, in seconds, to look back in determining attempts to consider. + */ + public function setInterval($interval) + { + $this->interval = $interval; + + return $this; + } + + /** + * Sets the current throttling method ('ip' or 'data'). + * + * @param string Set to 'ip' for ip-based throttling, 'data' for request-data-based throttling. + */ + public function setMethod($method) + { + $this->method = $method; + + return $this; + } +} diff --git a/main/app/sprinkles/core/src/Throttle/Throttler.php b/main/app/sprinkles/core/src/Throttle/Throttler.php new file mode 100755 index 0000000..0d42442 --- /dev/null +++ b/main/app/sprinkles/core/src/Throttle/Throttler.php @@ -0,0 +1,178 @@ +<?php +/** + * UserFrosting (http://www.userfrosting.com) + * + * @link https://github.com/userfrosting/UserFrosting + * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + */ +namespace UserFrosting\Sprinkle\Core\Throttle; + +use Carbon\Carbon; +use UserFrosting\Sprinkle\Core\Util\ClassMapper; + +/** + * Handles throttling (rate limiting) of specific types of requests. + * + * @author Alex Weissman (https://alexanderweissman.com) + */ +class Throttler +{ + /** + * @var UserFrosting\Sprinkle\Core\Util\ClassMapper + */ + protected $classMapper; + + /** + * @var ThrottleRule[] An array mapping throttle names to throttle rules. + */ + protected $throttleRules; + + /** + * Create a new Throttler object. + * + * @param ClassMapper $classMapper Maps generic class identifiers to specific class names. + */ + public function __construct(ClassMapper $classMapper) + { + $this->classMapper = $classMapper; + $this->throttleRules = []; + } + + /** + * Add a throttling rule for a particular throttle event type. + * + * @param string $type The type of throttle event to check against. + * @param ThrottleRule $rule The rule to use when throttling this type of event. + */ + public function addThrottleRule($type, $rule) + { + if (!($rule instanceof ThrottleRule || ($rule === null))) { + throw new ThrottlerException('$rule must be of type ThrottleRule (or null).'); + } + + $this->throttleRules[$type] = $rule; + + return $this; + } + + /** + * Check the current request against a specified throttle rule. + * + * @param string $type The type of throttle event to check against. + * @param mixed[] $requestData Any additional request parameters to use in checking the throttle. + * @return bool + */ + public function getDelay($type, $requestData = []) + { + $throttleRule = $this->getRule($type); + + if (is_null($throttleRule)) { + return 0; + } + + // Get earliest time to start looking for throttleable events + $startTime = Carbon::now() + ->subSeconds($throttleRule->getInterval()); + + // Fetch all throttle events of the specified type, that match the specified rule + if ($throttleRule->getMethod() == 'ip') { + $events = $this->classMapper->staticMethod('throttle', 'where', 'type', $type) + ->where('created_at', '>', $startTime) + ->where('ip', $_SERVER['REMOTE_ADDR']) + ->get(); + } else { + $events = $this->classMapper->staticMethod('throttle', 'where', 'type', $type) + ->where('created_at', '>', $startTime) + ->get(); + + // Filter out only events that match the required JSON data + $events = $events->filter(function ($item, $key) use ($requestData) { + $data = json_decode($item->request_data); + + // If a field is not specified in the logged data, or it doesn't match the value we're searching for, + // then filter out this event from the collection. + foreach ($requestData as $name => $value) { + if (!isset($data->$name) || ($data->$name != $value)) { + return false; + } + } + + return true; + }); + } + + // Check the collection of events against the specified throttle rule. + return $this->computeDelay($events, $throttleRule); + } + + /** + * Get a registered rule of a particular type. + * + * @param string $type + * @throws ThrottlerException + * @return ThrottleRule[] + */ + public function getRule($type) + { + if (!array_key_exists($type, $this->throttleRules)) { + throw new ThrottlerException("The throttling rule for '$type' could not be found."); + } + + return $this->throttleRules[$type]; + } + + /** + * Get the current throttling rules. + * + * @return ThrottleRule[] + */ + public function getThrottleRules() + { + return $this->throttleRules; + } + + /** + * Log a throttleable event to the database. + * + * @param string $type the type of event + * @param string[] $requestData an array of field names => values that are relevant to throttling for this event (e.g. username, email, etc). + */ + public function logEvent($type, $requestData = []) + { + // Just a check to make sure the rule exists + $throttleRule = $this->getRule($type); + + if (is_null($throttleRule)) { + return $this; + } + + $event = $this->classMapper->createInstance('throttle', [ + 'type' => $type, + 'ip' => $_SERVER['REMOTE_ADDR'], + 'request_data' => json_encode($requestData) + ]); + + $event->save(); + + return $this; + } + + /** + * Returns the current delay for a specified throttle rule. + * + * @param Throttle[] $events a Collection of throttle events. + * @param ThrottleRule $throttleRule a rule representing the strategy to use for throttling a particular type of event. + * @return int seconds remaining until a particular event is permitted to be attempted again. + */ + protected function computeDelay($events, $throttleRule) + { + // If no matching events found, then there is no delay + if (!$events->count()) { + return 0; + } + + // Great, now we compare our delay against the most recent attempt + $lastEvent = $events->last(); + return $throttleRule->getDelay($lastEvent->created_at, $events->count()); + } +} diff --git a/main/app/sprinkles/core/src/Throttle/ThrottlerException.php b/main/app/sprinkles/core/src/Throttle/ThrottlerException.php new file mode 100755 index 0000000..2fd9035 --- /dev/null +++ b/main/app/sprinkles/core/src/Throttle/ThrottlerException.php @@ -0,0 +1,18 @@ +<?php +/** + * UserFrosting (http://www.userfrosting.com) + * + * @link https://github.com/userfrosting/UserFrosting + * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + */ +namespace UserFrosting\Sprinkle\Core\Throttle; + +/** + * Throttler exception. Used when there is a problem with the request throttler. + * + * @author Alex Weissman (https://alexanderweissman.com) + */ +class ThrottlerException extends \RuntimeException +{ + +} |