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

Add a pre-enroll step for MFA, primarily as a CSRF gate

Summary:
Depends on D20020. Ref T13222. This puts another step in the MFA enrollment flow: pick a provider; read text and click "Continue"; actually enroll.

This is primarily to stop CSRF attacks, since otherwise an attacker can put `<img src="phabricator.com/auth/settings/enroll/?providerPHID=xyz" />` on `cute-cat-pix.com` and get you to send yourself some SMS enrollment text messages, which would be mildly annoying.

We could skip this step if we already have a valid CSRF token (and we often will), but I think there's some value in doing it anyway. In particular:

- For SMS/Duo, it seems nice to have an explicit "we're about to hit your phone" button.
- We could let installs customize this text and give users a smoother onboard.
- It allows the relatively wordy enroll form to be a little less wordy.
- For tokens which can expire (SMS, Duo) it might save you from answering too slowly if you have to go dig your phone out of your bag downstairs or something.

Test Plan: Added factors, read text. Tried to CSRF the endpoint, got a dialog instead of a live challenge generation.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

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

+58 -19
+15 -9
src/applications/auth/factor/PhabricatorAuthFactor.php
··· 61 61 return null; 62 62 } 63 63 64 + abstract public function getEnrollDescription( 65 + PhabricatorAuthFactorProvider $provider, 66 + PhabricatorUser $user); 67 + 68 + public function getEnrollButtonText( 69 + PhabricatorAuthFactorProvider $provider, 70 + PhabricatorUser $user) { 71 + return pht('Continue'); 72 + } 73 + 64 74 public function getFactorOrder() { 65 75 return 1000; 66 76 } ··· 315 325 ->setTokenCode($sync_key_digest) 316 326 ->setTokenExpires($now + $sync_ttl); 317 327 318 - // Note that property generation is unguarded, since factors that push 319 - // a challenge generally need to perform a write there. 320 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 321 - $properties = $this->newMFASyncTokenProperties($user); 328 + $properties = $this->newMFASyncTokenProperties($user); 322 329 323 - foreach ($properties as $key => $value) { 324 - $sync_token->setTemporaryTokenProperty($key, $value); 325 - } 330 + foreach ($properties as $key => $value) { 331 + $sync_token->setTemporaryTokenProperty($key, $value); 332 + } 326 333 327 - $sync_token->save(); 328 - unset($unguarded); 334 + $sync_token->save(); 329 335 } 330 336 331 337 $form->addHiddenInput($this->getMFASyncTokenFormKey(), $sync_key);
+17 -9
src/applications/auth/factor/PhabricatorTOTPAuthFactor.php
··· 23 23 'authenticate, you will enter a code shown on your phone.'); 24 24 } 25 25 26 + public function getEnrollDescription( 27 + PhabricatorAuthFactorProvider $provider, 28 + PhabricatorUser $user) { 29 + 30 + return pht( 31 + 'To add a TOTP factor to your account, you will first need to install '. 32 + 'a mobile authenticator application on your phone. Two applications '. 33 + 'which work well are **Google Authenticator** and **Authy**, but any '. 34 + 'other TOTP application should also work.'. 35 + "\n\n". 36 + 'If you haven\'t already, download and install a TOTP application on '. 37 + 'your phone now. Once you\'ve launched the application and are ready '. 38 + 'to add a new TOTP code, continue to the next step.'); 39 + } 40 + 26 41 public function processAddFactorForm( 27 42 PhabricatorAuthFactorProvider $provider, 28 43 AphrontFormView $form, ··· 60 75 } 61 76 } 62 77 63 - $form->appendRemarkupInstructions( 64 - pht( 65 - 'First, download an authenticator application on your phone. Two '. 66 - 'applications which work well are **Authy** and **Google '. 67 - 'Authenticator**, but any other TOTP application should also work.')); 68 - 69 78 $form->appendInstructions( 70 79 pht( 71 - 'Launch the application on your phone, and add a new entry for '. 72 - 'this Phabricator install. When prompted, scan the QR code or '. 73 - 'manually enter the key shown below into the application.')); 80 + 'Scan the QR code or manually enter the key shown below into the '. 81 + 'application.')); 74 82 75 83 $prod_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/')); 76 84 $issuer = $prod_uri->getDomain();
+7
src/applications/auth/storage/PhabricatorAuthFactorProvider.php
··· 109 109 ->addInt($this->getID()); 110 110 } 111 111 112 + public function getEnrollDescription(PhabricatorUser $user) { 113 + return $this->getFactor()->getEnrollDescription($this, $user); 114 + } 115 + 116 + public function getEnrollButtonText(PhabricatorUser $user) { 117 + return $this->getFactor()->getEnrollButtonText($this, $user); 118 + } 112 119 113 120 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 114 121
+19 -1
src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php
··· 231 231 ->addCancelButton($cancel_uri); 232 232 } 233 233 234 + // NOTE: Beyond providing guidance, this step is also providing a CSRF gate 235 + // on this endpoint, since prompting the user to respond to a challenge 236 + // sometimes requires us to push a challenge to them as a side effect (for 237 + // example, with SMS). 238 + if (!$request->isFormPost() || !$request->getBool('mfa.start')) { 239 + $description = $selected_provider->getEnrollDescription($viewer); 240 + 241 + return $this->newDialog() 242 + ->addHiddenInput('providerPHID', $selected_provider->getPHID()) 243 + ->addHiddenInput('mfa.start', 1) 244 + ->setTitle(pht('Add Authentication Factor')) 245 + ->appendChild(new PHUIRemarkupView($viewer, $description)) 246 + ->addCancelButton($cancel_uri) 247 + ->addSubmitButton($selected_provider->getEnrollButtonText($viewer)); 248 + } 249 + 234 250 $form = id(new AphrontFormView()) 235 251 ->setViewer($viewer); 236 252 237 - if ($request->isFormPost()) { 253 + if ($request->getBool('mfa.enroll')) { 238 254 // Subject users to rate limiting so that it's difficult to add factors 239 255 // by pure brute force. This is normally not much of an attack, but push 240 256 // factor types may have side effects. ··· 295 311 296 312 return $this->newDialog() 297 313 ->addHiddenInput('providerPHID', $selected_provider->getPHID()) 314 + ->addHiddenInput('mfa.start', 1) 315 + ->addHiddenInput('mfa.enroll', 1) 298 316 ->setWidth(AphrontDialogView::WIDTH_FORM) 299 317 ->setTitle(pht('Add Authentication Factor')) 300 318 ->appendChild($form->buildLayoutView())