/** * @covers RequestContext::importScopedSession */ public function testImportScopedSession() { // Make sure session handling is started if (!MediaWiki\Session\PHPSessionHandler::isInstalled()) { MediaWiki\Session\PHPSessionHandler::install(MediaWiki\Session\SessionManager::singleton()); } $oldSessionId = session_id(); $context = RequestContext::getMain(); $oInfo = $context->exportSession(); $this->assertEquals('127.0.0.1', $oInfo['ip'], "Correct initial IP address."); $this->assertEquals(0, $oInfo['userId'], "Correct initial user ID."); $this->assertFalse(MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent(), 'Global session isn\'t persistent to start'); $user = User::newFromName('UnitTestContextUser'); $user->addToDatabase(); $sinfo = array('sessionId' => 'd612ee607c87e749ef14da4983a702cd', 'userId' => $user->getId(), 'ip' => '192.0.2.0', 'headers' => array('USER-AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0')); // importScopedSession() sets these variables $this->setMwGlobals(array('wgUser' => new User(), 'wgRequest' => new FauxRequest())); $sc = RequestContext::importScopedSession($sinfo); // load new context $info = $context->exportSession(); $this->assertEquals($sinfo['ip'], $info['ip'], "Correct IP address."); $this->assertEquals($sinfo['headers'], $info['headers'], "Correct headers."); $this->assertEquals($sinfo['sessionId'], $info['sessionId'], "Correct session ID."); $this->assertEquals($sinfo['userId'], $info['userId'], "Correct user ID."); $this->assertEquals($sinfo['ip'], $context->getRequest()->getIP(), "Correct context IP address."); $this->assertEquals($sinfo['headers'], $context->getRequest()->getAllHeaders(), "Correct context headers."); $this->assertEquals($sinfo['sessionId'], MediaWiki\Session\SessionManager::getGlobalSession()->getId(), "Correct context session ID."); if (\MediaWiki\Session\PhpSessionHandler::isEnabled()) { $this->assertEquals($sinfo['sessionId'], session_id(), "Correct context session ID."); } else { $this->assertEquals($oldSessionId, session_id(), "Unchanged PHP session ID."); } $this->assertEquals(true, $context->getUser()->isLoggedIn(), "Correct context user."); $this->assertEquals($sinfo['userId'], $context->getUser()->getId(), "Correct context user ID."); $this->assertEquals('UnitTestContextUser', $context->getUser()->getName(), "Correct context user name."); unset($sc); // restore previous context $info = $context->exportSession(); $this->assertEquals($oInfo['ip'], $info['ip'], "Correct restored IP address."); $this->assertEquals($oInfo['headers'], $info['headers'], "Correct restored headers."); $this->assertEquals($oInfo['sessionId'], $info['sessionId'], "Correct restored session ID."); $this->assertEquals($oInfo['userId'], $info['userId'], "Correct restored user ID."); $this->assertFalse(MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent(), 'Global session isn\'t persistent after restoring the context'); }
} if ($session->isPersistent()) { $wgInitialSessionId = $session->getSessionId(); } $session->renew(); if (MediaWiki\Session\PHPSessionHandler::isEnabled() && ($session->isPersistent() || $session->shouldRememberUser())) { // Start the PHP-session for backwards compatibility session_id($session->getId()); MediaWiki\quietCall('session_start'); } unset($session); } else { // Even if we didn't set up a global Session, still install our session // handler unless specifically requested not to. if (!defined('MW_NO_SESSION_HANDLER')) { MediaWiki\Session\PHPSessionHandler::install(MediaWiki\Session\SessionManager::singleton()); } } Profiler::instance()->scopedProfileOut($ps_session); /** * @var User $wgUser */ $wgUser = RequestContext::getMain()->getUser(); // BackCompat /** * @var Language $wgLang */ $wgLang = new StubUserLang(); /** * @var OutputPage $wgOut */
/** * Import an client IP address, HTTP headers, user ID, and session ID * * This sets the current session, $wgUser, and $wgRequest from $params. * Once the return value falls out of scope, the old context is restored. * This method should only be called in contexts where there is no session * ID or end user receiving the response (CLI or HTTP job runners). This * is partly enforced, and is done so to avoid leaking cookies if certain * error conditions arise. * * This is useful when background scripts inherit context when acting on * behalf of a user. In general the 'sessionId' parameter should be set * to an empty string unless session importing is *truly* needed. This * feature is somewhat deprecated. * * @note suhosin.session.encrypt may interfere with this method. * * @param array $params Result of RequestContext::exportSession() * @return ScopedCallback * @throws MWException * @since 1.21 */ public static function importScopedSession(array $params) { if (strlen($params['sessionId']) && MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent()) { // Sanity check to avoid sending random cookies for the wrong users. // This method should only called by CLI scripts or by HTTP job runners. throw new MWException("Sessions can only be imported when none is active."); } elseif (!IP::isValid($params['ip'])) { throw new MWException("Invalid client IP address '{$params['ip']}'."); } if ($params['userId']) { // logged-in user $user = User::newFromId($params['userId']); $user->load(); if (!$user->getId()) { throw new MWException("No user with ID '{$params['userId']}'."); } } else { // anon user $user = User::newFromName($params['ip'], false); } $importSessionFunc = function (User $user, array $params) { global $wgRequest, $wgUser; $context = RequestContext::getMain(); // Commit and close any current session if (MediaWiki\Session\PHPSessionHandler::isEnabled()) { session_write_close(); // persist session_id(''); // detach $_SESSION = []; // clear in-memory array } // Get new session, if applicable $session = null; if (strlen($params['sessionId'])) { // don't make a new random ID $manager = MediaWiki\Session\SessionManager::singleton(); $session = $manager->getSessionById($params['sessionId'], true) ?: $manager->getEmptySession(); } // Remove any user IP or agent information, and attach the request // with the new session. $context->setRequest(new FauxRequest([], false, $session)); $wgRequest = $context->getRequest(); // b/c // Now that all private information is detached from the user, it should // be safe to load the new user. If errors occur or an exception is thrown // and caught (leaving the main context in a mixed state), there is no risk // of the User object being attached to the wrong IP, headers, or session. $context->setUser($user); $wgUser = $context->getUser(); // b/c if ($session && MediaWiki\Session\PHPSessionHandler::isEnabled()) { session_id($session->getId()); MediaWiki\quietCall('session_start'); } $request = new FauxRequest([], false, $session); $request->setIP($params['ip']); foreach ($params['headers'] as $name => $value) { $request->setHeader($name, $value); } // Set the current context to use the new WebRequest $context->setRequest($request); $wgRequest = $context->getRequest(); // b/c }; // Stash the old session and load in the new one $oUser = self::getMain()->getUser(); $oParams = self::getMain()->exportSession(); $oRequest = self::getMain()->getRequest(); $importSessionFunc($user, $params); // Set callback to save and close the new session and reload the old one return new ScopedCallback(function () use($importSessionFunc, $oUser, $oParams, $oRequest) { global $wgRequest; $importSessionFunc($oUser, $oParams); // Restore the exact previous Request object (instead of leaving FauxRequest) RequestContext::getMain()->setRequest($oRequest); $wgRequest = RequestContext::getMain()->getRequest(); // b/c }); }
public function testBotPassword() { global $wgServer, $wgSessionProviders; if (!isset($wgServer)) { $this->markTestIncomplete('This test needs $wgServer to be set in LocalSettings.php'); } $this->setMwGlobals(array('wgSessionProviders' => array_merge($wgSessionProviders, array(array('class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => array(array('priority' => 40))))), 'wgEnableBotPasswords' => true, 'wgBotPasswordsDatabase' => false, 'wgCentralIdLookupProvider' => 'local', 'wgGrantPermissions' => array('test' => array('read' => true)))); // Make sure our session provider is present $manager = TestingAccessWrapper::newFromObject(MediaWiki\Session\SessionManager::singleton()); if (!isset($manager->sessionProviders['MediaWiki\\Session\\BotPasswordSessionProvider'])) { $tmp = $manager->sessionProviders; $manager->sessionProviders = null; $manager->sessionProviders = $tmp + $manager->getProviders(); } $this->assertNotNull(MediaWiki\Session\SessionManager::singleton()->getProvider('MediaWiki\\Session\\BotPasswordSessionProvider'), 'sanity check'); $user = self::$users['sysop']; $centralId = CentralIdLookup::factory()->centralIdFromLocalUser($user->getUser()); $this->assertNotEquals(0, $centralId, 'sanity check'); $passwordFactory = new PasswordFactory(); $passwordFactory->init(RequestContext::getMain()->getConfig()); // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only $passwordFactory->setDefaultType('A'); $pwhash = $passwordFactory->newFromPlaintext('foobaz'); $dbw = wfGetDB(DB_MASTER); $dbw->insert('bot_passwords', array('bp_user' => $centralId, 'bp_app_id' => 'foo', 'bp_password' => $pwhash->toString(), 'bp_token' => '', 'bp_restrictions' => MWRestrictions::newDefault()->toJson(), 'bp_grants' => '["test"]'), __METHOD__); $lgName = $user->username . BotPassword::getSeparator() . 'foo'; $ret = $this->doApiRequest(array('action' => 'login', 'lgname' => $lgName, 'lgpassword' => 'foobaz')); $result = $ret[0]; $this->assertNotInternalType('bool', $result); $this->assertNotInternalType('null', $result['login']); $a = $result['login']['result']; $this->assertEquals('NeedToken', $a); $token = $result['login']['token']; $ret = $this->doApiRequest(array('action' => 'login', 'lgtoken' => $token, 'lgname' => $lgName, 'lgpassword' => 'foobaz'), $ret[2]); $result = $ret[0]; $this->assertNotInternalType('bool', $result); $a = $result['login']['result']; $this->assertEquals('Success', $a); }
/** * Try to log the user in * @param string $username Combined user name and app ID * @param string $password Supplied password * @param WebRequest $request * @return Status On success, the good status's value is the new Session object */ public static function login($username, $password, WebRequest $request) { global $wgEnableBotPasswords; if (!$wgEnableBotPasswords) { return Status::newFatal('botpasswords-disabled'); } $manager = MediaWiki\Session\SessionManager::singleton(); $provider = $manager->getProvider('MediaWiki\\Session\\BotPasswordSessionProvider'); if (!$provider) { return Status::newFatal('botpasswords-no-provider'); } // Split name into name+appId $sep = self::getSeparator(); if (strpos($username, $sep) === false) { return Status::newFatal('botpasswords-invalid-name', $sep); } list($name, $appId) = explode($sep, $username, 2); // Find the named user $user = User::newFromName($name); if (!$user || $user->isAnon()) { return Status::newFatal('nosuchuser', $name); } // Get the bot password $bp = self::newFromUser($user, $appId); if (!$bp) { return Status::newFatal('botpasswords-not-exist', $name, $appId); } // Check restrictions $status = $bp->getRestrictions()->check($request); if (!$status->isOk()) { return Status::newFatal('botpasswords-restriction-failed'); } // Check the password if (!$bp->getPassword()->equals($password)) { return Status::newFatal('wrongpassword'); } // Ok! Create the session. return Status::newGood($provider->newSessionForRequest($user, $bp, $request)); }
/** * Leave a message on the user talk page or in the session according to * $params['leaveMessage']. * * @param Status $status */ protected function leaveMessage($status) { if ($this->params['leaveMessage']) { if ($status->isGood()) { // @todo FIXME: user->leaveUserMessage does not exist. $this->user->leaveUserMessage(wfMessage('upload-success-subj')->text(), wfMessage('upload-success-msg', $this->upload->getTitle()->getText(), $this->params['url'])->text()); } else { // @todo FIXME: user->leaveUserMessage does not exist. $this->user->leaveUserMessage(wfMessage('upload-failure-subj')->text(), wfMessage('upload-failure-msg', $status->getWikiText(), $this->params['url'])->text()); } } else { $session = MediaWiki\Session\SessionManager::singleton()->getSessionById($this->params['sessionId']); if ($status->isOk()) { $this->storeResultInSession($session, 'Success', 'filename', $this->upload->getLocalFile()->getName()); } else { $this->storeResultInSession($session, 'Failure', 'errors', $status->getErrorsArray()); } } }
if (is_object($func[0])) { $profName = $fname . '-extensions-' . get_class($func[0]) . '::' . $func[1]; } else { $profName = $fname . '-extensions-' . implode('::', $func); } } else { $profName = $fname . '-extensions-' . strval($func); } $ps_ext_func = Profiler::instance()->scopedProfileIn($profName); call_user_func($func); Profiler::instance()->scopedProfileOut($ps_ext_func); } // If the session user has a 0 id but a valid name, that means we need to // autocreate it. if (!defined('MW_NO_SESSION') && !$wgCommandLineMode) { $sessionUser = MediaWiki\Session\SessionManager::getGlobalSession()->getUser(); if ($sessionUser->getId() === 0 && User::isValidUserName($sessionUser->getName())) { $ps_autocreate = Profiler::instance()->scopedProfileIn($fname . '-autocreate'); MediaWiki\Session\SessionManager::autoCreateUser($sessionUser); Profiler::instance()->scopedProfileOut($ps_autocreate); } unset($sessionUser); } wfDebug("Fully initialised\n"); $wgFullyInitialised = true; // T125455 if (!defined('MW_NO_SESSION') && !$wgCommandLineMode) { MediaWiki\Session\SessionManager::singleton()->checkIpLimits(); } Profiler::instance()->scopedProfileOut($ps_extensions); Profiler::instance()->scopedProfileOut($ps_setup);
/** * Constructs an instance of ApiMain that utilizes the module and format specified by $request. * * @param IContextSource|WebRequest $context If this is an instance of * FauxRequest, errors are thrown and no printing occurs * @param bool $enableWrite Should be set to true if the api may modify data */ public function __construct($context = null, $enableWrite = false) { if ($context === null) { $context = RequestContext::getMain(); } elseif ($context instanceof WebRequest) { // BC for pre-1.19 $request = $context; $context = RequestContext::getMain(); } // We set a derivative context so we can change stuff later $this->setContext(new DerivativeContext($context)); if (isset($request)) { $this->getContext()->setRequest($request); } else { $request = $this->getRequest(); } $this->mInternalMode = $request instanceof FauxRequest; // Special handling for the main module: $parent === $this parent::__construct($this, $this->mInternalMode ? 'main_int' : 'main'); $config = $this->getConfig(); if (!$this->mInternalMode) { // Log if a request with a non-whitelisted Origin header is seen // with session cookies. $originHeader = $request->getHeader('Origin'); if ($originHeader === false) { $origins = []; } else { $originHeader = trim($originHeader); $origins = preg_split('/\\s+/', $originHeader); } $sessionCookies = array_intersect(array_keys($_COOKIE), MediaWiki\Session\SessionManager::singleton()->getVaryCookies()); if ($origins && $sessionCookies && (count($origins) !== 1 || !self::matchOrigin($origins[0], $config->get('CrossSiteAJAXdomains'), $config->get('CrossSiteAJAXdomainExceptions')))) { LoggerFactory::getInstance('cors')->warning('Non-whitelisted CORS request with session cookies', ['origin' => $originHeader, 'cookies' => $sessionCookies, 'ip' => $request->getIP(), 'userAgent' => $this->getUserAgent(), 'wiki' => wfWikiID()]); } // If we're in a mode that breaks the same-origin policy, strip // user credentials for security. if ($this->lacksSameOriginSecurity()) { global $wgUser; wfDebug("API: stripping user credentials when the same-origin policy is not applied\n"); $wgUser = new User(); $this->getContext()->setUser($wgUser); } } $uselang = $this->getParameter('uselang'); if ($uselang === 'user') { // Assume the parent context is going to return the user language // for uselang=user (see T85635). } else { if ($uselang === 'content') { global $wgContLang; $uselang = $wgContLang->getCode(); } $code = RequestContext::sanitizeLangCode($uselang); $this->getContext()->setLanguage($code); if (!$this->mInternalMode) { global $wgLang; $wgLang = $this->getContext()->getLanguage(); RequestContext::getMain()->setLanguage($wgLang); } } $this->mModuleMgr = new ApiModuleManager($this); $this->mModuleMgr->addModules(self::$Modules, 'action'); $this->mModuleMgr->addModules($config->get('APIModules'), 'action'); $this->mModuleMgr->addModules(self::$Formats, 'format'); $this->mModuleMgr->addModules($config->get('APIFormatModules'), 'format'); Hooks::run('ApiMain::moduleManager', [$this->mModuleMgr]); $this->mResult = new ApiResult($this->getConfig()->get('APIMaxResultSize')); $this->mErrorFormatter = new ApiErrorFormatter_BackCompat($this->mResult); $this->mResult->setErrorFormatter($this->mErrorFormatter); $this->mContinuationManager = null; $this->mEnableWrite = $enableWrite; $this->mSquidMaxage = -1; // flag for executeActionWithErrorHandling() $this->mCommit = false; }