@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 271 lines 8.1 kB view raw
1<?php 2 3final class PhabricatorApplicationTransactionCommentEditor 4 extends PhabricatorEditor { 5 6 private $contentSource; 7 private $actingAsPHID; 8 private $request; 9 private $cancelURI; 10 private $isNewComment; 11 12 public function setActingAsPHID($acting_as_phid) { 13 $this->actingAsPHID = $acting_as_phid; 14 return $this; 15 } 16 17 public function getActingAsPHID() { 18 if ($this->actingAsPHID) { 19 return $this->actingAsPHID; 20 } 21 return $this->getActor()->getPHID(); 22 } 23 24 public function setContentSource(PhabricatorContentSource $content_source) { 25 $this->contentSource = $content_source; 26 return $this; 27 } 28 29 public function getContentSource() { 30 return $this->contentSource; 31 } 32 33 public function setRequest(AphrontRequest $request) { 34 $this->request = $request; 35 return $this; 36 } 37 38 public function getRequest() { 39 return $this->request; 40 } 41 42 public function setCancelURI($cancel_uri) { 43 $this->cancelURI = $cancel_uri; 44 return $this; 45 } 46 47 public function getCancelURI() { 48 return $this->cancelURI; 49 } 50 51 public function setIsNewComment($is_new) { 52 $this->isNewComment = $is_new; 53 return $this; 54 } 55 56 public function getIsNewComment() { 57 return $this->isNewComment; 58 } 59 60 /** 61 * Edit a transaction's comment. This method effects the required create, 62 * update or delete to set the transaction's comment to the provided comment. 63 */ 64 public function applyEdit( 65 PhabricatorApplicationTransaction $xaction, 66 PhabricatorApplicationTransactionComment $comment) { 67 68 $this->validateEdit($xaction, $comment); 69 70 $actor = $this->requireActor(); 71 72 $this->applyMFAChecks($xaction, $comment); 73 74 $comment->setContentSource($this->getContentSource()); 75 $comment->setAuthorPHID($this->getActingAsPHID()); 76 77 // TODO: This needs to be more sophisticated once we have meta-policies. 78 $comment->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC); 79 $comment->setEditPolicy($this->getActingAsPHID()); 80 81 $xaction->openTransaction(); 82 $xaction->beginReadLocking(); 83 if ($xaction->getID()) { 84 $xaction->reload(); 85 } 86 87 $new_version = $xaction->getCommentVersion() + 1; 88 89 $comment->setCommentVersion($new_version); 90 $comment->setTransactionPHID($xaction->getPHID()); 91 $comment->save(); 92 93 $old_comment = $xaction->getComment(); 94 $comment->attachOldComment($old_comment); 95 96 $xaction->setCommentVersion($new_version); 97 $xaction->setCommentPHID($comment->getPHID()); 98 $xaction->setViewPolicy($comment->getViewPolicy()); 99 $xaction->setEditPolicy($comment->getEditPolicy()); 100 $xaction->save(); 101 $xaction->attachComment($comment); 102 103 // For comment edits, we need to make sure there are no automagical 104 // transactions like adding mentions or projects. 105 if ($new_version > 1) { 106 $object = id(new PhabricatorObjectQuery()) 107 ->withPHIDs(array($xaction->getObjectPHID())) 108 ->setViewer($this->getActor()) 109 ->executeOne(); 110 if ($object && 111 $object instanceof PhabricatorApplicationTransactionInterface) { 112 $editor = $object->getApplicationTransactionEditor(); 113 $editor->setActor($this->getActor()); 114 $support_xactions = $editor->getExpandedSupportTransactions( 115 $object, 116 $xaction); 117 if ($support_xactions) { 118 $editor 119 ->setContentSource($this->getContentSource()) 120 ->setContinueOnNoEffect(true) 121 ->setContinueOnMissingFields(true) 122 ->applyTransactions($object, $support_xactions); 123 } 124 } 125 } 126 $xaction->endReadLocking(); 127 $xaction->saveTransaction(); 128 129 return $this; 130 } 131 132 /** 133 * Validate that the edit is permissible, and the actor has permission to 134 * perform it. 135 */ 136 private function validateEdit( 137 PhabricatorApplicationTransaction $xaction, 138 PhabricatorApplicationTransactionComment $comment) { 139 140 if (!$xaction->getPHID()) { 141 throw new Exception( 142 pht( 143 'Transaction must have a PHID before calling %s!', 144 'applyEdit()')); 145 } 146 147 $type_comment = PhabricatorTransactions::TYPE_COMMENT; 148 if ($xaction->getTransactionType() == $type_comment) { 149 if ($comment->getPHID()) { 150 throw new Exception( 151 pht('Transaction comment must not yet have a PHID!')); 152 } 153 } 154 155 if (!$this->getContentSource()) { 156 throw new PhutilInvalidStateException('applyEdit'); 157 } 158 159 $actor = $this->requireActor(); 160 161 PhabricatorPolicyFilter::requireCapability( 162 $actor, 163 $xaction, 164 PhabricatorPolicyCapability::CAN_VIEW); 165 166 if ($comment->getIsRemoved() && $actor->getIsAdmin()) { 167 // NOTE: Administrators can remove comments by any user, and don't need 168 // to pass the edit check. 169 } else { 170 PhabricatorPolicyFilter::requireCapability( 171 $actor, 172 $xaction, 173 PhabricatorPolicyCapability::CAN_EDIT); 174 175 PhabricatorPolicyFilter::requireCanInteract( 176 $actor, 177 $xaction->getObject()); 178 } 179 } 180 181 private function applyMFAChecks( 182 PhabricatorApplicationTransaction $xaction, 183 PhabricatorApplicationTransactionComment $comment) { 184 $actor = $this->requireActor(); 185 186 // We don't do any MFA checks here when you're creating a comment for the 187 // first time (the parent editor handles them for us), so we can just bail 188 // out if this is the creation flow. 189 if ($this->getIsNewComment()) { 190 return; 191 } 192 193 $request = $this->getRequest(); 194 if (!$request) { 195 throw new PhutilInvalidStateException('setRequest'); 196 } 197 198 $cancel_uri = $this->getCancelURI(); 199 if (!strlen($cancel_uri)) { 200 throw new PhutilInvalidStateException('setCancelURI'); 201 } 202 203 // If you're deleting a comment, we try to prompt you for MFA if you have 204 // it configured, but do not require that you have it configured. In most 205 // cases, this is administrators removing content. 206 207 // See PHI1173. If you're editing a comment you authored and the original 208 // comment was signed with MFA, you MUST have MFA on your account and you 209 // MUST sign the edit with MFA. Otherwise, we can end up with an MFA badge 210 // on different content than what was signed. 211 212 $want_mfa = false; 213 $need_mfa = false; 214 215 if ($comment->getIsRemoved()) { 216 // Try to prompt on removal. 217 $want_mfa = true; 218 } 219 220 if ($xaction->getIsMFATransaction()) { 221 if ($actor->getPHID() === $xaction->getAuthorPHID()) { 222 // Strictly require MFA if the original transaction was signed and 223 // you're the author. 224 $want_mfa = true; 225 $need_mfa = true; 226 } 227 } 228 229 if (!$want_mfa) { 230 return; 231 } 232 233 if ($need_mfa) { 234 $factors = id(new PhabricatorAuthFactorConfigQuery()) 235 ->setViewer($actor) 236 ->withUserPHIDs(array($this->getActingAsPHID())) 237 ->withFactorProviderStatuses( 238 array( 239 PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE, 240 PhabricatorAuthFactorProviderStatus::STATUS_DEPRECATED, 241 )) 242 ->execute(); 243 if (!$factors) { 244 $error = new PhabricatorApplicationTransactionValidationError( 245 $xaction->getTransactionType(), 246 pht('No MFA'), 247 pht( 248 'This comment was signed with MFA, so edits to it must also be '. 249 'signed with MFA. You do not have any MFA factors attached to '. 250 'your account, so you can not sign this edit. Add MFA to your '. 251 'account in Settings.'), 252 $xaction); 253 254 throw new PhabricatorApplicationTransactionValidationException( 255 array( 256 $error, 257 )); 258 } 259 } 260 261 $workflow_key = sprintf( 262 'comment.edit(%s, %d)', 263 $xaction->getPHID(), 264 $xaction->getComment()->getID()); 265 266 $hisec_token = id(new PhabricatorAuthSessionEngine()) 267 ->setWorkflowKey($workflow_key) 268 ->requireHighSecurityToken($actor, $request, $cancel_uri); 269 } 270 271}