1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-04-12 12:28:36 +02: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,12 +4826,23 @@ abstract class PhabricatorApplicationTransactionEditor
$request = $this->getRequest(); $request = $this->getRequest();
if ($request === null) { if ($request === null) {
$source_type = $this->getContentSource()->getSourceTypeConstant();
$conduit_type = PhabricatorConduitContentSource::SOURCECONST;
$is_conduit = ($source_type === $conduit_type);
if ($is_conduit) {
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( throw new Exception(
pht( pht(
'This transaction group requires MFA to apply, but the Editor was '. 'This transaction group requires MFA to apply, but the Editor was '.
'not configured with a Request. This workflow can not perform an '. 'not configured with a Request. This workflow can not perform an '.
'MFA check.')); 'MFA check.'));
} }
}
$cancel_uri = $this->getCancelURI(); $cancel_uri = $this->getCancelURI();
if ($cancel_uri === null) { if ($cancel_uri === null) {
@ -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;
}
} }