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

Update repository identities after all mutations to users and email addresses

Summary:
Ref T13444. Currently, many mutations to users and email addresses (particularly: user creation; and user and address destruction) do not propagate properly to repository identities.

Add hooks to all mutation workflows so repository identities get rebuilt properly when users are created, email addresses are removed, users or email addresses are destroyed, or email addresses are reassigned.

Test Plan:
- Added random email address to account, removed it.
- Added unassociated email address to account, saw identity update (and associate).
- Removed it, saw identity update (and disassociate).
- Registered an account with an unassociated email address, saw identity update (and associate).
- Destroyed the account, saw identity update (and disassociate).
- Added address X to account A, unverified.
- Invited address X.
- Clicked invite link as account B.
- Confirmed desire to steal address.
- Saw identity update and reassociate.

Maniphest Tasks: T13444

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

+161 -26
+2
src/__phutil_library_map__.php
··· 984 984 'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php', 985 985 'DiffusionRepositoryFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryFunctionDatasource.php', 986 986 'DiffusionRepositoryHistoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php', 987 + 'DiffusionRepositoryIdentityDestructionEngineExtension' => 'applications/diffusion/identity/DiffusionRepositoryIdentityDestructionEngineExtension.php', 987 988 'DiffusionRepositoryIdentityEditor' => 'applications/diffusion/editor/DiffusionRepositoryIdentityEditor.php', 988 989 'DiffusionRepositoryIdentityEngine' => 'applications/diffusion/identity/DiffusionRepositoryIdentityEngine.php', 989 990 'DiffusionRepositoryIdentitySearchEngine' => 'applications/diffusion/query/DiffusionRepositoryIdentitySearchEngine.php', ··· 6968 6969 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryManageController', 6969 6970 'DiffusionRepositoryFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 6970 6971 'DiffusionRepositoryHistoryManagementPanel' => 'DiffusionRepositoryManagementPanel', 6972 + 'DiffusionRepositoryIdentityDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 6971 6973 'DiffusionRepositoryIdentityEditor' => 'PhabricatorApplicationTransactionEditor', 6972 6974 'DiffusionRepositoryIdentityEngine' => 'Phobject', 6973 6975 'DiffusionRepositoryIdentitySearchEngine' => 'PhabricatorApplicationSearchEngine',
+40
src/applications/diffusion/identity/DiffusionRepositoryIdentityDestructionEngineExtension.php
··· 1 + <?php 2 + 3 + final class DiffusionRepositoryIdentityDestructionEngineExtension 4 + extends PhabricatorDestructionEngineExtension { 5 + 6 + const EXTENSIONKEY = 'repository-identities'; 7 + 8 + public function getExtensionName() { 9 + return pht('Repository Identities'); 10 + } 11 + 12 + public function didDestroyObject( 13 + PhabricatorDestructionEngine $engine, 14 + $object) { 15 + 16 + // When users or email addresses are destroyed, queue a task to update 17 + // any repository identities that are associated with them. See T13444. 18 + 19 + $related_phids = array(); 20 + $email_addresses = array(); 21 + 22 + if ($object instanceof PhabricatorUser) { 23 + $related_phids[] = $object->getPHID(); 24 + } 25 + 26 + if ($object instanceof PhabricatorUserEmail) { 27 + $email_addresses[] = $object->getAddress(); 28 + } 29 + 30 + if ($related_phids || $email_addresses) { 31 + PhabricatorWorker::scheduleTask( 32 + 'PhabricatorRepositoryIdentityChangeWorker', 33 + array( 34 + 'relatedPHIDs' => $related_phids, 35 + 'emailAddresses' => $email_addresses, 36 + )); 37 + } 38 + } 39 + 40 + }
+8
src/applications/diffusion/identity/DiffusionRepositoryIdentityEngine.php
··· 95 95 return $identity; 96 96 } 97 97 98 + public function didUpdateEmailAddress($raw_address) { 99 + PhabricatorWorker::scheduleTask( 100 + 'PhabricatorRepositoryIdentityChangeWorker', 101 + array( 102 + 'emailAddresses' => array($raw_address), 103 + )); 104 + } 105 + 98 106 }
+11 -7
src/applications/people/editor/PhabricatorUserEditor.php
··· 89 89 $this->didVerifyEmail($user, $email); 90 90 } 91 91 92 + id(new DiffusionRepositoryIdentityEngine()) 93 + ->didUpdateEmailAddress($email->getAddress()); 94 + 92 95 return $this; 93 96 } 94 97 ··· 202 205 $user->endWriteLocking(); 203 206 $user->saveTransaction(); 204 207 205 - // Try and match this new address against unclaimed `RepositoryIdentity`s 206 - PhabricatorWorker::scheduleTask( 207 - 'PhabricatorRepositoryIdentityChangeWorker', 208 - array('userPHID' => $user->getPHID()), 209 - array('objectPHID' => $user->getPHID())); 208 + id(new DiffusionRepositoryIdentityEngine()) 209 + ->didUpdateEmailAddress($email->getAddress()); 210 210 211 211 return $this; 212 212 } ··· 241 241 throw new Exception(pht('Email not owned by user!')); 242 242 } 243 243 244 - id(new PhabricatorDestructionEngine()) 244 + $destruction_engine = id(new PhabricatorDestructionEngine()) 245 + ->setWaitToFinalizeDestruction(true) 245 246 ->destroyObject($email); 246 247 247 248 $log = PhabricatorUserLog::initializeNewLog( ··· 255 256 $user->saveTransaction(); 256 257 257 258 $this->revokePasswordResetLinks($user); 259 + $destruction_engine->finalizeDestruction(); 258 260 259 261 return $this; 260 262 } ··· 327 329 } 328 330 $email->sendNewPrimaryEmail($user); 329 331 330 - 331 332 $this->revokePasswordResetLinks($user); 332 333 333 334 return $this; ··· 441 442 442 443 $user->endWriteLocking(); 443 444 $user->saveTransaction(); 445 + 446 + id(new DiffusionRepositoryIdentityEngine()) 447 + ->didUpdateEmailAddress($email->getAddress()); 444 448 } 445 449 446 450
+40 -16
src/applications/repository/worker/PhabricatorRepositoryIdentityChangeWorker.php
··· 6 6 protected function doWork() { 7 7 $viewer = PhabricatorUser::getOmnipotentUser(); 8 8 9 - $task_data = $this->getTaskData(); 10 - $user_phid = idx($task_data, 'userPHID'); 9 + $related_phids = $this->getTaskDataValue('relatedPHIDs'); 10 + $email_addresses = $this->getTaskDataValue('emailAddresses'); 11 11 12 - $user = id(new PhabricatorPeopleQuery()) 13 - ->withPHIDs(array($user_phid)) 14 - ->setViewer($viewer) 15 - ->executeOne(); 12 + // Retain backward compatibility with older tasks which may still be in 13 + // queue. Previously, this worker accepted a single "userPHID". See 14 + // T13444. This can be removed in some future version of Phabricator once 15 + // these tasks have likely flushed out of queue. 16 + $legacy_phid = $this->getTaskDataValue('userPHID'); 17 + if ($legacy_phid) { 18 + if (!is_array($related_phids)) { 19 + $related_phids = array(); 20 + } 21 + $related_phids[] = $legacy_phid; 22 + } 16 23 17 - $emails = id(new PhabricatorUserEmail())->loadAllWhere( 18 - 'userPHID = %s', 19 - $user->getPHID()); 24 + // Note that we may arrive in this worker after the associated objects 25 + // have already been destroyed, so we can't (and shouldn't) verify that 26 + // PHIDs correspond to real objects. If you "bin/remove destroy" a user, 27 + // we'll end up here with a now-bogus user PHID that we should 28 + // disassociate from identities. 20 29 21 - $identity_engine = id(new DiffusionRepositoryIdentityEngine()) 22 - ->setViewer($viewer); 30 + $identity_map = array(); 23 31 24 - foreach ($emails as $email) { 32 + if ($related_phids) { 25 33 $identities = id(new PhabricatorRepositoryIdentityQuery()) 26 34 ->setViewer($viewer) 27 - ->withEmailAddresses(array($email->getAddress())) 35 + ->withRelatedPHIDs($related_phids) 28 36 ->execute(); 37 + $identity_map += mpull($identities, null, 'getPHID'); 38 + } 29 39 30 - foreach ($identities as $identity) { 31 - $identity_engine->newUpdatedIdentity($identity); 32 - } 40 + if ($email_addresses) { 41 + $identities = id(new PhabricatorRepositoryIdentityQuery()) 42 + ->setViewer($viewer) 43 + ->withEmailAddresses($email_addresses) 44 + ->execute(); 45 + $identity_map += mpull($identities, null, 'getPHID'); 46 + } 47 + 48 + // If we didn't find any related identities, we're all set. 49 + if (!$identity_map) { 50 + return; 51 + } 52 + 53 + $identity_engine = id(new DiffusionRepositoryIdentityEngine()) 54 + ->setViewer($viewer); 55 + foreach ($identity_map as $identity) { 56 + $identity_engine->newUpdatedIdentity($identity); 33 57 } 34 58 } 35 59
+50 -1
src/applications/system/engine/PhabricatorDestructionEngine.php
··· 5 5 private $rootLogID; 6 6 private $collectNotes; 7 7 private $notes = array(); 8 + private $depth = 0; 9 + private $destroyedObjects = array(); 10 + private $waitToFinalizeDestruction = false; 8 11 9 12 public function setCollectNotes($collect_notes) { 10 13 $this->collectNotes = $collect_notes; ··· 19 22 return PhabricatorUser::getOmnipotentUser(); 20 23 } 21 24 25 + public function setWaitToFinalizeDestruction($wait) { 26 + $this->waitToFinalizeDestruction = $wait; 27 + return $this; 28 + } 29 + 30 + public function getWaitToFinalizeDestruction() { 31 + return $this->waitToFinalizeDestruction; 32 + } 33 + 22 34 public function destroyObject(PhabricatorDestructibleInterface $object) { 35 + $this->depth++; 36 + 23 37 $log = id(new PhabricatorSystemDestructionLog()) 24 - ->setEpoch(time()) 38 + ->setEpoch(PhabricatorTime::getNow()) 25 39 ->setObjectClass(get_class($object)); 26 40 27 41 if ($this->rootLogID) { ··· 73 87 foreach ($extensions as $key => $extension) { 74 88 $extension->destroyObject($this, $object); 75 89 } 90 + 91 + $this->destroyedObjects[] = $object; 76 92 } 93 + 94 + $this->depth--; 95 + 96 + // If this is a root-level invocation of "destroyObject()", flush the 97 + // queue of destroyed objects and fire "didDestroyObject()" hooks. This 98 + // hook allows extensions to do things like queue cache updates which 99 + // might race if we fire them during object destruction. 100 + 101 + if (!$this->depth) { 102 + if (!$this->getWaitToFinalizeDestruction()) { 103 + $this->finalizeDestruction(); 104 + } 105 + } 106 + 107 + return $this; 108 + } 109 + 110 + public function finalizeDestruction() { 111 + $extensions = PhabricatorDestructionEngineExtension::getAllExtensions(); 112 + 113 + foreach ($this->destroyedObjects as $object) { 114 + foreach ($extensions as $extension) { 115 + if (!$extension->canDestroyObject($this, $object)) { 116 + continue; 117 + } 118 + 119 + $extension->didDestroyObject($this, $object); 120 + } 121 + } 122 + 123 + $this->destroyedObjects = array(); 124 + 125 + return $this; 77 126 } 78 127 79 128 private function getObjectPHID($object) {
+10 -2
src/applications/system/engine/PhabricatorDestructionEngineExtension.php
··· 14 14 return true; 15 15 } 16 16 17 - abstract public function destroyObject( 17 + public function destroyObject( 18 18 PhabricatorDestructionEngine $engine, 19 - $object); 19 + $object) { 20 + return null; 21 + } 22 + 23 + public function didDestroyObject( 24 + PhabricatorDestructionEngine $engine, 25 + $object) { 26 + return null; 27 + } 20 28 21 29 final public static function getAllExtensions() { 22 30 return id(new PhutilClassMapQuery())