MediaWiki  master
ApiLogin.php
Go to the documentation of this file.
1 <?php
30 
36 class ApiLogin extends ApiBase {
37 
39  private $authManager;
40 
46  public function __construct(
47  ApiMain $main,
48  $action,
49  AuthManager $authManager
50  ) {
51  parent::__construct( $main, $action, 'lg' );
52  $this->authManager = $authManager;
53  }
54 
55  protected function getExtendedDescription() {
56  if ( $this->getConfig()->get( MainConfigNames::EnableBotPasswords ) ) {
57  return 'apihelp-login-extended-description';
58  } else {
59  return 'apihelp-login-extended-description-nobotpasswords';
60  }
61  }
62 
68  private function formatMessage( $message ) {
69  $message = Message::newFromSpecifier( $message );
70  $errorFormatter = $this->getErrorFormatter();
71  if ( $errorFormatter instanceof ApiErrorFormatter_BackCompat ) {
73  $message->useDatabase( false )->inLanguage( 'en' )->text()
74  );
75  } else {
76  return $errorFormatter->formatMessage( $message );
77  }
78  }
79 
89  public function execute() {
90  // If we're in a mode that breaks the same-origin policy, no tokens can
91  // be obtained
92  if ( $this->lacksSameOriginSecurity() ) {
93  $this->getResult()->addValue( null, 'login', [
94  'result' => 'Aborted',
95  'reason' => $this->formatMessage( 'api-login-fail-sameorigin' ),
96  ] );
97 
98  return;
99  }
100 
101  $this->requirePostedParameters( [ 'password', 'token' ] );
102 
103  $params = $this->extractRequestParams();
104 
105  $result = [];
106 
107  // Make sure session is persisted
109  $session->persist();
110 
111  // Make sure it's possible to log in
112  if ( !$session->canSetUser() ) {
113  $this->getResult()->addValue( null, 'login', [
114  'result' => 'Aborted',
115  'reason' => $this->formatMessage( [
116  'api-login-fail-badsessionprovider',
117  $session->getProvider()->describe( $this->getErrorFormatter()->getLanguage() ),
118  ] )
119  ] );
120 
121  return;
122  }
123 
124  $authRes = false;
125  $loginType = 'N/A';
126 
127  // Check login token
128  $token = $session->getToken( '', 'login' );
129  if ( !$params['token'] ) {
130  $authRes = 'NeedToken';
131  } elseif ( $token->wasNew() ) {
132  $authRes = 'Failed';
133  $message = ApiMessage::create( 'authpage-cannot-login-continue', 'sessionlost' );
134  } elseif ( !$token->match( $params['token'] ) ) {
135  $authRes = 'WrongToken';
136  }
137 
138  // Try bot passwords
139  if (
140  $authRes === false && $this->getConfig()->get( MainConfigNames::EnableBotPasswords ) &&
141  ( $botLoginData = BotPassword::canonicalizeLoginData( $params['name'], $params['password'] ) )
142  ) {
143  $status = BotPassword::login(
144  $botLoginData[0], $botLoginData[1], $this->getRequest()
145  );
146  if ( $status->isOK() ) {
147  $session = $status->getValue();
148  $authRes = 'Success';
149  $loginType = 'BotPassword';
150  } elseif (
151  $status->hasMessage( 'login-throttled' ) ||
152  $status->hasMessage( 'botpasswords-needs-reset' ) ||
153  $status->hasMessage( 'botpasswords-locked' )
154  ) {
155  $authRes = 'Failed';
156  $message = $status->getMessage();
157  LoggerFactory::getInstance( 'authentication' )->info(
158  'BotPassword login failed: ' . $status->getWikiText( false, false, 'en' )
159  );
160  }
161  // For other errors, let's see if it's a valid non-bot login
162  }
163 
164  if ( $authRes === false ) {
165  // Simplified AuthManager login, for backwards compatibility
166  $reqs = AuthenticationRequest::loadRequestsFromSubmission(
167  $this->authManager->getAuthenticationRequests(
168  AuthManager::ACTION_LOGIN,
169  $this->getUser()
170  ),
171  [
172  'username' => $params['name'],
173  'password' => $params['password'],
174  'domain' => $params['domain'],
175  'rememberMe' => true,
176  ]
177  );
178  $res = $this->authManager->beginAuthentication( $reqs, 'null:' );
179  switch ( $res->status ) {
180  case AuthenticationResponse::PASS:
181  if ( $this->getConfig()->get( MainConfigNames::EnableBotPasswords ) ) {
182  $this->addDeprecation( 'apiwarn-deprecation-login-botpw', 'main-account-login' );
183  } else {
184  $this->addDeprecation( 'apiwarn-deprecation-login-nobotpw', 'main-account-login' );
185  }
186  $authRes = 'Success';
187  $loginType = 'AuthManager';
188  break;
189 
190  case AuthenticationResponse::FAIL:
191  // Hope it's not a PreAuthenticationProvider that failed...
192  $authRes = 'Failed';
193  $message = $res->message;
194  LoggerFactory::getInstance( 'authentication' )
195  ->info( __METHOD__ . ': Authentication failed: '
196  . $message->inLanguage( 'en' )->plain() );
197  break;
198 
199  default:
200  LoggerFactory::getInstance( 'authentication' )
201  ->info( __METHOD__ . ': Authentication failed due to unsupported response type: '
202  . $res->status, $this->getAuthenticationResponseLogData( $res ) );
203  $authRes = 'Aborted';
204  break;
205  }
206  }
207 
208  $result['result'] = $authRes;
209  switch ( $authRes ) {
210  case 'Success':
211  $user = $session->getUser();
212 
213  // Deprecated hook
214  $injected_html = '';
215  $this->getHookRunner()->onUserLoginComplete( $user, $injected_html, true );
216 
217  $result['lguserid'] = $user->getId();
218  $result['lgusername'] = $user->getName();
219  break;
220 
221  case 'NeedToken':
222  $result['token'] = $token->toString();
223  $this->addDeprecation( 'apiwarn-deprecation-login-token', 'action=login&!lgtoken' );
224  break;
225 
226  case 'WrongToken':
227  break;
228 
229  case 'Failed':
230  // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable
231  // message set on error
232  $result['reason'] = $this->formatMessage( $message );
233  break;
234 
235  case 'Aborted':
236  $result['reason'] = $this->formatMessage(
237  $this->getConfig()->get( MainConfigNames::EnableBotPasswords )
238  ? 'api-login-fail-aborted'
239  : 'api-login-fail-aborted-nobotpw'
240  );
241  break;
242 
243  // @codeCoverageIgnoreStart
244  // Unreachable
245  default:
246  ApiBase::dieDebug( __METHOD__, "Unhandled case value: {$authRes}" );
247  // @codeCoverageIgnoreEnd
248  }
249 
250  $this->getResult()->addValue( null, 'login', $result );
251 
252  LoggerFactory::getInstance( 'authevents' )->info( 'Login attempt', [
253  'event' => 'login',
254  'successful' => $authRes === 'Success',
255  'loginType' => $loginType,
256  'status' => $authRes,
257  ] );
258  }
259 
260  public function isDeprecated() {
261  return !$this->getConfig()->get( MainConfigNames::EnableBotPasswords );
262  }
263 
264  public function mustBePosted() {
265  return true;
266  }
267 
268  public function isReadMode() {
269  return false;
270  }
271 
272  public function isWriteMode() {
273  // (T283394) Logging in triggers some database writes, so should be marked appropriately.
274  return true;
275  }
276 
277  public function getAllowedParams() {
278  return [
279  'name' => null,
280  'password' => [
281  ParamValidator::PARAM_TYPE => 'password',
282  ],
283  'domain' => null,
284  'token' => [
285  ParamValidator::PARAM_TYPE => 'string',
286  ParamValidator::PARAM_REQUIRED => false, // for BC
287  ParamValidator::PARAM_SENSITIVE => true,
288  ApiBase::PARAM_HELP_MSG => [ 'api-help-param-token', 'login' ],
289  ],
290  ];
291  }
292 
293  protected function getExamplesMessages() {
294  return [
295  'action=login&lgname=user&lgpassword=password&lgtoken=123ABC'
296  => 'apihelp-login-example-login',
297  ];
298  }
299 
300  public function getHelpUrls() {
301  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Login';
302  }
303 
310  $ret = [
311  'status' => $response->status,
312  ];
313  if ( $response->message ) {
314  $ret['responseMessage'] = $response->message->inLanguage( 'en' )->plain();
315  }
316  $reqs = [
317  'neededRequests' => $response->neededRequests,
318  'createRequest' => $response->createRequest,
319  'linkRequest' => $response->linkRequest,
320  ];
321  foreach ( $reqs as $k => $v ) {
322  if ( $v ) {
323  $v = is_array( $v ) ? $v : [ $v ];
324  $reqClasses = array_unique( array_map( 'get_class', $v ) );
325  sort( $reqClasses );
326  $ret[$k] = implode( ', ', $reqClasses );
327  }
328  }
329  return $ret;
330  }
331 }
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:59
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition: ApiBase.php:1701
getErrorFormatter()
Definition: ApiBase.php:648
requirePostedParameters( $params, $prefix='prefix')
Die if any of the specified parameters were found in the query part of the URL rather than the post b...
Definition: ApiBase.php:998
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition: ApiBase.php:1392
getResult()
Get the result object.
Definition: ApiBase.php:637
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:773
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:166
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition: ApiBase.php:719
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition: ApiBase.php:568
Format errors and warnings in the old style, for backwards compatibility.
static stripMarkup( $text)
Turn wikitext into something resembling plaintext.
Unit to authenticate log-in attempts to the current wiki.
Definition: ApiLogin.php:36
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiLogin.php:300
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiLogin.php:272
getExtendedDescription()
Return the extended help text message.
Definition: ApiLogin.php:55
isDeprecated()
Indicates whether this module is deprecated.
Definition: ApiLogin.php:260
isReadMode()
Indicates whether this module requires read rights.
Definition: ApiLogin.php:268
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiLogin.php:264
execute()
Executes the log-in attempt using the parameters passed.
Definition: ApiLogin.php:89
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiLogin.php:277
getAuthenticationResponseLogData(AuthenticationResponse $response)
Turns an AuthenticationResponse into a hash suitable for passing to Logger.
Definition: ApiLogin.php:309
__construct(ApiMain $main, $action, AuthManager $authManager)
Definition: ApiLogin.php:46
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiLogin.php:293
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:59
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:45
static login( $username, $password, WebRequest $request)
Try to log the user in.
static canonicalizeLoginData( $username, $password)
There are two ways to login with a bot password: "username@appId", "password" and "username",...
This serves as the entry point to the authentication system.
This is a value object for authentication requests.
This is a value object to hold authentication response data.
PSR-3 logger instance factory.
A class containing constants representing the names of configuration variables.
static getGlobalSession()
If PHP's session_id() has been set, returns that session.
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition: Message.php:426
Service for formatting and validating API parameters.