@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow users to unlink their last external account with a warning, instead of preventing the action

Summary:
Depends on D20105. Fixes T7732. T7732 describes a case where a user had their Google credentials swapped and had trouble regaining access to their account.

Since we now allow email login even if password auth is disabled, it's okay to let users unlink their final account, and it's even reasonable for users to unlink their final account if it is mis-linked.

Just give them a warning that what they're doing is a little sketchy, rather than preventing the workflow.

Test Plan: Unlinked my only login account, got a stern warning instead of a dead end.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T7732

Differential Revision: https://secure.phabricator.com/D20106

+38 -39
+38 -30
src/applications/auth/controller/PhabricatorAuthUnlinkController.php
··· 32 32 } 33 33 } 34 34 35 - // Check that this account isn't the last account which can be used to 36 - // login. We prevent you from removing the last account. 35 + $confirmations = $request->getStrList('confirmations'); 36 + $confirmations = array_fuse($confirmations); 37 + 38 + if (!$request->isFormPost() || !isset($confirmations['unlink'])) { 39 + return $this->renderConfirmDialog($confirmations); 40 + } 41 + 42 + // Check that this account isn't the only account which can be used to 43 + // login. We warn you when you remove your only login account. 37 44 if ($account->isUsableForLogin()) { 38 45 $other_accounts = id(new PhabricatorExternalAccount())->loadAllWhere( 39 46 'userPHID = %s', ··· 47 54 } 48 55 49 56 if ($valid_accounts < 2) { 50 - return $this->renderLastUsableAccountErrorDialog(); 57 + if (!isset($confirmations['only'])) { 58 + return $this->renderOnlyUsableAccountConfirmDialog($confirmations); 59 + } 51 60 } 52 61 } 53 62 54 - if ($request->isDialogFormPost()) { 55 - $account->delete(); 63 + $account->delete(); 56 64 57 - id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( 58 - $viewer, 59 - new PhutilOpaqueEnvelope( 60 - $request->getCookie(PhabricatorCookies::COOKIE_SESSION))); 61 - 62 - return id(new AphrontRedirectResponse())->setURI($this->getDoneURI()); 63 - } 65 + id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( 66 + $viewer, 67 + new PhutilOpaqueEnvelope( 68 + $request->getCookie(PhabricatorCookies::COOKIE_SESSION))); 64 69 65 - return $this->renderConfirmDialog(); 70 + return id(new AphrontRedirectResponse())->setURI($this->getDoneURI()); 66 71 } 67 72 68 73 private function getDoneURI() { ··· 97 102 return id(new AphrontDialogResponse())->setDialog($dialog); 98 103 } 99 104 100 - private function renderLastUsableAccountErrorDialog() { 101 - $dialog = id(new AphrontDialogView()) 102 - ->setUser($this->getRequest()->getUser()) 103 - ->setTitle(pht('Last Valid Account')) 104 - ->appendChild( 105 + private function renderOnlyUsableAccountConfirmDialog(array $confirmations) { 106 + $confirmations[] = 'only'; 107 + 108 + return $this->newDialog() 109 + ->setTitle(pht('Unlink Your Only Login Account?')) 110 + ->addHiddenInput('confirmations', implode(',', $confirmations)) 111 + ->appendParagraph( 105 112 pht( 106 - 'You can not unlink this account because you have no other '. 107 - 'valid login accounts. If you removed it, you would be unable '. 108 - 'to log in. Add another authentication method before removing '. 109 - 'this one.')) 110 - ->addCancelButton($this->getDoneURI()); 111 - 112 - return id(new AphrontDialogResponse())->setDialog($dialog); 113 + 'This is the only external login account linked to your Phabicator '. 114 + 'account. If you remove it, you may no longer be able to log in.')) 115 + ->appendParagraph( 116 + pht( 117 + 'If you lose access to your account, you can recover access by '. 118 + 'sending yourself an email login link from the login screen.')) 119 + ->addCancelButton($this->getDoneURI()) 120 + ->addSubmitButton(pht('Unlink External Account')); 113 121 } 114 122 115 - private function renderConfirmDialog() { 123 + private function renderConfirmDialog(array $confirmations) { 124 + $confirmations[] = 'unlink'; 125 + 116 126 $provider_key = $this->providerKey; 117 127 $provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key); 118 128 ··· 129 139 'to Phabricator.'); 130 140 } 131 141 132 - $dialog = id(new AphrontDialogView()) 133 - ->setUser($this->getRequest()->getUser()) 142 + return $this->newDialog() 134 143 ->setTitle($title) 144 + ->addHiddenInput('confirmations', implode(',', $confirmations)) 135 145 ->appendParagraph($body) 136 146 ->appendParagraph( 137 147 pht( ··· 139 149 'other active login sessions.')) 140 150 ->addSubmitButton(pht('Unlink Account')) 141 151 ->addCancelButton($this->getDoneURI()); 142 - 143 - return id(new AphrontDialogResponse())->setDialog($dialog); 144 152 } 145 153 146 154 }
-9
src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php
··· 41 41 ->setUser($viewer) 42 42 ->setNoDataString(pht('You have no linked accounts.')); 43 43 44 - $login_accounts = 0; 45 - foreach ($accounts as $account) { 46 - if ($account->isUsableForLogin()) { 47 - $login_accounts++; 48 - } 49 - } 50 - 51 44 foreach ($accounts as $account) { 52 45 $item = new PHUIObjectItemView(); 53 46 ··· 71 64 'Disabled (an administrator has disabled login for this '. 72 65 'account provider).')); 73 66 } 74 - 75 - $can_unlink = $can_unlink && (!$can_login || ($login_accounts > 1)); 76 67 77 68 $can_refresh = $provider && $provider->shouldAllowAccountRefresh(); 78 69 if ($can_refresh) {