MediaWiki master
ApiLogin.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\Api;
11
23
29class ApiLogin extends ApiBase {
30
31 public function __construct(
32 ApiMain $main,
33 string $action,
34 private readonly AuthManager $authManager,
35 private readonly UserIdentityUtils $identityUtils
36 ) {
37 parent::__construct( $main, $action, 'lg' );
38 }
39
41 protected function getExtendedDescription() {
42 if ( $this->getConfig()->get( MainConfigNames::EnableBotPasswords ) ) {
43 return 'apihelp-login-extended-description';
44 } else {
45 return 'apihelp-login-extended-description-nobotpasswords';
46 }
47 }
48
54 private function formatMessage( $message ) {
55 $message = Message::newFromSpecifier( $message );
56 $errorFormatter = $this->getErrorFormatter();
57 if ( $errorFormatter instanceof ApiErrorFormatter_BackCompat ) {
59 $message->useDatabase( false )->inLanguage( 'en' )->text()
60 );
61 } else {
62 return $errorFormatter->formatMessage( $message );
63 }
64 }
65
71 private function getErrorCode( $message ) {
72 $message = Message::newFromSpecifier( $message );
73 if ( $message instanceof ApiMessage ) {
74 return $message->getApiCode();
75 } else {
76 return $message->getKey();
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
108 $session = $this->getRequest()->getSession();
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 $performer = $this->getUser();
127
128 // Check login token
129 $token = $session->getToken( '', 'login' );
130 if ( !$params['token'] ) {
131 $authRes = 'NeedToken';
132 } elseif ( $token->wasNew() ) {
133 $authRes = 'Failed';
134 $message = ApiMessage::create( 'authpage-cannot-login-continue', 'sessionlost' );
135 } elseif ( !$token->match( $params['token'] ) ) {
136 $authRes = 'WrongToken';
137 }
138
139 // Try bot passwords
140 if ( $authRes === false && $this->getConfig()->get( MainConfigNames::EnableBotPasswords ) ) {
141 $botLoginData = BotPassword::canonicalizeLoginData( $params['name'] ?? '', $params['password'] ?? '' );
142 if ( $botLoginData ) {
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 }
162 // For other errors, let's see if it's a valid non-bot login
163 }
164
165 if ( $authRes === false ) {
166 // Simplified AuthManager login, for backwards compatibility
167 $reqs = AuthenticationRequest::loadRequestsFromSubmission(
168 $this->authManager->getAuthenticationRequests(
169 AuthManager::ACTION_LOGIN,
170 $this->getUser()
171 ),
172 [
173 'username' => $params['name'],
174 'password' => $params['password'],
175 'domain' => $params['domain'],
176 'rememberMe' => true,
177 ]
178 );
179 $res = $this->authManager->beginAuthentication( $reqs, 'null:' );
180 switch ( $res->status ) {
181 case AuthenticationResponse::PASS:
182 if ( $this->getConfig()->get( MainConfigNames::EnableBotPasswords ) ) {
183 $this->addDeprecation( 'apiwarn-deprecation-login-botpw', 'main-account-login' );
184 } else {
185 $this->addDeprecation( 'apiwarn-deprecation-login-nobotpw', 'main-account-login' );
186 }
187 $authRes = 'Success';
188 $loginType = 'AuthManager';
189 break;
190
191 case AuthenticationResponse::FAIL:
192 // Hope it's not a PreAuthenticationProvider that failed...
193 $authRes = 'Failed';
194 $message = $res->message;
195 LoggerFactory::getInstance( 'authentication' )
196 ->info( __METHOD__ . ': Authentication failed: '
197 . $message->inLanguage( 'en' )->plain() );
198 break;
199
200 default:
201 LoggerFactory::getInstance( 'authentication' )
202 ->info( __METHOD__ . ': Authentication failed due to unsupported response type: '
203 . $res->status, $this->getAuthenticationResponseLogData( $res ) );
204 $authRes = 'Aborted';
205 break;
206 }
207 }
208
209 $result['result'] = $authRes;
210 switch ( $authRes ) {
211 case 'Success':
212 $user = $session->getUser();
213 $user->debouncedDBTouch();
214
215 // Deprecated hook
216 $injected_html = '';
217 $this->getHookRunner()->onUserLoginComplete( $user, $injected_html, true );
218
219 $result['lguserid'] = $user->getId();
220 $result['lgusername'] = $user->getName();
221 break;
222
223 case 'NeedToken':
224 $result['token'] = $token->toString();
225 $this->addDeprecation( 'apiwarn-deprecation-login-token', 'action=login&!lgtoken' );
226 break;
227
228 case 'WrongToken':
229 break;
230
231 case 'Failed':
232 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable
233 // message set on error
234 $result['reason'] = $this->formatMessage( $message );
235 break;
236
237 case 'Aborted':
238 $result['reason'] = $this->formatMessage(
240 ? 'api-login-fail-aborted'
241 : 'api-login-fail-aborted-nobotpw'
242 );
243 break;
244
245 // @codeCoverageIgnoreStart
246 // Unreachable
247 default:
248 ApiBase::dieDebug( __METHOD__, "Unhandled case value: {$authRes}" );
249 // @codeCoverageIgnoreEnd
250 }
251
252 $this->getResult()->addValue( null, 'login', $result );
253
254 LoggerFactory::getInstance( 'authevents' )->info( 'Login attempt', [
255 'event' => 'login',
256 'successful' => $authRes === 'Success',
257 'accountType' => $this->identityUtils->getShortUserTypeInternal( $performer ),
258 'loginType' => $loginType,
259 'status' => ( $authRes === 'Failed' && isset( $message ) ) ? $this->getErrorCode( $message ) : $authRes,
260 'full_message' => isset( $message ) ? $this->formatMessage( $message ) : '',
261 ] );
262 }
263
265 public function deprecationMsg(): ?MessageSpecifier {
266 if ( $this->getConfig()->get( MainConfigNames::EnableBotPasswords ) ) {
267 return null;
268 }
269 return new MessageValue( 'apiwarn-deprecation-login-nobotpw' );
270 }
271
273 public function mustBePosted() {
274 return true;
275 }
276
278 public function isReadMode() {
279 return false;
280 }
281
283 public function isWriteMode() {
284 // (T283394) Logging in triggers some database writes, so should be marked appropriately.
285 return true;
286 }
287
289 public function getAllowedParams() {
290 return [
291 'name' => null,
292 'password' => [
293 ParamValidator::PARAM_TYPE => 'password',
294 ],
295 'domain' => null,
296 'token' => [
297 ParamValidator::PARAM_TYPE => 'string',
298 ParamValidator::PARAM_REQUIRED => false, // for BC
299 ParamValidator::PARAM_SENSITIVE => true,
300 ApiBase::PARAM_HELP_MSG => [ 'api-help-param-token', 'login' ],
301 ],
302 ];
303 }
304
306 protected function getExamplesMessages() {
307 return [
308 'action=login&lgname=user&lgpassword=password&lgtoken=123ABC'
309 => 'apihelp-login-example-login',
310 ];
311 }
312
314 public function getHelpUrls() {
315 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Login';
316 }
317
324 $ret = [
325 'status' => $response->status,
326 ];
327 if ( $response->message ) {
328 $ret['responseMessage'] = $response->message->inLanguage( 'en' )->plain();
329 }
330 $reqs = [
331 'neededRequests' => $response->neededRequests,
332 'createRequest' => $response->createRequest,
333 'linkRequest' => $response->linkRequest,
334 ];
335 foreach ( $reqs as $k => $v ) {
336 if ( $v ) {
337 $v = is_array( $v ) ? $v : [ $v ];
338 $reqClasses = array_unique( array_map( 'get_class', $v ) );
339 sort( $reqClasses );
340 $ret[$k] = implode( ', ', $reqClasses );
341 }
342 }
343 return $ret;
344 }
345}
346
348class_alias( ApiLogin::class, 'ApiLogin' );
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:60
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:1101
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:166
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:29
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiLogin.php:289
deprecationMsg()
Returns a MessageSpecifier describing the deprecation if this module is deprecated,...
Definition ApiLogin.php:265
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
Definition ApiLogin.php:306
mustBePosted()
Indicates whether this module must be called with a POST request.Implementations of this method must ...
Definition ApiLogin.php:273
__construct(ApiMain $main, string $action, private readonly AuthManager $authManager, private readonly UserIdentityUtils $identityUtils)
Definition ApiLogin.php:31
getExtendedDescription()
Return the extended help text message.This is additional text to display at the top of the help secti...
Definition ApiLogin.php:41
isWriteMode()
Indicates whether this module requires write access to the wiki.API modules must override this method...
Definition ApiLogin.php:283
isReadMode()
Indicates whether this module requires read rights.to override bool
Definition ApiLogin.php:278
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
Definition ApiLogin.php:314
getAuthenticationResponseLogData(AuthenticationResponse $response)
Turns an AuthenticationResponse into a hash suitable for passing to Logger.
Definition ApiLogin.php:323
execute()
Executes the log-in attempt using the parameters passed.
Definition ApiLogin.php:89
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 login(string $username, string $password, WebRequest $request)
Try to log the user in.
static canonicalizeLoginData(string $username, string $password)
There are two ways to login with a bot password: "username@appId", "password" and "username",...
Convenience functions for interpreting UserIdentity objects using additional services or config.
Value object representing a message for i18n.
Service for formatting and validating API parameters.