1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

Begin adding more guidance to the "One-Time Login" flow

Summary:
Ref T13244. See PHI774. If an install does not use password auth, the "one-time login" flow (via "Welcome" email or "bin/auth recover") is pretty rough. Current behavior:

  - If an install uses passwords, the user is prompted to set a password.
  - If an install does not use passwords, you're dumped to `/settings/external/` to link an external account. This is pretty sketchy and this UI does not make it clear what users are expected to do (link an account) or why (so they can log in).

Instead, improve this flow:

  - Password reset flow is fine.
  - (Future Change) If there are external linkable accounts (like Google) and the user doesn't have any linked, I want to give users a flow like a password reset flow that says "link to an external account".
  - (This Change) If you're an administrator and there are no providers at all, go to "/auth/" so you can set something up.
  - (This Change) If we don't hit on any other rules, just go home?

This may be tweaked a bit as we go, but basically I want to refine the "/settings/external/" case into a more useful flow which gives users more of a chance of surviving it.

Test Plan: Logged in with passwords enabled (got password reset), with nothing enabled as an admin (got sent to Auth), and with something other than passwords enabled (got sent home).

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13244

Differential Revision: https://secure.phabricator.com/D20094
This commit is contained in:
epriestley 2019-02-05 05:22:39 -08:00
parent 03eb989fd8
commit 6f3bd13cf5
2 changed files with 72 additions and 74 deletions

View file

@ -119,38 +119,9 @@ final class PhabricatorAuthOneTimeLoginController
} }
unset($unguarded); unset($unguarded);
$next = '/'; $next_uri = $this->getNextStepURI($target_user);
if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
$next = '/settings/panel/external/';
} else {
// We're going to let the user reset their password without knowing PhabricatorCookies::setNextURICookie($request, $next_uri, $force = true);
// the old one. Generate a one-time token for that.
$key = Filesystem::readRandomCharacters(16);
$password_type =
PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE;
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
id(new PhabricatorAuthTemporaryToken())
->setTokenResource($target_user->getPHID())
->setTokenType($password_type)
->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode(PhabricatorHash::weakDigest($key))
->save();
unset($unguarded);
$panel_uri = '/auth/password/';
$next = (string)id(new PhutilURI($panel_uri))
->setQueryParams(
array(
'key' => $key,
));
$request->setTemporaryCookie(PhabricatorCookies::COOKIE_HISEC, 'yes');
}
PhabricatorCookies::setNextURICookie($request, $next, $force = true);
$force_full_session = false; $force_full_session = false;
if ($link_type === PhabricatorAuthSessionEngine::ONETIME_RECOVER) { if ($link_type === PhabricatorAuthSessionEngine::ONETIME_RECOVER) {
@ -206,4 +177,57 @@ final class PhabricatorAuthOneTimeLoginController
return id(new AphrontDialogResponse())->setDialog($dialog); return id(new AphrontDialogResponse())->setDialog($dialog);
} }
private function getNextStepURI(PhabricatorUser $user) {
$request = $this->getRequest();
// If we have password auth, let the user set or reset their password after
// login.
$have_passwords = PhabricatorPasswordAuthProvider::getPasswordProvider();
if ($have_passwords) {
// We're going to let the user reset their password without knowing
// the old one. Generate a one-time token for that.
$key = Filesystem::readRandomCharacters(16);
$password_type =
PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE;
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
id(new PhabricatorAuthTemporaryToken())
->setTokenResource($user->getPHID())
->setTokenType($password_type)
->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode(PhabricatorHash::weakDigest($key))
->save();
unset($unguarded);
$panel_uri = '/auth/password/';
$request->setTemporaryCookie(PhabricatorCookies::COOKIE_HISEC, 'yes');
return (string)id(new PhutilURI($panel_uri))
->setQueryParams(
array(
'key' => $key,
));
}
$providers = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($user)
->withIsEnabled(true)
->execute();
// If there are no configured providers and the user is an administrator,
// send them to Auth to configure a provider. This is probably where they
// want to go. You can end up in this state by accidentally losing your
// first session during initial setup, or after restoring exported data
// from a hosted instance.
if (!$providers && $user->getIsAdmin()) {
return '/auth/';
}
// If we didn't find anywhere better to send them, give up and just send
// them to the home page.
return '/';
}
} }

View file

@ -6,11 +6,7 @@ final class PhabricatorAuthProviderConfigQuery
private $ids; private $ids;
private $phids; private $phids;
private $providerClasses; private $providerClasses;
private $isEnabled;
const STATUS_ALL = 'status:all';
const STATUS_ENABLED = 'status:enabled';
private $status = self::STATUS_ALL;
public function withPHIDs(array $phids) { public function withPHIDs(array $phids) {
$this->phids = $phids; $this->phids = $phids;
@ -22,40 +18,26 @@ final class PhabricatorAuthProviderConfigQuery
return $this; return $this;
} }
public function withStatus($status) {
$this->status = $status;
return $this;
}
public function withProviderClasses(array $classes) { public function withProviderClasses(array $classes) {
$this->providerClasses = $classes; $this->providerClasses = $classes;
return $this; return $this;
} }
public static function getStatusOptions() { public function withIsEnabled($is_enabled) {
return array( $this->isEnabled = $is_enabled;
self::STATUS_ALL => pht('All Providers'), return $this;
self::STATUS_ENABLED => pht('Enabled Providers'), }
);
public function newResultObject() {
return new PhabricatorAuthProviderConfig();
} }
protected function loadPage() { protected function loadPage() {
$table = new PhabricatorAuthProviderConfig(); return $this->loadStandardPage($this->newResultObject());
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
} }
protected function buildWhereClause(AphrontDatabaseConnection $conn) { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = array(); $where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) { if ($this->ids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
@ -78,22 +60,14 @@ final class PhabricatorAuthProviderConfigQuery
$this->providerClasses); $this->providerClasses);
} }
$status = $this->status; if ($this->isEnabled !== null) {
switch ($status) { $where[] = qsprintf(
case self::STATUS_ALL: $conn,
break; 'isEnabled = %d',
case self::STATUS_ENABLED: (int)$this->isEnabled);
$where[] = qsprintf(
$conn,
'isEnabled = 1');
break;
default:
throw new Exception(pht("Unknown status '%s'!", $status));
} }
$where[] = $this->buildPagingClause($conn); return $where;
return $this->formatWhereClause($conn, $where);
} }
public function getQueryApplicationClass() { public function getQueryApplicationClass() {