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
142 lines
4.3 KiB
PHP
142 lines
4.3 KiB
PHP
<?php
|
|
|
|
final class PhabricatorAuthUnlinkController
|
|
extends PhabricatorAuthController {
|
|
|
|
public function handleRequest(AphrontRequest $request) {
|
|
$viewer = $this->getViewer();
|
|
$id = $request->getURIData('id');
|
|
|
|
$account = id(new PhabricatorExternalAccountQuery())
|
|
->setViewer($viewer)
|
|
->withIDs(array($id))
|
|
->requireCapabilities(
|
|
array(
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
))
|
|
->executeOne();
|
|
if (!$account) {
|
|
return new Aphront404Response();
|
|
}
|
|
|
|
$done_uri = '/settings/panel/external/';
|
|
|
|
$config = $account->getProviderConfig();
|
|
$provider = $config->getProvider();
|
|
if (!$provider->shouldAllowAccountUnlink()) {
|
|
return $this->renderNotUnlinkableErrorDialog($provider, $done_uri);
|
|
}
|
|
|
|
$confirmations = $request->getStrList('confirmations');
|
|
$confirmations = array_fuse($confirmations);
|
|
|
|
if (!$request->isFormOrHisecPost() || !isset($confirmations['unlink'])) {
|
|
return $this->renderConfirmDialog($confirmations, $config, $done_uri);
|
|
}
|
|
|
|
// Check that this account isn't the only account which can be used to
|
|
// login. We warn you when you remove your only login account.
|
|
if ($account->isUsableForLogin()) {
|
|
$other_accounts = id(new PhabricatorExternalAccountQuery())
|
|
->setViewer($viewer)
|
|
->withUserPHIDs(array($viewer->getPHID()))
|
|
->execute();
|
|
|
|
$valid_accounts = 0;
|
|
foreach ($other_accounts as $other_account) {
|
|
if ($other_account->isUsableForLogin()) {
|
|
$valid_accounts++;
|
|
}
|
|
}
|
|
|
|
if ($valid_accounts < 2) {
|
|
if (!isset($confirmations['only'])) {
|
|
return $this->renderOnlyUsableAccountConfirmDialog(
|
|
$confirmations,
|
|
$done_uri);
|
|
}
|
|
}
|
|
}
|
|
|
|
$workflow_key = sprintf(
|
|
'account.unlink(%s)',
|
|
$account->getPHID());
|
|
|
|
$hisec_token = id(new PhabricatorAuthSessionEngine())
|
|
->setWorkflowKey($workflow_key)
|
|
->requireHighSecurityToken($viewer, $request, $done_uri);
|
|
|
|
$account->delete();
|
|
|
|
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
|
|
$viewer,
|
|
new PhutilOpaqueEnvelope(
|
|
$request->getCookie(PhabricatorCookies::COOKIE_SESSION)));
|
|
|
|
return id(new AphrontRedirectResponse())->setURI($done_uri);
|
|
}
|
|
|
|
private function renderNotUnlinkableErrorDialog(
|
|
PhabricatorAuthProvider $provider,
|
|
$done_uri) {
|
|
|
|
return $this->newDialog()
|
|
->setTitle(pht('Permanent Account Link'))
|
|
->appendChild(
|
|
pht(
|
|
'You can not unlink this account because the administrator has '.
|
|
'configured Phabricator to make links to "%s" accounts permanent.',
|
|
$provider->getProviderName()))
|
|
->addCancelButton($done_uri);
|
|
}
|
|
|
|
private function renderOnlyUsableAccountConfirmDialog(
|
|
array $confirmations,
|
|
$done_uri) {
|
|
|
|
$confirmations[] = 'only';
|
|
|
|
return $this->newDialog()
|
|
->setTitle(pht('Unlink Your Only Login Account?'))
|
|
->addHiddenInput('confirmations', implode(',', $confirmations))
|
|
->appendParagraph(
|
|
pht(
|
|
'This is the only external login account linked to your Phabicator '.
|
|
'account. If you remove it, you may no longer be able to log in.'))
|
|
->appendParagraph(
|
|
pht(
|
|
'If you lose access to your account, you can recover access by '.
|
|
'sending yourself an email login link from the login screen.'))
|
|
->addCancelButton($done_uri)
|
|
->addSubmitButton(pht('Unlink External Account'));
|
|
}
|
|
|
|
private function renderConfirmDialog(
|
|
array $confirmations,
|
|
PhabricatorAuthProviderConfig $config,
|
|
$done_uri) {
|
|
|
|
$confirmations[] = 'unlink';
|
|
$provider = $config->getProvider();
|
|
|
|
$title = pht('Unlink "%s" Account?', $provider->getProviderName());
|
|
$body = pht(
|
|
'You will no longer be able to use your %s account to '.
|
|
'log in to Phabricator.',
|
|
$provider->getProviderName());
|
|
|
|
return $this->newDialog()
|
|
->setTitle($title)
|
|
->addHiddenInput('confirmations', implode(',', $confirmations))
|
|
->appendParagraph($body)
|
|
->appendParagraph(
|
|
pht(
|
|
'Note: Unlinking an authentication provider will terminate any '.
|
|
'other active login sessions.'))
|
|
->addSubmitButton(pht('Unlink Account'))
|
|
->addCancelButton($done_uri);
|
|
}
|
|
|
|
}
|