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

Remove all readers and all nontrivial writers for "accountType" and "accountDomain" on "ExternalAccount"

Summary:
Depends on D21018. Ref T13493. Ref T6703. The "ExternalAccount" table has a unique key on `<accountType, accountDomain, accountID>` but this no longer matches our model of reality and changes in this sequence end writes to `accountID`.

Remove this key.

Then, remove all readers of `accountType` and `accountDomain` (and all nontrivial writers) because none of these callsites are well-aligned with plans in T6703.

This change has no user-facing impact today: all the rules about linking/unlinking/etc remain unchanged, because other rules currently prevent creation of more than one provider with a given "accountType".

Test Plan:
- Linked an OAuth1 account (JIRA).
- Linked an OAuth2 account (Asana).
- Used `bin/auth refresh` to cycle OAuth tokens.
- Grepped for affected symbols.
- Published an Asana update.
- Published a JIRA link.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13493, T6703

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

+104 -121
+21
resources/sql/autopatches/20200222.xident.02.dropkey.php
··· 1 + <?php 2 + 3 + // See T13493. This table previously had a UNIQUE KEY on "<accountType, 4 + // accountDomain, accountID>", which is obsolete. The application now violates 5 + // this key, so make sure it gets dropped. 6 + 7 + // There's no "IF EXISTS" modifier for "ALTER TABLE" so run this as a PHP patch 8 + // instead of an SQL patch. 9 + 10 + $table = new PhabricatorExternalAccount(); 11 + $conn = $table->establishConnection('w'); 12 + 13 + try { 14 + queryfx( 15 + $conn, 16 + 'ALTER TABLE %R DROP KEY %T', 17 + $table, 18 + 'account_details'); 19 + } catch (AphrontQueryException $ex) { 20 + // Ignore. 21 + }
+12 -5
src/applications/auth/controller/PhabricatorAuthLoginController.php
··· 116 116 } 117 117 } else { 118 118 119 - // If the user already has a linked account of this type, prevent them 120 - // from linking a second account. This can happen if they swap logins 121 - // and then refresh the account link. See T6707. We will eventually 122 - // allow this after T2549. 119 + // If the user already has a linked account on this provider, prevent 120 + // them from linking a second account. This can happen if they swap 121 + // logins and then refresh the account link. 122 + 123 + // There's no technical reason we can't allow you to link multiple 124 + // accounts from a single provider; disallowing this is currently a 125 + // product deciison. See T2549. 126 + 123 127 $existing_accounts = id(new PhabricatorExternalAccountQuery()) 124 128 ->setViewer($viewer) 125 129 ->withUserPHIDs(array($viewer->getPHID())) 126 - ->withAccountTypes(array($account->getAccountType())) 130 + ->withProviderConfigPHIDs( 131 + array( 132 + $provider->getProviderConfigPHID(), 133 + )) 127 134 ->execute(); 128 135 if ($existing_accounts) { 129 136 return $this->renderError(
+6 -28
src/applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php
··· 18 18 'param' => 'user', 19 19 'help' => pht('Refresh tokens for a given user.'), 20 20 ), 21 - array( 22 - 'name' => 'type', 23 - 'param' => 'provider', 24 - 'help' => pht('Refresh tokens for a given provider type.'), 25 - ), 26 - array( 27 - 'name' => 'domain', 28 - 'param' => 'domain', 29 - 'help' => pht('Refresh tokens for a given domain.'), 30 - ), 31 21 )); 32 22 } 33 23 ··· 57 47 } 58 48 } 59 49 60 - 61 - $type = $args->getArg('type'); 62 - if (strlen($type)) { 63 - $query->withAccountTypes(array($type)); 64 - } 65 - 66 - $domain = $args->getArg('domain'); 67 - if (strlen($domain)) { 68 - $query->withAccountDomains(array($domain)); 69 - } 70 - 71 50 $accounts = $query->execute(); 72 51 73 52 if (!$accounts) { ··· 82 61 } 83 62 84 63 $providers = PhabricatorAuthProvider::getAllEnabledProviders(); 64 + $providers = mpull($providers, null, 'getProviderConfigPHID'); 85 65 86 66 foreach ($accounts as $account) { 87 67 $console->writeOut( 88 68 "%s\n", 89 69 pht( 90 - 'Refreshing account #%d (%s/%s).', 91 - $account->getID(), 92 - $account->getAccountType(), 93 - $account->getAccountDomain())); 70 + 'Refreshing account #%d.', 71 + $account->getID())); 94 72 95 - $key = $account->getProviderKey(); 96 - if (empty($providers[$key])) { 73 + $config_phid = $account->getProviderConfigPHID(); 74 + if (empty($providers[$config_phid])) { 97 75 $console->writeOut( 98 76 "> %s\n", 99 77 pht('Skipping, provider is not enabled or does not exist.')); 100 78 continue; 101 79 } 102 80 103 - $provider = $providers[$key]; 81 + $provider = $providers[$config_phid]; 104 82 if (!($provider instanceof PhabricatorOAuth2AuthProvider)) { 105 83 $console->writeOut( 106 84 "> %s\n",
+14 -3
src/applications/auth/provider/PhabricatorAuthProvider.php
··· 20 20 return $this->providerConfig; 21 21 } 22 22 23 + public function getProviderConfigPHID() { 24 + return $this->getProviderConfig()->getPHID(); 25 + } 26 + 23 27 public function getConfigurationHelp() { 24 28 return null; 25 29 } ··· 360 364 $config = $this->getProviderConfig(); 361 365 $adapter = $this->getAdapter(); 362 366 363 - return id(new PhabricatorExternalAccount()) 364 - ->setAccountType($adapter->getAdapterType()) 365 - ->setAccountDomain($adapter->getAdapterDomain()) 367 + $account = id(new PhabricatorExternalAccount()) 366 368 ->setProviderConfigPHID($config->getPHID()) 367 369 ->attachAccountIdentifiers(array()); 370 + 371 + // TODO: Remove this when these columns are removed. They no longer have 372 + // readers or writers (other than this callsite). 373 + 374 + $account 375 + ->setAccountType($adapter->getAdapterType()) 376 + ->setAccountDomain($adapter->getAdapterDomain()); 377 + 378 + return $account; 368 379 } 369 380 370 381 public function getLoginOrder() {
+1 -1
src/applications/auth/provider/PhabricatorOAuth2AuthProvider.php
··· 199 199 PhabricatorExternalAccount $account, 200 200 $force_refresh = false) { 201 201 202 - if ($account->getProviderKey() !== $this->getProviderKey()) { 202 + if ($account->getProviderConfigPHID() !== $this->getProviderConfigPHID()) { 203 203 throw new Exception(pht('Account does not match provider!')); 204 204 } 205 205
-26
src/applications/auth/query/PhabricatorExternalAccountQuery.php
··· 15 15 16 16 private $ids; 17 17 private $phids; 18 - private $accountTypes; 19 - private $accountDomains; 20 18 private $accountIDs; 21 19 private $userPHIDs; 22 20 private $needImages; ··· 31 29 32 30 public function withAccountIDs(array $account_ids) { 33 31 $this->accountIDs = $account_ids; 34 - return $this; 35 - } 36 - 37 - public function withAccountDomains(array $account_domains) { 38 - $this->accountDomains = $account_domains; 39 - return $this; 40 - } 41 - 42 - public function withAccountTypes(array $account_types) { 43 - $this->accountTypes = $account_types; 44 32 return $this; 45 33 } 46 34 ··· 173 161 $conn, 174 162 'phid IN (%Ls)', 175 163 $this->phids); 176 - } 177 - 178 - if ($this->accountTypes !== null) { 179 - $where[] = qsprintf( 180 - $conn, 181 - 'accountType IN (%Ls)', 182 - $this->accountTypes); 183 - } 184 - 185 - if ($this->accountDomains !== null) { 186 - $where[] = qsprintf( 187 - $conn, 188 - 'accountDomain IN (%Ls)', 189 - $this->accountDomains); 190 164 } 191 165 192 166 if ($this->accountIDs !== null) {
+4 -2
src/applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php
··· 35 35 $accounts = id(new PhabricatorExternalAccountQuery()) 36 36 ->setViewer($viewer) 37 37 ->withUserPHIDs(array($viewer->getPHID())) 38 - ->withAccountTypes(array($provider->getProviderType())) 39 - ->withAccountDomains(array($provider->getProviderDomain())) 38 + ->withProviderConfigPHIDs( 39 + array( 40 + $provider->getProviderConfigPHID(), 41 + )) 40 42 ->requireCapabilities( 41 43 array( 42 44 PhabricatorPolicyCapability::CAN_VIEW,
+4 -1
src/applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php
··· 30 30 $accounts = id(new PhabricatorExternalAccountQuery()) 31 31 ->setViewer($viewer) 32 32 ->withUserPHIDs(array($viewer->getPHID())) 33 - ->withAccountTypes(array($provider->getProviderType())) 33 + ->withProviderConfigPHIDs( 34 + array( 35 + $provider->getProviderConfigPHID(), 36 + )) 34 37 ->requireCapabilities( 35 38 array( 36 39 PhabricatorPolicyCapability::CAN_VIEW,
+4 -2
src/applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php
··· 65 65 $account = id(new PhabricatorExternalAccountQuery()) 66 66 ->setViewer($viewer) 67 67 ->withUserPHIDs(array($viewer->getPHID())) 68 - ->withAccountTypes(array($provider->getProviderType())) 69 - ->withAccountDomains(array($provider->getProviderDomain())) 68 + ->withProviderConfigPHIDs( 69 + array( 70 + $provider->getProviderConfigPHID(), 71 + )) 70 72 ->requireCapabilities( 71 73 array( 72 74 PhabricatorPolicyCapability::CAN_VIEW,
+24 -22
src/applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php
··· 525 525 return $phid_map; 526 526 } 527 527 528 - $provider = PhabricatorAsanaAuthProvider::getAsanaProvider(); 529 - 530 - $accounts = id(new PhabricatorExternalAccountQuery()) 531 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 532 - ->withUserPHIDs($all_phids) 533 - ->withAccountTypes(array($provider->getProviderType())) 534 - ->withAccountDomains(array($provider->getProviderDomain())) 535 - ->needAccountIdentifiers(true) 536 - ->requireCapabilities( 537 - array( 538 - PhabricatorPolicyCapability::CAN_VIEW, 539 - PhabricatorPolicyCapability::CAN_EDIT, 540 - )) 541 - ->execute(); 528 + $accounts = $this->loadAsanaExternalAccounts($all_phids); 542 529 543 530 foreach ($accounts as $account) { 544 531 $phid_map[$account->getUserPHID()] = $this->getAsanaAccountID($account); ··· 550 537 return $phid_map; 551 538 } 552 539 553 - private function findAnyValidAsanaAccessToken(array $user_phids) { 554 - if (!$user_phids) { 555 - return array(null, null, null); 556 - } 557 - 540 + private function loadAsanaExternalAccounts(array $user_phids) { 558 541 $provider = $this->getProvider(); 559 542 $viewer = $this->getViewer(); 560 543 544 + if (!$user_phids) { 545 + return array(); 546 + } 547 + 561 548 $accounts = id(new PhabricatorExternalAccountQuery()) 562 - ->setViewer($viewer) 549 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 563 550 ->withUserPHIDs($user_phids) 564 - ->withAccountTypes(array($provider->getProviderType())) 565 - ->withAccountDomains(array($provider->getProviderDomain())) 551 + ->withProviderConfigPHIDs( 552 + array( 553 + $provider->getProviderConfigPHID(), 554 + )) 566 555 ->needAccountIdentifiers(true) 567 556 ->requireCapabilities( 568 557 array( ··· 570 559 PhabricatorPolicyCapability::CAN_EDIT, 571 560 )) 572 561 ->execute(); 562 + 563 + return $accounts; 564 + } 565 + 566 + private function findAnyValidAsanaAccessToken(array $user_phids) { 567 + $provider = $this->getProvider(); 568 + $viewer = $this->getViewer(); 569 + 570 + if (!$user_phids) { 571 + return array(null, null, null); 572 + } 573 + 574 + $accounts = $this->loadAsanaExternalAccounts($user_phids); 573 575 574 576 // Reorder accounts in the original order. 575 577 // TODO: This needs to be adjusted if/when we allow you to link multiple
+5 -2
src/applications/doorkeeper/worker/DoorkeeperJIRAFeedWorker.php
··· 74 74 $accounts = id(new PhabricatorExternalAccountQuery()) 75 75 ->setViewer($viewer) 76 76 ->withUserPHIDs($try_users) 77 - ->withAccountTypes(array($provider->getProviderType())) 78 - ->withAccountDomains(array($domain)) 77 + ->withProviderConfigPHIDs( 78 + array( 79 + $provider->getProviderConfigPHID(), 80 + )) 79 81 ->requireCapabilities( 80 82 array( 81 83 PhabricatorPolicyCapability::CAN_VIEW, 82 84 PhabricatorPolicyCapability::CAN_EDIT, 83 85 )) 84 86 ->execute(); 87 + 85 88 // Reorder accounts in the original order. 86 89 // TODO: This needs to be adjusted if/when we allow you to link multiple 87 90 // accounts.
+9 -29
src/applications/people/storage/PhabricatorExternalAccount.php
··· 7 7 PhabricatorDestructibleInterface { 8 8 9 9 protected $userPHID; 10 - protected $accountType; 11 - protected $accountDomain; 12 10 protected $accountSecret; 13 - protected $accountID; 14 11 protected $displayName; 15 12 protected $username; 16 13 protected $realName; ··· 20 17 protected $profileImagePHID; 21 18 protected $properties = array(); 22 19 protected $providerConfigPHID; 20 + 21 + // TODO: Remove these (see T13493). These columns are obsolete and have 22 + // no readers and only trivial writers. 23 + protected $accountType; 24 + protected $accountDomain; 25 + protected $accountID; 23 26 24 27 private $profileImageFile = self::ATTACHABLE; 25 28 private $providerConfig = self::ATTACHABLE; ··· 60 63 'accountURI' => 'text255?', 61 64 ), 62 65 self::CONFIG_KEY_SCHEMA => array( 63 - 'account_details' => array( 64 - 'columns' => array('accountType', 'accountDomain', 'accountID'), 65 - 'unique' => true, 66 - ), 67 66 'key_user' => array( 68 67 'columns' => array('userPHID'), 69 68 ), 69 + 'key_provider' => array( 70 + 'columns' => array('providerConfigPHID', 'userPHID'), 71 + ), 70 72 ), 71 73 ) + parent::getConfiguration(); 72 74 } 73 75 74 - public function getProviderKey() { 75 - return $this->getAccountType().':'.$this->getAccountDomain(); 76 - } 77 - 78 76 public function save() { 79 77 if (!$this->getAccountSecret()) { 80 78 $this->setAccountSecret(Filesystem::readRandomCharacters(32)); ··· 144 142 } 145 143 146 144 return true; 147 - } 148 - 149 - public function getDisplayName() { 150 - if (strlen($this->displayName)) { 151 - return $this->displayName; 152 - } 153 - 154 - // TODO: Figure out how much identifying information we're going to show 155 - // to users about external accounts. For now, just show a string which is 156 - // clearly not an error, but don't disclose any identifying information. 157 - 158 - $map = array( 159 - 'email' => pht('Email User'), 160 - ); 161 - 162 - $type = $this->getAccountType(); 163 - 164 - return idx($map, $type, pht('"%s" User', $type)); 165 145 } 166 146 167 147 public function attachProviderConfig(PhabricatorAuthProviderConfig $config) {