MediaWiki  master
SpecialChangeCredentials.php
Go to the documentation of this file.
1 <?php
2 
3 namespace MediaWiki\Specials;
4 
5 use LogicException;
16 use Message;
17 
25 
26  protected static $messagePrefix = 'changecredentials';
27 
29  protected static $loadUserData = true;
30 
34  public function __construct( AuthManager $authManager ) {
35  parent::__construct( 'ChangeCredentials', 'editmyprivateinfo' );
36  $this->setAuthManager( $authManager );
37  }
38 
39  protected function getGroupName() {
40  return 'login';
41  }
42 
43  public function isListed() {
44  $this->loadAuth( '' );
45  return (bool)$this->authRequests;
46  }
47 
48  public function doesWrites() {
49  return true;
50  }
51 
52  protected function getDefaultAction( $subPage ) {
54  }
55 
56  protected function getPreservedParams( $withToken = false ) {
57  $request = $this->getRequest();
58  $params = parent::getPreservedParams( $withToken );
59  $params += [
60  'returnto' => $request->getVal( 'returnto' ),
61  'returntoquery' => $request->getVal( 'returntoquery' ),
62  ];
63  return $params;
64  }
65 
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 ) {
97  $this->success();
98  break;
100  $this->displayForm( Status::newFatal( $response->message ) );
101  break;
102  default:
103  throw new LogicException( 'invalid AuthenticationResponse' );
104  }
105  }
106 
107  protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
108  parent::loadAuth( $subPage, $authAction );
109  if ( $subPage ) {
110  $foundReqs = [];
111  foreach ( $this->authRequests as $req ) {
112  if ( $req->getUniqueId() === $subPage ) {
113  $foundReqs[] = $req;
114  }
115  }
116  if ( count( $foundReqs ) > 1 ) {
117  throw new LogicException( 'Multiple AuthenticationRequest objects with same ID!' );
118  }
119  $this->authRequests = $foundReqs;
120  }
121  }
122 
124  public function onAuthChangeFormFields(
125  array $requests, array $fieldInfo, array &$formDescriptor, $action
126  ) {
127  parent::onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
128 
129  // Add some UI flair for password changes, the most common use case for this page.
130  if ( AuthenticationRequest::getRequestByClass( $this->authRequests,
131  PasswordAuthenticationRequest::class )
132  ) {
133  $formDescriptor = self::mergeDefaultFormDescriptor( $fieldInfo, $formDescriptor, [
134  'password' => [
135  'autocomplete' => 'new-password',
136  'placeholder-message' => 'createacct-yourpassword-ph',
137  'help-message' => 'createacct-useuniquepass',
138  ],
139  'retype' => [
140  'autocomplete' => 'new-password',
141  'placeholder-message' => 'createacct-yourpasswordagain-ph',
142  ],
143  // T263927 - the Chromium password form guide recommends always having a username field
144  'username' => [
145  'type' => 'text',
146  'baseField' => 'password',
147  'autocomplete' => 'username',
148  'nodata' => true,
149  'readonly' => true,
150  'cssclass' => 'mw-htmlform-hidden-field',
151  'label-message' => 'userlogin-yourname',
152  'placeholder-message' => 'userlogin-yourname-ph',
153  ],
154  ] );
155  }
156  }
157 
158  protected function getAuthFormDescriptor( $requests, $action ) {
159  if ( !static::$loadUserData ) {
160  return [];
161  } else {
162  $descriptor = parent::getAuthFormDescriptor( $requests, $action );
163 
164  $any = false;
165  foreach ( $descriptor as &$field ) {
166  if ( $field['type'] === 'password' && $field['name'] !== 'retype' ) {
167  $any = true;
168  if ( isset( $field['cssclass'] ) ) {
169  $field['cssclass'] .= ' mw-changecredentials-validate-password';
170  } else {
171  $field['cssclass'] = 'mw-changecredentials-validate-password';
172  }
173  }
174  }
175 
176  if ( $any ) {
177  $this->getOutput()->addModules( 'mediawiki.misc-authed-ooui' );
178  }
179 
180  return $descriptor;
181  }
182  }
183 
184  protected function getAuthForm( array $requests, $action ) {
185  $form = parent::getAuthForm( $requests, $action );
186  $req = reset( $requests );
187  $info = $req->describeCredentials();
188 
189  $form->addPreHtml(
190  Html::openElement( 'dl' )
191  . Html::element( 'dt', [], $this->msg( 'credentialsform-provider' )->text() )
192  . Html::element( 'dd', [], $info['provider']->text() )
193  . Html::element( 'dt', [], $this->msg( 'credentialsform-account' )->text() )
194  . Html::element( 'dd', [], $info['account']->text() )
195  . Html::closeElement( 'dl' )
196  );
197 
198  // messages used: changecredentials-submit removecredentials-submit
199  $form->setSubmitTextMsg( static::$messagePrefix . '-submit' );
200  $form->showCancel()->setCancelTarget( $this->getReturnUrl() ?: Title::newMainPage() );
201  $form->setSubmitID( 'change_credentials_submit' );
202  return $form;
203  }
204 
205  protected function needsSubmitButton( array $requests ) {
206  // Change/remove forms show are built from a single AuthenticationRequest and do not allow
207  // for redirect flow; they always need a submit button.
208  return true;
209  }
210 
211  public function handleFormSubmit( $data ) {
212  // remove requests do not accept user input
213  $requests = $this->authRequests;
214  if ( static::$loadUserData ) {
215  $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
216  }
217 
218  $response = $this->performAuthenticationStep( $this->authAction, $requests );
219 
220  // we can't handle FAIL or similar as failure here since it might require changing the form
221  return Status::newGood( $response );
222  }
223 
227  protected function showSubpageList( $error = null ) {
228  $out = $this->getOutput();
229 
230  if ( $error ) {
231  $out->addHTML( $error->parse() );
232  }
233 
234  $groupedRequests = [];
235  foreach ( $this->authRequests as $req ) {
236  $info = $req->describeCredentials();
237  $groupedRequests[$info['provider']->text()][] = $req;
238  }
239 
240  $linkRenderer = $this->getLinkRenderer();
241  $out->addHTML( Html::openElement( 'dl' ) );
242  foreach ( $groupedRequests as $group => $members ) {
243  $out->addHTML( Html::element( 'dt', [], $group ) );
244  foreach ( $members as $req ) {
246  $info = $req->describeCredentials();
247  $out->addHTML( Html::rawElement( 'dd', [],
248  $linkRenderer->makeLink(
249  $this->getPageTitle( $req->getUniqueId() ),
250  $info['account']->text()
251  )
252  ) );
253  }
254  }
255  $out->addHTML( Html::closeElement( 'dl' ) );
256  }
257 
258  protected function success() {
259  $session = $this->getRequest()->getSession();
260  $user = $this->getUser();
261  $out = $this->getOutput();
262  $returnUrl = $this->getReturnUrl();
263 
264  // change user token and update the session
265  SessionManager::singleton()->invalidateSessionsForUser( $user );
266  $session->setUser( $user );
267  $session->resetId();
268 
269  if ( $returnUrl ) {
270  $out->redirect( $returnUrl );
271  } else {
272  // messages used: changecredentials-success removecredentials-success
273  $out->addHTML(
275  $out->msg( static::$messagePrefix . '-success' )->parse()
276  )
277  );
278  $out->returnToMain();
279  }
280  }
281 
285  protected function getReturnUrl() {
286  $request = $this->getRequest();
287  $returnTo = $request->getText( 'returnto' );
288  $returnToQuery = $request->getText( 'returntoquery', '' );
289 
290  if ( !$returnTo ) {
291  return null;
292  }
293 
294  $title = Title::newFromText( $returnTo );
295  return $title->getFullUrlForRedirect( $returnToQuery );
296  }
297 
298  protected function getRequestBlacklist() {
300  }
301 }
302 
306 class_alias( SpecialChangeCredentials::class, 'SpecialChangeCredentials' );
This serves as the entry point to the authentication system.
const ACTION_CHANGE
Change a user's credentials.
This is a value object for authentication requests.
static loadRequestsFromSubmission(array $reqs, array $data)
Update a set of requests with form submit data, discarding ones that fail.
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
This is a value object to hold authentication response data.
const FAIL
Indicates that the authentication failed.
const PASS
Indicates that the authentication succeeded.
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:57
static successBox( $html, $className='')
Return a success box.
Definition: Html.php:844
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:288
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:239
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:352
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:264
A class containing constants representing the names of configuration variables.
const ChangeCredentialsBlacklist
Name constant for the ChangeCredentialsBlacklist setting, for use with Config::get()
This serves as the entry point to the MediaWiki session handling system.
static singleton()
Get the global SessionManager.
A special page subclass for authentication-related special pages.
string $subPage
Subpage of the special page.
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 Per default the message key is the canonical name o...
Special change to change 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...
isListed()
Whether this special page is listed in Special:SpecialPages.
getPreservedParams( $withToken=false)
Returns URL query parameters which can be used to reload the page (or leave and return) while preserv...
getRequestBlacklist()
Allows blacklisting certain request types.
static $loadUserData
Change action needs user data; remove action does not.
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 this special page may perform database writes.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:58
Represents a title within MediaWiki.
Definition: Title.php:76
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:686
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:400
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:144
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