Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / auth / provider / PhabricatorPasswordAuthProvider.php
blob4513787d89c3294832e13a457a8b4607bcdbd91e
1 <?php
3 final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
5 private $adapter;
7 public function getProviderName() {
8 return pht('Username/Password');
11 public function getConfigurationHelp() {
12 return pht(
13 "(WARNING) Examine the table below for information on how password ".
14 "hashes will be stored in the database.\n\n".
15 "(NOTE) You can select a minimum password length by setting ".
16 "`%s` in configuration.",
17 'account.minimum-password-length');
20 public function renderConfigurationFooter() {
21 $hashers = PhabricatorPasswordHasher::getAllHashers();
22 $hashers = msort($hashers, 'getStrength');
23 $hashers = array_reverse($hashers);
25 $yes = phutil_tag(
26 'strong',
27 array(
28 'style' => 'color: #009900',
30 pht('Yes'));
32 $no = phutil_tag(
33 'strong',
34 array(
35 'style' => 'color: #990000',
37 pht('Not Installed'));
39 $best_hasher_name = null;
40 try {
41 $best_hasher = PhabricatorPasswordHasher::getBestHasher();
42 $best_hasher_name = $best_hasher->getHashName();
43 } catch (PhabricatorPasswordHasherUnavailableException $ex) {
44 // There are no suitable hashers. The user might be able to enable some,
45 // so we don't want to fatal here. We'll fatal when users try to actually
46 // use this stuff if it isn't fixed before then. Until then, we just
47 // don't highlight a row. In practice, at least one hasher should always
48 // be available.
51 $rows = array();
52 $rowc = array();
53 foreach ($hashers as $hasher) {
54 $is_installed = $hasher->canHashPasswords();
56 $rows[] = array(
57 $hasher->getHumanReadableName(),
58 $hasher->getHashName(),
59 $hasher->getHumanReadableStrength(),
60 ($is_installed ? $yes : $no),
61 ($is_installed ? null : $hasher->getInstallInstructions()),
63 $rowc[] = ($best_hasher_name == $hasher->getHashName())
64 ? 'highlighted'
65 : null;
68 $table = new AphrontTableView($rows);
69 $table->setRowClasses($rowc);
70 $table->setHeaders(
71 array(
72 pht('Algorithm'),
73 pht('Name'),
74 pht('Strength'),
75 pht('Installed'),
76 pht('Install Instructions'),
77 ));
79 $table->setColumnClasses(
80 array(
81 '',
82 '',
83 '',
84 '',
85 'wide',
86 ));
88 $header = id(new PHUIHeaderView())
89 ->setHeader(pht('Password Hash Algorithms'))
90 ->setSubheader(
91 pht(
92 'Stronger algorithms are listed first. The highlighted algorithm '.
93 'will be used when storing new hashes. Older hashes will be '.
94 'upgraded to the best algorithm over time.'));
96 return id(new PHUIObjectBoxView())
97 ->setHeader($header)
98 ->setTable($table);
101 public function getDescriptionForCreate() {
102 return pht(
103 'Allow users to log in or register using a username and password.');
106 public function getAdapter() {
107 if (!$this->adapter) {
108 $adapter = new PhutilEmptyAuthAdapter();
109 $adapter->setAdapterType('password');
110 $adapter->setAdapterDomain('self');
111 $this->adapter = $adapter;
113 return $this->adapter;
116 public function getLoginOrder() {
117 // Make sure username/password appears first if it is enabled.
118 return '100-'.$this->getProviderName();
121 public function shouldAllowAccountLink() {
122 return false;
125 public function shouldAllowAccountUnlink() {
126 return false;
129 public function isDefaultRegistrationProvider() {
130 return true;
133 public function buildLoginForm(
134 PhabricatorAuthStartController $controller) {
135 $request = $controller->getRequest();
136 return $this->renderPasswordLoginForm($request);
139 public function buildInviteForm(
140 PhabricatorAuthStartController $controller) {
141 $request = $controller->getRequest();
142 $viewer = $request->getViewer();
144 $form = id(new AphrontFormView())
145 ->setUser($viewer)
146 ->addHiddenInput('invite', true)
147 ->appendChild(
148 id(new AphrontFormTextControl())
149 ->setLabel(pht('Username'))
150 ->setName('username'));
152 $dialog = id(new AphrontDialogView())
153 ->setUser($viewer)
154 ->setTitle(pht('Register an Account'))
155 ->appendForm($form)
156 ->setSubmitURI('/auth/register/')
157 ->addSubmitButton(pht('Continue'));
159 return $dialog;
162 public function buildLinkForm($controller) {
163 throw new Exception(pht("Password providers can't be linked."));
166 private function renderPasswordLoginForm(
167 AphrontRequest $request,
168 $require_captcha = false,
169 $captcha_valid = false) {
171 $viewer = $request->getUser();
173 $dialog = id(new AphrontDialogView())
174 ->setSubmitURI($this->getLoginURI())
175 ->setUser($viewer)
176 ->setTitle(pht('Log In'))
177 ->addSubmitButton(pht('Log In'));
179 if ($this->shouldAllowRegistration()) {
180 $dialog->addCancelButton(
181 '/auth/register/',
182 pht('Register New Account'));
185 $dialog->addFooter(
186 phutil_tag(
187 'a',
188 array(
189 'href' => '/login/email/',
191 pht('Forgot your password?')));
193 $v_user = nonempty(
194 $request->getStr('username'),
195 $request->getCookie(PhabricatorCookies::COOKIE_USERNAME));
197 $e_user = null;
198 $e_pass = null;
199 $e_captcha = null;
201 $errors = array();
202 if ($require_captcha && !$captcha_valid) {
203 if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) {
204 $e_captcha = pht('Invalid');
205 $errors[] = pht('CAPTCHA was not entered correctly.');
206 } else {
207 $e_captcha = pht('Required');
208 $errors[] = pht(
209 'Too many login failures recently. You must '.
210 'submit a CAPTCHA with your login request.');
212 } else if ($request->isHTTPPost()) {
213 // NOTE: This is intentionally vague so as not to disclose whether a
214 // given username or email is registered.
215 $e_user = pht('Invalid');
216 $e_pass = pht('Invalid');
217 $errors[] = pht('Username or password are incorrect.');
220 if ($errors) {
221 $errors = id(new PHUIInfoView())->setErrors($errors);
224 $form = id(new PHUIFormLayoutView())
225 ->setFullWidth(true)
226 ->appendChild($errors)
227 ->appendChild(
228 id(new AphrontFormTextControl())
229 ->setLabel(pht('Username or Email'))
230 ->setName('username')
231 ->setAutofocus(true)
232 ->setValue($v_user)
233 ->setError($e_user))
234 ->appendChild(
235 id(new AphrontFormPasswordControl())
236 ->setLabel(pht('Password'))
237 ->setName('password')
238 ->setError($e_pass));
240 if ($require_captcha) {
241 $form->appendChild(
242 id(new AphrontFormRecaptchaControl())
243 ->setError($e_captcha));
246 $dialog->appendChild($form);
248 return $dialog;
251 public function processLoginRequest(
252 PhabricatorAuthLoginController $controller) {
254 $request = $controller->getRequest();
255 $viewer = $request->getUser();
256 $content_source = PhabricatorContentSource::newFromRequest($request);
258 $rate_actor = PhabricatorSystemActionEngine::newActorFromRequest($request);
260 PhabricatorSystemActionEngine::willTakeAction(
261 array($rate_actor),
262 new PhabricatorAuthTryPasswordAction(),
265 // If the same remote address has submitted several failed login attempts
266 // recently, require they provide a CAPTCHA response for new attempts.
267 $require_captcha = false;
268 $captcha_valid = false;
269 if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) {
270 try {
271 PhabricatorSystemActionEngine::willTakeAction(
272 array($rate_actor),
273 new PhabricatorAuthTryPasswordWithoutCAPTCHAAction(),
275 } catch (PhabricatorSystemActionRateLimitException $ex) {
276 $require_captcha = true;
277 $captcha_valid = AphrontFormRecaptchaControl::processCaptcha($request);
281 $response = null;
282 $account = null;
283 $log_user = null;
285 if ($request->isFormPost()) {
286 if (!$require_captcha || $captcha_valid) {
287 $username_or_email = $request->getStr('username');
288 if (strlen($username_or_email)) {
289 $user = id(new PhabricatorUser())->loadOneWhere(
290 'username = %s',
291 $username_or_email);
293 if (!$user) {
294 $user = PhabricatorUser::loadOneWithEmailAddress(
295 $username_or_email);
298 if ($user) {
299 $envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
301 $engine = id(new PhabricatorAuthPasswordEngine())
302 ->setViewer($user)
303 ->setContentSource($content_source)
304 ->setPasswordType(PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT)
305 ->setObject($user);
307 if ($engine->isValidPassword($envelope)) {
308 $account = $this->newExternalAccountForUser($user);
309 $log_user = $user;
316 if (!$account) {
317 if ($request->isFormPost()) {
318 $log = PhabricatorUserLog::initializeNewLog(
319 null,
320 $log_user ? $log_user->getPHID() : null,
321 PhabricatorLoginFailureUserLogType::LOGTYPE);
322 $log->save();
325 $request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);
327 $response = $controller->buildProviderPageResponse(
328 $this,
329 $this->renderPasswordLoginForm(
330 $request,
331 $require_captcha,
332 $captcha_valid));
335 return array($account, $response);
338 public function shouldRequireRegistrationPassword() {
339 return true;
342 public static function getPasswordProvider() {
343 $providers = self::getAllEnabledProviders();
345 foreach ($providers as $provider) {
346 if ($provider instanceof PhabricatorPasswordAuthProvider) {
347 return $provider;
351 return null;
354 public function willRenderLinkedAccount(
355 PhabricatorUser $viewer,
356 PHUIObjectItemView $item,
357 PhabricatorExternalAccount $account) {
358 return;
361 public function shouldAllowAccountRefresh() {
362 return false;
365 public function shouldAllowEmailTrustConfiguration() {
366 return false;