Index: trunk/phase3/includes/api/ApiLogin.php |
— | — | @@ -5,7 +5,8 @@ |
6 | 6 | * |
7 | 7 | * API for MediaWiki 1.8+ |
8 | 8 | * |
9 | | - * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com |
| 9 | + * Copyright (C) 2006-2007 Yuri Astrakhan <Firstname><Lastname>@gmail.com, |
| 10 | + * Daniel Cannon (cannon dot danielc at gmail dot com) |
10 | 11 | * |
11 | 12 | * This program is free software; you can redistribute it and/or modify |
12 | 13 | * it under the terms of the GNU General Public License as published by |
— | — | @@ -29,14 +30,44 @@ |
30 | 31 | } |
31 | 32 | |
32 | 33 | /** |
| 34 | + * Unit to authenticate log-in attempts to the current wiki. |
| 35 | + * |
33 | 36 | * @addtogroup API |
34 | 37 | */ |
35 | 38 | class ApiLogin extends ApiBase { |
| 39 | + |
| 40 | + /** |
| 41 | + * The amount of time a user must wait after submitting |
| 42 | + * a bad login (will be multiplied by the THROTTLE_FACTOR for each bad attempt) |
| 43 | + */ |
| 44 | + const THROTTLE_TIME = 10; |
36 | 45 | |
| 46 | + /** |
| 47 | + * The factor by which the wait-time in between authentication |
| 48 | + * attempts is increased every failed attempt. |
| 49 | + */ |
| 50 | + const THROTTLE_FACTOR = 1.5; |
| 51 | + |
| 52 | + /** |
| 53 | + * The maximum number of failed logins after which the wait increase stops. |
| 54 | + */ |
| 55 | + const THOTTLE_MAX_COUNT = 10; |
| 56 | + |
37 | 57 | public function __construct($main, $action) { |
38 | 58 | parent :: __construct($main, $action, 'lg'); |
39 | 59 | } |
40 | 60 | |
| 61 | + /** |
| 62 | + * Executes the log-in attempt using the parameters passed. If |
| 63 | + * the log-in succeeeds, it attaches a cookie to the session |
| 64 | + * and outputs the user id, username, and session token. If a |
| 65 | + * log-in fails, as the result of a bad password, a nonexistant |
| 66 | + * user, or any other reason, the host is cached with an expiry |
| 67 | + * and no log-in attempts will be accepted until that expiry |
| 68 | + * is reached. The expiry is $this->mLoginThrottle. |
| 69 | + * |
| 70 | + * @access public |
| 71 | + */ |
41 | 72 | public function execute() { |
42 | 73 | $name = $password = $domain = null; |
43 | 74 | extract($this->extractRequestParams()); |
— | — | @@ -50,6 +81,15 @@ |
51 | 82 | |
52 | 83 | $result = array (); |
53 | 84 | |
| 85 | + $nextLoginIn = $this->getNextLoginTimeout(); |
| 86 | + if ($nextLoginIn > 0) { |
| 87 | + $result['result'] = 'NeedToWait'; |
| 88 | + $result['details'] = "Please wait $nextLoginIn seconds before next log-in attempt"; |
| 89 | + $result['wait'] = $nextLoginIn; |
| 90 | + $this->getResult()->addValue(null, 'login', $result); |
| 91 | + return; |
| 92 | + } |
| 93 | + |
54 | 94 | $loginForm = new LoginForm($params); |
55 | 95 | switch ($loginForm->authenticateUserData()) { |
56 | 96 | case LoginForm :: SUCCESS : |
— | — | @@ -86,9 +126,89 @@ |
87 | 127 | ApiBase :: dieDebug(__METHOD__, 'Unhandled case value'); |
88 | 128 | } |
89 | 129 | |
| 130 | + if ($result['result'] != 'Success') { |
| 131 | + $result['wait'] = $this->cacheBadLogin(); |
| 132 | + } |
| 133 | + // if we were allowed to try to login, memcache is fine |
| 134 | + |
90 | 135 | $this->getResult()->addValue(null, 'login', $result); |
91 | 136 | } |
92 | 137 | |
| 138 | + |
| 139 | + /** |
| 140 | + * Caches a bad-login attempt associated with the host and with an |
| 141 | + * expiry of $this->mLoginThrottle. These are cached by a key |
| 142 | + * separate from that used by the captcha system--as such, logging |
| 143 | + * in through the standard interface will get you a legal session |
| 144 | + * and cookies to prove it, but will not remove this entry. |
| 145 | + * |
| 146 | + * Returns the number of seconds until next login attempt will be allowed. |
| 147 | + * |
| 148 | + * @access private |
| 149 | + */ |
| 150 | + private function cacheBadLogin() { |
| 151 | + global $wgMemc; |
| 152 | + |
| 153 | + $key = $this->getMemCacheKey(); |
| 154 | + $val =& $wgMemc->get( $key ); |
| 155 | + |
| 156 | + $val['lastReqTime'] = time(); |
| 157 | + if (!isset($val['count'])) { |
| 158 | + $val['count'] = 1; |
| 159 | + } else { |
| 160 | + $val['count'] = 1 + $val['count']; |
| 161 | + } |
| 162 | + |
| 163 | + $delay = ApiLogin::calculateDelay($val); |
| 164 | + |
| 165 | + $wgMemc->delete($key); |
| 166 | + $wgMemc->add( $key, $val, $delay ); |
| 167 | + |
| 168 | + return $delay; |
| 169 | + } |
| 170 | + |
| 171 | + /** |
| 172 | + * How much time the client must wait before it will be |
| 173 | + * allowed to try to log-in next. |
| 174 | + * The return value is 0 if no wait is required. |
| 175 | + */ |
| 176 | + private function getNextLoginTimeout() { |
| 177 | + global $wgMemc; |
| 178 | + |
| 179 | + $val = $wgMemc->get($this->getMemCacheKey()); |
| 180 | + |
| 181 | + $elapse = (time() - $val['lastReqTime']) / 1000; // in seconds |
| 182 | + $canRetryIn = ApiLogin::calculateDelay($val) - $elapse; |
| 183 | + $canRetryIn = $canRetryIn < 0 ? 0 : $canRetryIn; |
| 184 | + |
| 185 | + return $canRetryIn; |
| 186 | + } |
| 187 | + |
| 188 | + /** |
| 189 | + * Based on the number of previously attempted logins, returns |
| 190 | + * the delay (in seconds) when the next login attempt will be allowed. |
| 191 | + */ |
| 192 | + private static function calculateDelay($val) { |
| 193 | + // Defensive programming |
| 194 | + $count = $val['count']; |
| 195 | + $count = $count < 1 ? 1 : $count; |
| 196 | + $count = $count > self::THOTTLE_MAX_COUNT ? self::THOTTLE_MAX_COUNT : $count; |
| 197 | + |
| 198 | + return self::THROTTLE_TIME + self::THROTTLE_TIME * ($count - 1) * self::THROTTLE_FACTOR; |
| 199 | + } |
| 200 | + |
| 201 | + /** |
| 202 | + * Internal cache key for badlogin checks. Robbed from the |
| 203 | + * ConfirmEdit extension and modified to use a key unique to the |
| 204 | + * API login.3 |
| 205 | + * |
| 206 | + * @return string |
| 207 | + * @access private |
| 208 | + */ |
| 209 | + private function getMemCacheKey() { |
| 210 | + return wfMemcKey( 'apilogin', 'badlogin', 'ip', wfGetIP() ); |
| 211 | + } |
| 212 | + |
93 | 213 | protected function getAllowedParams() { |
94 | 214 | return array ( |
95 | 215 | 'name' => null, |
— | — | @@ -107,7 +227,12 @@ |
108 | 228 | |
109 | 229 | protected function getDescription() { |
110 | 230 | return array ( |
111 | | - 'This module is used to login and get the authentication tokens.' |
| 231 | + 'This module is used to login and get the authentication tokens. ' . |
| 232 | + 'In the event of a successful log-in, a cookie will be attached ' . |
| 233 | + 'to your session. In the event of a failed log-in, you will not ' . |
| 234 | + 'be able to attempt another log-in through this method for 60 ' . |
| 235 | + 'seconds--this is to prevent its use in aiding automated password ' . |
| 236 | + 'crackers.' |
112 | 237 | ); |
113 | 238 | } |
114 | 239 | |
Index: trunk/phase3/includes/api/ApiMain.php |
— | — | @@ -52,7 +52,7 @@ |
53 | 53 | * List of available modules: action name => module class |
54 | 54 | */ |
55 | 55 | private static $Modules = array ( |
56 | | -// 'login' => 'ApiLogin', // LOGIN is temporarily disabled until it becomes more secure |
| 56 | + 'login' => 'ApiLogin', |
57 | 57 | 'query' => 'ApiQuery', |
58 | 58 | 'opensearch' => 'ApiOpenSearch', |
59 | 59 | 'feedwatchlist' => 'ApiFeedWatchlist', |
Index: trunk/phase3/includes/api/ApiQueryRevisions.php |
— | — | @@ -134,7 +134,7 @@ |
135 | 135 | $this->addWhereFld('rev_page', current(array_keys($pageSet->getGoodTitles()))); |
136 | 136 | |
137 | 137 | if(!is_null($user)) { |
138 | | - $this->addWhere('rev_user_text =' . $this->getDB()->addQuotes($user)); |
| 138 | + $this->addWhereFld('rev_user_text', $user); |
139 | 139 | } elseif (!is_null( $excludeuser)) { |
140 | 140 | $this->addWhere('rev_user_text != ' . $this->getDB()->addQuotes($excludeuser)); |
141 | 141 | } |
Index: trunk/phase3/includes/memcached-client.php |
— | — | @@ -152,7 +152,7 @@ |
153 | 153 | /** |
154 | 154 | * At how many bytes should we compress? |
155 | 155 | * |
156 | | - * @var interger |
| 156 | + * @var integer |
157 | 157 | * @access private |
158 | 158 | */ |
159 | 159 | var $_compress_threshold; |
— | — | @@ -192,7 +192,7 @@ |
193 | 193 | /** |
194 | 194 | * Total # of bit buckets we have |
195 | 195 | * |
196 | | - * @var interger |
| 196 | + * @var integer |
197 | 197 | * @access private |
198 | 198 | */ |
199 | 199 | var $_bucketcount; |
— | — | @@ -200,7 +200,7 @@ |
201 | 201 | /** |
202 | 202 | * # of total servers we have |
203 | 203 | * |
204 | | - * @var interger |
| 204 | + * @var integer |
205 | 205 | * @access private |
206 | 206 | */ |
207 | 207 | var $_active; |
— | — | @@ -272,9 +272,9 @@ |
273 | 273 | * Adds a key/value to the memcache server if one isn't already set with |
274 | 274 | * that key |
275 | 275 | * |
276 | | - * @param string $key Key to set with data |
277 | | - * @param mixed $val Value to store |
278 | | - * @param interger $exp (optional) Time to expire data at |
| 276 | + * @param string $key Key to set with data |
| 277 | + * @param mixed $val Value to store |
| 278 | + * @param integer $exp (optional) Time to expire data at |
279 | 279 | * |
280 | 280 | * @return boolean |
281 | 281 | * @access public |
— | — | @@ -291,7 +291,7 @@ |
292 | 292 | * Decriment a value stored on the memcache server |
293 | 293 | * |
294 | 294 | * @param string $key Key to decriment |
295 | | - * @param interger $amt (optional) Amount to decriment |
| 295 | + * @param integer $amt (optional) Amount to decriment |
296 | 296 | * |
297 | 297 | * @return mixed FALSE on failure, value on success |
298 | 298 | * @access public |
— | — | @@ -308,7 +308,7 @@ |
309 | 309 | * Deletes a key from the server, optionally after $time |
310 | 310 | * |
311 | 311 | * @param string $key Key to delete |
312 | | - * @param interger $time (optional) How long to wait before deleting |
| 312 | + * @param integer $time (optional) How long to wait before deleting |
313 | 313 | * |
314 | 314 | * @return boolean TRUE on success, FALSE on failure |
315 | 315 | * @access public |
— | — | @@ -506,9 +506,9 @@ |
507 | 507 | * Increments $key (optionally) by $amt |
508 | 508 | * |
509 | 509 | * @param string $key Key to increment |
510 | | - * @param interger $amt (optional) amount to increment |
| 510 | + * @param integer $amt (optional) amount to increment |
511 | 511 | * |
512 | | - * @return interger New key value? |
| 512 | + * @return integer New key value? |
513 | 513 | * @access public |
514 | 514 | */ |
515 | 515 | function incr ($key, $amt=1) |
— | — | @@ -524,7 +524,7 @@ |
525 | 525 | * |
526 | 526 | * @param string $key Key to set value as |
527 | 527 | * @param mixed $value Value to store |
528 | | - * @param interger $exp (optional) Experiation time |
| 528 | + * @param integer $exp (optional) Experiation time |
529 | 529 | * |
530 | 530 | * @return boolean |
531 | 531 | * @access public |
— | — | @@ -582,7 +582,7 @@ |
583 | 583 | * |
584 | 584 | * @param string $key Key to set value as |
585 | 585 | * @param mixed $value Value to set |
586 | | - * @param interger $exp (optional) Experiation time |
| 586 | + * @param integer $exp (optional) Experiation time |
587 | 587 | * |
588 | 588 | * @return boolean TRUE on success |
589 | 589 | * @access public |
— | — | @@ -598,7 +598,7 @@ |
599 | 599 | /** |
600 | 600 | * Sets the compression threshold |
601 | 601 | * |
602 | | - * @param interger $thresh Threshold to compress if larger than |
| 602 | + * @param integer $thresh Threshold to compress if larger than |
603 | 603 | * |
604 | 604 | * @access public |
605 | 605 | */ |
— | — | @@ -687,7 +687,7 @@ |
688 | 688 | /** |
689 | 689 | * Connects $sock to $host, timing out after $timeout |
690 | 690 | * |
691 | | - * @param interger $sock Socket to connect |
| 691 | + * @param integer $sock Socket to connect |
692 | 692 | * @param string $host Host:IP to connect to |
693 | 693 | * |
694 | 694 | * @return boolean |
— | — | @@ -807,11 +807,11 @@ |
808 | 808 | // {{{ _hashfunc() |
809 | 809 | |
810 | 810 | /** |
811 | | - * Creates a hash interger based on the $key |
| 811 | + * Creates a hash integer based on the $key |
812 | 812 | * |
813 | 813 | * @param string $key Key to hash |
814 | 814 | * |
815 | | - * @return interger Hash value |
| 815 | + * @return integer Hash value |
816 | 816 | * @access private |
817 | 817 | */ |
818 | 818 | function _hashfunc ($key) |
— | — | @@ -830,9 +830,9 @@ |
831 | 831 | * |
832 | 832 | * @param string $cmd Command to perform |
833 | 833 | * @param string $key Key to perform it on |
834 | | - * @param interger $amt Amount to adjust |
| 834 | + * @param integer $amt Amount to adjust |
835 | 835 | * |
836 | | - * @return interger New value of $key |
| 836 | + * @return integer New value of $key |
837 | 837 | * @access private |
838 | 838 | */ |
839 | 839 | function _incrdecr ($cmd, $key, $amt=1) |
— | — | @@ -929,7 +929,7 @@ |
930 | 930 | * @param string $cmd Command to perform |
931 | 931 | * @param string $key Key to act on |
932 | 932 | * @param mixed $val What we need to store |
933 | | - * @param interger $exp When it should expire |
| 933 | + * @param integer $exp When it should expire |
934 | 934 | * |
935 | 935 | * @return boolean |
936 | 936 | * @access private |