* @file
*/
+use MediaWiki\Session\SessionManager;
+
/**
* String Some punctuation to prevent editing from broken text-mangling proxies.
* @ingroup Constants
'apihighlimits',
'applychangetags',
'autoconfirmed',
+ 'autocreateaccount',
'autopatrol',
'bigdelete',
'block',
* - 'defaults' anonymous user initialised from class defaults
* - 'name' initialise from mName
* - 'id' initialise from mId
- * - 'session' log in from cookies or session if possible
+ * - 'session' log in from session if possible
*
* Use the User::newFrom*() family of functions to set this.
*/
* @param integer $flags User::READ_* constant bitfield
*/
public function load( $flags = self::READ_NORMAL ) {
+ global $wgFullyInitialised;
+
if ( $this->mLoadedItems === true ) {
return;
}
// Set it now to avoid infinite recursion in accessors
+ $oldLoadedItems = $this->mLoadedItems;
$this->mLoadedItems = true;
$this->queryFlagsUsed = $flags;
+ // If this is called too early, things are likely to break.
+ if ( $this->mFrom === 'session' && empty( $wgFullyInitialised ) ) {
+ \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
+ ->warning( 'User::loadFromSession called before the end of Setup.php' );
+ $this->loadDefaults();
+ $this->mLoadedItems = $oldLoadedItems;
+ return;
+ }
+
switch ( $this->mFrom ) {
case 'defaults':
$this->loadDefaults();
}
/**
- * Create a new user object using data from session or cookies. If the
- * login credentials are invalid, the result is an anonymous user.
+ * Create a new user object using data from session. If the login
+ * credentials are invalid, the result is an anonymous user.
*
* @param WebRequest|null $request Object to use; $wgRequest will be used if omitted.
* @return User
$user->saveSettings();
}
+ SessionManager::singleton()->preventSessionsForUser( $user->getName() );
+
return $user;
}
$this->mOptionOverrides = null;
$this->mOptionsLoaded = false;
- $loggedOut = $this->getRequest()->getCookie( 'LoggedOut' );
- if ( $loggedOut !== null ) {
+ $request = $this->getRequest();
+ $loggedOut = $request ? $request->getSession()->getLoggedOutTimestamp() : 0;
+ if ( $loggedOut !== 0 ) {
$this->mTouched = wfTimestamp( TS_MW, $loggedOut );
} else {
$this->mTouched = '1'; # Allow any pages to be cached
}
/**
- * Load user data from the session or login cookie.
+ * Load user data from the session.
*
* @return bool True if the user is logged in, false otherwise.
*/
private function loadFromSession() {
+ // Deprecated hook
$result = null;
- Hooks::run( 'UserLoadFromSession', array( $this, &$result ) );
+ Hooks::run( 'UserLoadFromSession', array( $this, &$result ), '1.27' );
if ( $result !== null ) {
return $result;
}
- $request = $this->getRequest();
-
- $cookieId = $request->getCookie( 'UserID' );
- $sessId = $request->getSessionData( 'wsUserID' );
-
- if ( $cookieId !== null ) {
- $sId = intval( $cookieId );
- if ( $sessId !== null && $cookieId != $sessId ) {
- wfDebugLog( 'loginSessions', "Session user ID ($sessId) and
- cookie user ID ($sId) don't match!" );
- return false;
- }
- $request->setSessionData( 'wsUserID', $sId );
- } elseif ( $sessId !== null && $sessId != 0 ) {
- $sId = $sessId;
- } else {
- return false;
- }
-
- if ( $request->getSessionData( 'wsUserName' ) !== null ) {
- $sName = $request->getSessionData( 'wsUserName' );
- } elseif ( $request->getCookie( 'UserName' ) !== null ) {
- $sName = $request->getCookie( 'UserName' );
- $request->setSessionData( 'wsUserName', $sName );
- } else {
- return false;
- }
-
- $proposedUser = User::newFromId( $sId );
- if ( !$proposedUser->isLoggedIn() ) {
- // Not a valid ID
- return false;
- }
-
- global $wgBlockDisablesLogin;
- if ( $wgBlockDisablesLogin && $proposedUser->isBlocked() ) {
- // User blocked and we've disabled blocked user logins
- return false;
- }
-
- if ( $request->getSessionData( 'wsToken' ) ) {
- $passwordCorrect =
- ( $proposedUser->getToken( false ) === $request->getSessionData( 'wsToken' ) );
- $from = 'session';
- } elseif ( $request->getCookie( 'Token' ) ) {
- # Get the token from DB/cache and clean it up to remove garbage padding.
- # This deals with historical problems with bugs and the default column value.
- $token = rtrim( $proposedUser->getToken( false ) ); // correct token
- // Make comparison in constant time (bug 61346)
- $passwordCorrect = strlen( $token )
- && hash_equals( $token, $request->getCookie( 'Token' ) );
- $from = 'cookie';
- } else {
- // No session or persistent login cookie
- return false;
- }
-
- if ( ( $sName === $proposedUser->getName() ) && $passwordCorrect ) {
- $this->loadFromUserObject( $proposedUser );
- $request->setSessionData( 'wsToken', $this->mToken );
- wfDebug( "User: logged in from $from\n" );
+ // MediaWiki\Session\Session already did the necessary authentication of the user
+ // returned here, so just use it if applicable.
+ $session = $this->getRequest()->getSession();
+ $user = $session->getUser();
+ if ( $user->isLoggedIn() ) {
+ $this->loadFromUserObject( $user );
+ // Other code expects these to be set in the session, so set them.
+ $session->set( 'wsUserID', $this->getId() );
+ $session->set( 'wsUserName', $this->getName() );
+ $session->set( 'wsToken', $this->mToken );
return true;
- } else {
- // Invalid credentials
- wfDebug( "User: can't log in from $from, invalid credentials\n" );
- return false;
}
+
+ return false;
}
/**
),
__METHOD__
);
+
+ // When the main password is changed, invalidate all bot passwords too
+ BotPassword::invalidateAllPasswordsForUser( $this->getName() );
}
/**
public function getRights() {
if ( is_null( $this->mRights ) ) {
$this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
+
+ $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
+ if ( $allowedRights !== null ) {
+ $this->mRights = array_intersect( $this->mRights, $allowedRights );
+ }
+
Hooks::run( 'UserGetRights', array( $this, &$this->mRights ) );
// Force reindexation of rights when a hook has unset one of them
$this->mRights = array_values( array_unique( $this->mRights ) );
/**
* Set a cookie on the user's client. Wrapper for
* WebResponse::setCookie
+ * @deprecated since 1.27
* @param string $name Name of the cookie to set
* @param string $value Value to set
* @param int $exp Expiration time, as a UNIX time value;
protected function setCookie(
$name, $value, $exp = 0, $secure = null, $params = array(), $request = null
) {
+ wfDeprecated( __METHOD__, '1.27' );
if ( $request === null ) {
$request = $this->getRequest();
}
/**
* Clear a cookie on the user's client
+ * @deprecated since 1.27
* @param string $name Name of the cookie to clear
* @param bool $secure
* true: Force setting the secure attribute when setting the cookie
* @param array $params Array of options sent passed to WebResponse::setcookie()
*/
protected function clearCookie( $name, $secure = null, $params = array() ) {
+ wfDeprecated( __METHOD__, '1.27' );
$this->setCookie( $name, '', time() - 86400, $secure, $params );
}
*
* @see User::setCookie
*
+ * @deprecated since 1.27
* @param string $name Name of the cookie to set
* @param string $value Value to set
* @param bool $secure
protected function setExtendedLoginCookie( $name, $value, $secure ) {
global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
+ wfDeprecated( __METHOD__, '1.27' );
+
$exp = time();
$exp += $wgExtendedLoginCookieExpiration !== null
? $wgExtendedLoginCookieExpiration
}
/**
- * Set the default cookies for this session on the user's client.
+ * Persist this user's session (e.g. set cookies)
*
* @param WebRequest|null $request WebRequest object to use; $wgRequest will be used if null
* is passed.
* @param bool $rememberMe Whether to add a Token cookie for elongated sessions
*/
public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
- global $wgExtendedLoginCookies;
-
- if ( $request === null ) {
- $request = $this->getRequest();
- }
-
$this->load();
if ( 0 == $this->mId ) {
return;
}
- if ( !$this->mToken ) {
- // When token is empty or NULL generate a new one and then save it to the database
- // This allows a wiki to re-secure itself after a leak of it's user table or $wgSecretKey
- // Simply by setting every cell in the user_token column to NULL and letting them be
- // regenerated as users log back into the wiki.
- $this->setToken();
- if ( !wfReadOnly() ) {
- $this->saveSettings();
- }
- }
- $session = array(
- 'wsUserID' => $this->mId,
- 'wsToken' => $this->mToken,
- 'wsUserName' => $this->getName()
- );
- $cookies = array(
- 'UserID' => $this->mId,
- 'UserName' => $this->getName(),
- );
- if ( $rememberMe ) {
- $cookies['Token'] = $this->mToken;
- } else {
- $cookies['Token'] = false;
- }
-
- Hooks::run( 'UserSetCookies', array( $this, &$session, &$cookies ) );
- foreach ( $session as $name => $value ) {
- $request->setSessionData( $name, $value );
+ $session = $this->getRequest()->getSession();
+ if ( $request && $session->getRequest() !== $request ) {
+ $session = $session->sessionWithRequest( $request );
}
- foreach ( $cookies as $name => $value ) {
- if ( $value === false ) {
- $this->clearCookie( $name );
- } elseif ( $rememberMe && in_array( $name, $wgExtendedLoginCookies ) ) {
- $this->setExtendedLoginCookie( $name, $value, $secure );
- } else {
- $this->setCookie( $name, $value, 0, $secure, array(), $request );
+ $delay = $session->delaySave();
+
+ if ( !$session->getUser()->equals( $this ) ) {
+ if ( !$session->canSetUser() ) {
+ \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
+ ->warning( __METHOD__ .
+ ": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
+ );
+ return;
}
+ $session->setUser( $this );
}
- /**
- * If wpStickHTTPS was selected, also set an insecure cookie that
- * will cause the site to redirect the user to HTTPS, if they access
- * it over HTTP. Bug 29898. Use an un-prefixed cookie, so it's the same
- * as the one set by centralauth (bug 53538). Also set it to session, or
- * standard time setting, based on if rememberme was set.
- */
- if ( $request->getCheck( 'wpStickHTTPS' ) || $this->requiresHTTPS() ) {
- $this->setCookie(
- 'forceHTTPS',
- 'true',
- $rememberMe ? 0 : null,
- false,
- array( 'prefix' => '' ) // no prefix
- );
+ $session->setRememberUser( $rememberMe );
+ if ( $secure !== null ) {
+ $session->setForceHTTPS( $secure );
}
+
+ $session->persist();
+
+ ScopedCallback::consume( $delay );
}
/**
}
/**
- * Clear the user's cookies and session, and reset the instance cache.
+ * Clear the user's session, and reset the instance cache.
* @see logout()
*/
public function doLogout() {
- $this->clearInstanceCache( 'defaults' );
-
- $this->getRequest()->setSessionData( 'wsUserID', 0 );
-
- $this->clearCookie( 'UserID' );
- $this->clearCookie( 'Token' );
- $this->clearCookie( 'forceHTTPS', false, array( 'prefix' => '' ) );
-
- // Remember when user logged out, to prevent seeing cached pages
- $this->setCookie( 'LoggedOut', time(), time() + 86400 );
+ $session = $this->getRequest()->getSession();
+ if ( !$session->canSetUser() ) {
+ \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
+ ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
+ } elseif ( !$session->getUser()->equals( $this ) ) {
+ \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
+ ->warning( __METHOD__ .
+ ": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
+ );
+ // But we still may as well make this user object anon
+ $this->clearInstanceCache( 'defaults' );
+ } else {
+ $this->clearInstanceCache( 'defaults' );
+ $delay = $session->delaySave();
+ $session->setLoggedOutTimestamp( time() );
+ $session->setUser( new User );
+ $session->set( 'wsUserID', 0 ); // Other code expects this
+ ScopedCallback::consume( $delay );
+ }
}
/**
);
}
- /**
- * Generate a looking random token for various uses.
- *
- * @return string The new random token
- * @deprecated since 1.20: Use MWCryptRand for secure purposes or
- * wfRandomString for pseudo-randomness.
- */
- public static function generateToken() {
- return MWCryptRand::generateHex( 32 );
- }
-
/**
* Get the embedded timestamp from a token.
* @param string $val Input token
}
/**
- * Check if all users have the given permission
+ * Check if all users may be assumed to have the given permission
+ *
+ * We generally assume so if the right is granted to '*' and isn't revoked
+ * on any group. It doesn't attempt to take grants or other extension
+ * limitations on rights into account in the general case, though, as that
+ * would require it to always return false and defeat the purpose.
+ * Specifically, session-based rights restrictions (such as OAuth or bot
+ * passwords) are applied based on the current session.
*
* @since 1.22
* @param string $right Right to check
}
}
- // Allow extensions (e.g. OAuth) to say false
+ // Remove any rights that aren't allowed to the global-session user
+ $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
+ if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
+ $cache[$right] = false;
+ return false;
+ }
+
+ // Allow extensions to say false
if ( !Hooks::run( 'UserIsEveryoneAllowed', array( $right ) ) ) {
$cache[$right] = false;
return false;