mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-29 02:02:41 +01:00
Make external link/refresh use provider IDs, switch external account MFA to one-shot
Summary: Depends on D20113. Ref T6703. Continue moving toward a future where multiple copies of a given type of provider may exist. Switch MFA from session-MFA at the start to one-shot MFA at the actual link action. Add one-shot MFA to the unlink action. This theoretically prevents an attacker from unlinking an account while you're getting coffee, registering `alIce` which they control, adding a copy of your profile picture, and then trying to trick you into writing a private note with your personal secrets or something. Test Plan: Linked and unlinked accounts. Refreshed account. Unlinked, then registered a new account. Unlinked, then relinked to my old account. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T6703 Differential Revision: https://secure.phabricator.com/D20117
This commit is contained in:
parent
e5ee656fff
commit
d22495a820
8 changed files with 76 additions and 60 deletions
|
@ -62,7 +62,7 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
|
||||||
'validate/' => 'PhabricatorAuthValidateController',
|
'validate/' => 'PhabricatorAuthValidateController',
|
||||||
'finish/' => 'PhabricatorAuthFinishController',
|
'finish/' => 'PhabricatorAuthFinishController',
|
||||||
'unlink/(?P<id>\d+)/' => 'PhabricatorAuthUnlinkController',
|
'unlink/(?P<id>\d+)/' => 'PhabricatorAuthUnlinkController',
|
||||||
'(?P<action>link|refresh)/(?P<pkey>[^/]+)/'
|
'(?P<action>link|refresh)/(?P<id>\d+)/'
|
||||||
=> 'PhabricatorAuthLinkController',
|
=> 'PhabricatorAuthLinkController',
|
||||||
'confirmlink/(?P<akey>[^/]+)/'
|
'confirmlink/(?P<akey>[^/]+)/'
|
||||||
=> 'PhabricatorAuthConfirmLinkController',
|
=> 'PhabricatorAuthConfirmLinkController',
|
||||||
|
|
|
@ -20,7 +20,15 @@ final class PhabricatorAuthConfirmLinkController
|
||||||
|
|
||||||
$panel_uri = '/settings/panel/external/';
|
$panel_uri = '/settings/panel/external/';
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormOrHisecPost()) {
|
||||||
|
$workflow_key = sprintf(
|
||||||
|
'account.link(%s)',
|
||||||
|
$account->getPHID());
|
||||||
|
|
||||||
|
$hisec_token = id(new PhabricatorAuthSessionEngine())
|
||||||
|
->setWorkflowKey($workflow_key)
|
||||||
|
->requireHighSecurityToken($viewer, $request, $panel_uri);
|
||||||
|
|
||||||
$account->setUserPHID($viewer->getPHID());
|
$account->setUserPHID($viewer->getPHID());
|
||||||
$account->save();
|
$account->save();
|
||||||
|
|
||||||
|
@ -31,14 +39,7 @@ final class PhabricatorAuthConfirmLinkController
|
||||||
return id(new AphrontRedirectResponse())->setURI($panel_uri);
|
return id(new AphrontRedirectResponse())->setURI($panel_uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Provide more information about the external account. Clicking
|
$dialog = $this->newDialog()
|
||||||
// through this form blindly is dangerous.
|
|
||||||
|
|
||||||
// TODO: If the user has password authentication, require them to retype
|
|
||||||
// their password here.
|
|
||||||
|
|
||||||
$dialog = id(new AphrontDialogView())
|
|
||||||
->setUser($viewer)
|
|
||||||
->setTitle(pht('Confirm %s Account Link', $provider->getProviderName()))
|
->setTitle(pht('Confirm %s Account Link', $provider->getProviderName()))
|
||||||
->addCancelButton($panel_uri)
|
->addCancelButton($panel_uri)
|
||||||
->addSubmitButton(pht('Confirm Account Link'));
|
->addSubmitButton(pht('Confirm Account Link'));
|
||||||
|
|
|
@ -213,19 +213,19 @@ abstract class PhabricatorAuthController extends PhabricatorController {
|
||||||
return array($account, $provider, $response);
|
return array($account, $provider, $response);
|
||||||
}
|
}
|
||||||
|
|
||||||
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
|
$config = $account->getProviderConfig();
|
||||||
$account->getProviderKey());
|
if (!$config->getIsEnabled()) {
|
||||||
|
|
||||||
if (!$provider) {
|
|
||||||
$response = $this->renderError(
|
$response = $this->renderError(
|
||||||
pht(
|
pht(
|
||||||
'The account you are attempting to register with uses a nonexistent '.
|
'The account you are attempting to register with uses a disabled '.
|
||||||
'or disabled authentication provider (with key "%s"). An '.
|
'authentication provider ("%s"). An administrator may have '.
|
||||||
'administrator may have recently disabled this provider.',
|
'recently disabled this provider.',
|
||||||
$account->getProviderKey()));
|
$config->getDisplayName()));
|
||||||
return array($account, $provider, $response);
|
return array($account, $provider, $response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$provider = $config->getProvider();
|
||||||
|
|
||||||
return array($account, $provider, null);
|
return array($account, $provider, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,20 @@ final class PhabricatorAuthLinkController
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
$action = $request->getURIData('action');
|
$action = $request->getURIData('action');
|
||||||
$provider_key = $request->getURIData('pkey');
|
|
||||||
|
|
||||||
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
|
$id = $request->getURIData('id');
|
||||||
$provider_key);
|
|
||||||
if (!$provider) {
|
$config = id(new PhabricatorAuthProviderConfigQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->withIsEnabled(true)
|
||||||
|
->executeOne();
|
||||||
|
if (!$config) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$provider = $config->getProvider();
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'link':
|
case 'link':
|
||||||
if (!$provider->shouldAllowAccountLink()) {
|
if (!$provider->shouldAllowAccountLink()) {
|
||||||
|
@ -37,15 +43,15 @@ final class PhabricatorAuthLinkController
|
||||||
return new Aphront400Response();
|
return new Aphront400Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
$account = id(new PhabricatorExternalAccount())->loadOneWhere(
|
$accounts = id(new PhabricatorExternalAccountQuery())
|
||||||
'accountType = %s AND accountDomain = %s AND userPHID = %s',
|
->setViewer($viewer)
|
||||||
$provider->getProviderType(),
|
->withUserPHIDs(array($viewer->getPHID()))
|
||||||
$provider->getProviderDomain(),
|
->withProviderConfigPHIDs(array($config->getPHID()))
|
||||||
$viewer->getPHID());
|
->execute();
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'link':
|
case 'link':
|
||||||
if ($account) {
|
if ($accounts) {
|
||||||
return $this->renderErrorPage(
|
return $this->renderErrorPage(
|
||||||
pht('Account Already Linked'),
|
pht('Account Already Linked'),
|
||||||
array(
|
array(
|
||||||
|
@ -56,7 +62,7 @@ final class PhabricatorAuthLinkController
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
if (!$account) {
|
if (!$accounts) {
|
||||||
return $this->renderErrorPage(
|
return $this->renderErrorPage(
|
||||||
pht('No Account Linked'),
|
pht('No Account Linked'),
|
||||||
array(
|
array(
|
||||||
|
@ -76,11 +82,6 @@ final class PhabricatorAuthLinkController
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'link':
|
case 'link':
|
||||||
id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
|
|
||||||
$viewer,
|
|
||||||
$request,
|
|
||||||
$panel_uri);
|
|
||||||
|
|
||||||
$form = $provider->buildLinkForm($this);
|
$form = $provider->buildLinkForm($this);
|
||||||
break;
|
break;
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
|
|
|
@ -31,7 +31,7 @@ final class PhabricatorAuthUnlinkController
|
||||||
$confirmations = $request->getStrList('confirmations');
|
$confirmations = $request->getStrList('confirmations');
|
||||||
$confirmations = array_fuse($confirmations);
|
$confirmations = array_fuse($confirmations);
|
||||||
|
|
||||||
if (!$request->isFormPost() || !isset($confirmations['unlink'])) {
|
if (!$request->isFormOrHisecPost() || !isset($confirmations['unlink'])) {
|
||||||
return $this->renderConfirmDialog($confirmations, $config, $done_uri);
|
return $this->renderConfirmDialog($confirmations, $config, $done_uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,14 @@ final class PhabricatorAuthUnlinkController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$workflow_key = sprintf(
|
||||||
|
'account.unlink(%s)',
|
||||||
|
$account->getPHID());
|
||||||
|
|
||||||
|
$hisec_token = id(new PhabricatorAuthSessionEngine())
|
||||||
|
->setWorkflowKey($workflow_key)
|
||||||
|
->requireHighSecurityToken($viewer, $request, $done_uri);
|
||||||
|
|
||||||
$account->delete();
|
$account->delete();
|
||||||
|
|
||||||
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
|
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
|
||||||
|
|
|
@ -21,6 +21,7 @@ final class PhabricatorExternalAccountQuery
|
||||||
private $userPHIDs;
|
private $userPHIDs;
|
||||||
private $needImages;
|
private $needImages;
|
||||||
private $accountSecrets;
|
private $accountSecrets;
|
||||||
|
private $providerConfigPHIDs;
|
||||||
|
|
||||||
public function withUserPHIDs(array $user_phids) {
|
public function withUserPHIDs(array $user_phids) {
|
||||||
$this->userPHIDs = $user_phids;
|
$this->userPHIDs = $user_phids;
|
||||||
|
@ -62,6 +63,11 @@ final class PhabricatorExternalAccountQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withProviderConfigPHIDs(array $phids) {
|
||||||
|
$this->providerConfigPHIDs = $phids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function newResultObject() {
|
public function newResultObject() {
|
||||||
return new PhabricatorExternalAccount();
|
return new PhabricatorExternalAccount();
|
||||||
}
|
}
|
||||||
|
@ -181,6 +187,13 @@ final class PhabricatorExternalAccountQuery
|
||||||
$this->accountSecrets);
|
$this->accountSecrets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->providerConfigPHIDs !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'providerConfigPHID IN (%Ls)',
|
||||||
|
$this->providerConfigPHIDs);
|
||||||
|
}
|
||||||
|
|
||||||
return $where;
|
return $where;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -157,13 +157,10 @@ final class PhabricatorPeopleProfilePictureController
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
|
$config = $account->getProviderConfig();
|
||||||
$account->getProviderKey());
|
$provider = $config->getProvider();
|
||||||
if ($provider) {
|
|
||||||
$tip = pht('Picture From %s', $provider->getProviderName());
|
$tip = pht('Picture From %s', $provider->getProviderName());
|
||||||
} else {
|
|
||||||
$tip = pht('Picture From External Account');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($file->isTransformableImage()) {
|
if ($file->isTransformableImage()) {
|
||||||
$images[$file->getPHID()] = array(
|
$images[$file->getPHID()] = array(
|
||||||
|
|
|
@ -44,17 +44,13 @@ final class PhabricatorExternalAccountsSettingsPanel
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
$item = new PHUIObjectItemView();
|
$item = new PHUIObjectItemView();
|
||||||
|
|
||||||
$provider = idx($providers, $account->getProviderKey());
|
$config = $account->getProviderConfig();
|
||||||
if ($provider) {
|
$provider = $config->getProvider();
|
||||||
$item->setHeader($provider->getProviderName());
|
|
||||||
$can_unlink = $provider->shouldAllowAccountUnlink();
|
$item->setHeader($provider->getProviderName());
|
||||||
if (!$can_unlink) {
|
$can_unlink = $provider->shouldAllowAccountUnlink();
|
||||||
$item->addAttribute(pht('Permanently Linked'));
|
if (!$can_unlink) {
|
||||||
}
|
$item->addAttribute(pht('Permanently Linked'));
|
||||||
} else {
|
|
||||||
$item->setHeader(
|
|
||||||
pht('Unknown Account ("%s")', $account->getProviderKey()));
|
|
||||||
$can_unlink = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$can_login = $account->isUsableForLogin();
|
$can_login = $account->isUsableForLogin();
|
||||||
|
@ -65,12 +61,12 @@ final class PhabricatorExternalAccountsSettingsPanel
|
||||||
'account provider).'));
|
'account provider).'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$can_refresh = $provider && $provider->shouldAllowAccountRefresh();
|
$can_refresh = $provider->shouldAllowAccountRefresh();
|
||||||
if ($can_refresh) {
|
if ($can_refresh) {
|
||||||
$item->addAction(
|
$item->addAction(
|
||||||
id(new PHUIListItemView())
|
id(new PHUIListItemView())
|
||||||
->setIcon('fa-refresh')
|
->setIcon('fa-refresh')
|
||||||
->setHref('/auth/refresh/'.$account->getProviderKey().'/'));
|
->setHref('/auth/refresh/'.$config->getID().'/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$item->addAction(
|
$item->addAction(
|
||||||
|
@ -94,14 +90,15 @@ final class PhabricatorExternalAccountsSettingsPanel
|
||||||
->setNoDataString(
|
->setNoDataString(
|
||||||
pht('Your account is linked with all available providers.'));
|
pht('Your account is linked with all available providers.'));
|
||||||
|
|
||||||
$accounts = mpull($accounts, null, 'getProviderKey');
|
|
||||||
|
|
||||||
$configs = id(new PhabricatorAuthProviderConfigQuery())
|
$configs = id(new PhabricatorAuthProviderConfigQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withIsEnabled(true)
|
->withIsEnabled(true)
|
||||||
->execute();
|
->execute();
|
||||||
$configs = msort($configs, 'getSortVector');
|
$configs = msort($configs, 'getSortVector');
|
||||||
|
|
||||||
|
$account_map = mgroup($accounts, 'getProviderConfigPHID');
|
||||||
|
|
||||||
|
|
||||||
foreach ($configs as $config) {
|
foreach ($configs as $config) {
|
||||||
$provider = $config->getProvider();
|
$provider = $config->getProvider();
|
||||||
|
|
||||||
|
@ -110,12 +107,11 @@ final class PhabricatorExternalAccountsSettingsPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't show the user providers they already have linked.
|
// Don't show the user providers they already have linked.
|
||||||
$provider_key = $config->getProvider()->getProviderKey();
|
if (isset($account_map[$config->getPHID()])) {
|
||||||
if (isset($accounts[$provider_key])) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$link_uri = '/auth/link/'.$provider->getProviderKey().'/';
|
$link_uri = '/auth/link/'.$config->getID().'/';
|
||||||
|
|
||||||
$link_button = id(new PHUIButtonView())
|
$link_button = id(new PHUIButtonView())
|
||||||
->setTag('a')
|
->setTag('a')
|
||||||
|
|
Loading…
Reference in a new issue