@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

Allow users to access some settings at the "Add MFA" account setup roadblock

Summary:
Depends on D20006. Ref T13222. Currently, the "MFA Is Required" gate doesn't let you do anything else, but you'll need to be able to access "Contact Numbers" if an install provides SMS MFA.

Tweak this UI to give users limited access to settings, so they can set up contact numbers and change their language.

(This is a little bit fiddly, and I'm doing it early on partly so it can get more testing as these changes move forward.)

Test Plan: {F6146136}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

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

+228 -62
+4 -2
src/applications/auth/application/PhabricatorAuthApplication.php
··· 72 72 => 'PhabricatorAuthRevokeTokenController', 73 73 'session/downgrade/' 74 74 => 'PhabricatorAuthDowngradeSessionController', 75 - 'multifactor/' 76 - => 'PhabricatorAuthNeedsMultiFactorController', 75 + 'enroll/' => array( 76 + '(?:(?P<pageKey>[^/]+)/)?(?:(?P<formSaved>saved)/)?' 77 + => 'PhabricatorAuthNeedsMultiFactorController', 78 + ), 77 79 'sshkey/' => array( 78 80 $this->getQueryRoutePattern('for/(?P<forPHID>[^/]+)/') 79 81 => 'PhabricatorAuthSSHKeyListController',
+166 -59
src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php
··· 30 30 return new Aphront400Response(); 31 31 } 32 32 33 - $panel = id(new PhabricatorMultiFactorSettingsPanel()) 34 - ->setUser($viewer) 35 - ->setViewer($viewer) 36 - ->setOverrideURI($this->getApplicationURI('/multifactor/')) 37 - ->processRequest($request); 33 + $panels = $this->loadPanels(); 34 + 35 + $multifactor_key = id(new PhabricatorMultiFactorSettingsPanel()) 36 + ->getPanelKey(); 37 + 38 + $panel_key = $request->getURIData('pageKey'); 39 + if (!strlen($panel_key)) { 40 + $panel_key = $multifactor_key; 41 + } 38 42 39 - if ($panel instanceof AphrontResponse) { 40 - return $panel; 43 + if (!isset($panels[$panel_key])) { 44 + return new Aphront404Response(); 41 45 } 42 46 43 - $crumbs = $this->buildApplicationCrumbs(); 44 - $crumbs->addTextCrumb(pht('Add Multi-Factor Auth')); 47 + $nav = $this->newNavigation(); 48 + $nav->selectFilter($panel_key); 49 + 50 + $panel = $panels[$panel_key]; 45 51 46 52 $viewer->updateMultiFactorEnrollment(); 47 53 48 - if (!$viewer->getIsEnrolledInMultiFactor()) { 49 - $help = id(new PHUIInfoView()) 50 - ->setTitle(pht('Add Multi-Factor Authentication To Your Account')) 51 - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 52 - ->setErrors( 53 - array( 54 - pht( 55 - 'Before you can use Phabricator, you need to add multi-factor '. 56 - 'authentication to your account.'), 57 - pht( 58 - 'Multi-factor authentication helps secure your account by '. 59 - 'making it more difficult for attackers to gain access or '. 60 - 'take sensitive actions.'), 61 - pht( 62 - 'To learn more about multi-factor authentication, click the '. 63 - '%s button below.', 64 - phutil_tag('strong', array(), pht('Help'))), 65 - pht( 66 - 'To add an authentication factor, click the %s button below.', 67 - phutil_tag('strong', array(), pht('Add Authentication Factor'))), 68 - pht( 69 - 'To continue, add at least one authentication factor to your '. 70 - 'account.'), 71 - )); 54 + if ($panel_key === $multifactor_key) { 55 + $header_text = pht('Add Multi-Factor Auth'); 56 + $help = $this->newGuidance(); 57 + $panel->setIsEnrollment(true); 72 58 } else { 73 - $help = id(new PHUIInfoView()) 74 - ->setTitle(pht('Multi-Factor Authentication Configured')) 75 - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) 76 - ->setErrors( 77 - array( 78 - pht( 79 - 'You have successfully configured multi-factor authentication '. 80 - 'for your account.'), 81 - pht( 82 - 'You can make adjustments from the Settings panel later.'), 83 - pht( 84 - 'When you are ready, %s.', 85 - phutil_tag( 86 - 'strong', 87 - array(), 88 - phutil_tag( 89 - 'a', 90 - array( 91 - 'href' => '/', 92 - ), 93 - pht('continue to Phabricator')))), 94 - )); 59 + $header_text = $panel->getPanelName(); 60 + $help = null; 61 + } 62 + 63 + $response = $panel 64 + ->setController($this) 65 + ->setNavigation($nav) 66 + ->processRequest($request); 67 + 68 + if (($response instanceof AphrontResponse) || 69 + ($response instanceof AphrontResponseProducerInterface)) { 70 + return $response; 95 71 } 96 72 97 - $view = array( 98 - $help, 99 - $panel, 100 - ); 73 + $crumbs = $this->buildApplicationCrumbs() 74 + ->addTextCrumb(pht('Add Multi-Factor Auth')) 75 + ->setBorder(true); 76 + 77 + $header = id(new PHUIHeaderView()) 78 + ->setHeader($header_text); 79 + 80 + $view = id(new PHUITwoColumnView()) 81 + ->setHeader($header) 82 + ->setFooter( 83 + array( 84 + $help, 85 + $response, 86 + )); 101 87 102 88 return $this->newPage() 103 89 ->setTitle(pht('Add Multi-Factor Authentication')) 104 90 ->setCrumbs($crumbs) 91 + ->setNavigation($nav) 105 92 ->appendChild($view); 106 93 94 + } 95 + 96 + private function loadPanels() { 97 + $viewer = $this->getViewer(); 98 + $preferences = PhabricatorUserPreferences::loadUserPreferences($viewer); 99 + 100 + $panels = PhabricatorSettingsPanel::getAllDisplayPanels(); 101 + $base_uri = $this->newEnrollBaseURI(); 102 + 103 + $result = array(); 104 + foreach ($panels as $key => $panel) { 105 + $panel 106 + ->setPreferences($preferences) 107 + ->setViewer($viewer) 108 + ->setUser($viewer) 109 + ->setOverrideURI(urisprintf('%s%s/', $base_uri, $key)); 110 + 111 + if (!$panel->isEnabled()) { 112 + continue; 113 + } 114 + 115 + if (!$panel->isUserPanel()) { 116 + continue; 117 + } 118 + 119 + if (!$panel->isMultiFactorEnrollmentPanel()) { 120 + continue; 121 + } 122 + 123 + if (!empty($result[$key])) { 124 + throw new Exception(pht( 125 + "Two settings panels share the same panel key ('%s'): %s, %s.", 126 + $key, 127 + get_class($panel), 128 + get_class($result[$key]))); 129 + } 130 + 131 + $result[$key] = $panel; 132 + } 133 + 134 + return $result; 135 + } 136 + 137 + 138 + private function newNavigation() { 139 + $viewer = $this->getViewer(); 140 + 141 + $enroll_uri = $this->newEnrollBaseURI(); 142 + 143 + $nav = id(new AphrontSideNavFilterView()) 144 + ->setBaseURI(new PhutilURI($enroll_uri)); 145 + 146 + $multifactor_key = id(new PhabricatorMultiFactorSettingsPanel()) 147 + ->getPanelKey(); 148 + 149 + $nav->addFilter( 150 + $multifactor_key, 151 + pht('Enroll in MFA'), 152 + null, 153 + 'fa-exclamation-triangle blue'); 154 + 155 + $panels = $this->loadPanels(); 156 + 157 + if ($panels) { 158 + $nav->addLabel(pht('Settings')); 159 + } 160 + 161 + foreach ($panels as $panel_key => $panel) { 162 + if ($panel_key === $multifactor_key) { 163 + continue; 164 + } 165 + 166 + $nav->addFilter( 167 + $panel->getPanelKey(), 168 + $panel->getPanelName(), 169 + null, 170 + $panel->getPanelMenuIcon()); 171 + } 172 + 173 + return $nav; 174 + } 175 + 176 + private function newEnrollBaseURI() { 177 + return $this->getApplicationURI('enroll/'); 178 + } 179 + 180 + private function newGuidance() { 181 + $viewer = $this->getViewer(); 182 + 183 + if ($viewer->getIsEnrolledInMultiFactor()) { 184 + $guidance = pht( 185 + '{icon check, color="green"} **Setup Complete!**'. 186 + "\n\n". 187 + 'You have successfully configured multi-factor authentication '. 188 + 'for your account.'. 189 + "\n\n". 190 + 'You can make adjustments from the [[ /settings/ | Settings ]] panel '. 191 + 'later.'); 192 + 193 + return $this->newDialog() 194 + ->setTitle(pht('Multi-Factor Authentication Setup Complete')) 195 + ->setWidth(AphrontDialogView::WIDTH_FULL) 196 + ->appendChild(new PHUIRemarkupView($viewer, $guidance)) 197 + ->addCancelButton('/', pht('Continue')); 198 + } 199 + 200 + $messages = array(); 201 + 202 + $messages[] = pht( 203 + 'Before you can use Phabricator, you need to add multi-factor '. 204 + 'authentication to your account. Multi-factor authentication helps '. 205 + 'secure your account by making it more difficult for attackers to '. 206 + 'gain access or take sensitive actions.'); 207 + 208 + $view = id(new PHUIInfoView()) 209 + ->setTitle(pht('Add Multi-Factor Authentication To Your Account')) 210 + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 211 + ->setErrors($messages); 212 + 213 + return $view; 107 214 } 108 215 109 216 }
+15
src/applications/auth/controller/contact/PhabricatorAuthContactNumberController.php
··· 3 3 abstract class PhabricatorAuthContactNumberController 4 4 extends PhabricatorAuthController { 5 5 6 + // Users may need to access these controllers to enroll in SMS MFA during 7 + // account setup. 8 + 9 + public function shouldRequireMultiFactorEnrollment() { 10 + return false; 11 + } 12 + 13 + public function shouldRequireEnabledUser() { 14 + return false; 15 + } 16 + 17 + public function shouldRequireEmailVerification() { 18 + return false; 19 + } 20 + 6 21 protected function buildApplicationCrumbs() { 7 22 $crumbs = parent::buildApplicationCrumbs(); 8 23
+4
src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php
··· 19 19 return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY; 20 20 } 21 21 22 + public function isMultiFactorEnrollmentPanel() { 23 + return true; 24 + } 25 + 22 26 public function processRequest(AphrontRequest $request) { 23 27 $user = $this->getUser(); 24 28 $viewer = $request->getUser();
+4
src/applications/settings/panel/PhabricatorLanguageSettingsPanel.php
··· 25 25 return true; 26 26 } 27 27 28 + public function isMultiFactorEnrollmentPanel() { 29 + return true; 30 + } 31 + 28 32 }
+24 -1
src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php
··· 3 3 final class PhabricatorMultiFactorSettingsPanel 4 4 extends PhabricatorSettingsPanel { 5 5 6 + private $isEnrollment; 7 + 6 8 public function getPanelKey() { 7 9 return 'multifactor'; 8 10 } ··· 17 19 18 20 public function getPanelGroupKey() { 19 21 return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY; 22 + } 23 + 24 + public function isMultiFactorEnrollmentPanel() { 25 + return true; 26 + } 27 + 28 + public function setIsEnrollment($is_enrollment) { 29 + $this->isEnrollment = $is_enrollment; 30 + return $this; 31 + } 32 + 33 + public function getIsEnrollment() { 34 + return $this->isEnrollment; 20 35 } 21 36 22 37 public function processRequest(AphrontRequest $request) { ··· 106 121 107 122 $buttons = array(); 108 123 124 + // If we're enrolling a new account in MFA, provide a small visual hint 125 + // that this is the button they want to click. 126 + if ($this->getIsEnrollment()) { 127 + $add_color = PHUIButtonView::BLUE; 128 + } else { 129 + $add_color = PHUIButtonView::GREY; 130 + } 131 + 109 132 $buttons[] = id(new PHUIButtonView()) 110 133 ->setTag('a') 111 134 ->setIcon('fa-plus') 112 135 ->setText(pht('Add Auth Factor')) 113 136 ->setHref($this->getPanelURI('?new=true')) 114 137 ->setWorkflow(true) 115 - ->setColor(PHUIButtonView::GREY); 138 + ->setColor($add_color); 116 139 117 140 $buttons[] = id(new PHUIButtonView()) 118 141 ->setTag('a')
+11
src/applications/settings/panel/PhabricatorSettingsPanel.php
··· 198 198 return false; 199 199 } 200 200 201 + /** 202 + * Return true if this panel should be available when enrolling in MFA on 203 + * a new account with MFA requiredd. 204 + * 205 + * @return bool True to allow configuration during MFA enrollment. 206 + * @task config 207 + */ 208 + public function isMultiFactorEnrollmentPanel() { 209 + return false; 210 + } 211 + 201 212 202 213 /* -( Panel Implementation )----------------------------------------------- */ 203 214