@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
at recaptime-dev/main 233 lines 7.4 kB view raw
1<?php 2 3final class DiffusionUpdateObjectAfterCommitWorker 4 extends PhabricatorWorker { 5 6 private $properties; 7 8 protected function getViewer() { 9 return PhabricatorUser::getOmnipotentUser(); 10 } 11 12 protected function doWork() { 13 $viewer = $this->getViewer(); 14 $data = $this->getTaskData(); 15 16 $commit_phid = idx($data, 'commitPHID'); 17 if (!$commit_phid) { 18 throw new PhabricatorWorkerPermanentFailureException( 19 pht('No "commitPHID" in task data.')); 20 } 21 22 $commit = id(new DiffusionCommitQuery()) 23 ->setViewer($viewer) 24 ->withPHIDs(array($commit_phid)) 25 ->needIdentities(true) 26 ->executeOne(); 27 if (!$commit) { 28 throw new PhabricatorWorkerPermanentFailureException( 29 pht( 30 'Unable to load commit "%s".', 31 $commit_phid)); 32 } 33 34 $object_phid = idx($data, 'objectPHID'); 35 if (!$object_phid) { 36 throw new PhabricatorWorkerPermanentFailureException( 37 pht('No "objectPHID" in task data.')); 38 } 39 40 $object = id(new PhabricatorObjectQuery()) 41 ->setViewer($viewer) 42 ->withPHIDs(array($object_phid)) 43 ->executeOne(); 44 if (!$object) { 45 throw new PhabricatorWorkerPermanentFailureException( 46 pht( 47 'Unable to load object "%s".', 48 $object_phid)); 49 } 50 51 $properties = idx($data, 'properties', array()); 52 $this->properties = $properties; 53 54 if ($object instanceof ManiphestTask) { 55 $this->updateTask($commit, $object); 56 } else if ($object instanceof DifferentialRevision) { 57 $this->updateRevision($commit, $object); 58 } 59 } 60 61 protected function getUpdateProperty($key, $default = null) { 62 return idx($this->properties, $key, $default); 63 } 64 65 protected function getActingPHID(PhabricatorRepositoryCommit $commit) { 66 if ($commit->hasCommitterIdentity()) { 67 return $commit->getCommitterIdentity()->getIdentityDisplayPHID(); 68 } 69 70 if ($commit->hasAuthorIdentity()) { 71 return $commit->getAuthorIdentity()->getIdentityDisplayPHID(); 72 } 73 74 return id(new PhabricatorDiffusionApplication())->getPHID(); 75 } 76 77 protected function loadActingUser($acting_phid) { 78 // If we we were able to identify an author or committer for the commit, we 79 // try to act as that user when affecting other objects, like tasks marked 80 // with "Fixes Txxx". 81 82 // This helps to prevent mistakes where a user accidentally writes the 83 // wrong task IDs and affects tasks they can't see (and thus can't undo the 84 // status changes for). 85 86 // This is just a guard rail, not a security measure. An attacker can still 87 // forge another user's identity trivially by forging author or committer 88 // email addresses. 89 90 // We also let commits with unrecognized authors act on any task to make 91 // behavior less confusing for new installs, and any user can craft a 92 // commit with an unrecognized author and committer. 93 94 $viewer = $this->getViewer(); 95 96 $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; 97 if (phid_get_type($acting_phid) === $user_type) { 98 $acting_user = id(new PhabricatorPeopleQuery()) 99 ->setViewer($viewer) 100 ->withPHIDs(array($acting_phid)) 101 ->executeOne(); 102 if ($acting_user) { 103 return $acting_user; 104 } 105 } 106 107 return $viewer; 108 } 109 110 private function updateTask( 111 PhabricatorRepositoryCommit $commit, 112 ManiphestTask $task) { 113 114 $acting_phid = $this->getActingPHID($commit); 115 $acting_user = $this->loadActingUser($acting_phid); 116 117 $commit_phid = $commit->getPHID(); 118 119 $xactions = array(); 120 121 $xactions[] = $this->newEdgeTransaction( 122 $task, 123 $commit, 124 ManiphestTaskHasCommitEdgeType::EDGECONST); 125 126 $status = $this->getUpdateProperty('status'); 127 if ($status) { 128 $xactions[] = $task->getApplicationTransactionTemplate() 129 ->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE) 130 ->setMetadataValue('commitPHID', $commit_phid) 131 ->setNewValue($status); 132 } 133 134 $content_source = $this->newContentSource(); 135 136 $editor = $task->getApplicationTransactionEditor() 137 ->setActor($acting_user) 138 ->setActingAsPHID($acting_phid) 139 ->setContentSource($content_source) 140 ->setContinueOnNoEffect(true) 141 ->setContinueOnMissingFields(true) 142 ->addUnmentionablePHIDs(array($commit_phid)); 143 144 $editor->applyTransactions($task, $xactions); 145 } 146 147 private function updateRevision( 148 PhabricatorRepositoryCommit $commit, 149 DifferentialRevision $revision) { 150 151 $acting_phid = $this->getActingPHID($commit); 152 $acting_user = $this->loadActingUser($acting_phid); 153 154 // See T13625. The "Acting User" is the author of the commit based on the 155 // author string, or the Diffusion application PHID if we could not 156 // identify an author. 157 158 // This user may not be able to view the commit or the revision, and may 159 // also be unable to make API calls. Here, we execute queries and apply 160 // transactions as the omnipotent user. 161 162 // It would probably be better to use the acting user everywhere here, and 163 // exit gracefully if they can't see the revision (this is how the flow 164 // on tasks works). However, without a positive indicator in the UI 165 // explaining "no revision was updated because the author of this commit 166 // can't see anything", this might be fairly confusing, and break workflows 167 // which have worked historically. 168 169 // This isn't, per se, a policy violation (you can't get access to anything 170 // you don't already have access to by making commits that reference 171 // revisions, even if you can't see the commits or revisions), so just 172 // leave it for now. 173 174 $viewer = $this->getViewer(); 175 176 // Reload the revision to get the active diff, which is currently required 177 // by "updateRevisionWithCommit()". 178 $revision = id(new DifferentialRevisionQuery()) 179 ->setViewer($viewer) 180 ->withIDs(array($revision->getID())) 181 ->needActiveDiffs(true) 182 ->executeOne(); 183 184 $xactions = array(); 185 186 $xactions[] = $this->newEdgeTransaction( 187 $revision, 188 $commit, 189 DifferentialRevisionHasCommitEdgeType::EDGECONST); 190 191 $match_data = $this->getUpdateProperty('revisionMatchData'); 192 193 $type_close = DifferentialRevisionCloseTransaction::TRANSACTIONTYPE; 194 $xactions[] = $revision->getApplicationTransactionTemplate() 195 ->setTransactionType($type_close) 196 ->setNewValue(true) 197 ->setMetadataValue('isCommitClose', true) 198 ->setMetadataValue('revisionMatchData', $match_data) 199 ->setMetadataValue('commitPHID', $commit->getPHID()); 200 201 $extraction_engine = id(new DifferentialDiffExtractionEngine()) 202 ->setViewer($viewer) 203 ->setAuthorPHID($acting_phid); 204 205 $content_source = $this->newContentSource(); 206 207 $extraction_engine->updateRevisionWithCommit( 208 $revision, 209 $commit, 210 $xactions, 211 $content_source); 212 } 213 214 private function newEdgeTransaction( 215 $object, 216 PhabricatorRepositoryCommit $commit, 217 $edge_type) { 218 219 $commit_phid = $commit->getPHID(); 220 221 return $object->getApplicationTransactionTemplate() 222 ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 223 ->setMetadataValue('edge:type', $edge_type) 224 ->setNewValue( 225 array( 226 '+' => array( 227 $commit_phid => $commit_phid, 228 ), 229 )); 230 } 231 232 233}