Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.36% covered (warning)
89.36%
84 / 94
53.33% covered (warning)
53.33%
8 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialPasswordReset
90.32% covered (success)
90.32%
84 / 93
53.33% covered (warning)
53.33%
8 / 15
35.05
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRestriction
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 doesWrites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 userCanExecute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 checkExecutePermissions
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getLoginHelper
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 execute
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getFormFields
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
6.01
 getPreservedParams
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getForm
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getDisplayFormat
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 alterForm
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 onSubmit
97.44% covered (success)
97.44%
38 / 39
0.00% covered (danger)
0.00%
0 / 1
6
 isListed
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Specials;
8
9use MediaWiki\Exception\ErrorPageError;
10use MediaWiki\Exception\ThrottledError;
11use MediaWiki\HTMLForm\HTMLForm;
12use MediaWiki\MainConfigNames;
13use MediaWiki\MediaWikiServices;
14use MediaWiki\SpecialPage\FormSpecialPage;
15use MediaWiki\SpecialPage\SpecialPage;
16use MediaWiki\Specials\Helpers\LoginHelper;
17use MediaWiki\Status\Status;
18use MediaWiki\User\PasswordReset;
19use MediaWiki\User\User;
20
21/**
22 * Special page for requesting a password reset email.
23 *
24 * Requires the TemporaryPasswordPrimaryAuthenticationProvider and the
25 * EmailNotificationSecondaryAuthenticationProvider (or something providing equivalent
26 * functionality) to be enabled.
27 *
28 * @ingroup SpecialPage
29 */
30class SpecialPasswordReset extends FormSpecialPage {
31    /** @var LoginHelper */
32    protected $loginHelper = null;
33
34    public function __construct(
35        private readonly PasswordReset $passwordReset,
36    ) {
37        parent::__construct( 'PasswordReset' );
38    }
39
40    /** @inheritDoc */
41    public function getRestriction(): string {
42        return 'editmyprivateinfo';
43    }
44
45    /** @inheritDoc */
46    public function doesWrites() {
47        return true;
48    }
49
50    /** @inheritDoc */
51    public function userCanExecute( User $user ) {
52        return $this->passwordReset->isAllowed( $user )->isGood();
53    }
54
55    public function checkExecutePermissions( User $user ) {
56        $status = Status::wrap( $this->passwordReset->isAllowed( $user ) );
57        if ( !$status->isGood() ) {
58            throw new ErrorPageError( 'internalerror', $status->getMessage() );
59        }
60
61        parent::checkExecutePermissions( $user );
62    }
63
64    /**
65     * Get the login helper.
66     * @return LoginHelper
67     */
68    private function getLoginHelper(): LoginHelper {
69        if ( $this->loginHelper === null ) {
70            $this->loginHelper = new LoginHelper( $this->getContext() );
71        }
72        return $this->loginHelper;
73    }
74
75    /**
76     * @param string|null $par
77     */
78    public function execute( $par ) {
79        // Use the authentication-popup skin in popup mode.
80        if ( $this->getLoginHelper()->isDisplayModePopup() ) {
81            $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
82            $this->getContext()->setSkin( $skinFactory->makeSkin( 'authentication-popup' ) );
83        }
84
85        $out = $this->getOutput();
86        $out->disallowUserJs();
87        parent::execute( $par );
88    }
89
90    /** @inheritDoc */
91    protected function getFormFields() {
92        $resetRoutes = $this->getConfig()->get( MainConfigNames::PasswordResetRoutes );
93        $a = [];
94        if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
95            $a['Username'] = [
96                'type' => 'user',
97                'default' => $this->getRequest()->getSession()->suggestLoginUsername(),
98                'label-message' => 'passwordreset-username',
99                'excludetemp' => true,
100            ];
101
102            if ( $this->getUser()->isRegistered() ) {
103                $a['Username']['default'] = $this->getUser()->getName();
104            }
105        }
106
107        if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
108            $a['Email'] = [
109                'type' => 'email',
110                'label-message' => 'passwordreset-email',
111            ];
112        }
113
114        return $a;
115    }
116
117    /**
118     * Get preserved URL parameters.
119     * @return array
120     */
121    private function getPreservedParams(): array {
122        $loginHelper = new LoginHelper( $this->getContext() );
123        return $loginHelper->getPreservedParams( [] );
124    }
125
126    /** @inheritDoc */
127    protected function getForm() {
128        $form = parent::getForm();
129        $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
130        return $form;
131    }
132
133    /** @inheritDoc */
134    protected function getDisplayFormat() {
135        return 'codex';
136    }
137
138    public function alterForm( HTMLForm $form ) {
139        $resetRoutes = $this->getConfig()->get( MainConfigNames::PasswordResetRoutes );
140
141        $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
142
143        $i = 0;
144        if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
145            $i++;
146        }
147        if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
148            $i++;
149        }
150
151        $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one';
152
153        $form->setHeaderHtml( $this->msg( $message, $i )->parseAsBlock() );
154        $form->setSubmitTextMsg( 'mailmypassword' );
155    }
156
157    /**
158     * Process the form.
159     * At this point, we know that the user passes all the criteria in
160     * userCanExecute(), and if the data array contains 'Username', etc., then Username
161     * resets are allowed.
162     * @param array $data
163     * @return Status
164     */
165    public function onSubmit( array $data ) {
166        $username = $data['Username'] ?? null;
167        $email = $data['Email'] ?? null;
168
169        $result = Status::wrap(
170            $this->passwordReset->execute( $this->getUser(), $username, $email ) );
171
172        if ( $result->hasMessage( 'actionthrottledtext' ) ) {
173            throw new ThrottledError;
174        }
175
176        // Show a message on the successful processing of the form.
177        // This doesn't necessarily mean a reset email was sent.
178        if ( $result->isGood() ) {
179            $output = $this->getOutput();
180
181            // Information messages.
182            $output->addWikiMsg( 'passwordreset-success' );
183            $output->addWikiMsg( 'passwordreset-success-details-generic',
184                $this->getConfig()->get( MainConfigNames::PasswordReminderResendTime ) );
185
186            // Confirmation of what the user has just submitted.
187            $info = "\n";
188            if ( $username ) {
189                $info .= "* " . $this->msg( 'passwordreset-username' ) . ' '
190                    . wfEscapeWikiText( $username ) . "\n";
191            }
192            if ( $email ) {
193                $info .= "* " . $this->msg( 'passwordreset-email' ) . ' '
194                    . wfEscapeWikiText( $email ) . "\n";
195            }
196            $output->addWikiMsg( 'passwordreset-success-info', $info );
197
198            if ( $this->getLoginHelper()->isDisplayModePopup() ) {
199                $linkRenderer = MediaWikiServices::getInstance()->getLinkRendererFactory()->create();
200                $linkClasses = [
201                    'mw-authentication-popup-return-to-login',
202                    'mw-authentication-popup-link',
203                    'cdx-button',
204                    'cdx-button--fake-button',
205                    'cdx-button--fake-button--enabled',
206                    'cdx-button--weight-primary',
207                    'cdx-button--action-progressive',
208                ];
209                $link = $linkRenderer->makeLink(
210                    SpecialPage::getTitleFor( 'Userlogin' ),
211                    $this->msg( 'returnto-login' )->text(),
212                    [ 'class' => $linkClasses ],
213                    $this->getPreservedParams()
214                );
215                $output->addHTML( $link );
216            } else {
217                // Add a return to link to the main page.
218                $output->returnToMain();
219            }
220        }
221
222        return $result;
223    }
224
225    /**
226     * Hide the password reset page if resets are disabled.
227     * @return bool
228     */
229    public function isListed() {
230        if ( !$this->passwordReset->isEnabled()->isGood() ) {
231            return false;
232        }
233
234        return parent::isListed();
235    }
236
237    /** @inheritDoc */
238    protected function getGroupName() {
239        return 'login';
240    }
241}
242
243/**
244 * Retain the old class name for backwards compatibility.
245 * @deprecated since 1.41
246 */
247class_alias( SpecialPasswordReset::class, 'SpecialPasswordReset' );