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