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