/** * @dataProvider provideCategoryContent * @covers WikiCategoryPage::isHidden */ public function testHiddenCategory_PropertyIsSet($isHidden) { $categoryTitle = Title::makeTitle(NS_CATEGORY, 'CategoryPage'); $categoryPage = WikiCategoryPage::factory($categoryTitle); $pageProps = $this->getMockPageProps(); $pageProps->expects($this->once())->method('getProperties')->with($categoryTitle, 'hiddencat')->will($this->returnValue($isHidden ? [$categoryTitle->getArticleID() => ''] : [])); $scopedOverride = PageProps::overrideInstance($pageProps); $this->assertEquals($isHidden, $categoryPage->isHidden()); ScopedCallback::consume($scopedOverride); }
public function run() { $scope = RequestContext::importScopedSession($this->params['session']); $this->addTeardownCallback(function () use(&$scope) { ScopedCallback::consume($scope); // T126450 }); $context = RequestContext::getMain(); $user = $context->getUser(); try { if (!$user->isLoggedIn()) { $this->setLastError("Could not load the author user from session."); return false; } UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Poll', 'stage' => 'publish', 'status' => Status::newGood()]); $upload = new UploadFromStash($user); // @todo initialize() causes a GET, ideally we could frontload the antivirus // checks and anything else to the stash stage (which includes concatenation and // the local file is thus already there). That way, instead of GET+PUT, there could // just be a COPY operation from the stash to the public zone. $upload->initialize($this->params['filekey'], $this->params['filename']); // Check if the local file checks out (this is generally a no-op) $verification = $upload->verifyUpload(); if ($verification['status'] !== UploadBase::OK) { $status = Status::newFatal('verification-error'); $status->value = ['verification' => $verification]; UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Failure', 'stage' => 'publish', 'status' => $status]); $this->setLastError("Could not verify upload."); return false; } // Upload the stashed file to a permanent location $status = $upload->performUpload($this->params['comment'], $this->params['text'], $this->params['watch'], $user, isset($this->params['tags']) ? $this->params['tags'] : []); if (!$status->isGood()) { UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Failure', 'stage' => 'publish', 'status' => $status]); $this->setLastError($status->getWikiText(false, false, 'en')); return false; } // Build the image info array while we have the local reference handy $apiMain = new ApiMain(); // dummy object (XXX) $imageInfo = $upload->getImageInfo($apiMain->getResult()); // Cleanup any temporary local file $upload->cleanupTempFile(); // Cache the info so the user doesn't have to wait forever to get the final info UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Success', 'stage' => 'publish', 'filename' => $upload->getLocalFile()->getName(), 'imageinfo' => $imageInfo, 'status' => Status::newGood()]); } catch (Exception $e) { UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Failure', 'stage' => 'publish', 'status' => Status::newFatal('api-error-publishfailed')]); $this->setLastError(get_class($e) . ": " . $e->getMessage()); // To prevent potential database referential integrity issues. // See bug 32551. MWExceptionHandler::rollbackMasterChangesAndLog($e); return false; } return true; }
public function run() { $scope = RequestContext::importScopedSession($this->params['session']); $this->addTeardownCallback(function () use(&$scope) { ScopedCallback::consume($scope); // T126450 }); $context = RequestContext::getMain(); $user = $context->getUser(); try { if (!$user->isLoggedIn()) { $this->setLastError("Could not load the author user from session."); return false; } UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Poll', 'stage' => 'assembling', 'status' => Status::newGood()]); $upload = new UploadFromChunks($user); $upload->continueChunks($this->params['filename'], $this->params['filekey'], new WebRequestUpload($context->getRequest(), 'null')); // Combine all of the chunks into a local file and upload that to a new stash file $status = $upload->concatenateChunks(); if (!$status->isGood()) { UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Failure', 'stage' => 'assembling', 'status' => $status]); $this->setLastError($status->getWikiText(false, false, 'en')); return false; } // We can only get warnings like 'duplicate' after concatenating the chunks $status = Status::newGood(); $status->value = ['warnings' => $upload->checkWarnings()]; // We have a new filekey for the fully concatenated file $newFileKey = $upload->getStashFile()->getFileKey(); // Remove the old stash file row and first chunk file $upload->stash->removeFileNoAuth($this->params['filekey']); // Build the image info array while we have the local reference handy $apiMain = new ApiMain(); // dummy object (XXX) $imageInfo = $upload->getImageInfo($apiMain->getResult()); // Cleanup any temporary local file $upload->cleanupTempFile(); // Cache the info so the user doesn't have to wait forever to get the final info UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Success', 'stage' => 'assembling', 'filekey' => $newFileKey, 'imageinfo' => $imageInfo, 'status' => $status]); } catch (Exception $e) { UploadBase::setSessionStatus($user, $this->params['filekey'], ['result' => 'Failure', 'stage' => 'assembling', 'status' => Status::newFatal('api-error-stashfailed')]); $this->setLastError(get_class($e) . ": " . $e->getMessage()); // To be extra robust. MWExceptionHandler::rollbackMasterChangesAndLog($e); return false; } return true; }
public function tearDown() { if ($this->ptTeardownScope) { ScopedCallback::consume($this->ptTeardownScope); } }
/** * @dataProvider provideCategoryContent * @covers RecentChange::newForCategorization */ public function testHiddenCategoryChange($isHidden) { $categoryTitle = Title::newFromText('CategoryPage', NS_CATEGORY); $pageProps = $this->getMockPageProps(); $pageProps->expects($this->once())->method('getProperties')->with($categoryTitle, 'hiddencat')->will($this->returnValue($isHidden ? [$categoryTitle->getArticleID() => ''] : [])); $scopedOverride = PageProps::overrideInstance($pageProps); $rc = RecentChange::newForCategorization('0', $categoryTitle, $this->user, $this->user_comment, $this->title, $categoryTitle->getLatestRevID(), $categoryTitle->getLatestRevID(), '0', false); $this->assertEquals($isHidden, $rc->getParam('hidden-cat')); ScopedCallback::consume($scopedOverride); }
/** * Parse the page for a preview. Subclasses may override this class, in order * to parse with different options, or to otherwise modify the preview HTML. * * @param Content $content The page content * @return array with keys: * - parserOutput: The ParserOutput object * - html: The HTML to be displayed */ protected function doPreviewParse(Content $content) { global $wgUser; $parserOptions = $this->getPreviewParserOptions(); $pstContent = $content->preSaveTransform($this->mTitle, $wgUser, $parserOptions); $scopedCallback = $parserOptions->setupFakeRevision($this->mTitle, $pstContent, $wgUser); $parserOutput = $pstContent->getParserOutput($this->mTitle, null, $parserOptions); ScopedCallback::consume($scopedCallback); $parserOutput->setEditSectionTokens(false); // no section edit links return ['parserOutput' => $parserOutput, 'html' => $parserOutput->getText()]; }
/** * Update link tables with outgoing links from an updated article * * @note: this is managed by DeferredUpdates::execute(). Do not run this in a transaction. */ public function doUpdate() { if ($this->ticket) { // Make sure all links update threads see the changes of each other. // This handles the case when updates have to batched into several COMMITs. $scopedLock = self::acquirePageLock($this->getDB(), $this->mId); } Hooks::run('LinksUpdate', [&$this]); $this->doIncrementalUpdate(); // Commit and release the lock (if set) ScopedCallback::consume($scopedLock); // Run post-commit hooks without DBO_TRX $this->getDB()->onTransactionIdle(function () { Hooks::run('LinksUpdateComplete', [&$this, $this->ticket]); }, __METHOD__); }
public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() { $user = $this->getMockNonAnonUserWithId(1); $oldid = 22; $title = $this->getMockTitle('SomeDbKey'); $title->expects($this->once())->method('getNextRevisionID')->with($oldid)->will($this->returnValue(33)); $mockDb = $this->getMockDb(); $mockDb->expects($this->once())->method('selectRow')->with('watchlist', 'wl_notificationtimestamp', ['wl_user' => 1, 'wl_namespace' => 0, 'wl_title' => 'SomeDbKey'])->will($this->returnValue($this->getFakeRow(['wl_notificationtimestamp' => '30151212010101']))); $mockCache = $this->getMockCache(); $mockDb->expects($this->never())->method('get'); $mockDb->expects($this->never())->method('set'); $mockDb->expects($this->never())->method('delete'); $store = $this->newWatchedItemStore($this->getMockLoadBalancer($mockDb), $mockCache); $addUpdateCallCounter = 0; $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(function ($callable) use(&$addUpdateCallCounter, $title, $user) { $addUpdateCallCounter++; $this->verifyCallbackJob($callable, $title, $user->getId(), function ($time) { return $time === false; }); }); $getTimestampCallCounter = 0; $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(function ($titleParam, $oldidParam) use(&$getTimestampCallCounter, $title, $oldid) { $getTimestampCallCounter++; $this->assertEquals($title, $titleParam); $this->assertEquals($oldid, $oldidParam); }); $this->assertTrue($store->resetNotificationTimestamp($user, $title, '', $oldid)); $this->assertEquals(1, $addUpdateCallCounter); $this->assertEquals(1, $getTimestampCallCounter); ScopedCallback::consume($scopedOverrideDeferred); ScopedCallback::consume($scopedOverrideRevision); }
public function doUpdate() { $services = MediaWikiServices::getInstance(); $config = $services->getMainConfig(); $lbFactory = $services->getDBLoadBalancerFactory(); $batchSize = $config->get('UpdateRowsPerQuery'); // Page may already be deleted, so don't just getId() $id = $this->pageId; if ($this->ticket) { // Make sure all links update threads see the changes of each other. // This handles the case when updates have to batched into several COMMITs. $scopedLock = LinksUpdate::acquirePageLock($this->getDB(), $id); } $title = $this->page->getTitle(); $dbw = $this->getDB(); // convenience // Delete restrictions for it $dbw->delete('page_restrictions', ['pr_page' => $id], __METHOD__); // Fix category table counts $cats = $dbw->selectFieldValues('categorylinks', 'cl_to', ['cl_from' => $id], __METHOD__); $catBatches = array_chunk($cats, $batchSize); foreach ($catBatches as $catBatch) { $this->page->updateCategoryCounts([], $catBatch, $id); if (count($catBatches) > 1) { $lbFactory->commitAndWaitForReplication(__METHOD__, $this->ticket, ['wiki' => $dbw->getWikiID()]); } } // Refresh the category table entry if it seems to have no pages. Check // master for the most up-to-date cat_pages count. if ($title->getNamespace() === NS_CATEGORY) { $row = $dbw->selectRow('category', ['cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files'], ['cat_title' => $title->getDBkey(), 'cat_pages <= 0'], __METHOD__); if ($row) { Category::newFromRow($row, $title)->refreshCounts(); } } $this->batchDeleteByPK('pagelinks', ['pl_from' => $id], ['pl_from', 'pl_namespace', 'pl_title'], $batchSize); $this->batchDeleteByPK('imagelinks', ['il_from' => $id], ['il_from', 'il_to'], $batchSize); $this->batchDeleteByPK('categorylinks', ['cl_from' => $id], ['cl_from', 'cl_to'], $batchSize); $this->batchDeleteByPK('templatelinks', ['tl_from' => $id], ['tl_from', 'tl_namespace', 'tl_title'], $batchSize); $this->batchDeleteByPK('externallinks', ['el_from' => $id], ['el_id'], $batchSize); $this->batchDeleteByPK('langlinks', ['ll_from' => $id], ['ll_from', 'll_lang'], $batchSize); $this->batchDeleteByPK('iwlinks', ['iwl_from' => $id], ['iwl_from', 'iwl_prefix', 'iwl_title'], $batchSize); // Delete any redirect entry or page props entries $dbw->delete('redirect', ['rd_from' => $id], __METHOD__); $dbw->delete('page_props', ['pp_page' => $id], __METHOD__); // Find recentchanges entries to clean up... $rcIdsForTitle = $dbw->selectFieldValues('recentchanges', 'rc_id', ['rc_type != ' . RC_LOG, 'rc_namespace' => $title->getNamespace(), 'rc_title' => $title->getDBkey(), 'rc_timestamp < ' . $dbw->addQuotes($dbw->timestamp($this->timestamp))], __METHOD__); $rcIdsForPage = $dbw->selectFieldValues('recentchanges', 'rc_id', ['rc_type != ' . RC_LOG, 'rc_cur_id' => $id], __METHOD__); // T98706: delete by PK to avoid lock contention with RC delete log insertions $rcIdBatches = array_chunk(array_merge($rcIdsForTitle, $rcIdsForPage), $batchSize); foreach ($rcIdBatches as $rcIdBatch) { $dbw->delete('recentchanges', ['rc_id' => $rcIdBatch], __METHOD__); if (count($rcIdBatches) > 1) { $lbFactory->commitAndWaitForReplication(__METHOD__, $this->ticket, ['wiki' => $dbw->getWikiID()]); } } // Commit and release the lock (if set) ScopedCallback::consume($scopedLock); }
public function testCheckAccountCreatePermissions() { global $wgGroupPermissions; $this->stashMwGlobals(['wgGroupPermissions']); $this->initializeManager(true); $wgGroupPermissions['*']['createaccount'] = true; $this->assertEquals(\Status::newGood(), $this->manager->checkAccountCreatePermissions(new \User())); $this->setMwGlobals(['wgReadOnly' => 'Because']); $this->assertEquals(\Status::newFatal('readonlytext', 'Because'), $this->manager->checkAccountCreatePermissions(new \User())); $this->setMwGlobals(['wgReadOnly' => false]); $wgGroupPermissions['*']['createaccount'] = false; $status = $this->manager->checkAccountCreatePermissions(new \User()); $this->assertFalse($status->isOK()); $this->assertTrue($status->hasMessage('badaccess-groups')); $wgGroupPermissions['*']['createaccount'] = true; $user = \User::newFromName('UTBlockee'); if ($user->getID() == 0) { $user->addToDatabase(); \TestUser::setPasswordForUser($user, 'UTBlockeePassword'); $user->saveSettings(); } $oldBlock = \Block::newFromTarget('UTBlockee'); if ($oldBlock) { // An old block will prevent our new one from saving. $oldBlock->delete(); } $blockOptions = ['address' => 'UTBlockee', 'user' => $user->getID(), 'reason' => __METHOD__, 'expiry' => time() + 100500, 'createAccount' => true]; $block = new \Block($blockOptions); $block->insert(); $status = $this->manager->checkAccountCreatePermissions($user); $this->assertFalse($status->isOK()); $this->assertTrue($status->hasMessage('cantcreateaccount-text')); $blockOptions = ['address' => '127.0.0.0/24', 'reason' => __METHOD__, 'expiry' => time() + 100500, 'createAccount' => true]; $block = new \Block($blockOptions); $block->insert(); $scopeVariable = new ScopedCallback([$block, 'delete']); $status = $this->manager->checkAccountCreatePermissions(new \User()); $this->assertFalse($status->isOK()); $this->assertTrue($status->hasMessage('cantcreateaccount-range-text')); ScopedCallback::consume($scopeVariable); $this->setMwGlobals(['wgEnableDnsBlacklist' => true, 'wgDnsBlacklistUrls' => ['local.wmftest.net'], 'wgProxyWhitelist' => []]); $status = $this->manager->checkAccountCreatePermissions(new \User()); $this->assertFalse($status->isOK()); $this->assertTrue($status->hasMessage('sorbs_create_account_reason')); $this->setMwGlobals('wgProxyWhitelist', ['127.0.0.1']); $status = $this->manager->checkAccountCreatePermissions(new \User()); $this->assertTrue($status->isGood()); }
/** * Reset the session id * * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead * @since 1.22 */ function wfResetSessionID() { wfDeprecated(__FUNCTION__, '1.27'); $session = SessionManager::getGlobalSession(); $delay = $session->delaySave(); $session->resetId(); // Make sure a session is started, since that's what the old // wfResetSessionID() did. if (session_id() !== $session->getId()) { wfSetupSession($session->getId()); } ScopedCallback::consume($delay); }
/** * Run a fuzz test series * Draw input from a set of test files * @param array $filenames */ function fuzzTest($filenames) { $dict = $this->getFuzzInput($filenames); $dictSize = strlen($dict); $logMaxLength = log($this->maxFuzzTestLength); $teardown = $this->parserTest->staticSetup(); $teardown = $this->parserTest->setupDatabase($teardown); $teardown = $this->parserTest->setupUploads($teardown); $fakeTest = ['test' => '', 'desc' => '', 'input' => '', 'result' => '', 'options' => '', 'config' => '']; ini_set('memory_limit', $this->memoryLimit * 1048576 * 2); $numTotal = 0; $numSuccess = 0; $user = new User(); $opts = ParserOptions::newFromUser($user); $title = Title::makeTitle(NS_MAIN, 'Parser_test'); while (true) { // Generate test input mt_srand(++$this->seed); $totalLength = mt_rand(1, $this->maxFuzzTestLength); $input = ''; while (strlen($input) < $totalLength) { $logHairLength = mt_rand(0, 1000000) / 1000000 * $logMaxLength; $hairLength = min(intval(exp($logHairLength)), $dictSize); $offset = mt_rand(0, $dictSize - $hairLength); $input .= substr($dict, $offset, $hairLength); } $perTestTeardown = $this->parserTest->perTestSetup($fakeTest); $parser = $this->parserTest->getParser(); // Run the test try { $parser->parse($input, $title, $opts); $fail = false; } catch (Exception $exception) { $fail = true; } if ($fail) { echo "Test failed with seed {$this->seed}\n"; echo "Input:\n"; printf("string(%d) \"%s\"\n\n", strlen($input), $input); echo "{$exception}\n"; } else { $numSuccess++; } $numTotal++; ScopedCallback::consume($perTestTeardown); if ($numTotal % 100 == 0) { $usage = intval(memory_get_usage(true) / $this->memoryLimit / 1048576 * 100); echo "{$this->seed}: {$numSuccess}/{$numTotal} (mem: {$usage}%)\n"; if ($usage >= 100) { echo "Out of memory:\n"; $memStats = $this->getMemoryBreakdown(); foreach ($memStats as $name => $usage) { echo "{$name}: {$usage}\n"; } if (function_exists('hphpd_break')) { hphpd_break(); } return; } } } }
public function testLogin() { // Test failure when bot passwords aren't enabled $this->setMwGlobals('wgEnableBotPasswords', false); $status = BotPassword::login("{$this->testUserName}@BotPassword", 'foobaz', new FauxRequest()); $this->assertEquals(Status::newFatal('botpasswords-disabled'), $status); $this->setMwGlobals('wgEnableBotPasswords', true); // Test failure when BotPasswordSessionProvider isn't configured $manager = new SessionManager(['logger' => new Psr\Log\NullLogger(), 'store' => new EmptyBagOStuff()]); $reset = MediaWiki\Session\TestUtils::setSessionManagerSingleton($manager); $this->assertNull($manager->getProvider(MediaWiki\Session\BotPasswordSessionProvider::class), 'sanity check'); $status = BotPassword::login("{$this->testUserName}@BotPassword", 'foobaz', new FauxRequest()); $this->assertEquals(Status::newFatal('botpasswords-no-provider'), $status); ScopedCallback::consume($reset); // Now configure BotPasswordSessionProvider for further tests... $mainConfig = RequestContext::getMain()->getConfig(); $config = new HashConfig(['SessionProviders' => $mainConfig->get('SessionProviders') + [MediaWiki\Session\BotPasswordSessionProvider::class => ['class' => MediaWiki\Session\BotPasswordSessionProvider::class, 'args' => [['priority' => 40]]]]]); $manager = new SessionManager(['config' => new MultiConfig([$config, RequestContext::getMain()->getConfig()]), 'logger' => new Psr\Log\NullLogger(), 'store' => new EmptyBagOStuff()]); $reset = MediaWiki\Session\TestUtils::setSessionManagerSingleton($manager); // No "@"-thing in the username $status = BotPassword::login($this->testUserName, 'foobaz', new FauxRequest()); $this->assertEquals(Status::newFatal('botpasswords-invalid-name', '@'), $status); // No base user $status = BotPassword::login('UTDummy@BotPassword', 'foobaz', new FauxRequest()); $this->assertEquals(Status::newFatal('nosuchuser', 'UTDummy'), $status); // No bot password $status = BotPassword::login("{$this->testUserName}@DoesNotExist", 'foobaz', new FauxRequest()); $this->assertEquals(Status::newFatal('botpasswords-not-exist', $this->testUserName, 'DoesNotExist'), $status); // Failed restriction $request = $this->getMock('FauxRequest', ['getIP']); $request->expects($this->any())->method('getIP')->will($this->returnValue('10.0.0.1')); $status = BotPassword::login("{$this->testUserName}@BotPassword", 'foobaz', $request); $this->assertEquals(Status::newFatal('botpasswords-restriction-failed'), $status); // Wrong password $status = BotPassword::login("{$this->testUserName}@BotPassword", $this->testUser->getPassword(), new FauxRequest()); $this->assertEquals(Status::newFatal('wrongpassword'), $status); // Success! $request = new FauxRequest(); $this->assertNotInstanceOf(MediaWiki\Session\BotPasswordSessionProvider::class, $request->getSession()->getProvider(), 'sanity check'); $status = BotPassword::login("{$this->testUserName}@BotPassword", 'foobaz', $request); $this->assertInstanceOf('Status', $status); $this->assertTrue($status->isGood()); $session = $status->getValue(); $this->assertInstanceOf(MediaWiki\Session\Session::class, $session); $this->assertInstanceOf(MediaWiki\Session\BotPasswordSessionProvider::class, $session->getProvider()); $this->assertSame($session->getId(), $request->getSession()->getId()); ScopedCallback::consume($reset); }
/** * Set the files this module depends on indirectly for a given skin. * * @since 1.27 * @param ResourceLoaderContext $context * @param array $localFileRefs List of files */ protected function saveFileDependencies(ResourceLoaderContext $context, $localFileRefs) { // Normalise array $localFileRefs = array_values(array_unique($localFileRefs)); sort($localFileRefs); try { // If the list has been modified since last time we cached it, update the cache if ($localFileRefs !== $this->getFileDependencies($context)) { $cache = ObjectCache::getLocalClusterInstance(); $key = $cache->makeKey(__METHOD__, $this->getName()); $scopeLock = $cache->getScopedLock($key, 0); if (!$scopeLock) { return; // T124649; avoid write slams } $vary = $context->getSkin() . '|' . $context->getLanguage(); $dbw = wfGetDB(DB_MASTER); $dbw->replace('module_deps', [['md_module', 'md_skin']], ['md_module' => $this->getName(), 'md_skin' => $vary, 'md_deps' => FormatJson::encode(self::getRelativePaths($localFileRefs))]); if ($dbw->trxLevel()) { $dbw->onTransactionResolution(function () use(&$scopeLock) { ScopedCallback::consume($scopeLock); // release after commit }, __METHOD__); } } } catch (Exception $e) { wfDebugLog('resourceloader', __METHOD__ . ": failed to update DB: {$e}"); } }
/** * Clear the user's session, and reset the instance cache. * @see logout() */ public function doLogout() { $session = $this->getRequest()->getSession(); if (!$session->canSetUser()) { \MediaWiki\Logger\LoggerFactory::getInstance('session')->warning(__METHOD__ . ": Cannot log out of an immutable session"); $error = 'immutable'; } 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'); $error = 'wronguser'; } else { $this->clearInstanceCache('defaults'); $delay = $session->delaySave(); $session->unpersist(); // Clear cookies (T127436) $session->setLoggedOutTimestamp(time()); $session->setUser(new User()); $session->set('wsUserID', 0); // Other code expects this $session->resetAllTokens(); ScopedCallback::consume($delay); $error = false; } \MediaWiki\Logger\LoggerFactory::getInstance('authevents')->info('Logout', ['event' => 'logout', 'successful' => $error === false, 'status' => $error ?: 'success']); }
public function testAccountCreationEmail() { $creator = \User::newFromName('Foo'); $user = self::getMutableTestUser()->getUser(); $user->setEmail(null); $req = TemporaryPasswordAuthenticationRequest::newRandom(); $req->username = $user->getName(); $req->mailpassword = true; $provider = $this->getProvider(['emailEnabled' => false]); $status = $provider->testForAccountCreation($user, $creator, [$req]); $this->assertEquals(\StatusValue::newFatal('emaildisabled'), $status); $req->hasBackchannel = true; $status = $provider->testForAccountCreation($user, $creator, [$req]); $this->assertFalse($status->hasMessage('emaildisabled')); $req->hasBackchannel = false; $provider = $this->getProvider(['emailEnabled' => true]); $status = $provider->testForAccountCreation($user, $creator, [$req]); $this->assertEquals(\StatusValue::newFatal('noemailcreate'), $status); $req->hasBackchannel = true; $status = $provider->testForAccountCreation($user, $creator, [$req]); $this->assertFalse($status->hasMessage('noemailcreate')); $req->hasBackchannel = false; $user->setEmail('*****@*****.**'); $status = $provider->testForAccountCreation($user, $creator, [$req]); $this->assertEquals(\StatusValue::newGood(), $status); $mailed = false; $resetMailer = $this->hookMailer(function ($headers, $to, $from, $subject, $body) use(&$mailed, $req) { $mailed = true; $this->assertSame('*****@*****.**', $to[0]->address); $this->assertContains($req->password, $body); return false; }); $expect = AuthenticationResponse::newPass($user->getName()); $expect->createRequest = clone $req; $expect->createRequest->username = $user->getName(); $res = $provider->beginPrimaryAccountCreation($user, $creator, [$req]); $this->assertEquals($expect, $res); $this->assertTrue($this->manager->getAuthenticationSessionData('no-email')); $this->assertFalse($mailed); $this->assertSame('byemail', $provider->finishAccountCreation($user, $creator, $res)); $this->assertTrue($mailed); ScopedCallback::consume($resetMailer); $this->assertTrue($mailed); }
/** * Log the user in * @param User $user * @param bool|null $remember */ private function setSessionDataForUser($user, $remember = null) { $session = $this->request->getSession(); $delay = $session->delaySave(); $session->resetId(); $session->resetAllTokens(); if ($session->canSetUser()) { $session->setUser($user); } if ($remember !== null) { $session->setRememberUser($remember); } $session->set('AuthManager:lastAuthId', $user->getId()); $session->set('AuthManager:lastAuthTimestamp', time()); $session->persist(); \Wikimedia\ScopedCallback::consume($delay); \Hooks::run('UserLoggedIn', [$user]); }
public function testDelaySave() { $this->mergeMwGlobalArrayValue('wgHooks', ['SessionMetadata' => [$this]]); $backend = $this->getBackend(); $priv = \TestingAccessWrapper::newFromObject($backend); $priv->persist = true; // Saves happen normally when no delay is in effect $this->onSessionMetadataCalled = false; $priv->metaDirty = true; $backend->save(); $this->assertTrue($this->onSessionMetadataCalled, 'sanity check'); $this->onSessionMetadataCalled = false; $priv->metaDirty = true; $priv->autosave(); $this->assertTrue($this->onSessionMetadataCalled, 'sanity check'); $delay = $backend->delaySave(); // Autosave doesn't happen when no delay is in effect $this->onSessionMetadataCalled = false; $priv->metaDirty = true; $priv->autosave(); $this->assertFalse($this->onSessionMetadataCalled); // Save still does happen when no delay is in effect $priv->save(); $this->assertTrue($this->onSessionMetadataCalled); // Save happens when delay is consumed $this->onSessionMetadataCalled = false; $priv->metaDirty = true; \Wikimedia\ScopedCallback::consume($delay); $this->assertTrue($this->onSessionMetadataCalled); // Test multiple delays $delay1 = $backend->delaySave(); $delay2 = $backend->delaySave(); $delay3 = $backend->delaySave(); $this->onSessionMetadataCalled = false; $priv->metaDirty = true; $priv->autosave(); $this->assertFalse($this->onSessionMetadataCalled); \Wikimedia\ScopedCallback::consume($delay3); $this->assertFalse($this->onSessionMetadataCalled); \Wikimedia\ScopedCallback::consume($delay1); $this->assertFalse($this->onSessionMetadataCalled); \Wikimedia\ScopedCallback::consume($delay2); $this->assertTrue($this->onSessionMetadataCalled); }
/** * Issue a commit on all masters who are currently in a transaction and have * made changes to the database. It also supports sometimes waiting for the * local wiki's replica DBs to catch up. See the documentation for * $wgJobSerialCommitThreshold for more. * * @param LBFactory $lbFactory * @param Job $job * @param string $fnameTrxOwner * @throws DBError */ private function commitMasterChanges(LBFactory $lbFactory, Job $job, $fnameTrxOwner) { global $wgJobSerialCommitThreshold; $time = false; $lb = $lbFactory->getMainLB(wfWikiID()); if ($wgJobSerialCommitThreshold !== false && $lb->getServerCount() > 1) { // Generally, there is one master connection to the local DB $dbwSerial = $lb->getAnyOpenConnection($lb->getWriterIndex()); // We need natively blocking fast locks if ($dbwSerial && $dbwSerial->namedLocksEnqueue()) { $time = $dbwSerial->pendingWriteQueryDuration($dbwSerial::ESTIMATE_DB_APPLY); if ($time < $wgJobSerialCommitThreshold) { $dbwSerial = false; } } else { $dbwSerial = false; } } else { // There are no replica DBs or writes are all to foreign DB (we don't handle that) $dbwSerial = false; } if (!$dbwSerial) { $lbFactory->commitMasterChanges($fnameTrxOwner); return; } $ms = intval(1000 * $time); $msg = $job->toString() . " COMMIT ENQUEUED [{$ms}ms of writes]"; $this->logger->info($msg); $this->debugCallback($msg); // Wait for an exclusive lock to commit if (!$dbwSerial->lock('jobrunner-serial-commit', __METHOD__, 30)) { // This will trigger a rollback in the main loop throw new DBError($dbwSerial, "Timed out waiting on commit queue."); } $unlocker = new ScopedCallback(function () use($dbwSerial) { $dbwSerial->unlock('jobrunner-serial-commit', __METHOD__); }); // Wait for the replica DBs to catch up $pos = $lb->getMasterPos(); if ($pos) { $lb->waitForAll($pos); } // Actually commit the DB master changes $lbFactory->commitMasterChanges($fnameTrxOwner); ScopedCallback::consume($unlocker); }
/** * Run a given wikitext input through a freshly-constructed wiki parser, * and compare the output against the expected results. * Prints status and explanatory messages to stdout. * * staticSetup() and setupWikiData() must be called before this function * is entered. * * @param array $test The test parameters: * - test: The test name * - desc: The subtest description * - input: Wikitext to try rendering * - options: Array of test options * - config: Overrides for global variables, one per line * * @return ParserTestResult or false if skipped */ public function runTest($test) { wfDebug(__METHOD__ . ": running {$test['desc']}"); $opts = $this->parseOptions($test['options']); $teardownGuard = $this->perTestSetup($test); $context = RequestContext::getMain(); $user = $context->getUser(); $options = ParserOptions::newFromContext($context); if (isset($opts['tidy'])) { if (!$this->tidySupport->isEnabled()) { $this->recorder->skipped($test, 'tidy extension is not installed'); return false; } else { $options->setTidy(true); } } if (isset($opts['title'])) { $titleText = $opts['title']; } else { $titleText = 'Parser test'; } $local = isset($opts['local']); $preprocessor = isset($opts['preprocessor']) ? $opts['preprocessor'] : null; $parser = $this->getParser($preprocessor); $title = Title::newFromText($titleText); if (isset($opts['pst'])) { $out = $parser->preSaveTransform($test['input'], $title, $user, $options); } elseif (isset($opts['msg'])) { $out = $parser->transformMsg($test['input'], $options, $title); } elseif (isset($opts['section'])) { $section = $opts['section']; $out = $parser->getSection($test['input'], $section); } elseif (isset($opts['replace'])) { $section = $opts['replace'][0]; $replace = $opts['replace'][1]; $out = $parser->replaceSection($test['input'], $section, $replace); } elseif (isset($opts['comment'])) { $out = Linker::formatComment($test['input'], $title, $local); } elseif (isset($opts['preload'])) { $out = $parser->getPreloadText($test['input'], $title, $options); } else { $output = $parser->parse($test['input'], $title, $options, true, true, 1337); $output->setTOCEnabled(!isset($opts['notoc'])); $out = $output->getText(); if (isset($opts['tidy'])) { $out = preg_replace('/\\s+$/', '', $out); } if (isset($opts['showtitle'])) { if ($output->getTitleText()) { $title = $output->getTitleText(); } $out = "{$title}\n{$out}"; } if (isset($opts['showindicators'])) { $indicators = ''; foreach ($output->getIndicators() as $id => $content) { $indicators .= "{$id}={$content}\n"; } $out = $indicators . $out; } if (isset($opts['ill'])) { $out = implode(' ', $output->getLanguageLinks()); } elseif (isset($opts['cat'])) { $out = ''; foreach ($output->getCategories() as $name => $sortkey) { if ($out !== '') { $out .= "\n"; } $out .= "cat={$name} sort={$sortkey}"; } } } ScopedCallback::consume($teardownGuard); $expected = $test['result']; if (count($this->normalizationFunctions)) { $expected = ParserTestResultNormalizer::normalize($test['expected'], $this->normalizationFunctions); $out = ParserTestResultNormalizer::normalize($out, $this->normalizationFunctions); } $testResult = new ParserTestResult($test, $expected, $out); return $testResult; }
/** * Create a Session corresponding to the passed SessionInfo * @private For use by a SessionProvider that needs to specially create its * own Session. Most session providers won't need this. * @param SessionInfo $info * @param WebRequest $request * @return Session */ public function getSessionFromInfo(SessionInfo $info, WebRequest $request) { // @codeCoverageIgnoreStart if (defined('MW_NO_SESSION')) { if (MW_NO_SESSION === 'warn') { // Undocumented safety case for converting existing entry points $this->logger->error('Sessions are supposed to be disabled for this entry point', ['exception' => new \BadMethodCallException('Sessions are disabled for this entry point')]); } else { throw new \BadMethodCallException('Sessions are disabled for this entry point'); } } // @codeCoverageIgnoreEnd $id = $info->getId(); if (!isset($this->allSessionBackends[$id])) { if (!isset($this->allSessionIds[$id])) { $this->allSessionIds[$id] = new SessionId($id); } $backend = new SessionBackend($this->allSessionIds[$id], $info, $this->store, $this->logger, $this->config->get('ObjectCacheSessionExpiry')); $this->allSessionBackends[$id] = $backend; $delay = $backend->delaySave(); } else { $backend = $this->allSessionBackends[$id]; $delay = $backend->delaySave(); if ($info->wasPersisted()) { $backend->persist(); } if ($info->wasRemembered()) { $backend->setRememberUser(true); } } $request->setSessionId($backend->getSessionId()); $session = $backend->getSession($request); if (!$info->isIdSafe()) { $session->resetId(); } \Wikimedia\ScopedCallback::consume($delay); return $session; }
/** * Updates cache as necessary when message page is changed * * @param string|bool $title Name of the page changed (false if deleted) * @param mixed $text New contents of the page. */ public function replace($title, $text) { global $wgMaxMsgCacheEntrySize, $wgContLang, $wgLanguageCode; if ($this->mDisable) { return; } list($msg, $code) = $this->figureMessage($title); if (strpos($title, '/') !== false && $code === $wgLanguageCode) { // Content language overrides do not use the /<code> suffix return; } // Note that if the cache is volatile, load() may trigger a DB fetch. // In that case we reenter/reuse the existing cache key lock to avoid // a self-deadlock. This is safe as no reads happen *directly* in this // method between getReentrantScopedLock() and load() below. There is // no risk of data "changing under our feet" for replace(). $cacheKey = wfMemcKey('messages', $code); $scopedLock = $this->getReentrantScopedLock($cacheKey); $this->load($code, self::FOR_UPDATE); $titleKey = wfMemcKey('messages', 'individual', $title); if ($text === false) { // Article was deleted $this->mCache[$code][$title] = '!NONEXISTENT'; $this->wanCache->delete($titleKey); } elseif (strlen($text) > $wgMaxMsgCacheEntrySize) { // Check for size $this->mCache[$code][$title] = '!TOO BIG'; $this->wanCache->set($titleKey, ' ' . $text, $this->mExpiry); } else { $this->mCache[$code][$title] = ' ' . $text; $this->wanCache->delete($titleKey); } // Mark this cache as definitely "latest" (non-volatile) so // load() calls do try to refresh the cache with replica DB data $this->mCache[$code]['LATEST'] = time(); // Update caches if the lock was acquired if ($scopedLock) { $this->saveToCaches($this->mCache[$code], 'all', $code); } ScopedCallback::consume($scopedLock); // Relay the purge to APC and other DCs $this->wanCache->touchCheckKey(wfMemcKey('messages', $code)); // Also delete cached sidebar... just in case it is affected $codes = [$code]; if ($code === 'en') { // Delete all sidebars, like for example on action=purge on the // sidebar messages $codes = array_keys(Language::fetchLanguageNames()); } foreach ($codes as $code) { $sidebarKey = wfMemcKey('sidebar', $code); $this->wanCache->delete($sidebarKey); } // Update the message in the message blob store $resourceloader = RequestContext::getMain()->getOutput()->getResourceLoader(); $blobStore = $resourceloader->getMessageBlobStore(); $blobStore->updateMessage($wgContLang->lcfirst($msg)); Hooks::run('MessageCacheReplace', [$title, $text]); }
public function tearDown() { wfDebug(__METHOD__); if ($this->ptTeardownScope) { ScopedCallback::consume($this->ptTeardownScope); } }