SpecialChangeCredentials.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. use MediaWiki\Auth\AuthenticationRequest;
  3. use MediaWiki\Auth\AuthenticationResponse;
  4. use MediaWiki\Auth\AuthManager;
  5. use MediaWiki\Session\SessionManager;
  6. /**
  7. * Special change to change credentials (such as the password).
  8. *
  9. * Also does most of the work for SpecialRemoveCredentials.
  10. */
  11. class SpecialChangeCredentials extends AuthManagerSpecialPage {
  12. protected static $allowedActions = [ AuthManager::ACTION_CHANGE ];
  13. protected static $messagePrefix = 'changecredentials';
  14. /** Change action needs user data; remove action does not */
  15. protected static $loadUserData = true;
  16. public function __construct( $name = 'ChangeCredentials' ) {
  17. parent::__construct( $name, 'editmyprivateinfo' );
  18. }
  19. protected function getGroupName() {
  20. return 'users';
  21. }
  22. public function isListed() {
  23. $this->loadAuth( '' );
  24. return (bool)$this->authRequests;
  25. }
  26. public function doesWrites() {
  27. return true;
  28. }
  29. protected function getDefaultAction( $subPage ) {
  30. return AuthManager::ACTION_CHANGE;
  31. }
  32. protected function getPreservedParams( $withToken = false ) {
  33. $request = $this->getRequest();
  34. $params = parent::getPreservedParams( $withToken );
  35. $params += [
  36. 'returnto' => $request->getVal( 'returnto' ),
  37. 'returntoquery' => $request->getVal( 'returntoquery' ),
  38. ];
  39. return $params;
  40. }
  41. public function execute( $subPage ) {
  42. $this->setHeaders();
  43. $this->outputHeader();
  44. $this->loadAuth( $subPage );
  45. if ( !$subPage ) {
  46. $this->showSubpageList();
  47. return;
  48. }
  49. if ( !$this->authRequests ) {
  50. // messages used: changecredentials-invalidsubpage, removecredentials-invalidsubpage
  51. $this->showSubpageList( $this->msg( static::$messagePrefix . '-invalidsubpage', $subPage ) );
  52. return;
  53. }
  54. $this->getOutput()->addBacklinkSubtitle( $this->getPageTitle() );
  55. $status = $this->trySubmit();
  56. if ( $status === false || !$status->isOK() ) {
  57. $this->displayForm( $status );
  58. return;
  59. }
  60. $response = $status->getValue();
  61. switch ( $response->status ) {
  62. case AuthenticationResponse::PASS:
  63. $this->success();
  64. break;
  65. case AuthenticationResponse::FAIL:
  66. $this->displayForm( Status::newFatal( $response->message ) );
  67. break;
  68. default:
  69. throw new LogicException( 'invalid AuthenticationResponse' );
  70. }
  71. }
  72. protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
  73. parent::loadAuth( $subPage, $authAction );
  74. if ( $subPage ) {
  75. $this->authRequests = array_filter( $this->authRequests, function ( $req ) use ( $subPage ) {
  76. return $req->getUniqueId() === $subPage;
  77. } );
  78. if ( count( $this->authRequests ) > 1 ) {
  79. throw new LogicException( 'Multiple AuthenticationRequest objects with same ID!' );
  80. }
  81. }
  82. }
  83. protected function getAuthFormDescriptor( $requests, $action ) {
  84. if ( !static::$loadUserData ) {
  85. return [];
  86. } else {
  87. $descriptor = parent::getAuthFormDescriptor( $requests, $action );
  88. $any = false;
  89. foreach ( $descriptor as &$field ) {
  90. if ( $field['type'] === 'password' && $field['name'] !== 'retype' ) {
  91. $any = true;
  92. if ( isset( $field['cssclass'] ) ) {
  93. $field['cssclass'] .= ' mw-changecredentials-validate-password';
  94. } else {
  95. $field['cssclass'] = 'mw-changecredentials-validate-password';
  96. }
  97. }
  98. }
  99. if ( $any ) {
  100. $this->getOutput()->addModules( 'mediawiki.misc-authed-ooui' );
  101. }
  102. return $descriptor;
  103. }
  104. }
  105. protected function getAuthForm( array $requests, $action ) {
  106. $form = parent::getAuthForm( $requests, $action );
  107. $req = reset( $requests );
  108. $info = $req->describeCredentials();
  109. $form->addPreText(
  110. Html::openElement( 'dl' )
  111. . Html::element( 'dt', [], $this->msg( 'credentialsform-provider' )->text() )
  112. . Html::element( 'dd', [], $info['provider'] )
  113. . Html::element( 'dt', [], $this->msg( 'credentialsform-account' )->text() )
  114. . Html::element( 'dd', [], $info['account'] )
  115. . Html::closeElement( 'dl' )
  116. );
  117. // messages used: changecredentials-submit removecredentials-submit
  118. $form->setSubmitTextMsg( static::$messagePrefix . '-submit' );
  119. $form->showCancel()->setCancelTarget( $this->getReturnUrl() ?: Title::newMainPage() );
  120. return $form;
  121. }
  122. protected function needsSubmitButton( array $requests ) {
  123. // Change/remove forms show are built from a single AuthenticationRequest and do not allow
  124. // for redirect flow; they always need a submit button.
  125. return true;
  126. }
  127. public function handleFormSubmit( $data ) {
  128. // remove requests do not accept user input
  129. $requests = $this->authRequests;
  130. if ( static::$loadUserData ) {
  131. $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
  132. }
  133. $response = $this->performAuthenticationStep( $this->authAction, $requests );
  134. // we can't handle FAIL or similar as failure here since it might require changing the form
  135. return Status::newGood( $response );
  136. }
  137. /**
  138. * @param Message|null $error
  139. */
  140. protected function showSubpageList( $error = null ) {
  141. $out = $this->getOutput();
  142. if ( $error ) {
  143. $out->addHTML( $error->parse() );
  144. }
  145. $groupedRequests = [];
  146. foreach ( $this->authRequests as $req ) {
  147. $info = $req->describeCredentials();
  148. $groupedRequests[(string)$info['provider']][] = $req;
  149. }
  150. $linkRenderer = $this->getLinkRenderer();
  151. $out->addHTML( Html::openElement( 'dl' ) );
  152. foreach ( $groupedRequests as $group => $members ) {
  153. $out->addHTML( Html::element( 'dt', [], $group ) );
  154. foreach ( $members as $req ) {
  155. /** @var AuthenticationRequest $req */
  156. $info = $req->describeCredentials();
  157. $out->addHTML( Html::rawElement( 'dd', [],
  158. $linkRenderer->makeLink(
  159. $this->getPageTitle( $req->getUniqueId() ),
  160. $info['account']
  161. )
  162. ) );
  163. }
  164. }
  165. $out->addHTML( Html::closeElement( 'dl' ) );
  166. }
  167. protected function success() {
  168. $session = $this->getRequest()->getSession();
  169. $user = $this->getUser();
  170. $out = $this->getOutput();
  171. $returnUrl = $this->getReturnUrl();
  172. // change user token and update the session
  173. SessionManager::singleton()->invalidateSessionsForUser( $user );
  174. $session->setUser( $user );
  175. $session->resetId();
  176. if ( $returnUrl ) {
  177. $out->redirect( $returnUrl );
  178. } else {
  179. // messages used: changecredentials-success removecredentials-success
  180. $out->wrapWikiMsg( "<div class=\"successbox\">\n$1\n</div>", static::$messagePrefix
  181. . '-success' );
  182. $out->returnToMain();
  183. }
  184. }
  185. /**
  186. * @return string|null
  187. */
  188. protected function getReturnUrl() {
  189. $request = $this->getRequest();
  190. $returnTo = $request->getText( 'returnto' );
  191. $returnToQuery = $request->getText( 'returntoquery', '' );
  192. if ( !$returnTo ) {
  193. return null;
  194. }
  195. $title = Title::newFromText( $returnTo );
  196. return $title->getFullUrlForRedirect( $returnToQuery );
  197. }
  198. protected function getRequestBlacklist() {
  199. return $this->getConfig()->get( 'ChangeCredentialsBlacklist' );
  200. }
  201. }