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

Require MFA implementations to return a formal result object when validating factors

Summary:
Ref T13222. See PHI873. Currently, MFA implementations return this weird sort of ad-hoc dictionary from validation, which is later used to render form/control stuff.

I want to make this more formal to handle token reuse / session binding cases, and let MFA factors share more code around challenges. Formalize this into a proper object instead of an ad-hoc bundle of properties.

Test Plan:
- Answered a TOTP MFA prompt wrong (nothing, bad value).
- Answered a TOTP MFA prompt properly.
- Added new TOTP MFA, survived enrollment.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

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

+79 -26
+2
src/__phutil_library_map__.php
··· 2198 2198 'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php', 2199 2199 'PhabricatorAuthFactor' => 'applications/auth/factor/PhabricatorAuthFactor.php', 2200 2200 'PhabricatorAuthFactorConfig' => 'applications/auth/storage/PhabricatorAuthFactorConfig.php', 2201 + 'PhabricatorAuthFactorResult' => 'applications/auth/factor/PhabricatorAuthFactorResult.php', 2201 2202 'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php', 2202 2203 'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php', 2203 2204 'PhabricatorAuthHMACKey' => 'applications/auth/storage/PhabricatorAuthHMACKey.php', ··· 7833 7834 'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController', 7834 7835 'PhabricatorAuthFactor' => 'Phobject', 7835 7836 'PhabricatorAuthFactorConfig' => 'PhabricatorAuthDAO', 7837 + 'PhabricatorAuthFactorResult' => 'Phobject', 7836 7838 'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase', 7837 7839 'PhabricatorAuthFinishController' => 'PhabricatorAuthController', 7838 7840 'PhabricatorAuthHMACKey' => 'PhabricatorAuthDAO',
+17 -3
src/applications/auth/engine/PhabricatorAuthSessionEngine.php
··· 496 496 $id = $factor->getID(); 497 497 $impl = $factor->requireImplementation(); 498 498 499 - $validation_results[$id] = $impl->processValidateFactorForm( 499 + $validation_result = $impl->processValidateFactorForm( 500 500 $factor, 501 501 $viewer, 502 502 $request); 503 503 504 - if (!$impl->isFactorValid($factor, $validation_results[$id])) { 504 + if (!($validation_result instanceof PhabricatorAuthFactorResult)) { 505 + throw new Exception( 506 + pht( 507 + 'Expected "processValidateFactorForm()" to return an object '. 508 + 'of class "%s"; got something else (from "%s").', 509 + 'PhabricatorAuthFactorResult', 510 + get_class($impl))); 511 + } 512 + 513 + if (!$validation_result->getIsValid()) { 505 514 $ok = false; 506 515 } 516 + 517 + $validation_results[$id] = $validation_result; 507 518 } 508 519 509 520 if ($ok) { ··· 595 606 array $validation_results, 596 607 PhabricatorUser $viewer, 597 608 AphrontRequest $request) { 609 + assert_instances_of($validation_results, 'PhabricatorAuthFactorResult'); 598 610 599 611 $form = id(new AphrontFormView()) 600 612 ->setUser($viewer) 601 613 ->appendRemarkupInstructions(''); 602 614 603 615 foreach ($factors as $factor) { 616 + $result = idx($validation_results, $factor->getID()); 617 + 604 618 $factor->requireImplementation()->renderValidateFactorForm( 605 619 $factor, 606 620 $form, 607 621 $viewer, 608 - idx($validation_results, $factor->getID())); 622 + $result); 609 623 } 610 624 611 625 $form->appendRemarkupInstructions('');
+1
src/applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php
··· 7 7 private $factorValidationResults; 8 8 9 9 public function setFactorValidationResults(array $results) { 10 + assert_instances_of($results, 'PhabricatorAuthFactorResult'); 10 11 $this->factorValidationResults = $results; 11 12 return $this; 12 13 }
+1 -7
src/applications/auth/factor/PhabricatorAuthFactor.php
··· 14 14 PhabricatorAuthFactorConfig $config, 15 15 AphrontFormView $form, 16 16 PhabricatorUser $viewer, 17 - $validation_result); 17 + PhabricatorAuthFactorResult $validation_result = null); 18 18 19 19 abstract public function processValidateFactorForm( 20 20 PhabricatorAuthFactorConfig $config, 21 21 PhabricatorUser $viewer, 22 22 AphrontRequest $request); 23 - 24 - public function isFactorValid( 25 - PhabricatorAuthFactorConfig $config, 26 - $validation_result) { 27 - return (idx($validation_result, 'valid') === true); 28 - } 29 23 30 24 public function getParameterName( 31 25 PhabricatorAuthFactorConfig $config,
+37
src/applications/auth/factor/PhabricatorAuthFactorResult.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthFactorResult 4 + extends Phobject { 5 + 6 + private $isValid = false; 7 + private $hint; 8 + private $value; 9 + 10 + public function setIsValid($is_valid) { 11 + $this->isValid = $is_valid; 12 + return $this; 13 + } 14 + 15 + public function getIsValid() { 16 + return $this->isValid; 17 + } 18 + 19 + public function setHint($hint) { 20 + $this->hint = $hint; 21 + return $this; 22 + } 23 + 24 + public function getHint() { 25 + return $this->hint; 26 + } 27 + 28 + public function setValue($value) { 29 + $this->value = $value; 30 + return $this; 31 + } 32 + 33 + public function getValue() { 34 + return $this->value; 35 + } 36 + 37 + }
+21 -16
src/applications/auth/factor/PhabricatorTOTPAuthFactor.php
··· 154 154 PhabricatorAuthFactorConfig $config, 155 155 AphrontFormView $form, 156 156 PhabricatorUser $viewer, 157 - $validation_result) { 157 + PhabricatorAuthFactorResult $validation_result = null) { 158 158 159 - if (!$validation_result) { 160 - $validation_result = array(); 159 + if ($validation_result) { 160 + $value = $validation_result->getValue(); 161 + $hint = $validation_result->getHint(); 162 + } else { 163 + $value = null; 164 + $hint = true; 161 165 } 162 166 163 167 $form->appendChild( ··· 166 170 ->setLabel(pht('App Code')) 167 171 ->setDisableAutocomplete(true) 168 172 ->setCaption(pht('Factor Name: %s', $config->getFactorName())) 169 - ->setValue(idx($validation_result, 'value')) 170 - ->setError(idx($validation_result, 'error', true))); 173 + ->setValue($value) 174 + ->setError($hint)); 171 175 } 172 176 173 177 public function processValidateFactorForm( ··· 178 182 $code = $request->getStr($this->getParameterName($config, 'totpcode')); 179 183 $key = new PhutilOpaqueEnvelope($config->getFactorSecret()); 180 184 185 + $result = id(new PhabricatorAuthFactorResult()) 186 + ->setValue($code); 187 + 181 188 if (self::verifyTOTPCode($viewer, $key, $code)) { 182 - return array( 183 - 'error' => null, 184 - 'value' => $code, 185 - 'valid' => true, 186 - ); 189 + $result->setIsValid(true); 187 190 } else { 188 - return array( 189 - 'error' => strlen($code) ? pht('Invalid') : pht('Required'), 190 - 'value' => $code, 191 - 'valid' => false, 192 - ); 191 + if (strlen($code)) { 192 + $hint = pht('Invalid'); 193 + } else { 194 + $hint = pht('Required'); 195 + } 196 + $result->setHint($hint); 193 197 } 194 - } 195 198 199 + return $result; 200 + } 196 201 197 202 public static function generateNewTOTPKey() { 198 203 return strtoupper(Filesystem::readRandomCharacters(32));