Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php
index 20547d8ca3..4c0003e806 100644
--- a/src/applications/auth/application/PhabricatorAuthApplication.php
+++ b/src/applications/auth/application/PhabricatorAuthApplication.php
@@ -1,144 +1,146 @@
<?php
final class PhabricatorAuthApplication extends PhabricatorApplication {
public function canUninstall() {
return false;
}
public function getBaseURI() {
return '/auth/';
}
public function getIcon() {
return 'fa-key';
}
public function isPinnedByDefault(PhabricatorUser $viewer) {
return $viewer->getIsAdmin();
}
public function getName() {
return pht('Auth');
}
public function getShortDescription() {
return pht('Login/Registration');
}
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
// NOTE: Although reasonable help exists for this in "Configuring Accounts
// and Registration", specifying help items here means we get the menu
// item in all the login/link interfaces, which is confusing and not
// helpful.
// TODO: Special case this, or split the auth and auth administration
// applications?
return array();
}
public function getApplicationGroup() {
return self::GROUP_ADMIN;
}
public function getRoutes() {
return array(
'/auth/' => array(
'' => 'PhabricatorAuthListController',
'config/' => array(
'new/' => 'PhabricatorAuthNewController',
'new/(?P<className>[^/]+)/' => 'PhabricatorAuthEditController',
'edit/(?P<id>\d+)/' => 'PhabricatorAuthEditController',
'(?P<action>enable|disable)/(?P<id>\d+)/'
=> 'PhabricatorAuthDisableController',
),
'login/(?P<pkey>[^/]+)/(?:(?P<extra>[^/]+)/)?'
=> 'PhabricatorAuthLoginController',
'(?P<loggedout>loggedout)/' => 'PhabricatorAuthStartController',
'invite/(?P<code>[^/]+)/' => 'PhabricatorAuthInviteController',
'register/(?:(?P<akey>[^/]+)/)?' => 'PhabricatorAuthRegisterController',
'start/' => 'PhabricatorAuthStartController',
'validate/' => 'PhabricatorAuthValidateController',
'finish/' => 'PhabricatorAuthFinishController',
'unlink/(?P<pkey>[^/]+)/' => 'PhabricatorAuthUnlinkController',
'(?P<action>link|refresh)/(?P<pkey>[^/]+)/'
=> 'PhabricatorAuthLinkController',
'confirmlink/(?P<akey>[^/]+)/'
=> 'PhabricatorAuthConfirmLinkController',
'session/terminate/(?P<id>[^/]+)/'
=> 'PhabricatorAuthTerminateSessionController',
'token/revoke/(?P<id>[^/]+)/'
=> 'PhabricatorAuthRevokeTokenController',
'session/downgrade/'
=> 'PhabricatorAuthDowngradeSessionController',
- 'multifactor/'
- => 'PhabricatorAuthNeedsMultiFactorController',
+ 'enroll/' => array(
+ '(?:(?P<pageKey>[^/]+)/)?(?:(?P<formSaved>saved)/)?'
+ => 'PhabricatorAuthNeedsMultiFactorController',
+ ),
'sshkey/' => array(
$this->getQueryRoutePattern('for/(?P<forPHID>[^/]+)/')
=> 'PhabricatorAuthSSHKeyListController',
'generate/' => 'PhabricatorAuthSSHKeyGenerateController',
'upload/' => 'PhabricatorAuthSSHKeyEditController',
'edit/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyEditController',
'revoke/(?P<id>\d+)/'
=> 'PhabricatorAuthSSHKeyRevokeController',
'view/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyViewController',
),
'password/' => 'PhabricatorAuthSetPasswordController',
'mfa/' => array(
$this->getQueryRoutePattern() =>
'PhabricatorAuthFactorProviderListController',
$this->getEditRoutePattern('edit/') =>
'PhabricatorAuthFactorProviderEditController',
'(?P<id>[1-9]\d*)/' =>
'PhabricatorAuthFactorProviderViewController',
),
'message/' => array(
$this->getQueryRoutePattern() =>
'PhabricatorAuthMessageListController',
$this->getEditRoutePattern('edit/') =>
'PhabricatorAuthMessageEditController',
'(?P<id>[1-9]\d*)/' =>
'PhabricatorAuthMessageViewController',
),
'contact/' => array(
$this->getEditRoutePattern('edit/') =>
'PhabricatorAuthContactNumberEditController',
'(?P<id>[1-9]\d*)/' =>
'PhabricatorAuthContactNumberViewController',
),
),
'/oauth/(?P<provider>\w+)/login/'
=> 'PhabricatorAuthOldOAuthRedirectController',
'/login/' => array(
'' => 'PhabricatorAuthStartController',
'email/' => 'PhabricatorEmailLoginController',
'once/'.
'(?P<type>[^/]+)/'.
'(?P<id>\d+)/'.
'(?P<key>[^/]+)/'.
'(?:(?P<emailID>\d+)/)?' => 'PhabricatorAuthOneTimeLoginController',
'refresh/' => 'PhabricatorRefreshCSRFController',
'mustverify/' => 'PhabricatorMustVerifyEmailController',
),
'/emailverify/(?P<code>[^/]+)/'
=> 'PhabricatorEmailVerificationController',
'/logout/' => 'PhabricatorLogoutController',
);
}
protected function getCustomCapabilities() {
return array(
AuthManageProvidersCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
);
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php
index 27e03485ca..92328b2000 100644
--- a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php
+++ b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php
@@ -1,109 +1,216 @@
<?php
final class PhabricatorAuthNeedsMultiFactorController
extends PhabricatorAuthController {
public function shouldRequireMultiFactorEnrollment() {
// Users need access to this controller in order to enroll in multi-factor
// auth.
return false;
}
public function shouldRequireEnabledUser() {
// Users who haven't been approved yet are allowed to enroll in MFA. We'll
// kick disabled users out later.
return false;
}
public function shouldRequireEmailVerification() {
// Users who haven't verified their email addresses yet can still enroll
// in MFA.
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
if ($viewer->getIsDisabled()) {
// We allowed unapproved and disabled users to hit this controller, but
// want to kick out disabled users now.
return new Aphront400Response();
}
- $panel = id(new PhabricatorMultiFactorSettingsPanel())
- ->setUser($viewer)
- ->setViewer($viewer)
- ->setOverrideURI($this->getApplicationURI('/multifactor/'))
- ->processRequest($request);
+ $panels = $this->loadPanels();
+
+ $multifactor_key = id(new PhabricatorMultiFactorSettingsPanel())
+ ->getPanelKey();
+
+ $panel_key = $request->getURIData('pageKey');
+ if (!strlen($panel_key)) {
+ $panel_key = $multifactor_key;
+ }
- if ($panel instanceof AphrontResponse) {
- return $panel;
+ if (!isset($panels[$panel_key])) {
+ return new Aphront404Response();
}
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb(pht('Add Multi-Factor Auth'));
+ $nav = $this->newNavigation();
+ $nav->selectFilter($panel_key);
+
+ $panel = $panels[$panel_key];
$viewer->updateMultiFactorEnrollment();
- if (!$viewer->getIsEnrolledInMultiFactor()) {
- $help = id(new PHUIInfoView())
- ->setTitle(pht('Add Multi-Factor Authentication To Your Account'))
- ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
- ->setErrors(
- array(
- pht(
- 'Before you can use Phabricator, you need to add multi-factor '.
- 'authentication to your account.'),
- pht(
- 'Multi-factor authentication helps secure your account by '.
- 'making it more difficult for attackers to gain access or '.
- 'take sensitive actions.'),
- pht(
- 'To learn more about multi-factor authentication, click the '.
- '%s button below.',
- phutil_tag('strong', array(), pht('Help'))),
- pht(
- 'To add an authentication factor, click the %s button below.',
- phutil_tag('strong', array(), pht('Add Authentication Factor'))),
- pht(
- 'To continue, add at least one authentication factor to your '.
- 'account.'),
- ));
+ if ($panel_key === $multifactor_key) {
+ $header_text = pht('Add Multi-Factor Auth');
+ $help = $this->newGuidance();
+ $panel->setIsEnrollment(true);
} else {
- $help = id(new PHUIInfoView())
- ->setTitle(pht('Multi-Factor Authentication Configured'))
- ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
- ->setErrors(
- array(
- pht(
- 'You have successfully configured multi-factor authentication '.
- 'for your account.'),
- pht(
- 'You can make adjustments from the Settings panel later.'),
- pht(
- 'When you are ready, %s.',
- phutil_tag(
- 'strong',
- array(),
- phutil_tag(
- 'a',
- array(
- 'href' => '/',
- ),
- pht('continue to Phabricator')))),
- ));
+ $header_text = $panel->getPanelName();
+ $help = null;
}
- $view = array(
- $help,
- $panel,
- );
+ $response = $panel
+ ->setController($this)
+ ->setNavigation($nav)
+ ->processRequest($request);
+
+ if (($response instanceof AphrontResponse) ||
+ ($response instanceof AphrontResponseProducerInterface)) {
+ return $response;
+ }
+
+ $crumbs = $this->buildApplicationCrumbs()
+ ->addTextCrumb(pht('Add Multi-Factor Auth'))
+ ->setBorder(true);
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader($header_text);
+
+ $view = id(new PHUITwoColumnView())
+ ->setHeader($header)
+ ->setFooter(
+ array(
+ $help,
+ $response,
+ ));
return $this->newPage()
->setTitle(pht('Add Multi-Factor Authentication'))
->setCrumbs($crumbs)
+ ->setNavigation($nav)
->appendChild($view);
}
+ private function loadPanels() {
+ $viewer = $this->getViewer();
+ $preferences = PhabricatorUserPreferences::loadUserPreferences($viewer);
+
+ $panels = PhabricatorSettingsPanel::getAllDisplayPanels();
+ $base_uri = $this->newEnrollBaseURI();
+
+ $result = array();
+ foreach ($panels as $key => $panel) {
+ $panel
+ ->setPreferences($preferences)
+ ->setViewer($viewer)
+ ->setUser($viewer)
+ ->setOverrideURI(urisprintf('%s%s/', $base_uri, $key));
+
+ if (!$panel->isEnabled()) {
+ continue;
+ }
+
+ if (!$panel->isUserPanel()) {
+ continue;
+ }
+
+ if (!$panel->isMultiFactorEnrollmentPanel()) {
+ continue;
+ }
+
+ if (!empty($result[$key])) {
+ throw new Exception(pht(
+ "Two settings panels share the same panel key ('%s'): %s, %s.",
+ $key,
+ get_class($panel),
+ get_class($result[$key])));
+ }
+
+ $result[$key] = $panel;
+ }
+
+ return $result;
+ }
+
+
+ private function newNavigation() {
+ $viewer = $this->getViewer();
+
+ $enroll_uri = $this->newEnrollBaseURI();
+
+ $nav = id(new AphrontSideNavFilterView())
+ ->setBaseURI(new PhutilURI($enroll_uri));
+
+ $multifactor_key = id(new PhabricatorMultiFactorSettingsPanel())
+ ->getPanelKey();
+
+ $nav->addFilter(
+ $multifactor_key,
+ pht('Enroll in MFA'),
+ null,
+ 'fa-exclamation-triangle blue');
+
+ $panels = $this->loadPanels();
+
+ if ($panels) {
+ $nav->addLabel(pht('Settings'));
+ }
+
+ foreach ($panels as $panel_key => $panel) {
+ if ($panel_key === $multifactor_key) {
+ continue;
+ }
+
+ $nav->addFilter(
+ $panel->getPanelKey(),
+ $panel->getPanelName(),
+ null,
+ $panel->getPanelMenuIcon());
+ }
+
+ return $nav;
+ }
+
+ private function newEnrollBaseURI() {
+ return $this->getApplicationURI('enroll/');
+ }
+
+ private function newGuidance() {
+ $viewer = $this->getViewer();
+
+ if ($viewer->getIsEnrolledInMultiFactor()) {
+ $guidance = pht(
+ '{icon check, color="green"} **Setup Complete!**'.
+ "\n\n".
+ 'You have successfully configured multi-factor authentication '.
+ 'for your account.'.
+ "\n\n".
+ 'You can make adjustments from the [[ /settings/ | Settings ]] panel '.
+ 'later.');
+
+ return $this->newDialog()
+ ->setTitle(pht('Multi-Factor Authentication Setup Complete'))
+ ->setWidth(AphrontDialogView::WIDTH_FULL)
+ ->appendChild(new PHUIRemarkupView($viewer, $guidance))
+ ->addCancelButton('/', pht('Continue'));
+ }
+
+ $messages = array();
+
+ $messages[] = pht(
+ 'Before you can use Phabricator, you need to add multi-factor '.
+ 'authentication to your account. Multi-factor authentication helps '.
+ 'secure your account by making it more difficult for attackers to '.
+ 'gain access or take sensitive actions.');
+
+ $view = id(new PHUIInfoView())
+ ->setTitle(pht('Add Multi-Factor Authentication To Your Account'))
+ ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
+ ->setErrors($messages);
+
+ return $view;
+ }
+
}
diff --git a/src/applications/auth/controller/contact/PhabricatorAuthContactNumberController.php b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberController.php
index a713f48a3b..3ae923fbbc 100644
--- a/src/applications/auth/controller/contact/PhabricatorAuthContactNumberController.php
+++ b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberController.php
@@ -1,16 +1,31 @@
<?php
abstract class PhabricatorAuthContactNumberController
extends PhabricatorAuthController {
+ // Users may need to access these controllers to enroll in SMS MFA during
+ // account setup.
+
+ public function shouldRequireMultiFactorEnrollment() {
+ return false;
+ }
+
+ public function shouldRequireEnabledUser() {
+ return false;
+ }
+
+ public function shouldRequireEmailVerification() {
+ return false;
+ }
+
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Contact Numbers'),
pht('/settings/panel/contact/'));
return $crumbs;
}
}
diff --git a/src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php b/src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php
index 7834bff593..3e4ed8880e 100644
--- a/src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php
@@ -1,73 +1,77 @@
<?php
final class PhabricatorContactNumbersSettingsPanel
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'contact';
}
public function getPanelName() {
return pht('Contact Numbers');
}
public function getPanelMenuIcon() {
return 'fa-mobile';
}
public function getPanelGroupKey() {
return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY;
}
+ public function isMultiFactorEnrollmentPanel() {
+ return true;
+ }
+
public function processRequest(AphrontRequest $request) {
$user = $this->getUser();
$viewer = $request->getUser();
$numbers = id(new PhabricatorAuthContactNumberQuery())
->setViewer($viewer)
->withObjectPHIDs(array($user->getPHID()))
->execute();
$rows = array();
foreach ($numbers as $number) {
$rows[] = array(
$number->newIconView(),
phutil_tag(
'a',
array(
'href' => $number->getURI(),
),
$number->getDisplayName()),
phabricator_datetime($number->getDateCreated(), $viewer),
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(
pht("You haven't added any contact numbers to your account."))
->setHeaders(
array(
null,
pht('Number'),
pht('Created'),
))
->setColumnClasses(
array(
null,
'wide pri',
'right',
));
$buttons = array();
$buttons[] = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-plus')
->setText(pht('Add Contact Number'))
->setHref('/auth/contact/edit/')
->setColor(PHUIButtonView::GREY);
return $this->newBox(pht('Contact Numbers'), $table, $buttons);
}
}
diff --git a/src/applications/settings/panel/PhabricatorLanguageSettingsPanel.php b/src/applications/settings/panel/PhabricatorLanguageSettingsPanel.php
index 65a0be4e79..39bd5deac9 100644
--- a/src/applications/settings/panel/PhabricatorLanguageSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorLanguageSettingsPanel.php
@@ -1,28 +1,32 @@
<?php
final class PhabricatorLanguageSettingsPanel
extends PhabricatorEditEngineSettingsPanel {
const PANELKEY = 'language';
public function getPanelName() {
return pht('Language');
}
public function getPanelMenuIcon() {
return 'fa-globe';
}
public function getPanelGroupKey() {
return PhabricatorSettingsAccountPanelGroup::PANELGROUPKEY;
}
public function isManagementPanel() {
return true;
}
public function isTemplatePanel() {
return true;
}
+ public function isMultiFactorEnrollmentPanel() {
+ return true;
+ }
+
}
diff --git a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php
index 93a95254a9..d2e9c34247 100644
--- a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php
@@ -1,333 +1,356 @@
<?php
final class PhabricatorMultiFactorSettingsPanel
extends PhabricatorSettingsPanel {
+ private $isEnrollment;
+
public function getPanelKey() {
return 'multifactor';
}
public function getPanelName() {
return pht('Multi-Factor Auth');
}
public function getPanelMenuIcon() {
return 'fa-lock';
}
public function getPanelGroupKey() {
return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY;
}
+ public function isMultiFactorEnrollmentPanel() {
+ return true;
+ }
+
+ public function setIsEnrollment($is_enrollment) {
+ $this->isEnrollment = $is_enrollment;
+ return $this;
+ }
+
+ public function getIsEnrollment() {
+ return $this->isEnrollment;
+ }
+
public function processRequest(AphrontRequest $request) {
if ($request->getExists('new') || $request->getExists('providerPHID')) {
return $this->processNew($request);
}
if ($request->getExists('edit')) {
return $this->processEdit($request);
}
if ($request->getExists('delete')) {
return $this->processDelete($request);
}
$user = $this->getUser();
$viewer = $request->getUser();
$factors = id(new PhabricatorAuthFactorConfigQuery())
->setViewer($viewer)
->withUserPHIDs(array($user->getPHID()))
->setOrderVector(array('-id'))
->execute();
$rows = array();
$rowc = array();
$highlight_id = $request->getInt('id');
foreach ($factors as $factor) {
$provider = $factor->getFactorProvider();
if ($factor->getID() == $highlight_id) {
$rowc[] = 'highlighted';
} else {
$rowc[] = null;
}
$rows[] = array(
javelin_tag(
'a',
array(
'href' => $this->getPanelURI('?edit='.$factor->getID()),
'sigil' => 'workflow',
),
$factor->getFactorName()),
$provider->getDisplayName(),
phabricator_datetime($factor->getDateCreated(), $viewer),
javelin_tag(
'a',
array(
'href' => $this->getPanelURI('?delete='.$factor->getID()),
'sigil' => 'workflow',
'class' => 'small button button-grey',
),
pht('Remove')),
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(
pht("You haven't added any authentication factors to your account yet."));
$table->setHeaders(
array(
pht('Name'),
pht('Type'),
pht('Created'),
'',
));
$table->setColumnClasses(
array(
'wide pri',
'',
'right',
'action',
));
$table->setRowClasses($rowc);
$table->setDeviceVisibility(
array(
true,
false,
false,
true,
));
$help_uri = PhabricatorEnv::getDoclink(
'User Guide: Multi-Factor Authentication');
$buttons = array();
+ // If we're enrolling a new account in MFA, provide a small visual hint
+ // that this is the button they want to click.
+ if ($this->getIsEnrollment()) {
+ $add_color = PHUIButtonView::BLUE;
+ } else {
+ $add_color = PHUIButtonView::GREY;
+ }
+
$buttons[] = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-plus')
->setText(pht('Add Auth Factor'))
->setHref($this->getPanelURI('?new=true'))
->setWorkflow(true)
- ->setColor(PHUIButtonView::GREY);
+ ->setColor($add_color);
$buttons[] = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-book')
->setText(pht('Help'))
->setHref($help_uri)
->setColor(PHUIButtonView::GREY);
return $this->newBox(pht('Authentication Factors'), $table, $buttons);
}
private function processNew(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $this->getUser();
$cancel_uri = $this->getPanelURI();
// Check that we have providers before we send the user through the MFA
// gate, so you don't authenticate and then immediately get roadblocked.
$providers = id(new PhabricatorAuthFactorProviderQuery())
->setViewer($viewer)
->withStatuses(array(PhabricatorAuthFactorProvider::STATUS_ACTIVE))
->execute();
if (!$providers) {
return $this->newDialog()
->setTitle(pht('No MFA Providers'))
->appendParagraph(
pht(
'There are no active MFA providers. At least one active provider '.
'must be available to add new MFA factors.'))
->addCancelButton($cancel_uri);
}
$providers = mpull($providers, null, 'getPHID');
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$cancel_uri);
$selected_phid = $request->getStr('providerPHID');
if (empty($providers[$selected_phid])) {
$selected_provider = null;
} else {
$selected_provider = $providers[$selected_phid];
}
if (!$selected_provider) {
$menu = id(new PHUIObjectItemListView())
->setViewer($viewer)
->setBig(true)
->setFlush(true);
foreach ($providers as $provider_phid => $provider) {
$provider_uri = id(new PhutilURI($this->getPanelURI()))
->setQueryParam('providerPHID', $provider_phid);
$item = id(new PHUIObjectItemView())
->setHeader($provider->getDisplayName())
->setHref($provider_uri)
->setClickable(true)
->setImageIcon($provider->newIconView())
->addAttribute($provider->getDisplayDescription());
$menu->addItem($item);
}
return $this->newDialog()
->setTitle(pht('Choose Factor Type'))
->appendChild($menu)
->addCancelButton($cancel_uri);
}
$form = id(new AphrontFormView())
->setViewer($viewer);
$config = $selected_provider->processAddFactorForm(
$form,
$request,
$user);
if ($config) {
$config->save();
$log = PhabricatorUserLog::initializeNewLog(
$viewer,
$user->getPHID(),
PhabricatorUserLog::ACTION_MULTI_ADD);
$log->save();
$user->updateMultiFactorEnrollment();
// Terminate other sessions so they must log in and survive the
// multi-factor auth check.
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
$user,
new PhutilOpaqueEnvelope(
$request->getCookie(PhabricatorCookies::COOKIE_SESSION)));
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?id='.$config->getID()));
}
return $this->newDialog()
->addHiddenInput('providerPHID', $selected_provider->getPHID())
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle(pht('Add Authentication Factor'))
->appendChild($form->buildLayoutView())
->addSubmitButton(pht('Continue'))
->addCancelButton($cancel_uri);
}
private function processEdit(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $this->getUser();
$factor = id(new PhabricatorAuthFactorConfig())->loadOneWhere(
'id = %d AND userPHID = %s',
$request->getInt('edit'),
$user->getPHID());
if (!$factor) {
return new Aphront404Response();
}
$e_name = true;
$errors = array();
if ($request->isFormPost()) {
$name = $request->getStr('name');
if (!strlen($name)) {
$e_name = pht('Required');
$errors[] = pht(
'Authentication factors must have a name to identify them.');
}
if (!$errors) {
$factor->setFactorName($name);
$factor->save();
$user->updateMultiFactorEnrollment();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?id='.$factor->getID()));
}
} else {
$name = $factor->getFactorName();
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setName('name')
->setLabel(pht('Name'))
->setValue($name)
->setError($e_name));
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('edit', $factor->getID())
->setTitle(pht('Edit Authentication Factor'))
->setErrors($errors)
->appendChild($form->buildLayoutView())
->addSubmitButton(pht('Save'))
->addCancelButton($this->getPanelURI());
return id(new AphrontDialogResponse())
->setDialog($dialog);
}
private function processDelete(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $this->getUser();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$this->getPanelURI());
$factor = id(new PhabricatorAuthFactorConfig())->loadOneWhere(
'id = %d AND userPHID = %s',
$request->getInt('delete'),
$user->getPHID());
if (!$factor) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
$factor->delete();
$log = PhabricatorUserLog::initializeNewLog(
$viewer,
$user->getPHID(),
PhabricatorUserLog::ACTION_MULTI_REMOVE);
$log->save();
$user->updateMultiFactorEnrollment();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI());
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('delete', $factor->getID())
->setTitle(pht('Delete Authentication Factor'))
->appendParagraph(
pht(
'Really remove the authentication factor %s from your account?',
phutil_tag('strong', array(), $factor->getFactorName())))
->addSubmitButton(pht('Remove Factor'))
->addCancelButton($this->getPanelURI());
return id(new AphrontDialogResponse())
->setDialog($dialog);
}
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanel.php b/src/applications/settings/panel/PhabricatorSettingsPanel.php
index 8250418812..e2efd92093 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanel.php
@@ -1,311 +1,322 @@
<?php
/**
* Defines a settings panel. Settings panels appear in the Settings application,
* and behave like lightweight controllers -- generally, they render some sort
* of form with options in it, and then update preferences when the user
* submits the form. By extending this class, you can add new settings
* panels.
*
* @task config Panel Configuration
* @task panel Panel Implementation
* @task internal Internals
*/
abstract class PhabricatorSettingsPanel extends Phobject {
private $user;
private $viewer;
private $controller;
private $navigation;
private $overrideURI;
private $preferences;
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setOverrideURI($override_uri) {
$this->overrideURI = $override_uri;
return $this;
}
final public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
final public function getController() {
return $this->controller;
}
final public function setNavigation(AphrontSideNavFilterView $navigation) {
$this->navigation = $navigation;
return $this;
}
final public function getNavigation() {
return $this->navigation;
}
public function setPreferences(PhabricatorUserPreferences $preferences) {
$this->preferences = $preferences;
return $this;
}
public function getPreferences() {
return $this->preferences;
}
final public static function getAllPanels() {
$panels = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getPanelKey')
->execute();
return msortv($panels, 'getPanelOrderVector');
}
final public static function getAllDisplayPanels() {
$panels = array();
$groups = PhabricatorSettingsPanelGroup::getAllPanelGroupsWithPanels();
foreach ($groups as $group) {
foreach ($group->getPanels() as $key => $panel) {
$panels[$key] = $panel;
}
}
return $panels;
}
final public function getPanelGroup() {
$group_key = $this->getPanelGroupKey();
$groups = PhabricatorSettingsPanelGroup::getAllPanelGroupsWithPanels();
$group = idx($groups, $group_key);
if (!$group) {
throw new Exception(
pht(
'No settings panel group with key "%s" exists!',
$group_key));
}
return $group;
}
/* -( Panel Configuration )------------------------------------------------ */
/**
* Return a unique string used in the URI to identify this panel, like
* "example".
*
* @return string Unique panel identifier (used in URIs).
* @task config
*/
public function getPanelKey() {
return $this->getPhobjectClassConstant('PANELKEY');
}
/**
* Return a human-readable description of the panel's contents, like
* "Example Settings".
*
* @return string Human-readable panel name.
* @task config
*/
abstract public function getPanelName();
/**
* Return an icon for the panel in the menu.
*
* @return string Icon identifier.
* @task config
*/
public function getPanelMenuIcon() {
return 'fa-wrench';
}
/**
* Return a panel group key constant for this panel.
*
* @return const Panel group key.
* @task config
*/
abstract public function getPanelGroupKey();
/**
* Return false to prevent this panel from being displayed or used. You can
* do, e.g., configuration checks here, to determine if the feature your
* panel controls is unavailable in this install. By default, all panels are
* enabled.
*
* @return bool True if the panel should be shown.
* @task config
*/
public function isEnabled() {
return true;
}
/**
* Return true if this panel is available to users while editing their own
* settings.
*
* @return bool True to enable management on behalf of a user.
* @task config
*/
public function isUserPanel() {
return true;
}
/**
* Return true if this panel is available to administrators while managing
* bot and mailing list accounts.
*
* @return bool True to enable management on behalf of accounts.
* @task config
*/
public function isManagementPanel() {
return false;
}
/**
* Return true if this panel is available while editing settings templates.
*
* @return bool True to allow editing in templates.
* @task config
*/
public function isTemplatePanel() {
return false;
}
+ /**
+ * Return true if this panel should be available when enrolling in MFA on
+ * a new account with MFA requiredd.
+ *
+ * @return bool True to allow configuration during MFA enrollment.
+ * @task config
+ */
+ public function isMultiFactorEnrollmentPanel() {
+ return false;
+ }
+
/* -( Panel Implementation )----------------------------------------------- */
/**
* Process a user request for this settings panel. Implement this method like
* a lightweight controller. If you return an @{class:AphrontResponse}, the
* response will be used in whole. If you return anything else, it will be
* treated as a view and composed into a normal settings page.
*
* Generally, render your settings panel by returning a form, then return
* a redirect when the user saves settings.
*
* @param AphrontRequest Incoming request.
* @return wild Response to request, either as an
* @{class:AphrontResponse} or something which can
* be composed into a @{class:AphrontView}.
* @task panel
*/
abstract public function processRequest(AphrontRequest $request);
/**
* Get the URI for this panel.
*
* @param string? Optional path to append.
* @return string Relative URI for the panel.
* @task panel
*/
final public function getPanelURI($path = '') {
$path = ltrim($path, '/');
if ($this->overrideURI) {
return rtrim($this->overrideURI, '/').'/'.$path;
}
$key = $this->getPanelKey();
$key = phutil_escape_uri($key);
$user = $this->getUser();
if ($user) {
if ($user->isLoggedIn()) {
$username = $user->getUsername();
return "/settings/user/{$username}/page/{$key}/{$path}";
} else {
// For logged-out users, we can't put their username in the URI. This
// page will prompt them to login, then redirect them to the correct
// location.
return "/settings/panel/{$key}/";
}
} else {
$builtin = $this->getPreferences()->getBuiltinKey();
return "/settings/builtin/{$builtin}/page/{$key}/{$path}";
}
}
/* -( Internals )---------------------------------------------------------- */
/**
* Generates a key to sort the list of panels.
*
* @return string Sortable key.
* @task internal
*/
final public function getPanelOrderVector() {
return id(new PhutilSortVector())
->addString($this->getPanelName());
}
protected function newDialog() {
return $this->getController()->newDialog();
}
protected function writeSetting(
PhabricatorUserPreferences $preferences,
$key,
$value) {
$viewer = $this->getViewer();
$request = $this->getController()->getRequest();
$editor = id(new PhabricatorUserPreferencesEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$xactions = array();
$xactions[] = $preferences->newTransaction($key, $value);
$editor->applyTransactions($preferences, $xactions);
}
public function newBox($title, $content, $actions = array()) {
$header = id(new PHUIHeaderView())
->setHeader($title);
foreach ($actions as $action) {
$header->addActionLink($action);
}
$view = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($content)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG);
return $view;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 19:24 (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127936
Default Alt Text
(37 KB)

Event Timeline