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