mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-29 18:22:41 +01:00
Always allow users to login via email link, even if an install does not use passwords
Summary: Depends on D20099. Ref T13244. See PHI774. When password auth is enabled, we support a standard email-based account recovery mechanism with "Forgot password?". When password auth is not enabled, we disable the self-serve version of this mechanism. You can still get email account login links via "Send Welcome Mail" or "bin/auth recover". There's no real technical, product, or security reason not to let everyone do email login all the time. On the technical front, these links already work and are used in other contexts. On the product front, we just need to tweak a couple of strings. On the security front, there's some argument that this mechanism provides more overall surface area for an attacker, but if we find that argument compelling we should probably provide a way to disable the self-serve pathway in all cases, rather than coupling it to which providers are enabled. Also, inch toward having things iterate over configurations (saved database objects) instead of providers (abstract implementations) so we can some day live in a world where we support multiple configurations of the same provider type (T6703). Test Plan: - With password auth enabled, reset password. - Without password auth enabled, did an email login recovery. {F6184910} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13244 Differential Revision: https://secure.phabricator.com/D20100
This commit is contained in:
parent
99e5ef84fc
commit
9632c704c6
2 changed files with 127 additions and 62 deletions
|
@ -75,6 +75,11 @@ final class PhabricatorAuthStartController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$configs = array();
|
||||||
|
foreach ($providers as $provider) {
|
||||||
|
$configs[] = $provider->getProviderConfig();
|
||||||
|
}
|
||||||
|
|
||||||
if (!$providers) {
|
if (!$providers) {
|
||||||
if ($this->isFirstTimeSetup()) {
|
if ($this->isFirstTimeSetup()) {
|
||||||
// If this is a fresh install, let the user register their admin
|
// If this is a fresh install, let the user register their admin
|
||||||
|
@ -179,6 +184,8 @@ final class PhabricatorAuthStartController
|
||||||
|
|
||||||
$custom_message = $this->newCustomStartMessage();
|
$custom_message = $this->newCustomStartMessage();
|
||||||
|
|
||||||
|
$email_login = $this->newEmailLoginView($configs);
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
$crumbs->addTextCrumb(pht('Login'));
|
$crumbs->addTextCrumb(pht('Login'));
|
||||||
$crumbs->setBorder(true);
|
$crumbs->setBorder(true);
|
||||||
|
@ -188,6 +195,7 @@ final class PhabricatorAuthStartController
|
||||||
$invite_message,
|
$invite_message,
|
||||||
$custom_message,
|
$custom_message,
|
||||||
$out,
|
$out,
|
||||||
|
$email_login,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->newPage()
|
return $this->newPage()
|
||||||
|
@ -311,4 +319,43 @@ final class PhabricatorAuthStartController
|
||||||
$remarkup_view);
|
$remarkup_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function newEmailLoginView(array $configs) {
|
||||||
|
assert_instances_of($configs, 'PhabricatorAuthProviderConfig');
|
||||||
|
|
||||||
|
// Check if password auth is enabled. If it is, the password login form
|
||||||
|
// renders a "Forgot password?" link, so we don't need to provide a
|
||||||
|
// supplemental link.
|
||||||
|
|
||||||
|
$has_password = false;
|
||||||
|
foreach ($configs as $config) {
|
||||||
|
$provider = $config->getProvider();
|
||||||
|
if ($provider instanceof PhabricatorPasswordAuthProvider) {
|
||||||
|
$has_password = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($has_password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$view = array(
|
||||||
|
pht('Trouble logging in?'),
|
||||||
|
' ',
|
||||||
|
phutil_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => '/login/email/',
|
||||||
|
),
|
||||||
|
pht('Send a login link to your email address.')),
|
||||||
|
);
|
||||||
|
|
||||||
|
return phutil_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'class' => 'auth-custom-message',
|
||||||
|
),
|
||||||
|
$view);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,13 @@ final class PhabricatorEmailLoginController
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
|
|
||||||
return new Aphront400Response();
|
|
||||||
}
|
|
||||||
|
|
||||||
$e_email = true;
|
$e_email = true;
|
||||||
$e_captcha = true;
|
$e_captcha = true;
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
|
||||||
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
|
$v_email = $request->getStr('email');
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
$e_email = null;
|
$e_email = null;
|
||||||
$e_captcha = pht('Again');
|
$e_captcha = pht('Again');
|
||||||
|
@ -29,8 +25,7 @@ final class PhabricatorEmailLoginController
|
||||||
$e_captcha = pht('Invalid');
|
$e_captcha = pht('Invalid');
|
||||||
}
|
}
|
||||||
|
|
||||||
$email = $request->getStr('email');
|
if (!strlen($v_email)) {
|
||||||
if (!strlen($email)) {
|
|
||||||
$errors[] = pht('You must provide an email address.');
|
$errors[] = pht('You must provide an email address.');
|
||||||
$e_email = pht('Required');
|
$e_email = pht('Required');
|
||||||
}
|
}
|
||||||
|
@ -42,7 +37,7 @@ final class PhabricatorEmailLoginController
|
||||||
|
|
||||||
$target_email = id(new PhabricatorUserEmail())->loadOneWhere(
|
$target_email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
'address = %s',
|
'address = %s',
|
||||||
$email);
|
$v_email);
|
||||||
|
|
||||||
$target_user = null;
|
$target_user = null;
|
||||||
if ($target_email) {
|
if ($target_email) {
|
||||||
|
@ -81,12 +76,78 @@ final class PhabricatorEmailLoginController
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
|
$body = $this->newAccountLoginMailBody($target_user);
|
||||||
|
|
||||||
|
$mail = id(new PhabricatorMetaMTAMail())
|
||||||
|
->setSubject(pht('[Phabricator] Account Login Link'))
|
||||||
|
->setForceDelivery(true)
|
||||||
|
->addRawTos(array($target_email->getAddress()))
|
||||||
|
->setBody($body)
|
||||||
|
->saveAndSend();
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Check Your Email'))
|
||||||
|
->setShortTitle(pht('Email Sent'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht('An email has been sent with a link you can use to log in.'))
|
||||||
|
->addCancelButton('/', pht('Done'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setViewer($viewer);
|
||||||
|
|
||||||
|
if ($this->isPasswordAuthEnabled()) {
|
||||||
|
$form->appendRemarkupInstructions(
|
||||||
|
pht(
|
||||||
|
'To reset your password, provide your email address. An email '.
|
||||||
|
'with a login link will be sent to you.'));
|
||||||
|
} else {
|
||||||
|
$form->appendRemarkupInstructions(
|
||||||
|
pht(
|
||||||
|
'To access your account, provide your email address. An email '.
|
||||||
|
'with a login link will be sent to you.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$form
|
||||||
|
->appendControl(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel(pht('Email Address'))
|
||||||
|
->setName('email')
|
||||||
|
->setValue($v_email)
|
||||||
|
->setError($e_email))
|
||||||
|
->appendControl(
|
||||||
|
id(new AphrontFormRecaptchaControl())
|
||||||
|
->setLabel(pht('Captcha'))
|
||||||
|
->setError($e_captcha));
|
||||||
|
|
||||||
|
if ($this->isPasswordAuthEnabled()) {
|
||||||
|
$title = pht('Password Reset');
|
||||||
|
} else {
|
||||||
|
$title = pht('Email Login');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle($title)
|
||||||
|
->setErrors($errors)
|
||||||
|
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||||
|
->appendForm($form)
|
||||||
|
->addCancelButton('/auth/start/')
|
||||||
|
->addSubmitButton(pht('Send Email'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newAccountLoginMailBody(PhabricatorUser $user) {
|
||||||
$engine = new PhabricatorAuthSessionEngine();
|
$engine = new PhabricatorAuthSessionEngine();
|
||||||
$uri = $engine->getOneTimeLoginURI(
|
$uri = $engine->getOneTimeLoginURI(
|
||||||
$target_user,
|
$user,
|
||||||
null,
|
null,
|
||||||
PhabricatorAuthSessionEngine::ONETIME_RESET);
|
PhabricatorAuthSessionEngine::ONETIME_RESET);
|
||||||
|
|
||||||
|
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
|
||||||
|
$have_passwords = $this->isPasswordAuthEnabled();
|
||||||
|
|
||||||
|
if ($have_passwords) {
|
||||||
if ($is_serious) {
|
if ($is_serious) {
|
||||||
$body = pht(
|
$body = pht(
|
||||||
"You can use this link to reset your Phabricator password:".
|
"You can use this link to reset your Phabricator password:".
|
||||||
|
@ -105,62 +166,19 @@ final class PhabricatorEmailLoginController
|
||||||
$uri);
|
$uri);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
$mail = id(new PhabricatorMetaMTAMail())
|
$body = pht(
|
||||||
->setSubject(pht('[Phabricator] Password Reset'))
|
"You can use this login link to regain access to your Phabricator ".
|
||||||
->setForceDelivery(true)
|
"account:".
|
||||||
->addRawTos(array($target_email->getAddress()))
|
"\n\n".
|
||||||
->setBody($body)
|
" %s\n",
|
||||||
->saveAndSend();
|
$uri);
|
||||||
|
|
||||||
return $this->newDialog()
|
|
||||||
->setTitle(pht('Check Your Email'))
|
|
||||||
->setShortTitle(pht('Email Sent'))
|
|
||||||
->appendParagraph(
|
|
||||||
pht('An email has been sent with a link you can use to log in.'))
|
|
||||||
->addCancelButton('/', pht('Done'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$error_view = null;
|
return $body;
|
||||||
if ($errors) {
|
|
||||||
$error_view = new PHUIInfoView();
|
|
||||||
$error_view->setErrors($errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$email_auth = new PHUIFormLayoutView();
|
private function isPasswordAuthEnabled() {
|
||||||
$email_auth->appendChild($error_view);
|
return (bool)PhabricatorPasswordAuthProvider::getPasswordProvider();
|
||||||
$email_auth
|
|
||||||
->setUser($request->getUser())
|
|
||||||
->setFullWidth(true)
|
|
||||||
->appendChild(
|
|
||||||
id(new AphrontFormTextControl())
|
|
||||||
->setLabel(pht('Email'))
|
|
||||||
->setName('email')
|
|
||||||
->setValue($request->getStr('email'))
|
|
||||||
->setError($e_email))
|
|
||||||
->appendChild(
|
|
||||||
id(new AphrontFormRecaptchaControl())
|
|
||||||
->setLabel(pht('Captcha'))
|
|
||||||
->setError($e_captcha));
|
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
|
||||||
$crumbs->addTextCrumb(pht('Reset Password'));
|
|
||||||
$crumbs->setBorder(true);
|
|
||||||
|
|
||||||
$dialog = new AphrontDialogView();
|
|
||||||
$dialog->setUser($request->getUser());
|
|
||||||
$dialog->setTitle(pht('Forgot Password / Email Login'));
|
|
||||||
$dialog->appendChild($email_auth);
|
|
||||||
$dialog->addSubmitButton(pht('Send Email'));
|
|
||||||
$dialog->setSubmitURI('/login/email/');
|
|
||||||
|
|
||||||
return $this->newPage()
|
|
||||||
->setTitle(pht('Forgot Password'))
|
|
||||||
->setCrumbs($crumbs)
|
|
||||||
->appendChild($dialog);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue