Ejemplo n.º 1
0
 /**
  * @dataProvider provideSave
  * @param string|null $password
  */
 public function testSave($password)
 {
     $passwordFactory = new \PasswordFactory();
     $passwordFactory->init(\RequestContext::getMain()->getConfig());
     $bp = BotPassword::newUnsaved(['centralId' => 42, 'appId' => 'TestSave', 'restrictions' => MWRestrictions::newFromJson('{"IPAddresses":["127.0.0.0/8"]}'), 'grants' => ['test']]);
     $this->assertFalse($bp->isSaved(), 'sanity check');
     $this->assertNull(BotPassword::newFromCentralId(42, 'TestSave', BotPassword::READ_LATEST), 'sanity check');
     $passwordHash = $password ? $passwordFactory->newFromPlaintext($password) : null;
     $this->assertFalse($bp->save('update', $passwordHash));
     $this->assertTrue($bp->save('insert', $passwordHash));
     $bp2 = BotPassword::newFromCentralId(42, 'TestSave', BotPassword::READ_LATEST);
     $this->assertInstanceOf('BotPassword', $bp2);
     $this->assertEquals($bp->getUserCentralId(), $bp2->getUserCentralId());
     $this->assertEquals($bp->getAppId(), $bp2->getAppId());
     $this->assertEquals($bp->getToken(), $bp2->getToken());
     $this->assertEquals($bp->getRestrictions(), $bp2->getRestrictions());
     $this->assertEquals($bp->getGrants(), $bp2->getGrants());
     $pw = TestingAccessWrapper::newFromObject($bp)->getPassword();
     if ($password === null) {
         $this->assertInstanceOf('InvalidPassword', $pw);
     } else {
         $this->assertTrue($pw->equals($password));
     }
     $token = $bp->getToken();
     $this->assertFalse($bp->save('insert'));
     $this->assertTrue($bp->save('update'));
     $this->assertNotEquals($token, $bp->getToken());
     $bp2 = BotPassword::newFromCentralId(42, 'TestSave', BotPassword::READ_LATEST);
     $this->assertInstanceOf('BotPassword', $bp2);
     $this->assertEquals($bp->getToken(), $bp2->getToken());
     $pw = TestingAccessWrapper::newFromObject($bp)->getPassword();
     if ($password === null) {
         $this->assertInstanceOf('InvalidPassword', $pw);
     } else {
         $this->assertTrue($pw->equals($password));
     }
     $passwordHash = $passwordFactory->newFromPlaintext('XXX');
     $token = $bp->getToken();
     $this->assertTrue($bp->save('update', $passwordHash));
     $this->assertNotEquals($token, $bp->getToken());
     $pw = TestingAccessWrapper::newFromObject($bp)->getPassword();
     $this->assertTrue($pw->equals('XXX'));
     $this->assertTrue($bp->delete());
     $this->assertFalse($bp->isSaved());
     $this->assertNull(BotPassword::newFromCentralId(42, 'TestSave', BotPassword::READ_LATEST));
     $this->assertFalse($bp->save('foobar'));
 }
Ejemplo n.º 2
0
 public function onSuccess()
 {
     $out = $this->getOutput();
     $username = $this->getUser()->getName();
     switch ($this->operation) {
         case 'insert':
             $out->setPageTitle($this->msg('botpasswords-created-title')->text());
             $out->addWikiMsg('botpasswords-created-body', $this->par, $username);
             break;
         case 'update':
             $out->setPageTitle($this->msg('botpasswords-updated-title')->text());
             $out->addWikiMsg('botpasswords-updated-body', $this->par, $username);
             break;
         case 'delete':
             $out->setPageTitle($this->msg('botpasswords-deleted-title')->text());
             $out->addWikiMsg('botpasswords-deleted-body', $this->par, $username);
             $this->password = null;
             break;
     }
     if ($this->password !== null) {
         $sep = BotPassword::getSeparator();
         $out->addWikiMsg('botpasswords-newpassword', htmlspecialchars($username . $sep . $this->par), htmlspecialchars($this->password));
         $this->password = null;
     }
     $out->addReturnTo($this->getPageTitle());
 }
Ejemplo n.º 3
0
 public function testBotPassword()
 {
     global $wgServer, $wgSessionProviders;
     if (!isset($wgServer)) {
         $this->markTestIncomplete('This test needs $wgServer to be set in LocalSettings.php');
     }
     $this->setMwGlobals(array('wgSessionProviders' => array_merge($wgSessionProviders, array(array('class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => array(array('priority' => 40))))), 'wgEnableBotPasswords' => true, 'wgBotPasswordsDatabase' => false, 'wgCentralIdLookupProvider' => 'local', 'wgGrantPermissions' => array('test' => array('read' => true))));
     // Make sure our session provider is present
     $manager = TestingAccessWrapper::newFromObject(MediaWiki\Session\SessionManager::singleton());
     if (!isset($manager->sessionProviders['MediaWiki\\Session\\BotPasswordSessionProvider'])) {
         $tmp = $manager->sessionProviders;
         $manager->sessionProviders = null;
         $manager->sessionProviders = $tmp + $manager->getProviders();
     }
     $this->assertNotNull(MediaWiki\Session\SessionManager::singleton()->getProvider('MediaWiki\\Session\\BotPasswordSessionProvider'), 'sanity check');
     $user = self::$users['sysop'];
     $centralId = CentralIdLookup::factory()->centralIdFromLocalUser($user->getUser());
     $this->assertNotEquals(0, $centralId, 'sanity check');
     $passwordFactory = new PasswordFactory();
     $passwordFactory->init(RequestContext::getMain()->getConfig());
     // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
     $passwordFactory->setDefaultType('A');
     $pwhash = $passwordFactory->newFromPlaintext('foobaz');
     $dbw = wfGetDB(DB_MASTER);
     $dbw->insert('bot_passwords', array('bp_user' => $centralId, 'bp_app_id' => 'foo', 'bp_password' => $pwhash->toString(), 'bp_token' => '', 'bp_restrictions' => MWRestrictions::newDefault()->toJson(), 'bp_grants' => '["test"]'), __METHOD__);
     $lgName = $user->username . BotPassword::getSeparator() . 'foo';
     $ret = $this->doApiRequest(array('action' => 'login', 'lgname' => $lgName, 'lgpassword' => 'foobaz'));
     $result = $ret[0];
     $this->assertNotInternalType('bool', $result);
     $this->assertNotInternalType('null', $result['login']);
     $a = $result['login']['result'];
     $this->assertEquals('NeedToken', $a);
     $token = $result['login']['token'];
     $ret = $this->doApiRequest(array('action' => 'login', 'lgtoken' => $token, 'lgname' => $lgName, 'lgpassword' => 'foobaz'), $ret[2]);
     $result = $ret[0];
     $this->assertNotInternalType('bool', $result);
     $a = $result['login']['result'];
     $this->assertEquals('Success', $a);
 }
Ejemplo n.º 4
0
 /**
  * Executes the log-in attempt using the parameters passed. If
  * the log-in succeeds, it attaches a cookie to the session
  * and outputs the user id, username, and session token. If a
  * log-in fails, as the result of a bad password, a nonexistent
  * user, or any other reason, the host is cached with an expiry
  * and no log-in attempts will be accepted until that expiry
  * is reached. The expiry is $this->mLoginThrottle.
  */
 public function execute()
 {
     // If we're in a mode that breaks the same-origin policy, no tokens can
     // be obtained
     if ($this->lacksSameOriginSecurity()) {
         $this->getResult()->addValue(null, 'login', ['result' => 'Aborted', 'reason' => 'Cannot log in when the same-origin policy is not applied']);
         return;
     }
     try {
         $this->requirePostedParameters(['password', 'token']);
     } catch (UsageException $ex) {
         // Make this a warning for now, upgrade to an error in 1.29.
         $this->setWarning($ex->getMessage());
         $this->logFeatureUsage('login-params-in-query-string');
     }
     $params = $this->extractRequestParams();
     $result = [];
     // Make sure session is persisted
     $session = MediaWiki\Session\SessionManager::getGlobalSession();
     $session->persist();
     // Make sure it's possible to log in
     if (!$session->canSetUser()) {
         $this->getResult()->addValue(null, 'login', ['result' => 'Aborted', 'reason' => 'Cannot log in when using ' . $session->getProvider()->describe(Language::factory('en'))]);
         return;
     }
     $authRes = false;
     $context = new DerivativeContext($this->getContext());
     $loginType = 'N/A';
     // Check login token
     $token = $session->getToken('', 'login');
     if ($token->wasNew() || !$params['token']) {
         $authRes = 'NeedToken';
     } elseif (!$token->match($params['token'])) {
         $authRes = 'WrongToken';
     }
     // Try bot passwords
     if ($authRes === false && $this->getConfig()->get('EnableBotPasswords') && ($botLoginData = BotPassword::canonicalizeLoginData($params['name'], $params['password']))) {
         $status = BotPassword::login($botLoginData[0], $botLoginData[1], $this->getRequest());
         if ($status->isOK()) {
             $session = $status->getValue();
             $authRes = 'Success';
             $loginType = 'BotPassword';
         } elseif (!$botLoginData[2]) {
             $authRes = 'Failed';
             $message = $status->getMessage();
             LoggerFactory::getInstance('authentication')->info('BotPassword login failed: ' . $status->getWikiText(false, false, 'en'));
         }
     }
     if ($authRes === false) {
         // Simplified AuthManager login, for backwards compatibility
         $manager = AuthManager::singleton();
         $reqs = AuthenticationRequest::loadRequestsFromSubmission($manager->getAuthenticationRequests(AuthManager::ACTION_LOGIN, $this->getUser()), ['username' => $params['name'], 'password' => $params['password'], 'domain' => $params['domain'], 'rememberMe' => true]);
         $res = AuthManager::singleton()->beginAuthentication($reqs, 'null:');
         switch ($res->status) {
             case AuthenticationResponse::PASS:
                 if ($this->getConfig()->get('EnableBotPasswords')) {
                     $warn = 'Main-account login via action=login is deprecated and may stop working ' . 'without warning.';
                     $warn .= ' To continue login with action=login, see [[Special:BotPasswords]].';
                     $warn .= ' To safely continue using main-account login, see action=clientlogin.';
                 } else {
                     $warn = 'Login via action=login is deprecated and may stop working without warning.';
                     $warn .= ' To safely log in, see action=clientlogin.';
                 }
                 $this->setWarning($warn);
                 $authRes = 'Success';
                 $loginType = 'AuthManager';
                 break;
             case AuthenticationResponse::FAIL:
                 // Hope it's not a PreAuthenticationProvider that failed...
                 $authRes = 'Failed';
                 $message = $res->message;
                 \MediaWiki\Logger\LoggerFactory::getInstance('authentication')->info(__METHOD__ . ': Authentication failed: ' . $message->inLanguage('en')->plain());
                 break;
             default:
                 \MediaWiki\Logger\LoggerFactory::getInstance('authentication')->info(__METHOD__ . ': Authentication failed due to unsupported response type: ' . $res->status, $this->getAuthenticationResponseLogData($res));
                 $authRes = 'Aborted';
                 break;
         }
     }
     $result['result'] = $authRes;
     switch ($authRes) {
         case 'Success':
             $user = $session->getUser();
             ApiQueryInfo::resetTokenCache();
             // Deprecated hook
             $injected_html = '';
             Hooks::run('UserLoginComplete', [&$user, &$injected_html, true]);
             $result['lguserid'] = intval($user->getId());
             $result['lgusername'] = $user->getName();
             break;
         case 'NeedToken':
             $result['token'] = $token->toString();
             $this->setWarning('Fetching a token via action=login is deprecated. ' . 'Use action=query&meta=tokens&type=login instead.');
             $this->logFeatureUsage('action=login&!lgtoken');
             break;
         case 'WrongToken':
             break;
         case 'Failed':
             $result['reason'] = $message->useDatabase('false')->inLanguage('en')->text();
             break;
         case 'Aborted':
             $result['reason'] = 'Authentication requires user interaction, ' . 'which is not supported by action=login.';
             if ($this->getConfig()->get('EnableBotPasswords')) {
                 $result['reason'] .= ' To be able to login with action=login, see [[Special:BotPasswords]].';
                 $result['reason'] .= ' To continue using main-account login, see action=clientlogin.';
             } else {
                 $result['reason'] .= ' To log in, see action=clientlogin.';
             }
             break;
         default:
             ApiBase::dieDebug(__METHOD__, "Unhandled case value: {$authRes}");
     }
     $this->getResult()->addValue(null, 'login', $result);
     if ($loginType === 'LoginForm' && isset(LoginForm::$statusCodes[$authRes])) {
         $authRes = LoginForm::$statusCodes[$authRes];
     }
     LoggerFactory::getInstance('authevents')->info('Login attempt', ['event' => 'login', 'successful' => $authRes === 'Success', 'loginType' => $loginType, 'status' => $authRes]);
 }
 public function testCheckSessionInfo()
 {
     $logger = new \TestLogger(true, function ($m) {
         return preg_replace('/^Session \\[\\d+\\][a-zA-Z0-9_\\\\]+<(?:null|anon|[+-]:\\d+:\\w+)>\\w+: /', 'Session X: ', $m);
     });
     $provider = $this->getProvider();
     $provider->setLogger($logger);
     $user = \User::newFromName('UTSysop');
     $request = $this->getMock('FauxRequest', array('getIP'));
     $request->expects($this->any())->method('getIP')->will($this->returnValue('127.0.0.1'));
     $bp = \BotPassword::newFromUser($user, 'BotPasswordSessionProvider');
     $data = array('provider' => $provider, 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'userInfo' => UserInfo::newFromUser($user, true), 'persisted' => false, 'metadata' => array('centralId' => $bp->getUserCentralId(), 'appId' => $bp->getAppId(), 'token' => $bp->getToken()));
     $dataMD = $data['metadata'];
     foreach (array_keys($data['metadata']) as $key) {
         $data['metadata'] = $dataMD;
         unset($data['metadata'][$key]);
         $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
         $metadata = $info->getProviderMetadata();
         $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata));
         $this->assertSame(array(array(LogLevel::INFO, "Session X: Missing metadata: {$key}")), $logger->getBuffer());
         $logger->clearBuffer();
     }
     $data['metadata'] = $dataMD;
     $data['metadata']['appId'] = 'Foobar';
     $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
     $metadata = $info->getProviderMetadata();
     $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata));
     $this->assertSame(array(array(LogLevel::INFO, "Session X: No BotPassword for {$bp->getUserCentralId()} Foobar")), $logger->getBuffer());
     $logger->clearBuffer();
     $data['metadata'] = $dataMD;
     $data['metadata']['token'] = 'Foobar';
     $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
     $metadata = $info->getProviderMetadata();
     $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata));
     $this->assertSame(array(array(LogLevel::INFO, 'Session X: BotPassword token check failed')), $logger->getBuffer());
     $logger->clearBuffer();
     $request2 = $this->getMock('FauxRequest', array('getIP'));
     $request2->expects($this->any())->method('getIP')->will($this->returnValue('10.0.0.1'));
     $data['metadata'] = $dataMD;
     $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
     $metadata = $info->getProviderMetadata();
     $this->assertFalse($provider->refreshSessionInfo($info, $request2, $metadata));
     $this->assertSame(array(array(LogLevel::INFO, 'Session X: Restrictions check failed')), $logger->getBuffer());
     $logger->clearBuffer();
     $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
     $metadata = $info->getProviderMetadata();
     $this->assertTrue($provider->refreshSessionInfo($info, $request, $metadata));
     $this->assertSame(array(), $logger->getBuffer());
     $this->assertEquals($dataMD + array('rights' => array('read')), $metadata);
 }
Ejemplo n.º 6
0
 /**
  * Executes the log-in attempt using the parameters passed. If
  * the log-in succeeds, it attaches a cookie to the session
  * and outputs the user id, username, and session token. If a
  * log-in fails, as the result of a bad password, a nonexistent
  * user, or any other reason, the host is cached with an expiry
  * and no log-in attempts will be accepted until that expiry
  * is reached. The expiry is $this->mLoginThrottle.
  */
 public function execute()
 {
     // If we're in a mode that breaks the same-origin policy, no tokens can
     // be obtained
     if ($this->lacksSameOriginSecurity()) {
         $this->getResult()->addValue(null, 'login', array('result' => 'Aborted', 'reason' => 'Cannot log in when the same-origin policy is not applied'));
         return;
     }
     $params = $this->extractRequestParams();
     $result = array();
     // Make sure session is persisted
     $session = MediaWiki\Session\SessionManager::getGlobalSession();
     $session->persist();
     // Make sure it's possible to log in
     if (!$session->canSetUser()) {
         $this->getResult()->addValue(null, 'login', array('result' => 'Aborted', 'reason' => 'Cannot log in when using ' . $session->getProvider()->describe(Language::factory('en'))));
         return;
     }
     $authRes = false;
     $context = new DerivativeContext($this->getContext());
     $loginType = 'N/A';
     // Check login token
     $token = LoginForm::getLoginToken();
     if (!$token) {
         LoginForm::setLoginToken();
         $authRes = LoginForm::NEED_TOKEN;
     } elseif (!$params['token']) {
         $authRes = LoginForm::NEED_TOKEN;
     } elseif ($token !== $params['token']) {
         $authRes = LoginForm::WRONG_TOKEN;
     }
     // Try bot passwords
     if ($authRes === false && $this->getConfig()->get('EnableBotPasswords') && strpos($params['name'], BotPassword::getSeparator()) !== false) {
         $status = BotPassword::login($params['name'], $params['password'], $this->getRequest());
         if ($status->isOk()) {
             $session = $status->getValue();
             $authRes = LoginForm::SUCCESS;
             $loginType = 'BotPassword';
         } else {
             LoggerFactory::getInstance('authmanager')->info('BotPassword login failed: ' . $status->getWikiText());
         }
     }
     // Normal login
     if ($authRes === false) {
         $context->setRequest(new DerivativeRequest($this->getContext()->getRequest(), array('wpName' => $params['name'], 'wpPassword' => $params['password'], 'wpDomain' => $params['domain'], 'wpLoginToken' => $params['token'], 'wpRemember' => '')));
         $loginForm = new LoginForm();
         $loginForm->setContext($context);
         $authRes = $loginForm->authenticateUserData();
         $loginType = 'LoginForm';
     }
     switch ($authRes) {
         case LoginForm::SUCCESS:
             $user = $context->getUser();
             $this->getContext()->setUser($user);
             $user->setCookies($this->getRequest(), null, true);
             ApiQueryInfo::resetTokenCache();
             // Run hooks.
             // @todo FIXME: Split back and frontend from this hook.
             // @todo FIXME: This hook should be placed in the backend
             $injected_html = '';
             Hooks::run('UserLoginComplete', array(&$user, &$injected_html));
             $result['result'] = 'Success';
             $result['lguserid'] = intval($user->getId());
             $result['lgusername'] = $user->getName();
             // @todo: These are deprecated, and should be removed at some
             // point (1.28 at the earliest, and see T121527). They were ok
             // when the core cookie-based login was the only thing, but
             // CentralAuth broke that a while back and
             // SessionManager/AuthManager are *really* going to break it.
             $result['lgtoken'] = $user->getToken();
             $result['cookieprefix'] = $this->getConfig()->get('CookiePrefix');
             $result['sessionid'] = $session->getId();
             break;
         case LoginForm::NEED_TOKEN:
             $result['result'] = 'NeedToken';
             $result['token'] = LoginForm::getLoginToken();
             // @todo: See above about deprecation
             $result['cookieprefix'] = $this->getConfig()->get('CookiePrefix');
             $result['sessionid'] = $session->getId();
             break;
         case LoginForm::WRONG_TOKEN:
             $result['result'] = 'WrongToken';
             break;
         case LoginForm::NO_NAME:
             $result['result'] = 'NoName';
             break;
         case LoginForm::ILLEGAL:
             $result['result'] = 'Illegal';
             break;
         case LoginForm::WRONG_PLUGIN_PASS:
             $result['result'] = 'WrongPluginPass';
             break;
         case LoginForm::NOT_EXISTS:
             $result['result'] = 'NotExists';
             break;
             // bug 20223 - Treat a temporary password as wrong. Per SpecialUserLogin:
             // The e-mailed temporary password should not be used for actual logins.
         // bug 20223 - Treat a temporary password as wrong. Per SpecialUserLogin:
         // The e-mailed temporary password should not be used for actual logins.
         case LoginForm::RESET_PASS:
         case LoginForm::WRONG_PASS:
             $result['result'] = 'WrongPass';
             break;
         case LoginForm::EMPTY_PASS:
             $result['result'] = 'EmptyPass';
             break;
         case LoginForm::CREATE_BLOCKED:
             $result['result'] = 'CreateBlocked';
             $result['details'] = 'Your IP address is blocked from account creation';
             $block = $context->getUser()->getBlock();
             if ($block) {
                 $result = array_merge($result, ApiQueryUserInfo::getBlockInfo($block));
             }
             break;
         case LoginForm::THROTTLED:
             $result['result'] = 'Throttled';
             $throttle = $this->getConfig()->get('PasswordAttemptThrottle');
             $result['wait'] = intval($throttle['seconds']);
             break;
         case LoginForm::USER_BLOCKED:
             $result['result'] = 'Blocked';
             $block = User::newFromName($params['name'])->getBlock();
             if ($block) {
                 $result = array_merge($result, ApiQueryUserInfo::getBlockInfo($block));
             }
             break;
         case LoginForm::ABORTED:
             $result['result'] = 'Aborted';
             $result['reason'] = $loginForm->mAbortLoginErrorMsg;
             break;
         default:
             ApiBase::dieDebug(__METHOD__, "Unhandled case value: {$authRes}");
     }
     $this->getResult()->addValue(null, 'login', $result);
     LoggerFactory::getInstance('authmanager')->info('Login attempt', array('event' => 'login', 'successful' => $authRes === LoginForm::SUCCESS, 'loginType' => $loginType, 'status' => LoginForm::$statusCodes[$authRes]));
 }
Ejemplo n.º 7
0
 /**
  * There are two ways to login with a bot password: "******", "password" and
  * "username", "appId@password". Transform it so it is always in the first form.
  * Returns [bot username, bot password, could be normal password?] where the last one is a flag
  * meaning this could either be a bot password or a normal password, it cannot be decided for
  * certain (although in such cases it almost always will be a bot password).
  * If this cannot be a bot password login just return false.
  * @param string $username
  * @param string $password
  * @return array|false
  */
 public static function canonicalizeLoginData($username, $password)
 {
     $sep = BotPassword::getSeparator();
     // the strlen check helps minimize the password information obtainable from timing
     if (strlen($password) >= 32 && strpos($username, $sep) !== false) {
         // the separator is not valid in new usernames but might appear in legacy ones
         if (preg_match('/^[0-9a-w]{32,}$/', $password)) {
             return [$username, $password, true];
         }
     } elseif (strlen($password) > 32 && strpos($password, $sep) !== false) {
         $segments = explode($sep, $password);
         $password = array_pop($segments);
         $appId = implode($sep, $segments);
         if (preg_match('/^[0-9a-w]{32,}$/', $password)) {
             return [$username . $sep . $appId, $password, true];
         }
     }
     return false;
 }
Ejemplo n.º 8
0
 /**
  * Change authentication data (e.g. passwords)
  *
  * If $req was returned for AuthManager::ACTION_CHANGE, using $req should
  * result in a successful login in the future.
  *
  * If $req was returned for AuthManager::ACTION_REMOVE, using $req should
  * no longer result in a successful login.
  *
  * @param AuthenticationRequest $req
  */
 public function changeAuthenticationData(AuthenticationRequest $req)
 {
     $this->logger->info('Changing authentication data for {user} class {what}', ['user' => is_string($req->username) ? $req->username : '******', 'what' => get_class($req)]);
     $this->callMethodOnProviders(6, 'providerChangeAuthenticationData', [$req]);
     // When the main account's authentication data is changed, invalidate
     // all BotPasswords too.
     \BotPassword::invalidateAllPasswordsForUser($req->username);
 }
Ejemplo n.º 9
0
Archivo: User.php Proyecto: paladox/2
 /**
  * Actually set the password and such
  * @since 1.27 cannot set a password for a user not in the database
  * @param string|null $str New password to set or null to set an invalid
  *  password hash meaning that the user will not be able to log in
  *  through the web interface.
  */
 private function setPasswordInternal($str)
 {
     $id = self::idFromName($this->getName(), self::READ_LATEST);
     if ($id == 0) {
         throw new LogicException('Cannot set a password for a user that is not in the database.');
     }
     $passwordFactory = new PasswordFactory();
     $passwordFactory->init(RequestContext::getMain()->getConfig());
     $dbw = wfGetDB(DB_MASTER);
     $dbw->update('user', array('user_password' => $passwordFactory->newFromPlaintext($str)->toString(), 'user_newpassword' => PasswordFactory::newInvalidPassword()->toString(), 'user_newpass_time' => $dbw->timestampOrNull(null)), array('user_id' => $id), __METHOD__);
     // When the main password is changed, invalidate all bot passwords too
     BotPassword::invalidateAllPasswordsForUser($this->getName());
 }
 public function testCheckSessionInfo()
 {
     $logger = new \TestLogger(true);
     $provider = $this->getProvider();
     $provider->setLogger($logger);
     $user = static::getTestSysop()->getUser();
     $request = $this->getMock('FauxRequest', ['getIP']);
     $request->expects($this->any())->method('getIP')->will($this->returnValue('127.0.0.1'));
     $bp = \BotPassword::newFromUser($user, 'BotPasswordSessionProvider');
     $data = ['provider' => $provider, 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'userInfo' => UserInfo::newFromUser($user, true), 'persisted' => false, 'metadata' => ['centralId' => $bp->getUserCentralId(), 'appId' => $bp->getAppId(), 'token' => $bp->getToken()]];
     $dataMD = $data['metadata'];
     foreach (array_keys($data['metadata']) as $key) {
         $data['metadata'] = $dataMD;
         unset($data['metadata'][$key]);
         $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
         $metadata = $info->getProviderMetadata();
         $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata));
         $this->assertSame([[LogLevel::INFO, 'Session "{session}": Missing metadata: {missing}']], $logger->getBuffer());
         $logger->clearBuffer();
     }
     $data['metadata'] = $dataMD;
     $data['metadata']['appId'] = 'Foobar';
     $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
     $metadata = $info->getProviderMetadata();
     $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata));
     $this->assertSame([[LogLevel::INFO, 'Session "{session}": No BotPassword for {centralId} {appId}']], $logger->getBuffer());
     $logger->clearBuffer();
     $data['metadata'] = $dataMD;
     $data['metadata']['token'] = 'Foobar';
     $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
     $metadata = $info->getProviderMetadata();
     $this->assertFalse($provider->refreshSessionInfo($info, $request, $metadata));
     $this->assertSame([[LogLevel::INFO, 'Session "{session}": BotPassword token check failed']], $logger->getBuffer());
     $logger->clearBuffer();
     $request2 = $this->getMock('FauxRequest', ['getIP']);
     $request2->expects($this->any())->method('getIP')->will($this->returnValue('10.0.0.1'));
     $data['metadata'] = $dataMD;
     $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
     $metadata = $info->getProviderMetadata();
     $this->assertFalse($provider->refreshSessionInfo($info, $request2, $metadata));
     $this->assertSame([[LogLevel::INFO, 'Session "{session}": Restrictions check failed']], $logger->getBuffer());
     $logger->clearBuffer();
     $info = new SessionInfo(SessionInfo::MIN_PRIORITY, $data);
     $metadata = $info->getProviderMetadata();
     $this->assertTrue($provider->refreshSessionInfo($info, $request, $metadata));
     $this->assertSame([], $logger->getBuffer());
     $this->assertEquals($dataMD + ['rights' => ['read']], $metadata);
 }
Ejemplo n.º 11
0
 /**
  * Actually set the password and such
  * @since 1.27 cannot set a password for a user not in the database
  * @param string|null $str New password to set or null to set an invalid
  *  password hash meaning that the user will not be able to log in
  *  through the web interface.
  * @return bool Success
  */
 private function setPasswordInternal($str)
 {
     global $wgDisableAuthManager;
     if ($wgDisableAuthManager) {
         $id = self::idFromName($this->getName(), self::READ_LATEST);
         if ($id == 0) {
             throw new LogicException('Cannot set a password for a user that is not in the database.');
         }
         $passwordFactory = new PasswordFactory();
         $passwordFactory->init(RequestContext::getMain()->getConfig());
         $dbw = wfGetDB(DB_MASTER);
         $dbw->update('user', ['user_password' => $passwordFactory->newFromPlaintext($str)->toString(), 'user_newpassword' => PasswordFactory::newInvalidPassword()->toString(), 'user_newpass_time' => $dbw->timestampOrNull(null)], ['user_id' => $id], __METHOD__);
         // When the main password is changed, invalidate all bot passwords too
         BotPassword::invalidateAllPasswordsForUser($this->getName());
     } else {
         $manager = AuthManager::singleton();
         // If the user doesn't exist yet, fail
         if (!$manager->userExists($this->getName())) {
             throw new LogicException('Cannot set a password for a user that is not in the database.');
         }
         $data = ['username' => $this->getName(), 'password' => $str, 'retype' => $str];
         $reqs = $manager->getAuthenticationRequests(AuthManager::ACTION_CHANGE, $this);
         $reqs = AuthenticationRequest::loadRequestsFromSubmission($reqs, $data);
         foreach ($reqs as $req) {
             $status = $manager->allowsAuthenticationDataChange($req);
             if (!$status->isOk()) {
                 \MediaWiki\Logger\LoggerFactory::getInstance('authentication')->info(__METHOD__ . ': Password change rejected: ' . $status->getWikiText());
                 return false;
             }
         }
         foreach ($reqs as $req) {
             $manager->changeAuthenticationData($req);
         }
         $this->setOption('watchlisttoken', false);
     }
     SessionManager::singleton()->invalidateSessionsForUser($this);
     return true;
 }
Ejemplo n.º 12
0
 /**
  * Executes the log-in attempt using the parameters passed. If
  * the log-in succeeds, it attaches a cookie to the session
  * and outputs the user id, username, and session token. If a
  * log-in fails, as the result of a bad password, a nonexistent
  * user, or any other reason, the host is cached with an expiry
  * and no log-in attempts will be accepted until that expiry
  * is reached. The expiry is $this->mLoginThrottle.
  */
 public function execute()
 {
     // If we're in a mode that breaks the same-origin policy, no tokens can
     // be obtained
     if ($this->lacksSameOriginSecurity()) {
         $this->getResult()->addValue(null, 'login', ['result' => 'Aborted', 'reason' => 'Cannot log in when the same-origin policy is not applied']);
         return;
     }
     $params = $this->extractRequestParams();
     $result = [];
     // Make sure session is persisted
     $session = MediaWiki\Session\SessionManager::getGlobalSession();
     $session->persist();
     // Make sure it's possible to log in
     if (!$session->canSetUser()) {
         $this->getResult()->addValue(null, 'login', ['result' => 'Aborted', 'reason' => 'Cannot log in when using ' . $session->getProvider()->describe(Language::factory('en'))]);
         return;
     }
     $authRes = false;
     $context = new DerivativeContext($this->getContext());
     $loginType = 'N/A';
     // Check login token
     $token = $session->getToken('', 'login');
     if ($token->wasNew() || !$params['token']) {
         $authRes = 'NeedToken';
     } elseif (!$token->match($params['token'])) {
         $authRes = 'WrongToken';
     }
     // Try bot passwords
     if ($authRes === false && $this->getConfig()->get('EnableBotPasswords') && strpos($params['name'], BotPassword::getSeparator()) !== false) {
         $status = BotPassword::login($params['name'], $params['password'], $this->getRequest());
         if ($status->isOK()) {
             $session = $status->getValue();
             $authRes = 'Success';
             $loginType = 'BotPassword';
         } else {
             $authRes = 'Failed';
             $message = $status->getMessage();
             LoggerFactory::getInstance('authmanager')->info('BotPassword login failed: ' . $status->getWikiText(false, false, 'en'));
         }
     }
     if ($authRes === false) {
         if ($this->getConfig()->get('DisableAuthManager')) {
             // Non-AuthManager login
             $context->setRequest(new DerivativeRequest($this->getContext()->getRequest(), ['wpName' => $params['name'], 'wpPassword' => $params['password'], 'wpDomain' => $params['domain'], 'wpLoginToken' => $params['token'], 'wpRemember' => '']));
             $loginForm = new LoginForm();
             $loginForm->setContext($context);
             $authRes = $loginForm->authenticateUserData();
             $loginType = 'LoginForm';
             switch ($authRes) {
                 case LoginForm::SUCCESS:
                     $authRes = 'Success';
                     break;
                 case LoginForm::NEED_TOKEN:
                     $authRes = 'NeedToken';
                     break;
             }
         } else {
             // Simplified AuthManager login, for backwards compatibility
             $manager = AuthManager::singleton();
             $reqs = AuthenticationRequest::loadRequestsFromSubmission($manager->getAuthenticationRequests(AuthManager::ACTION_LOGIN, $this->getUser()), ['username' => $params['name'], 'password' => $params['password'], 'domain' => $params['domain'], 'rememberMe' => true]);
             $res = AuthManager::singleton()->beginAuthentication($reqs, 'null:');
             switch ($res->status) {
                 case AuthenticationResponse::PASS:
                     if ($this->getConfig()->get('EnableBotPasswords')) {
                         $warn = 'Main-account login via action=login is deprecated and may stop working ' . 'without warning.';
                         $warn .= ' To continue login with action=login, see [[Special:BotPasswords]].';
                         $warn .= ' To safely continue using main-account login, see action=clientlogin.';
                     } else {
                         $warn = 'Login via action=login is deprecated and may stop working without warning.';
                         $warn .= ' To safely log in, see action=clientlogin.';
                     }
                     $this->setWarning($warn);
                     $authRes = 'Success';
                     $loginType = 'AuthManager';
                     break;
                 case AuthenticationResponse::FAIL:
                     // Hope it's not a PreAuthenticationProvider that failed...
                     $authRes = 'Failed';
                     $message = $res->message;
                     \MediaWiki\Logger\LoggerFactory::getInstance('authentication')->info(__METHOD__ . ': Authentication failed: ' . $message->plain());
                     break;
                 default:
                     $authRes = 'Aborted';
                     break;
             }
         }
     }
     $result['result'] = $authRes;
     switch ($authRes) {
         case 'Success':
             if ($this->getConfig()->get('DisableAuthManager')) {
                 $user = $context->getUser();
                 $this->getContext()->setUser($user);
                 $user->setCookies($this->getRequest(), null, true);
             } else {
                 $user = $session->getUser();
             }
             ApiQueryInfo::resetTokenCache();
             // Deprecated hook
             $injected_html = '';
             Hooks::run('UserLoginComplete', [&$user, &$injected_html]);
             $result['lguserid'] = intval($user->getId());
             $result['lgusername'] = $user->getName();
             // @todo: These are deprecated, and should be removed at some
             // point (1.28 at the earliest, and see T121527). They were ok
             // when the core cookie-based login was the only thing, but
             // CentralAuth broke that a while back and
             // SessionManager/AuthManager *really* break it.
             $result['lgtoken'] = $user->getToken();
             $result['cookieprefix'] = $this->getConfig()->get('CookiePrefix');
             $result['sessionid'] = $session->getId();
             break;
         case 'NeedToken':
             $result['token'] = $token->toString();
             $this->setWarning('Fetching a token via action=login is deprecated. ' . 'Use action=query&meta=tokens&type=login instead.');
             $this->logFeatureUsage('action=login&!lgtoken');
             // @todo: See above about deprecation
             $result['cookieprefix'] = $this->getConfig()->get('CookiePrefix');
             $result['sessionid'] = $session->getId();
             break;
         case 'WrongToken':
             break;
         case 'Failed':
             $result['reason'] = $message->useDatabase('false')->inLanguage('en')->text();
             break;
         case 'Aborted':
             $result['reason'] = 'Authentication requires user interaction, ' . 'which is not supported by action=login.';
             if ($this->getConfig()->get('EnableBotPasswords')) {
                 $result['reason'] .= ' To be able to login with action=login, see [[Special:BotPasswords]].';
                 $result['reason'] .= ' To continue using main-account login, see action=clientlogin.';
             } else {
                 $result['reason'] .= ' To log in, see action=clientlogin.';
             }
             break;
             // Results from LoginForm for when $wgDisableAuthManager is true
         // Results from LoginForm for when $wgDisableAuthManager is true
         case LoginForm::WRONG_TOKEN:
             $result['result'] = 'WrongToken';
             break;
         case LoginForm::NO_NAME:
             $result['result'] = 'NoName';
             break;
         case LoginForm::ILLEGAL:
             $result['result'] = 'Illegal';
             break;
         case LoginForm::WRONG_PLUGIN_PASS:
             $result['result'] = 'WrongPluginPass';
             break;
         case LoginForm::NOT_EXISTS:
             $result['result'] = 'NotExists';
             break;
             // bug 20223 - Treat a temporary password as wrong. Per SpecialUserLogin:
             // The e-mailed temporary password should not be used for actual logins.
         // bug 20223 - Treat a temporary password as wrong. Per SpecialUserLogin:
         // The e-mailed temporary password should not be used for actual logins.
         case LoginForm::RESET_PASS:
         case LoginForm::WRONG_PASS:
             $result['result'] = 'WrongPass';
             break;
         case LoginForm::EMPTY_PASS:
             $result['result'] = 'EmptyPass';
             break;
         case LoginForm::CREATE_BLOCKED:
             $result['result'] = 'CreateBlocked';
             $result['details'] = 'Your IP address is blocked from account creation';
             $block = $context->getUser()->getBlock();
             if ($block) {
                 $result = array_merge($result, ApiQueryUserInfo::getBlockInfo($block));
             }
             break;
         case LoginForm::THROTTLED:
             $result['result'] = 'Throttled';
             $result['wait'] = intval($loginForm->mThrottleWait);
             break;
         case LoginForm::USER_BLOCKED:
             $result['result'] = 'Blocked';
             $block = User::newFromName($params['name'])->getBlock();
             if ($block) {
                 $result = array_merge($result, ApiQueryUserInfo::getBlockInfo($block));
             }
             break;
         case LoginForm::ABORTED:
             $result['result'] = 'Aborted';
             $result['reason'] = $loginForm->mAbortLoginErrorMsg;
             break;
         default:
             ApiBase::dieDebug(__METHOD__, "Unhandled case value: {$authRes}");
     }
     $this->getResult()->addValue(null, 'login', $result);
     if ($loginType === 'LoginForm' && isset(LoginForm::$statusCodes[$authRes])) {
         $authRes = LoginForm::$statusCodes[$authRes];
     }
     LoggerFactory::getInstance('authmanager')->info('Login attempt', ['event' => 'login', 'successful' => $authRes === 'Success', 'loginType' => $loginType, 'status' => $authRes]);
 }