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

Allow objects to be put in an "MFA required for all interactions" mode, and support "MFA required" statuses in Maniphest

Summary:
Depends on D19898. Ref T13222. See PHI873. Allow objects to opt into an "MFA is required for all edits" mode.

Put tasks in this mode if they're in a status that specifies it is an `mfa` status.

This is still a little rough for now:

- There's no UI hint that you'll have to MFA. I'll likely add some hinting in a followup.
- All edits currently require MFA, even subscribe/unsubscribe. We could maybe relax this if it's an issue.

Test Plan:
- Edited an MFA-required object via comments, edit forms, and most/all of the extensions. These prompted for MFA, then worked correctly.
- Tried to edit via Conduit, failed with a reasonably comprehensible error.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

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

+142 -8
+6
src/__phutil_library_map__.php
··· 1731 1731 'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php', 1732 1732 'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php', 1733 1733 'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php', 1734 + 'ManiphestTaskMFAEngine' => 'applications/maniphest/engine/ManiphestTaskMFAEngine.php', 1734 1735 'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php', 1735 1736 'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php', 1736 1737 'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php', ··· 2977 2978 'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php', 2978 2979 'PhabricatorEditEngineLock' => 'applications/transactions/editengine/PhabricatorEditEngineLock.php', 2979 2980 'PhabricatorEditEngineLockableInterface' => 'applications/transactions/editengine/PhabricatorEditEngineLockableInterface.php', 2981 + 'PhabricatorEditEngineMFAEngine' => 'applications/transactions/editengine/PhabricatorEditEngineMFAEngine.php', 2982 + 'PhabricatorEditEngineMFAInterface' => 'applications/transactions/editengine/PhabricatorEditEngineMFAInterface.php', 2980 2983 'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php', 2981 2984 'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php', 2982 2985 'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php', ··· 7298 7301 'DoorkeeperBridgedObjectInterface', 7299 7302 'PhabricatorEditEngineSubtypeInterface', 7300 7303 'PhabricatorEditEngineLockableInterface', 7304 + 'PhabricatorEditEngineMFAInterface', 7301 7305 ), 7302 7306 'ManiphestTaskAssignHeraldAction' => 'HeraldAction', 7303 7307 'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction', ··· 7336 7340 'ManiphestTaskListController' => 'ManiphestController', 7337 7341 'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType', 7338 7342 'ManiphestTaskListView' => 'ManiphestView', 7343 + 'ManiphestTaskMFAEngine' => 'PhabricatorEditEngineMFAEngine', 7339 7344 'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver', 7340 7345 'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship', 7341 7346 'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType', ··· 8754 8759 'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule', 8755 8760 'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController', 8756 8761 'PhabricatorEditEngineLock' => 'Phobject', 8762 + 'PhabricatorEditEngineMFAEngine' => 'Phobject', 8757 8763 'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction', 8758 8764 'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem', 8759 8765 'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+2
src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
··· 212 212 status. 213 213 - `locked` //Optional bool.// Lock tasks in this status, preventing users 214 214 from commenting. 215 + - `mfa` //Optional bool.// Require all edits to this task to be signed with 216 + multi-factor authentication. 215 217 216 218 Statuses will appear in the UI in the order specified. Note the status marked 217 219 `special` as `duplicate` is not settable directly and will not appear in UI
+5
src/applications/maniphest/constants/ManiphestTaskStatus.php
··· 160 160 return self::getStatusAttribute($status, 'locked', false); 161 161 } 162 162 163 + public static function isMFAStatus($status) { 164 + return self::getStatusAttribute($status, 'mfa', false); 165 + } 166 + 163 167 public static function getStatusActionName($status) { 164 168 return self::getStatusAttribute($status, 'name.action'); 165 169 } ··· 282 286 'disabled' => 'optional bool', 283 287 'claim' => 'optional bool', 284 288 'locked' => 'optional bool', 289 + 'mfa' => 'optional bool', 285 290 )); 286 291 } 287 292
+11
src/applications/maniphest/engine/ManiphestTaskMFAEngine.php
··· 1 + <?php 2 + 3 + final class ManiphestTaskMFAEngine 4 + extends PhabricatorEditEngineMFAEngine { 5 + 6 + public function shouldRequireMFA() { 7 + $status = $this->getObject()->getStatus(); 8 + return ManiphestTaskStatus::isMFAStatus($status); 9 + } 10 + 11 + }
+10 -1
src/applications/maniphest/storage/ManiphestTask.php
··· 19 19 PhabricatorFerretInterface, 20 20 DoorkeeperBridgedObjectInterface, 21 21 PhabricatorEditEngineSubtypeInterface, 22 - PhabricatorEditEngineLockableInterface { 22 + PhabricatorEditEngineLockableInterface, 23 + PhabricatorEditEngineMFAInterface { 23 24 24 25 const MARKUP_FIELD_DESCRIPTION = 'markup:desc'; 25 26 ··· 617 618 618 619 public function newFerretEngine() { 619 620 return new ManiphestTaskFerretEngine(); 621 + } 622 + 623 + 624 + /* -( PhabricatorEditEngineMFAInterface )---------------------------------- */ 625 + 626 + 627 + public function newEditEngineMFAEngine() { 628 + return new ManiphestTaskMFAEngine(); 620 629 } 621 630 622 631 }
+2 -1
src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php
··· 8 8 $phid = $request->getURIData('phid'); 9 9 $action = $request->getURIData('action'); 10 10 11 - if (!$request->isFormPost()) { 11 + if (!$request->isFormOrHisecPost()) { 12 12 return new Aphront400Response(); 13 13 } 14 14 ··· 73 73 74 74 $editor = id($object->getApplicationTransactionEditor()) 75 75 ->setActor($viewer) 76 + ->setCancelURI($handle->getURI()) 76 77 ->setContinueOnNoEffect(true) 77 78 ->setContinueOnMissingFields(true) 78 79 ->setContentSourceFromRequest($request);
+1 -1
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 1041 1041 } 1042 1042 1043 1043 $validation_exception = null; 1044 - if ($request->isFormPost() && $request->getBool('editEngine')) { 1044 + if ($request->isFormOrHisecPost() && $request->getBool('editEngine')) { 1045 1045 $submit_fields = $fields; 1046 1046 1047 1047 foreach ($submit_fields as $key => $field) {
+39
src/applications/transactions/editengine/PhabricatorEditEngineMFAEngine.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorEditEngineMFAEngine 4 + extends Phobject { 5 + 6 + private $object; 7 + private $viewer; 8 + 9 + public function setObject(PhabricatorEditEngineMFAInterface $object) { 10 + $this->object = $object; 11 + return $this; 12 + } 13 + 14 + public function getObject() { 15 + return $this->object; 16 + } 17 + 18 + public function setViewer(PhabricatorUser $viewer) { 19 + $this->viewer = $viewer; 20 + return $this; 21 + } 22 + 23 + public function getViewer() { 24 + if (!$this->viewer) { 25 + throw new PhutilInvalidStateException('setViewer'); 26 + } 27 + 28 + return $this->viewer; 29 + } 30 + 31 + final public static function newEngineForObject( 32 + PhabricatorEditEngineMFAInterface $object) { 33 + return $object->newEditEngineMFAEngine() 34 + ->setObject($object); 35 + } 36 + 37 + abstract public function shouldRequireMFA(); 38 + 39 + }
+7
src/applications/transactions/editengine/PhabricatorEditEngineMFAInterface.php
··· 1 + <?php 2 + 3 + interface PhabricatorEditEngineMFAInterface { 4 + 5 + public function newEditEngineMFAEngine(); 6 + 7 + }
+59 -5
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 953 953 $this->isNewObject = ($object->getPHID() === null); 954 954 955 955 $this->validateEditParameters($object, $xactions); 956 + $xactions = $this->newMFATransactions($object, $xactions); 956 957 957 958 $actor = $this->requireActor(); 958 959 ··· 4825 4826 4826 4827 $request = $this->getRequest(); 4827 4828 if ($request === null) { 4828 - throw new Exception( 4829 - pht( 4830 - 'This transaction group requires MFA to apply, but the Editor was '. 4831 - 'not configured with a Request. This workflow can not perform an '. 4832 - 'MFA check.')); 4829 + $source_type = $this->getContentSource()->getSourceTypeConstant(); 4830 + $conduit_type = PhabricatorConduitContentSource::SOURCECONST; 4831 + $is_conduit = ($source_type === $conduit_type); 4832 + if ($is_conduit) { 4833 + throw new Exception( 4834 + pht( 4835 + 'This transaction group requires MFA to apply, but you can not '. 4836 + 'provide an MFA response via Conduit. Edit this object via the '. 4837 + 'web UI.')); 4838 + } else { 4839 + throw new Exception( 4840 + pht( 4841 + 'This transaction group requires MFA to apply, but the Editor was '. 4842 + 'not configured with a Request. This workflow can not perform an '. 4843 + 'MFA check.')); 4844 + } 4833 4845 } 4834 4846 4835 4847 $cancel_uri = $this->getCancelURI(); ··· 4848 4860 foreach ($xactions as $xaction) { 4849 4861 $xaction->setIsMFATransaction(true); 4850 4862 } 4863 + } 4864 + 4865 + private function newMFATransactions( 4866 + PhabricatorLiskDAO $object, 4867 + array $xactions) { 4868 + 4869 + $is_mfa = ($object instanceof PhabricatorEditEngineMFAInterface); 4870 + if (!$is_mfa) { 4871 + return $xactions; 4872 + } 4873 + 4874 + $engine = PhabricatorEditEngineMFAEngine::newEngineForObject($object) 4875 + ->setViewer($this->getActor()); 4876 + $require_mfa = $engine->shouldRequireMFA(); 4877 + 4878 + if (!$require_mfa) { 4879 + return $xactions; 4880 + } 4881 + 4882 + $type_mfa = PhabricatorTransactions::TYPE_MFA; 4883 + 4884 + $has_mfa = false; 4885 + foreach ($xactions as $xaction) { 4886 + if ($xaction->getTransactionType() === $type_mfa) { 4887 + $has_mfa = true; 4888 + break; 4889 + } 4890 + } 4891 + 4892 + if ($has_mfa) { 4893 + return $xactions; 4894 + } 4895 + 4896 + $template = $object->getApplicationTransactionTemplate(); 4897 + 4898 + $mfa_xaction = id(clone $template) 4899 + ->setTransactionType($type_mfa) 4900 + ->setNewValue(true); 4901 + 4902 + array_unshift($xactions, $mfa_xaction); 4903 + 4904 + return $xactions; 4851 4905 } 4852 4906 4853 4907 }