Merge "API: Remove deprecated response values from action=login"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 11 Oct 2016 16:27:54 +0000 (16:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 11 Oct 2016 16:27:54 +0000 (16:27 +0000)
1  2 
includes/api/ApiLogin.php
tests/phpunit/includes/api/ApiLoginTest.php

@@@ -42,7 -42,9 +42,7 @@@ class ApiLogin extends ApiBase 
        }
  
        protected function getDescriptionMessage() {
 -              if ( $this->getConfig()->get( 'DisableAuthManager' ) ) {
 -                      return 'apihelp-login-description-nonauthmanager';
 -              } elseif ( $this->getConfig()->get( 'EnableBotPasswords' ) ) {
 +              if ( $this->getConfig()->get( 'EnableBotPasswords' ) ) {
                        return 'apihelp-login-description';
                } else {
                        return 'apihelp-login-description-nobotpasswords';
                        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 = [];
                }
  
                // Try bot passwords
 -              if ( $authRes === false && $this->getConfig()->get( 'EnableBotPasswords' ) &&
 -                      strpos( $params['name'], BotPassword::getSeparator() ) !== false
 +              if (
 +                      $authRes === false && $this->getConfig()->get( 'EnableBotPasswords' ) &&
 +                      ( $botLoginData = BotPassword::canonicalizeLoginData( $params['name'], $params['password'] ) )
                ) {
                        $status = BotPassword::login(
 -                              $params['name'], $params['password'], $this->getRequest()
 +                              $botLoginData[0], $botLoginData[1], $this->getRequest()
                        );
                        if ( $status->isOK() ) {
                                $session = $status->getValue();
                                $authRes = 'Success';
                                $loginType = 'BotPassword';
 -                      } else {
 +                      } elseif ( !$botLoginData[2] ) {
                                $authRes = 'Failed';
                                $message = $status->getMessage();
 -                              LoggerFactory::getInstance( 'authmanager' )->info(
 +                              LoggerFactory::getInstance( 'authentication' )->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;
 -                              }
 +                      // 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':
 -                              if ( $this->getConfig()->get( 'DisableAuthManager' ) ) {
 -                                      $user = $context->getUser();
 -                                      $this->getContext()->setUser( $user );
 -                                      $user->setCookies( $this->getRequest(), null, true );
 -                              } else {
 -                                      $user = $session->getUser();
 -                              }
 +                              $user = $session->getUser();
  
                                ApiQueryInfo::resetTokenCache();
  
  
                                $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':
                                $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;
  
 -                      // 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.
 -                      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}" );
                }
                if ( $loginType === 'LoginForm' && isset( LoginForm::$statusCodes[$authRes] ) ) {
                        $authRes = LoginForm::$statusCodes[$authRes];
                }
 -              LoggerFactory::getInstance( 'authmanager' )->info( 'Login attempt', [
 +              LoggerFactory::getInstance( 'authevents' )->info( 'Login attempt', [
                        'event' => 'login',
                        'successful' => $authRes === 'Success',
                        'loginType' => $loginType,
        }
  
        public function isDeprecated() {
 -              return !$this->getConfig()->get( 'DisableAuthManager' ) &&
 -                      !$this->getConfig()->get( 'EnableBotPasswords' );
 +              return !$this->getConfig()->get( 'EnableBotPasswords' );
        }
  
        public function mustBePosted() {
        public function getHelpUrls() {
                return 'https://www.mediawiki.org/wiki/API:Login';
        }
 +
 +      /**
 +       * Turns an AuthenticationResponse into a hash suitable for passing to Logger
 +       * @param AuthenticationResponse $response
 +       * @return array
 +       */
 +      protected function getAuthenticationResponseLogData( AuthenticationResponse $response ) {
 +              $ret = [
 +                      'status' => $response->status,
 +              ];
 +              if ( $response->message ) {
 +                      $ret['message'] = $response->message->inLanguage( 'en' )->plain();
 +              };
 +              $reqs = [
 +                      'neededRequests' => $response->neededRequests,
 +                      'createRequest' => $response->createRequest,
 +                      'linkRequest' => $response->linkRequest,
 +              ];
 +              foreach ( $reqs as $k => $v ) {
 +                      if ( $v ) {
 +                              $v = is_array( $v ) ? $v : [ $v ];
 +                              $reqClasses = array_unique( array_map( 'get_class', $v ) );
 +                              sort( $reqClasses );
 +                              $ret[$k] = implode( ', ', $reqClasses );
 +                      }
 +              }
 +              return $ret;
 +      }
  }
@@@ -13,6 -13,8 +13,6 @@@ class ApiLoginTest extends ApiTestCase 
         * Test result of attempted login with an empty username
         */
        public function testApiLoginNoName() {
 -              global $wgDisableAuthManager;
 -
                $session = [
                        'wsTokenSecrets' => [ 'login' => 'foobar' ],
                ];
                        'lgname' => '', 'lgpassword' => self::$users['sysop']->getPassword(),
                        'lgtoken' => (string)( new MediaWiki\Session\Token( 'foobar', '' ) )
                ], $session );
 -              $this->assertEquals( $wgDisableAuthManager ? 'NoName' : 'Failed', $data[0]['login']['result'] );
 +              $this->assertEquals( 'Failed', $data[0]['login']['result'] );
        }
  
        public function testApiLoginBadPass() {
 -              global $wgServer, $wgDisableAuthManager;
 +              global $wgServer;
  
                $user = self::$users['sysop'];
                $userName = $user->getUser()->getName();
@@@ -62,7 -64,7 +62,7 @@@
                $this->assertNotInternalType( "bool", $result );
                $a = $result["login"]["result"];
  
 -              $this->assertEquals( $wgDisableAuthManager ? 'WrongPass' : 'Failed', $a );
 +              $this->assertEquals( 'Failed', $a );
        }
  
        public function testApiLoginGoodPass() {
                $this->assertArrayHasKey( "login", $data[0] );
                $this->assertArrayHasKey( "result", $data[0]['login'] );
                $this->assertEquals( "Success", $data[0]['login']['result'] );
-               $this->assertArrayHasKey( 'lgtoken', $data[0]['login'] );
        }
  
        public function testBotPassword() {
                $centralId = CentralIdLookup::factory()->centralIdFromLocalUser( $user->getUser() );
                $this->assertNotEquals( 0, $centralId, 'sanity check' );
  
 +              $password = 'ngfhmjm64hv0854493hsj5nncjud2clk';
                $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
 -              $passwordHash = $passwordFactory->newFromPlaintext( 'foobaz' );
 +              $passwordHash = $passwordFactory->newFromPlaintext( $password );
  
                $dbw = wfGetDB( DB_MASTER );
                $dbw->insert(
                $ret = $this->doApiRequest( [
                        'action' => 'login',
                        'lgname' => $lgName,
 -                      'lgpassword' => 'foobaz',
 +                      'lgpassword' => $password,
                ] );
  
                $result = $ret[0];
                        'action' => 'login',
                        'lgtoken' => $token,
                        'lgname' => $lgName,
 -                      'lgpassword' => 'foobaz',
 +                      'lgpassword' => $password,
                ], $ret[2] );
  
                $result = $ret[0];