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 private SessionManager $sessionManager;
37
38 public function __construct( AuthManager $authManager, SessionManager $sessionManager ) {
39 parent::__construct( 'ChangeCredentials', 'editmyprivateinfo' );
40 $this->setAuthManager( $authManager );
41 $this->sessionManager = $sessionManager;
42 }
43
45 protected function getGroupName() {
46 return 'login';
47 }
48
50 public function isListed() {
51 $this->loadAuth( '' );
52 return (bool)$this->authRequests;
53 }
54
56 public function doesWrites() {
57 return true;
58 }
59
61 protected function getDefaultAction( $subPage ) {
62 return AuthManager::ACTION_CHANGE;
63 }
64
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
108 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
109 parent::loadAuth( $subPage, $authAction );
110 if ( $subPage ) {
111 $foundReqs = [];
112 foreach ( $this->authRequests as $req ) {
113 if ( $req->getUniqueId() === $subPage ) {
114 $foundReqs[] = $req;
115 }
116 }
117 if ( count( $foundReqs ) > 1 ) {
118 throw new LogicException( 'Multiple AuthenticationRequest objects with same ID!' );
119 }
120 $this->authRequests = $foundReqs;
121 }
122 }
123
125 public function onAuthChangeFormFields(
126 array $requests, array $fieldInfo, array &$formDescriptor, $action
127 ) {
128 parent::onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
129
130 // Add some UI flair for password changes, the most common use case for this page.
131 if ( AuthenticationRequest::getRequestByClass( $this->authRequests,
132 PasswordAuthenticationRequest::class )
133 ) {
134 $formDescriptor = self::mergeDefaultFormDescriptor( $fieldInfo, $formDescriptor, [
135 'password' => [
136 'autocomplete' => 'new-password',
137 'placeholder-message' => 'createacct-yourpassword-ph',
138 'help-message' => 'createacct-useuniquepass',
139 ],
140 'retype' => [
141 'autocomplete' => 'new-password',
142 'placeholder-message' => 'createacct-yourpasswordagain-ph',
143 ],
144 // T263927 - the Chromium password form guide recommends always having a username field
145 'username' => [
146 'type' => 'text',
147 'baseField' => 'password',
148 'autocomplete' => 'username',
149 'nodata' => true,
150 'readonly' => true,
151 'cssclass' => 'mw-htmlform-hidden-field',
152 'label-message' => 'userlogin-yourname',
153 'placeholder-message' => 'userlogin-yourname-ph',
154 ],
155 ] );
156 }
157 }
158
160 protected function getAuthFormDescriptor( $requests, $action ) {
161 if ( !static::$loadUserData ) {
162 return [];
163 }
164
165 $descriptor = parent::getAuthFormDescriptor( $requests, $action );
166
167 $any = false;
168 foreach ( $descriptor as &$field ) {
169 if ( $field['type'] === 'password' && $field['name'] !== 'retype' ) {
170 $any = true;
171 if ( isset( $field['cssclass'] ) ) {
172 $field['cssclass'] .= ' mw-changecredentials-validate-password';
173 } else {
174 $field['cssclass'] = 'mw-changecredentials-validate-password';
175 }
176 }
177 }
178 unset( $field );
179
180 if ( $any ) {
181 $this->getOutput()->addModules( 'mediawiki.misc-authed-ooui' );
182 }
183
184 return $descriptor;
185 }
186
188 protected function getAuthForm( array $requests, $action ) {
189 $form = parent::getAuthForm( $requests, $action );
190 $req = reset( $requests );
191 $info = $req->describeCredentials();
192
193 $form->addPreHtml(
194 Html::openElement( 'dl' )
195 . Html::element( 'dt', [], $this->msg( 'credentialsform-provider' )->text() )
196 . Html::element( 'dd', [], $info['provider']->text() )
197 . Html::element( 'dt', [], $this->msg( 'credentialsform-account' )->text() )
198 . Html::element( 'dd', [], $info['account']->text() )
199 . Html::closeElement( 'dl' )
200 );
201
202 // messages used: changecredentials-submit removecredentials-submit
203 $form->setSubmitTextMsg( static::$messagePrefix . '-submit' );
204 $form->showCancel()->setCancelTarget( $this->getReturnUrl() ?: Title::newMainPage() );
205 $form->setSubmitID( 'change_credentials_submit' );
206 return $form;
207 }
208
210 protected function needsSubmitButton( array $requests ) {
211 // Change/remove forms show are built from a single AuthenticationRequest and do not allow
212 // for redirect flow; they always need a submit button.
213 return true;
214 }
215
217 public function handleFormSubmit( $data ) {
218 // remove requests do not accept user input
219 $requests = $this->authRequests;
220 if ( static::$loadUserData ) {
221 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
222 }
223
224 $response = $this->performAuthenticationStep( $this->authAction, $requests );
225
226 // we can't handle FAIL or similar as failure here since it might require changing the form
227 return Status::newGood( $response );
228 }
229
233 protected function showSubpageList( $error = null ) {
234 $out = $this->getOutput();
235
236 if ( $error ) {
237 $out->addHTML( $error->parse() );
238 }
239
240 $groupedRequests = [];
241 foreach ( $this->authRequests as $req ) {
242 $info = $req->describeCredentials();
243 $groupedRequests[$info['provider']->text()][] = $req;
244 }
245
246 $linkRenderer = $this->getLinkRenderer();
247 $out->addHTML( Html::openElement( 'dl' ) );
248 foreach ( $groupedRequests as $group => $members ) {
249 $out->addHTML( Html::element( 'dt', [], $group ) );
250 foreach ( $members as $req ) {
252 $info = $req->describeCredentials();
253 $out->addHTML( Html::rawElement( 'dd', [],
254 $linkRenderer->makeLink(
255 $this->getPageTitle( $req->getUniqueId() ),
256 $info['account']->text()
257 )
258 ) );
259 }
260 }
261 $out->addHTML( Html::closeElement( 'dl' ) );
262 }
263
264 protected function success() {
265 $session = $this->getRequest()->getSession();
266 $user = $this->getUser();
267 $out = $this->getOutput();
268 $returnUrl = $this->getReturnUrl();
269
270 // change user token and update the session
271 $this->sessionManager->invalidateSessionsForUser( $user );
272 $session->setUser( $user );
273 $session->resetId();
274
275 if ( $returnUrl ) {
276 $out->redirect( $returnUrl );
277 } else {
278 // messages used: changecredentials-success removecredentials-success
279 $out->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
280 $out->addHTML(
281 Html::successBox(
282 $out->msg( static::$messagePrefix . '-success' )->parse()
283 )
284 );
285 $out->returnToMain();
286 }
287 }
288
292 protected function getReturnUrl() {
293 $request = $this->getRequest();
294 $returnTo = $request->getText( 'returnto' );
295 $returnToQuery = $request->getText( 'returntoquery', '' );
296
297 if ( !$returnTo ) {
298 return null;
299 }
300
301 return Title::newFromText( $returnTo )->getFullUrlForRedirect( $returnToQuery );
302 }
303
305 protected function getRequestBlacklist() {
307 }
308}
309
311class_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, 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:70
element(SerializerNode $parent, SerializerNode $node, $contents)