public function execute()
 {
     if (!$this->hasAnyRoutes()) {
         $this->dieUsage('No password reset routes are available.', 'moduledisabled');
     }
     $params = $this->extractRequestParams() + ['user' => null, 'email' => null];
     $this->requireOnlyOneParameter($params, 'user', 'email');
     $passwordReset = new PasswordReset($this->getConfig(), AuthManager::singleton());
     $status = $passwordReset->isAllowed($this->getUser(), $params['capture']);
     if (!$status->isOK()) {
         $this->dieStatus(Status::wrap($status));
     }
     $status = $passwordReset->execute($this->getUser(), $params['user'], $params['email'], $params['capture']);
     if (!$status->isOK()) {
         $status->value = null;
         $this->dieStatus(Status::wrap($status));
     }
     $result = $this->getResult();
     $result->addValue(['resetpassword'], 'status', 'success');
     if ($params['capture']) {
         $passwords = $status->getValue() ?: [];
         ApiResult::setArrayType($passwords, 'kvp', 'user');
         ApiResult::setIndexedTagName($passwords, 'p');
         $result->addValue(['resetpassword'], 'passwords', $passwords);
     }
 }
 /**
  * Hide the password reset page if resets are disabled.
  * @return bool
  */
 public function isListed()
 {
     if ($this->passwordReset->isAllowed($this->getUser())->isGood()) {
         return parent::isListed();
     }
     return false;
 }
 /**
  * @dataProvider provideIsAllowed
  */
 public function testIsAllowed($passwordResetRoutes, $enableEmail, $allowsAuthenticationDataChange, $canEditPrivate, $canSeePassword, $userIsBlocked, $isAllowed, $isAllowedToDisplayPassword)
 {
     $config = new HashConfig(['PasswordResetRoutes' => $passwordResetRoutes, 'EnableEmail' => $enableEmail]);
     $authManager = $this->getMockBuilder(AuthManager::class)->disableOriginalConstructor()->getMock();
     $authManager->expects($this->any())->method('allowsAuthenticationDataChange')->willReturn($allowsAuthenticationDataChange ? Status::newGood() : Status::newFatal('foo'));
     $user = $this->getMock(User::class);
     $user->expects($this->any())->method('getName')->willReturn('Foo');
     $user->expects($this->any())->method('isBlocked')->willReturn($userIsBlocked);
     $user->expects($this->any())->method('isAllowed')->will($this->returnCallback(function ($perm) use($canEditPrivate, $canSeePassword) {
         if ($perm === 'editmyprivateinfo') {
             return $canEditPrivate;
         } elseif ($perm === 'passwordreset') {
             return $canSeePassword;
         } else {
             $this->fail('Unexpected permission check');
         }
     }));
     $passwordReset = new PasswordReset($config, $authManager);
     $this->assertSame($isAllowed, $passwordReset->isAllowed($user)->isGood());
     $this->assertSame($isAllowedToDisplayPassword, $passwordReset->isAllowed($user, true)->isGood());
 }
 /**
  * Generates a form from the given request.
  * @param AuthenticationRequest[] $requests
  * @param string $action AuthManager action name
  * @param string|Message $msg
  * @param string $msgType
  * @return HTMLForm
  */
 protected function getAuthForm(array $requests, $action, $msg = '', $msgType = 'error')
 {
     global $wgSecureLogin, $wgLoginLanguageSelector;
     // FIXME merge this with parent
     if (isset($this->authForm)) {
         return $this->authForm;
     }
     $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
     // get basic form description from the auth logic
     $fieldInfo = AuthenticationRequest::mergeFieldInfo($requests);
     $fakeTemplate = $this->getFakeTemplate($msg, $msgType);
     $this->fakeTemplate = $fakeTemplate;
     // FIXME there should be a saner way to pass this to the hook
     // this will call onAuthChangeFormFields()
     $formDescriptor = static::fieldInfoToFormDescriptor($requests, $fieldInfo, $this->authAction);
     $this->postProcessFormDescriptor($formDescriptor);
     $context = $this->getContext();
     if ($context->getRequest() !== $this->getRequest()) {
         // We have overridden the request, need to make sure the form uses that too.
         $context = new DerivativeContext($this->getContext());
         $context->setRequest($this->getRequest());
     }
     $form = HTMLForm::factory('vform', $formDescriptor, $context);
     $form->addHiddenField('authAction', $this->authAction);
     if ($wgLoginLanguageSelector) {
         $form->addHiddenField('uselang', $this->mLanguage);
     }
     $form->addHiddenField('force', $this->securityLevel);
     $form->addHiddenField($this->getTokenName(), $this->getToken()->toString());
     if ($wgSecureLogin) {
         // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
         if (!$this->isSignup()) {
             $form->addHiddenField('wpForceHttps', (int) $this->mStickHTTPS);
             $form->addHiddenField('wpFromhttp', $usingHTTPS);
         }
     }
     // set properties of the form itself
     $form->setAction($this->getPageTitle()->getLocalURL($this->getReturnToQueryStringFragment()));
     $form->setName('userlogin' . ($this->isSignup() ? '2' : ''));
     if ($this->isSignup()) {
         $form->setId('userlogin2');
     }
     // add pre/post text
     // header used by ConfirmEdit, CondfirmAccount, Persona, WikimediaIncubator, SemanticSignup
     // should be above the error message but HTMLForm doesn't support that
     $form->addHeaderText($fakeTemplate->html('header'));
     // FIXME the old form used this for error/warning messages which does not play well with
     // HTMLForm (maybe it could with a subclass?); for now only display it for signups
     // (where the JS username validation needs it) and alway empty
     if ($this->isSignup()) {
         // used by the mediawiki.special.userlogin.signup.js module
         $statusAreaAttribs = ['id' => 'mw-createacct-status-area'];
         // $statusAreaAttribs += $msg ? [ 'class' => "{$msgType}box" ] : [ 'style' => 'display: none;' ];
         $form->addHeaderText(Html::element('div', $statusAreaAttribs));
     }
     // header used by MobileFrontend
     $form->addHeaderText($fakeTemplate->html('formheader'));
     // blank signup footer for site customization
     if ($this->isSignup() && $this->showExtraInformation()) {
         // Use signupend-https for HTTPS requests if it's not blank, signupend otherwise
         $signupendMsg = $this->msg('signupend');
         $signupendHttpsMsg = $this->msg('signupend-https');
         if (!$signupendMsg->isDisabled()) {
             $signupendText = $usingHTTPS && !$signupendHttpsMsg->isBlank() ? $signupendHttpsMsg->parse() : $signupendMsg->parse();
             $form->addPostText(Html::rawElement('div', ['id' => 'signupend'], $signupendText));
         }
     }
     // warning header for non-standard workflows (e.g. security reauthentication)
     if (!$this->isSignup() && $this->getUser()->isLoggedIn()) {
         $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
         $form->addHeaderText(Html::rawElement('div', ['class' => 'warningbox'], $this->msg($reauthMessage)->params($this->getUser()->getName())->parse()));
     }
     if (!$this->isSignup() && $this->showExtraInformation()) {
         $passwordReset = new PasswordReset($this->getConfig(), AuthManager::singleton());
         if ($passwordReset->isAllowed($this->getUser())) {
             $form->addFooterText(Html::rawElement('div', ['class' => 'mw-ui-vform-field mw-form-related-link-container'], Linker::link(SpecialPage::getTitleFor('PasswordReset'), $this->msg('userlogin-resetpassword-link')->escaped())));
         }
         // Don't show a "create account" link if the user can't.
         if ($this->showCreateAccountLink()) {
             // link to the other action
             $linkTitle = $this->getTitleFor($this->isSignup() ? 'Userlogin' : 'CreateAccount');
             $linkq = $this->getReturnToQueryStringFragment();
             // Pass any language selection on to the mode switch link
             if ($wgLoginLanguageSelector && $this->mLanguage) {
                 $linkq .= '&uselang=' . $this->mLanguage;
             }
             $createOrLoginHref = $linkTitle->getLocalURL($linkq);
             if ($this->getUser()->isLoggedIn()) {
                 $createOrLoginHtml = Html::rawElement('div', ['class' => 'mw-ui-vform-field'], Html::element('a', ['id' => 'mw-createaccount-join', 'href' => $createOrLoginHref, 'tabindex' => 100], $this->msg('userlogin-createanother')->escaped()));
             } else {
                 $createOrLoginHtml = Html::rawElement('div', ['id' => 'mw-createaccount-cta', 'class' => 'mw-ui-vform-field'], $this->msg('userlogin-noaccount')->escaped() . Html::element('a', ['id' => 'mw-createaccount-join', 'href' => $createOrLoginHref, 'class' => 'mw-ui-button', 'tabindex' => 100], $this->msg('userlogin-joinproject')->escaped()));
             }
             $form->addFooterText($createOrLoginHtml);
         }
     }
     $form->suppressDefaultSubmit();
     $this->authForm = $form;
     return $form;
 }
 /**
  * Create a HTMLForm descriptor for the core login fields.
  * @param FakeAuthTemplate $template B/C data (not used but needed by getBCFieldDefinitions)
  * @return array
  */
 protected function getFieldDefinitions($template)
 {
     global $wgEmailConfirmToEdit, $wgLoginLanguageSelector;
     $isLoggedIn = $this->getUser()->isLoggedIn();
     $continuePart = $this->isContinued() ? 'continue-' : '';
     $anotherPart = $isLoggedIn ? 'another-' : '';
     $expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration();
     $expirationDays = ceil($expiration / (3600 * 24));
     $secureLoginLink = '';
     if ($this->mSecureLoginUrl) {
         $secureLoginLink = Html::element('a', ['href' => $this->mSecureLoginUrl, 'class' => 'mw-ui-flush-right mw-secure'], $this->msg('userlogin-signwithsecure')->text());
     }
     $usernameHelpLink = '';
     if (!$this->msg('createacct-helpusername')->isDisabled()) {
         $usernameHelpLink = Html::rawElement('span', ['class' => 'mw-ui-flush-right'], $this->msg('createacct-helpusername')->parse());
     }
     if ($this->isSignup()) {
         $fieldDefinitions = ['statusarea' => ['type' => 'info', 'raw' => true, 'default' => Html::element('div', ['id' => 'mw-createacct-status-area']), 'weight' => -105], 'username' => ['label-raw' => $this->msg('userlogin-yourname')->escaped() . $usernameHelpLink, 'id' => 'wpName2', 'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph' : 'userlogin-yourname-ph'], 'mailpassword' => ['type' => 'check', 'label-message' => 'createaccountmail', 'name' => 'wpCreateaccountMail', 'id' => 'wpCreateaccountMail'], 'password' => ['id' => 'wpPassword2', 'placeholder-message' => 'createacct-yourpassword-ph', 'hide-if' => ['===', 'wpCreateaccountMail', '1']], 'domain' => [], 'retype' => ['baseField' => 'password', 'type' => 'password', 'label-message' => 'createacct-yourpasswordagain', 'id' => 'wpRetype', 'cssclass' => 'loginPassword', 'size' => 20, 'validation-callback' => function ($value, $alldata) {
             if (empty($alldata['mailpassword']) && !empty($alldata['password'])) {
                 if (!$value) {
                     return $this->msg('htmlform-required');
                 } elseif ($value !== $alldata['password']) {
                     return $this->msg('badretype');
                 }
             }
             return true;
         }, 'hide-if' => ['===', 'wpCreateaccountMail', '1'], 'placeholder-message' => 'createacct-yourpasswordagain-ph'], 'email' => ['type' => 'email', 'label-message' => $wgEmailConfirmToEdit ? 'createacct-emailrequired' : 'createacct-emailoptional', 'id' => 'wpEmail', 'cssclass' => 'loginText', 'size' => '20', 'required' => $wgEmailConfirmToEdit, 'validation-callback' => function ($value, $alldata) {
             global $wgEmailConfirmToEdit;
             // AuthManager will check most of these, but that will make the auth
             // session fail and this won't, so nicer to do it this way
             if (!$value && $wgEmailConfirmToEdit) {
                 // no point in allowing registration without email when email is
                 // required to edit
                 return $this->msg('noemailtitle');
             } elseif (!$value && !empty($alldata['mailpassword'])) {
                 // cannot send password via email when there is no email address
                 return $this->msg('noemailcreate');
             } elseif ($value && !Sanitizer::validateEmail($value)) {
                 return $this->msg('invalidemailaddress');
             }
             return true;
         }, 'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph'], 'realname' => ['type' => 'text', 'help-message' => $isLoggedIn ? 'createacct-another-realname-tip' : 'prefs-help-realname', 'label-message' => 'createacct-realname', 'cssclass' => 'loginText', 'size' => 20, 'id' => 'wpRealName'], 'reason' => ['type' => 'text', 'label-message' => 'createacct-reason', 'cssclass' => 'loginText', 'id' => 'wpReason', 'size' => '20', 'placeholder-message' => 'createacct-reason-ph'], 'extrainput' => [], 'createaccount' => ['type' => 'submit', 'default' => $this->msg('createacct-' . $anotherPart . $continuePart . 'submit')->text(), 'name' => 'wpCreateaccount', 'id' => 'wpCreateaccount', 'weight' => 100]];
     } else {
         $fieldDefinitions = ['username' => ['label-raw' => $this->msg('userlogin-yourname')->escaped() . $secureLoginLink, 'id' => 'wpName1', 'placeholder-message' => 'userlogin-yourname-ph'], 'password' => ['id' => 'wpPassword1', 'placeholder-message' => 'userlogin-yourpassword-ph'], 'domain' => [], 'extrainput' => [], 'rememberMe' => ['type' => 'check', 'name' => 'wpRemember', 'label-message' => $this->msg('userlogin-remembermypassword')->numParams($expirationDays), 'id' => 'wpRemember'], 'loginattempt' => ['type' => 'submit', 'default' => $this->msg('pt-login-' . $continuePart . 'button')->text(), 'id' => 'wpLoginAttempt', 'weight' => 100], 'linkcontainer' => ['type' => 'info', 'cssclass' => 'mw-form-related-link-container mw-userlogin-help', 'raw' => true, 'default' => Html::element('a', ['href' => Skin::makeInternalOrExternalUrl(wfMessage('helplogin-url')->inContentLanguage()->text())], $this->msg('userlogin-helplink2')->text()), 'weight' => 200], 'skipReset' => ['weight' => 110, 'flags' => []]];
     }
     $fieldDefinitions['username'] += ['type' => 'text', 'name' => 'wpName', 'cssclass' => 'loginText', 'size' => 20];
     $fieldDefinitions['password'] += ['type' => 'password', 'name' => 'wpPassword', 'cssclass' => 'loginPassword', 'size' => 20];
     if ($template->get('header') || $template->get('formheader')) {
         // B/C for old extensions that haven't been converted to AuthManager (or have been
         // but somebody is using the old version) and still use templates via the
         // UserCreateForm/UserLoginForm hook.
         // 'header' used by ConfirmEdit, CondfirmAccount, Persona, WikimediaIncubator, SemanticSignup
         // 'formheader' used by MobileFrontend
         $fieldDefinitions['header'] = ['type' => 'info', 'raw' => true, 'default' => $template->get('header') ?: $template->get('formheader'), 'weight' => -110];
     }
     if ($this->mEntryError) {
         $fieldDefinitions['entryError'] = ['type' => 'info', 'default' => Html::rawElement('div', ['class' => $this->mEntryErrorType . 'box'], $this->mEntryError), 'raw' => true, 'rawrow' => true, 'weight' => -100];
     }
     if (!$this->showExtraInformation()) {
         unset($fieldDefinitions['linkcontainer'], $fieldDefinitions['signupend']);
     }
     if ($this->isSignup() && $this->showExtraInformation()) {
         // blank signup footer for site customization
         // uses signupend-https for HTTPS requests if it's not blank, signupend otherwise
         $signupendMsg = $this->msg('signupend');
         $signupendHttpsMsg = $this->msg('signupend-https');
         if (!$signupendMsg->isDisabled()) {
             $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
             $signupendText = $usingHTTPS && !$signupendHttpsMsg->isBlank() ? $signupendHttpsMsg->parse() : $signupendMsg->parse();
             $fieldDefinitions['signupend'] = ['type' => 'info', 'raw' => true, 'default' => Html::rawElement('div', ['id' => 'signupend'], $signupendText), 'weight' => 225];
         }
     }
     if (!$this->isSignup() && $this->showExtraInformation()) {
         $passwordReset = new PasswordReset($this->getConfig(), AuthManager::singleton());
         if ($passwordReset->isAllowed($this->getUser())->isGood()) {
             $fieldDefinitions['passwordReset'] = ['type' => 'info', 'raw' => true, 'cssclass' => 'mw-form-related-link-container', 'default' => Linker::link(SpecialPage::getTitleFor('PasswordReset'), $this->msg('userlogin-resetpassword-link')->escaped()), 'weight' => 230];
         }
         // Don't show a "create account" link if the user can't.
         if ($this->showCreateAccountLink()) {
             // link to the other action
             $linkTitle = $this->getTitleFor($this->isSignup() ? 'Userlogin' : 'CreateAccount');
             $linkq = $this->getReturnToQueryStringFragment();
             // Pass any language selection on to the mode switch link
             if ($wgLoginLanguageSelector && $this->mLanguage) {
                 $linkq .= '&uselang=' . $this->mLanguage;
             }
             $loggedIn = $this->getUser()->isLoggedIn();
             $fieldDefinitions['createOrLogin'] = ['type' => 'info', 'raw' => true, 'linkQuery' => $linkq, 'default' => function ($params) use($loggedIn, $linkTitle) {
                 return Html::rawElement('div', ['id' => 'mw-createaccount' . (!$loggedIn ? '-cta' : ''), 'class' => $loggedIn ? 'mw-form-related-link-container' : 'mw-ui-vform-field'], ($loggedIn ? '' : $this->msg('userlogin-noaccount')->escaped()) . Html::element('a', ['id' => 'mw-createaccount-join' . ($loggedIn ? '-loggedin' : ''), 'href' => $linkTitle->getLocalURL($params['linkQuery']), 'class' => $loggedIn ? '' : 'mw-ui-button', 'tabindex' => 100], $this->msg($loggedIn ? 'userlogin-createanother' : 'userlogin-joinproject')->escaped()));
             }, 'weight' => 235];
         }
     }
     $fieldDefinitions = $this->getBCFieldDefinitions($fieldDefinitions, $template);
     $fieldDefinitions = array_filter($fieldDefinitions);
     return $fieldDefinitions;
 }