From cf14306c2b3f82a81f8d56669a71633b4d4b5fce Mon Sep 17 00:00:00 2001
From: marvin-borner@live.com
Date: Mon, 16 Apr 2018 21:09:05 +0200
Subject: Main merge to user management system - files are now at /main/public/

---
 .../sprinkles/core/src/Throttle/ThrottleRule.php   | 140 ++++++++++++++++
 main/app/sprinkles/core/src/Throttle/Throttler.php | 178 +++++++++++++++++++++
 .../core/src/Throttle/ThrottlerException.php       |  18 +++
 3 files changed, 336 insertions(+)
 create mode 100755 main/app/sprinkles/core/src/Throttle/ThrottleRule.php
 create mode 100755 main/app/sprinkles/core/src/Throttle/Throttler.php
 create mode 100755 main/app/sprinkles/core/src/Throttle/ThrottlerException.php

(limited to 'main/app/sprinkles/core/src/Throttle')

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
+{
+
+}
-- 
cgit v1.2.3