@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.

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

+76 -60
+1 -1
src/applications/auth/application/PhabricatorAuthApplication.php
··· 62 62 'validate/' => 'PhabricatorAuthValidateController', 63 63 'finish/' => 'PhabricatorAuthFinishController', 64 64 'unlink/(?P<id>\d+)/' => 'PhabricatorAuthUnlinkController', 65 - '(?P<action>link|refresh)/(?P<pkey>[^/]+)/' 65 + '(?P<action>link|refresh)/(?P<id>\d+)/' 66 66 => 'PhabricatorAuthLinkController', 67 67 'confirmlink/(?P<akey>[^/]+)/' 68 68 => 'PhabricatorAuthConfirmLinkController',
+10 -9
src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php
··· 20 20 21 21 $panel_uri = '/settings/panel/external/'; 22 22 23 - if ($request->isFormPost()) { 23 + if ($request->isFormOrHisecPost()) { 24 + $workflow_key = sprintf( 25 + 'account.link(%s)', 26 + $account->getPHID()); 27 + 28 + $hisec_token = id(new PhabricatorAuthSessionEngine()) 29 + ->setWorkflowKey($workflow_key) 30 + ->requireHighSecurityToken($viewer, $request, $panel_uri); 31 + 24 32 $account->setUserPHID($viewer->getPHID()); 25 33 $account->save(); 26 34 ··· 31 39 return id(new AphrontRedirectResponse())->setURI($panel_uri); 32 40 } 33 41 34 - // TODO: Provide more information about the external account. Clicking 35 - // through this form blindly is dangerous. 36 - 37 - // TODO: If the user has password authentication, require them to retype 38 - // their password here. 39 - 40 - $dialog = id(new AphrontDialogView()) 41 - ->setUser($viewer) 42 + $dialog = $this->newDialog() 42 43 ->setTitle(pht('Confirm %s Account Link', $provider->getProviderName())) 43 44 ->addCancelButton($panel_uri) 44 45 ->addSubmitButton(pht('Confirm Account Link'));
+8 -8
src/applications/auth/controller/PhabricatorAuthController.php
··· 213 213 return array($account, $provider, $response); 214 214 } 215 215 216 - $provider = PhabricatorAuthProvider::getEnabledProviderByKey( 217 - $account->getProviderKey()); 218 - 219 - if (!$provider) { 216 + $config = $account->getProviderConfig(); 217 + if (!$config->getIsEnabled()) { 220 218 $response = $this->renderError( 221 219 pht( 222 - 'The account you are attempting to register with uses a nonexistent '. 223 - 'or disabled authentication provider (with key "%s"). An '. 224 - 'administrator may have recently disabled this provider.', 225 - $account->getProviderKey())); 220 + 'The account you are attempting to register with uses a disabled '. 221 + 'authentication provider ("%s"). An administrator may have '. 222 + 'recently disabled this provider.', 223 + $config->getDisplayName())); 226 224 return array($account, $provider, $response); 227 225 } 226 + 227 + $provider = $config->getProvider(); 228 228 229 229 return array($account, $provider, null); 230 230 }
+17 -16
src/applications/auth/controller/PhabricatorAuthLinkController.php
··· 6 6 public function handleRequest(AphrontRequest $request) { 7 7 $viewer = $this->getViewer(); 8 8 $action = $request->getURIData('action'); 9 - $provider_key = $request->getURIData('pkey'); 10 9 11 - $provider = PhabricatorAuthProvider::getEnabledProviderByKey( 12 - $provider_key); 13 - if (!$provider) { 10 + $id = $request->getURIData('id'); 11 + 12 + $config = id(new PhabricatorAuthProviderConfigQuery()) 13 + ->setViewer($viewer) 14 + ->withIDs(array($id)) 15 + ->withIsEnabled(true) 16 + ->executeOne(); 17 + if (!$config) { 14 18 return new Aphront404Response(); 15 19 } 20 + 21 + $provider = $config->getProvider(); 16 22 17 23 switch ($action) { 18 24 case 'link': ··· 37 43 return new Aphront400Response(); 38 44 } 39 45 40 - $account = id(new PhabricatorExternalAccount())->loadOneWhere( 41 - 'accountType = %s AND accountDomain = %s AND userPHID = %s', 42 - $provider->getProviderType(), 43 - $provider->getProviderDomain(), 44 - $viewer->getPHID()); 46 + $accounts = id(new PhabricatorExternalAccountQuery()) 47 + ->setViewer($viewer) 48 + ->withUserPHIDs(array($viewer->getPHID())) 49 + ->withProviderConfigPHIDs(array($config->getPHID())) 50 + ->execute(); 45 51 46 52 switch ($action) { 47 53 case 'link': 48 - if ($account) { 54 + if ($accounts) { 49 55 return $this->renderErrorPage( 50 56 pht('Account Already Linked'), 51 57 array( ··· 56 62 } 57 63 break; 58 64 case 'refresh': 59 - if (!$account) { 65 + if (!$accounts) { 60 66 return $this->renderErrorPage( 61 67 pht('No Account Linked'), 62 68 array( ··· 76 82 77 83 switch ($action) { 78 84 case 'link': 79 - id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( 80 - $viewer, 81 - $request, 82 - $panel_uri); 83 - 84 85 $form = $provider->buildLinkForm($this); 85 86 break; 86 87 case 'refresh':
+9 -1
src/applications/auth/controller/PhabricatorAuthUnlinkController.php
··· 31 31 $confirmations = $request->getStrList('confirmations'); 32 32 $confirmations = array_fuse($confirmations); 33 33 34 - if (!$request->isFormPost() || !isset($confirmations['unlink'])) { 34 + if (!$request->isFormOrHisecPost() || !isset($confirmations['unlink'])) { 35 35 return $this->renderConfirmDialog($confirmations, $config, $done_uri); 36 36 } 37 37 ··· 58 58 } 59 59 } 60 60 } 61 + 62 + $workflow_key = sprintf( 63 + 'account.unlink(%s)', 64 + $account->getPHID()); 65 + 66 + $hisec_token = id(new PhabricatorAuthSessionEngine()) 67 + ->setWorkflowKey($workflow_key) 68 + ->requireHighSecurityToken($viewer, $request, $done_uri); 61 69 62 70 $account->delete(); 63 71
+13
src/applications/auth/query/PhabricatorExternalAccountQuery.php
··· 21 21 private $userPHIDs; 22 22 private $needImages; 23 23 private $accountSecrets; 24 + private $providerConfigPHIDs; 24 25 25 26 public function withUserPHIDs(array $user_phids) { 26 27 $this->userPHIDs = $user_phids; ··· 59 60 60 61 public function needImages($need) { 61 62 $this->needImages = $need; 63 + return $this; 64 + } 65 + 66 + public function withProviderConfigPHIDs(array $phids) { 67 + $this->providerConfigPHIDs = $phids; 62 68 return $this; 63 69 } 64 70 ··· 179 185 $conn, 180 186 'accountSecret IN (%Ls)', 181 187 $this->accountSecrets); 188 + } 189 + 190 + if ($this->providerConfigPHIDs !== null) { 191 + $where[] = qsprintf( 192 + $conn, 193 + 'providerConfigPHID IN (%Ls)', 194 + $this->providerConfigPHIDs); 182 195 } 183 196 184 197 return $where;
+4 -7
src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
··· 157 157 continue; 158 158 } 159 159 160 - $provider = PhabricatorAuthProvider::getEnabledProviderByKey( 161 - $account->getProviderKey()); 162 - if ($provider) { 163 - $tip = pht('Picture From %s', $provider->getProviderName()); 164 - } else { 165 - $tip = pht('Picture From External Account'); 166 - } 160 + $config = $account->getProviderConfig(); 161 + $provider = $config->getProvider(); 162 + 163 + $tip = pht('Picture From %s', $provider->getProviderName()); 167 164 168 165 if ($file->isTransformableImage()) { 169 166 $images[$file->getPHID()] = array(
+14 -18
src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php
··· 44 44 foreach ($accounts as $account) { 45 45 $item = new PHUIObjectItemView(); 46 46 47 - $provider = idx($providers, $account->getProviderKey()); 48 - if ($provider) { 49 - $item->setHeader($provider->getProviderName()); 50 - $can_unlink = $provider->shouldAllowAccountUnlink(); 51 - if (!$can_unlink) { 52 - $item->addAttribute(pht('Permanently Linked')); 53 - } 54 - } else { 55 - $item->setHeader( 56 - pht('Unknown Account ("%s")', $account->getProviderKey())); 57 - $can_unlink = true; 47 + $config = $account->getProviderConfig(); 48 + $provider = $config->getProvider(); 49 + 50 + $item->setHeader($provider->getProviderName()); 51 + $can_unlink = $provider->shouldAllowAccountUnlink(); 52 + if (!$can_unlink) { 53 + $item->addAttribute(pht('Permanently Linked')); 58 54 } 59 55 60 56 $can_login = $account->isUsableForLogin(); ··· 65 61 'account provider).')); 66 62 } 67 63 68 - $can_refresh = $provider && $provider->shouldAllowAccountRefresh(); 64 + $can_refresh = $provider->shouldAllowAccountRefresh(); 69 65 if ($can_refresh) { 70 66 $item->addAction( 71 67 id(new PHUIListItemView()) 72 68 ->setIcon('fa-refresh') 73 - ->setHref('/auth/refresh/'.$account->getProviderKey().'/')); 69 + ->setHref('/auth/refresh/'.$config->getID().'/')); 74 70 } 75 71 76 72 $item->addAction( ··· 93 89 ->setUser($viewer) 94 90 ->setNoDataString( 95 91 pht('Your account is linked with all available providers.')); 96 - 97 - $accounts = mpull($accounts, null, 'getProviderKey'); 98 92 99 93 $configs = id(new PhabricatorAuthProviderConfigQuery()) 100 94 ->setViewer($viewer) ··· 102 96 ->execute(); 103 97 $configs = msort($configs, 'getSortVector'); 104 98 99 + $account_map = mgroup($accounts, 'getProviderConfigPHID'); 100 + 101 + 105 102 foreach ($configs as $config) { 106 103 $provider = $config->getProvider(); 107 104 ··· 110 107 } 111 108 112 109 // Don't show the user providers they already have linked. 113 - $provider_key = $config->getProvider()->getProviderKey(); 114 - if (isset($accounts[$provider_key])) { 110 + if (isset($account_map[$config->getPHID()])) { 115 111 continue; 116 112 } 117 113 118 - $link_uri = '/auth/link/'.$provider->getProviderKey().'/'; 114 + $link_uri = '/auth/link/'.$config->getID().'/'; 119 115 120 116 $link_button = id(new PHUIButtonView()) 121 117 ->setTag('a')