MediaWiki  master
SpecialChangeCredentials.php
Go to the documentation of this file.
1 <?php
2 
11 
18  protected static $allowedActions = [ AuthManager::ACTION_CHANGE ];
19 
20  protected static $messagePrefix = 'changecredentials';
21 
23  protected static $loadUserData = true;
24 
28  public function __construct( AuthManager $authManager ) {
29  parent::__construct( 'ChangeCredentials', 'editmyprivateinfo' );
30  $this->setAuthManager( $authManager );
31  }
32 
33  protected function getGroupName() {
34  return 'users';
35  }
36 
37  public function isListed() {
38  $this->loadAuth( '' );
39  return (bool)$this->authRequests;
40  }
41 
42  public function doesWrites() {
43  return true;
44  }
45 
46  protected function getDefaultAction( $subPage ) {
47  return AuthManager::ACTION_CHANGE;
48  }
49 
50  protected function getPreservedParams( $withToken = false ) {
51  $request = $this->getRequest();
52  $params = parent::getPreservedParams( $withToken );
53  $params += [
54  'returnto' => $request->getVal( 'returnto' ),
55  'returntoquery' => $request->getVal( 'returntoquery' ),
56  ];
57  return $params;
58  }
59 
60  public function execute( $subPage ) {
61  $this->setHeaders();
62  $this->outputHeader();
63 
64  $this->loadAuth( $subPage );
65 
66  if ( !$subPage ) {
67  $this->showSubpageList();
68  return;
69  }
70 
71  if ( !$this->authRequests ) {
72  // messages used: changecredentials-invalidsubpage, removecredentials-invalidsubpage
73  $this->showSubpageList( $this->msg( static::$messagePrefix . '-invalidsubpage', $subPage ) );
74  return;
75  }
76 
77  $out = $this->getOutput();
78  $out->addModules( 'mediawiki.special.changecredentials' );
79  $out->addBacklinkSubtitle( $this->getPageTitle() );
80  $status = $this->trySubmit();
81 
82  if ( $status === false || !$status->isOK() ) {
83  $this->displayForm( $status );
84  return;
85  }
86 
87  $response = $status->getValue();
88 
89  switch ( $response->status ) {
90  case AuthenticationResponse::PASS:
91  $this->success();
92  break;
93  case AuthenticationResponse::FAIL:
94  $this->displayForm( Status::newFatal( $response->message ) );
95  break;
96  default:
97  throw new LogicException( 'invalid AuthenticationResponse' );
98  }
99  }
100 
101  protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
102  parent::loadAuth( $subPage, $authAction );
103  if ( $subPage ) {
104  $foundReqs = [];
105  foreach ( $this->authRequests as $req ) {
106  if ( $req->getUniqueId() === $subPage ) {
107  $foundReqs[] = $req;
108  }
109  }
110  if ( count( $foundReqs ) > 1 ) {
111  throw new LogicException( 'Multiple AuthenticationRequest objects with same ID!' );
112  }
113  $this->authRequests = $foundReqs;
114  }
115  }
116 
118  public function onAuthChangeFormFields(
119  array $requests, array $fieldInfo, array &$formDescriptor, $action
120  ) {
121  parent::onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
122 
123  // Add some UI flair for password changes, the most common use case for this page.
124  if ( AuthenticationRequest::getRequestByClass( $this->authRequests,
125  PasswordAuthenticationRequest::class )
126  ) {
127  $formDescriptor = self::mergeDefaultFormDescriptor( $fieldInfo, $formDescriptor, [
128  'password' => [
129  'autocomplete' => 'new-password',
130  'placeholder-message' => 'createacct-yourpassword-ph',
131  'help-message' => 'createacct-useuniquepass',
132  ],
133  'retype' => [
134  'autocomplete' => 'new-password',
135  'placeholder-message' => 'createacct-yourpasswordagain-ph',
136  ],
137  // T263927 - the Chromium password form guide recommends always having a username field
138  'username' => [
139  'type' => 'text',
140  'baseField' => 'password',
141  'autocomplete' => 'username',
142  'nodata' => true,
143  'readonly' => true,
144  'cssclass' => 'mw-htmlform-hidden-field',
145  'label-message' => 'userlogin-yourname',
146  'placeholder-message' => 'userlogin-yourname-ph',
147  ],
148  ] );
149  }
150  }
151 
152  protected function getAuthFormDescriptor( $requests, $action ) {
153  if ( !static::$loadUserData ) {
154  return [];
155  } else {
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 
170  if ( $any ) {
171  $this->getOutput()->addModules( 'mediawiki.misc-authed-ooui' );
172  }
173 
174  return $descriptor;
175  }
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  $title = Title::newFromText( $returnTo );
289  return $title->getFullUrlForRedirect( $returnToQuery );
290  }
291 
292  protected function getRequestBlacklist() {
293  return $this->getConfig()->get( MainConfigNames::ChangeCredentialsBlacklist );
294  }
295 }
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-existent 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.
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:55
A class containing constants representing the names of configuration variables.
This serves as the entry point to the MediaWiki session handling system.
Represents a title within MediaWiki.
Definition: Title.php:82
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.
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.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:73
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:85