1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +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',
'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php',
'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php',
'ManiphestTaskMFAEngine' => 'applications/maniphest/engine/ManiphestTaskMFAEngine.php',
'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php',
'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php',
'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php',
@ -2977,6 +2978,8 @@ phutil_register_library_map(array(
'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php',
'PhabricatorEditEngineLock' => 'applications/transactions/editengine/PhabricatorEditEngineLock.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',
'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php',
'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php',
@ -7298,6 +7301,7 @@ phutil_register_library_map(array(
'DoorkeeperBridgedObjectInterface',
'PhabricatorEditEngineSubtypeInterface',
'PhabricatorEditEngineLockableInterface',
'PhabricatorEditEngineMFAInterface',
),
'ManiphestTaskAssignHeraldAction' => 'HeraldAction',
'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
@ -7336,6 +7340,7 @@ phutil_register_library_map(array(
'ManiphestTaskListController' => 'ManiphestController',
'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType',
'ManiphestTaskListView' => 'ManiphestView',
'ManiphestTaskMFAEngine' => 'PhabricatorEditEngineMFAEngine',
'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver',
'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType',
@ -8754,6 +8759,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineLock' => 'Phobject',
'PhabricatorEditEngineMFAEngine' => 'Phobject',
'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',

View file

@ -212,6 +212,8 @@ The keys you can provide in a specification are:
status.
- `locked` //Optional bool.// Lock tasks in this status, preventing users
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
`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);
}
public static function isMFAStatus($status) {
return self::getStatusAttribute($status, 'mfa', false);
}
public static function getStatusActionName($status) {
return self::getStatusAttribute($status, 'name.action');
}
@ -282,6 +286,7 @@ final class ManiphestTaskStatus extends ManiphestConstants {
'disabled' => 'optional bool',
'claim' => '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,
DoorkeeperBridgedObjectInterface,
PhabricatorEditEngineSubtypeInterface,
PhabricatorEditEngineLockableInterface {
PhabricatorEditEngineLockableInterface,
PhabricatorEditEngineMFAInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
@ -619,4 +620,12 @@ final class ManiphestTask extends ManiphestDAO
return new ManiphestTaskFerretEngine();
}
/* -( PhabricatorEditEngineMFAInterface )---------------------------------- */
public function newEditEngineMFAEngine() {
return new ManiphestTaskMFAEngine();
}
}

View file

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

View file

@ -1041,7 +1041,7 @@ abstract class PhabricatorEditEngine
}
$validation_exception = null;
if ($request->isFormPost() && $request->getBool('editEngine')) {
if ($request->isFormOrHisecPost() && $request->getBool('editEngine')) {
$submit_fields = $fields;
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->validateEditParameters($object, $xactions);
$xactions = $this->newMFATransactions($object, $xactions);
$actor = $this->requireActor();
@ -4825,11 +4826,22 @@ abstract class PhabricatorApplicationTransactionEditor
$request = $this->getRequest();
if ($request === null) {
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.'));
$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(
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();
@ -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;
}
}