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
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 = SessionManager::getGlobalSession();
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
224 // Deprecated hook
225 $injected_html = '';
226 $this->getHookRunner()->onUserLoginComplete( $user, $injected_html, true );
227
228 $result['lguserid'] = $user->getId();
229 $result['lgusername'] = $user->getName();
230 break;
231
232 case 'NeedToken':
233 $result['token'] = $token->toString();
234 $this->addDeprecation( 'apiwarn-deprecation-login-token', 'action=login&!lgtoken' );
235 break;
236
237 case 'WrongToken':
238 break;
239
240 case 'Failed':
241 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable
242 // message set on error
243 $result['reason'] = $this->formatMessage( $message );
244 break;
245
246 case 'Aborted':
247 $result['reason'] = $this->formatMessage(
249 ? 'api-login-fail-aborted'
250 : 'api-login-fail-aborted-nobotpw'
251 );
252 break;
253
254 // @codeCoverageIgnoreStart
255 // Unreachable
256 default:
257 ApiBase::dieDebug( __METHOD__, "Unhandled case value: {$authRes}" );
258 // @codeCoverageIgnoreEnd
259 }
260
261 $this->getResult()->addValue( null, 'login', $result );
262
263 LoggerFactory::getInstance( 'authevents' )->info( 'Login attempt', [
264 'event' => 'login',
265 'successful' => $authRes === 'Success',
266 'accountType' => $this->identityUtils->getShortUserTypeInternal( $performer ),
267 'loginType' => $loginType,
268 'status' => $authRes,
269 ] );
270 }
271
272 public function isDeprecated() {
273 return !$this->getConfig()->get( MainConfigNames::EnableBotPasswords );
274 }
275
276 public function mustBePosted() {
277 return true;
278 }
279
280 public function isReadMode() {
281 return false;
282 }
283
284 public function isWriteMode() {
285 // (T283394) Logging in triggers some database writes, so should be marked appropriately.
286 return true;
287 }
288
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
305 protected function getExamplesMessages() {
306 return [
307 'action=login&lgname=user&lgpassword=password&lgtoken=123ABC'
308 => 'apihelp-login-example-login',
309 ];
310 }
311
312 public function getHelpUrls() {
313 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Login';
314 }
315
322 $ret = [
323 'status' => $response->status,
324 ];
325 if ( $response->message ) {
326 $ret['responseMessage'] = $response->message->inLanguage( 'en' )->plain();
327 }
328 $reqs = [
329 'neededRequests' => $response->neededRequests,
330 'createRequest' => $response->createRequest,
331 'linkRequest' => $response->linkRequest,
332 ];
333 foreach ( $reqs as $k => $v ) {
334 if ( $v ) {
335 $v = is_array( $v ) ? $v : [ $v ];
336 $reqClasses = array_unique( array_map( 'get_class', $v ) );
337 sort( $reqClasses );
338 $ret[$k] = implode( ', ', $reqClasses );
339 }
340 }
341 return $ret;
342 }
343}
344
346class_alias( ApiLogin::class, 'ApiLogin' );
array $params
The job parameters.
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:76
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:1112
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:795
getResult()
Get the result object.
Definition ApiBase.php:710
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition ApiBase.php:637
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1808
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:184
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition ApiBase.php:1497
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:851
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:289
getExamplesMessages()
Returns usage examples for this module.
Definition ApiLogin.php:305
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition ApiLogin.php:276
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:284
isReadMode()
Indicates whether this module requires read rights.
Definition ApiLogin.php:280
getHelpUrls()
Return links to more detailed help pages about the module.
Definition ApiLogin.php:312
getAuthenticationResponseLogData(AuthenticationResponse $response)
Turns an AuthenticationResponse into a hash suitable for passing to Logger.
Definition ApiLogin.php:321
isDeprecated()
Indicates whether this module is deprecated.
Definition ApiLogin.php:272
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:54
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:78
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:155
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition Message.php:471
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.