3 final class PhabricatorPasswordAuthProvider
extends PhabricatorAuthProvider
{
7 public function getProviderName() {
8 return pht('Username/Password');
11 public function getConfigurationHelp() {
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);
28 'style' => 'color: #009900',
35 'style' => 'color: #990000',
37 pht('Not Installed'));
39 $best_hasher_name = null;
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
53 foreach ($hashers as $hasher) {
54 $is_installed = $hasher->canHashPasswords();
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())
68 $table = new AphrontTableView($rows);
69 $table->setRowClasses($rowc);
76 pht('Install Instructions'),
79 $table->setColumnClasses(
88 $header = id(new PHUIHeaderView())
89 ->setHeader(pht('Password Hash Algorithms'))
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())
101 public function getDescriptionForCreate() {
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() {
125 public function shouldAllowAccountUnlink() {
129 public function isDefaultRegistrationProvider() {
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())
146 ->addHiddenInput('invite', true)
148 id(new AphrontFormTextControl())
149 ->setLabel(pht('Username'))
150 ->setName('username'));
152 $dialog = id(new AphrontDialogView())
154 ->setTitle(pht('Register an Account'))
156 ->setSubmitURI('/auth/register/')
157 ->addSubmitButton(pht('Continue'));
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())
176 ->setTitle(pht('Log In'))
177 ->addSubmitButton(pht('Log In'));
179 if ($this->shouldAllowRegistration()) {
180 $dialog->addCancelButton(
182 pht('Register New Account'));
189 'href' => '/login/email/',
191 pht('Forgot your password?')));
194 $request->getStr('username'),
195 $request->getCookie(PhabricatorCookies
::COOKIE_USERNAME
));
202 if ($require_captcha && !$captcha_valid) {
203 if (AphrontFormRecaptchaControl
::hasCaptchaResponse($request)) {
204 $e_captcha = pht('Invalid');
205 $errors[] = pht('CAPTCHA was not entered correctly.');
207 $e_captcha = pht('Required');
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.');
221 $errors = id(new PHUIInfoView())->setErrors($errors);
224 $form = id(new PHUIFormLayoutView())
226 ->appendChild($errors)
228 id(new AphrontFormTextControl())
229 ->setLabel(pht('Username or Email'))
230 ->setName('username')
235 id(new AphrontFormPasswordControl())
236 ->setLabel(pht('Password'))
237 ->setName('password')
238 ->setError($e_pass));
240 if ($require_captcha) {
242 id(new AphrontFormRecaptchaControl())
243 ->setError($e_captcha));
246 $dialog->appendChild($form);
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(
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()) {
271 PhabricatorSystemActionEngine
::willTakeAction(
273 new PhabricatorAuthTryPasswordWithoutCAPTCHAAction(),
275 } catch (PhabricatorSystemActionRateLimitException
$ex) {
276 $require_captcha = true;
277 $captcha_valid = AphrontFormRecaptchaControl
::processCaptcha($request);
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(
294 $user = PhabricatorUser
::loadOneWithEmailAddress(
299 $envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
301 $engine = id(new PhabricatorAuthPasswordEngine())
303 ->setContentSource($content_source)
304 ->setPasswordType(PhabricatorAuthPassword
::PASSWORD_TYPE_ACCOUNT
)
307 if ($engine->isValidPassword($envelope)) {
308 $account = $this->newExternalAccountForUser($user);
317 if ($request->isFormPost()) {
318 $log = PhabricatorUserLog
::initializeNewLog(
320 $log_user ?
$log_user->getPHID() : null,
321 PhabricatorLoginFailureUserLogType
::LOGTYPE
);
325 $request->clearCookie(PhabricatorCookies
::COOKIE_USERNAME
);
327 $response = $controller->buildProviderPageResponse(
329 $this->renderPasswordLoginForm(
335 return array($account, $response);
338 public function shouldRequireRegistrationPassword() {
342 public static function getPasswordProvider() {
343 $providers = self
::getAllEnabledProviders();
345 foreach ($providers as $provider) {
346 if ($provider instanceof PhabricatorPasswordAuthProvider
) {
354 public function willRenderLinkedAccount(
355 PhabricatorUser
$viewer,
356 PHUIObjectItemView
$item,
357 PhabricatorExternalAccount
$account) {
361 public function shouldAllowAccountRefresh() {
365 public function shouldAllowEmailTrustConfiguration() {