1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Holds shared logic for login and account creation pages. |
4
|
|
|
* |
5
|
|
|
* This program is free software; you can redistribute it and/or modify |
6
|
|
|
* it under the terms of the GNU General Public License as published by |
7
|
|
|
* the Free Software Foundation; either version 2 of the License, or |
8
|
|
|
* (at your option) any later version. |
9
|
|
|
* |
10
|
|
|
* This program is distributed in the hope that it will be useful, |
11
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
12
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13
|
|
|
* GNU General Public License for more details. |
14
|
|
|
* |
15
|
|
|
* You should have received a copy of the GNU General Public License along |
16
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc., |
17
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18
|
|
|
* http://www.gnu.org/copyleft/gpl.html |
19
|
|
|
* |
20
|
|
|
* @file |
21
|
|
|
* @ingroup SpecialPage |
22
|
|
|
*/ |
23
|
|
|
|
24
|
|
|
use MediaWiki\Auth\AuthenticationRequest; |
25
|
|
|
use MediaWiki\Auth\AuthenticationResponse; |
26
|
|
|
use MediaWiki\Auth\AuthManager; |
27
|
|
|
use MediaWiki\Auth\Throttler; |
28
|
|
|
use MediaWiki\Logger\LoggerFactory; |
29
|
|
|
use MediaWiki\Session\SessionManager; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Holds shared logic for login and account creation pages. |
33
|
|
|
* |
34
|
|
|
* @ingroup SpecialPage |
35
|
|
|
*/ |
36
|
|
|
abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage { |
37
|
|
|
protected $mReturnTo; |
38
|
|
|
protected $mPosted; |
39
|
|
|
protected $mAction; |
40
|
|
|
protected $mLanguage; |
41
|
|
|
protected $mReturnToQuery; |
42
|
|
|
protected $mToken; |
43
|
|
|
protected $mStickHTTPS; |
44
|
|
|
protected $mFromHTTP; |
45
|
|
|
protected $mEntryError = ''; |
46
|
|
|
protected $mEntryErrorType = 'error'; |
47
|
|
|
|
48
|
|
|
protected $mLoaded = false; |
49
|
|
|
protected $mLoadedRequest = false; |
50
|
|
|
protected $mSecureLoginUrl; |
51
|
|
|
|
52
|
|
|
/** @var string */ |
53
|
|
|
protected $securityLevel; |
54
|
|
|
|
55
|
|
|
/** @var bool True if the user if creating an account for someone else. Flag used for internal |
56
|
|
|
* communication, only set at the very end. */ |
57
|
|
|
protected $proxyAccountCreation; |
58
|
|
|
/** @var User FIXME another flag for passing data. */ |
59
|
|
|
protected $targetUser; |
60
|
|
|
|
61
|
|
|
/** @var HTMLForm */ |
62
|
|
|
protected $authForm; |
63
|
|
|
|
64
|
|
|
/** @var FakeAuthTemplate */ |
65
|
|
|
protected $fakeTemplate; |
66
|
|
|
|
67
|
|
|
abstract protected function isSignup(); |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @param bool $direct True if the action was successful just now; false if that happened |
71
|
|
|
* pre-redirection (so this handler was called already) |
72
|
|
|
* @param StatusValue|null $extraMessages |
73
|
|
|
* @return void |
74
|
|
|
*/ |
75
|
|
|
abstract protected function successfulAction( $direct = false, $extraMessages = null ); |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Logs to the authmanager-stats channel. |
79
|
|
|
* @param bool $success |
80
|
|
|
* @param string|null $status Error message key |
81
|
|
|
*/ |
82
|
|
|
abstract protected function logAuthResult( $success, $status = null ); |
83
|
|
|
|
84
|
|
|
public function __construct( $name ) { |
85
|
|
|
global $wgUseMediaWikiUIEverywhere; |
86
|
|
|
parent::__construct( $name ); |
87
|
|
|
|
88
|
|
|
// Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui |
89
|
|
|
$wgUseMediaWikiUIEverywhere = true; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
protected function setRequest( array $data, $wasPosted = null ) { |
93
|
|
|
parent::setRequest( $data, $wasPosted ); |
94
|
|
|
$this->mLoadedRequest = false; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Load basic request parameters for this Special page. |
99
|
|
|
* @param $subPage |
100
|
|
|
*/ |
101
|
|
|
private function loadRequestParameters( $subPage ) { |
102
|
|
|
if ( $this->mLoadedRequest ) { |
103
|
|
|
return; |
104
|
|
|
} |
105
|
|
|
$this->mLoadedRequest = true; |
106
|
|
|
$request = $this->getRequest(); |
107
|
|
|
|
108
|
|
|
$this->mPosted = $request->wasPosted(); |
109
|
|
|
$this->mIsReturn = $subPage === 'return'; |
|
|
|
|
110
|
|
|
$this->mAction = $request->getVal( 'action' ); |
111
|
|
|
$this->mFromHTTP = $request->getBool( 'fromhttp', false ) |
112
|
|
|
|| $request->getBool( 'wpFromhttp', false ); |
113
|
|
|
$this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' ) |
114
|
|
|
|| $request->getBool( 'wpForceHttps', false ); |
115
|
|
|
$this->mLanguage = $request->getText( 'uselang' ); |
116
|
|
|
$this->mReturnTo = $request->getVal( 'returnto', '' ); |
117
|
|
|
$this->mReturnToQuery = $request->getVal( 'returntoquery', '' ); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Load data from request. |
122
|
|
|
* @private |
123
|
|
|
* @param string $subPage Subpage of Special:Userlogin |
124
|
|
|
*/ |
125
|
|
|
protected function load( $subPage ) { |
126
|
|
|
global $wgSecureLogin; |
127
|
|
|
|
128
|
|
|
$this->loadRequestParameters( $subPage ); |
129
|
|
|
if ( $this->mLoaded ) { |
130
|
|
|
return; |
131
|
|
|
} |
132
|
|
|
$this->mLoaded = true; |
133
|
|
|
$request = $this->getRequest(); |
134
|
|
|
|
135
|
|
|
$securityLevel = $this->getRequest()->getText( 'force' ); |
136
|
|
|
if ( |
137
|
|
|
$securityLevel && AuthManager::singleton()->securitySensitiveOperationStatus( |
138
|
|
|
$securityLevel ) === AuthManager::SEC_REAUTH |
139
|
|
|
) { |
140
|
|
|
$this->securityLevel = $securityLevel; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
$this->loadAuth( $subPage ); |
144
|
|
|
|
145
|
|
|
$this->mToken = $request->getVal( $this->getTokenName() ); |
146
|
|
|
|
147
|
|
|
// Show an error or warning passed on from a previous page |
148
|
|
|
$entryError = $this->msg( $request->getVal( 'error', '' ) ); |
149
|
|
|
$entryWarning = $this->msg( $request->getVal( 'warning', '' ) ); |
150
|
|
|
// bc: provide login link as a parameter for messages where the translation |
151
|
|
|
// was not updated |
152
|
|
|
$loginreqlink = Linker::linkKnown( |
153
|
|
|
$this->getPageTitle(), |
154
|
|
|
$this->msg( 'loginreqlink' )->escaped(), |
155
|
|
|
[], |
156
|
|
|
[ |
157
|
|
|
'returnto' => $this->mReturnTo, |
158
|
|
|
'returntoquery' => $this->mReturnToQuery, |
159
|
|
|
'uselang' => $this->mLanguage ?: null, |
160
|
|
|
'fromhttp' => $wgSecureLogin && $this->mFromHTTP ? '1' : null, |
161
|
|
|
] |
162
|
|
|
); |
163
|
|
|
|
164
|
|
|
// Only show valid error or warning messages. |
165
|
|
|
if ( $entryError->exists() |
166
|
|
|
&& in_array( $entryError->getKey(), LoginHelper::getValidErrorMessages(), true ) |
167
|
|
|
) { |
168
|
|
|
$this->mEntryErrorType = 'error'; |
169
|
|
|
$this->mEntryError = $entryError->rawParams( $loginreqlink )->parse(); |
170
|
|
|
|
171
|
|
|
} elseif ( $entryWarning->exists() |
172
|
|
|
&& in_array( $entryWarning->getKey(), LoginHelper::getValidErrorMessages(), true ) |
173
|
|
|
) { |
174
|
|
|
$this->mEntryErrorType = 'warning'; |
175
|
|
|
$this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse(); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
# 1. When switching accounts, it sucks to get automatically logged out |
179
|
|
|
# 2. Do not return to PasswordReset after a successful password change |
180
|
|
|
# but goto Wiki start page (Main_Page) instead ( bug 33997 ) |
181
|
|
|
$returnToTitle = Title::newFromText( $this->mReturnTo ); |
182
|
|
|
if ( is_object( $returnToTitle ) |
183
|
|
|
&& ( $returnToTitle->isSpecial( 'Userlogout' ) |
184
|
|
|
|| $returnToTitle->isSpecial( 'PasswordReset' ) ) |
185
|
|
|
) { |
186
|
|
|
$this->mReturnTo = ''; |
187
|
|
|
$this->mReturnToQuery = ''; |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
protected function getPreservedParams( $withToken = false ) { |
192
|
|
|
global $wgSecureLogin; |
193
|
|
|
|
194
|
|
|
$params = parent::getPreservedParams( $withToken ); |
195
|
|
|
$params += [ |
196
|
|
|
'returnto' => $this->mReturnTo ?: null, |
197
|
|
|
'returntoquery' => $this->mReturnToQuery ?: null, |
198
|
|
|
]; |
199
|
|
|
if ( $wgSecureLogin && !$this->isSignup() ) { |
200
|
|
|
$params['fromhttp'] = $this->mFromHTTP ? '1' : null; |
201
|
|
|
} |
202
|
|
|
return $params; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
protected function beforeExecute( $subPage ) { |
206
|
|
|
// finish initializing the class before processing the request - T135924 |
207
|
|
|
$this->loadRequestParameters( $subPage ); |
208
|
|
|
return parent::beforeExecute( $subPage ); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @param string|null $subPage |
213
|
|
|
*/ |
214
|
|
|
public function execute( $subPage ) { |
215
|
|
|
$authManager = AuthManager::singleton(); |
216
|
|
|
$session = SessionManager::getGlobalSession(); |
217
|
|
|
|
218
|
|
|
// Session data is used for various things in the authentication process, so we must make |
219
|
|
|
// sure a session cookie or some equivalent mechanism is set. |
220
|
|
|
$session->persist(); |
221
|
|
|
|
222
|
|
|
$this->load( $subPage ); |
223
|
|
|
$this->setHeaders(); |
224
|
|
|
$this->checkPermissions(); |
225
|
|
|
|
226
|
|
|
// Make sure the system configuration allows log in / sign up |
227
|
|
|
if ( !$this->isSignup() && !$authManager->canAuthenticateNow() ) { |
228
|
|
View Code Duplication |
if ( !$session->canSetUser() ) { |
229
|
|
|
throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [ |
230
|
|
|
$session->getProvider()->describe( RequestContext::getMain()->getLanguage() ) |
231
|
|
|
] ); |
232
|
|
|
} |
233
|
|
|
throw new ErrorPageError( 'cannotlogin-title', 'cannotlogin-text' ); |
234
|
|
|
} elseif ( $this->isSignup() && !$authManager->canCreateAccounts() ) { |
235
|
|
|
throw new ErrorPageError( 'cannotcreateaccount-title', 'cannotcreateaccount-text' ); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/* |
239
|
|
|
* In the case where the user is already logged in, and was redirected to |
240
|
|
|
* the login form from a page that requires login, do not show the login |
241
|
|
|
* page. The use case scenario for this is when a user opens a large number |
242
|
|
|
* of tabs, is redirected to the login page on all of them, and then logs |
243
|
|
|
* in on one, expecting all the others to work properly. |
244
|
|
|
* |
245
|
|
|
* However, do show the form if it was visited intentionally (no 'returnto' |
246
|
|
|
* is present). People who often switch between several accounts have grown |
247
|
|
|
* accustomed to this behavior. |
248
|
|
|
* |
249
|
|
|
* Also make an exception when force=<level> is set in the URL, which means the user must |
250
|
|
|
* reauthenticate for security reasons. |
251
|
|
|
*/ |
252
|
|
|
if ( !$this->isSignup() && !$this->mPosted && !$this->securityLevel && |
253
|
|
|
( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) && |
254
|
|
|
$this->getUser()->isLoggedIn() |
255
|
|
|
) { |
256
|
|
|
$this->successfulAction(); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
// If logging in and not on HTTPS, either redirect to it or offer a link. |
260
|
|
|
global $wgSecureLogin; |
261
|
|
|
if ( $this->getRequest()->getProtocol() !== 'https' ) { |
262
|
|
|
$title = $this->getFullTitle(); |
263
|
|
|
$query = $this->getPreservedParams( false ) + [ |
264
|
|
|
'title' => null, |
265
|
|
|
( $this->mEntryErrorType === 'error' ? 'error' |
266
|
|
|
: 'warning' ) => $this->mEntryError, |
267
|
|
|
] + $this->getRequest()->getQueryValues(); |
268
|
|
|
$url = $title->getFullURL( $query, false, PROTO_HTTPS ); |
269
|
|
|
if ( $wgSecureLogin && !$this->mFromHTTP && |
270
|
|
|
wfCanIPUseHTTPS( $this->getRequest()->getIP() ) |
271
|
|
|
) { |
272
|
|
|
// Avoid infinite redirect |
273
|
|
|
$url = wfAppendQuery( $url, 'fromhttp=1' ); |
274
|
|
|
$this->getOutput()->redirect( $url ); |
275
|
|
|
// Since we only do this redir to change proto, always vary |
276
|
|
|
$this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' ); |
277
|
|
|
|
278
|
|
|
return; |
279
|
|
|
} else { |
280
|
|
|
// A wiki without HTTPS login support should set $wgServer to |
281
|
|
|
// http://somehost, in which case the secure URL generated |
282
|
|
|
// above won't actually start with https:// |
283
|
|
|
if ( substr( $url, 0, 8 ) === 'https://' ) { |
284
|
|
|
$this->mSecureLoginUrl = $url; |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
if ( !$this->isActionAllowed( $this->authAction ) ) { |
290
|
|
|
// FIXME how do we explain this to the user? can we handle session loss better? |
291
|
|
|
// messages used: authpage-cannot-login, authpage-cannot-login-continue, |
292
|
|
|
// authpage-cannot-create, authpage-cannot-create-continue |
293
|
|
|
$this->mainLoginForm( [], 'authpage-cannot-' . $this->authAction ); |
294
|
|
|
return; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
$status = $this->trySubmit(); |
298
|
|
|
|
299
|
|
|
if ( !$status || !$status->isGood() ) { |
300
|
|
|
$this->mainLoginForm( $this->authRequests, $status ? $status->getMessage() : '', 'error' ); |
301
|
|
|
return; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** @var AuthenticationResponse $response */ |
305
|
|
|
$response = $status->getValue(); |
306
|
|
|
|
307
|
|
|
$returnToUrl = $this->getPageTitle( 'return' ) |
308
|
|
|
->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS ); |
309
|
|
|
switch ( $response->status ) { |
310
|
|
|
case AuthenticationResponse::PASS: |
311
|
|
|
$this->logAuthResult( true ); |
312
|
|
|
$this->proxyAccountCreation = $this->isSignup() && !$this->getUser()->isAnon(); |
313
|
|
|
$this->targetUser = User::newFromName( $response->username ); |
|
|
|
|
314
|
|
|
|
315
|
|
|
if ( |
316
|
|
|
!$this->proxyAccountCreation |
317
|
|
|
&& $response->loginRequest |
318
|
|
|
&& $authManager->canAuthenticateNow() |
319
|
|
|
) { |
320
|
|
|
// successful registration; log the user in instantly |
321
|
|
|
$response2 = $authManager->beginAuthentication( [ $response->loginRequest ], |
322
|
|
|
$returnToUrl ); |
323
|
|
|
if ( $response2->status !== AuthenticationResponse::PASS ) { |
324
|
|
|
LoggerFactory::getInstance( 'login' ) |
325
|
|
|
->error( 'Could not log in after account creation' ); |
326
|
|
|
$this->successfulAction( true, Status::newFatal( 'createacct-loginerror' ) ); |
327
|
|
|
break; |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
if ( !$this->proxyAccountCreation ) { |
332
|
|
|
// Ensure that the context user is the same as the session user. |
333
|
|
|
$this->setSessionUserForCurrentRequest(); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
$this->successfulAction( true ); |
337
|
|
|
break; |
338
|
|
|
case AuthenticationResponse::FAIL: |
339
|
|
|
// fall through |
340
|
|
|
case AuthenticationResponse::RESTART: |
341
|
|
|
unset( $this->authForm ); |
342
|
|
|
if ( $response->status === AuthenticationResponse::FAIL ) { |
343
|
|
|
$action = $this->getDefaultAction( $subPage ); |
344
|
|
|
$messageType = 'error'; |
345
|
|
|
} else { |
346
|
|
|
$action = $this->getContinueAction( $this->authAction ); |
347
|
|
|
$messageType = 'warning'; |
348
|
|
|
} |
349
|
|
|
$this->logAuthResult( false, $response->message ? $response->message->getKey() : '-' ); |
350
|
|
|
$this->loadAuth( $subPage, $action, true ); |
351
|
|
|
$this->mainLoginForm( $this->authRequests, $response->message, $messageType ); |
|
|
|
|
352
|
|
|
break; |
353
|
|
|
case AuthenticationResponse::REDIRECT: |
354
|
|
|
unset( $this->authForm ); |
355
|
|
|
$this->getOutput()->redirect( $response->redirectTarget ); |
356
|
|
|
break; |
357
|
|
|
case AuthenticationResponse::UI: |
358
|
|
|
unset( $this->authForm ); |
359
|
|
|
$this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE |
360
|
|
|
: AuthManager::ACTION_LOGIN_CONTINUE; |
361
|
|
|
$this->authRequests = $response->neededRequests; |
362
|
|
|
$this->mainLoginForm( $response->neededRequests, $response->message, $response->messageType ); |
|
|
|
|
363
|
|
|
break; |
364
|
|
|
default: |
365
|
|
|
throw new LogicException( 'invalid AuthenticationResponse' ); |
366
|
|
|
} |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* Show the success page. |
371
|
|
|
* |
372
|
|
|
* @param string $type Condition of return to; see `executeReturnTo` |
373
|
|
|
* @param string|Message $title Page's title |
374
|
|
|
* @param string $msgname |
375
|
|
|
* @param string $injected_html |
376
|
|
|
* @param StatusValue|null $extraMessages |
377
|
|
|
*/ |
378
|
|
|
protected function showSuccessPage( |
379
|
|
|
$type, $title, $msgname, $injected_html, $extraMessages |
380
|
|
|
) { |
381
|
|
|
$out = $this->getOutput(); |
382
|
|
|
$out->setPageTitle( $title ); |
383
|
|
|
if ( $msgname ) { |
384
|
|
|
$out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) ); |
385
|
|
|
} |
386
|
|
|
if ( $extraMessages ) { |
387
|
|
|
$extraMessages = Status::wrap( $extraMessages ); |
388
|
|
|
$out->addWikiText( $extraMessages->getWikiText() ); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
$out->addHTML( $injected_html ); |
392
|
|
|
|
393
|
|
|
$helper = new LoginHelper( $this->getContext() ); |
394
|
|
|
$helper->showReturnToPage( $type, $this->mReturnTo, $this->mReturnToQuery, $this->mStickHTTPS ); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Add a "return to" link or redirect to it. |
399
|
|
|
* Extensions can use this to reuse the "return to" logic after |
400
|
|
|
* inject steps (such as redirection) into the login process. |
401
|
|
|
* |
402
|
|
|
* @param string $type One of the following: |
403
|
|
|
* - error: display a return to link ignoring $wgRedirectOnLogin |
404
|
|
|
* - signup: display a return to link using $wgRedirectOnLogin if needed |
405
|
|
|
* - success: display a return to link using $wgRedirectOnLogin if needed |
406
|
|
|
* - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed |
407
|
|
|
* @param string $returnTo |
408
|
|
|
* @param array|string $returnToQuery |
409
|
|
|
* @param bool $stickHTTPS Keep redirect link on HTTPS |
410
|
|
|
* @since 1.22 |
411
|
|
|
*/ |
412
|
|
|
public function showReturnToPage( |
413
|
|
|
$type, $returnTo = '', $returnToQuery = '', $stickHTTPS = false |
414
|
|
|
) { |
415
|
|
|
$helper = new LoginHelper( $this->getContext() ); |
416
|
|
|
$helper->showReturnToPage( $type, $returnTo, $returnToQuery, $stickHTTPS ); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* Replace some globals to make sure the fact that the user has just been logged in is |
421
|
|
|
* reflected in the current request. |
422
|
|
|
* @param User $user |
|
|
|
|
423
|
|
|
*/ |
424
|
|
|
protected function setSessionUserForCurrentRequest() { |
425
|
|
|
global $wgUser, $wgLang; |
426
|
|
|
|
427
|
|
|
$context = RequestContext::getMain(); |
428
|
|
|
$localContext = $this->getContext(); |
429
|
|
|
if ( $context !== $localContext ) { |
430
|
|
|
// remove AuthManagerSpecialPage context hack |
431
|
|
|
$this->setContext( $context ); |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
$user = $context->getRequest()->getSession()->getUser(); |
435
|
|
|
|
436
|
|
|
$wgUser = $user; |
437
|
|
|
$context->setUser( $user ); |
438
|
|
|
|
439
|
|
|
$code = $this->getRequest()->getVal( 'uselang', $user->getOption( 'language' ) ); |
440
|
|
|
$userLang = Language::factory( $code ); |
441
|
|
|
$wgLang = $userLang; |
442
|
|
|
$context->setLanguage( $userLang ); |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
/** |
446
|
|
|
* @param AuthenticationRequest[] $requests A list of AuthorizationRequest objects, |
447
|
|
|
* used to generate the form fields. An empty array means a fatal error |
448
|
|
|
* (authentication cannot continue). |
449
|
|
|
* @param string|Message $msg |
450
|
|
|
* @param string $msgtype |
451
|
|
|
* @throws ErrorPageError |
452
|
|
|
* @throws Exception |
453
|
|
|
* @throws FatalError |
454
|
|
|
* @throws MWException |
455
|
|
|
* @throws PermissionsError |
456
|
|
|
* @throws ReadOnlyError |
457
|
|
|
* @private |
458
|
|
|
*/ |
459
|
|
|
protected function mainLoginForm( array $requests, $msg = '', $msgtype = 'error' ) { |
460
|
|
|
$titleObj = $this->getPageTitle(); |
|
|
|
|
461
|
|
|
$user = $this->getUser(); |
462
|
|
|
$out = $this->getOutput(); |
463
|
|
|
|
464
|
|
|
// FIXME how to handle empty $requests - restart, or no form, just an error message? |
465
|
|
|
// no form would be better for no session type errors, restart is better when can* fails. |
466
|
|
|
if ( !$requests ) { |
467
|
|
|
$this->authAction = $this->getDefaultAction( $this->subPage ); |
468
|
|
|
$this->authForm = null; |
469
|
|
|
$requests = AuthManager::singleton()->getAuthenticationRequests( $this->authAction, $user ); |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
// Generic styles and scripts for both login and signup form |
473
|
|
|
$out->addModuleStyles( [ |
474
|
|
|
'mediawiki.ui', |
475
|
|
|
'mediawiki.ui.button', |
476
|
|
|
'mediawiki.ui.checkbox', |
477
|
|
|
'mediawiki.ui.input', |
478
|
|
|
'mediawiki.special.userlogin.common.styles' |
479
|
|
|
] ); |
480
|
|
|
if ( $this->isSignup() ) { |
481
|
|
|
// XXX hack pending RL or JS parse() support for complex content messages T27349 |
482
|
|
|
$out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp', |
483
|
|
|
$this->msg( 'createacct-imgcaptcha-help' )->parse() ); |
484
|
|
|
|
485
|
|
|
// Additional styles and scripts for signup form |
486
|
|
|
$out->addModules( [ |
487
|
|
|
'mediawiki.special.userlogin.signup.js' |
488
|
|
|
] ); |
489
|
|
|
$out->addModuleStyles( [ |
490
|
|
|
'mediawiki.special.userlogin.signup.styles' |
491
|
|
|
] ); |
492
|
|
|
} else { |
493
|
|
|
// Additional styles for login form |
494
|
|
|
$out->addModuleStyles( [ |
495
|
|
|
'mediawiki.special.userlogin.login.styles' |
496
|
|
|
] ); |
497
|
|
|
} |
498
|
|
|
$out->disallowUserJs(); // just in case... |
499
|
|
|
|
500
|
|
|
$form = $this->getAuthForm( $requests, $this->authAction, $msg, $msgtype ); |
501
|
|
|
$form->prepareForm(); |
502
|
|
|
|
503
|
|
|
$submitStatus = Status::newGood(); |
504
|
|
|
if ( $msg && $msgtype === 'warning' ) { |
505
|
|
|
$submitStatus->warning( $msg ); |
506
|
|
|
} elseif ( $msg && $msgtype === 'error' ) { |
507
|
|
|
$submitStatus->fatal( $msg ); |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
// warning header for non-standard workflows (e.g. security reauthentication) |
511
|
|
|
if ( |
512
|
|
|
!$this->isSignup() && |
513
|
|
|
$this->getUser()->isLoggedIn() && |
514
|
|
|
$this->authAction !== AuthManager::ACTION_LOGIN_CONTINUE |
515
|
|
|
) { |
516
|
|
|
$reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin'; |
517
|
|
|
$submitStatus->warning( $reauthMessage, $this->getUser()->getName() ); |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
$formHtml = $form->getHTML( $submitStatus ); |
521
|
|
|
|
522
|
|
|
$out->addHTML( $this->getPageHtml( $formHtml ) ); |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* Add page elements which are outside the form. |
527
|
|
|
* FIXME this should probably be a template, but use a sane language (handlebars?) |
528
|
|
|
* @param string $formHtml |
529
|
|
|
* @return string |
530
|
|
|
*/ |
531
|
|
|
protected function getPageHtml( $formHtml ) { |
532
|
|
|
global $wgLoginLanguageSelector; |
533
|
|
|
|
534
|
|
|
$loginPrompt = $this->isSignup() ? '' : Html::rawElement( 'div', |
535
|
|
|
[ 'id' => 'userloginprompt' ], $this->msg( 'loginprompt' )->parseAsBlock() ); |
536
|
|
|
$languageLinks = $wgLoginLanguageSelector ? $this->makeLanguageSelector() : ''; |
537
|
|
|
$signupStartMsg = $this->msg( 'signupstart' ); |
538
|
|
|
$signupStart = ( $this->isSignup() && !$signupStartMsg->isDisabled() ) |
539
|
|
|
? Html::rawElement( 'div', [ 'id' => 'signupstart' ], $signupStartMsg->parseAsBlock() ) : ''; |
540
|
|
|
if ( $languageLinks ) { |
541
|
|
|
$languageLinks = Html::rawElement( 'div', [ 'id' => 'languagelinks' ], |
542
|
|
|
Html::rawElement( 'p', [], $languageLinks ) |
543
|
|
|
); |
544
|
|
|
} |
545
|
|
|
|
546
|
|
|
$benefitsContainer = ''; |
547
|
|
|
if ( $this->isSignup() && $this->showExtraInformation() ) { |
548
|
|
|
// messages used: |
549
|
|
|
// createacct-benefit-icon1 createacct-benefit-head1 createacct-benefit-body1 |
550
|
|
|
// createacct-benefit-icon2 createacct-benefit-head2 createacct-benefit-body2 |
551
|
|
|
// createacct-benefit-icon3 createacct-benefit-head3 createacct-benefit-body3 |
552
|
|
|
$benefitCount = 3; |
553
|
|
|
$benefitList = ''; |
554
|
|
|
for ( $benefitIdx = 1; $benefitIdx <= $benefitCount; $benefitIdx++ ) { |
555
|
|
|
$headUnescaped = $this->msg( "createacct-benefit-head$benefitIdx" )->text(); |
556
|
|
|
$iconClass = $this->msg( "createacct-benefit-icon$benefitIdx" )->escaped(); |
557
|
|
|
$benefitList .= Html::rawElement( 'div', [ 'class' => "mw-number-text $iconClass" ], |
558
|
|
|
Html::rawElement( 'h3', [], |
559
|
|
|
$this->msg( "createacct-benefit-head$benefitIdx" )->escaped() |
560
|
|
|
) |
561
|
|
|
. Html::rawElement( 'p', [], |
562
|
|
|
$this->msg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped() |
563
|
|
|
) |
564
|
|
|
); |
565
|
|
|
} |
566
|
|
|
$benefitsContainer = Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-container' ], |
567
|
|
|
Html::rawElement( 'h2', [], $this->msg( 'createacct-benefit-heading' )->escaped() ) |
568
|
|
|
. Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-list' ], |
569
|
|
|
$benefitList |
570
|
|
|
) |
571
|
|
|
); |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
$html = Html::rawElement( 'div', [ 'class' => 'mw-ui-container' ], |
575
|
|
|
$loginPrompt |
576
|
|
|
. $languageLinks |
577
|
|
|
. $signupStart |
578
|
|
|
. Html::rawElement( 'div', [ 'id' => 'userloginForm' ], |
579
|
|
|
$formHtml |
580
|
|
|
) |
581
|
|
|
. $benefitsContainer |
582
|
|
|
); |
583
|
|
|
|
584
|
|
|
return $html; |
585
|
|
|
} |
586
|
|
|
|
587
|
|
|
/** |
588
|
|
|
* Generates a form from the given request. |
589
|
|
|
* @param AuthenticationRequest[] $requests |
590
|
|
|
* @param string $action AuthManager action name |
591
|
|
|
* @param string|Message $msg |
592
|
|
|
* @param string $msgType |
593
|
|
|
* @return HTMLForm |
594
|
|
|
*/ |
595
|
|
|
protected function getAuthForm( array $requests, $action, $msg = '', $msgType = 'error' ) { |
596
|
|
|
global $wgSecureLogin, $wgLoginLanguageSelector; |
597
|
|
|
// FIXME merge this with parent |
598
|
|
|
|
599
|
|
|
if ( isset( $this->authForm ) ) { |
600
|
|
|
return $this->authForm; |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
$usingHTTPS = $this->getRequest()->getProtocol() === 'https'; |
604
|
|
|
|
605
|
|
|
// get basic form description from the auth logic |
606
|
|
|
$fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests ); |
607
|
|
|
$fakeTemplate = $this->getFakeTemplate( $msg, $msgType ); |
608
|
|
|
$this->fakeTemplate = $fakeTemplate; // FIXME there should be a saner way to pass this to the hook |
609
|
|
|
// this will call onAuthChangeFormFields() |
610
|
|
|
$formDescriptor = static::fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction ); |
611
|
|
|
$this->postProcessFormDescriptor( $formDescriptor, $requests ); |
612
|
|
|
|
613
|
|
|
$context = $this->getContext(); |
614
|
|
View Code Duplication |
if ( $context->getRequest() !== $this->getRequest() ) { |
615
|
|
|
// We have overridden the request, need to make sure the form uses that too. |
616
|
|
|
$context = new DerivativeContext( $this->getContext() ); |
617
|
|
|
$context->setRequest( $this->getRequest() ); |
618
|
|
|
} |
619
|
|
|
$form = HTMLForm::factory( 'vform', $formDescriptor, $context ); |
620
|
|
|
|
621
|
|
|
$form->addHiddenField( 'authAction', $this->authAction ); |
622
|
|
|
if ( $wgLoginLanguageSelector && $this->mLanguage ) { |
623
|
|
|
$form->addHiddenField( 'uselang', $this->mLanguage ); |
624
|
|
|
} |
625
|
|
|
$form->addHiddenField( 'force', $this->securityLevel ); |
626
|
|
|
$form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() ); |
627
|
|
|
if ( $wgSecureLogin ) { |
628
|
|
|
// If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved |
629
|
|
|
if ( !$this->isSignup() ) { |
630
|
|
|
$form->addHiddenField( 'wpForceHttps', (int)$this->mStickHTTPS ); |
631
|
|
|
$form->addHiddenField( 'wpFromhttp', $usingHTTPS ); |
632
|
|
|
} |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
// set properties of the form itself |
636
|
|
|
$form->setAction( $this->getPageTitle()->getLocalURL( $this->getReturnToQueryStringFragment() ) ); |
637
|
|
|
$form->setName( 'userlogin' . ( $this->isSignup() ? '2' : '' ) ); |
638
|
|
|
if ( $this->isSignup() ) { |
639
|
|
|
$form->setId( 'userlogin2' ); |
640
|
|
|
} |
641
|
|
|
|
642
|
|
|
$form->suppressDefaultSubmit(); |
643
|
|
|
|
644
|
|
|
$this->authForm = $form; |
645
|
|
|
|
646
|
|
|
return $form; |
647
|
|
|
} |
648
|
|
|
|
649
|
|
|
/** |
650
|
|
|
* Temporary B/C method to handle extensions using the UserLoginForm/UserCreateForm hooks. |
651
|
|
|
* @param string|Message $msg |
652
|
|
|
* @param string $msgType |
653
|
|
|
* @return FakeAuthTemplate |
654
|
|
|
*/ |
655
|
|
|
protected function getFakeTemplate( $msg, $msgType ) { |
656
|
|
|
global $wgAuth, $wgEnableEmail, $wgHiddenPrefs, $wgEmailConfirmToEdit, $wgEnableUserEmail, |
657
|
|
|
$wgSecureLogin, $wgLoginLanguageSelector, $wgPasswordResetRoutes; |
658
|
|
|
|
659
|
|
|
// make a best effort to get the value of fields which used to be fixed in the old login |
660
|
|
|
// template but now might or might not exist depending on what providers are used |
661
|
|
|
$request = $this->getRequest(); |
662
|
|
|
$data = (object)[ |
663
|
|
|
'mUsername' => $request->getText( 'wpName' ), |
664
|
|
|
'mPassword' => $request->getText( 'wpPassword' ), |
665
|
|
|
'mRetype' => $request->getText( 'wpRetype' ), |
666
|
|
|
'mEmail' => $request->getText( 'wpEmail' ), |
667
|
|
|
'mRealName' => $request->getText( 'wpRealName' ), |
668
|
|
|
'mDomain' => $request->getText( 'wpDomain' ), |
669
|
|
|
'mReason' => $request->getText( 'wpReason' ), |
670
|
|
|
'mRemember' => $request->getCheck( 'wpRemember' ), |
671
|
|
|
]; |
672
|
|
|
|
673
|
|
|
// Preserves a bunch of logic from the old code that was rewritten in getAuthForm(). |
674
|
|
|
// There is no code reuse to make this easier to remove . |
675
|
|
|
// If an extension tries to change any of these values, they are out of luck - we only |
676
|
|
|
// actually use the domain/usedomain/domainnames, extraInput and extrafields keys. |
677
|
|
|
|
678
|
|
|
$titleObj = $this->getPageTitle(); |
679
|
|
|
$user = $this->getUser(); |
680
|
|
|
$template = new FakeAuthTemplate(); |
681
|
|
|
|
682
|
|
|
// Pre-fill username (if not creating an account, bug 44775). |
683
|
|
|
if ( $data->mUsername == '' && $this->isSignup() ) { |
684
|
|
|
if ( $user->isLoggedIn() ) { |
685
|
|
|
$data->mUsername = $user->getName(); |
686
|
|
|
} else { |
687
|
|
|
$data->mUsername = $this->getRequest()->getSession()->suggestLoginUsername(); |
688
|
|
|
} |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
if ( $this->isSignup() ) { |
692
|
|
|
// Must match number of benefits defined in messages |
693
|
|
|
$template->set( 'benefitCount', 3 ); |
694
|
|
|
|
695
|
|
|
$q = 'action=submitlogin&type=signup'; |
696
|
|
|
$linkq = 'type=login'; |
697
|
|
|
} else { |
698
|
|
|
$q = 'action=submitlogin&type=login'; |
699
|
|
|
$linkq = 'type=signup'; |
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
if ( $this->mReturnTo !== '' ) { |
703
|
|
|
$returnto = '&returnto=' . wfUrlencode( $this->mReturnTo ); |
704
|
|
|
if ( $this->mReturnToQuery !== '' ) { |
705
|
|
|
$returnto .= '&returntoquery=' . |
706
|
|
|
wfUrlencode( $this->mReturnToQuery ); |
707
|
|
|
} |
708
|
|
|
$q .= $returnto; |
709
|
|
|
$linkq .= $returnto; |
710
|
|
|
} |
711
|
|
|
|
712
|
|
|
# Don't show a "create account" link if the user can't. |
713
|
|
|
if ( $this->showCreateAccountLink() ) { |
714
|
|
|
# Pass any language selection on to the mode switch link |
715
|
|
|
if ( $wgLoginLanguageSelector && $this->mLanguage ) { |
716
|
|
|
$linkq .= '&uselang=' . $this->mLanguage; |
717
|
|
|
} |
718
|
|
|
// Supply URL, login template creates the button. |
719
|
|
|
$template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) ); |
720
|
|
|
} else { |
721
|
|
|
$template->set( 'link', '' ); |
722
|
|
|
} |
723
|
|
|
|
724
|
|
|
$resetLink = $this->isSignup() |
725
|
|
|
? null |
726
|
|
|
: is_array( $wgPasswordResetRoutes ) |
727
|
|
|
&& in_array( true, array_values( $wgPasswordResetRoutes ), true ); |
728
|
|
|
|
729
|
|
|
$template->set( 'header', '' ); |
730
|
|
|
$template->set( 'formheader', '' ); |
731
|
|
|
$template->set( 'skin', $this->getSkin() ); |
732
|
|
|
|
733
|
|
|
$template->set( 'name', $data->mUsername ); |
734
|
|
|
$template->set( 'password', $data->mPassword ); |
735
|
|
|
$template->set( 'retype', $data->mRetype ); |
736
|
|
|
$template->set( 'createemailset', false ); // no easy way to get that from AuthManager |
737
|
|
|
$template->set( 'email', $data->mEmail ); |
738
|
|
|
$template->set( 'realname', $data->mRealName ); |
739
|
|
|
$template->set( 'domain', $data->mDomain ); |
740
|
|
|
$template->set( 'reason', $data->mReason ); |
741
|
|
|
$template->set( 'remember', $data->mRemember ); |
742
|
|
|
|
743
|
|
|
$template->set( 'action', $titleObj->getLocalURL( $q ) ); |
744
|
|
|
$template->set( 'message', $msg ); |
745
|
|
|
$template->set( 'messagetype', $msgType ); |
746
|
|
|
$template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() ); |
747
|
|
|
$template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs, true ) ); |
748
|
|
|
$template->set( 'useemail', $wgEnableEmail ); |
749
|
|
|
$template->set( 'emailrequired', $wgEmailConfirmToEdit ); |
750
|
|
|
$template->set( 'emailothers', $wgEnableUserEmail ); |
751
|
|
|
$template->set( 'canreset', $wgAuth->allowPasswordChange() ); |
752
|
|
|
$template->set( 'resetlink', $resetLink ); |
753
|
|
|
$template->set( 'canremember', $request->getSession()->getProvider() |
754
|
|
|
->getRememberUserDuration() !== null ); |
755
|
|
|
$template->set( 'usereason', $user->isLoggedIn() ); |
756
|
|
|
$template->set( 'cansecurelogin', ( $wgSecureLogin ) ); |
757
|
|
|
$template->set( 'stickhttps', (int)$this->mStickHTTPS ); |
758
|
|
|
$template->set( 'loggedin', $user->isLoggedIn() ); |
759
|
|
|
$template->set( 'loggedinuser', $user->getName() ); |
760
|
|
|
$template->set( 'token', $this->getToken()->toString() ); |
761
|
|
|
|
762
|
|
|
$action = $this->isSignup() ? 'signup' : 'login'; |
763
|
|
|
$wgAuth->modifyUITemplate( $template, $action ); |
764
|
|
|
|
765
|
|
|
$oldTemplate = $template; |
766
|
|
|
|
767
|
|
|
// Both Hooks::run are explicit here to make findHooks.php happy |
768
|
|
|
if ( $this->isSignup() ) { |
769
|
|
|
Hooks::run( 'UserCreateForm', [ &$template ] ); |
770
|
|
|
if ( $oldTemplate !== $template ) { |
771
|
|
|
wfDeprecated( "reference in UserCreateForm hook", '1.27' ); |
772
|
|
|
} |
773
|
|
|
} else { |
774
|
|
|
Hooks::run( 'UserLoginForm', [ &$template ] ); |
775
|
|
|
if ( $oldTemplate !== $template ) { |
776
|
|
|
wfDeprecated( "reference in UserLoginForm hook", '1.27' ); |
777
|
|
|
} |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
return $template; |
781
|
|
|
} |
782
|
|
|
|
783
|
|
|
public function onAuthChangeFormFields( |
784
|
|
|
array $requests, array $fieldInfo, array &$formDescriptor, $action |
785
|
|
|
) { |
786
|
|
|
$coreFieldDescriptors = $this->getFieldDefinitions( $this->fakeTemplate ); |
787
|
|
|
$specialFields = array_merge( [ 'extraInput' ], |
788
|
|
|
array_keys( $this->fakeTemplate->getExtraInputDefinitions() ) ); |
789
|
|
|
|
790
|
|
|
// keep the ordering from getCoreFieldDescriptors() where there is no explicit weight |
791
|
|
|
foreach ( $coreFieldDescriptors as $fieldName => $coreField ) { |
792
|
|
|
$requestField = isset( $formDescriptor[$fieldName] ) ? |
793
|
|
|
$formDescriptor[$fieldName] : []; |
794
|
|
|
|
795
|
|
|
// remove everything that is not in the fieldinfo, is not marked as a supplemental field |
796
|
|
|
// to something in the fieldinfo, is not B/C for the pre-AuthManager templates, |
797
|
|
|
// and is not an info field or a submit button |
798
|
|
|
if ( |
799
|
|
|
!isset( $fieldInfo[$fieldName] ) |
800
|
|
|
&& ( |
801
|
|
|
!isset( $coreField['baseField'] ) |
802
|
|
|
|| !isset( $fieldInfo[$coreField['baseField']] ) |
803
|
|
|
) |
804
|
|
|
&& !in_array( $fieldName, $specialFields, true ) |
805
|
|
|
&& ( |
806
|
|
|
!isset( $coreField['type'] ) |
807
|
|
|
|| !in_array( $coreField['type'], [ 'submit', 'info' ], true ) |
808
|
|
|
) |
809
|
|
|
) { |
810
|
|
|
$coreFieldDescriptors[$fieldName] = null; |
811
|
|
|
continue; |
812
|
|
|
} |
813
|
|
|
|
814
|
|
|
// core message labels should always take priority |
815
|
|
|
if ( |
816
|
|
|
isset( $coreField['label'] ) |
817
|
|
|
|| isset( $coreField['label-message'] ) |
818
|
|
|
|| isset( $coreField['label-raw'] ) |
819
|
|
|
) { |
820
|
|
|
unset( $requestField['label'], $requestField['label-message'], $coreField['label-raw'] ); |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
$coreFieldDescriptors[$fieldName] += $requestField; |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
$formDescriptor = array_filter( $coreFieldDescriptors + $formDescriptor ); |
827
|
|
|
return true; |
828
|
|
|
} |
829
|
|
|
|
830
|
|
|
/** |
831
|
|
|
* Show extra information such as password recovery information, link from login to signup, |
832
|
|
|
* CTA etc? Such information should only be shown on the "landing page", ie. when the user |
833
|
|
|
* is at the first step of the authentication process. |
834
|
|
|
* @return bool |
835
|
|
|
*/ |
836
|
|
|
protected function showExtraInformation() { |
837
|
|
|
return $this->authAction !== $this->getContinueAction( $this->authAction ) |
838
|
|
|
&& !$this->securityLevel; |
839
|
|
|
} |
840
|
|
|
|
841
|
|
|
/** |
842
|
|
|
* Create a HTMLForm descriptor for the core login fields. |
843
|
|
|
* @param FakeAuthTemplate $template B/C data (not used but needed by getBCFieldDefinitions) |
844
|
|
|
* @return array |
845
|
|
|
*/ |
846
|
|
|
protected function getFieldDefinitions( $template ) { |
847
|
|
|
global $wgEmailConfirmToEdit, $wgLoginLanguageSelector; |
848
|
|
|
|
849
|
|
|
$isLoggedIn = $this->getUser()->isLoggedIn(); |
850
|
|
|
$continuePart = $this->isContinued() ? 'continue-' : ''; |
851
|
|
|
$anotherPart = $isLoggedIn ? 'another-' : ''; |
852
|
|
|
$expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration(); |
853
|
|
|
$expirationDays = ceil( $expiration / ( 3600 * 24 ) ); |
854
|
|
|
$secureLoginLink = ''; |
855
|
|
|
if ( $this->mSecureLoginUrl ) { |
856
|
|
|
$secureLoginLink = Html::element( 'a', [ |
857
|
|
|
'href' => $this->mSecureLoginUrl, |
858
|
|
|
'class' => 'mw-ui-flush-right mw-secure', |
859
|
|
|
], $this->msg( 'userlogin-signwithsecure' )->text() ); |
860
|
|
|
} |
861
|
|
|
$usernameHelpLink = ''; |
862
|
|
|
if ( !$this->msg( 'createacct-helpusername' )->isDisabled() ) { |
863
|
|
|
$usernameHelpLink = Html::rawElement( 'span', [ |
864
|
|
|
'class' => 'mw-ui-flush-right', |
865
|
|
|
], $this->msg( 'createacct-helpusername' )->parse() ); |
866
|
|
|
} |
867
|
|
|
|
868
|
|
|
if ( $this->isSignup() ) { |
869
|
|
|
$fieldDefinitions = [ |
870
|
|
|
'statusarea' => [ |
871
|
|
|
// used by the mediawiki.special.userlogin.signup.js module for error display |
872
|
|
|
// FIXME merge this with HTMLForm's normal status (error) area |
873
|
|
|
'type' => 'info', |
874
|
|
|
'raw' => true, |
875
|
|
|
'default' => Html::element( 'div', [ 'id' => 'mw-createacct-status-area' ] ), |
876
|
|
|
'weight' => -105, |
877
|
|
|
], |
878
|
|
|
'username' => [ |
879
|
|
|
'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $usernameHelpLink, |
880
|
|
|
'id' => 'wpName2', |
881
|
|
|
'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph' |
882
|
|
|
: 'userlogin-yourname-ph', |
883
|
|
|
], |
884
|
|
|
'mailpassword' => [ |
885
|
|
|
// create account without providing password, a temporary one will be mailed |
886
|
|
|
'type' => 'check', |
887
|
|
|
'label-message' => 'createaccountmail', |
888
|
|
|
'name' => 'wpCreateaccountMail', |
889
|
|
|
'id' => 'wpCreateaccountMail', |
890
|
|
|
], |
891
|
|
|
'password' => [ |
892
|
|
|
'id' => 'wpPassword2', |
893
|
|
|
'placeholder-message' => 'createacct-yourpassword-ph', |
894
|
|
|
'hide-if' => [ '===', 'wpCreateaccountMail', '1' ], |
895
|
|
|
], |
896
|
|
|
'domain' => [], |
897
|
|
|
'retype' => [ |
898
|
|
|
'baseField' => 'password', |
899
|
|
|
'type' => 'password', |
900
|
|
|
'label-message' => 'createacct-yourpasswordagain', |
901
|
|
|
'id' => 'wpRetype', |
902
|
|
|
'cssclass' => 'loginPassword', |
903
|
|
|
'size' => 20, |
904
|
|
|
'validation-callback' => function ( $value, $alldata ) { |
905
|
|
|
if ( empty( $alldata['mailpassword'] ) && !empty( $alldata['password'] ) ) { |
906
|
|
|
if ( !$value ) { |
907
|
|
|
return $this->msg( 'htmlform-required' ); |
908
|
|
|
} elseif ( $value !== $alldata['password'] ) { |
909
|
|
|
return $this->msg( 'badretype' ); |
910
|
|
|
} |
911
|
|
|
} |
912
|
|
|
return true; |
913
|
|
|
}, |
914
|
|
|
'hide-if' => [ '===', 'wpCreateaccountMail', '1' ], |
915
|
|
|
'placeholder-message' => 'createacct-yourpasswordagain-ph', |
916
|
|
|
], |
917
|
|
|
'email' => [ |
918
|
|
|
'type' => 'email', |
919
|
|
|
'label-message' => $wgEmailConfirmToEdit ? 'createacct-emailrequired' |
920
|
|
|
: 'createacct-emailoptional', |
921
|
|
|
'id' => 'wpEmail', |
922
|
|
|
'cssclass' => 'loginText', |
923
|
|
|
'size' => '20', |
924
|
|
|
// FIXME will break non-standard providers |
925
|
|
|
'required' => $wgEmailConfirmToEdit, |
926
|
|
|
'validation-callback' => function ( $value, $alldata ) { |
927
|
|
|
global $wgEmailConfirmToEdit; |
928
|
|
|
|
929
|
|
|
// AuthManager will check most of these, but that will make the auth |
930
|
|
|
// session fail and this won't, so nicer to do it this way |
931
|
|
|
if ( !$value && $wgEmailConfirmToEdit ) { |
932
|
|
|
// no point in allowing registration without email when email is |
933
|
|
|
// required to edit |
934
|
|
|
return $this->msg( 'noemailtitle' ); |
935
|
|
|
} elseif ( !$value && !empty( $alldata['mailpassword'] ) ) { |
936
|
|
|
// cannot send password via email when there is no email address |
937
|
|
|
return $this->msg( 'noemailcreate' ); |
938
|
|
|
} elseif ( $value && !Sanitizer::validateEmail( $value ) ) { |
|
|
|
|
939
|
|
|
return $this->msg( 'invalidemailaddress' ); |
940
|
|
|
} |
941
|
|
|
return true; |
942
|
|
|
}, |
943
|
|
|
'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph', |
944
|
|
|
], |
945
|
|
|
'realname' => [ |
946
|
|
|
'type' => 'text', |
947
|
|
|
'help-message' => $isLoggedIn ? 'createacct-another-realname-tip' |
948
|
|
|
: 'prefs-help-realname', |
949
|
|
|
'label-message' => 'createacct-realname', |
950
|
|
|
'cssclass' => 'loginText', |
951
|
|
|
'size' => 20, |
952
|
|
|
'id' => 'wpRealName', |
953
|
|
|
], |
954
|
|
|
'reason' => [ |
955
|
|
|
// comment for the user creation log |
956
|
|
|
'type' => 'text', |
957
|
|
|
'label-message' => 'createacct-reason', |
958
|
|
|
'cssclass' => 'loginText', |
959
|
|
|
'id' => 'wpReason', |
960
|
|
|
'size' => '20', |
961
|
|
|
'placeholder-message' => 'createacct-reason-ph', |
962
|
|
|
], |
963
|
|
|
'extrainput' => [], // placeholder for fields coming from the template |
964
|
|
|
'createaccount' => [ |
965
|
|
|
// submit button |
966
|
|
|
'type' => 'submit', |
967
|
|
|
'default' => $this->msg( 'createacct-' . $anotherPart . $continuePart . |
968
|
|
|
'submit' )->text(), |
969
|
|
|
'name' => 'wpCreateaccount', |
970
|
|
|
'id' => 'wpCreateaccount', |
971
|
|
|
'weight' => 100, |
972
|
|
|
], |
973
|
|
|
]; |
974
|
|
|
} else { |
975
|
|
|
$fieldDefinitions = [ |
976
|
|
|
'username' => [ |
977
|
|
|
'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $secureLoginLink, |
978
|
|
|
'id' => 'wpName1', |
979
|
|
|
'placeholder-message' => 'userlogin-yourname-ph', |
980
|
|
|
], |
981
|
|
|
'password' => [ |
982
|
|
|
'id' => 'wpPassword1', |
983
|
|
|
'placeholder-message' => 'userlogin-yourpassword-ph', |
984
|
|
|
], |
985
|
|
|
'domain' => [], |
986
|
|
|
'extrainput' => [], |
987
|
|
|
'rememberMe' => [ |
988
|
|
|
// option for saving the user token to a cookie |
989
|
|
|
'type' => 'check', |
990
|
|
|
'name' => 'wpRemember', |
991
|
|
|
'label-message' => $this->msg( 'userlogin-remembermypassword' ) |
992
|
|
|
->numParams( $expirationDays ), |
993
|
|
|
'id' => 'wpRemember', |
994
|
|
|
], |
995
|
|
|
'loginattempt' => [ |
996
|
|
|
// submit button |
997
|
|
|
'type' => 'submit', |
998
|
|
|
'default' => $this->msg( 'pt-login-' . $continuePart . 'button' )->text(), |
999
|
|
|
'id' => 'wpLoginAttempt', |
1000
|
|
|
'weight' => 100, |
1001
|
|
|
], |
1002
|
|
|
'linkcontainer' => [ |
1003
|
|
|
// help link |
1004
|
|
|
'type' => 'info', |
1005
|
|
|
'cssclass' => 'mw-form-related-link-container mw-userlogin-help', |
1006
|
|
|
// 'id' => 'mw-userlogin-help', // FIXME HTMLInfoField ignores this |
1007
|
|
|
'raw' => true, |
1008
|
|
|
'default' => Html::element( 'a', [ |
1009
|
|
|
'href' => Skin::makeInternalOrExternalUrl( wfMessage( 'helplogin-url' ) |
1010
|
|
|
->inContentLanguage() |
1011
|
|
|
->text() ), |
1012
|
|
|
], $this->msg( 'userlogin-helplink2' )->text() ), |
1013
|
|
|
'weight' => 200, |
1014
|
|
|
], |
1015
|
|
|
// button for ResetPasswordSecondaryAuthenticationProvider |
1016
|
|
|
'skipReset' => [ |
1017
|
|
|
'weight' => 110, |
1018
|
|
|
'flags' => [], |
1019
|
|
|
], |
1020
|
|
|
]; |
1021
|
|
|
} |
1022
|
|
|
|
1023
|
|
|
$fieldDefinitions['username'] += [ |
1024
|
|
|
'type' => 'text', |
1025
|
|
|
'name' => 'wpName', |
1026
|
|
|
'cssclass' => 'loginText', |
1027
|
|
|
'size' => 20, |
1028
|
|
|
// 'required' => true, |
1029
|
|
|
]; |
1030
|
|
|
$fieldDefinitions['password'] += [ |
1031
|
|
|
'type' => 'password', |
1032
|
|
|
// 'label-message' => 'userlogin-yourpassword', // would override the changepassword label |
1033
|
|
|
'name' => 'wpPassword', |
1034
|
|
|
'cssclass' => 'loginPassword', |
1035
|
|
|
'size' => 20, |
1036
|
|
|
// 'required' => true, |
1037
|
|
|
]; |
1038
|
|
|
|
1039
|
|
|
if ( $template->get( 'header' ) || $template->get( 'formheader' ) ) { |
1040
|
|
|
// B/C for old extensions that haven't been converted to AuthManager (or have been |
1041
|
|
|
// but somebody is using the old version) and still use templates via the |
1042
|
|
|
// UserCreateForm/UserLoginForm hook. |
1043
|
|
|
// 'header' used by ConfirmEdit, CondfirmAccount, Persona, WikimediaIncubator, SemanticSignup |
1044
|
|
|
// 'formheader' used by MobileFrontend |
1045
|
|
|
$fieldDefinitions['header'] = [ |
1046
|
|
|
'type' => 'info', |
1047
|
|
|
'raw' => true, |
1048
|
|
|
'default' => $template->get( 'header' ) ?: $template->get( 'formheader' ), |
1049
|
|
|
'weight' => - 110, |
1050
|
|
|
]; |
1051
|
|
|
} |
1052
|
|
|
if ( $this->mEntryError ) { |
1053
|
|
|
$fieldDefinitions['entryError'] = [ |
1054
|
|
|
'type' => 'info', |
1055
|
|
|
'default' => Html::rawElement( 'div', [ 'class' => $this->mEntryErrorType . 'box', ], |
1056
|
|
|
$this->mEntryError ), |
1057
|
|
|
'raw' => true, |
1058
|
|
|
'rawrow' => true, |
1059
|
|
|
'weight' => -100, |
1060
|
|
|
]; |
1061
|
|
|
} |
1062
|
|
|
if ( !$this->showExtraInformation() ) { |
1063
|
|
|
unset( $fieldDefinitions['linkcontainer'], $fieldDefinitions['signupend'] ); |
1064
|
|
|
} |
1065
|
|
|
if ( $this->isSignup() && $this->showExtraInformation() ) { |
1066
|
|
|
// blank signup footer for site customization |
1067
|
|
|
// uses signupend-https for HTTPS requests if it's not blank, signupend otherwise |
1068
|
|
|
$signupendMsg = $this->msg( 'signupend' ); |
1069
|
|
|
$signupendHttpsMsg = $this->msg( 'signupend-https' ); |
1070
|
|
|
if ( !$signupendMsg->isDisabled() ) { |
1071
|
|
|
$usingHTTPS = $this->getRequest()->getProtocol() === 'https'; |
1072
|
|
|
$signupendText = ( $usingHTTPS && !$signupendHttpsMsg->isBlank() ) |
1073
|
|
|
? $signupendHttpsMsg ->parse() : $signupendMsg->parse(); |
1074
|
|
|
$fieldDefinitions['signupend'] = [ |
1075
|
|
|
'type' => 'info', |
1076
|
|
|
'raw' => true, |
1077
|
|
|
'default' => Html::rawElement( 'div', [ 'id' => 'signupend' ], $signupendText ), |
1078
|
|
|
'weight' => 225, |
1079
|
|
|
]; |
1080
|
|
|
} |
1081
|
|
|
} |
1082
|
|
|
if ( !$this->isSignup() && $this->showExtraInformation() ) { |
1083
|
|
|
$passwordReset = new PasswordReset( $this->getConfig(), AuthManager::singleton() ); |
1084
|
|
|
if ( $passwordReset->isAllowed( $this->getUser() )->isGood() ) { |
1085
|
|
|
$fieldDefinitions['passwordReset'] = [ |
1086
|
|
|
'type' => 'info', |
1087
|
|
|
'raw' => true, |
1088
|
|
|
'cssclass' => 'mw-form-related-link-container', |
1089
|
|
|
'default' => Linker::link( |
1090
|
|
|
SpecialPage::getTitleFor( 'PasswordReset' ), |
1091
|
|
|
$this->msg( 'userlogin-resetpassword-link' )->escaped() |
1092
|
|
|
), |
1093
|
|
|
'weight' => 230, |
1094
|
|
|
]; |
1095
|
|
|
} |
1096
|
|
|
|
1097
|
|
|
// Don't show a "create account" link if the user can't. |
1098
|
|
|
if ( $this->showCreateAccountLink() ) { |
1099
|
|
|
// link to the other action |
1100
|
|
|
$linkTitle = $this->getTitleFor( $this->isSignup() ? 'Userlogin' :'CreateAccount' ); |
1101
|
|
|
$linkq = $this->getReturnToQueryStringFragment(); |
1102
|
|
|
// Pass any language selection on to the mode switch link |
1103
|
|
|
if ( $wgLoginLanguageSelector && $this->mLanguage ) { |
1104
|
|
|
$linkq .= '&uselang=' . $this->mLanguage; |
1105
|
|
|
} |
1106
|
|
|
$loggedIn = $this->getUser()->isLoggedIn(); |
1107
|
|
|
|
1108
|
|
|
$fieldDefinitions['createOrLogin'] = [ |
1109
|
|
|
'type' => 'info', |
1110
|
|
|
'raw' => true, |
1111
|
|
|
'linkQuery' => $linkq, |
1112
|
|
|
'default' => function ( $params ) use ( $loggedIn, $linkTitle ) { |
1113
|
|
|
return Html::rawElement( 'div', |
1114
|
|
|
[ 'id' => 'mw-createaccount' . ( !$loggedIn ? '-cta' : '' ), |
1115
|
|
|
'class' => ( $loggedIn ? 'mw-form-related-link-container' : 'mw-ui-vform-field' ) ], |
1116
|
|
|
( $loggedIn ? '' : $this->msg( 'userlogin-noaccount' )->escaped() ) |
1117
|
|
|
. Html::element( 'a', |
1118
|
|
|
[ |
1119
|
|
|
'id' => 'mw-createaccount-join' . ( $loggedIn ? '-loggedin' : '' ), |
1120
|
|
|
'href' => $linkTitle->getLocalURL( $params['linkQuery'] ), |
1121
|
|
|
'class' => ( $loggedIn ? '' : 'mw-ui-button' ), |
1122
|
|
|
'tabindex' => 100, |
1123
|
|
|
], |
1124
|
|
|
$this->msg( |
1125
|
|
|
$loggedIn ? 'userlogin-createanother' : 'userlogin-joinproject' |
1126
|
|
|
)->escaped() |
1127
|
|
|
) |
1128
|
|
|
); |
1129
|
|
|
}, |
1130
|
|
|
'weight' => 235, |
1131
|
|
|
]; |
1132
|
|
|
} |
1133
|
|
|
} |
1134
|
|
|
|
1135
|
|
|
$fieldDefinitions = $this->getBCFieldDefinitions( $fieldDefinitions, $template ); |
1136
|
|
|
$fieldDefinitions = array_filter( $fieldDefinitions ); |
1137
|
|
|
|
1138
|
|
|
return $fieldDefinitions; |
1139
|
|
|
} |
1140
|
|
|
|
1141
|
|
|
/** |
1142
|
|
|
* Adds fields provided via the deprecated UserLoginForm / UserCreateForm hooks |
1143
|
|
|
* @param $fieldDefinitions array |
1144
|
|
|
* @param FakeAuthTemplate $template |
1145
|
|
|
* @return array |
1146
|
|
|
*/ |
1147
|
|
|
protected function getBCFieldDefinitions( $fieldDefinitions, $template ) { |
1148
|
|
|
if ( $template->get( 'usedomain', false ) ) { |
1149
|
|
|
// TODO probably should be translated to the new domain notation in AuthManager |
1150
|
|
|
$fieldDefinitions['domain'] = [ |
1151
|
|
|
'type' => 'select', |
1152
|
|
|
'label-message' => 'yourdomainname', |
1153
|
|
|
'options' => array_combine( $template->get( 'domainnames', [] ), |
1154
|
|
|
$template->get( 'domainnames', [] ) ), |
1155
|
|
|
'default' => $template->get( 'domain', '' ), |
1156
|
|
|
'name' => 'wpDomain', |
1157
|
|
|
// FIXME id => 'mw-user-domain-section' on the parent div |
1158
|
|
|
]; |
1159
|
|
|
} |
1160
|
|
|
|
1161
|
|
|
// poor man's associative array_splice |
1162
|
|
|
$extraInputPos = array_search( 'extrainput', array_keys( $fieldDefinitions ), true ); |
1163
|
|
|
$fieldDefinitions = array_slice( $fieldDefinitions, 0, $extraInputPos, true ) |
1164
|
|
|
+ $template->getExtraInputDefinitions() |
1165
|
|
|
+ array_slice( $fieldDefinitions, $extraInputPos + 1, null, true ); |
1166
|
|
|
|
1167
|
|
|
return $fieldDefinitions; |
1168
|
|
|
} |
1169
|
|
|
|
1170
|
|
|
/** |
1171
|
|
|
* Check if a session cookie is present. |
1172
|
|
|
* |
1173
|
|
|
* This will not pick up a cookie set during _this_ request, but is meant |
1174
|
|
|
* to ensure that the client is returning the cookie which was set on a |
1175
|
|
|
* previous pass through the system. |
1176
|
|
|
* |
1177
|
|
|
* @return bool |
1178
|
|
|
*/ |
1179
|
|
|
protected function hasSessionCookie() { |
1180
|
|
|
global $wgDisableCookieCheck, $wgInitialSessionId; |
1181
|
|
|
|
1182
|
|
|
return $wgDisableCookieCheck || ( |
1183
|
|
|
$wgInitialSessionId && |
1184
|
|
|
$this->getRequest()->getSession()->getId() === (string)$wgInitialSessionId |
1185
|
|
|
); |
1186
|
|
|
} |
1187
|
|
|
|
1188
|
|
|
/** |
1189
|
|
|
* Returns a string that can be appended to the URL (without encoding) to preserve the |
1190
|
|
|
* return target. Does not include leading '?'/'&'. |
1191
|
|
|
*/ |
1192
|
|
|
protected function getReturnToQueryStringFragment() { |
1193
|
|
|
$returnto = ''; |
1194
|
|
|
if ( $this->mReturnTo !== '' ) { |
1195
|
|
|
$returnto = 'returnto=' . wfUrlencode( $this->mReturnTo ); |
1196
|
|
|
if ( $this->mReturnToQuery !== '' ) { |
1197
|
|
|
$returnto .= '&returntoquery=' . wfUrlencode( $this->mReturnToQuery ); |
1198
|
|
|
} |
1199
|
|
|
} |
1200
|
|
|
return $returnto; |
1201
|
|
|
} |
1202
|
|
|
|
1203
|
|
|
/** |
1204
|
|
|
* Whether the login/create account form should display a link to the |
1205
|
|
|
* other form (in addition to whatever the skin provides). |
1206
|
|
|
* @return bool |
1207
|
|
|
*/ |
1208
|
|
|
private function showCreateAccountLink() { |
1209
|
|
|
if ( $this->isSignup() ) { |
1210
|
|
|
return true; |
1211
|
|
|
} elseif ( $this->getUser()->isAllowed( 'createaccount' ) ) { |
1212
|
|
|
return true; |
1213
|
|
|
} else { |
1214
|
|
|
return false; |
1215
|
|
|
} |
1216
|
|
|
} |
1217
|
|
|
|
1218
|
|
|
protected function getTokenName() { |
1219
|
|
|
return $this->isSignup() ? 'wpCreateaccountToken' : 'wpLoginToken'; |
1220
|
|
|
} |
1221
|
|
|
|
1222
|
|
|
/** |
1223
|
|
|
* Produce a bar of links which allow the user to select another language |
1224
|
|
|
* during login/registration but retain "returnto" |
1225
|
|
|
* |
1226
|
|
|
* @return string |
1227
|
|
|
*/ |
1228
|
|
|
protected function makeLanguageSelector() { |
1229
|
|
|
$msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage(); |
1230
|
|
|
if ( $msg->isBlank() ) { |
1231
|
|
|
return ''; |
1232
|
|
|
} |
1233
|
|
|
$langs = explode( "\n", $msg->text() ); |
1234
|
|
|
$links = []; |
1235
|
|
|
foreach ( $langs as $lang ) { |
1236
|
|
|
$lang = trim( $lang, '* ' ); |
1237
|
|
|
$parts = explode( '|', $lang ); |
1238
|
|
|
if ( count( $parts ) >= 2 ) { |
1239
|
|
|
$links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) ); |
1240
|
|
|
} |
1241
|
|
|
} |
1242
|
|
|
|
1243
|
|
|
return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams( |
1244
|
|
|
$this->getLanguage()->pipeList( $links ) )->escaped() : ''; |
1245
|
|
|
} |
1246
|
|
|
|
1247
|
|
|
/** |
1248
|
|
|
* Create a language selector link for a particular language |
1249
|
|
|
* Links back to this page preserving type and returnto |
1250
|
|
|
* |
1251
|
|
|
* @param string $text Link text |
1252
|
|
|
* @param string $lang Language code |
1253
|
|
|
* @return string |
1254
|
|
|
*/ |
1255
|
|
|
protected function makeLanguageSelectorLink( $text, $lang ) { |
1256
|
|
|
if ( $this->getLanguage()->getCode() == $lang ) { |
1257
|
|
|
// no link for currently used language |
1258
|
|
|
return htmlspecialchars( $text ); |
1259
|
|
|
} |
1260
|
|
|
$query = [ 'uselang' => $lang ]; |
1261
|
|
|
if ( $this->mReturnTo !== '' ) { |
1262
|
|
|
$query['returnto'] = $this->mReturnTo; |
1263
|
|
|
$query['returntoquery'] = $this->mReturnToQuery; |
1264
|
|
|
} |
1265
|
|
|
|
1266
|
|
|
$attr = []; |
1267
|
|
|
$targetLanguage = Language::factory( $lang ); |
1268
|
|
|
$attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode(); |
1269
|
|
|
|
1270
|
|
|
return Linker::linkKnown( |
1271
|
|
|
$this->getPageTitle(), |
1272
|
|
|
htmlspecialchars( $text ), |
1273
|
|
|
$attr, |
1274
|
|
|
$query |
1275
|
|
|
); |
1276
|
|
|
} |
1277
|
|
|
|
1278
|
|
|
protected function getGroupName() { |
1279
|
|
|
return 'login'; |
1280
|
|
|
} |
1281
|
|
|
|
1282
|
|
|
/** |
1283
|
|
|
* @param array $formDescriptor |
1284
|
|
|
*/ |
1285
|
|
|
protected function postProcessFormDescriptor( &$formDescriptor, $requests ) { |
1286
|
|
|
// Pre-fill username (if not creating an account, T46775). |
1287
|
|
|
if ( |
1288
|
|
|
isset( $formDescriptor['username'] ) && |
1289
|
|
|
!isset( $formDescriptor['username']['default'] ) && |
1290
|
|
|
!$this->isSignup() |
1291
|
|
|
) { |
1292
|
|
|
$user = $this->getUser(); |
1293
|
|
|
if ( $user->isLoggedIn() ) { |
1294
|
|
|
$formDescriptor['username']['default'] = $user->getName(); |
1295
|
|
|
} else { |
1296
|
|
|
$formDescriptor['username']['default'] = |
1297
|
|
|
$this->getRequest()->getSession()->suggestLoginUsername(); |
1298
|
|
|
} |
1299
|
|
|
} |
1300
|
|
|
|
1301
|
|
|
// don't show a submit button if there is nothing to submit (i.e. the only form content |
1302
|
|
|
// is other submit buttons, for redirect flows) |
1303
|
|
|
if ( !$this->needsSubmitButton( $requests ) ) { |
1304
|
|
|
unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] ); |
1305
|
|
|
} |
1306
|
|
|
|
1307
|
|
|
if ( !$this->isSignup() ) { |
1308
|
|
|
// FIXME HACK don't focus on non-empty field |
1309
|
|
|
// maybe there should be an autofocus-if similar to hide-if? |
1310
|
|
|
if ( |
1311
|
|
|
isset( $formDescriptor['username'] ) |
1312
|
|
|
&& empty( $formDescriptor['username']['default'] ) |
1313
|
|
|
&& !$this->getRequest()->getCheck( 'wpName' ) |
1314
|
|
|
) { |
1315
|
|
|
$formDescriptor['username']['autofocus'] = true; |
1316
|
|
|
} elseif ( isset( $formDescriptor['password'] ) ) { |
1317
|
|
|
$formDescriptor['password']['autofocus'] = true; |
1318
|
|
|
} |
1319
|
|
|
} |
1320
|
|
|
|
1321
|
|
|
$this->addTabIndex( $formDescriptor ); |
1322
|
|
|
} |
1323
|
|
|
} |
1324
|
|
|
|
1325
|
|
|
/** |
1326
|
|
|
* B/C class to try handling login/signup template modifications even though login/signup does not |
1327
|
|
|
* actually happen through a template anymore. Just collects extra field definitions and allows |
1328
|
|
|
* some other class to do decide what to do with threm.. |
1329
|
|
|
* TODO find the right place for adding extra fields and kill this |
1330
|
|
|
*/ |
1331
|
|
|
class FakeAuthTemplate extends BaseTemplate { |
1332
|
|
|
public function execute() { |
1333
|
|
|
throw new LogicException( 'not used' ); |
1334
|
|
|
} |
1335
|
|
|
|
1336
|
|
|
/** |
1337
|
|
|
* Extensions (AntiSpoof and TitleBlacklist) call this in response to |
1338
|
|
|
* UserCreateForm hook to add checkboxes to the create account form. |
1339
|
|
|
*/ |
1340
|
|
|
public function addInputItem( $name, $value, $type, $msg, $helptext = false ) { |
1341
|
|
|
// use the same indexes as UserCreateForm just in case someone adds an item manually |
1342
|
|
|
$this->data['extrainput'][] = [ |
|
|
|
|
1343
|
|
|
'name' => $name, |
1344
|
|
|
'value' => $value, |
1345
|
|
|
'type' => $type, |
1346
|
|
|
'msg' => $msg, |
1347
|
|
|
'helptext' => $helptext, |
1348
|
|
|
]; |
1349
|
|
|
} |
1350
|
|
|
|
1351
|
|
|
/** |
1352
|
|
|
* Turns addInputItem-style field definitions into HTMLForm field definitions. |
1353
|
|
|
* @return array |
1354
|
|
|
*/ |
1355
|
|
|
public function getExtraInputDefinitions() { |
1356
|
|
|
$definitions = []; |
1357
|
|
|
|
1358
|
|
|
foreach ( $this->get( 'extrainput', [] ) as $field ) { |
1359
|
|
|
$definition = [ |
1360
|
|
|
'type' => $field['type'] === 'checkbox' ? 'check' : $field['type'], |
1361
|
|
|
'name' => $field['name'], |
1362
|
|
|
'value' => $field['value'], |
1363
|
|
|
'id' => $field['name'], |
1364
|
|
|
]; |
1365
|
|
|
if ( $field['msg'] ) { |
1366
|
|
|
$definition['label-message'] = $this->getMsg( $field['msg'] ); |
1367
|
|
|
} |
1368
|
|
|
if ( $field['helptext'] ) { |
1369
|
|
|
$definition['help'] = $this->msgWiki( $field['helptext'] ); |
|
|
|
|
1370
|
|
|
} |
1371
|
|
|
|
1372
|
|
|
// the array key doesn't matter much when name is defined explicitly but |
1373
|
|
|
// let's try and follow HTMLForm conventions |
1374
|
|
|
$name = preg_replace( '/^wp(?=[A-Z])/', '', $field['name'] ); |
1375
|
|
|
$definitions[$name] = $definition; |
1376
|
|
|
} |
1377
|
|
|
|
1378
|
|
|
if ( $this->haveData( 'extrafields' ) ) { |
1379
|
|
|
$definitions['extrafields'] = [ |
1380
|
|
|
'type' => 'info', |
1381
|
|
|
'raw' => true, |
1382
|
|
|
'default' => $this->get( 'extrafields' ), |
1383
|
|
|
]; |
1384
|
|
|
} |
1385
|
|
|
|
1386
|
|
|
return $definitions; |
1387
|
|
|
} |
1388
|
|
|
} |
1389
|
|
|
|
1390
|
|
|
/** |
1391
|
|
|
* LoginForm as a special page has been replaced by SpecialUserLogin and SpecialCreateAccount, |
1392
|
|
|
* but some extensions called its public methods directly, so the class is retained as a |
1393
|
|
|
* B/C wrapper. Anything that used it before should use AuthManager instead. |
1394
|
|
|
*/ |
1395
|
|
|
class LoginForm extends SpecialPage { |
1396
|
|
|
const SUCCESS = 0; |
1397
|
|
|
const NO_NAME = 1; |
1398
|
|
|
const ILLEGAL = 2; |
1399
|
|
|
const WRONG_PLUGIN_PASS = 3; |
1400
|
|
|
const NOT_EXISTS = 4; |
1401
|
|
|
const WRONG_PASS = 5; |
1402
|
|
|
const EMPTY_PASS = 6; |
1403
|
|
|
const RESET_PASS = 7; |
1404
|
|
|
const ABORTED = 8; |
1405
|
|
|
const CREATE_BLOCKED = 9; |
1406
|
|
|
const THROTTLED = 10; |
1407
|
|
|
const USER_BLOCKED = 11; |
1408
|
|
|
const NEED_TOKEN = 12; |
1409
|
|
|
const WRONG_TOKEN = 13; |
1410
|
|
|
const USER_MIGRATED = 14; |
1411
|
|
|
|
1412
|
|
|
public static $statusCodes = [ |
1413
|
|
|
self::SUCCESS => 'success', |
1414
|
|
|
self::NO_NAME => 'no_name', |
1415
|
|
|
self::ILLEGAL => 'illegal', |
1416
|
|
|
self::WRONG_PLUGIN_PASS => 'wrong_plugin_pass', |
1417
|
|
|
self::NOT_EXISTS => 'not_exists', |
1418
|
|
|
self::WRONG_PASS => 'wrong_pass', |
1419
|
|
|
self::EMPTY_PASS => 'empty_pass', |
1420
|
|
|
self::RESET_PASS => 'reset_pass', |
1421
|
|
|
self::ABORTED => 'aborted', |
1422
|
|
|
self::CREATE_BLOCKED => 'create_blocked', |
1423
|
|
|
self::THROTTLED => 'throttled', |
1424
|
|
|
self::USER_BLOCKED => 'user_blocked', |
1425
|
|
|
self::NEED_TOKEN => 'need_token', |
1426
|
|
|
self::WRONG_TOKEN => 'wrong_token', |
1427
|
|
|
self::USER_MIGRATED => 'user_migrated', |
1428
|
|
|
]; |
1429
|
|
|
|
1430
|
|
|
/** |
1431
|
|
|
* @param WebRequest $request |
1432
|
|
|
*/ |
1433
|
|
|
public function __construct( $request = null ) { |
1434
|
|
|
wfDeprecated( 'LoginForm', '1.27' ); |
1435
|
|
|
parent::__construct(); |
1436
|
|
|
} |
1437
|
|
|
|
1438
|
|
|
/** |
1439
|
|
|
* @deprecated since 1.27 - call LoginHelper::getValidErrorMessages instead. |
1440
|
|
|
*/ |
1441
|
|
|
public static function getValidErrorMessages() { |
1442
|
|
|
return LoginHelper::getValidErrorMessages(); |
1443
|
|
|
} |
1444
|
|
|
|
1445
|
|
|
/** |
1446
|
|
|
* @deprecated since 1.27 - don't use LoginForm, use AuthManager instead |
1447
|
|
|
*/ |
1448
|
|
View Code Duplication |
public static function incrementLoginThrottle( $username ) { |
1449
|
|
|
wfDeprecated( __METHOD__, "1.27" ); |
1450
|
|
|
global $wgRequest; |
1451
|
|
|
$username = User::getCanonicalName( $username, 'usable' ) ?: $username; |
1452
|
|
|
$throttler = new Throttler(); |
1453
|
|
|
return $throttler->increase( $username, $wgRequest->getIP(), __METHOD__ ); |
1454
|
|
|
} |
1455
|
|
|
|
1456
|
|
|
/** |
1457
|
|
|
* @deprecated since 1.27 - don't use LoginForm, use AuthManager instead |
1458
|
|
|
*/ |
1459
|
|
|
public static function incLoginThrottle( $username ) { |
1460
|
|
|
wfDeprecated( __METHOD__, "1.27" ); |
1461
|
|
|
$res = self::incrementLoginThrottle( $username ); |
1462
|
|
|
return is_array( $res ) ? true : 0; |
1463
|
|
|
} |
1464
|
|
|
|
1465
|
|
|
/** |
1466
|
|
|
* @deprecated since 1.27 - don't use LoginForm, use AuthManager instead |
1467
|
|
|
*/ |
1468
|
|
View Code Duplication |
public static function clearLoginThrottle( $username ) { |
1469
|
|
|
wfDeprecated( __METHOD__, "1.27" ); |
1470
|
|
|
global $wgRequest; |
1471
|
|
|
$username = User::getCanonicalName( $username, 'usable' ) ?: $username; |
1472
|
|
|
$throttler = new Throttler(); |
1473
|
|
|
return $throttler->clear( $username, $wgRequest->getIP() ); |
1474
|
|
|
} |
1475
|
|
|
|
1476
|
|
|
/** |
1477
|
|
|
* @deprecated since 1.27 - don't use LoginForm, use AuthManager instead |
1478
|
|
|
*/ |
1479
|
|
|
public static function getLoginToken() { |
1480
|
|
|
wfDeprecated( __METHOD__, '1.27' ); |
1481
|
|
|
global $wgRequest; |
1482
|
|
|
return $wgRequest->getSession()->getToken( '', 'login' )->toString(); |
1483
|
|
|
} |
1484
|
|
|
|
1485
|
|
|
/** |
1486
|
|
|
* @deprecated since 1.27 - don't use LoginForm, use AuthManager instead |
1487
|
|
|
*/ |
1488
|
|
|
public static function setLoginToken() { |
1489
|
|
|
wfDeprecated( __METHOD__, '1.27' ); |
1490
|
|
|
} |
1491
|
|
|
|
1492
|
|
|
/** |
1493
|
|
|
* @deprecated since 1.27 - don't use LoginForm, use AuthManager instead |
1494
|
|
|
*/ |
1495
|
|
|
public static function clearLoginToken() { |
1496
|
|
|
wfDeprecated( __METHOD__, '1.27' ); |
1497
|
|
|
global $wgRequest; |
1498
|
|
|
$wgRequest->getSession()->resetToken( 'login' ); |
1499
|
|
|
} |
1500
|
|
|
|
1501
|
|
|
/** |
1502
|
|
|
* @deprecated since 1.27 - don't use LoginForm, use AuthManager instead |
1503
|
|
|
*/ |
1504
|
|
|
public static function getCreateaccountToken() { |
1505
|
|
|
wfDeprecated( __METHOD__, '1.27' ); |
1506
|
|
|
global $wgRequest; |
1507
|
|
|
return $wgRequest->getSession()->getToken( '', 'createaccount' )->toString(); |
1508
|
|
|
} |
1509
|
|
|
|
1510
|
|
|
/** |
1511
|
|
|
* @deprecated since 1.27 - don't use LoginForm, use AuthManager instead |
1512
|
|
|
*/ |
1513
|
|
|
public static function setCreateaccountToken() { |
1514
|
|
|
wfDeprecated( __METHOD__, '1.27' ); |
1515
|
|
|
} |
1516
|
|
|
|
1517
|
|
|
/** |
1518
|
|
|
* @deprecated since 1.27 - don't use LoginForm, use AuthManager instead |
1519
|
|
|
*/ |
1520
|
|
|
public static function clearCreateaccountToken() { |
1521
|
|
|
wfDeprecated( __METHOD__, '1.27' ); |
1522
|
|
|
global $wgRequest; |
1523
|
|
|
$wgRequest->getSession()->resetToken( 'createaccount' ); |
1524
|
|
|
} |
1525
|
|
|
} |
1526
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.