wikimedia /
mediawiki
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * MediaWiki session backend |
||
| 4 | * |
||
| 5 | * This program is free software; you can redistribute it and/or modify |
||
| 6 | * it under the terms of the GNU General Public License as published by |
||
| 7 | * the Free Software Foundation; either version 2 of the License, or |
||
| 8 | * (at your option) any later version. |
||
| 9 | * |
||
| 10 | * This program is distributed in the hope that it will be useful, |
||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 13 | * GNU General Public License for more details. |
||
| 14 | * |
||
| 15 | * You should have received a copy of the GNU General Public License along |
||
| 16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
| 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
| 18 | * http://www.gnu.org/copyleft/gpl.html |
||
| 19 | * |
||
| 20 | * @file |
||
| 21 | * @ingroup Session |
||
| 22 | */ |
||
| 23 | |||
| 24 | namespace MediaWiki\Session; |
||
| 25 | |||
| 26 | use CachedBagOStuff; |
||
| 27 | use Psr\Log\LoggerInterface; |
||
| 28 | use User; |
||
| 29 | use WebRequest; |
||
| 30 | |||
| 31 | /** |
||
| 32 | * This is the actual workhorse for Session. |
||
| 33 | * |
||
| 34 | * Most code does not need to use this class, you want \MediaWiki\Session\Session. |
||
| 35 | * The exceptions are SessionProviders and SessionMetadata hook functions, |
||
| 36 | * which get an instance of this class rather than Session. |
||
| 37 | * |
||
| 38 | * The reasons for this split are: |
||
| 39 | * 1. A session can be attached to multiple requests, but we want the Session |
||
| 40 | * object to have some features that correspond to just one of those |
||
| 41 | * requests. |
||
| 42 | * 2. We want reasonable garbage collection behavior, but we also want the |
||
| 43 | * SessionManager to hold a reference to every active session so it can be |
||
| 44 | * saved when the request ends. |
||
| 45 | * |
||
| 46 | * @ingroup Session |
||
| 47 | * @since 1.27 |
||
| 48 | */ |
||
| 49 | final class SessionBackend { |
||
| 50 | /** @var SessionId */ |
||
| 51 | private $id; |
||
| 52 | |||
| 53 | private $persist = false; |
||
| 54 | private $remember = false; |
||
| 55 | private $forceHTTPS = false; |
||
| 56 | |||
| 57 | /** @var array|null */ |
||
| 58 | private $data = null; |
||
| 59 | |||
| 60 | private $forcePersist = false; |
||
| 61 | private $metaDirty = false; |
||
| 62 | private $dataDirty = false; |
||
| 63 | |||
| 64 | /** @var string Used to detect subarray modifications */ |
||
| 65 | private $dataHash = null; |
||
| 66 | |||
| 67 | /** @var CachedBagOStuff */ |
||
| 68 | private $store; |
||
| 69 | |||
| 70 | /** @var LoggerInterface */ |
||
| 71 | private $logger; |
||
| 72 | |||
| 73 | /** @var int */ |
||
| 74 | private $lifetime; |
||
| 75 | |||
| 76 | /** @var User */ |
||
| 77 | private $user; |
||
| 78 | |||
| 79 | private $curIndex = 0; |
||
| 80 | |||
| 81 | /** @var WebRequest[] Session requests */ |
||
| 82 | private $requests = []; |
||
| 83 | |||
| 84 | /** @var SessionProvider provider */ |
||
| 85 | private $provider; |
||
| 86 | |||
| 87 | /** @var array|null provider-specified metadata */ |
||
| 88 | private $providerMetadata = null; |
||
| 89 | |||
| 90 | private $expires = 0; |
||
| 91 | private $loggedOut = 0; |
||
| 92 | private $delaySave = 0; |
||
| 93 | |||
| 94 | private $usePhpSessionHandling = true; |
||
| 95 | private $checkPHPSessionRecursionGuard = false; |
||
| 96 | |||
| 97 | private $shutdown = false; |
||
| 98 | |||
| 99 | /** |
||
| 100 | * @param SessionId $id Session ID object |
||
| 101 | * @param SessionInfo $info Session info to populate from |
||
| 102 | * @param CachedBagOStuff $store Backend data store |
||
| 103 | * @param LoggerInterface $logger |
||
| 104 | * @param int $lifetime Session data lifetime in seconds |
||
| 105 | */ |
||
| 106 | public function __construct( |
||
| 107 | SessionId $id, SessionInfo $info, CachedBagOStuff $store, LoggerInterface $logger, $lifetime |
||
| 108 | ) { |
||
| 109 | $phpSessionHandling = \RequestContext::getMain()->getConfig()->get( 'PHPSessionHandling' ); |
||
| 110 | $this->usePhpSessionHandling = $phpSessionHandling !== 'disable'; |
||
| 111 | |||
| 112 | if ( $info->getUserInfo() && !$info->getUserInfo()->isVerified() ) { |
||
| 113 | throw new \InvalidArgumentException( |
||
| 114 | "Refusing to create session for unverified user {$info->getUserInfo()}" |
||
| 115 | ); |
||
| 116 | } |
||
| 117 | if ( $info->getProvider() === null ) { |
||
| 118 | throw new \InvalidArgumentException( 'Cannot create session without a provider' ); |
||
| 119 | } |
||
| 120 | if ( $info->getId() !== $id->getId() ) { |
||
| 121 | throw new \InvalidArgumentException( 'SessionId and SessionInfo don\'t match' ); |
||
| 122 | } |
||
| 123 | |||
| 124 | $this->id = $id; |
||
| 125 | $this->user = $info->getUserInfo() ? $info->getUserInfo()->getUser() : new User; |
||
| 126 | $this->store = $store; |
||
| 127 | $this->logger = $logger; |
||
| 128 | $this->lifetime = $lifetime; |
||
| 129 | $this->provider = $info->getProvider(); |
||
| 130 | $this->persist = $info->wasPersisted(); |
||
| 131 | $this->remember = $info->wasRemembered(); |
||
| 132 | $this->forceHTTPS = $info->forceHTTPS(); |
||
| 133 | $this->providerMetadata = $info->getProviderMetadata(); |
||
| 134 | |||
| 135 | $blob = $store->get( wfMemcKey( 'MWSession', (string)$this->id ) ); |
||
| 136 | if ( !is_array( $blob ) || |
||
| 137 | !isset( $blob['metadata'] ) || !is_array( $blob['metadata'] ) || |
||
| 138 | !isset( $blob['data'] ) || !is_array( $blob['data'] ) |
||
| 139 | ) { |
||
| 140 | $this->data = []; |
||
| 141 | $this->dataDirty = true; |
||
| 142 | $this->metaDirty = true; |
||
| 143 | $this->logger->debug( |
||
| 144 | 'SessionBackend "{session}" is unsaved, marking dirty in constructor', |
||
| 145 | [ |
||
| 146 | 'session' => $this->id, |
||
| 147 | ] ); |
||
| 148 | } else { |
||
| 149 | $this->data = $blob['data']; |
||
| 150 | if ( isset( $blob['metadata']['loggedOut'] ) ) { |
||
| 151 | $this->loggedOut = (int)$blob['metadata']['loggedOut']; |
||
| 152 | } |
||
| 153 | if ( isset( $blob['metadata']['expires'] ) ) { |
||
| 154 | $this->expires = (int)$blob['metadata']['expires']; |
||
| 155 | } else { |
||
| 156 | $this->metaDirty = true; |
||
| 157 | $this->logger->debug( |
||
| 158 | 'SessionBackend "{session}" metadata dirty due to missing expiration timestamp', |
||
| 159 | [ |
||
| 160 | 'session' => $this->id, |
||
| 161 | ] ); |
||
| 162 | } |
||
| 163 | } |
||
| 164 | $this->dataHash = md5( serialize( $this->data ) ); |
||
| 165 | } |
||
| 166 | |||
| 167 | /** |
||
| 168 | * Return a new Session for this backend |
||
| 169 | * @param WebRequest $request |
||
| 170 | * @return Session |
||
| 171 | */ |
||
| 172 | public function getSession( WebRequest $request ) { |
||
| 173 | $index = ++$this->curIndex; |
||
| 174 | $this->requests[$index] = $request; |
||
| 175 | $session = new Session( $this, $index, $this->logger ); |
||
| 176 | return $session; |
||
| 177 | } |
||
| 178 | |||
| 179 | /** |
||
| 180 | * Deregister a Session |
||
| 181 | * @private For use by \MediaWiki\Session\Session::__destruct() only |
||
| 182 | * @param int $index |
||
| 183 | */ |
||
| 184 | public function deregisterSession( $index ) { |
||
| 185 | unset( $this->requests[$index] ); |
||
| 186 | if ( !$this->shutdown && !count( $this->requests ) ) { |
||
| 187 | $this->save( true ); |
||
| 188 | $this->provider->getManager()->deregisterSessionBackend( $this ); |
||
| 189 | } |
||
| 190 | } |
||
| 191 | |||
| 192 | /** |
||
| 193 | * Shut down a session |
||
| 194 | * @private For use by \MediaWiki\Session\SessionManager::shutdown() only |
||
| 195 | */ |
||
| 196 | public function shutdown() { |
||
| 197 | $this->save( true ); |
||
| 198 | $this->shutdown = true; |
||
| 199 | } |
||
| 200 | |||
| 201 | /** |
||
| 202 | * Returns the session ID. |
||
| 203 | * @return string |
||
| 204 | */ |
||
| 205 | public function getId() { |
||
| 206 | return (string)$this->id; |
||
| 207 | } |
||
| 208 | |||
| 209 | /** |
||
| 210 | * Fetch the SessionId object |
||
| 211 | * @private For internal use by WebRequest |
||
| 212 | * @return SessionId |
||
| 213 | */ |
||
| 214 | public function getSessionId() { |
||
| 215 | return $this->id; |
||
| 216 | } |
||
| 217 | |||
| 218 | /** |
||
| 219 | * Changes the session ID |
||
| 220 | * @return string New ID (might be the same as the old) |
||
| 221 | */ |
||
| 222 | public function resetId() { |
||
| 223 | if ( $this->provider->persistsSessionId() ) { |
||
| 224 | $oldId = (string)$this->id; |
||
| 225 | $restart = $this->usePhpSessionHandling && $oldId === session_id() && |
||
| 226 | PHPSessionHandler::isEnabled(); |
||
| 227 | |||
| 228 | if ( $restart ) { |
||
| 229 | // If this session is the one behind PHP's $_SESSION, we need |
||
| 230 | // to close then reopen it. |
||
| 231 | session_write_close(); |
||
| 232 | } |
||
| 233 | |||
| 234 | $this->provider->getManager()->changeBackendId( $this ); |
||
| 235 | $this->provider->sessionIdWasReset( $this, $oldId ); |
||
| 236 | $this->metaDirty = true; |
||
| 237 | $this->logger->debug( |
||
| 238 | 'SessionBackend "{session}" metadata dirty due to ID reset (formerly "{oldId}")', |
||
| 239 | [ |
||
| 240 | 'session' => $this->id, |
||
| 241 | 'oldId' => $oldId, |
||
| 242 | ] ); |
||
| 243 | |||
| 244 | if ( $restart ) { |
||
| 245 | session_id( (string)$this->id ); |
||
| 246 | \MediaWiki\quietCall( 'session_start' ); |
||
| 247 | } |
||
| 248 | |||
| 249 | $this->autosave(); |
||
| 250 | |||
| 251 | // Delete the data for the old session ID now |
||
| 252 | $this->store->delete( wfMemcKey( 'MWSession', $oldId ) ); |
||
| 253 | } |
||
| 254 | } |
||
| 255 | |||
| 256 | /** |
||
| 257 | * Fetch the SessionProvider for this session |
||
| 258 | * @return SessionProviderInterface |
||
| 259 | */ |
||
| 260 | public function getProvider() { |
||
| 261 | return $this->provider; |
||
| 262 | } |
||
| 263 | |||
| 264 | /** |
||
| 265 | * Indicate whether this session is persisted across requests |
||
| 266 | * |
||
| 267 | * For example, if cookies are set. |
||
| 268 | * |
||
| 269 | * @return bool |
||
| 270 | */ |
||
| 271 | public function isPersistent() { |
||
| 272 | return $this->persist; |
||
| 273 | } |
||
| 274 | |||
| 275 | /** |
||
| 276 | * Make this session persisted across requests |
||
| 277 | * |
||
| 278 | * If the session is already persistent, equivalent to calling |
||
| 279 | * $this->renew(). |
||
| 280 | */ |
||
| 281 | public function persist() { |
||
| 282 | if ( !$this->persist ) { |
||
| 283 | $this->persist = true; |
||
| 284 | $this->forcePersist = true; |
||
| 285 | $this->metaDirty = true; |
||
| 286 | $this->logger->debug( |
||
| 287 | 'SessionBackend "{session}" force-persist due to persist()', |
||
| 288 | [ |
||
| 289 | 'session' => $this->id, |
||
| 290 | ] ); |
||
| 291 | $this->autosave(); |
||
| 292 | } else { |
||
| 293 | $this->renew(); |
||
| 294 | } |
||
| 295 | } |
||
| 296 | |||
| 297 | /** |
||
| 298 | * Make this session not persisted across requests |
||
| 299 | */ |
||
| 300 | public function unpersist() { |
||
| 301 | if ( $this->persist ) { |
||
| 302 | // Close the PHP session, if we're the one that's open |
||
| 303 | View Code Duplication | if ( $this->usePhpSessionHandling && PHPSessionHandler::isEnabled() && |
|
| 304 | session_id() === (string)$this->id |
||
| 305 | ) { |
||
| 306 | $this->logger->debug( |
||
| 307 | 'SessionBackend "{session}" Closing PHP session for unpersist', |
||
| 308 | [ 'session' => $this->id ] |
||
| 309 | ); |
||
| 310 | session_write_close(); |
||
| 311 | session_id( '' ); |
||
| 312 | } |
||
| 313 | |||
| 314 | $this->persist = false; |
||
| 315 | $this->forcePersist = true; |
||
| 316 | $this->metaDirty = true; |
||
| 317 | |||
| 318 | // Delete the session data, so the local cache-only write in |
||
| 319 | // self::save() doesn't get things out of sync with the backend. |
||
| 320 | $this->store->delete( wfMemcKey( 'MWSession', (string)$this->id ) ); |
||
| 321 | |||
| 322 | $this->autosave(); |
||
| 323 | } |
||
| 324 | } |
||
| 325 | |||
| 326 | /** |
||
| 327 | * Indicate whether the user should be remembered independently of the |
||
| 328 | * session ID. |
||
| 329 | * @return bool |
||
| 330 | */ |
||
| 331 | public function shouldRememberUser() { |
||
| 332 | return $this->remember; |
||
| 333 | } |
||
| 334 | |||
| 335 | /** |
||
| 336 | * Set whether the user should be remembered independently of the session |
||
| 337 | * ID. |
||
| 338 | * @param bool $remember |
||
| 339 | */ |
||
| 340 | View Code Duplication | public function setRememberUser( $remember ) { |
|
| 341 | if ( $this->remember !== (bool)$remember ) { |
||
| 342 | $this->remember = (bool)$remember; |
||
| 343 | $this->metaDirty = true; |
||
| 344 | $this->logger->debug( |
||
| 345 | 'SessionBackend "{session}" metadata dirty due to remember-user change', |
||
| 346 | [ |
||
| 347 | 'session' => $this->id, |
||
| 348 | ] ); |
||
| 349 | $this->autosave(); |
||
| 350 | } |
||
| 351 | } |
||
| 352 | |||
| 353 | /** |
||
| 354 | * Returns the request associated with a Session |
||
| 355 | * @param int $index Session index |
||
| 356 | * @return WebRequest |
||
| 357 | */ |
||
| 358 | public function getRequest( $index ) { |
||
| 359 | if ( !isset( $this->requests[$index] ) ) { |
||
| 360 | throw new \InvalidArgumentException( 'Invalid session index' ); |
||
| 361 | } |
||
| 362 | return $this->requests[$index]; |
||
| 363 | } |
||
| 364 | |||
| 365 | /** |
||
| 366 | * Returns the authenticated user for this session |
||
| 367 | * @return User |
||
| 368 | */ |
||
| 369 | public function getUser() { |
||
| 370 | return $this->user; |
||
| 371 | } |
||
| 372 | |||
| 373 | /** |
||
| 374 | * Fetch the rights allowed the user when this session is active. |
||
| 375 | * @return null|string[] Allowed user rights, or null to allow all. |
||
| 376 | */ |
||
| 377 | public function getAllowedUserRights() { |
||
| 378 | return $this->provider->getAllowedUserRights( $this ); |
||
| 379 | } |
||
| 380 | |||
| 381 | /** |
||
| 382 | * Indicate whether the session user info can be changed |
||
| 383 | * @return bool |
||
| 384 | */ |
||
| 385 | public function canSetUser() { |
||
| 386 | return $this->provider->canChangeUser(); |
||
| 387 | } |
||
| 388 | |||
| 389 | /** |
||
| 390 | * Set a new user for this session |
||
| 391 | * @note This should only be called when the user has been authenticated via a login process |
||
| 392 | * @param User $user User to set on the session. |
||
| 393 | * This may become a "UserValue" in the future, or User may be refactored |
||
| 394 | * into such. |
||
| 395 | */ |
||
| 396 | public function setUser( $user ) { |
||
| 397 | if ( !$this->canSetUser() ) { |
||
| 398 | throw new \BadMethodCallException( |
||
| 399 | 'Cannot set user on this session; check $session->canSetUser() first' |
||
| 400 | ); |
||
| 401 | } |
||
| 402 | |||
| 403 | $this->user = $user; |
||
| 404 | $this->metaDirty = true; |
||
| 405 | $this->logger->debug( |
||
| 406 | 'SessionBackend "{session}" metadata dirty due to user change', |
||
| 407 | [ |
||
| 408 | 'session' => $this->id, |
||
| 409 | ] ); |
||
| 410 | $this->autosave(); |
||
| 411 | } |
||
| 412 | |||
| 413 | /** |
||
| 414 | * Get a suggested username for the login form |
||
| 415 | * @param int $index Session index |
||
| 416 | * @return string|null |
||
| 417 | */ |
||
| 418 | public function suggestLoginUsername( $index ) { |
||
| 419 | if ( !isset( $this->requests[$index] ) ) { |
||
| 420 | throw new \InvalidArgumentException( 'Invalid session index' ); |
||
| 421 | } |
||
| 422 | return $this->provider->suggestLoginUsername( $this->requests[$index] ); |
||
| 423 | } |
||
| 424 | |||
| 425 | /** |
||
| 426 | * Whether HTTPS should be forced |
||
| 427 | * @return bool |
||
| 428 | */ |
||
| 429 | public function shouldForceHTTPS() { |
||
| 430 | return $this->forceHTTPS; |
||
| 431 | } |
||
| 432 | |||
| 433 | /** |
||
| 434 | * Set whether HTTPS should be forced |
||
| 435 | * @param bool $force |
||
| 436 | */ |
||
| 437 | View Code Duplication | public function setForceHTTPS( $force ) { |
|
| 438 | if ( $this->forceHTTPS !== (bool)$force ) { |
||
| 439 | $this->forceHTTPS = (bool)$force; |
||
| 440 | $this->metaDirty = true; |
||
| 441 | $this->logger->debug( |
||
| 442 | 'SessionBackend "{session}" metadata dirty due to force-HTTPS change', |
||
| 443 | [ |
||
| 444 | 'session' => $this->id, |
||
| 445 | ] ); |
||
| 446 | $this->autosave(); |
||
| 447 | } |
||
| 448 | } |
||
| 449 | |||
| 450 | /** |
||
| 451 | * Fetch the "logged out" timestamp |
||
| 452 | * @return int |
||
| 453 | */ |
||
| 454 | public function getLoggedOutTimestamp() { |
||
| 455 | return $this->loggedOut; |
||
| 456 | } |
||
| 457 | |||
| 458 | /** |
||
| 459 | * Set the "logged out" timestamp |
||
| 460 | * @param int $ts |
||
| 461 | */ |
||
| 462 | public function setLoggedOutTimestamp( $ts = null ) { |
||
| 463 | $ts = (int)$ts; |
||
| 464 | if ( $this->loggedOut !== $ts ) { |
||
| 465 | $this->loggedOut = $ts; |
||
| 466 | $this->metaDirty = true; |
||
| 467 | $this->logger->debug( |
||
| 468 | 'SessionBackend "{session}" metadata dirty due to logged-out-timestamp change', |
||
| 469 | [ |
||
| 470 | 'session' => $this->id, |
||
| 471 | ] ); |
||
| 472 | $this->autosave(); |
||
| 473 | } |
||
| 474 | } |
||
| 475 | |||
| 476 | /** |
||
| 477 | * Fetch provider metadata |
||
| 478 | * @protected For use by SessionProvider subclasses only |
||
| 479 | * @return array|null |
||
| 480 | */ |
||
| 481 | public function getProviderMetadata() { |
||
| 482 | return $this->providerMetadata; |
||
| 483 | } |
||
| 484 | |||
| 485 | /** |
||
| 486 | * Set provider metadata |
||
| 487 | * @protected For use by SessionProvider subclasses only |
||
| 488 | * @param array|null $metadata |
||
| 489 | */ |
||
| 490 | public function setProviderMetadata( $metadata ) { |
||
| 491 | if ( $metadata !== null && !is_array( $metadata ) ) { |
||
| 492 | throw new \InvalidArgumentException( '$metadata must be an array or null' ); |
||
| 493 | } |
||
| 494 | if ( $this->providerMetadata !== $metadata ) { |
||
| 495 | $this->providerMetadata = $metadata; |
||
| 496 | $this->metaDirty = true; |
||
| 497 | $this->logger->debug( |
||
| 498 | 'SessionBackend "{session}" metadata dirty due to provider metadata change', |
||
| 499 | [ |
||
| 500 | 'session' => $this->id, |
||
| 501 | ] ); |
||
| 502 | $this->autosave(); |
||
| 503 | } |
||
| 504 | } |
||
| 505 | |||
| 506 | /** |
||
| 507 | * Fetch the session data array |
||
| 508 | * |
||
| 509 | * Note the caller is responsible for calling $this->dirty() if anything in |
||
| 510 | * the array is changed. |
||
| 511 | * |
||
| 512 | * @private For use by \MediaWiki\Session\Session only. |
||
| 513 | * @return array |
||
| 514 | */ |
||
| 515 | public function &getData() { |
||
| 516 | return $this->data; |
||
| 517 | } |
||
| 518 | |||
| 519 | /** |
||
| 520 | * Add data to the session. |
||
| 521 | * |
||
| 522 | * Overwrites any existing data under the same keys. |
||
| 523 | * |
||
| 524 | * @param array $newData Key-value pairs to add to the session |
||
| 525 | */ |
||
| 526 | public function addData( array $newData ) { |
||
| 527 | $data = &$this->getData(); |
||
| 528 | foreach ( $newData as $key => $value ) { |
||
| 529 | if ( !array_key_exists( $key, $data ) || $data[$key] !== $value ) { |
||
| 530 | $data[$key] = $value; |
||
| 531 | $this->dataDirty = true; |
||
| 532 | $this->logger->debug( |
||
| 533 | 'SessionBackend "{session}" data dirty due to addData(): {callers}', |
||
| 534 | [ |
||
| 535 | 'session' => $this->id, |
||
| 536 | 'callers' => wfGetAllCallers( 5 ), |
||
| 537 | ] ); |
||
| 538 | } |
||
| 539 | } |
||
| 540 | } |
||
| 541 | |||
| 542 | /** |
||
| 543 | * Mark data as dirty |
||
| 544 | * @private For use by \MediaWiki\Session\Session only. |
||
| 545 | */ |
||
| 546 | public function dirty() { |
||
| 547 | $this->dataDirty = true; |
||
| 548 | $this->logger->debug( |
||
| 549 | 'SessionBackend "{session}" data dirty due to dirty(): {callers}', |
||
| 550 | [ |
||
| 551 | 'session' => $this->id, |
||
| 552 | 'callers' => wfGetAllCallers( 5 ), |
||
| 553 | ] ); |
||
| 554 | } |
||
| 555 | |||
| 556 | /** |
||
| 557 | * Renew the session by resaving everything |
||
| 558 | * |
||
| 559 | * Resets the TTL in the backend store if the session is near expiring, and |
||
| 560 | * re-persists the session to any active WebRequests if persistent. |
||
| 561 | */ |
||
| 562 | public function renew() { |
||
| 563 | if ( time() + $this->lifetime / 2 > $this->expires ) { |
||
| 564 | $this->metaDirty = true; |
||
| 565 | $this->logger->debug( |
||
| 566 | 'SessionBackend "{callers}" metadata dirty for renew(): {callers}', |
||
| 567 | [ |
||
| 568 | 'session' => $this->id, |
||
| 569 | 'callers' => wfGetAllCallers( 5 ), |
||
| 570 | ] ); |
||
| 571 | if ( $this->persist ) { |
||
| 572 | $this->forcePersist = true; |
||
| 573 | $this->logger->debug( |
||
| 574 | 'SessionBackend "{session}" force-persist for renew(): {callers}', |
||
| 575 | [ |
||
| 576 | 'session' => $this->id, |
||
| 577 | 'callers' => wfGetAllCallers( 5 ), |
||
| 578 | ] ); |
||
| 579 | } |
||
| 580 | } |
||
| 581 | $this->autosave(); |
||
| 582 | } |
||
| 583 | |||
| 584 | /** |
||
| 585 | * Delay automatic saving while multiple updates are being made |
||
| 586 | * |
||
| 587 | * Calls to save() will not be delayed. |
||
| 588 | * |
||
| 589 | * @return \Wikimedia\ScopedCallback When this goes out of scope, a save will be triggered |
||
| 590 | */ |
||
| 591 | public function delaySave() { |
||
| 592 | $this->delaySave++; |
||
| 593 | return new \Wikimedia\ScopedCallback( function () { |
||
| 594 | if ( --$this->delaySave <= 0 ) { |
||
| 595 | $this->delaySave = 0; |
||
| 596 | $this->save(); |
||
| 597 | } |
||
| 598 | } ); |
||
| 599 | } |
||
| 600 | |||
| 601 | /** |
||
| 602 | * Save the session, unless delayed |
||
| 603 | * @see SessionBackend::save() |
||
| 604 | */ |
||
| 605 | private function autosave() { |
||
| 606 | if ( $this->delaySave <= 0 ) { |
||
| 607 | $this->save(); |
||
| 608 | } |
||
| 609 | } |
||
| 610 | |||
| 611 | /** |
||
| 612 | * Save the session |
||
| 613 | * |
||
| 614 | * Update both the backend data and the associated WebRequest(s) to |
||
| 615 | * reflect the state of the the SessionBackend. This might include |
||
| 616 | * persisting or unpersisting the session. |
||
| 617 | * |
||
| 618 | * @param bool $closing Whether the session is being closed |
||
| 619 | */ |
||
| 620 | public function save( $closing = false ) { |
||
| 621 | $anon = $this->user->isAnon(); |
||
| 622 | |||
| 623 | if ( !$anon && $this->provider->getManager()->isUserSessionPrevented( $this->user->getName() ) ) { |
||
| 624 | $this->logger->debug( |
||
| 625 | 'SessionBackend "{session}" not saving, user {user} was ' . |
||
| 626 | 'passed to SessionManager::preventSessionsForUser', |
||
| 627 | [ |
||
| 628 | 'session' => $this->id, |
||
| 629 | 'user' => $this->user, |
||
| 630 | ] ); |
||
| 631 | return; |
||
| 632 | } |
||
| 633 | |||
| 634 | // Ensure the user has a token |
||
| 635 | // @codeCoverageIgnoreStart |
||
| 636 | if ( !$anon && !$this->user->getToken( false ) ) { |
||
|
0 ignored issues
–
show
|
|||
| 637 | $this->logger->debug( |
||
| 638 | 'SessionBackend "{session}" creating token for user {user} on save', |
||
| 639 | [ |
||
| 640 | 'session' => $this->id, |
||
| 641 | 'user' => $this->user, |
||
| 642 | ] ); |
||
| 643 | $this->user->setToken(); |
||
| 644 | if ( !wfReadOnly() ) { |
||
| 645 | // Promise that the token set here will be valid; save it at end of request |
||
| 646 | $user = $this->user; |
||
| 647 | \DeferredUpdates::addCallableUpdate( function () use ( $user ) { |
||
| 648 | $user->saveSettings(); |
||
| 649 | } ); |
||
| 650 | } |
||
| 651 | $this->metaDirty = true; |
||
| 652 | } |
||
| 653 | // @codeCoverageIgnoreEnd |
||
| 654 | |||
| 655 | if ( !$this->metaDirty && !$this->dataDirty && |
||
| 656 | $this->dataHash !== md5( serialize( $this->data ) ) |
||
| 657 | ) { |
||
| 658 | $this->logger->debug( |
||
| 659 | 'SessionBackend "{session}" data dirty due to hash mismatch, {expected} !== {got}', |
||
| 660 | [ |
||
| 661 | 'session' => $this->id, |
||
| 662 | 'expected' => $this->dataHash, |
||
| 663 | 'got' => md5( serialize( $this->data ) ), |
||
| 664 | ] ); |
||
| 665 | $this->dataDirty = true; |
||
| 666 | } |
||
| 667 | |||
| 668 | if ( !$this->metaDirty && !$this->dataDirty && !$this->forcePersist ) { |
||
| 669 | return; |
||
| 670 | } |
||
| 671 | |||
| 672 | $this->logger->debug( |
||
| 673 | 'SessionBackend "{session}" save: dataDirty={dataDirty} ' . |
||
| 674 | 'metaDirty={metaDirty} forcePersist={forcePersist}', |
||
| 675 | [ |
||
| 676 | 'session' => $this->id, |
||
| 677 | 'dataDirty' => (int)$this->dataDirty, |
||
| 678 | 'metaDirty' => (int)$this->metaDirty, |
||
| 679 | 'forcePersist' => (int)$this->forcePersist, |
||
| 680 | ] ); |
||
| 681 | |||
| 682 | // Persist or unpersist to the provider, if necessary |
||
| 683 | if ( $this->metaDirty || $this->forcePersist ) { |
||
| 684 | if ( $this->persist ) { |
||
| 685 | foreach ( $this->requests as $request ) { |
||
| 686 | $request->setSessionId( $this->getSessionId() ); |
||
| 687 | $this->provider->persistSession( $this, $request ); |
||
| 688 | } |
||
| 689 | if ( !$closing ) { |
||
| 690 | $this->checkPHPSession(); |
||
| 691 | } |
||
| 692 | } else { |
||
| 693 | foreach ( $this->requests as $request ) { |
||
| 694 | if ( $request->getSessionId() === $this->id ) { |
||
| 695 | $this->provider->unpersistSession( $request ); |
||
| 696 | } |
||
| 697 | } |
||
| 698 | } |
||
| 699 | } |
||
| 700 | |||
| 701 | $this->forcePersist = false; |
||
| 702 | |||
| 703 | if ( !$this->metaDirty && !$this->dataDirty ) { |
||
| 704 | return; |
||
| 705 | } |
||
| 706 | |||
| 707 | // Save session data to store, if necessary |
||
| 708 | $metadata = $origMetadata = [ |
||
| 709 | 'provider' => (string)$this->provider, |
||
| 710 | 'providerMetadata' => $this->providerMetadata, |
||
| 711 | 'userId' => $anon ? 0 : $this->user->getId(), |
||
| 712 | 'userName' => User::isValidUserName( $this->user->getName() ) ? $this->user->getName() : null, |
||
| 713 | 'userToken' => $anon ? null : $this->user->getToken(), |
||
| 714 | 'remember' => !$anon && $this->remember, |
||
| 715 | 'forceHTTPS' => $this->forceHTTPS, |
||
| 716 | 'expires' => time() + $this->lifetime, |
||
| 717 | 'loggedOut' => $this->loggedOut, |
||
| 718 | 'persisted' => $this->persist, |
||
| 719 | ]; |
||
| 720 | |||
| 721 | \Hooks::run( 'SessionMetadata', [ $this, &$metadata, $this->requests ] ); |
||
| 722 | |||
| 723 | foreach ( $origMetadata as $k => $v ) { |
||
| 724 | if ( $metadata[$k] !== $v ) { |
||
| 725 | throw new \UnexpectedValueException( "SessionMetadata hook changed metadata key \"$k\"" ); |
||
| 726 | } |
||
| 727 | } |
||
| 728 | |||
| 729 | $flags = $this->persist ? 0 : CachedBagOStuff::WRITE_CACHE_ONLY; |
||
| 730 | $flags |= CachedBagOStuff::WRITE_SYNC; // write to all datacenters |
||
| 731 | $this->store->set( |
||
| 732 | wfMemcKey( 'MWSession', (string)$this->id ), |
||
| 733 | [ |
||
| 734 | 'data' => $this->data, |
||
| 735 | 'metadata' => $metadata, |
||
| 736 | ], |
||
| 737 | $metadata['expires'], |
||
| 738 | $flags |
||
| 739 | ); |
||
| 740 | |||
| 741 | $this->metaDirty = false; |
||
| 742 | $this->dataDirty = false; |
||
| 743 | $this->dataHash = md5( serialize( $this->data ) ); |
||
| 744 | $this->expires = $metadata['expires']; |
||
| 745 | } |
||
| 746 | |||
| 747 | /** |
||
| 748 | * For backwards compatibility, open the PHP session when the global |
||
| 749 | * session is persisted |
||
| 750 | */ |
||
| 751 | private function checkPHPSession() { |
||
| 752 | if ( !$this->checkPHPSessionRecursionGuard ) { |
||
| 753 | $this->checkPHPSessionRecursionGuard = true; |
||
| 754 | $reset = new \Wikimedia\ScopedCallback( function () { |
||
|
0 ignored issues
–
show
$reset is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the Loading history...
|
|||
| 755 | $this->checkPHPSessionRecursionGuard = false; |
||
| 756 | } ); |
||
| 757 | |||
| 758 | View Code Duplication | if ( $this->usePhpSessionHandling && session_id() === '' && PHPSessionHandler::isEnabled() && |
|
| 759 | SessionManager::getGlobalSession()->getId() === (string)$this->id |
||
| 760 | ) { |
||
| 761 | $this->logger->debug( |
||
| 762 | 'SessionBackend "{session}" Taking over PHP session', |
||
| 763 | [ |
||
| 764 | 'session' => $this->id, |
||
| 765 | ] ); |
||
| 766 | session_id( (string)$this->id ); |
||
| 767 | \MediaWiki\quietCall( 'session_start' ); |
||
| 768 | } |
||
| 769 | } |
||
| 770 | } |
||
| 771 | |||
| 772 | } |
||
| 773 |
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
stringvalues, the empty string''is a special case, in particular the following results might be unexpected: