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

Allow installs to require multi-factor authentication for all users

Summary: Ref T5089. Adds a `security.require-multi-factor-auth` which forces all users to enroll in MFA before they can use their accounts.

Test Plan:
Config:

{F159750}

Roadblock:

{F159748}

After configuration:

{F159749}

- Required MFA, got roadblocked, added MFA, got unblocked.
- Removed MFA, got blocked again.
- Used `bin/auth strip` to strip MFA, got blocked.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5089

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

+236 -4
+2
resources/sql/autopatches/20140524.auth.mfa.cache.sql
··· 1 + ALTER TABLE {$NAMESPACE}_user.user 2 + ADD isEnrolledInMultiFactor BOOL NOT NULL DEFAULT 0;
+2
src/__phutil_library_map__.php
··· 1247 1247 'PhabricatorAuthManagementStripWorkflow' => 'applications/auth/management/PhabricatorAuthManagementStripWorkflow.php', 1248 1248 'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php', 1249 1249 'PhabricatorAuthNeedsApprovalController' => 'applications/auth/controller/PhabricatorAuthNeedsApprovalController.php', 1250 + 'PhabricatorAuthNeedsMultiFactorController' => 'applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php', 1250 1251 'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php', 1251 1252 'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php', 1252 1253 'PhabricatorAuthOneTimeLoginController' => 'applications/auth/controller/PhabricatorAuthOneTimeLoginController.php', ··· 4013 4014 'PhabricatorAuthManagementStripWorkflow' => 'PhabricatorAuthManagementWorkflow', 4014 4015 'PhabricatorAuthManagementWorkflow' => 'PhabricatorManagementWorkflow', 4015 4016 'PhabricatorAuthNeedsApprovalController' => 'PhabricatorAuthController', 4017 + 'PhabricatorAuthNeedsMultiFactorController' => 'PhabricatorAuthController', 4016 4018 'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController', 4017 4019 'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController', 4018 4020 'PhabricatorAuthOneTimeLoginController' => 'PhabricatorAuthController',
+2
src/applications/auth/application/PhabricatorApplicationAuth.php
··· 101 101 => 'PhabricatorAuthTerminateSessionController', 102 102 'session/downgrade/' 103 103 => 'PhabricatorAuthDowngradeSessionController', 104 + 'multifactor/' 105 + => 'PhabricatorAuthNeedsMultiFactorController', 104 106 ), 105 107 106 108 '/oauth/(?P<provider>\w+)/login/'
+92
src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthNeedsMultiFactorController 4 + extends PhabricatorAuthController { 5 + 6 + public function shouldRequireMultiFactorEnrollment() { 7 + // Users need access to this controller in order to enroll in multi-factor 8 + // auth. 9 + return false; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $panel = id(new PhabricatorSettingsPanelMultiFactor()) 17 + ->setUser($viewer) 18 + ->setViewer($viewer) 19 + ->setOverrideURI($this->getApplicationURI('/multifactor/')) 20 + ->processRequest($request); 21 + 22 + if ($panel instanceof AphrontResponse) { 23 + return $panel; 24 + } 25 + 26 + $crumbs = $this->buildApplicationCrumbs(); 27 + $crumbs->addTextCrumb(pht('Add Multi-Factor Auth')); 28 + 29 + $viewer->updateMultiFactorEnrollment(); 30 + 31 + if (!$viewer->getIsEnrolledInMultiFactor()) { 32 + $help = id(new AphrontErrorView()) 33 + ->setTitle(pht('Add Multi-Factor Authentication To Your Account')) 34 + ->setSeverity(AphrontErrorView::SEVERITY_WARNING) 35 + ->setErrors( 36 + array( 37 + pht( 38 + 'Before you can use Phabricator, you need to add multi-factor '. 39 + 'authentication to your account.'), 40 + pht( 41 + 'Multi-factor authentication helps secure your account by '. 42 + 'making it more difficult for attackers to gain access or '. 43 + 'take senstive actions.'), 44 + pht( 45 + 'To learn more about multi-factor authentication, click the '. 46 + '%s button below.', 47 + phutil_tag('strong', array(), pht('Help'))), 48 + pht( 49 + 'To add an authentication factor, click the %s button below.', 50 + phutil_tag('strong', array(), pht('Add Authentication Factor'))), 51 + pht( 52 + 'To continue, add at least one authentication factor to your '. 53 + 'account.'), 54 + )); 55 + } else { 56 + $help = id(new AphrontErrorView()) 57 + ->setTitle(pht('Multi-Factor Authentication Configured')) 58 + ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) 59 + ->setErrors( 60 + array( 61 + pht( 62 + 'You have successfully configured multi-factor authentication '. 63 + 'for your account.'), 64 + pht( 65 + 'You can make adjustments from the Settings panel later.'), 66 + pht( 67 + 'When you are ready, %s.', 68 + phutil_tag( 69 + 'strong', 70 + array(), 71 + phutil_tag( 72 + 'a', 73 + array( 74 + 'href' => '/', 75 + ), 76 + pht('continue to Phabricator')))), 77 + )); 78 + } 79 + 80 + return $this->buildApplicationPage( 81 + array( 82 + $crumbs, 83 + $help, 84 + $panel, 85 + ), 86 + array( 87 + 'title' => pht('Add Multi-Factor Authentication'), 88 + 'device' => true, 89 + )); 90 + } 91 + 92 + }
+9
src/applications/auth/management/PhabricatorAuthManagementStripWorkflow.php
··· 153 153 $console->writeOut("%s\n", pht('Stripping authentication factors...')); 154 154 155 155 foreach ($factors as $factor) { 156 + $user = id(new PhabricatorPeopleQuery()) 157 + ->setViewer($this->getViewer()) 158 + ->withPHIDs(array($factor->getUserPHID())) 159 + ->executeOne(); 160 + 156 161 $factor->delete(); 162 + 163 + if ($user) { 164 + $user->updateMultiFactorEnrollment(); 165 + } 157 166 } 158 167 159 168 $console->writeOut("%s\n", pht('Done.'));
+36
src/applications/base/controller/PhabricatorController.php
··· 32 32 return false; 33 33 } 34 34 35 + public function shouldRequireMultiFactorEnrollment() { 36 + if (!$this->shouldRequireLogin()) { 37 + return false; 38 + } 39 + 40 + if (!$this->shouldRequireEnabledUser()) { 41 + return false; 42 + } 43 + 44 + if ($this->shouldAllowPartialSessions()) { 45 + return false; 46 + } 47 + 48 + $user = $this->getRequest()->getUser(); 49 + if (!$user->getIsStandardUser()) { 50 + return false; 51 + } 52 + 53 + return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth'); 54 + } 55 + 35 56 public function willBeginExecution() { 36 57 $request = $this->getRequest(); 37 58 ··· 148 169 $login_controller = new PhabricatorAuthFinishController($request); 149 170 $this->setCurrentApplication($auth_application); 150 171 return $this->delegateToController($login_controller); 172 + } 173 + } 174 + 175 + // Check if the user needs to configure MFA. 176 + $need_mfa = $this->shouldRequireMultiFactorEnrollment(); 177 + $have_mfa = $user->getIsEnrolledInMultiFactor(); 178 + if ($need_mfa && !$have_mfa) { 179 + // Check if the cache is just out of date. Otherwise, roadblock the user 180 + // and require MFA enrollment. 181 + $user->updateMultiFactorEnrollment(); 182 + if (!$user->getIsEnrolledInMultiFactor()) { 183 + $mfa_controller = new PhabricatorAuthNeedsMultiFactorController( 184 + $request); 185 + $this->setCurrentApplication($auth_application); 186 + return $this->delegateToController($mfa_controller); 151 187 } 152 188 } 153 189
+1
src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php
··· 57 57 $env->overrideEnvConfig('policy.allow-public', false); 58 58 $env->overrideEnvConfig('auth.require-email-verification', false); 59 59 $env->overrideEnvConfig('auth.email-domains', array()); 60 + $env->overrideEnvConfig('security.require-multi-factor-auth', false); 60 61 61 62 62 63 // Test standard defaults.
+16
src/applications/config/option/PhabricatorSecurityConfigOptions.php
··· 82 82 pht('Force HTTPS'), 83 83 pht('Allow HTTP'), 84 84 )), 85 + $this->newOption('security.require-multi-factor-auth', 'bool', false) 86 + ->setLocked(true) 87 + ->setSummary( 88 + pht('Require all users to configure multi-factor authentication.')) 89 + ->setDescription( 90 + pht( 91 + 'By default, Phabricator allows users to add multi-factor '. 92 + 'authentication to their accounts, but does not require it. '. 93 + 'By enabling this option, you can force all users to add '. 94 + 'at least one authentication factor before they can use their '. 95 + 'accounts.')) 96 + ->setBoolOptions( 97 + array( 98 + pht('Multi-Factor Required'), 99 + pht('Multi-Factor Optional'), 100 + )), 85 101 $this->newOption( 86 102 'phabricator.csrf-key', 87 103 'string',
+57
src/applications/people/storage/PhabricatorUser.php
··· 1 1 <?php 2 2 3 + /** 4 + * @task factors Multi-Factor Authentication 5 + */ 3 6 final class PhabricatorUser 4 7 extends PhabricatorUserDAO 5 8 implements ··· 32 35 protected $isDisabled = 0; 33 36 protected $isEmailVerified = 0; 34 37 protected $isApproved = 0; 38 + protected $isEnrolledInMultiFactor = 0; 35 39 36 40 protected $accountSecret; 37 41 ··· 687 691 return id(new PhabricatorUser())->loadOneWhere( 688 692 'phid = %s', 689 693 $email->getUserPHID()); 694 + } 695 + 696 + /* -( Multi-Factor Authentication )---------------------------------------- */ 697 + 698 + 699 + /** 700 + * Update the flag storing this user's enrollment in multi-factor auth. 701 + * 702 + * With certain settings, we need to check if a user has MFA on every page, 703 + * so we cache MFA enrollment on the user object for performance. Calling this 704 + * method synchronizes the cache by examining enrollment records. After 705 + * updating the cache, use @{method:getIsEnrolledInMultiFactor} to check if 706 + * the user is enrolled. 707 + * 708 + * This method should be called after any changes are made to a given user's 709 + * multi-factor configuration. 710 + * 711 + * @return void 712 + * @task factors 713 + */ 714 + public function updateMultiFactorEnrollment() { 715 + $factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere( 716 + 'userPHID = %s', 717 + $this->getPHID()); 718 + 719 + $enrolled = count($factors) ? 1 : 0; 720 + if ($enrolled !== $this->isEnrolledInMultiFactor) { 721 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 722 + queryfx( 723 + $this->establishConnection('w'), 724 + 'UPDATE %T SET isEnrolledInMultiFactor = %d WHERE id = %d', 725 + $this->getTableName(), 726 + $enrolled, 727 + $this->getID()); 728 + unset($unguarded); 729 + 730 + $this->isEnrolledInMultiFactor = $enrolled; 731 + } 732 + } 733 + 734 + 735 + /** 736 + * Check if the user is enrolled in multi-factor authentication. 737 + * 738 + * Enrolled users have one or more multi-factor authentication sources 739 + * attached to their account. For performance, this value is cached. You 740 + * can use @{method:updateMultiFactorEnrollment} to update the cache. 741 + * 742 + * @return bool True if the user is enrolled. 743 + * @task factors 744 + */ 745 + public function getIsEnrolledInMultiFactor() { 746 + return $this->isEnrolledInMultiFactor; 690 747 } 691 748 692 749
+13 -4
src/applications/settings/panel/PhabricatorSettingsPanel.php
··· 12 12 * @task config Panel Configuration 13 13 * @task panel Panel Implementation 14 14 * @task internal Internals 15 - * 16 - * @group settings 17 15 */ 18 16 abstract class PhabricatorSettingsPanel { 19 17 20 18 private $user; 21 19 private $viewer; 20 + private $overrideURI; 21 + 22 22 23 23 public function setUser(PhabricatorUser $user) { 24 24 $this->user = $user; ··· 36 36 37 37 public function getViewer() { 38 38 return $this->viewer; 39 + } 40 + 41 + public function setOverrideURI($override_uri) { 42 + $this->overrideURI = $override_uri; 43 + return $this; 39 44 } 40 45 41 46 ··· 148 153 * @task panel 149 154 */ 150 155 final public function getPanelURI($path = '') { 156 + $path = ltrim($path, '/'); 157 + 158 + if ($this->overrideURI) { 159 + return rtrim($this->overrideURI, '/').'/'.$path; 160 + } 161 + 151 162 $key = $this->getPanelKey(); 152 163 $key = phutil_escape_uri($key); 153 - 154 - $path = ltrim($path, '/'); 155 164 156 165 if ($this->getUser()->getPHID() != $this->getViewer()->getPHID()) { 157 166 $user_id = $this->getUser()->getID();
+6
src/applications/settings/panel/PhabricatorSettingsPanelMultiFactor.php
··· 196 196 PhabricatorUserLog::ACTION_MULTI_ADD); 197 197 $log->save(); 198 198 199 + $user->updateMultiFactorEnrollment(); 200 + 199 201 return id(new AphrontRedirectResponse()) 200 202 ->setURI($this->getPanelURI('?id='.$config->getID())); 201 203 } ··· 237 239 if (!$errors) { 238 240 $factor->setFactorName($name); 239 241 $factor->save(); 242 + 243 + $user->updateMultiFactorEnrollment(); 240 244 241 245 return id(new AphrontRedirectResponse()) 242 246 ->setURI($this->getPanelURI('?id='.$factor->getID())); ··· 292 296 $user->getPHID(), 293 297 PhabricatorUserLog::ACTION_MULTI_REMOVE); 294 298 $log->save(); 299 + 300 + $user->updateMultiFactorEnrollment(); 295 301 296 302 return id(new AphrontRedirectResponse()) 297 303 ->setURI($this->getPanelURI());