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

When a comment was signed with MFA, require MFA to edit it

Summary:
Ref PHI1173. Currently, you can edit an MFA'd comment without redoing MFA. This is inconsistent with the intent of the MFA badge, since it means an un-MFA'd comment may have an "MFA" badge on it.

Instead, implement these rules:

- If a comment was signed with MFA, you MUST MFA to edit it.
- When removing a comment, add an extra MFA prompt if the user has MFA. This one isn't strictly required, this action is just very hard to undo and seems reasonable to MFA.

Test Plan:
- Made normal comments and MFA comments.
- Edited normal comments and MFA comments (got prompted).
- Removed normal comments and MFA comments (prompted in both cases).
- Tried to edit an MFA comment without MFA on my account, got a hard "MFA absolutely required" failure.

Reviewers: amckinley

Reviewed By: amckinley

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

+151 -11
+21 -6
src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php
··· 29 29 $handles = $viewer->loadHandles(array($phid)); 30 30 $obj_handle = $handles[$phid]; 31 31 32 - if ($request->isDialogFormPost()) { 32 + $done_uri = $obj_handle->getURI(); 33 + 34 + if ($request->isFormOrHisecPost()) { 33 35 $text = $request->getStr('text'); 34 36 35 37 $comment = $xaction->getApplicationTransactionCommentObject(); ··· 41 43 $editor = id(new PhabricatorApplicationTransactionCommentEditor()) 42 44 ->setActor($viewer) 43 45 ->setContentSource(PhabricatorContentSource::newFromRequest($request)) 46 + ->setRequest($request) 47 + ->setCancelURI($done_uri) 44 48 ->applyEdit($xaction, $comment); 45 49 46 50 if ($request->isAjax()) { 47 51 return id(new AphrontAjaxResponse())->setContent(array()); 48 52 } else { 49 - return id(new AphrontReloadResponse())->setURI($obj_handle->getURI()); 53 + return id(new AphrontReloadResponse())->setURI($done_uri); 50 54 } 51 55 } 52 56 57 + $errors = array(); 58 + if ($xaction->getIsMFATransaction()) { 59 + $message = pht( 60 + 'This comment was signed with MFA, so you will be required to '. 61 + 'provide MFA credentials to make changes.'); 62 + 63 + $errors[] = id(new PHUIInfoView()) 64 + ->setSeverity(PHUIInfoView::SEVERITY_MFA) 65 + ->setErrors(array($message)); 66 + } 67 + 53 68 $form = id(new AphrontFormView()) 54 69 ->setUser($viewer) 55 70 ->setFullWidth(true) 56 71 ->appendControl( 57 72 id(new PhabricatorRemarkupControl()) 58 - ->setName('text') 59 - ->setValue($xaction->getComment()->getContent())); 73 + ->setName('text') 74 + ->setValue($xaction->getComment()->getContent())); 60 75 61 76 return $this->newDialog() 62 77 ->setTitle(pht('Edit Comment')) 63 - ->addHiddenInput('anchor', $request->getStr('anchor')) 78 + ->appendChild($errors) 64 79 ->appendForm($form) 65 80 ->addSubmitButton(pht('Save Changes')) 66 - ->addCancelButton($obj_handle->getURI()); 81 + ->addCancelButton($done_uri); 67 82 } 68 83 69 84 }
+7 -4
src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php
··· 30 30 ->withPHIDs(array($obj_phid)) 31 31 ->executeOne(); 32 32 33 - if ($request->isDialogFormPost()) { 33 + $done_uri = $obj_handle->getURI(); 34 + 35 + if ($request->isFormOrHisecPost()) { 34 36 $comment = $xaction->getApplicationTransactionCommentObject() 35 37 ->setContent('') 36 38 ->setIsRemoved(true); 37 39 38 40 $editor = id(new PhabricatorApplicationTransactionCommentEditor()) 39 41 ->setActor($viewer) 42 + ->setRequest($request) 43 + ->setCancelURI($done_uri) 40 44 ->setContentSource(PhabricatorContentSource::newFromRequest($request)) 41 45 ->applyEdit($xaction, $comment); 42 46 43 47 if ($request->isAjax()) { 44 48 return id(new AphrontAjaxResponse())->setContent(array()); 45 49 } else { 46 - return id(new AphrontReloadResponse())->setURI($obj_handle->getURI()); 50 + return id(new AphrontReloadResponse())->setURI($done_uri); 47 51 } 48 52 } 49 53 ··· 54 58 ->setTitle(pht('Remove Comment')); 55 59 56 60 $dialog 57 - ->addHiddenInput('anchor', $request->getStr('anchor')) 58 61 ->appendParagraph( 59 62 pht( 60 63 "Removing a comment prevents anyone (including you) from reading ". ··· 65 68 66 69 $dialog 67 70 ->addSubmitButton(pht('Remove Comment')) 68 - ->addCancelButton($obj_handle->getURI()); 71 + ->addCancelButton($done_uri); 69 72 70 73 return $dialog; 71 74 }
+121
src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php
··· 5 5 6 6 private $contentSource; 7 7 private $actingAsPHID; 8 + private $request; 9 + private $cancelURI; 10 + private $isNewComment; 8 11 9 12 public function setActingAsPHID($acting_as_phid) { 10 13 $this->actingAsPHID = $acting_as_phid; ··· 27 30 return $this->contentSource; 28 31 } 29 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 + 30 60 /** 31 61 * Edit a transaction's comment. This method effects the required create, 32 62 * update or delete to set the transaction's comment to the provided comment. ··· 38 68 $this->validateEdit($xaction, $comment); 39 69 40 70 $actor = $this->requireActor(); 71 + 72 + $this->applyMFAChecks($xaction, $comment); 41 73 42 74 $comment->setContentSource($this->getContentSource()); 43 75 $comment->setAuthorPHID($this->getActingAsPHID()); ··· 160 192 } 161 193 } 162 194 195 + private function applyMFAChecks( 196 + PhabricatorApplicationTransaction $xaction, 197 + PhabricatorApplicationTransactionComment $comment) { 198 + $actor = $this->requireActor(); 199 + 200 + // We don't do any MFA checks here when you're creating a comment for the 201 + // first time (the parent editor handles them for us), so we can just bail 202 + // out if this is the creation flow. 203 + if ($this->getIsNewComment()) { 204 + return; 205 + } 206 + 207 + $request = $this->getRequest(); 208 + if (!$request) { 209 + throw new PhutilInvalidStateException('setRequest'); 210 + } 211 + 212 + $cancel_uri = $this->getCancelURI(); 213 + if (!strlen($cancel_uri)) { 214 + throw new PhutilInvalidStateException('setCancelURI'); 215 + } 216 + 217 + // If you're deleting a comment, we try to prompt you for MFA if you have 218 + // it configured, but do not require that you have it configured. In most 219 + // cases, this is administrators removing content. 220 + 221 + // See PHI1173. If you're editing a comment you authored and the original 222 + // comment was signed with MFA, you MUST have MFA on your account and you 223 + // MUST sign the edit with MFA. Otherwise, we can end up with an MFA badge 224 + // on different content than what was signed. 225 + 226 + $want_mfa = false; 227 + $need_mfa = false; 228 + 229 + if ($comment->getIsRemoved()) { 230 + // Try to prompt on removal. 231 + $want_mfa = true; 232 + } 233 + 234 + if ($xaction->getIsMFATransaction()) { 235 + if ($actor->getPHID() === $xaction->getAuthorPHID()) { 236 + // Strictly require MFA if the original transaction was signed and 237 + // you're the author. 238 + $want_mfa = true; 239 + $need_mfa = true; 240 + } 241 + } 242 + 243 + if (!$want_mfa) { 244 + return; 245 + } 246 + 247 + if ($need_mfa) { 248 + $factors = id(new PhabricatorAuthFactorConfigQuery()) 249 + ->setViewer($actor) 250 + ->withUserPHIDs(array($this->getActingAsPHID())) 251 + ->withFactorProviderStatuses( 252 + array( 253 + PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE, 254 + PhabricatorAuthFactorProviderStatus::STATUS_DEPRECATED, 255 + )) 256 + ->execute(); 257 + if (!$factors) { 258 + $error = new PhabricatorApplicationTransactionValidationError( 259 + $xaction->getTransactionType(), 260 + pht('No MFA'), 261 + pht( 262 + 'This comment was signed with MFA, so edits to it must also be '. 263 + 'signed with MFA. You do not have any MFA factors attached to '. 264 + 'your account, so you can not sign this edit. Add MFA to your '. 265 + 'account in Settings.'), 266 + $xaction); 267 + 268 + throw new PhabricatorApplicationTransactionValidationException( 269 + array( 270 + $error, 271 + )); 272 + } 273 + } 274 + 275 + $workflow_key = sprintf( 276 + 'comment.edit(%s, %d)', 277 + $xaction->getPHID(), 278 + $xaction->getComment()->getID()); 279 + 280 + $hisec_token = id(new PhabricatorAuthSessionEngine()) 281 + ->setWorkflowKey($workflow_key) 282 + ->requireHighSecurityToken($actor, $request, $cancel_uri); 283 + } 163 284 164 285 }
+2 -1
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 1113 1113 $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor()) 1114 1114 ->setActor($actor) 1115 1115 ->setActingAsPHID($this->getActingAsPHID()) 1116 - ->setContentSource($this->getContentSource()); 1116 + ->setContentSource($this->getContentSource()) 1117 + ->setIsNewComment(true); 1117 1118 1118 1119 if (!$transaction_open) { 1119 1120 $object->openTransaction();