Пример #1
0
 public function testExecute_email()
 {
     $config = new HashConfig(['PasswordResetRoutes' => ['username' => true, 'email' => true], 'EnableEmail' => true]);
     $authManager = $this->getMockBuilder(AuthManager::class)->disableOriginalConstructor()->getMock();
     $authManager->expects($this->any())->method('allowsAuthenticationDataChange')->willReturn(Status::newGood());
     $authManager->expects($this->exactly(2))->method('changeAuthenticationData');
     $request = new FauxRequest();
     $request->setIP('1.2.3.4');
     $performingUser = $this->getMock(User::class);
     $performingUser->expects($this->any())->method('getRequest')->willReturn($request);
     $performingUser->expects($this->any())->method('isAllowed')->willReturn(true);
     $targetUser1 = $this->getMock(User::class);
     $targetUser2 = $this->getMock(User::class);
     $targetUser1->expects($this->any())->method('getName')->willReturn('User1');
     $targetUser2->expects($this->any())->method('getName')->willReturn('User2');
     $targetUser1->expects($this->any())->method('getId')->willReturn(1);
     $targetUser2->expects($this->any())->method('getId')->willReturn(2);
     $targetUser1->expects($this->any())->method('getEmail')->willReturn('*****@*****.**');
     $targetUser2->expects($this->any())->method('getEmail')->willReturn('*****@*****.**');
     $passwordReset = $this->getMockBuilder(PasswordReset::class)->setMethods(['getUsersByEmail'])->setConstructorArgs([$config, $authManager])->getMock();
     $passwordReset->expects($this->any())->method('getUsersByEmail')->with('*****@*****.**')->willReturn([$targetUser1, $targetUser2]);
     $status = $passwordReset->isAllowed($performingUser);
     $this->assertTrue($status->isGood());
     $status = $passwordReset->execute($performingUser, null, '*****@*****.**');
     $this->assertTrue($status->isGood());
 }
Пример #2
0
 /**
  * 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
     });
 }
 /**
  * Import the resolved user IP, HTTP headers, user ID, and session ID.
  * This sets the current session and sets $wgUser and $wgRequest.
  * Once the return value falls out of scope, the old context is restored.
  * This function can only be called within CLI mode scripts.
  *
  * This will setup the session from the given ID. This is useful when
  * background scripts inherit context when acting on behalf of a user.
  *
  * $param array $params Result of RequestContext::exportSession()
  * @return ScopedCallback
  * @throws MWException
  * @since 1.21
  */
 public static function importScopedSession(array $params)
 {
     if (PHP_SAPI !== 'cli') {
         // Don't send random private cookies or turn $wgRequest into FauxRequest
         throw new MWException("Sessions can only be imported in cli mode.");
     } elseif (!strlen($params['sessionId'])) {
         throw new MWException("No session ID was specified.");
     }
     if ($params['userId']) {
         // logged-in user
         $user = User::newFromId($params['userId']);
         if (!$user) {
             throw new MWException("No user with ID '{$params['userId']}'.");
         }
     } elseif (!IP::isValid($params['ip'])) {
         throw new MWException("Could not load user '{$params['ip']}'.");
     } else {
         // anon user
         $user = User::newFromName($params['ip'], false);
     }
     $importSessionFunction = function (User $user, array $params) {
         global $wgRequest, $wgUser;
         $context = RequestContext::getMain();
         // Commit and close any current session
         session_write_close();
         // persist
         session_id('');
         // detach
         $_SESSION = array();
         // clear in-memory array
         // Remove any user IP or agent information
         $context->setRequest(new FauxRequest());
         $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 (strlen($params['sessionId'])) {
             // don't make a new random ID
             wfSetupSession($params['sessionId']);
             // sets $_SESSION
         }
         $request = new FauxRequest(array(), 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();
     $importSessionFunction($user, $params);
     // Set callback to save and close the new session and reload the old one
     return new ScopedCallback(function () use($importSessionFunction, $oUser, $oParams) {
         $importSessionFunction($oUser, $oParams);
     });
 }
 /**
  * Execute the requested Api actions.
  * @todo: Write some unit tests for API results
  */
 public function execute()
 {
     // Logged-in users' parser options depend on preferences
     $this->getMain()->setCacheMode('anon-public-user-private');
     // Enough '*' keys in JSON!!!
     $isXml = $this->getMain()->isInternalMode() || $this->getMain()->getPrinter()->getFormat() == 'XML';
     $textElement = $isXml ? '*' : 'text';
     $params = $this->extractRequestParams();
     $prop = array_flip($params['prop']);
     $sectionProp = array_flip($params['sectionprop']);
     $this->variant = $params['variant'];
     $this->followRedirects = $params['redirect'] == 'yes';
     $this->noHeadings = $params['noheadings'];
     $this->noTransform = $params['notransform'];
     $onlyRequestedSections = $params['onlyrequestedsections'];
     $this->offset = $params['offset'];
     $this->maxlen = $params['maxlen'];
     if ($this->offset === 0 && $this->maxlen === 0) {
         $this->offset = -1;
         // Disable text splitting
     } elseif ($this->maxlen === 0) {
         $this->maxlen = PHP_INT_MAX;
     }
     $title = $this->makeTitle($params['page']);
     // See whether the actual page (or if enabled, the redirect target) is the main page
     $this->mainPage = $this->isMainPage($title);
     if ($this->mainPage && $this->noHeadings) {
         $this->noHeadings = false;
         $this->setWarning("``noheadings'' makes no sense on the main page, ignoring");
     }
     if (isset($prop['normalizedtitle']) && $title->getPrefixedText() != $params['page']) {
         $this->getResult()->addValue(null, $this->getModuleName(), array('normalizedtitle' => $title->getPageLanguage()->convert($title->getPrefixedText())));
     }
     $data = $this->getData($title, $params['noimages']);
     // Bug 73109: #getData will return an empty array if the title redirects to
     // a page in a virtual namespace (NS_SPECIAL, NS_MEDIA), so make sure that
     // the requested data exists too.
     if (isset($prop['lastmodified']) && isset($data['lastmodified'])) {
         $this->getResult()->addValue(null, $this->getModuleName(), array('lastmodified' => $data['lastmodified']));
     }
     if (isset($prop['lastmodifiedby']) && isset($data['lastmodifiedby'])) {
         $this->getResult()->addValue(null, $this->getModuleName(), array('lastmodifiedby' => $data['lastmodifiedby']));
     }
     if (isset($prop['revision']) && isset($data['revision'])) {
         $this->getResult()->addValue(null, $this->getModuleName(), array('revision' => $data['revision']));
     }
     if (isset($prop['id']) && isset($data['id'])) {
         $this->getResult()->addValue(null, $this->getModuleName(), array('id' => $data['id']));
     }
     if (isset($prop['languagecount']) && isset($data['languagecount'])) {
         $this->getResult()->addValue(null, $this->getModuleName(), array('languagecount' => $data['languagecount']));
     }
     if (isset($prop['hasvariants']) && isset($data['hasvariants'])) {
         $this->getResult()->addValue(null, $this->getModuleName(), array('hasvariants' => $data['hasvariants']));
     }
     if (isset($prop['displaytitle']) && isset($data['displaytitle'])) {
         $this->getResult()->addValue(null, $this->getModuleName(), array('displaytitle' => $data['displaytitle']));
     }
     if (isset($prop['pageprops'])) {
         $propNames = $params['pageprops'];
         if ($propNames == '*' && isset($data['pageprops'])) {
             $pageProps = $data['pageprops'];
         } else {
             $propNames = explode('|', $propNames);
             $pageProps = array_intersect_key($data['pageprops'], array_flip($propNames));
         }
         $this->getResult()->addValue(null, $this->getModuleName(), array('pageprops' => $pageProps));
     }
     if (isset($prop['description']) && isset($data['pageprops']['wikibase_item'])) {
         $desc = ExtMobileFrontend::getWikibaseDescription($data['pageprops']['wikibase_item']);
         if ($desc) {
             $this->getResult()->addValue(null, $this->getModuleName(), array('description' => $desc));
         }
     }
     if ($this->usePageImages) {
         $this->addPageImage($data, $params, $prop);
     }
     $result = array();
     $missingSections = array();
     if ($this->mainPage) {
         if ($onlyRequestedSections) {
             $requestedSections = self::parseSections($params['sections'], $data, $missingSections);
         } else {
             $requestedSections = array(0);
         }
         $this->getResult()->addValue(null, $this->getModuleName(), array('mainpage' => ''));
     } elseif (isset($params['sections'])) {
         $requestedSections = self::parseSections($params['sections'], $data, $missingSections);
     } else {
         $requestedSections = array();
     }
     if (isset($data['sections'])) {
         if (isset($prop['sections'])) {
             $sectionCount = count($data['sections']);
             for ($i = 0; $i <= $sectionCount; $i++) {
                 if (!isset($requestedSections[$i]) && $onlyRequestedSections) {
                     continue;
                 }
                 $section = array();
                 if ($i > 0) {
                     $section = array_intersect_key($data['sections'][$i - 1], $sectionProp);
                 }
                 $section['id'] = $i;
                 if (isset($prop['text']) && isset($requestedSections[$i]) && isset($data['text'][$i])) {
                     $section[$textElement] = $this->stringSplitter($this->prepareSection($data['text'][$i]));
                     unset($requestedSections[$i]);
                 }
                 if (isset($data['refsections'][$i])) {
                     $section['references'] = '';
                 }
                 $result[] = $section;
             }
             $missingSections = array_keys($requestedSections);
         } else {
             foreach (array_keys($requestedSections) as $index) {
                 $section = array('id' => $index);
                 if (isset($data['text'][$index])) {
                     $section[$textElement] = $this->stringSplitter($this->prepareSection($data['text'][$index]));
                 } else {
                     $missingSections[] = $index;
                 }
                 $result[] = $section;
             }
         }
         $this->getResult()->setIndexedTagName($result, 'section');
         $this->getResult()->addValue(null, $this->getModuleName(), array('sections' => $result));
     }
     if (isset($prop['protection'])) {
         $this->addProtection($title);
     }
     if (isset($prop['editable'])) {
         $user = $this->getUser();
         if ($user->isAnon()) {
             // HACK: Anons receive cached information, so don't check blocked status for them
             // to avoid them receiving false positives. Currently there is no way to check
             // all permissions except blocked status from the Title class.
             $req = new FauxRequest();
             $req->setIP('127.0.0.1');
             $user = User::newFromSession($req);
         }
         $editable = $title->quickUserCan('edit', $user);
         if ($isXml) {
             $editable = intval($editable);
         }
         $this->getResult()->addValue(null, $this->getModuleName(), array('editable' => $editable));
     }
     // https://bugzilla.wikimedia.org/show_bug.cgi?id=51586
     // Inform ppl if the page is infested with LiquidThreads but that's the
     // only thing we support about it.
     if (class_exists('LqtDispatch') && LqtDispatch::isLqtPage($title)) {
         $this->getResult()->addValue(null, $this->getModuleName(), array('liquidthreads' => ''));
     }
     if (count($missingSections) && isset($prop['text'])) {
         $this->setWarning('Section(s) ' . implode(', ', $missingSections) . ' not found');
     }
     if ($this->maxlen < 0) {
         // There is more data available
         $this->getResult()->addValue(null, $this->getModuleName(), array('continue-offset' => $params['offset'] + $params['maxlen']));
     }
 }
Пример #5
0
 /**
  * @dataProvider provideCheckIpLimits
  */
 public function testCheckIpLimits($ip, $sessionData, $userData, $logLevel1, $logLevel2)
 {
     $this->setMwGlobals(array('wgSuspiciousIpPerSessionLimit' => 5, 'wgSuspiciousIpPerUserLimit' => 10, 'wgSuspiciousIpExpiry' => 600, 'wgSquidServers' => array('11.22.33.44')));
     $manager = new SessionManager();
     $logger = $this->getMock('\\Psr\\Log\\LoggerInterface');
     $this->setLogger('session-ip', $logger);
     $request = new \FauxRequest();
     $request->setIP($ip);
     $session = $manager->getSessionForRequest($request);
     /** @var SessionBackend $backend */
     $backend = \TestingAccessWrapper::newFromObject($session)->backend;
     $data =& $backend->getData();
     $data = array('SessionManager-ip' => $sessionData);
     $backend->setUser(User::newFromName('UTSysop'));
     $manager = \TestingAccessWrapper::newFromObject($manager);
     $manager->store->set('SessionManager-ip:' . md5('UTSysop'), $userData);
     $logger->expects($this->exactly(isset($logLevel1) + isset($logLevel2)))->method('log');
     if ($logLevel1) {
         $logger->expects($this->at(0))->method('log')->with($logLevel1, 'Same session used from {count} IPs', $this->isType('array'));
     }
     if ($logLevel2) {
         $logger->expects($this->at(isset($logLevel1)))->method('log')->with($logLevel2, 'Same user had sessions from {count} IPs', $this->isType('array'));
     }
     $manager->checkIpLimits($session);
 }