@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Modularize rate/connection limits in Phabricator

Summary:
Depends on D18702. Ref T13008. This replaces the old hard-coded single rate limit with multiple flexible limits, and defines two types of limits:

- Rate: reject requests if a client has completed too many requests recently.
- Connection: reject requests if a client has too many more connections than disconnections recently.

The connection limit adds +1 to the score for each connection, then adds -1 for each disconnection. So the overall number is how many open connections they have, at least approximately.

Supporting multiple limits will let us do limiting by Hostname and by remote address (e.g., a specific IP can't exceed a low limit, and all requests to a hostname can't exceed a higher limit).

Configuring the new limits looks something like this:

```
PhabricatorStartup::addRateLimit(new PhabricatorClientRateLimit())
->setLimitKey('rate')
->setClientKey($_SERVER['REMOTE_ADDR'])
->setLimit(5);

PhabricatorStartup::addRateLimit(new PhabricatorClientConnectionLimit())
->setLimitKey('conn')
->setClientKey($_SERVER['REMOTE_ADDR'])
->setLimit(2);
```

Test Plan:
- Configured limits as above.
- Made a lot of requests, got cut off by the rate limit.
- Used `curl --limit-rate -F 'data=@the_letter_m.txt' ...` to upload files really slowly. Got cut off by the connection limit. With `enable_post_data_reading` off, this correctly killed the connections //before// the uploads finished.
- I'll send this stuff to `secure` before production to give it more of a chance.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13008

Differential Revision: https://secure.phabricator.com/D18703

+441 -268
+4 -14
src/aphront/configuration/AphrontApplicationConfiguration.php
··· 204 204 205 205 DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log); 206 206 207 - // Add points to the rate limits for this request. 208 - $rate_token = PhabricatorStartup::getRateLimitToken(); 209 - if ($rate_token !== null) { 210 - // The base score for a request allows users to make 30 requests per 211 - // minute. 212 - $score = (1000 / 30); 213 - 214 - // If the user was logged in, let them make more requests. 215 - if ($request->getUser() && $request->getUser()->getPHID()) { 216 - $score = $score / 5; 217 - } 218 - 219 - PhabricatorStartup::addRateLimitScore($rate_token, $score); 220 - } 207 + PhabricatorStartup::disconnectRateLimits( 208 + array( 209 + 'viewer' => $request->getUser(), 210 + )); 221 211 222 212 if ($processing_exception) { 223 213 throw $processing_exception;
+1 -1
src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php
··· 10 10 11 11 public function testControllerAccessControls() { 12 12 $root = dirname(phutil_get_library_root('phabricator')); 13 - require_once $root.'/support/PhabricatorStartup.php'; 13 + require_once $root.'/support/startup/PhabricatorStartup.php'; 14 14 15 15 $application_configuration = new AphrontDefaultApplicationConfiguration(); 16 16
+37 -252
support/PhabricatorStartup.php support/startup/PhabricatorStartup.php
··· 46 46 private static $oldMemoryLimit; 47 47 private static $phases; 48 48 49 - // TODO: For now, disable rate limiting entirely by default. We need to 50 - // iterate on it a bit for Conduit, some of the specific score levels, and 51 - // to deal with NAT'd offices. 52 - private static $maximumRate = 0; 53 - private static $rateLimitToken; 49 + private static $limits = array(); 54 50 55 51 56 52 /* -( Accessing Request Information )-------------------------------------- */ ··· 138 134 // we can switch over to relying on our own exception recovery mechanisms. 139 135 ini_set('display_errors', 0); 140 136 141 - $rate_token = self::getRateLimitToken(); 142 - if ($rate_token !== null) { 143 - self::rateLimitRequest($rate_token); 144 - } 137 + self::connectRateLimits(); 145 138 146 139 self::normalizeInput(); 147 140 ··· 193 186 } 194 187 195 188 public static function loadCoreLibraries() { 196 - $phabricator_root = dirname(dirname(__FILE__)); 189 + $phabricator_root = dirname(dirname(dirname(__FILE__))); 197 190 $libraries_root = dirname($phabricator_root); 198 191 199 192 $root = null; ··· 683 676 684 677 685 678 /** 686 - * Adjust the permissible rate limit score. 679 + * Add a new client limits. 687 680 * 688 - * By default, the limit is `1000`. You can use this method to set it to 689 - * a larger or smaller value. If you set it to `2000`, users may make twice 690 - * as many requests before rate limiting. 691 - * 692 - * @param int Maximum score before rate limiting. 693 - * @return void 694 - * @task ratelimit 681 + * @param PhabricatorClientLimit New limit. 682 + * @return PhabricatorClientLimit The limit. 695 683 */ 696 - public static function setMaximumRate($rate) { 697 - self::$maximumRate = $rate; 698 - } 699 - 700 - 701 - /** 702 - * Set a token to identify the client for purposes of rate limiting. 703 - * 704 - * By default, the `REMOTE_ADDR` is used. If your install is behind a load 705 - * balancer, you may want to parse `X-Forwarded-For` and use that address 706 - * instead. 707 - * 708 - * @param string Client identity for rate limiting. 709 - */ 710 - public static function setRateLimitToken($token) { 711 - self::$rateLimitToken = $token; 712 - } 713 - 714 - 715 - /** 716 - * Get the current client identity for rate limiting. 717 - */ 718 - public static function getRateLimitToken() { 719 - if (self::$rateLimitToken !== null) { 720 - return self::$rateLimitToken; 721 - } 722 - 723 - if (isset($_SERVER['REMOTE_ADDR'])) { 724 - return $_SERVER['REMOTE_ADDR']; 725 - } 726 - 727 - return null; 728 - } 729 - 730 - 731 - /** 732 - * Check if the user (identified by `$user_identity`) has issued too many 733 - * requests recently. If they have, end the request with a 429 error code. 734 - * 735 - * The key just needs to identify the user. Phabricator uses both user PHIDs 736 - * and user IPs as keys, tracking logged-in and logged-out users separately 737 - * and enforcing different limits. 738 - * 739 - * @param string Some key which identifies the user making the request. 740 - * @return void If the user has exceeded the rate limit, this method 741 - * does not return. 742 - * @task ratelimit 743 - */ 744 - public static function rateLimitRequest($user_identity) { 745 - if (!self::canRateLimit()) { 746 - return; 747 - } 748 - 749 - $score = self::getRateLimitScore($user_identity); 750 - $limit = self::$maximumRate * self::getRateLimitBucketCount(); 751 - if ($score > $limit) { 752 - // Give the user some bonus points for getting rate limited. This keeps 753 - // bad actors who keep slamming the 429 page locked out completely, 754 - // instead of letting them get a burst of requests through every minute 755 - // after a bucket expires. 756 - $penalty = 50; 757 - 758 - self::addRateLimitScore($user_identity, $penalty); 759 - $score += $penalty; 760 - 761 - self::didRateLimit($user_identity, $score, $limit); 762 - } 684 + public static function addRateLimit(PhabricatorClientLimit $limit) { 685 + self::$limits[] = $limit; 686 + return $limit; 763 687 } 764 688 765 689 766 690 /** 767 - * Add points to the rate limit score for some user. 691 + * Apply configured rate limits. 768 692 * 769 - * If users have earned more than 1000 points per minute across all the 770 - * buckets they'll be locked out of the application, so awarding 1 point per 771 - * request roughly corresponds to allowing 1000 requests per second, while 772 - * awarding 50 points roughly corresponds to allowing 20 requests per second. 693 + * If any limit is exceeded, this method terminates the request. 773 694 * 774 - * @param string Some key which identifies the user making the request. 775 - * @param float The cost for this request; more points pushes them toward 776 - * the limit faster. 777 695 * @return void 778 696 * @task ratelimit 779 697 */ 780 - public static function addRateLimitScore($user_identity, $score) { 781 - if (!self::canRateLimit()) { 782 - return; 783 - } 698 + private static function connectRateLimits() { 699 + $limits = self::$limits; 784 700 785 - $is_apcu = (bool)function_exists('apcu_fetch'); 786 - $current = self::getRateLimitBucket(); 787 - 788 - // There's a bit of a race here, if a second process reads the bucket 789 - // before this one writes it, but it's fine if we occasionally fail to 790 - // record a user's score. If they're making requests fast enough to hit 791 - // rate limiting, we'll get them soon enough. 792 - 793 - $bucket_key = self::getRateLimitBucketKey($current); 794 - if ($is_apcu) { 795 - $bucket = apcu_fetch($bucket_key); 796 - } else { 797 - $bucket = apc_fetch($bucket_key); 798 - } 799 - 800 - if (!is_array($bucket)) { 801 - $bucket = array(); 701 + $reason = null; 702 + $connected = array(); 703 + foreach ($limits as $limit) { 704 + $reason = $limit->didConnect(); 705 + $connected[] = $limit; 706 + if ($reason !== null) { 707 + break; 708 + } 802 709 } 803 710 804 - if (empty($bucket[$user_identity])) { 805 - $bucket[$user_identity] = 0; 806 - } 711 + // If we're killing the request here, disconnect any limits that we 712 + // connected to try to keep the accounting straight. 713 + if ($reason !== null) { 714 + foreach ($connected as $limit) { 715 + $limit->didDisconnect(array()); 716 + } 807 717 808 - $bucket[$user_identity] += $score; 809 - 810 - if ($is_apcu) { 811 - apcu_store($bucket_key, $bucket); 812 - } else { 813 - apc_store($bucket_key, $bucket); 718 + self::didRateLimit($reason); 814 719 } 815 720 } 816 721 817 722 818 723 /** 819 - * Determine if rate limiting is available. 724 + * Tear down rate limiting and allow limits to score the request. 820 725 * 821 - * Rate limiting depends on APC, and isn't available unless the APC user 822 - * cache is available. 823 - * 824 - * @return bool True if rate limiting is available. 726 + * @param map<string, wild> Additional, freeform request state. 727 + * @return void 825 728 * @task ratelimit 826 729 */ 827 - private static function canRateLimit() { 730 + public static function disconnectRateLimits(array $request_state) { 731 + $limits = self::$limits; 828 732 829 - if (!self::$maximumRate) { 830 - return false; 733 + foreach ($limits as $limit) { 734 + $limit->didDisconnect($request_state); 831 735 } 832 - 833 - if (!function_exists('apc_fetch') && !function_exists('apcu_fetch')) { 834 - return false; 835 - } 836 - 837 - return true; 838 736 } 839 737 840 738 841 - /** 842 - * Get the current bucket for storing rate limit scores. 843 - * 844 - * @return int The current bucket. 845 - * @task ratelimit 846 - */ 847 - private static function getRateLimitBucket() { 848 - return (int)(time() / 60); 849 - } 850 - 851 - 852 - /** 853 - * Get the total number of rate limit buckets to retain. 854 - * 855 - * @return int Total number of rate limit buckets to retain. 856 - * @task ratelimit 857 - */ 858 - private static function getRateLimitBucketCount() { 859 - return 5; 860 - } 861 - 862 - 863 - /** 864 - * Get the APC key for a given bucket. 865 - * 866 - * @param int Bucket to get the key for. 867 - * @return string APC key for the bucket. 868 - * @task ratelimit 869 - */ 870 - private static function getRateLimitBucketKey($bucket) { 871 - return 'rate:bucket:'.$bucket; 872 - } 873 - 874 - 875 - /** 876 - * Get the APC key for the smallest stored bucket. 877 - * 878 - * @return string APC key for the smallest stored bucket. 879 - * @task ratelimit 880 - */ 881 - private static function getRateLimitMinKey() { 882 - return 'rate:min'; 883 - } 884 - 885 - 886 - /** 887 - * Get the current rate limit score for a given user. 888 - * 889 - * @param string Unique key identifying the user. 890 - * @return float The user's current score. 891 - * @task ratelimit 892 - */ 893 - private static function getRateLimitScore($user_identity) { 894 - $is_apcu = (bool)function_exists('apcu_fetch'); 895 - 896 - $min_key = self::getRateLimitMinKey(); 897 - 898 - // Identify the oldest bucket stored in APC. 899 - $cur = self::getRateLimitBucket(); 900 - if ($is_apcu) { 901 - $min = apcu_fetch($min_key); 902 - } else { 903 - $min = apc_fetch($min_key); 904 - } 905 - 906 - // If we don't have any buckets stored yet, store the current bucket as 907 - // the oldest bucket. 908 - if (!$min) { 909 - if ($is_apcu) { 910 - apcu_store($min_key, $cur); 911 - } else { 912 - apc_store($min_key, $cur); 913 - } 914 - $min = $cur; 915 - } 916 - 917 - // Destroy any buckets that are older than the minimum bucket we're keeping 918 - // track of. Under load this normally shouldn't do anything, but will clean 919 - // up an old bucket once per minute. 920 - $count = self::getRateLimitBucketCount(); 921 - for ($cursor = $min; $cursor < ($cur - $count); $cursor++) { 922 - $bucket_key = self::getRateLimitBucketKey($cursor); 923 - if ($is_apcu) { 924 - apcu_delete($bucket_key); 925 - apcu_store($min_key, $cursor + 1); 926 - } else { 927 - apc_delete($bucket_key); 928 - apc_store($min_key, $cursor + 1); 929 - } 930 - } 931 - 932 - // Now, sum up the user's scores in all of the active buckets. 933 - $score = 0; 934 - for (; $cursor <= $cur; $cursor++) { 935 - $bucket_key = self::getRateLimitBucketKey($cursor); 936 - if ($is_apcu) { 937 - $bucket = apcu_fetch($bucket_key); 938 - } else { 939 - $bucket = apc_fetch($bucket_key); 940 - } 941 - if (isset($bucket[$user_identity])) { 942 - $score += $bucket[$user_identity]; 943 - } 944 - } 945 - 946 - return $score; 947 - } 948 - 949 739 950 740 /** 951 741 * Emit an HTTP 429 "Too Many Requests" response (indicating that the user ··· 954 744 * @return exit This method **does not return**. 955 745 * @task ratelimit 956 746 */ 957 - private static function didRateLimit($user_identity, $score, $limit) { 958 - $message = 959 - "TOO MANY REQUESTS\n". 960 - "You (\"{$user_identity}\") are issuing too many requests ". 961 - "too quickly.\n"; 962 - 747 + private static function didRateLimit($reason) { 963 748 header( 964 749 'Content-Type: text/plain; charset=utf-8', 965 750 $replace = true, 966 751 $http_error = 429); 967 752 968 - echo $message; 753 + echo $reason; 969 754 970 755 exit(1); 971 756 }
+44
support/startup/PhabricatorClientConnectionLimit.php
··· 1 + <?php 2 + 3 + final class PhabricatorClientConnectionLimit 4 + extends PhabricatorClientLimit { 5 + 6 + protected function getBucketDuration() { 7 + return 60; 8 + } 9 + 10 + protected function getBucketCount() { 11 + return 15; 12 + } 13 + 14 + protected function shouldRejectConnection($score) { 15 + // Reject connections if the cumulative score across all buckets exceeds 16 + // the limit. 17 + return ($score > $this->getLimit()); 18 + } 19 + 20 + protected function getConnectScore() { 21 + return 1; 22 + } 23 + 24 + protected function getPenaltyScore() { 25 + return 0; 26 + } 27 + 28 + protected function getDisconnectScore(array $request_state) { 29 + return -1; 30 + } 31 + 32 + protected function getRateLimitReason($score) { 33 + $client_key = $this->getClientKey(); 34 + 35 + // NOTE: This happens before we load libraries, so we can not use pht() 36 + // here. 37 + 38 + return 39 + "TOO MANY CONCURRENT CONNECTIONS\n". 40 + "You (\"{$client_key}\") have too many concurrent ". 41 + "connections.\n"; 42 + } 43 + 44 + }
+291
support/startup/PhabricatorClientLimit.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorClientLimit { 4 + 5 + private $limitKey; 6 + private $clientKey; 7 + private $limit; 8 + 9 + final public function setLimitKey($limit_key) { 10 + $this->limitKey = $limit_key; 11 + return $this; 12 + } 13 + 14 + final public function getLimitKey() { 15 + return $this->limitKey; 16 + } 17 + 18 + final public function setClientKey($client_key) { 19 + $this->clientKey = $client_key; 20 + return $this; 21 + } 22 + 23 + final public function getClientKey() { 24 + return $this->clientKey; 25 + } 26 + 27 + final public function setLimit($limit) { 28 + $this->limit = $limit; 29 + return $this; 30 + } 31 + 32 + final public function getLimit() { 33 + return $this->limit; 34 + } 35 + 36 + final public function didConnect() { 37 + // NOTE: We can not use pht() here because this runs before libraries 38 + // load. 39 + 40 + if (!function_exists('apc_fetch') && !function_exists('apcu_fetch')) { 41 + throw new Exception( 42 + 'You can not configure connection rate limits unless APC/APCu are '. 43 + 'available. Rate limits rely on APC/APCu to track clients and '. 44 + 'connections.'); 45 + } 46 + 47 + if ($this->getClientKey() === null) { 48 + throw new Exception( 49 + 'You must configure a client key when defining a rate limit.'); 50 + } 51 + 52 + if ($this->getLimitKey() === null) { 53 + throw new Exception( 54 + 'You must configure a limit key when defining a rate limit.'); 55 + } 56 + 57 + if ($this->getLimit() === null) { 58 + throw new Exception( 59 + 'You must configure a limit when defining a rate limit.'); 60 + } 61 + 62 + $points = $this->getConnectScore(); 63 + if ($points) { 64 + $this->addScore($points); 65 + } 66 + 67 + $score = $this->getScore(); 68 + if (!$this->shouldRejectConnection($score)) { 69 + // Client has not hit the limit, so continue processing the request. 70 + return null; 71 + } 72 + 73 + $penalty = $this->getPenaltyScore(); 74 + if ($penalty) { 75 + $this->addScore($penalty); 76 + $score += $penalty; 77 + } 78 + 79 + return $this->getRateLimitReason($score); 80 + } 81 + 82 + final public function didDisconnect(array $request_state) { 83 + $score = $this->getDisconnectScore($request_state); 84 + if ($score) { 85 + $this->addScore($score); 86 + } 87 + } 88 + 89 + 90 + /** 91 + * Get the number of seconds for each rate bucket. 92 + * 93 + * For example, a value of 60 will create one-minute buckets. 94 + * 95 + * @return int Number of seconds per bucket. 96 + */ 97 + abstract protected function getBucketDuration(); 98 + 99 + 100 + /** 101 + * Get the total number of rate limit buckets to retain. 102 + * 103 + * @return int Total number of rate limit buckets to retain. 104 + */ 105 + abstract protected function getBucketCount(); 106 + 107 + 108 + /** 109 + * Get the score to add when a client connects. 110 + * 111 + * @return double Connection score. 112 + */ 113 + abstract protected function getConnectScore(); 114 + 115 + 116 + /** 117 + * Get the number of penalty points to add when a client hits a rate limit. 118 + * 119 + * @return double Penalty score. 120 + */ 121 + abstract protected function getPenaltyScore(); 122 + 123 + 124 + /** 125 + * Get the score to add when a client disconnects. 126 + * 127 + * @return double Connection score. 128 + */ 129 + abstract protected function getDisconnectScore(array $request_state); 130 + 131 + 132 + /** 133 + * Get a human-readable explanation of why the client is being rejected. 134 + * 135 + * @return string Brief rejection message. 136 + */ 137 + abstract protected function getRateLimitReason($score); 138 + 139 + 140 + /** 141 + * Determine whether to reject a connection. 142 + * 143 + * @return bool True to reject the connection. 144 + */ 145 + abstract protected function shouldRejectConnection($score); 146 + 147 + 148 + /** 149 + * Get the APC key for the smallest stored bucket. 150 + * 151 + * @return string APC key for the smallest stored bucket. 152 + * @task ratelimit 153 + */ 154 + private function getMinimumBucketCacheKey() { 155 + $limit_key = $this->getLimitKey(); 156 + return "limit:min:{$limit_key}"; 157 + } 158 + 159 + 160 + /** 161 + * Get the current bucket ID for storing rate limit scores. 162 + * 163 + * @return int The current bucket ID. 164 + */ 165 + private function getCurrentBucketID() { 166 + return (int)(time() / $this->getBucketDuration()); 167 + } 168 + 169 + 170 + /** 171 + * Get the APC key for a given bucket. 172 + * 173 + * @param int Bucket to get the key for. 174 + * @return string APC key for the bucket. 175 + */ 176 + private function getBucketCacheKey($bucket_id) { 177 + $limit_key = $this->getLimitKey(); 178 + return "limit:bucket:{$limit_key}:{$bucket_id}"; 179 + } 180 + 181 + 182 + /** 183 + * Add points to the rate limit score for some client. 184 + * 185 + * @param string Some key which identifies the client making the request. 186 + * @param float The cost for this request; more points pushes them toward 187 + * the limit faster. 188 + * @return this 189 + */ 190 + private function addScore($score) { 191 + $is_apcu = (bool)function_exists('apcu_fetch'); 192 + 193 + $current = $this->getCurrentBucketID(); 194 + $bucket_key = $this->getBucketCacheKey($current); 195 + 196 + // There's a bit of a race here, if a second process reads the bucket 197 + // before this one writes it, but it's fine if we occasionally fail to 198 + // record a client's score. If they're making requests fast enough to hit 199 + // rate limiting, we'll get them soon enough. 200 + 201 + if ($is_apcu) { 202 + $bucket = apcu_fetch($bucket_key); 203 + } else { 204 + $bucket = apc_fetch($bucket_key); 205 + } 206 + 207 + if (!is_array($bucket)) { 208 + $bucket = array(); 209 + } 210 + 211 + $client_key = $this->getClientKey(); 212 + if (empty($bucket[$client_key])) { 213 + $bucket[$client_key] = 0; 214 + } 215 + 216 + $bucket[$client_key] += $score; 217 + 218 + if ($is_apcu) { 219 + apcu_store($bucket_key, $bucket); 220 + } else { 221 + apc_store($bucket_key, $bucket); 222 + } 223 + 224 + return $this; 225 + } 226 + 227 + 228 + /** 229 + * Get the current rate limit score for a given client. 230 + * 231 + * @return float The client's current score. 232 + * @task ratelimit 233 + */ 234 + private function getScore() { 235 + $is_apcu = (bool)function_exists('apcu_fetch'); 236 + 237 + // Identify the oldest bucket stored in APC. 238 + $min_key = $this->getMinimumBucketCacheKey(); 239 + if ($is_apcu) { 240 + $min = apcu_fetch($min_key); 241 + } else { 242 + $min = apc_fetch($min_key); 243 + } 244 + 245 + // If we don't have any buckets stored yet, store the current bucket as 246 + // the oldest bucket. 247 + $cur = $this->getCurrentBucketID(); 248 + if (!$min) { 249 + if ($is_apcu) { 250 + apcu_store($min_key, $cur); 251 + } else { 252 + apc_store($min_key, $cur); 253 + } 254 + $min = $cur; 255 + } 256 + 257 + // Destroy any buckets that are older than the minimum bucket we're keeping 258 + // track of. Under load this normally shouldn't do anything, but will clean 259 + // up an old bucket once per minute. 260 + $count = $this->getBucketCount(); 261 + for ($cursor = $min; $cursor < ($cur - $count); $cursor++) { 262 + $bucket_key = $this->getBucketCacheKey($cursor); 263 + if ($is_apcu) { 264 + apcu_delete($bucket_key); 265 + apcu_store($min_key, $cursor + 1); 266 + } else { 267 + apc_delete($bucket_key); 268 + apc_store($min_key, $cursor + 1); 269 + } 270 + } 271 + 272 + $client_key = $this->getClientKey(); 273 + 274 + // Now, sum up the client's scores in all of the active buckets. 275 + $score = 0; 276 + for (; $cursor <= $cur; $cursor++) { 277 + $bucket_key = $this->getBucketCacheKey($cursor); 278 + if ($is_apcu) { 279 + $bucket = apcu_fetch($bucket_key); 280 + } else { 281 + $bucket = apc_fetch($bucket_key); 282 + } 283 + if (isset($bucket[$client_key])) { 284 + $score += $bucket[$client_key]; 285 + } 286 + } 287 + 288 + return $score; 289 + } 290 + 291 + }
+58
support/startup/PhabricatorClientRateLimit.php
··· 1 + <?php 2 + 3 + final class PhabricatorClientRateLimit 4 + extends PhabricatorClientLimit { 5 + 6 + protected function getBucketDuration() { 7 + return 60; 8 + } 9 + 10 + protected function getBucketCount() { 11 + return 5; 12 + } 13 + 14 + protected function shouldRejectConnection($score) { 15 + $limit = $this->getLimit(); 16 + 17 + // Reject connections if the average score across all buckets exceeds the 18 + // limit. 19 + $average_score = $score / $this->getBucketCount(); 20 + 21 + return ($average_score > $limit); 22 + } 23 + 24 + protected function getConnectScore() { 25 + return 0; 26 + } 27 + 28 + protected function getPenaltyScore() { 29 + return 1; 30 + } 31 + 32 + protected function getDisconnectScore(array $request_state) { 33 + $score = 1; 34 + 35 + // If the user was logged in, let them make more requests. 36 + if (isset($request_state['viewer'])) { 37 + $viewer = $request_state['viewer']; 38 + if ($viewer->isLoggedIn()) { 39 + $score = 0.25; 40 + } 41 + } 42 + 43 + return $score; 44 + } 45 + 46 + protected function getRateLimitReason($score) { 47 + $client_key = $this->getClientKey(); 48 + 49 + // NOTE: This happens before we load libraries, so we can not use pht() 50 + // here. 51 + 52 + return 53 + "TOO MANY REQUESTS\n". 54 + "You (\"{$client_key}\") are issuing too many requests ". 55 + "too quickly.\n"; 56 + } 57 + 58 + }
+6 -1
webroot/index.php
··· 37 37 // Load the PhabricatorStartup class itself. 38 38 $t_startup = microtime(true); 39 39 $root = dirname(dirname(__FILE__)); 40 - require_once $root.'/support/PhabricatorStartup.php'; 40 + require_once $root.'/support/startup/PhabricatorStartup.php'; 41 + 42 + // Load client limit classes so the preamble can configure limits. 43 + require_once $root.'/support/startup/PhabricatorClientLimit.php'; 44 + require_once $root.'/support/startup/PhabricatorClientRateLimit.php'; 45 + require_once $root.'/support/startup/PhabricatorClientConnectionLimit.php'; 41 46 42 47 // If the preamble script exists, load it. 43 48 $t_preamble = microtime(true);