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