/**
  * @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);
 }
Exemple #6
0
 /**
  * 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()];
 }
Exemple #7
0
 /**
  * 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);
 }
Exemple #10
0
 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());
 }
Exemple #11
0
/**
 * 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);
}
Exemple #12
0
 /**
  * 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;
             }
         }
     }
 }
Exemple #13
0
 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}");
     }
 }
Exemple #15
0
 /**
  * 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);
 }
Exemple #17
0
 /**
  * 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);
 }
Exemple #19
0
 /**
  * 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;
 }
Exemple #21
0
 /**
  * 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;
 }
Exemple #22
0
 /**
  * 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);
     }
 }