diff --git a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php index e6e1493e5a..1e3023e5d2 100644 --- a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php @@ -1,146 +1,154 @@ getViewer(); $this->providerKey = $request->getURIData('pkey'); list($type, $domain) = explode(':', $this->providerKey, 2); // Check that this account link actually exists. We don't require the // provider to exist because we want users to be able to delete links to // dead accounts if they want. $account = id(new PhabricatorExternalAccount())->loadOneWhere( 'accountType = %s AND accountDomain = %s AND userPHID = %s', $type, $domain, $viewer->getPHID()); if (!$account) { return $this->renderNoAccountErrorDialog(); } // Check that the provider (if it exists) allows accounts to be unlinked. $provider_key = $this->providerKey; $provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key); if ($provider) { if (!$provider->shouldAllowAccountUnlink()) { return $this->renderNotUnlinkableErrorDialog($provider); } } - // Check that this account isn't the last account which can be used to - // login. We prevent you from removing the last account. + $confirmations = $request->getStrList('confirmations'); + $confirmations = array_fuse($confirmations); + + if (!$request->isFormPost() || !isset($confirmations['unlink'])) { + return $this->renderConfirmDialog($confirmations); + } + + // 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 PhabricatorExternalAccount())->loadAllWhere( 'userPHID = %s', $viewer->getPHID()); $valid_accounts = 0; foreach ($other_accounts as $other_account) { if ($other_account->isUsableForLogin()) { $valid_accounts++; } } if ($valid_accounts < 2) { - return $this->renderLastUsableAccountErrorDialog(); + if (!isset($confirmations['only'])) { + return $this->renderOnlyUsableAccountConfirmDialog($confirmations); + } } } - if ($request->isDialogFormPost()) { - $account->delete(); - - id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( - $viewer, - new PhutilOpaqueEnvelope( - $request->getCookie(PhabricatorCookies::COOKIE_SESSION))); + $account->delete(); - return id(new AphrontRedirectResponse())->setURI($this->getDoneURI()); - } + id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( + $viewer, + new PhutilOpaqueEnvelope( + $request->getCookie(PhabricatorCookies::COOKIE_SESSION))); - return $this->renderConfirmDialog(); + return id(new AphrontRedirectResponse())->setURI($this->getDoneURI()); } private function getDoneURI() { return '/settings/panel/external/'; } private function renderNoAccountErrorDialog() { $dialog = id(new AphrontDialogView()) ->setUser($this->getRequest()->getUser()) ->setTitle(pht('No Such Account')) ->appendChild( pht( 'You can not unlink this account because it is not linked.')) ->addCancelButton($this->getDoneURI()); return id(new AphrontDialogResponse())->setDialog($dialog); } private function renderNotUnlinkableErrorDialog( PhabricatorAuthProvider $provider) { $dialog = id(new AphrontDialogView()) ->setUser($this->getRequest()->getUser()) ->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($this->getDoneURI()); return id(new AphrontDialogResponse())->setDialog($dialog); } - private function renderLastUsableAccountErrorDialog() { - $dialog = id(new AphrontDialogView()) - ->setUser($this->getRequest()->getUser()) - ->setTitle(pht('Last Valid Account')) - ->appendChild( - pht( - 'You can not unlink this account because you have no other '. - 'valid login accounts. If you removed it, you would be unable '. - 'to log in. Add another authentication method before removing '. - 'this one.')) - ->addCancelButton($this->getDoneURI()); + private function renderOnlyUsableAccountConfirmDialog(array $confirmations) { + $confirmations[] = 'only'; - return id(new AphrontDialogResponse())->setDialog($dialog); + 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($this->getDoneURI()) + ->addSubmitButton(pht('Unlink External Account')); } - private function renderConfirmDialog() { + private function renderConfirmDialog(array $confirmations) { + $confirmations[] = 'unlink'; + $provider_key = $this->providerKey; $provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key); if ($provider) { $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()); } else { $title = pht('Unlink Account?'); $body = pht( 'You will no longer be able to use this account to log in '. 'to Phabricator.'); } - $dialog = id(new AphrontDialogView()) - ->setUser($this->getRequest()->getUser()) + 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($this->getDoneURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php index 9401cddbef..29ef9fa2c7 100644 --- a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php @@ -1,154 +1,145 @@ getUser(); $providers = PhabricatorAuthProvider::getAllProviders(); $accounts = id(new PhabricatorExternalAccountQuery()) ->setViewer($viewer) ->withUserPHIDs(array($viewer->getPHID())) ->needImages(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->execute(); $linked_head = pht('Linked Accounts and Authentication'); $linked = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setNoDataString(pht('You have no linked accounts.')); - $login_accounts = 0; - foreach ($accounts as $account) { - if ($account->isUsableForLogin()) { - $login_accounts++; - } - } - foreach ($accounts as $account) { $item = new PHUIObjectItemView(); $provider = idx($providers, $account->getProviderKey()); if ($provider) { $item->setHeader($provider->getProviderName()); $can_unlink = $provider->shouldAllowAccountUnlink(); if (!$can_unlink) { $item->addAttribute(pht('Permanently Linked')); } } else { $item->setHeader( pht('Unknown Account ("%s")', $account->getProviderKey())); $can_unlink = true; } $can_login = $account->isUsableForLogin(); if (!$can_login) { $item->addAttribute( pht( 'Disabled (an administrator has disabled login for this '. 'account provider).')); } - $can_unlink = $can_unlink && (!$can_login || ($login_accounts > 1)); - $can_refresh = $provider && $provider->shouldAllowAccountRefresh(); if ($can_refresh) { $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-refresh') ->setHref('/auth/refresh/'.$account->getProviderKey().'/')); } $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-times') ->setWorkflow(true) ->setDisabled(!$can_unlink) ->setHref('/auth/unlink/'.$account->getProviderKey().'/')); if ($provider) { $provider->willRenderLinkedAccount($viewer, $item, $account); } $linked->addItem($item); } $linkable_head = pht('Add External Account'); $linkable = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setNoDataString( pht('Your account is linked with all available providers.')); $accounts = mpull($accounts, null, 'getProviderKey'); $configs = id(new PhabricatorAuthProviderConfigQuery()) ->setViewer($viewer) ->withIsEnabled(true) ->execute(); $configs = msort($configs, 'getSortVector'); foreach ($configs as $config) { $provider = $config->getProvider(); if (!$provider->shouldAllowAccountLink()) { continue; } // Don't show the user providers they already have linked. $provider_key = $config->getProvider()->getProviderKey(); if (isset($accounts[$provider_key])) { continue; } $link_uri = '/auth/link/'.$provider->getProviderKey().'/'; $link_button = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-link') ->setHref($link_uri) ->setColor(PHUIButtonView::GREY) ->setText(pht('Link External Account')); $item = id(new PHUIObjectItemView()) ->setHeader($config->getDisplayName()) ->setHref($link_uri) ->setImageIcon($config->newIconView()) ->setSideColumn($link_button); $linkable->addItem($item); } $linked_box = $this->newBox($linked_head, $linked); $linkable_box = $this->newBox($linkable_head, $linkable); return array( $linked_box, $linkable_box, ); } }