X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fsession%2FCookieSessionProvider.php;h=74925bd7b6f7056df21f6f1f8a545de04586e4a7;hb=b95ca29602793f39191c06cd6941e3f32ab1bbb8;hp=f989cbc7f26352f9811266e5cf86be52ed8cb7f6;hpb=e139b0c0df0a9be71c038abc14438aab16a92ddb;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/session/CookieSessionProvider.php b/includes/session/CookieSessionProvider.php index f989cbc7f2..74925bd7b6 100644 --- a/includes/session/CookieSessionProvider.php +++ b/includes/session/CookieSessionProvider.php @@ -35,8 +35,8 @@ use WebRequest; */ class CookieSessionProvider extends SessionProvider { - protected $params = array(); - protected $cookieOptions = array(); + protected $params = []; + protected $cookieOptions = []; /** * @param array $params Keys include: @@ -51,13 +51,13 @@ class CookieSessionProvider extends SessionProvider { * - secure: Cookie secure flag, defaults to $wgCookieSecure * - httpOnly: Cookie httpOnly flag, defaults to $wgCookieHttpOnly */ - public function __construct( $params = array() ) { + public function __construct( $params = [] ) { parent::__construct(); - $params += array( - 'cookieOptions' => array(), + $params += [ + 'cookieOptions' => [], // @codeCoverageIgnoreStart - ); + ]; // @codeCoverageIgnoreEnd if ( !isset( $params['priority'] ) ) { @@ -84,34 +84,34 @@ class CookieSessionProvider extends SessionProvider { parent::setConfig( $config ); // @codeCoverageIgnoreStart - $this->params += array( + $this->params += [ // @codeCoverageIgnoreEnd 'callUserSetCookiesHook' => false, 'sessionName' => $config->get( 'SessionName' ) ?: $config->get( 'CookiePrefix' ) . '_session', - ); + ]; // @codeCoverageIgnoreStart - $this->cookieOptions += array( + $this->cookieOptions += [ // @codeCoverageIgnoreEnd 'prefix' => $config->get( 'CookiePrefix' ), 'path' => $config->get( 'CookiePath' ), 'domain' => $config->get( 'CookieDomain' ), 'secure' => $config->get( 'CookieSecure' ), 'httpOnly' => $config->get( 'CookieHttpOnly' ), - ); + ]; } public function provideSessionInfo( WebRequest $request ) { - $info = array( - 'id' => $this->getCookie( $request, $this->params['sessionName'], '' ), + $sessionId = $this->getCookie( $request, $this->params['sessionName'], '' ); + $info = [ 'provider' => $this, 'forceHTTPS' => $this->getCookie( $request, 'forceHTTPS', '', false ) - ); - if ( !SessionManager::validateSessionId( $info['id'] ) ) { - unset( $info['id'] ); + ]; + if ( SessionManager::validateSessionId( $sessionId ) ) { + $info['id'] = $sessionId; + $info['persisted'] = true; } - $info['persisted'] = isset( $info['id'] ); list( $userId, $userName, $token ) = $this->getUserInfoFromCookies( $request ); if ( $userId !== null ) { @@ -123,14 +123,32 @@ class CookieSessionProvider extends SessionProvider { // Sanity check if ( $userName !== null && $userInfo->getName() !== $userName ) { + $this->logger->warning( + 'Session "{session}" requested with mismatched UserID and UserName cookies.', + [ + 'session' => $sessionId, + 'mismatch' => [ + 'userid' => $userId, + 'cookie_username' => $userName, + 'username' => $userInfo->getName(), + ], + ] ); return null; } if ( $token !== null ) { if ( !hash_equals( $userInfo->getToken(), $token ) ) { + $this->logger->warning( + 'Session "{session}" requested with invalid Token cookie.', + [ + 'session' => $sessionId, + 'userid' => $userId, + 'username' => $userInfo->getName(), + ] ); return null; } $info['userInfo'] = $userInfo->verified(); + $info['persisted'] = true; // If we have user+token, it should be } elseif ( isset( $info['id'] ) ) { $info['userInfo'] = $userInfo; } else { @@ -140,6 +158,15 @@ class CookieSessionProvider extends SessionProvider { } } elseif ( isset( $info['id'] ) ) { // No UserID cookie, so insist that the session is anonymous. + // Note: this event occurs for several normal activities: + // * anon visits Special:UserLogin + // * anon browsing after seeing Special:UserLogin + // * anon browsing after edit or preview + $this->logger->debug( + 'Session "{session}" requested without UserID cookie', + [ + 'session' => $info['id'], + ] ); $info['userInfo'] = UserInfo::newAnonymous(); } else { // No session ID and no user is the same as an empty session, so @@ -173,7 +200,7 @@ class CookieSessionProvider extends SessionProvider { // Legacy hook if ( $this->params['callUserSetCookiesHook'] && !$user->isAnon() ) { - \Hooks::run( 'UserSetCookies', array( $user, &$sessionData, &$cookies ) ); + \Hooks::run( 'UserSetCookies', [ $user, &$sessionData, &$cookies ] ); } $options = $this->cookieOptions; @@ -187,22 +214,16 @@ class CookieSessionProvider extends SessionProvider { } $response->setCookie( $this->params['sessionName'], $session->getId(), null, - array( 'prefix' => '' ) + $options + [ 'prefix' => '' ] + $options ); - $extendedCookies = $this->config->get( 'ExtendedLoginCookies' ); - $extendedExpiry = $this->config->get( 'ExtendedLoginCookieExpiration' ); - foreach ( $cookies as $key => $value ) { if ( $value === false ) { $response->clearCookie( $key, $options ); } else { - if ( $extendedExpiry !== null && in_array( $key, $extendedCookies ) ) { - $expiry = time() + (int)$extendedExpiry; - } else { - $expiry = 0; // Default cookie expiration - } - $response->setCookie( $key, (string)$value, $expiry, $options ); + $expirationDuration = $this->getLoginCookieExpiration( $key, $session->shouldRememberUser() ); + $expiration = $expirationDuration ? $expirationDuration + time() : null; + $response->setCookie( $key, (string)$value, $expiration, $options ); } } @@ -222,13 +243,13 @@ class CookieSessionProvider extends SessionProvider { return; } - $cookies = array( + $cookies = [ 'UserID' => false, 'Token' => false, - ); + ]; $response->clearCookie( - $this->params['sessionName'], array( 'prefix' => '' ) + $this->cookieOptions + $this->params['sessionName'], [ 'prefix' => '' ] + $this->cookieOptions ); foreach ( $cookies as $key => $value ) { @@ -249,11 +270,20 @@ class CookieSessionProvider extends SessionProvider { ) { $response = $request->response(); if ( $set ) { - $response->setCookie( 'forceHTTPS', 'true', $backend->shouldRememberUser() ? 0 : null, - array( 'prefix' => '', 'secure' => false ) + $this->cookieOptions ); + if ( $backend->shouldRememberUser() ) { + $expirationDuration = $this->getLoginCookieExpiration( + 'forceHTTPS', + true + ); + $expiration = $expirationDuration ? $expirationDuration + time() : null; + } else { + $expiration = null; + } + $response->setCookie( 'forceHTTPS', 'true', $expiration, + [ 'prefix' => '', 'secure' => false ] + $this->cookieOptions ); } else { $response->clearCookie( 'forceHTTPS', - array( 'prefix' => '', 'secure' => false ) + $this->cookieOptions ); + [ 'prefix' => '', 'secure' => false ] + $this->cookieOptions ); } } @@ -272,14 +302,14 @@ class CookieSessionProvider extends SessionProvider { } public function getVaryCookies() { - return array( + return [ // Vary on token and session because those are the real authn // determiners. UserID and UserName don't matter without those. $this->cookieOptions['prefix'] . 'Token', $this->cookieOptions['prefix'] . 'LoggedOut', $this->params['sessionName'], 'forceHTTPS', - ); + ]; } public function suggestLoginUsername( WebRequest $request ) { @@ -297,11 +327,11 @@ class CookieSessionProvider extends SessionProvider { */ protected function getUserInfoFromCookies( $request ) { $prefix = $this->cookieOptions['prefix']; - return array( + return [ $this->getCookie( $request, 'UserID', $prefix ), $this->getCookie( $request, 'UserName', $prefix ), $this->getCookie( $request, 'Token', $prefix ), - ); + ]; } /** @@ -333,16 +363,16 @@ class CookieSessionProvider extends SessionProvider { */ protected function cookieDataToExport( $user, $remember ) { if ( $user->isAnon() ) { - return array( + return [ 'UserID' => false, 'Token' => false, - ); + ]; } else { - return array( + return [ 'UserID' => $user->getId(), 'UserName' => $user->getName(), 'Token' => $remember ? (string)$user->getToken() : false, - ); + ]; } } @@ -355,18 +385,55 @@ class CookieSessionProvider extends SessionProvider { // If we're calling the legacy hook, we should populate $session // like User::setCookies() did. if ( !$user->isAnon() && $this->params['callUserSetCookiesHook'] ) { - return array( + return [ 'wsUserID' => $user->getId(), 'wsToken' => $user->getToken(), 'wsUserName' => $user->getName(), - ); + ]; } - return array(); + return []; } public function whyNoSession() { return wfMessage( 'sessionprovider-nocookies' ); } + public function getRememberUserDuration() { + return min( $this->getLoginCookieExpiration( 'UserID', true ), + $this->getLoginCookieExpiration( 'Token', true ) ) ?: null; + } + + /** + * Gets the list of cookies that must be set to the 'remember me' duration, + * if $wgExtendedLoginCookieExpiration is in use. + * + * @return string[] Array of unprefixed cookie keys + */ + protected function getExtendedLoginCookies() { + return [ 'UserID', 'UserName', 'Token' ]; + } + + /** + * Returns the lifespan of the login cookies, in seconds. 0 means until the end of the session. + * + * Cookies that are session-length do not call this function. + * + * @param string $cookieName + * @param bool $shouldRememberUser Whether the user should be remembered + * long-term + * @return int Cookie expiration time in seconds; 0 for session cookies + */ + protected function getLoginCookieExpiration( $cookieName, $shouldRememberUser ) { + $extendedCookies = $this->getExtendedLoginCookies(); + $normalExpiration = $this->config->get( 'CookieExpiration' ); + + if ( $shouldRememberUser && in_array( $cookieName, $extendedCookies, true ) ) { + $extendedExpiration = $this->config->get( 'ExtendedLoginCookieExpiration' ); + + return ( $extendedExpiration !== null ) ? (int)$extendedExpiration : (int)$normalExpiration; + } else { + return (int)$normalExpiration; + } + } }