MediaWiki master
SpecialChangeCredentials.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Specials;
4
5use LogicException;
17
24 protected static $allowedActions = [ AuthManager::ACTION_CHANGE ];
25
26 protected static $messagePrefix = 'changecredentials';
27
29 protected static $loadUserData = true;
30
34 public function __construct( AuthManager $authManager ) {
35 parent::__construct( 'ChangeCredentials', 'editmyprivateinfo' );
36 $this->setAuthManager( $authManager );
37 }
38
39 protected function getGroupName() {
40 return 'login';
41 }
42
43 public function isListed() {
44 $this->loadAuth( '' );
45 return (bool)$this->authRequests;
46 }
47
48 public function doesWrites() {
49 return true;
50 }
51
52 protected function getDefaultAction( $subPage ) {
53 return AuthManager::ACTION_CHANGE;
54 }
55
56 protected function getPreservedParams( $withToken = false ) {
57 $request = $this->getRequest();
58 $params = parent::getPreservedParams( $withToken );
59 $params += [
60 'returnto' => $request->getVal( 'returnto' ),
61 'returntoquery' => $request->getVal( 'returntoquery' ),
62 ];
63 return $params;
64 }
65
66 public function execute( $subPage ) {
67 $this->setHeaders();
68 $this->outputHeader();
69
70 $this->loadAuth( $subPage );
71
72 if ( !$subPage ) {
73 $this->showSubpageList();
74 return;
75 }
76
77 if ( !$this->authRequests ) {
78 // messages used: changecredentials-invalidsubpage, removecredentials-invalidsubpage
79 $this->showSubpageList( $this->msg( static::$messagePrefix . '-invalidsubpage', $subPage ) );
80 return;
81 }
82
83 $out = $this->getOutput();
84 $out->addModules( 'mediawiki.special.changecredentials' );
85 $out->addBacklinkSubtitle( $this->getPageTitle() );
86 $status = $this->trySubmit();
87
88 if ( $status === false || !$status->isOK() ) {
89 $this->displayForm( $status );
90 return;
91 }
92
93 $response = $status->getValue();
94
95 switch ( $response->status ) {
96 case AuthenticationResponse::PASS:
97 $this->success();
98 break;
99 case AuthenticationResponse::FAIL:
100 $this->displayForm( Status::newFatal( $response->message ) );
101 break;
102 default:
103 throw new LogicException( 'invalid AuthenticationResponse' );
104 }
105 }
106
107 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
108 parent::loadAuth( $subPage, $authAction );
109 if ( $subPage ) {
110 $foundReqs = [];
111 foreach ( $this->authRequests as $req ) {
112 if ( $req->getUniqueId() === $subPage ) {
113 $foundReqs[] = $req;
114 }
115 }
116 if ( count( $foundReqs ) > 1 ) {
117 throw new LogicException( 'Multiple AuthenticationRequest objects with same ID!' );
118 }
119 $this->authRequests = $foundReqs;
120 }
121 }
122
124 public function onAuthChangeFormFields(
125 array $requests, array $fieldInfo, array &$formDescriptor, $action
126 ) {
127 parent::onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
128
129 // Add some UI flair for password changes, the most common use case for this page.
130 if ( AuthenticationRequest::getRequestByClass( $this->authRequests,
131 PasswordAuthenticationRequest::class )
132 ) {
133 $formDescriptor = self::mergeDefaultFormDescriptor( $fieldInfo, $formDescriptor, [
134 'password' => [
135 'autocomplete' => 'new-password',
136 'placeholder-message' => 'createacct-yourpassword-ph',
137 'help-message' => 'createacct-useuniquepass',
138 ],
139 'retype' => [
140 'autocomplete' => 'new-password',
141 'placeholder-message' => 'createacct-yourpasswordagain-ph',
142 ],
143 // T263927 - the Chromium password form guide recommends always having a username field
144 'username' => [
145 'type' => 'text',
146 'baseField' => 'password',
147 'autocomplete' => 'username',
148 'nodata' => true,
149 'readonly' => true,
150 'cssclass' => 'mw-htmlform-hidden-field',
151 'label-message' => 'userlogin-yourname',
152 'placeholder-message' => 'userlogin-yourname-ph',
153 ],
154 ] );
155 }
156 }
157
158 protected function getAuthFormDescriptor( $requests, $action ) {
159 if ( !static::$loadUserData ) {
160 return [];
161 }
162
163 $descriptor = parent::getAuthFormDescriptor( $requests, $action );
164
165 $any = false;
166 foreach ( $descriptor as &$field ) {
167 if ( $field['type'] === 'password' && $field['name'] !== 'retype' ) {
168 $any = true;
169 if ( isset( $field['cssclass'] ) ) {
170 $field['cssclass'] .= ' mw-changecredentials-validate-password';
171 } else {
172 $field['cssclass'] = 'mw-changecredentials-validate-password';
173 }
174 }
175 }
176 unset( $field );
177
178 if ( $any ) {
179 $this->getOutput()->addModules( 'mediawiki.misc-authed-ooui' );
180 }
181
182 return $descriptor;
183 }
184
185 protected function getAuthForm( array $requests, $action ) {
186 $form = parent::getAuthForm( $requests, $action );
187 $req = reset( $requests );
188 $info = $req->describeCredentials();
189
190 $form->addPreHtml(
191 Html::openElement( 'dl' )
192 . Html::element( 'dt', [], $this->msg( 'credentialsform-provider' )->text() )
193 . Html::element( 'dd', [], $info['provider']->text() )
194 . Html::element( 'dt', [], $this->msg( 'credentialsform-account' )->text() )
195 . Html::element( 'dd', [], $info['account']->text() )
196 . Html::closeElement( 'dl' )
197 );
198
199 // messages used: changecredentials-submit removecredentials-submit
200 $form->setSubmitTextMsg( static::$messagePrefix . '-submit' );
201 $form->showCancel()->setCancelTarget( $this->getReturnUrl() ?: Title::newMainPage() );
202 $form->setSubmitID( 'change_credentials_submit' );
203 return $form;
204 }
205
206 protected function needsSubmitButton( array $requests ) {
207 // Change/remove forms show are built from a single AuthenticationRequest and do not allow
208 // for redirect flow; they always need a submit button.
209 return true;
210 }
211
212 public function handleFormSubmit( $data ) {
213 // remove requests do not accept user input
214 $requests = $this->authRequests;
215 if ( static::$loadUserData ) {
216 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
217 }
218
219 $response = $this->performAuthenticationStep( $this->authAction, $requests );
220
221 // we can't handle FAIL or similar as failure here since it might require changing the form
222 return Status::newGood( $response );
223 }
224
228 protected function showSubpageList( $error = null ) {
229 $out = $this->getOutput();
230
231 if ( $error ) {
232 $out->addHTML( $error->parse() );
233 }
234
235 $groupedRequests = [];
236 foreach ( $this->authRequests as $req ) {
237 $info = $req->describeCredentials();
238 $groupedRequests[$info['provider']->text()][] = $req;
239 }
240
241 $linkRenderer = $this->getLinkRenderer();
242 $out->addHTML( Html::openElement( 'dl' ) );
243 foreach ( $groupedRequests as $group => $members ) {
244 $out->addHTML( Html::element( 'dt', [], $group ) );
245 foreach ( $members as $req ) {
247 $info = $req->describeCredentials();
248 $out->addHTML( Html::rawElement( 'dd', [],
249 $linkRenderer->makeLink(
250 $this->getPageTitle( $req->getUniqueId() ),
251 $info['account']->text()
252 )
253 ) );
254 }
255 }
256 $out->addHTML( Html::closeElement( 'dl' ) );
257 }
258
259 protected function success() {
260 $session = $this->getRequest()->getSession();
261 $user = $this->getUser();
262 $out = $this->getOutput();
263 $returnUrl = $this->getReturnUrl();
264
265 // change user token and update the session
266 SessionManager::singleton()->invalidateSessionsForUser( $user );
267 $session->setUser( $user );
268 $session->resetId();
269
270 if ( $returnUrl ) {
271 $out->redirect( $returnUrl );
272 } else {
273 // messages used: changecredentials-success removecredentials-success
274 $out->addHTML(
275 Html::successBox(
276 $out->msg( static::$messagePrefix . '-success' )->parse()
277 )
278 );
279 $out->returnToMain();
280 }
281 }
282
286 protected function getReturnUrl() {
287 $request = $this->getRequest();
288 $returnTo = $request->getText( 'returnto' );
289 $returnToQuery = $request->getText( 'returntoquery', '' );
290
291 if ( !$returnTo ) {
292 return null;
293 }
294
295 return Title::newFromText( $returnTo )->getFullUrlForRedirect( $returnToQuery );
296 }
297
298 protected function getRequestBlacklist() {
300 }
301}
302
304class_alias( SpecialChangeCredentials::class, 'SpecialChangeCredentials' );
array $params
The job parameters.
This serves as the entry point to the authentication system.
This is a value object for authentication requests.
describeCredentials()
Describe the credentials represented by this request.
This is a value object to hold authentication response data.
This is a value object for authentication requests with a username and password.
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
A class containing constants representing the names of configuration variables.
const ChangeCredentialsBlacklist
Name constant for the ChangeCredentialsBlacklist 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:158
This serves as the entry point to the MediaWiki session handling system.
A special page subclass for authentication-related special pages.
string $authAction
one of the AuthManager::ACTION_* constants.
static mergeDefaultFormDescriptor(array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor)
Apply defaults to a form descriptor, without creating non-existent fields.
trySubmit()
Attempts to do an authentication step with the submitted data.
getRequest()
Get the WebRequest being used for this instance.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getUser()
Shortcut to get the User executing this instance.
setAuthManager(AuthManager $authManager)
Set the injected AuthManager from the special page constructor.
getPageTitle( $subpage=false)
Get a self-referential title object.
getConfig()
Shortcut to get main config object.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages By default the message key is the canonical name of...
Special change to change credentials (such as the password).
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Change the form descriptor that determines how a field will look in the authentication form....
needsSubmitButton(array $requests)
Returns true if the form built from the given AuthenticationRequests needs a submit button.
loadAuth( $subPage, $authAction=null, $reset=false)
Load or initialize $authAction, $authRequests and $subPage.
handleFormSubmit( $data)
Submit handler callback for HTMLForm.
execute( $subPage)
Default execute method Checks user permissions.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
isListed()
Whether this special page is listed in Special:SpecialPages.
getPreservedParams( $withToken=false)
Returns URL query parameters which can be used to reload the page (or leave and return) while preserv...
getRequestBlacklist()
Allows blacklisting certain request types.
static $loadUserData
Change action needs user data; remove action does not.
getDefaultAction( $subPage)
Get the default action for this special page if none is given via URL/POST data.
getAuthFormDescriptor( $requests, $action)
Generates a HTMLForm descriptor array from a set of authentication requests.
doesWrites()
Indicates whether this special page may perform database writes.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Represents a title within MediaWiki.
Definition Title.php:78
element(SerializerNode $parent, SerializerNode $node, $contents)
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...