MediaWiki master
SpecialChangeCredentials.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Specials;
4
5use LogicException;
17
29 protected static $allowedActions = [ AuthManager::ACTION_CHANGE ];
30
32 protected static $messagePrefix = 'changecredentials';
33
35 protected static $loadUserData = true;
36
37 public function __construct( AuthManager $authManager ) {
38 parent::__construct( 'ChangeCredentials', 'editmyprivateinfo' );
39 $this->setAuthManager( $authManager );
40 }
41
42 protected function getGroupName() {
43 return 'login';
44 }
45
46 public function isListed() {
47 $this->loadAuth( '' );
48 return (bool)$this->authRequests;
49 }
50
51 public function doesWrites() {
52 return true;
53 }
54
55 protected function getDefaultAction( $subPage ) {
56 return AuthManager::ACTION_CHANGE;
57 }
58
59 public function execute( $subPage ) {
60 $this->setHeaders();
61 $this->outputHeader();
62
63 $this->loadAuth( $subPage );
64
65 if ( !$subPage ) {
66 $this->showSubpageList();
67 return;
68 }
69
70 if ( !$this->authRequests ) {
71 // messages used: changecredentials-invalidsubpage, removecredentials-invalidsubpage
72 $this->showSubpageList( $this->msg( static::$messagePrefix . '-invalidsubpage', $subPage ) );
73 return;
74 }
75
76 $out = $this->getOutput();
77 $out->addModules( 'mediawiki.special.changecredentials' );
78 $out->addBacklinkSubtitle( $this->getPageTitle() );
79 $status = $this->trySubmit();
80
81 if ( $status === false || !$status->isOK() ) {
82 $this->displayForm( $status );
83 return;
84 }
85
86 $response = $status->getValue();
87
88 switch ( $response->status ) {
89 case AuthenticationResponse::PASS:
90 $this->success();
91 break;
92 case AuthenticationResponse::FAIL:
93 $this->displayForm( Status::newFatal( $response->message ) );
94 break;
95 default:
96 throw new LogicException( 'invalid AuthenticationResponse' );
97 }
98 }
99
100 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
101 parent::loadAuth( $subPage, $authAction );
102 if ( $subPage ) {
103 $foundReqs = [];
104 foreach ( $this->authRequests as $req ) {
105 if ( $req->getUniqueId() === $subPage ) {
106 $foundReqs[] = $req;
107 }
108 }
109 if ( count( $foundReqs ) > 1 ) {
110 throw new LogicException( 'Multiple AuthenticationRequest objects with same ID!' );
111 }
112 $this->authRequests = $foundReqs;
113 }
114 }
115
117 public function onAuthChangeFormFields(
118 array $requests, array $fieldInfo, array &$formDescriptor, $action
119 ) {
120 parent::onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
121
122 // Add some UI flair for password changes, the most common use case for this page.
123 if ( AuthenticationRequest::getRequestByClass( $this->authRequests,
124 PasswordAuthenticationRequest::class )
125 ) {
126 $formDescriptor = self::mergeDefaultFormDescriptor( $fieldInfo, $formDescriptor, [
127 'password' => [
128 'autocomplete' => 'new-password',
129 'placeholder-message' => 'createacct-yourpassword-ph',
130 'help-message' => 'createacct-useuniquepass',
131 ],
132 'retype' => [
133 'autocomplete' => 'new-password',
134 'placeholder-message' => 'createacct-yourpasswordagain-ph',
135 ],
136 // T263927 - the Chromium password form guide recommends always having a username field
137 'username' => [
138 'type' => 'text',
139 'baseField' => 'password',
140 'autocomplete' => 'username',
141 'nodata' => true,
142 'readonly' => true,
143 'cssclass' => 'mw-htmlform-hidden-field',
144 'label-message' => 'userlogin-yourname',
145 'placeholder-message' => 'userlogin-yourname-ph',
146 ],
147 ] );
148 }
149 }
150
151 protected function getAuthFormDescriptor( $requests, $action ) {
152 if ( !static::$loadUserData ) {
153 return [];
154 }
155
156 $descriptor = parent::getAuthFormDescriptor( $requests, $action );
157
158 $any = false;
159 foreach ( $descriptor as &$field ) {
160 if ( $field['type'] === 'password' && $field['name'] !== 'retype' ) {
161 $any = true;
162 if ( isset( $field['cssclass'] ) ) {
163 $field['cssclass'] .= ' mw-changecredentials-validate-password';
164 } else {
165 $field['cssclass'] = 'mw-changecredentials-validate-password';
166 }
167 }
168 }
169 unset( $field );
170
171 if ( $any ) {
172 $this->getOutput()->addModules( 'mediawiki.misc-authed-ooui' );
173 }
174
175 return $descriptor;
176 }
177
178 protected function getAuthForm( array $requests, $action ) {
179 $form = parent::getAuthForm( $requests, $action );
180 $req = reset( $requests );
181 $info = $req->describeCredentials();
182
183 $form->addPreHtml(
184 Html::openElement( 'dl' )
185 . Html::element( 'dt', [], $this->msg( 'credentialsform-provider' )->text() )
186 . Html::element( 'dd', [], $info['provider']->text() )
187 . Html::element( 'dt', [], $this->msg( 'credentialsform-account' )->text() )
188 . Html::element( 'dd', [], $info['account']->text() )
189 . Html::closeElement( 'dl' )
190 );
191
192 // messages used: changecredentials-submit removecredentials-submit
193 $form->setSubmitTextMsg( static::$messagePrefix . '-submit' );
194 $form->showCancel()->setCancelTarget( $this->getReturnUrl() ?: Title::newMainPage() );
195 $form->setSubmitID( 'change_credentials_submit' );
196 return $form;
197 }
198
199 protected function needsSubmitButton( array $requests ) {
200 // Change/remove forms show are built from a single AuthenticationRequest and do not allow
201 // for redirect flow; they always need a submit button.
202 return true;
203 }
204
205 public function handleFormSubmit( $data ) {
206 // remove requests do not accept user input
207 $requests = $this->authRequests;
208 if ( static::$loadUserData ) {
209 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
210 }
211
212 $response = $this->performAuthenticationStep( $this->authAction, $requests );
213
214 // we can't handle FAIL or similar as failure here since it might require changing the form
215 return Status::newGood( $response );
216 }
217
221 protected function showSubpageList( $error = null ) {
222 $out = $this->getOutput();
223
224 if ( $error ) {
225 $out->addHTML( $error->parse() );
226 }
227
228 $groupedRequests = [];
229 foreach ( $this->authRequests as $req ) {
230 $info = $req->describeCredentials();
231 $groupedRequests[$info['provider']->text()][] = $req;
232 }
233
234 $linkRenderer = $this->getLinkRenderer();
235 $out->addHTML( Html::openElement( 'dl' ) );
236 foreach ( $groupedRequests as $group => $members ) {
237 $out->addHTML( Html::element( 'dt', [], $group ) );
238 foreach ( $members as $req ) {
240 $info = $req->describeCredentials();
241 $out->addHTML( Html::rawElement( 'dd', [],
242 $linkRenderer->makeLink(
243 $this->getPageTitle( $req->getUniqueId() ),
244 $info['account']->text()
245 )
246 ) );
247 }
248 }
249 $out->addHTML( Html::closeElement( 'dl' ) );
250 }
251
252 protected function success() {
253 $session = $this->getRequest()->getSession();
254 $user = $this->getUser();
255 $out = $this->getOutput();
256 $returnUrl = $this->getReturnUrl();
257
258 // change user token and update the session
259 SessionManager::singleton()->invalidateSessionsForUser( $user );
260 $session->setUser( $user );
261 $session->resetId();
262
263 if ( $returnUrl ) {
264 $out->redirect( $returnUrl );
265 } else {
266 // messages used: changecredentials-success removecredentials-success
267 $out->addHTML(
268 Html::successBox(
269 $out->msg( static::$messagePrefix . '-success' )->parse()
270 )
271 );
272 $out->returnToMain();
273 }
274 }
275
279 protected function getReturnUrl() {
280 $request = $this->getRequest();
281 $returnTo = $request->getText( 'returnto' );
282 $returnToQuery = $request->getText( 'returntoquery', '' );
283
284 if ( !$returnTo ) {
285 return null;
286 }
287
288 return Title::newFromText( $returnTo )->getFullUrlForRedirect( $returnToQuery );
289 }
290
291 protected function getRequestBlacklist() {
293 }
294}
295
297class_alias( SpecialChangeCredentials::class, 'SpecialChangeCredentials' );
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:155
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...
Change user 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...
static bool $loadUserData
Change action needs user data; remove action does not.
isListed()
Whether this special page is listed in Special:SpecialPages.
getRequestBlacklist()
Allows blacklisting certain request types.
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 POST requests to this special page require write access to the wiki.
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)