1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-03-29 04:28:12 +01:00

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
This commit is contained in:
epriestley 2018-12-18 07:01:20 -08:00
parent 3da9844564
commit d3c325c4fc
10 changed files with 142 additions and 8 deletions

View file

@ -1731,6 +1731,7 @@ phutil_register_library_map(array(
'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php', 'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php',
'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php', 'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php',
'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php', 'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php',
'ManiphestTaskMFAEngine' => 'applications/maniphest/engine/ManiphestTaskMFAEngine.php',
'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php', 'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php',
'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php', 'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php',
'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php', 'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php',
@ -2977,6 +2978,8 @@ phutil_register_library_map(array(
'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php', 'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php',
'PhabricatorEditEngineLock' => 'applications/transactions/editengine/PhabricatorEditEngineLock.php', 'PhabricatorEditEngineLock' => 'applications/transactions/editengine/PhabricatorEditEngineLock.php',
'PhabricatorEditEngineLockableInterface' => 'applications/transactions/editengine/PhabricatorEditEngineLockableInterface.php', 'PhabricatorEditEngineLockableInterface' => 'applications/transactions/editengine/PhabricatorEditEngineLockableInterface.php',
'PhabricatorEditEngineMFAEngine' => 'applications/transactions/editengine/PhabricatorEditEngineMFAEngine.php',
'PhabricatorEditEngineMFAInterface' => 'applications/transactions/editengine/PhabricatorEditEngineMFAInterface.php',
'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php', 'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php',
'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php', 'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php',
'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php', 'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php',
@ -7298,6 +7301,7 @@ phutil_register_library_map(array(
'DoorkeeperBridgedObjectInterface', 'DoorkeeperBridgedObjectInterface',
'PhabricatorEditEngineSubtypeInterface', 'PhabricatorEditEngineSubtypeInterface',
'PhabricatorEditEngineLockableInterface', 'PhabricatorEditEngineLockableInterface',
'PhabricatorEditEngineMFAInterface',
), ),
'ManiphestTaskAssignHeraldAction' => 'HeraldAction', 'ManiphestTaskAssignHeraldAction' => 'HeraldAction',
'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
@ -7336,6 +7340,7 @@ phutil_register_library_map(array(
'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListController' => 'ManiphestController',
'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType', 'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType',
'ManiphestTaskListView' => 'ManiphestView', 'ManiphestTaskListView' => 'ManiphestView',
'ManiphestTaskMFAEngine' => 'PhabricatorEditEngineMFAEngine',
'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver', 'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver',
'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship', 'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType',
@ -8754,6 +8759,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineLock' => 'Phobject', 'PhabricatorEditEngineLock' => 'Phobject',
'PhabricatorEditEngineMFAEngine' => 'Phobject',
'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',

View file

@ -212,6 +212,8 @@ The keys you can provide in a specification are:
status. status.
- `locked` //Optional bool.// Lock tasks in this status, preventing users - `locked` //Optional bool.// Lock tasks in this status, preventing users
from commenting. from commenting.
- `mfa` //Optional bool.// Require all edits to this task to be signed with
multi-factor authentication.
Statuses will appear in the UI in the order specified. Note the status marked Statuses will appear in the UI in the order specified. Note the status marked
`special` as `duplicate` is not settable directly and will not appear in UI `special` as `duplicate` is not settable directly and will not appear in UI

View file

@ -160,6 +160,10 @@ final class ManiphestTaskStatus extends ManiphestConstants {
return self::getStatusAttribute($status, 'locked', false); return self::getStatusAttribute($status, 'locked', false);
} }
public static function isMFAStatus($status) {
return self::getStatusAttribute($status, 'mfa', false);
}
public static function getStatusActionName($status) { public static function getStatusActionName($status) {
return self::getStatusAttribute($status, 'name.action'); return self::getStatusAttribute($status, 'name.action');
} }
@ -282,6 +286,7 @@ final class ManiphestTaskStatus extends ManiphestConstants {
'disabled' => 'optional bool', 'disabled' => 'optional bool',
'claim' => 'optional bool', 'claim' => 'optional bool',
'locked' => 'optional bool', 'locked' => 'optional bool',
'mfa' => 'optional bool',
)); ));
} }

View file

@ -0,0 +1,11 @@
<?php
final class ManiphestTaskMFAEngine
extends PhabricatorEditEngineMFAEngine {
public function shouldRequireMFA() {
$status = $this->getObject()->getStatus();
return ManiphestTaskStatus::isMFAStatus($status);
}
}

View file

@ -19,7 +19,8 @@ final class ManiphestTask extends ManiphestDAO
PhabricatorFerretInterface, PhabricatorFerretInterface,
DoorkeeperBridgedObjectInterface, DoorkeeperBridgedObjectInterface,
PhabricatorEditEngineSubtypeInterface, PhabricatorEditEngineSubtypeInterface,
PhabricatorEditEngineLockableInterface { PhabricatorEditEngineLockableInterface,
PhabricatorEditEngineMFAInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:desc'; const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
@ -619,4 +620,12 @@ final class ManiphestTask extends ManiphestDAO
return new ManiphestTaskFerretEngine(); return new ManiphestTaskFerretEngine();
} }
/* -( PhabricatorEditEngineMFAInterface )---------------------------------- */
public function newEditEngineMFAEngine() {
return new ManiphestTaskMFAEngine();
}
} }

View file

@ -8,7 +8,7 @@ final class PhabricatorSubscriptionsEditController
$phid = $request->getURIData('phid'); $phid = $request->getURIData('phid');
$action = $request->getURIData('action'); $action = $request->getURIData('action');
if (!$request->isFormPost()) { if (!$request->isFormOrHisecPost()) {
return new Aphront400Response(); return new Aphront400Response();
} }
@ -73,6 +73,7 @@ final class PhabricatorSubscriptionsEditController
$editor = id($object->getApplicationTransactionEditor()) $editor = id($object->getApplicationTransactionEditor())
->setActor($viewer) ->setActor($viewer)
->setCancelURI($handle->getURI())
->setContinueOnNoEffect(true) ->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true) ->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request); ->setContentSourceFromRequest($request);

View file

@ -1041,7 +1041,7 @@ abstract class PhabricatorEditEngine
} }
$validation_exception = null; $validation_exception = null;
if ($request->isFormPost() && $request->getBool('editEngine')) { if ($request->isFormOrHisecPost() && $request->getBool('editEngine')) {
$submit_fields = $fields; $submit_fields = $fields;
foreach ($submit_fields as $key => $field) { foreach ($submit_fields as $key => $field) {

View file

@ -0,0 +1,39 @@
<?php
abstract class PhabricatorEditEngineMFAEngine
extends Phobject {
private $object;
private $viewer;
public function setObject(PhabricatorEditEngineMFAInterface $object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->object;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
if (!$this->viewer) {
throw new PhutilInvalidStateException('setViewer');
}
return $this->viewer;
}
final public static function newEngineForObject(
PhabricatorEditEngineMFAInterface $object) {
return $object->newEditEngineMFAEngine()
->setObject($object);
}
abstract public function shouldRequireMFA();
}

View file

@ -0,0 +1,7 @@
<?php
interface PhabricatorEditEngineMFAInterface {
public function newEditEngineMFAEngine();
}

View file

@ -953,6 +953,7 @@ abstract class PhabricatorApplicationTransactionEditor
$this->isNewObject = ($object->getPHID() === null); $this->isNewObject = ($object->getPHID() === null);
$this->validateEditParameters($object, $xactions); $this->validateEditParameters($object, $xactions);
$xactions = $this->newMFATransactions($object, $xactions);
$actor = $this->requireActor(); $actor = $this->requireActor();
@ -4825,11 +4826,22 @@ abstract class PhabricatorApplicationTransactionEditor
$request = $this->getRequest(); $request = $this->getRequest();
if ($request === null) { if ($request === null) {
throw new Exception( $source_type = $this->getContentSource()->getSourceTypeConstant();
pht( $conduit_type = PhabricatorConduitContentSource::SOURCECONST;
'This transaction group requires MFA to apply, but the Editor was '. $is_conduit = ($source_type === $conduit_type);
'not configured with a Request. This workflow can not perform an '. if ($is_conduit) {
'MFA check.')); throw new Exception(
pht(
'This transaction group requires MFA to apply, but you can not '.
'provide an MFA response via Conduit. Edit this object via the '.
'web UI.'));
} else {
throw new Exception(
pht(
'This transaction group requires MFA to apply, but the Editor was '.
'not configured with a Request. This workflow can not perform an '.
'MFA check.'));
}
} }
$cancel_uri = $this->getCancelURI(); $cancel_uri = $this->getCancelURI();
@ -4850,4 +4862,46 @@ abstract class PhabricatorApplicationTransactionEditor
} }
} }
private function newMFATransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$is_mfa = ($object instanceof PhabricatorEditEngineMFAInterface);
if (!$is_mfa) {
return $xactions;
}
$engine = PhabricatorEditEngineMFAEngine::newEngineForObject($object)
->setViewer($this->getActor());
$require_mfa = $engine->shouldRequireMFA();
if (!$require_mfa) {
return $xactions;
}
$type_mfa = PhabricatorTransactions::TYPE_MFA;
$has_mfa = false;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() === $type_mfa) {
$has_mfa = true;
break;
}
}
if ($has_mfa) {
return $xactions;
}
$template = $object->getApplicationTransactionTemplate();
$mfa_xaction = id(clone $template)
->setTransactionType($type_mfa)
->setNewValue(true);
array_unshift($xactions, $mfa_xaction);
return $xactions;
}
} }