mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 14:00:56 +01:00
Allow any transaction group to be signed with a one-shot "Sign With MFA" action
Summary: Depends on D19896. Ref T13222. See PHI873. Add a core "Sign With MFA" transaction type which prompts you for MFA and marks your transactions as MFA'd. This is a one-shot gate and does not keep you in MFA. Test Plan: - Used "Sign with MFA", got prompted for MFA, answered MFA, saw transactions apply with MFA metadata and markers. - Tried to sign alone, got appropriate errors. - Tried to sign no-op changes, got appropriate errors. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13222 Differential Revision: https://secure.phabricator.com/D19897
This commit is contained in:
parent
10db27833b
commit
543f2b6bf1
7 changed files with 265 additions and 33 deletions
|
@ -2231,6 +2231,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
|
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
|
||||||
'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php',
|
'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php',
|
||||||
'PhabricatorAuthLogoutConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php',
|
'PhabricatorAuthLogoutConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php',
|
||||||
|
'PhabricatorAuthMFAEditEngineExtension' => 'applications/auth/engineextension/PhabricatorAuthMFAEditEngineExtension.php',
|
||||||
'PhabricatorAuthMainMenuBarExtension' => 'applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php',
|
'PhabricatorAuthMainMenuBarExtension' => 'applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php',
|
||||||
'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php',
|
'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php',
|
||||||
'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php',
|
'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php',
|
||||||
|
@ -7885,6 +7886,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
|
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
|
||||||
'PhabricatorAuthLoginHandler' => 'Phobject',
|
'PhabricatorAuthLoginHandler' => 'Phobject',
|
||||||
'PhabricatorAuthLogoutConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod',
|
'PhabricatorAuthLogoutConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod',
|
||||||
|
'PhabricatorAuthMFAEditEngineExtension' => 'PhabricatorEditEngineExtension',
|
||||||
'PhabricatorAuthMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
|
'PhabricatorAuthMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
|
||||||
'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow',
|
'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow',
|
||||||
'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow',
|
'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow',
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthMFAEditEngineExtension
|
||||||
|
extends PhabricatorEditEngineExtension {
|
||||||
|
|
||||||
|
const EXTENSIONKEY = 'auth.mfa';
|
||||||
|
const FIELDKEY = 'mfa';
|
||||||
|
|
||||||
|
public function getExtensionPriority() {
|
||||||
|
return 12000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExtensionEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExtensionName() {
|
||||||
|
return pht('MFA');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsObject(
|
||||||
|
PhabricatorEditEngine $engine,
|
||||||
|
PhabricatorApplicationTransactionInterface $object) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildCustomEditFields(
|
||||||
|
PhabricatorEditEngine $engine,
|
||||||
|
PhabricatorApplicationTransactionInterface $object) {
|
||||||
|
|
||||||
|
$mfa_type = PhabricatorTransactions::TYPE_MFA;
|
||||||
|
|
||||||
|
$viewer = $engine->getViewer();
|
||||||
|
|
||||||
|
$mfa_field = id(new PhabricatorApplyEditField())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setKey(self::FIELDKEY)
|
||||||
|
->setLabel(pht('MFA'))
|
||||||
|
->setIsFormField(false)
|
||||||
|
->setCommentActionLabel(pht('Sign With MFA'))
|
||||||
|
->setCommentActionOrder(12000)
|
||||||
|
->setActionDescription(
|
||||||
|
pht('You will be prompted to provide MFA when you submit.'))
|
||||||
|
->setDescription(pht('Sign this transaction group with MFA.'))
|
||||||
|
->setTransactionType($mfa_type);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
$mfa_field,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ final class PhabricatorTransactions extends Phobject {
|
||||||
const TYPE_COLUMNS = 'core:columns';
|
const TYPE_COLUMNS = 'core:columns';
|
||||||
const TYPE_SUBTYPE = 'core:subtype';
|
const TYPE_SUBTYPE = 'core:subtype';
|
||||||
const TYPE_HISTORY = 'core:history';
|
const TYPE_HISTORY = 'core:history';
|
||||||
|
const TYPE_MFA = 'core:mfa';
|
||||||
|
|
||||||
const COLOR_RED = 'red';
|
const COLOR_RED = 'red';
|
||||||
const COLOR_ORANGE = 'orange';
|
const COLOR_ORANGE = 'orange';
|
||||||
|
|
|
@ -1105,6 +1105,7 @@ abstract class PhabricatorEditEngine
|
||||||
$editor = $object->getApplicationTransactionEditor()
|
$editor = $object->getApplicationTransactionEditor()
|
||||||
->setActor($viewer)
|
->setActor($viewer)
|
||||||
->setContentSourceFromRequest($request)
|
->setContentSourceFromRequest($request)
|
||||||
|
->setCancelURI($cancel_uri)
|
||||||
->setContinueOnNoEffect(true);
|
->setContinueOnNoEffect(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1785,7 +1786,9 @@ abstract class PhabricatorEditEngine
|
||||||
$controller = $this->getController();
|
$controller = $this->getController();
|
||||||
$request = $controller->getRequest();
|
$request = $controller->getRequest();
|
||||||
|
|
||||||
if (!$request->isFormPost()) {
|
// NOTE: We handle hisec inside the transaction editor with "Sign With MFA"
|
||||||
|
// comment actions.
|
||||||
|
if (!$request->isFormOrHisecPost()) {
|
||||||
return new Aphront400Response();
|
return new Aphront400Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1919,6 +1922,7 @@ abstract class PhabricatorEditEngine
|
||||||
->setContinueOnNoEffect($request->isContinueRequest())
|
->setContinueOnNoEffect($request->isContinueRequest())
|
||||||
->setContinueOnMissingFields(true)
|
->setContinueOnMissingFields(true)
|
||||||
->setContentSourceFromRequest($request)
|
->setContentSourceFromRequest($request)
|
||||||
|
->setCancelURI($view_uri)
|
||||||
->setRaiseWarnings(!$request->getBool('editEngine.warnings'))
|
->setRaiseWarnings(!$request->getBool('editEngine.warnings'))
|
||||||
->setIsPreview($is_preview);
|
->setIsPreview($is_preview);
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,10 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
|
|
||||||
private $transactionQueue = array();
|
private $transactionQueue = array();
|
||||||
private $sendHistory = false;
|
private $sendHistory = false;
|
||||||
|
private $shouldRequireMFA = false;
|
||||||
|
private $hasRequiredMFA = false;
|
||||||
|
private $request;
|
||||||
|
private $cancelURI;
|
||||||
|
|
||||||
const STORAGE_ENCODING_BINARY = 'binary';
|
const STORAGE_ENCODING_BINARY = 'binary';
|
||||||
|
|
||||||
|
@ -284,6 +288,22 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
return $this->raiseWarnings;
|
return $this->raiseWarnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setShouldRequireMFA($should_require_mfa) {
|
||||||
|
if ($this->hasRequiredMFA) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Call to setShouldRequireMFA() is too late: this Editor has already '.
|
||||||
|
'checked for MFA requirements.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->shouldRequireMFA = $should_require_mfa;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShouldRequireMFA() {
|
||||||
|
return $this->shouldRequireMFA;
|
||||||
|
}
|
||||||
|
|
||||||
public function getTransactionTypesForObject($object) {
|
public function getTransactionTypesForObject($object) {
|
||||||
$old = $this->object;
|
$old = $this->object;
|
||||||
try {
|
try {
|
||||||
|
@ -328,6 +348,8 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$types[] = PhabricatorTransactions::TYPE_SPACE;
|
$types[] = PhabricatorTransactions::TYPE_SPACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$types[] = PhabricatorTransactions::TYPE_MFA;
|
||||||
|
|
||||||
$template = $this->object->getApplicationTransactionTemplate();
|
$template = $this->object->getApplicationTransactionTemplate();
|
||||||
if ($template instanceof PhabricatorModularTransaction) {
|
if ($template instanceof PhabricatorModularTransaction) {
|
||||||
$xtypes = $template->newModularTransactionTypes();
|
$xtypes = $template->newModularTransactionTypes();
|
||||||
|
@ -383,6 +405,8 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
return null;
|
return null;
|
||||||
case PhabricatorTransactions::TYPE_SUBTYPE:
|
case PhabricatorTransactions::TYPE_SUBTYPE:
|
||||||
return $object->getEditEngineSubtype();
|
return $object->getEditEngineSubtype();
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
|
return null;
|
||||||
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
||||||
return array_values($this->subscribers);
|
return array_values($this->subscribers);
|
||||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||||
|
@ -473,6 +497,8 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
case PhabricatorTransactions::TYPE_SUBTYPE:
|
case PhabricatorTransactions::TYPE_SUBTYPE:
|
||||||
case PhabricatorTransactions::TYPE_HISTORY:
|
case PhabricatorTransactions::TYPE_HISTORY:
|
||||||
return $xaction->getNewValue();
|
return $xaction->getNewValue();
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
|
return true;
|
||||||
case PhabricatorTransactions::TYPE_SPACE:
|
case PhabricatorTransactions::TYPE_SPACE:
|
||||||
$space_phid = $xaction->getNewValue();
|
$space_phid = $xaction->getNewValue();
|
||||||
if (!strlen($space_phid)) {
|
if (!strlen($space_phid)) {
|
||||||
|
@ -611,6 +637,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
case PhabricatorTransactions::TYPE_CREATE:
|
case PhabricatorTransactions::TYPE_CREATE:
|
||||||
case PhabricatorTransactions::TYPE_HISTORY:
|
case PhabricatorTransactions::TYPE_HISTORY:
|
||||||
case PhabricatorTransactions::TYPE_SUBTYPE:
|
case PhabricatorTransactions::TYPE_SUBTYPE:
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
case PhabricatorTransactions::TYPE_TOKEN:
|
case PhabricatorTransactions::TYPE_TOKEN:
|
||||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||||
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
case PhabricatorTransactions::TYPE_EDIT_POLICY:
|
||||||
|
@ -673,6 +700,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
case PhabricatorTransactions::TYPE_CREATE:
|
case PhabricatorTransactions::TYPE_CREATE:
|
||||||
case PhabricatorTransactions::TYPE_HISTORY:
|
case PhabricatorTransactions::TYPE_HISTORY:
|
||||||
case PhabricatorTransactions::TYPE_SUBTYPE:
|
case PhabricatorTransactions::TYPE_SUBTYPE:
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
case PhabricatorTransactions::TYPE_EDGE:
|
case PhabricatorTransactions::TYPE_EDGE:
|
||||||
case PhabricatorTransactions::TYPE_TOKEN:
|
case PhabricatorTransactions::TYPE_TOKEN:
|
||||||
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
case PhabricatorTransactions::TYPE_VIEW_POLICY:
|
||||||
|
@ -850,10 +878,6 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$xaction->setIsSilentTransaction(true);
|
$xaction->setIsSilentTransaction(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($actor->hasHighSecuritySession()) {
|
|
||||||
$xaction->setIsMFATransaction(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $xaction;
|
return $xaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -893,6 +917,7 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setContentSourceFromRequest(AphrontRequest $request) {
|
public function setContentSourceFromRequest(AphrontRequest $request) {
|
||||||
|
$this->setRequest($request);
|
||||||
return $this->setContentSource(
|
return $this->setContentSource(
|
||||||
PhabricatorContentSource::newFromRequest($request));
|
PhabricatorContentSource::newFromRequest($request));
|
||||||
}
|
}
|
||||||
|
@ -901,6 +926,24 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
return $this->contentSource;
|
return $this->contentSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setRequest(AphrontRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequest() {
|
||||||
|
return $this->request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCancelURI($cancel_uri) {
|
||||||
|
$this->cancelURI = $cancel_uri;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCancelURI() {
|
||||||
|
return $this->cancelURI;
|
||||||
|
}
|
||||||
|
|
||||||
final public function applyTransactions(
|
final public function applyTransactions(
|
||||||
PhabricatorLiskDAO $object,
|
PhabricatorLiskDAO $object,
|
||||||
array $xactions) {
|
array $xactions) {
|
||||||
|
@ -966,9 +1009,29 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$warnings);
|
$warnings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$this->adjustTransactionValues($object, $xaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we've merged and combined transactions, check for required
|
||||||
|
// capabilities. Note that we're doing this before filtering
|
||||||
|
// transactions: if you try to apply an edit which you do not have
|
||||||
|
// permission to apply, we want to give you a permissions error even
|
||||||
|
// if the edit would have no effect.
|
||||||
|
$this->applyCapabilityChecks($object, $xactions);
|
||||||
|
|
||||||
|
$xactions = $this->filterTransactions($object, $xactions);
|
||||||
|
|
||||||
|
if (!$is_preview) {
|
||||||
$this->willApplyTransactions($object, $xactions);
|
$this->willApplyTransactions($object, $xactions);
|
||||||
|
|
||||||
|
$this->hasRequiredMFA = true;
|
||||||
|
if ($this->getShouldRequireMFA()) {
|
||||||
|
$this->requireMFA($object, $xactions);
|
||||||
|
}
|
||||||
|
|
||||||
if ($object->getID()) {
|
if ($object->getID()) {
|
||||||
$this->buildOldRecipientLists($object, $xactions);
|
$this->buildOldRecipientLists($object, $xactions);
|
||||||
|
|
||||||
|
@ -994,33 +1057,6 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$this->applyInitialEffects($object, $xactions);
|
$this->applyInitialEffects($object, $xactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($xactions as $xaction) {
|
|
||||||
$this->adjustTransactionValues($object, $xaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we've merged and combined transactions, check for required
|
|
||||||
// capabilities. Note that we're doing this before filtering
|
|
||||||
// transactions: if you try to apply an edit which you do not have
|
|
||||||
// permission to apply, we want to give you a permissions error even
|
|
||||||
// if the edit would have no effect.
|
|
||||||
$this->applyCapabilityChecks($object, $xactions);
|
|
||||||
|
|
||||||
// See T13186. Fatal hard if this object has an older
|
|
||||||
// "requireCapabilities()" method. The code may rely on this method being
|
|
||||||
// called to apply policy checks, so err on the side of safety and fatal.
|
|
||||||
// TODO: Remove this check after some time has passed.
|
|
||||||
if (method_exists($this, 'requireCapabilities')) {
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Editor (of class "%s") implements obsolete policy method '.
|
|
||||||
'requireCapabilities(). The implementation for this Editor '.
|
|
||||||
'MUST be updated. See <%s> for discussion.',
|
|
||||||
get_class($this),
|
|
||||||
'https://secure.phabricator.com/T13186'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$xactions = $this->filterTransactions($object, $xactions);
|
|
||||||
|
|
||||||
// TODO: Once everything is on EditEngine, just use getIsNewObject() to
|
// TODO: Once everything is on EditEngine, just use getIsNewObject() to
|
||||||
// figure this out instead.
|
// figure this out instead.
|
||||||
$mark_as_create = false;
|
$mark_as_create = false;
|
||||||
|
@ -1580,6 +1616,10 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
// email and is only partially supported in the upstream. You don't
|
// email and is only partially supported in the upstream. You don't
|
||||||
// need any capabilities to apply it.
|
// need any capabilities to apply it.
|
||||||
return null;
|
return null;
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
|
// Signing a transaction group with MFA does not require permissions
|
||||||
|
// on its own.
|
||||||
|
return null;
|
||||||
case PhabricatorTransactions::TYPE_EDGE:
|
case PhabricatorTransactions::TYPE_EDGE:
|
||||||
return $this->getLegacyRequiredEdgeCapabilities($xaction);
|
return $this->getLegacyRequiredEdgeCapabilities($xaction);
|
||||||
default:
|
default:
|
||||||
|
@ -2272,11 +2312,19 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
array $xactions) {
|
array $xactions) {
|
||||||
|
|
||||||
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
|
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
|
||||||
|
$type_mfa = PhabricatorTransactions::TYPE_MFA;
|
||||||
|
|
||||||
$no_effect = array();
|
$no_effect = array();
|
||||||
$has_comment = false;
|
$has_comment = false;
|
||||||
$any_effect = false;
|
$any_effect = false;
|
||||||
|
|
||||||
|
$meta_xactions = array();
|
||||||
foreach ($xactions as $key => $xaction) {
|
foreach ($xactions as $key => $xaction) {
|
||||||
|
if ($xaction->getTransactionType() === $type_mfa) {
|
||||||
|
$meta_xactions[$key] = $xaction;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->transactionHasEffect($object, $xaction)) {
|
if ($this->transactionHasEffect($object, $xaction)) {
|
||||||
if ($xaction->getTransactionType() != $type_comment) {
|
if ($xaction->getTransactionType() != $type_comment) {
|
||||||
$any_effect = true;
|
$any_effect = true;
|
||||||
|
@ -2286,15 +2334,30 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
} else {
|
} else {
|
||||||
$no_effect[$key] = $xaction;
|
$no_effect[$key] = $xaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($xaction->hasComment()) {
|
if ($xaction->hasComment()) {
|
||||||
$has_comment = true;
|
$has_comment = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If every transaction is a meta-transaction applying to the transaction
|
||||||
|
// group, these transactions are junk.
|
||||||
|
if (count($meta_xactions) == count($xactions)) {
|
||||||
|
$no_effect = $xactions;
|
||||||
|
$any_effect = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$no_effect) {
|
if (!$no_effect) {
|
||||||
return $xactions;
|
return $xactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If none of the transactions have an effect, the meta-transactions also
|
||||||
|
// have no effect. Add them to the "no effect" list so we get a full set
|
||||||
|
// of errors for everything.
|
||||||
|
if (!$any_effect) {
|
||||||
|
$no_effect += $meta_xactions;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->getContinueOnNoEffect() && !$this->getIsPreview()) {
|
if (!$this->getContinueOnNoEffect() && !$this->getIsPreview()) {
|
||||||
throw new PhabricatorApplicationTransactionNoEffectException(
|
throw new PhabricatorApplicationTransactionNoEffectException(
|
||||||
$no_effect,
|
$no_effect,
|
||||||
|
@ -2375,6 +2438,12 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
$xactions,
|
$xactions,
|
||||||
$type);
|
$type);
|
||||||
break;
|
break;
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
|
$errors[] = $this->validateMFATransactions(
|
||||||
|
$object,
|
||||||
|
$xactions,
|
||||||
|
$type);
|
||||||
|
break;
|
||||||
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
|
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
|
||||||
$groups = array();
|
$groups = array();
|
||||||
foreach ($xactions as $xaction) {
|
foreach ($xactions as $xaction) {
|
||||||
|
@ -2556,6 +2625,36 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function validateMFATransactions(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
array $xactions,
|
||||||
|
$transaction_type) {
|
||||||
|
$errors = array();
|
||||||
|
|
||||||
|
$factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere(
|
||||||
|
'userPHID = %s',
|
||||||
|
$this->getActingAsPHID());
|
||||||
|
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
if (!$factors) {
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$transaction_type,
|
||||||
|
pht('No MFA'),
|
||||||
|
pht(
|
||||||
|
'You do not have any MFA factors attached to your account, so '.
|
||||||
|
'you can not sign this transaction group with MFA. Add MFA to '.
|
||||||
|
'your account in Settings.'),
|
||||||
|
$xaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($xactions) {
|
||||||
|
$this->setShouldRequireMFA(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
protected function adjustObjectForPolicyChecks(
|
protected function adjustObjectForPolicyChecks(
|
||||||
PhabricatorLiskDAO $object,
|
PhabricatorLiskDAO $object,
|
||||||
array $xactions) {
|
array $xactions) {
|
||||||
|
@ -4707,4 +4806,48 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
->setNewValue($new_value);
|
->setNewValue($new_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function requireMFA(PhabricatorLiskDAO $object, array $xactions) {
|
||||||
|
$editor_class = get_class($this);
|
||||||
|
|
||||||
|
$object_phid = $object->getPHID();
|
||||||
|
if ($object_phid) {
|
||||||
|
$workflow_key = sprintf(
|
||||||
|
'editor(%s).phid(%s)',
|
||||||
|
$editor_class,
|
||||||
|
$object_phid);
|
||||||
|
} else {
|
||||||
|
$workflow_key = sprintf(
|
||||||
|
'editor(%s).new()',
|
||||||
|
$editor_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
$actor = $this->getActor();
|
||||||
|
|
||||||
|
$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.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cancel_uri = $this->getCancelURI();
|
||||||
|
if ($cancel_uri === null) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'This transaction group requires MFA to apply, but the Editor was '.
|
||||||
|
'not configured with a Cancel URI. This workflow can not perform '.
|
||||||
|
'an MFA check.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
id(new PhabricatorAuthSessionEngine())
|
||||||
|
->setWorkflowKey($workflow_key)
|
||||||
|
->requireHighSecurityToken($actor, $request, $cancel_uri);
|
||||||
|
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$xaction->setIsMFATransaction(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -473,6 +473,8 @@ abstract class PhabricatorApplicationTransaction
|
||||||
return 'fa-th-large';
|
return 'fa-th-large';
|
||||||
case PhabricatorTransactions::TYPE_COLUMNS:
|
case PhabricatorTransactions::TYPE_COLUMNS:
|
||||||
return 'fa-columns';
|
return 'fa-columns';
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
|
return 'fa-vcard';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'fa-pencil';
|
return 'fa-pencil';
|
||||||
|
@ -510,6 +512,8 @@ abstract class PhabricatorApplicationTransaction
|
||||||
return 'sky';
|
return 'sky';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case PhabricatorTransactions::TYPE_MFA;
|
||||||
|
return 'pink';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -835,6 +839,10 @@ abstract class PhabricatorApplicationTransaction
|
||||||
return pht(
|
return pht(
|
||||||
'You have not moved this object to any columns it is not '.
|
'You have not moved this object to any columns it is not '.
|
||||||
'already in.');
|
'already in.');
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
|
return pht(
|
||||||
|
'You can not sign a transaction group that has no other '.
|
||||||
|
'effects.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return pht(
|
return pht(
|
||||||
|
@ -1076,6 +1084,12 @@ abstract class PhabricatorApplicationTransaction
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
|
return pht(
|
||||||
|
'%s signed these changes with MFA.',
|
||||||
|
$this->renderHandleLink($author_phid));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// In developer mode, provide a better hint here about which string
|
// In developer mode, provide a better hint here about which string
|
||||||
// we're missing.
|
// we're missing.
|
||||||
|
@ -1238,6 +1252,9 @@ abstract class PhabricatorApplicationTransaction
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getTitle();
|
return $this->getTitle();
|
||||||
|
@ -1320,6 +1337,10 @@ abstract class PhabricatorApplicationTransaction
|
||||||
// (which are shown anyway) but less interesting than any other type of
|
// (which are shown anyway) but less interesting than any other type of
|
||||||
// transaction.
|
// transaction.
|
||||||
return 0.75;
|
return 0.75;
|
||||||
|
case PhabricatorTransactions::TYPE_MFA:
|
||||||
|
// We want MFA signatures to render at the top of transaction groups,
|
||||||
|
// on top of the things they signed.
|
||||||
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1.0;
|
return 1.0;
|
||||||
|
@ -1434,6 +1455,8 @@ abstract class PhabricatorApplicationTransaction
|
||||||
$this_source = $this->getContentSource()->getSource();
|
$this_source = $this->getContentSource()->getSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$type_mfa = PhabricatorTransactions::TYPE_MFA;
|
||||||
|
|
||||||
foreach ($group as $xaction) {
|
foreach ($group as $xaction) {
|
||||||
// Don't group transactions by different authors.
|
// Don't group transactions by different authors.
|
||||||
if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) {
|
if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) {
|
||||||
|
@ -1477,6 +1500,13 @@ abstract class PhabricatorApplicationTransaction
|
||||||
if ($is_mfa != $xaction->getIsMFATransaction()) {
|
if ($is_mfa != $xaction->getIsMFATransaction()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't group two "Sign with MFA" transactions together.
|
||||||
|
if ($this->getTransactionType() === $type_mfa) {
|
||||||
|
if ($xaction->getTransactionType() === $type_mfa) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -605,7 +605,7 @@ final class PHUITimelineEventView extends AphrontView {
|
||||||
// provide a hint that it was extra authentic.
|
// provide a hint that it was extra authentic.
|
||||||
if ($this->getIsMFA()) {
|
if ($this->getIsMFA()) {
|
||||||
$extra[] = id(new PHUIIconView())
|
$extra[] = id(new PHUIIconView())
|
||||||
->setIcon('fa-vcard', 'green')
|
->setIcon('fa-vcard', 'pink')
|
||||||
->setTooltip(pht('MFA Authenticated'));
|
->setTooltip(pht('MFA Authenticated'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue