@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

Always require MFA to edit contact numbers

Summary:
Depends on D20023. Ref T13222. Although I think this isn't strictly necessary from a pure security perspective (since you can't modify the primary number while you have MFA SMS), it seems like a generally good idea.

This adds a slightly new MFA mode, where we want MFA if it's available but don't strictly require it.

Test Plan: Disabled, enabled, primaried, unprimaried, and edited contact numbers. With MFA enabled, got prompted for MFA. With no MFA, no prompts.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

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

+55 -6
+3
src/__phutil_library_map__.php
··· 2205 2205 'PhabricatorAuthContactNumberEditController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberEditController.php', 2206 2206 'PhabricatorAuthContactNumberEditEngine' => 'applications/auth/editor/PhabricatorAuthContactNumberEditEngine.php', 2207 2207 'PhabricatorAuthContactNumberEditor' => 'applications/auth/editor/PhabricatorAuthContactNumberEditor.php', 2208 + 'PhabricatorAuthContactNumberMFAEngine' => 'applications/auth/engine/PhabricatorAuthContactNumberMFAEngine.php', 2208 2209 'PhabricatorAuthContactNumberNumberTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php', 2209 2210 'PhabricatorAuthContactNumberPHIDType' => 'applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php', 2210 2211 'PhabricatorAuthContactNumberPrimaryController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberPrimaryController.php', ··· 7909 7910 'PhabricatorApplicationTransactionInterface', 7910 7911 'PhabricatorPolicyInterface', 7911 7912 'PhabricatorDestructibleInterface', 7913 + 'PhabricatorEditEngineMFAInterface', 7912 7914 ), 7913 7915 'PhabricatorAuthContactNumberController' => 'PhabricatorAuthController', 7914 7916 'PhabricatorAuthContactNumberDisableController' => 'PhabricatorAuthContactNumberController', 7915 7917 'PhabricatorAuthContactNumberEditController' => 'PhabricatorAuthContactNumberController', 7916 7918 'PhabricatorAuthContactNumberEditEngine' => 'PhabricatorEditEngine', 7917 7919 'PhabricatorAuthContactNumberEditor' => 'PhabricatorApplicationTransactionEditor', 7920 + 'PhabricatorAuthContactNumberMFAEngine' => 'PhabricatorEditEngineMFAEngine', 7918 7921 'PhabricatorAuthContactNumberNumberTransaction' => 'PhabricatorAuthContactNumberTransactionType', 7919 7922 'PhabricatorAuthContactNumberPHIDType' => 'PhabricatorPHIDType', 7920 7923 'PhabricatorAuthContactNumberPrimaryController' => 'PhabricatorAuthContactNumberController',
+3 -2
src/applications/auth/controller/contact/PhabricatorAuthContactNumberDisableController.php
··· 24 24 $id = $number->getID(); 25 25 $cancel_uri = $number->getURI(); 26 26 27 - if ($request->isFormPost()) { 27 + if ($request->isFormOrHisecPost()) { 28 28 $xactions = array(); 29 29 30 30 if ($is_disable) { ··· 42 42 ->setActor($viewer) 43 43 ->setContentSourceFromRequest($request) 44 44 ->setContinueOnNoEffect(true) 45 - ->setContinueOnMissingFields(true); 45 + ->setContinueOnMissingFields(true) 46 + ->setCancelURI($cancel_uri); 46 47 47 48 try { 48 49 $editor->applyTransactions($number, $xactions);
+3 -2
src/applications/auth/controller/contact/PhabricatorAuthContactNumberPrimaryController.php
··· 41 41 ->addCancelButton($cancel_uri); 42 42 } 43 43 44 - if ($request->isFormPost()) { 44 + if ($request->isFormOrHisecPost()) { 45 45 $xactions = array(); 46 46 47 47 $xactions[] = id(new PhabricatorAuthContactNumberTransaction()) ··· 53 53 ->setActor($viewer) 54 54 ->setContentSourceFromRequest($request) 55 55 ->setContinueOnNoEffect(true) 56 - ->setContinueOnMissingFields(true); 56 + ->setContinueOnMissingFields(true) 57 + ->setCancelURI($cancel_uri); 57 58 58 59 try { 59 60 $editor->applyTransactions($number, $xactions);
+10
src/applications/auth/engine/PhabricatorAuthContactNumberMFAEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthContactNumberMFAEngine 4 + extends PhabricatorEditEngineMFAEngine { 5 + 6 + public function shouldTryMFA() { 7 + return true; 8 + } 9 + 10 + }
+9 -1
src/applications/auth/storage/PhabricatorAuthContactNumber.php
··· 6 6 implements 7 7 PhabricatorApplicationTransactionInterface, 8 8 PhabricatorPolicyInterface, 9 - PhabricatorDestructibleInterface { 9 + PhabricatorDestructibleInterface, 10 + PhabricatorEditEngineMFAInterface { 10 11 11 12 protected $objectPHID; 12 13 protected $contactNumber; ··· 231 232 return new PhabricatorAuthContactNumberTransaction(); 232 233 } 233 234 235 + 236 + /* -( PhabricatorEditEngineMFAInterface )---------------------------------- */ 237 + 238 + 239 + public function newEditEngineMFAEngine() { 240 + return new PhabricatorAuthContactNumberMFAEngine(); 241 + } 234 242 235 243 }
+23 -1
src/applications/transactions/editengine/PhabricatorEditEngineMFAEngine.php
··· 34 34 ->setObject($object); 35 35 } 36 36 37 - abstract public function shouldRequireMFA(); 37 + /** 38 + * Do edits to this object REQUIRE that the user submit MFA? 39 + * 40 + * This is a strict requirement: users will need to add MFA to their accounts 41 + * if they don't already have it. 42 + * 43 + * @return bool True to strictly require MFA. 44 + */ 45 + public function shouldRequireMFA() { 46 + return false; 47 + } 48 + 49 + /** 50 + * Should edits to this object prompt for MFA if it's available? 51 + * 52 + * This is advisory: users without MFA on their accounts will be able to 53 + * perform edits without being required to add MFA. 54 + * 55 + * @return bool True to prompt for MFA if available. 56 + */ 57 + public function shouldTryMFA() { 58 + return false; 59 + } 38 60 39 61 }
+4
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 4916 4916 $require_mfa = $engine->shouldRequireMFA(); 4917 4917 4918 4918 if (!$require_mfa) { 4919 + $try_mfa = $engine->shouldTryMFA(); 4920 + if ($try_mfa) { 4921 + $this->setShouldRequireMFA(true); 4922 + } 4919 4923 return $xactions; 4920 4924 } 4921 4925