Index: branches/wmf/1.19wmf1/extensions/CharInsert/CharInsert.php |
— | — | @@ -49,7 +49,8 @@ |
50 | 50 | return true; |
51 | 51 | } |
52 | 52 | |
53 | | -function charInsert( $data ) { |
| 53 | +function charInsert( $data, $params, $parser ) { |
| 54 | + $data = $parser->mStripState->unstripBoth( $data ); |
54 | 55 | return implode( "<br />\n", |
55 | 56 | array_map( 'charInsertLine', |
56 | 57 | explode( "\n", trim( $data ) ) ) ); |
Index: branches/wmf/1.19wmf1/includes/CryptRand.php |
— | — | @@ -0,0 +1,463 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * A cryptographic random generator class used for generating secret keys |
| 5 | + * |
| 6 | + * This is based in part on Drupal code as well as what we used in our own code |
| 7 | + * prior to introduction of this class. |
| 8 | + * |
| 9 | + * @author Daniel Friesen |
| 10 | + * @file |
| 11 | + */ |
| 12 | + |
| 13 | +class MWCryptRand { |
| 14 | + |
| 15 | + /** |
| 16 | + * Minimum number of iterations we want to make in our drift calculations. |
| 17 | + */ |
| 18 | + const MIN_ITERATIONS = 1000; |
| 19 | + |
| 20 | + /** |
| 21 | + * Number of milliseconds we want to spend generating each separate byte |
| 22 | + * of the final generated bytes. |
| 23 | + * This is used in combination with the hash length to determine the duration |
| 24 | + * we should spend doing drift calculations. |
| 25 | + */ |
| 26 | + const MSEC_PER_BYTE = 0.5; |
| 27 | + |
| 28 | + /** |
| 29 | + * Singleton instance for public use |
| 30 | + */ |
| 31 | + protected static $singleton = null; |
| 32 | + |
| 33 | + /** |
| 34 | + * The hash algorithm being used |
| 35 | + */ |
| 36 | + protected $algo = null; |
| 37 | + |
| 38 | + /** |
| 39 | + * The number of bytes outputted by the hash algorithm |
| 40 | + */ |
| 41 | + protected $hashLength = null; |
| 42 | + |
| 43 | + /** |
| 44 | + * A boolean indicating whether the previous random generation was done using |
| 45 | + * cryptographically strong random number generator or not. |
| 46 | + */ |
| 47 | + protected $strong = null; |
| 48 | + |
| 49 | + /** |
| 50 | + * Initialize an initial random state based off of whatever we can find |
| 51 | + */ |
| 52 | + protected function initialRandomState() { |
| 53 | + // $_SERVER contains a variety of unstable user and system specific information |
| 54 | + // It'll vary a little with each page, and vary even more with separate users |
| 55 | + // It'll also vary slightly across different machines |
| 56 | + $state = serialize( $_SERVER ); |
| 57 | + |
| 58 | + // To try and vary the system information of the state a bit more |
| 59 | + // by including the system's hostname into the state |
| 60 | + $state .= wfHostname(); |
| 61 | + |
| 62 | + // Try to gather a little entropy from the different php rand sources |
| 63 | + $state .= rand() . uniqid( mt_rand(), true ); |
| 64 | + |
| 65 | + // Include some information about the filesystem's current state in the random state |
| 66 | + $files = array(); |
| 67 | + // We know this file is here so grab some info about ourself |
| 68 | + $files[] = __FILE__; |
| 69 | + // The config file is likely the most often edited file we know should be around |
| 70 | + // so if the constant with it's location is defined include it's stat info into the state |
| 71 | + if ( defined( 'MW_CONFIG_FILE' ) ) { |
| 72 | + $files[] = MW_CONFIG_FILE; |
| 73 | + } |
| 74 | + foreach ( $files as $file ) { |
| 75 | + wfSuppressWarnings(); |
| 76 | + $stat = stat( $file ); |
| 77 | + wfRestoreWarnings(); |
| 78 | + if ( $stat ) { |
| 79 | + // stat() duplicates data into numeric and string keys so kill off all the numeric ones |
| 80 | + foreach ( $stat as $k => $v ) { |
| 81 | + if ( is_numeric( $k ) ) { |
| 82 | + unset( $k ); |
| 83 | + } |
| 84 | + } |
| 85 | + // The absolute filename itself will differ from install to install so don't leave it out |
| 86 | + $state .= realpath( $file ); |
| 87 | + $state .= implode( '', $stat ); |
| 88 | + } else { |
| 89 | + // The fact that the file isn't there is worth at least a |
| 90 | + // minuscule amount of entropy. |
| 91 | + $state .= '0'; |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + // Try and make this a little more unstable by including the varying process |
| 96 | + // id of the php process we are running inside of if we are able to access it |
| 97 | + if ( function_exists( 'getmypid' ) ) { |
| 98 | + $state .= getmypid(); |
| 99 | + } |
| 100 | + |
| 101 | + // If available try to increase the instability of the data by throwing in |
| 102 | + // the precise amount of memory that we happen to be using at the moment. |
| 103 | + if ( function_exists( 'memory_get_usage' ) ) { |
| 104 | + $state .= memory_get_usage( true ); |
| 105 | + } |
| 106 | + |
| 107 | + // It's mostly worthless but throw the wiki's id into the data for a little more variance |
| 108 | + $state .= wfWikiID(); |
| 109 | + |
| 110 | + // If we have a secret key or proxy key set then throw it into the state as well |
| 111 | + global $wgSecretKey, $wgProxyKey; |
| 112 | + if ( $wgSecretKey ) { |
| 113 | + $state .= $wgSecretKey; |
| 114 | + } elseif ( $wgProxyKey ) { |
| 115 | + $state .= $wgProxyKey; |
| 116 | + } |
| 117 | + |
| 118 | + return $state; |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * Randomly hash data while mixing in clock drift data for randomness |
| 123 | + * |
| 124 | + * @param $data The data to randomly hash. |
| 125 | + * @return String The hashed bytes |
| 126 | + * @author Tim Starling |
| 127 | + */ |
| 128 | + protected function driftHash( $data ) { |
| 129 | + // Minimum number of iterations (to avoid slow operations causing the loop to gather little entropy) |
| 130 | + $minIterations = self::MIN_ITERATIONS; |
| 131 | + // Duration of time to spend doing calculations (in seconds) |
| 132 | + $duration = ( self::MSEC_PER_BYTE / 1000 ) * $this->hashLength(); |
| 133 | + // Create a buffer to use to trigger memory operations |
| 134 | + $bufLength = 10000000; |
| 135 | + $buffer = str_repeat( ' ', $bufLength ); |
| 136 | + $bufPos = 0; |
| 137 | + |
| 138 | + // Iterate for $duration seconds or at least $minIerations number of iterations |
| 139 | + $iterations = 0; |
| 140 | + $startTime = microtime( true ); |
| 141 | + $currentTime = $startTime; |
| 142 | + while ( $iterations < $minIterations || $currentTime - $startTime < $duration ) { |
| 143 | + // Trigger some memory writing to trigger some bus activity |
| 144 | + // This may create variance in the time between iterations |
| 145 | + $bufPos = ( $bufPos + 13 ) % $bufLength; |
| 146 | + $buffer[$bufPos] = ' '; |
| 147 | + // Add the drift between this iteration and the last in as entropy |
| 148 | + $nextTime = microtime( true ); |
| 149 | + $delta = (int)( ( $nextTime - $currentTime ) * 1000000 ); |
| 150 | + $data .= $delta; |
| 151 | + // Every 100 iterations hash the data and entropy |
| 152 | + if ( $iterations % 100 === 0 ) { |
| 153 | + $data = sha1( $data ); |
| 154 | + } |
| 155 | + $currentTime = $nextTime; |
| 156 | + $iterations++; |
| 157 | + } |
| 158 | + $timeTaken = $currentTime - $startTime; |
| 159 | + $data = $this->hash( $data ); |
| 160 | + |
| 161 | + wfDebug( __METHOD__ . ": Clock drift calculation " . |
| 162 | + "(time-taken=" . ( $timeTaken * 1000 ) . "ms, " . |
| 163 | + "iterations=$iterations, " . |
| 164 | + "time-per-iteration=" . ( $timeTaken / $iterations * 1e6 ) . "us)\n" ); |
| 165 | + return $data; |
| 166 | + } |
| 167 | + |
| 168 | + /** |
| 169 | + * Return a rolling random state initially build using data from unstable sources |
| 170 | + * @return A new weak random state |
| 171 | + */ |
| 172 | + protected function randomState() { |
| 173 | + static $state = null; |
| 174 | + if ( is_null( $state ) ) { |
| 175 | + // Initialize the state with whatever unstable data we can find |
| 176 | + // It's important that this data is hashed right afterwards to prevent |
| 177 | + // it from being leaked into the output stream |
| 178 | + $state = $this->hash( $this->initialRandomState() ); |
| 179 | + } |
| 180 | + // Generate a new random state based on the initial random state or previous |
| 181 | + // random state by combining it with clock drift |
| 182 | + $state = $this->driftHash( $state ); |
| 183 | + return $state; |
| 184 | + } |
| 185 | + |
| 186 | + /** |
| 187 | + * Decide on the best acceptable hash algorithm we have available for hash() |
| 188 | + * @return String A hash algorithm |
| 189 | + */ |
| 190 | + protected function hashAlgo() { |
| 191 | + if ( !is_null( $this->algo ) ) { |
| 192 | + return $this->algo; |
| 193 | + } |
| 194 | + |
| 195 | + $algos = hash_algos(); |
| 196 | + $preference = array( 'whirlpool', 'sha256', 'sha1', 'md5' ); |
| 197 | + |
| 198 | + foreach ( $preference as $algorithm ) { |
| 199 | + if ( in_array( $algorithm, $algos ) ) { |
| 200 | + $this->algo = $algorithm; |
| 201 | + wfDebug( __METHOD__ . ": Using the {$this->algo} hash algorithm.\n" ); |
| 202 | + return $this->algo; |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + // We only reach here if no acceptable hash is found in the list, this should |
| 207 | + // be a technical impossibility since most of php's hash list is fixed and |
| 208 | + // some of the ones we list are available as their own native functions |
| 209 | + // But since we already require at least 5.2 and hash() was default in |
| 210 | + // 5.1.2 we don't bother falling back to methods like sha1 and md5. |
| 211 | + throw new MWException( "Could not find an acceptable hashing function in hash_algos()" ); |
| 212 | + } |
| 213 | + |
| 214 | + /** |
| 215 | + * Return the byte-length output of the hash algorithm we are |
| 216 | + * using in self::hash and self::hmac. |
| 217 | + * |
| 218 | + * @return int Number of bytes the hash outputs |
| 219 | + */ |
| 220 | + protected function hashLength() { |
| 221 | + if ( is_null( $this->hashLength ) ) { |
| 222 | + $this->hashLength = strlen( $this->hash( '' ) ); |
| 223 | + } |
| 224 | + return $this->hashLength; |
| 225 | + } |
| 226 | + |
| 227 | + /** |
| 228 | + * Generate an acceptably unstable one-way-hash of some text |
| 229 | + * making use of the best hash algorithm that we have available. |
| 230 | + * |
| 231 | + * @return String A raw hash of the data |
| 232 | + */ |
| 233 | + protected function hash( $data ) { |
| 234 | + return hash( $this->hashAlgo(), $data, true ); |
| 235 | + } |
| 236 | + |
| 237 | + /** |
| 238 | + * Generate an acceptably unstable one-way-hmac of some text |
| 239 | + * making use of the best hash algorithm that we have available. |
| 240 | + * |
| 241 | + * @return String A raw hash of the data |
| 242 | + */ |
| 243 | + protected function hmac( $data, $key ) { |
| 244 | + return hash_hmac( $this->hashAlgo(), $data, $key, true ); |
| 245 | + } |
| 246 | + |
| 247 | + /** |
| 248 | + * @see self::wasStrong() |
| 249 | + */ |
| 250 | + public function realWasStrong() { |
| 251 | + if ( is_null( $this->strong ) ) { |
| 252 | + throw new MWException( __METHOD__ . ' called before generation of random data' ); |
| 253 | + } |
| 254 | + return $this->strong; |
| 255 | + } |
| 256 | + |
| 257 | + /** |
| 258 | + * @see self::generate() |
| 259 | + */ |
| 260 | + public function realGenerate( $bytes, $forceStrong = false ) { |
| 261 | + wfProfileIn( __METHOD__ ); |
| 262 | + |
| 263 | + wfDebug( __METHOD__ . ": Generating cryptographic random bytes for " . wfGetAllCallers( 5 ) . "\n" ); |
| 264 | + |
| 265 | + $bytes = floor( $bytes ); |
| 266 | + static $buffer = ''; |
| 267 | + if ( is_null( $this->strong ) ) { |
| 268 | + // Set strength to false initially until we know what source data is coming from |
| 269 | + $this->strong = true; |
| 270 | + } |
| 271 | + |
| 272 | + if ( strlen( $buffer ) < $bytes ) { |
| 273 | + // If available make use of mcrypt_create_iv URANDOM source to generate randomness |
| 274 | + // On unix-like systems this reads from /dev/urandom but does it without any buffering |
| 275 | + // and bypasses openbasdir restrictions so it's preferable to reading directly |
| 276 | + // On Windows starting in PHP 5.3.0 Windows' native CryptGenRandom is used to generate |
| 277 | + // entropy so this is also preferable to just trying to read urandom because it may work |
| 278 | + // on Windows systems as well. |
| 279 | + if ( function_exists( 'mcrypt_create_iv' ) ) { |
| 280 | + wfProfileIn( __METHOD__ . '-mcrypt' ); |
| 281 | + $rem = $bytes - strlen( $buffer ); |
| 282 | + $iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM ); |
| 283 | + if ( $iv === false ) { |
| 284 | + wfDebug( __METHOD__ . ": mcrypt_create_iv returned false.\n" ); |
| 285 | + } else { |
| 286 | + $bytes .= $iv; |
| 287 | + wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) . " bytes of randomness.\n" ); |
| 288 | + } |
| 289 | + wfProfileOut( __METHOD__ . '-mcrypt' ); |
| 290 | + } |
| 291 | + } |
| 292 | + |
| 293 | + if ( strlen( $buffer ) < $bytes ) { |
| 294 | + // If available make use of openssl's random_pesudo_bytes method to attempt to generate randomness. |
| 295 | + // However don't do this on Windows with PHP < 5.3.4 due to a bug: |
| 296 | + // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php |
| 297 | + if ( function_exists( 'openssl_random_pseudo_bytes' ) |
| 298 | + && ( !wfIsWindows() || version_compare( PHP_VERSION, '5.3.4', '>=' ) ) |
| 299 | + ) { |
| 300 | + wfProfileIn( __METHOD__ . '-openssl' ); |
| 301 | + $rem = $bytes - strlen( $buffer ); |
| 302 | + $openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong ); |
| 303 | + if ( $openssl_bytes === false ) { |
| 304 | + wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes returned false.\n" ); |
| 305 | + } else { |
| 306 | + $buffer .= $openssl_bytes; |
| 307 | + wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes generated " . strlen( $openssl_bytes ) . " bytes of " . ( $openssl_strong ? "strong" : "weak" ) . " randomness.\n" ); |
| 308 | + } |
| 309 | + if ( strlen( $buffer ) >= $bytes ) { |
| 310 | + // openssl tells us if the random source was strong, if some of our data was generated |
| 311 | + // using it use it's say on whether the randomness is strong |
| 312 | + $this->strong = !!$openssl_strong; |
| 313 | + } |
| 314 | + wfProfileOut( __METHOD__ . '-openssl' ); |
| 315 | + } |
| 316 | + } |
| 317 | + |
| 318 | + // Only read from urandom if we can control the buffer size or were passed forceStrong |
| 319 | + if ( strlen( $buffer ) < $bytes && ( function_exists( 'stream_set_read_buffer' ) || $forceStrong ) ) { |
| 320 | + wfProfileIn( __METHOD__ . '-fopen-urandom' ); |
| 321 | + $rem = $bytes - strlen( $buffer ); |
| 322 | + if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) { |
| 323 | + wfDebug( __METHOD__ . ": Was forced to read from /dev/urandom without control over the buffer size.\n" ); |
| 324 | + } |
| 325 | + // /dev/urandom is generally considered the best possible commonly |
| 326 | + // available random source, and is available on most *nix systems. |
| 327 | + wfSuppressWarnings(); |
| 328 | + $urandom = fopen( "/dev/urandom", "rb" ); |
| 329 | + wfRestoreWarnings(); |
| 330 | + |
| 331 | + // Attempt to read all our random data from urandom |
| 332 | + // php's fread always does buffered reads based on the stream's chunk_size |
| 333 | + // so in reality it will usually read more than the amount of data we're |
| 334 | + // asked for and not storing that risks depleting the system's random pool. |
| 335 | + // If stream_set_read_buffer is available set the chunk_size to the amount |
| 336 | + // of data we need. Otherwise read 8k, php's default chunk_size. |
| 337 | + if ( $urandom ) { |
| 338 | + // php's default chunk_size is 8k |
| 339 | + $chunk_size = 1024 * 8; |
| 340 | + if ( function_exists( 'stream_set_read_buffer' ) ) { |
| 341 | + // If possible set the chunk_size to the amount of data we need |
| 342 | + stream_set_read_buffer( $urandom, $rem ); |
| 343 | + $chunk_size = $rem; |
| 344 | + } |
| 345 | + $random_bytes = fread( $urandom, max( $chunk_size, $rem ) ); |
| 346 | + $buffer .= $random_bytes; |
| 347 | + fclose( $urandom ); |
| 348 | + wfDebug( __METHOD__ . ": /dev/urandom generated " . strlen( $random_bytes ) . " bytes of randomness.\n" ); |
| 349 | + if ( strlen( $buffer ) >= $bytes ) { |
| 350 | + // urandom is always strong, set to true if all our data was generated using it |
| 351 | + $this->strong = true; |
| 352 | + } |
| 353 | + } else { |
| 354 | + wfDebug( __METHOD__ . ": /dev/urandom could not be opened.\n" ); |
| 355 | + } |
| 356 | + wfProfileOut( __METHOD__ . '-fopen-urandom' ); |
| 357 | + } |
| 358 | + |
| 359 | + // If we cannot use or generate enough data from a secure source |
| 360 | + // use this loop to generate a good set of pseudo random data. |
| 361 | + // This works by initializing a random state using a pile of unstable data |
| 362 | + // and continually shoving it through a hash along with a variable salt. |
| 363 | + // We hash the random state with more salt to avoid the state from leaking |
| 364 | + // out and being used to predict the /randomness/ that follows. |
| 365 | + if ( strlen( $buffer ) < $bytes ) { |
| 366 | + wfDebug( __METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n" ); |
| 367 | + } |
| 368 | + while ( strlen( $buffer ) < $bytes ) { |
| 369 | + wfProfileIn( __METHOD__ . '-fallback' ); |
| 370 | + $buffer .= $this->hmac( $this->randomState(), mt_rand() ); |
| 371 | + // This code is never really cryptographically strong, if we use it |
| 372 | + // at all, then set strong to false. |
| 373 | + $this->strong = false; |
| 374 | + wfProfileOut( __METHOD__ . '-fallback' ); |
| 375 | + } |
| 376 | + |
| 377 | + // Once the buffer has been filled up with enough random data to fulfill |
| 378 | + // the request shift off enough data to handle the request and leave the |
| 379 | + // unused portion left inside the buffer for the next request for random data |
| 380 | + $generated = substr( $buffer, 0, $bytes ); |
| 381 | + $buffer = substr( $buffer, $bytes ); |
| 382 | + |
| 383 | + wfDebug( __METHOD__ . ": " . strlen( $buffer ) . " bytes of randomness leftover in the buffer.\n" ); |
| 384 | + |
| 385 | + wfProfileOut( __METHOD__ ); |
| 386 | + return $generated; |
| 387 | + } |
| 388 | + |
| 389 | + /** |
| 390 | + * @see self::generateHex() |
| 391 | + */ |
| 392 | + public function realGenerateHex( $chars, $forceStrong = false ) { |
| 393 | + // hex strings are 2x the length of raw binary so we divide the length in half |
| 394 | + // odd numbers will result in a .5 that leads the generate() being 1 character |
| 395 | + // short, so we use ceil() to ensure that we always have enough bytes |
| 396 | + $bytes = ceil( $chars / 2 ); |
| 397 | + // Generate the data and then convert it to a hex string |
| 398 | + $hex = bin2hex( $this->generate( $bytes, $forceStrong ) ); |
| 399 | + // A bit of paranoia here, the caller asked for a specific length of string |
| 400 | + // here, and it's possible (eg when given an odd number) that we may actually |
| 401 | + // have at least 1 char more than they asked for. Just in case they made this |
| 402 | + // call intending to insert it into a database that does truncation we don't |
| 403 | + // want to give them too much and end up with their database and their live |
| 404 | + // code having two different values because part of what we gave them is truncated |
| 405 | + // hence, we strip out any run of characters longer than what we were asked for. |
| 406 | + return substr( $hex, 0, $chars ); |
| 407 | + } |
| 408 | + |
| 409 | + /** Publicly exposed static methods **/ |
| 410 | + |
| 411 | + /** |
| 412 | + * Return a singleton instance of MWCryptRand |
| 413 | + */ |
| 414 | + protected static function singleton() { |
| 415 | + if ( is_null( self::$singleton ) ) { |
| 416 | + self::$singleton = new self; |
| 417 | + } |
| 418 | + return self::$singleton; |
| 419 | + } |
| 420 | + |
| 421 | + /** |
| 422 | + * Return a boolean indicating whether or not the source used for cryptographic |
| 423 | + * random bytes generation in the previously run generate* call |
| 424 | + * was cryptographically strong. |
| 425 | + * |
| 426 | + * @return bool Returns true if the source was strong, false if not. |
| 427 | + */ |
| 428 | + public static function wasStrong() { |
| 429 | + return self::singleton()->realWasStrong(); |
| 430 | + } |
| 431 | + |
| 432 | + /** |
| 433 | + * Generate a run of (ideally) cryptographically random data and return |
| 434 | + * it in raw binary form. |
| 435 | + * You can use MWCryptRand::wasStrong() if you wish to know if the source used |
| 436 | + * was cryptographically strong. |
| 437 | + * |
| 438 | + * @param $bytes int the number of bytes of random data to generate |
| 439 | + * @param $forceStrong bool Pass true if you want generate to prefer cryptographically |
| 440 | + * strong sources of entropy even if reading from them may steal |
| 441 | + * more entropy from the system than optimal. |
| 442 | + * @return String Raw binary random data |
| 443 | + */ |
| 444 | + public static function generate( $bytes, $forceStrong = false ) { |
| 445 | + return self::singleton()->realGenerate( $bytes, $forceStrong ); |
| 446 | + } |
| 447 | + |
| 448 | + /** |
| 449 | + * Generate a run of (ideally) cryptographically random data and return |
| 450 | + * it in hexadecimal string format. |
| 451 | + * You can use MWCryptRand::wasStrong() if you wish to know if the source used |
| 452 | + * was cryptographically strong. |
| 453 | + * |
| 454 | + * @param $chars int the number of hex chars of random data to generate |
| 455 | + * @param $forceStrong bool Pass true if you want generate to prefer cryptographically |
| 456 | + * strong sources of entropy even if reading from them may steal |
| 457 | + * more entropy from the system than optimal. |
| 458 | + * @return String Hexadecimal random data |
| 459 | + */ |
| 460 | + public static function generateHex( $chars, $forceStrong = false ) { |
| 461 | + return self::singleton()->realGenerateHex( $chars, $forceStrong ); |
| 462 | + } |
| 463 | + |
| 464 | +} |
Index: branches/wmf/1.19wmf1/includes/User.php |
— | — | @@ -831,23 +831,20 @@ |
832 | 832 | } |
833 | 833 | |
834 | 834 | /** |
835 | | - * Return a random password. Sourced from mt_rand, so it's not particularly secure. |
836 | | - * @todo hash random numbers to improve security, like generateToken() |
| 835 | + * Return a random password. |
837 | 836 | * |
838 | 837 | * @return String new random password |
839 | 838 | */ |
840 | 839 | public static function randomPassword() { |
841 | 840 | global $wgMinimalPasswordLength; |
842 | | - $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz'; |
843 | | - $l = strlen( $pwchars ) - 1; |
844 | | - |
845 | | - $pwlength = max( 7, $wgMinimalPasswordLength ); |
846 | | - $digit = mt_rand( 0, $pwlength - 1 ); |
847 | | - $np = ''; |
848 | | - for ( $i = 0; $i < $pwlength; $i++ ) { |
849 | | - $np .= $i == $digit ? chr( mt_rand( 48, 57 ) ) : $pwchars[ mt_rand( 0, $l ) ]; |
850 | | - } |
851 | | - return $np; |
| 841 | + // Decide the final password length based on our min password length, stopping at a minimum of 10 chars |
| 842 | + $length = max( 10, $wgMinimalPasswordLength ); |
| 843 | + // Multiply by 1.25 to get the number of hex characters we need |
| 844 | + $length = $length * 1.25; |
| 845 | + // Generate random hex chars |
| 846 | + $hex = MWCryptRand::generateHex( $length ); |
| 847 | + // Convert from base 16 to base 32 to get a proper password like string |
| 848 | + return wfBaseConvert( $hex, 16, 32 ); |
852 | 849 | } |
853 | 850 | |
854 | 851 | /** |
— | — | @@ -877,7 +874,7 @@ |
878 | 875 | $this->mTouched = '0'; # Allow any pages to be cached |
879 | 876 | } |
880 | 877 | |
881 | | - $this->setToken(); # Random |
| 878 | + $this->mToken = null; // Don't run cryptographic functions till we need a token |
882 | 879 | $this->mEmailAuthenticated = null; |
883 | 880 | $this->mEmailToken = ''; |
884 | 881 | $this->mEmailTokenExpires = null; |
— | — | @@ -984,11 +981,11 @@ |
985 | 982 | return false; |
986 | 983 | } |
987 | 984 | |
988 | | - if ( $request->getSessionData( 'wsToken' ) !== null ) { |
989 | | - $passwordCorrect = $proposedUser->getToken() === $request->getSessionData( 'wsToken' ); |
| 985 | + if ( $request->getSessionData( 'wsToken' ) ) { |
| 986 | + $passwordCorrect = $proposedUser->getToken( false ) === $request->getSessionData( 'wsToken' ); |
990 | 987 | $from = 'session'; |
991 | | - } elseif ( $request->getCookie( 'Token' ) !== null ) { |
992 | | - $passwordCorrect = $proposedUser->getToken() === $request->getCookie( 'Token' ); |
| 988 | + } elseif ( $request->getCookie( 'Token' ) ) { |
| 989 | + $passwordCorrect = $proposedUser->getToken( false ) === $request->getCookie( 'Token' ); |
993 | 990 | $from = 'cookie'; |
994 | 991 | } else { |
995 | 992 | # No session or persistent login cookie |
— | — | @@ -1093,6 +1090,9 @@ |
1094 | 1091 | } |
1095 | 1092 | $this->mTouched = wfTimestamp( TS_MW, $row->user_touched ); |
1096 | 1093 | $this->mToken = $row->user_token; |
| 1094 | + if ( $this->mToken == '' ) { |
| 1095 | + $this->mToken = null; |
| 1096 | + } |
1097 | 1097 | $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated ); |
1098 | 1098 | $this->mEmailToken = $row->user_email_token; |
1099 | 1099 | $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires ); |
— | — | @@ -2015,10 +2015,14 @@ |
2016 | 2016 | |
2017 | 2017 | /** |
2018 | 2018 | * Get the user's current token. |
| 2019 | + * @param $forceCreation Force the generation of a new token if the user doesn't have one (default=true for backwards compatibility) |
2019 | 2020 | * @return String Token |
2020 | 2021 | */ |
2021 | | - public function getToken() { |
| 2022 | + public function getToken( $forceCreation = true ) { |
2022 | 2023 | $this->load(); |
| 2024 | + if ( !$this->mToken && $forceCreation ) { |
| 2025 | + $this->setToken(); |
| 2026 | + } |
2023 | 2027 | return $this->mToken; |
2024 | 2028 | } |
2025 | 2029 | |
— | — | @@ -2032,14 +2036,7 @@ |
2033 | 2037 | global $wgSecretKey, $wgProxyKey; |
2034 | 2038 | $this->load(); |
2035 | 2039 | if ( !$token ) { |
2036 | | - if ( $wgSecretKey ) { |
2037 | | - $key = $wgSecretKey; |
2038 | | - } elseif ( $wgProxyKey ) { |
2039 | | - $key = $wgProxyKey; |
2040 | | - } else { |
2041 | | - $key = microtime(); |
2042 | | - } |
2043 | | - $this->mToken = md5( $key . mt_rand( 0, 0x7fffffff ) . wfWikiID() . $this->mId ); |
| 2040 | + $this->mToken = MWCryptRand::generateHex( USER_TOKEN_LENGTH ); |
2044 | 2041 | } else { |
2045 | 2042 | $this->mToken = $token; |
2046 | 2043 | } |
— | — | @@ -2745,6 +2742,14 @@ |
2746 | 2743 | |
2747 | 2744 | $this->load(); |
2748 | 2745 | if ( 0 == $this->mId ) return; |
| 2746 | + if ( !$this->mToken ) { |
| 2747 | + // When token is empty or NULL generate a new one and then save it to the database |
| 2748 | + // This allows a wiki to re-secure itself after a leak of it's user table or $wgSecretKey |
| 2749 | + // Simply by setting every cell in the user_token column to NULL and letting them be |
| 2750 | + // regenerated as users log back into the wiki. |
| 2751 | + $this->setToken(); |
| 2752 | + $this->saveSettings(); |
| 2753 | + } |
2749 | 2754 | $session = array( |
2750 | 2755 | 'wsUserID' => $this->mId, |
2751 | 2756 | 'wsToken' => $this->mToken, |
— | — | @@ -2821,7 +2826,7 @@ |
2822 | 2827 | 'user_email' => $this->mEmail, |
2823 | 2828 | 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), |
2824 | 2829 | 'user_touched' => $dbw->timestamp( $this->mTouched ), |
2825 | | - 'user_token' => $this->mToken, |
| 2830 | + 'user_token' => strval( $this->mToken ), |
2826 | 2831 | 'user_email_token' => $this->mEmailToken, |
2827 | 2832 | 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ), |
2828 | 2833 | ), array( /* WHERE */ |
— | — | @@ -2887,7 +2892,7 @@ |
2888 | 2893 | 'user_email' => $user->mEmail, |
2889 | 2894 | 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ), |
2890 | 2895 | 'user_real_name' => $user->mRealName, |
2891 | | - 'user_token' => $user->mToken, |
| 2896 | + 'user_token' => strval( $user->mToken ), |
2892 | 2897 | 'user_registration' => $dbw->timestamp( $user->mRegistration ), |
2893 | 2898 | 'user_editcount' => 0, |
2894 | 2899 | ); |
— | — | @@ -2920,7 +2925,7 @@ |
2921 | 2926 | 'user_email' => $this->mEmail, |
2922 | 2927 | 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), |
2923 | 2928 | 'user_real_name' => $this->mRealName, |
2924 | | - 'user_token' => $this->mToken, |
| 2929 | + 'user_token' => strval( $this->mToken ), |
2925 | 2930 | 'user_registration' => $dbw->timestamp( $this->mRegistration ), |
2926 | 2931 | 'user_editcount' => 0, |
2927 | 2932 | ), __METHOD__ |
— | — | @@ -3184,7 +3189,7 @@ |
3185 | 3190 | } else { |
3186 | 3191 | $token = $request->getSessionData( 'wsEditToken' ); |
3187 | 3192 | if ( $token === null ) { |
3188 | | - $token = self::generateToken(); |
| 3193 | + $token = MWCryptRand::generateHex( 32 ); |
3189 | 3194 | $request->setSessionData( 'wsEditToken', $token ); |
3190 | 3195 | } |
3191 | 3196 | if( is_array( $salt ) ) { |
— | — | @@ -3201,8 +3206,7 @@ |
3202 | 3207 | * @return String The new random token |
3203 | 3208 | */ |
3204 | 3209 | public static function generateToken( $salt = '' ) { |
3205 | | - $token = dechex( mt_rand() ) . dechex( mt_rand() ); |
3206 | | - return md5( $token . $salt ); |
| 3210 | + return MWCryptRand::generateHex( 32 ); |
3207 | 3211 | } |
3208 | 3212 | |
3209 | 3213 | /** |
— | — | @@ -3308,12 +3312,11 @@ |
3309 | 3313 | global $wgUserEmailConfirmationTokenExpiry; |
3310 | 3314 | $now = time(); |
3311 | 3315 | $expires = $now + $wgUserEmailConfirmationTokenExpiry; |
3312 | | - $expiration = wfTimestamp( TS_MW, $expires ); |
3313 | | - $token = self::generateToken( $this->mId . $this->mEmail . $expires ); |
| 3316 | + $this->load(); |
| 3317 | + $token = MWCryptRand::generateHex( 32 ); |
3314 | 3318 | $hash = md5( $token ); |
3315 | | - $this->load(); |
3316 | 3319 | $this->mEmailToken = $hash; |
3317 | | - $this->mEmailTokenExpires = $expiration; |
| 3320 | + $this->mEmailTokenExpires = wfTimestamp( TS_MW, $expires ); |
3318 | 3321 | return $token; |
3319 | 3322 | } |
3320 | 3323 | |
— | — | @@ -3862,7 +3865,7 @@ |
3863 | 3866 | |
3864 | 3867 | if( $wgPasswordSalt ) { |
3865 | 3868 | if ( $salt === false ) { |
3866 | | - $salt = substr( wfGenerateToken(), 0, 8 ); |
| 3869 | + $salt = MWCryptRand::generateHex( 8 ); |
3867 | 3870 | } |
3868 | 3871 | return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) ); |
3869 | 3872 | } else { |
Property changes on: branches/wmf/1.19wmf1/includes/User.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
3870 | 3873 | Merged /branches/REL1_19/phase3/includes/User.php:r114241,114283,114355 |
Index: branches/wmf/1.19wmf1/includes/GlobalFunctions.php |
— | — | @@ -3269,6 +3269,33 @@ |
3270 | 3270 | } |
3271 | 3271 | |
3272 | 3272 | /** |
| 3273 | + * Override session_id before session startup if php's built-in |
| 3274 | + * session generation code is not secure. |
| 3275 | + */ |
| 3276 | +function wfFixSessionID() { |
| 3277 | + // If the cookie or session id is already set we already have a session and should abort |
| 3278 | + if ( isset( $_COOKIE[ session_name() ] ) || session_id() ) { |
| 3279 | + return; |
| 3280 | + } |
| 3281 | + |
| 3282 | + // PHP's built-in session entropy is enabled if: |
| 3283 | + // - entropy_file is set or you're on Windows with php 5.3.3+ |
| 3284 | + // - AND entropy_length is > 0 |
| 3285 | + // We treat it as disabled if it doesn't have an entropy length of at least 32 |
| 3286 | + $entropyEnabled = ( |
| 3287 | + ( wfIsWindows() && version_compare( PHP_VERSION, '5.3.3', '>=' ) ) |
| 3288 | + || ini_get( 'session.entropy_file' ) |
| 3289 | + ) |
| 3290 | + && intval( ini_get( 'session.entropy_length' ) ) >= 32; |
| 3291 | + |
| 3292 | + // If built-in entropy is not enabled or not sufficient override php's built in session id generation code |
| 3293 | + if ( !$entropyEnabled ) { |
| 3294 | + wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, overriding session id generation using our cryptrand source.\n" ); |
| 3295 | + session_id( MWCryptRand::generateHex( 32 ) ); |
| 3296 | + } |
| 3297 | +} |
| 3298 | + |
| 3299 | +/** |
3273 | 3300 | * Initialise php session |
3274 | 3301 | * |
3275 | 3302 | * @param $sessionId Bool |
— | — | @@ -3307,6 +3334,8 @@ |
3308 | 3335 | session_cache_limiter( 'private, must-revalidate' ); |
3309 | 3336 | if ( $sessionId ) { |
3310 | 3337 | session_id( $sessionId ); |
| 3338 | + } else { |
| 3339 | + wfFixSessionID(); |
3311 | 3340 | } |
3312 | 3341 | wfSuppressWarnings(); |
3313 | 3342 | session_start(); |
Property changes on: branches/wmf/1.19wmf1/includes/GlobalFunctions.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
3314 | 3343 | Merged /branches/REL1_19/phase3/includes/GlobalFunctions.php:r114241,114283,114355 |
3315 | 3344 | Merged /trunk/phase3/includes/GlobalFunctions.php:r114354 |
Index: branches/wmf/1.19wmf1/includes/OutputPage.php |
— | — | @@ -2505,7 +2505,7 @@ |
2506 | 2506 | * @return string html <script> and <style> tags |
2507 | 2507 | */ |
2508 | 2508 | protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) { |
2509 | | - global $wgResourceLoaderUseESI, $wgResourceLoaderInlinePrivateModules; |
| 2509 | + global $wgResourceLoaderUseESI; |
2510 | 2510 | |
2511 | 2511 | if ( !count( $modules ) ) { |
2512 | 2512 | return ''; |
— | — | @@ -2584,10 +2584,11 @@ |
2585 | 2585 | continue; |
2586 | 2586 | } |
2587 | 2587 | |
2588 | | - // Support inlining of private modules if configured as such. Note that these |
2589 | | - // modules should be loaded from getHeadScripts() before the first loader call. |
2590 | | - // Otherwise other modules can't properly use them as dependencies (bug 30914) |
2591 | | - if ( $group === 'private' && $wgResourceLoaderInlinePrivateModules ) { |
| 2588 | + // Inline private modules. These can't be loaded through load.php for security |
| 2589 | + // reasons, see bug 34907. Note that these modules should be loaded from |
| 2590 | + // getHeadScripts() before the first loader call. Otherwise other modules can't |
| 2591 | + // properly use them as dependencies (bug 30914) |
| 2592 | + if ( $group === 'private' ) { |
2592 | 2593 | if ( $only == ResourceLoaderModule::TYPE_STYLES ) { |
2593 | 2594 | $links .= Html::inlineStyle( |
2594 | 2595 | $resourceLoader->makeModuleResponse( $context, $modules ) |
Index: branches/wmf/1.19wmf1/includes/installer/Installer.php |
— | — | @@ -1405,8 +1405,7 @@ |
1406 | 1406 | } |
1407 | 1407 | |
1408 | 1408 | /** |
1409 | | - * Generate $wgSecretKey. Will warn if we had to use mt_rand() instead of |
1410 | | - * /dev/urandom |
| 1409 | + * Generate $wgSecretKey. Will warn if we had to use an insecure random source. |
1411 | 1410 | * |
1412 | 1411 | * @return Status |
1413 | 1412 | */ |
— | — | @@ -1419,8 +1418,8 @@ |
1420 | 1419 | } |
1421 | 1420 | |
1422 | 1421 | /** |
1423 | | - * Generate a secret value for variables using either |
1424 | | - * /dev/urandom or mt_rand(). Produce a warning in the later case. |
| 1422 | + * Generate a secret value for variables using our CryptRand generator. |
| 1423 | + * Produce a warning if the random source was insecure. |
1425 | 1424 | * |
1426 | 1425 | * @param $keys Array |
1427 | 1426 | * @return Status |
— | — | @@ -1428,28 +1427,18 @@ |
1429 | 1428 | protected function doGenerateKeys( $keys ) { |
1430 | 1429 | $status = Status::newGood(); |
1431 | 1430 | |
1432 | | - wfSuppressWarnings(); |
1433 | | - $file = fopen( "/dev/urandom", "r" ); |
1434 | | - wfRestoreWarnings(); |
1435 | | - |
| 1431 | + $strong = true; |
1436 | 1432 | foreach ( $keys as $name => $length ) { |
1437 | | - if ( $file ) { |
1438 | | - $secretKey = bin2hex( fread( $file, $length / 2 ) ); |
1439 | | - } else { |
1440 | | - $secretKey = ''; |
1441 | | - |
1442 | | - for ( $i = 0; $i < $length / 8; $i++ ) { |
1443 | | - $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) ); |
1444 | | - } |
| 1433 | + $secretKey = MWCryptRand::generateHex( $length, true ); |
| 1434 | + if ( !MWCryptRand::wasStrong() ) { |
| 1435 | + $strong = false; |
1445 | 1436 | } |
1446 | 1437 | |
1447 | 1438 | $this->setVar( $name, $secretKey ); |
1448 | 1439 | } |
1449 | 1440 | |
1450 | | - if ( $file ) { |
1451 | | - fclose( $file ); |
1452 | | - } else { |
1453 | | - $names = array_keys ( $keys ); |
| 1441 | + if ( !$strong ) { |
| 1442 | + $names = array_keys( $keys ); |
1454 | 1443 | $names = preg_replace( '/^(.*)$/', '\$$1', $names ); |
1455 | 1444 | global $wgLang; |
1456 | 1445 | $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) ); |
Index: branches/wmf/1.19wmf1/includes/api/ApiMain.php |
— | — | @@ -595,7 +595,7 @@ |
596 | 596 | |
597 | 597 | // Die if token required, but not provided (unless there is a gettoken parameter) |
598 | 598 | $salt = $module->getTokenSalt(); |
599 | | - if ( $salt !== false && !isset( $moduleParams['gettoken'] ) ) { |
| 599 | + if ( $salt !== false && !$moduleParams['gettoken'] ) { |
600 | 600 | if ( !isset( $moduleParams['token'] ) ) { |
601 | 601 | $this->dieUsageMsg( array( 'missingparam', 'token' ) ); |
602 | 602 | } else { |
Index: branches/wmf/1.19wmf1/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php |
— | — | @@ -44,41 +44,18 @@ |
45 | 45 | } |
46 | 46 | |
47 | 47 | global $wgUser; |
48 | | - |
49 | | - if ( $context->getUser() === $wgUser->getName() ) { |
50 | | - return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() ); |
51 | | - } else { |
52 | | - return 1; |
53 | | - } |
| 48 | + return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() ); |
54 | 49 | } |
55 | | - |
56 | | - /** |
57 | | - * Fetch the context's user options, or if it doesn't match current user, |
58 | | - * the default options. |
59 | | - * |
60 | | - * @param $context ResourceLoaderContext: Context object |
61 | | - * @return Array: List of user options keyed by option name |
62 | | - */ |
63 | | - protected function contextUserOptions( ResourceLoaderContext $context ) { |
64 | | - global $wgUser; |
65 | | - |
66 | | - // Verify identity -- this is a private module |
67 | | - if ( $context->getUser() === $wgUser->getName() ) { |
68 | | - return $wgUser->getOptions(); |
69 | | - } else { |
70 | | - return User::getDefaultOptions(); |
71 | | - } |
72 | | - } |
73 | 50 | |
74 | 51 | /** |
75 | 52 | * @param $context ResourceLoaderContext |
76 | 53 | * @return array |
77 | 54 | */ |
78 | 55 | public function getStyles( ResourceLoaderContext $context ) { |
79 | | - global $wgAllowUserCssPrefs; |
| 56 | + global $wgAllowUserCssPrefs, $wgUser; |
80 | 57 | |
81 | 58 | if ( $wgAllowUserCssPrefs ) { |
82 | | - $options = $this->contextUserOptions( $context ); |
| 59 | + $options = $wgUser->getOptions(); |
83 | 60 | |
84 | 61 | // Build CSS rules |
85 | 62 | $rules = array(); |
Index: branches/wmf/1.19wmf1/includes/resourceloader/ResourceLoader.php |
— | — | @@ -173,7 +173,7 @@ |
174 | 174 | $cache->set( $key, $result ); |
175 | 175 | } catch ( Exception $exception ) { |
176 | 176 | // Return exception as a comment |
177 | | - $result = "/*\n{$exception->__toString()}\n*/\n"; |
| 177 | + $result = $this->makeComment( $exception->__toString() ); |
178 | 178 | } |
179 | 179 | |
180 | 180 | wfProfileOut( __METHOD__ ); |
— | — | @@ -430,13 +430,20 @@ |
431 | 431 | ob_start(); |
432 | 432 | |
433 | 433 | wfProfileIn( __METHOD__ ); |
434 | | - $exceptions = ''; |
| 434 | + $errors = ''; |
435 | 435 | |
436 | 436 | // Split requested modules into two groups, modules and missing |
437 | 437 | $modules = array(); |
438 | 438 | $missing = array(); |
439 | 439 | foreach ( $context->getModules() as $name ) { |
440 | 440 | if ( isset( $this->moduleInfos[$name] ) ) { |
| 441 | + $module = $this->getModule( $name ); |
| 442 | + // Do not allow private modules to be loaded from the web. |
| 443 | + // This is a security issue, see bug 34907. |
| 444 | + if ( $module->getGroup() === 'private' ) { |
| 445 | + $errors .= $this->makeComment( "Cannot show private module \"$name\"" ); |
| 446 | + continue; |
| 447 | + } |
441 | 448 | $modules[$name] = $this->getModule( $name ); |
442 | 449 | } else { |
443 | 450 | $missing[] = $name; |
— | — | @@ -448,12 +455,11 @@ |
449 | 456 | $this->preloadModuleInfo( array_keys( $modules ), $context ); |
450 | 457 | } catch( Exception $e ) { |
451 | 458 | // Add exception to the output as a comment |
452 | | - $exceptions .= "/*\n{$e->__toString()}\n*/\n"; |
| 459 | + $errors .= $this->makeComment( $e->__toString() ); |
453 | 460 | } |
454 | 461 | |
455 | 462 | wfProfileIn( __METHOD__.'-getModifiedTime' ); |
456 | 463 | |
457 | | - $private = false; |
458 | 464 | // To send Last-Modified and support If-Modified-Since, we need to detect |
459 | 465 | // the last modified time |
460 | 466 | $mtime = wfTimestamp( TS_UNIX, $wgCacheEpoch ); |
— | — | @@ -462,22 +468,18 @@ |
463 | 469 | * @var $module ResourceLoaderModule |
464 | 470 | */ |
465 | 471 | try { |
466 | | - // Bypass Squid and other shared caches if the request includes any private modules |
467 | | - if ( $module->getGroup() === 'private' ) { |
468 | | - $private = true; |
469 | | - } |
470 | 472 | // Calculate maximum modified time |
471 | 473 | $mtime = max( $mtime, $module->getModifiedTime( $context ) ); |
472 | 474 | } catch ( Exception $e ) { |
473 | 475 | // Add exception to the output as a comment |
474 | | - $exceptions .= "/*\n{$e->__toString()}\n*/\n"; |
| 476 | + $errors .= $this->makeComment( $e->__toString() ); |
475 | 477 | } |
476 | 478 | } |
477 | 479 | |
478 | 480 | wfProfileOut( __METHOD__.'-getModifiedTime' ); |
479 | 481 | |
480 | 482 | // Send content type and cache related headers |
481 | | - $this->sendResponseHeaders( $context, $mtime, $private ); |
| 483 | + $this->sendResponseHeaders( $context, $mtime ); |
482 | 484 | |
483 | 485 | // If there's an If-Modified-Since header, respond with a 304 appropriately |
484 | 486 | if ( $this->tryRespondLastModified( $context, $mtime ) ) { |
— | — | @@ -489,20 +491,20 @@ |
490 | 492 | $response = $this->makeModuleResponse( $context, $modules, $missing ); |
491 | 493 | |
492 | 494 | // Prepend comments indicating exceptions |
493 | | - $response = $exceptions . $response; |
| 495 | + $response = $errors . $response; |
494 | 496 | |
495 | 497 | // Capture any PHP warnings from the output buffer and append them to the |
496 | 498 | // response in a comment if we're in debug mode. |
497 | 499 | if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) { |
498 | | - $response = "/*\n$warnings\n*/\n" . $response; |
| 500 | + $response = $this->makeComment( $warnings ) . $response; |
499 | 501 | } |
500 | 502 | |
501 | 503 | // Remove the output buffer and output the response |
502 | 504 | ob_end_clean(); |
503 | 505 | echo $response; |
504 | 506 | |
505 | | - // Save response to file cache unless there are private modules or errors |
506 | | - if ( isset( $fileCache ) && !$private && !$exceptions && !$missing ) { |
| 507 | + // Save response to file cache unless there are errors |
| 508 | + if ( isset( $fileCache ) && !$errors && !$missing ) { |
507 | 509 | // Cache single modules...and other requests if there are enough hits |
508 | 510 | if ( ResourceFileCache::useFileCache( $context ) ) { |
509 | 511 | if ( $fileCache->isCacheWorthy() ) { |
— | — | @@ -520,10 +522,9 @@ |
521 | 523 | * Send content type and last modified headers to the client. |
522 | 524 | * @param $context ResourceLoaderContext |
523 | 525 | * @param $mtime string TS_MW timestamp to use for last-modified |
524 | | - * @param $private bool True iff response contains any private modules |
525 | 526 | * @return void |
526 | 527 | */ |
527 | | - protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $private ) { |
| 528 | + protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime ) { |
528 | 529 | global $wgResourceLoaderMaxage; |
529 | 530 | // If a version wasn't specified we need a shorter expiry time for updates |
530 | 531 | // to propagate to clients quickly |
— | — | @@ -547,13 +548,8 @@ |
548 | 549 | header( 'Cache-Control: private, no-cache, must-revalidate' ); |
549 | 550 | header( 'Pragma: no-cache' ); |
550 | 551 | } else { |
551 | | - if ( $private ) { |
552 | | - header( "Cache-Control: private, max-age=$maxage" ); |
553 | | - $exp = $maxage; |
554 | | - } else { |
555 | | - header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" ); |
556 | | - $exp = min( $maxage, $smaxage ); |
557 | | - } |
| 552 | + header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" ); |
| 553 | + $exp = min( $maxage, $smaxage ); |
558 | 554 | header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) ); |
559 | 555 | } |
560 | 556 | } |
— | — | @@ -650,6 +646,11 @@ |
651 | 647 | return false; // cache miss |
652 | 648 | } |
653 | 649 | |
| 650 | + protected function makeComment( $text ) { |
| 651 | + $encText = str_replace( '*/', '* /', $text ); |
| 652 | + return "/*\n$encText\n*/\n"; |
| 653 | + } |
| 654 | + |
654 | 655 | /** |
655 | 656 | * Generates code for a response |
656 | 657 | * |
— | — | @@ -674,7 +675,7 @@ |
675 | 676 | $blobs = MessageBlobStore::get( $this, $modules, $context->getLanguage() ); |
676 | 677 | } catch ( Exception $e ) { |
677 | 678 | // Add exception to the output as a comment |
678 | | - $exceptions .= "/*\n{$e->__toString()}\n*/\n"; |
| 679 | + $exceptions .= $this->makeComment( $e->__toString() ); |
679 | 680 | } |
680 | 681 | } else { |
681 | 682 | $blobs = array(); |
— | — | @@ -753,7 +754,7 @@ |
754 | 755 | } |
755 | 756 | } catch ( Exception $e ) { |
756 | 757 | // Add exception to the output as a comment |
757 | | - $exceptions .= "/*\n{$e->__toString()}\n*/\n"; |
| 758 | + $exceptions .= $this->makeComment( $e->__toString() ); |
758 | 759 | |
759 | 760 | // Register module as missing |
760 | 761 | $missing[] = $name; |
Index: branches/wmf/1.19wmf1/includes/resourceloader/ResourceLoaderUserOptionsModule.php |
— | — | @@ -42,41 +42,19 @@ |
43 | 43 | if ( isset( $this->modifiedTime[$hash] ) ) { |
44 | 44 | return $this->modifiedTime[$hash]; |
45 | 45 | } |
46 | | - |
| 46 | + |
47 | 47 | global $wgUser; |
48 | | - |
49 | | - if ( $context->getUser() === $wgUser->getName() ) { |
50 | | - return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() ); |
51 | | - } else { |
52 | | - return 1; |
53 | | - } |
| 48 | + return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() ); |
54 | 49 | } |
55 | 50 | |
56 | 51 | /** |
57 | | - * Fetch the context's user options, or if it doesn't match current user, |
58 | | - * the default options. |
59 | | - * |
60 | | - * @param $context ResourceLoaderContext: Context object |
61 | | - * @return Array: List of user options keyed by option name |
62 | | - */ |
63 | | - protected function contextUserOptions( ResourceLoaderContext $context ) { |
64 | | - global $wgUser; |
65 | | - |
66 | | - // Verify identity -- this is a private module |
67 | | - if ( $context->getUser() === $wgUser->getName() ) { |
68 | | - return $wgUser->getOptions(); |
69 | | - } else { |
70 | | - return User::getDefaultOptions(); |
71 | | - } |
72 | | - } |
73 | | - |
74 | | - /** |
75 | 52 | * @param $context ResourceLoaderContext |
76 | 53 | * @return string |
77 | 54 | */ |
78 | 55 | public function getScript( ResourceLoaderContext $context ) { |
| 56 | + global $wgUser; |
79 | 57 | return Xml::encodeJsCall( 'mw.user.options.set', |
80 | | - array( $this->contextUserOptions( $context ) ) ); |
| 58 | + array( $wgUser->getOptions() ) ); |
81 | 59 | } |
82 | 60 | |
83 | 61 | /** |
Index: branches/wmf/1.19wmf1/includes/AutoLoader.php |
— | — | @@ -49,6 +49,7 @@ |
50 | 50 | 'ConfEditorToken' => 'includes/ConfEditor.php', |
51 | 51 | 'Cookie' => 'includes/Cookie.php', |
52 | 52 | 'CookieJar' => 'includes/Cookie.php', |
| 53 | + 'MWCryptRand' => 'includes/CryptRand.php', |
53 | 54 | 'CurlHttpRequest' => 'includes/HttpFunctions.php', |
54 | 55 | 'DeferrableUpdate' => 'includes/DeferredUpdates.php', |
55 | 56 | 'DeferredUpdates' => 'includes/DeferredUpdates.php', |
Property changes on: branches/wmf/1.19wmf1/includes/AutoLoader.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
56 | 57 | Merged /branches/REL1_19/phase3/includes/AutoLoader.php:r114241,114283,114355 |
Index: branches/wmf/1.19wmf1/includes/DefaultSettings.php |
— | — | @@ -2581,13 +2581,6 @@ |
2582 | 2582 | ); |
2583 | 2583 | |
2584 | 2584 | /** |
2585 | | - * Whether to embed private modules inline with HTML output or to bypass |
2586 | | - * caching and check the user parameter against $wgUser to prevent |
2587 | | - * unauthorized access to private modules. |
2588 | | - */ |
2589 | | -$wgResourceLoaderInlinePrivateModules = true; |
2590 | | - |
2591 | | -/** |
2592 | 2585 | * The default debug mode (on/off) for of ResourceLoader requests. This will still |
2593 | 2586 | * be overridden when the debug URL parameter is used. |
2594 | 2587 | */ |
Index: branches/wmf/1.19wmf1/includes/specials/SpecialUserlogin.php |
— | — | @@ -1136,9 +1136,9 @@ |
1137 | 1137 | */ |
1138 | 1138 | public static function setLoginToken() { |
1139 | 1139 | global $wgRequest; |
1140 | | - // Use User::generateToken() instead of $user->editToken() |
| 1140 | + // Generate a token directly instead of using $user->editToken() |
1141 | 1141 | // because the latter reuses $_SESSION['wsEditToken'] |
1142 | | - $wgRequest->setSessionData( 'wsLoginToken', User::generateToken() ); |
| 1142 | + $wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) ); |
1143 | 1143 | } |
1144 | 1144 | |
1145 | 1145 | /** |
— | — | @@ -1162,7 +1162,7 @@ |
1163 | 1163 | */ |
1164 | 1164 | public static function setCreateaccountToken() { |
1165 | 1165 | global $wgRequest; |
1166 | | - $wgRequest->setSessionData( 'wsCreateaccountToken', User::generateToken() ); |
| 1166 | + $wgRequest->setSessionData( 'wsCreateaccountToken', MWCryptRand::generateHex( 32 ) ); |
1167 | 1167 | } |
1168 | 1168 | |
1169 | 1169 | /** |
Index: branches/wmf/1.19wmf1/includes/specials/SpecialUpload.php |
— | — | @@ -111,14 +111,7 @@ |
112 | 112 | |
113 | 113 | // If it was posted check for the token (no remote POST'ing with user credentials) |
114 | 114 | $token = $request->getVal( 'wpEditToken' ); |
115 | | - if( $this->mSourceType == 'file' && $token == null ) { |
116 | | - // Skip token check for file uploads as that can't be faked via JS... |
117 | | - // Some client-side tools don't expect to need to send wpEditToken |
118 | | - // with their submissions, as that's new in 1.16. |
119 | | - $this->mTokenOk = true; |
120 | | - } else { |
121 | | - $this->mTokenOk = $this->getUser()->matchEditToken( $token ); |
122 | | - } |
| 115 | + $this->mTokenOk = $this->getUser()->matchEditToken( $token ); |
123 | 116 | |
124 | 117 | $this->uploadFormTextTop = ''; |
125 | 118 | $this->uploadFormTextAfterSummary = ''; |
Index: branches/wmf/1.19wmf1/includes/specials/SpecialWatchlist.php |
— | — | @@ -43,7 +43,7 @@ |
44 | 44 | // Add feed links |
45 | 45 | $wlToken = $user->getOption( 'watchlisttoken' ); |
46 | 46 | if ( !$wlToken ) { |
47 | | - $wlToken = sha1( mt_rand() . microtime( true ) ); |
| 47 | + $wlToken = MWCryptRand::generateHex( 40 ); |
48 | 48 | $user->setOption( 'watchlisttoken', $wlToken ); |
49 | 49 | $user->saveSettings(); |
50 | 50 | } |
Property changes on: branches/wmf/1.19wmf1/includes/specials/SpecialWatchlist.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
51 | 51 | Merged /branches/REL1_19/phase3/includes/specials/SpecialWatchlist.php:r114241,114283,114355 |
Property changes on: branches/wmf/1.19wmf1/includes/specials |
___________________________________________________________________ |
Modified: svn:mergeinfo |
52 | 52 | Merged /branches/REL1_19/phase3/includes/specials:r114241,114283,114355 |
Property changes on: branches/wmf/1.19wmf1/includes |
___________________________________________________________________ |
Modified: svn:mergeinfo |
53 | 53 | Merged /branches/REL1_19/phase3/includes:r114241,114283,114355 |
Property changes on: branches/wmf/1.19wmf1 |
___________________________________________________________________ |
Modified: svn:mergeinfo |
54 | 54 | Merged /branches/REL1_19/phase3:r114241,114283,114355 |