Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / settings / panel / PhabricatorPasswordSettingsPanel.php
blob9980d1d3558bc6571cc274d1a95aeeecd1f295c6
1 <?php
3 final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel {
5 public function getPanelKey() {
6 return 'password';
9 public function getPanelName() {
10 return pht('Password');
13 public function getPanelMenuIcon() {
14 return 'fa-key';
17 public function getPanelGroupKey() {
18 return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY;
21 public function isEnabled() {
22 // There's no sense in showing a change password panel if this install
23 // doesn't support password authentication.
24 if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
25 return false;
28 return true;
31 public function processRequest(AphrontRequest $request) {
32 $viewer = $request->getUser();
33 $user = $this->getUser();
35 $content_source = PhabricatorContentSource::newFromRequest($request);
37 $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length');
38 $min_len = (int)$min_len;
40 // NOTE: Users can also change passwords through the separate "set/reset"
41 // interface which is reached by logging in with a one-time token after
42 // registration or password reset. If this flow changes, that flow may
43 // also need to change.
45 $account_type = PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT;
47 $password_objects = id(new PhabricatorAuthPasswordQuery())
48 ->setViewer($viewer)
49 ->withObjectPHIDs(array($user->getPHID()))
50 ->withPasswordTypes(array($account_type))
51 ->withIsRevoked(false)
52 ->execute();
53 if (!$password_objects) {
54 return $this->newSetPasswordView($request);
56 $password_object = head($password_objects);
58 $e_old = true;
59 $e_new = true;
60 $e_conf = true;
62 $errors = array();
63 if ($request->isFormOrHisecPost()) {
64 $workflow_key = sprintf(
65 'password.change(%s)',
66 $user->getPHID());
68 $hisec_token = id(new PhabricatorAuthSessionEngine())
69 ->setWorkflowKey($workflow_key)
70 ->requireHighSecurityToken($viewer, $request, '/settings/');
72 // Rate limit guesses about the old password. This page requires MFA and
73 // session compromise already, so this is mostly just to stop researchers
74 // from reporting this as a vulnerability.
75 PhabricatorSystemActionEngine::willTakeAction(
76 array($viewer->getPHID()),
77 new PhabricatorAuthChangePasswordAction(),
78 1);
80 $envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw'));
82 $engine = id(new PhabricatorAuthPasswordEngine())
83 ->setViewer($viewer)
84 ->setContentSource($content_source)
85 ->setPasswordType($account_type)
86 ->setObject($user);
88 if (!strlen($envelope->openEnvelope())) {
89 $errors[] = pht('You must enter your current password.');
90 $e_old = pht('Required');
91 } else if (!$engine->isValidPassword($envelope)) {
92 $errors[] = pht('The old password you entered is incorrect.');
93 $e_old = pht('Invalid');
94 } else {
95 $e_old = null;
97 // Refund the user an action credit for getting the password right.
98 PhabricatorSystemActionEngine::willTakeAction(
99 array($viewer->getPHID()),
100 new PhabricatorAuthChangePasswordAction(),
101 -1);
104 $pass = $request->getStr('new_pw');
105 $conf = $request->getStr('conf_pw');
106 $password_envelope = new PhutilOpaqueEnvelope($pass);
107 $confirm_envelope = new PhutilOpaqueEnvelope($conf);
109 try {
110 $engine->checkNewPassword($password_envelope, $confirm_envelope);
111 $e_new = null;
112 $e_conf = null;
113 } catch (PhabricatorAuthPasswordException $ex) {
114 $errors[] = $ex->getMessage();
115 $e_new = $ex->getPasswordError();
116 $e_conf = $ex->getConfirmError();
119 if (!$errors) {
120 $password_object
121 ->setPassword($password_envelope, $user)
122 ->save();
124 $next = $this->getPanelURI('?saved=true');
126 id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
127 $user,
128 new PhutilOpaqueEnvelope(
129 $request->getCookie(PhabricatorCookies::COOKIE_SESSION)));
131 return id(new AphrontRedirectResponse())->setURI($next);
135 if ($password_object->getID()) {
136 try {
137 $can_upgrade = $password_object->canUpgrade();
138 } catch (PhabricatorPasswordHasherUnavailableException $ex) {
139 $can_upgrade = false;
141 $errors[] = pht(
142 'Your password is currently hashed using an algorithm which is '.
143 'no longer available on this install.');
144 $errors[] = pht(
145 'Because the algorithm implementation is missing, your password '.
146 'can not be used or updated.');
147 $errors[] = pht(
148 'To set a new password, request a password reset link from the '.
149 'login screen and then follow the instructions.');
152 if ($can_upgrade) {
153 $errors[] = pht(
154 'The strength of your stored password hash can be upgraded. '.
155 'To upgrade, either: log out and log in using your password; or '.
156 'change your password.');
160 $len_caption = null;
161 if ($min_len) {
162 $len_caption = pht('Minimum password length: %d characters.', $min_len);
165 $form = id(new AphrontFormView())
166 ->setViewer($viewer)
167 ->appendChild(
168 id(new AphrontFormPasswordControl())
169 ->setLabel(pht('Old Password'))
170 ->setError($e_old)
171 ->setName('old_pw'))
172 ->appendChild(
173 id(new AphrontFormPasswordControl())
174 ->setDisableAutocomplete(true)
175 ->setLabel(pht('New Password'))
176 ->setError($e_new)
177 ->setName('new_pw'))
178 ->appendChild(
179 id(new AphrontFormPasswordControl())
180 ->setDisableAutocomplete(true)
181 ->setLabel(pht('Confirm Password'))
182 ->setCaption($len_caption)
183 ->setError($e_conf)
184 ->setName('conf_pw'))
185 ->appendChild(
186 id(new AphrontFormSubmitControl())
187 ->setValue(pht('Change Password')));
189 $properties = id(new PHUIPropertyListView());
191 $properties->addProperty(
192 pht('Current Algorithm'),
193 PhabricatorPasswordHasher::getCurrentAlgorithmName(
194 $password_object->newPasswordEnvelope()));
196 $properties->addProperty(
197 pht('Best Available Algorithm'),
198 PhabricatorPasswordHasher::getBestAlgorithmName());
200 $info_view = id(new PHUIInfoView())
201 ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
202 ->appendChild(
203 pht('Changing your password will terminate any other outstanding '.
204 'login sessions.'));
206 $algo_box = $this->newBox(pht('Password Algorithms'), $properties);
207 $form_box = id(new PHUIObjectBoxView())
208 ->setHeaderText(pht('Change Password'))
209 ->setFormErrors($errors)
210 ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
211 ->setForm($form);
213 return array(
214 $form_box,
215 $algo_box,
216 $info_view,
220 private function newSetPasswordView(AphrontRequest $request) {
221 $viewer = $request->getUser();
222 $user = $this->getUser();
224 $form = id(new AphrontFormView())
225 ->setViewer($viewer)
226 ->appendRemarkupInstructions(
227 pht(
228 'Your account does not currently have a password set. You can '.
229 'choose a password by performing a password reset.'))
230 ->appendControl(
231 id(new AphrontFormSubmitControl())
232 ->addCancelButton('/login/email/', pht('Reset Password')));
234 $form_box = id(new PHUIObjectBoxView())
235 ->setHeaderText(pht('Set Password'))
236 ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
237 ->setForm($form);
239 return $form_box;