Page MenuHomePhabricator

D8875.diff
No OneTemporary

D8875.diff

diff --git a/resources/sql/autopatches/20140427.mfactor.1.sql b/resources/sql/autopatches/20140427.mfactor.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20140427.mfactor.1.sql
@@ -0,0 +1,13 @@
+CREATE TABLE {$NAMESPACE}_auth.auth_factorconfig (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ userPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ factorKey VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ factorName LONGTEXT NOT NULL COLLATE utf8_general_ci,
+ factorSecret LONGTEXT NOT NULL COLLATE utf8_bin,
+ properties LONGTEXT NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ KEY `key_user` (userPHID),
+ UNIQUE KEY `key_phid` (phid)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1208,6 +1208,10 @@
'PhabricatorAuthDisableController' => 'applications/auth/controller/config/PhabricatorAuthDisableController.php',
'PhabricatorAuthDowngradeSessionController' => 'applications/auth/controller/PhabricatorAuthDowngradeSessionController.php',
'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php',
+ 'PhabricatorAuthFactor' => 'applications/auth/factor/PhabricatorAuthFactor.php',
+ 'PhabricatorAuthFactorConfig' => 'applications/auth/storage/PhabricatorAuthFactorConfig.php',
+ 'PhabricatorAuthFactorTOTP' => 'applications/auth/factor/PhabricatorAuthFactorTOTP.php',
+ 'PhabricatorAuthFactorTOTPTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTOTPTestCase.php',
'PhabricatorAuthHighSecurityRequiredException' => 'applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php',
'PhabricatorAuthHighSecurityToken' => 'applications/auth/data/PhabricatorAuthHighSecurityToken.php',
'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php',
@@ -1220,6 +1224,7 @@
'PhabricatorAuthNeedsApprovalController' => 'applications/auth/controller/PhabricatorAuthNeedsApprovalController.php',
'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php',
'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php',
+ 'PhabricatorAuthPHIDTypeAuthFactor' => 'applications/auth/phid/PhabricatorAuthPHIDTypeAuthFactor.php',
'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php',
'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php',
'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php',
@@ -2065,6 +2070,7 @@
'PhabricatorSettingsPanelEmailPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelEmailPreferences.php',
'PhabricatorSettingsPanelExternalAccounts' => 'applications/settings/panel/PhabricatorSettingsPanelExternalAccounts.php',
'PhabricatorSettingsPanelHomePreferences' => 'applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php',
+ 'PhabricatorSettingsPanelMultiFactor' => 'applications/settings/panel/PhabricatorSettingsPanelMultiFactor.php',
'PhabricatorSettingsPanelPassword' => 'applications/settings/panel/PhabricatorSettingsPanelPassword.php',
'PhabricatorSettingsPanelSSHKeys' => 'applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php',
'PhabricatorSettingsPanelSearchPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php',
@@ -3956,6 +3962,10 @@
'PhabricatorAuthDisableController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthDowngradeSessionController' => 'PhabricatorAuthController',
'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController',
+ 'PhabricatorAuthFactor' => 'Phobject',
+ 'PhabricatorAuthFactorConfig' => 'PhabricatorAuthDAO',
+ 'PhabricatorAuthFactorTOTP' => 'PhabricatorAuthFactor',
+ 'PhabricatorAuthFactorTOTPTestCase' => 'PhabricatorTestCase',
'PhabricatorAuthHighSecurityRequiredException' => 'Exception',
'PhabricatorAuthLinkController' => 'PhabricatorAuthController',
'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController',
@@ -3967,6 +3977,7 @@
'PhabricatorAuthNeedsApprovalController' => 'PhabricatorAuthController',
'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController',
+ 'PhabricatorAuthPHIDTypeAuthFactor' => 'PhabricatorPHIDType',
'PhabricatorAuthProviderConfig' =>
array(
0 => 'PhabricatorAuthDAO',
@@ -4962,6 +4973,7 @@
'PhabricatorSettingsPanelEmailPreferences' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelExternalAccounts' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelHomePreferences' => 'PhabricatorSettingsPanel',
+ 'PhabricatorSettingsPanelMultiFactor' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelPassword' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSSHKeys' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSearchPreferences' => 'PhabricatorSettingsPanel',
diff --git a/src/applications/auth/factor/PhabricatorAuthFactor.php b/src/applications/auth/factor/PhabricatorAuthFactor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/factor/PhabricatorAuthFactor.php
@@ -0,0 +1,51 @@
+<?php
+
+abstract class PhabricatorAuthFactor extends Phobject {
+
+ abstract public function getFactorName();
+ abstract public function getFactorKey();
+ abstract public function getFactorDescription();
+ abstract public function processAddFactorForm(
+ AphrontFormView $form,
+ AphrontRequest $request,
+ PhabricatorUser $user);
+
+ public static function getAllFactors() {
+ static $factors;
+
+ if ($factors === null) {
+ $map = id(new PhutilSymbolLoader())
+ ->setAncestorClass(__CLASS__)
+ ->loadObjects();
+
+ $factors = array();
+ foreach ($map as $factor) {
+ $key = $factor->getFactorKey();
+ if (empty($factors[$key])) {
+ $factors[$key] = $factor;
+ } else {
+ $this_class = get_class($factor);
+ $that_class = get_class($factors[$key]);
+
+ throw new Exception(
+ pht(
+ 'Two auth factors (with classes "%s" and "%s") both provide '.
+ 'implementations with the same key ("%s"). Each factor must '.
+ 'have a unique key.',
+ $this_class,
+ $that_class,
+ $key));
+ }
+ }
+ }
+
+ return $factors;
+ }
+
+ protected function newConfigForUser(PhabricatorUser $user) {
+ return id(new PhabricatorAuthFactorConfig())
+ ->setUserPHID($user->getPHID())
+ ->setFactorKey($this->getFactorKey());
+ }
+
+}
diff --git a/src/applications/auth/factor/PhabricatorAuthFactorTOTP.php b/src/applications/auth/factor/PhabricatorAuthFactorTOTP.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/factor/PhabricatorAuthFactorTOTP.php
@@ -0,0 +1,179 @@
+<?php
+
+final class PhabricatorAuthFactorTOTP extends PhabricatorAuthFactor {
+
+ public function getFactorKey() {
+ return 'totp';
+ }
+
+ public function getFactorName() {
+ return pht('Mobile Phone App (TOTP)');
+ }
+
+ public function getFactorDescription() {
+ return pht(
+ 'Attach a mobile authenticator application (like Authy '.
+ 'or Google Authenticator) to your account. When you need to '.
+ 'authenticate, you will enter a code shown on your phone.');
+ }
+
+ public function processAddFactorForm(
+ AphrontFormView $form,
+ AphrontRequest $request,
+ PhabricatorUser $user) {
+
+
+ $key = $request->getStr('totpkey');
+ if (!strlen($key)) {
+ // TODO: When the user submits a key, we should require that it be
+ // one we generated for them, so there's no way an attacker can ever
+ // force a key they control onto an account. However, it's clumsy to
+ // do this right now. Once we have one-time tokens for SMS and email,
+ // we should be able to put it on that infrastructure.
+ $key = self::generateNewTOTPKey();
+ }
+
+ $code = $request->getStr('totpcode');
+
+ $e_code = true;
+ if ($request->getExists('totp')) {
+ $okay = self::verifyTOTPCode(
+ $user,
+ new PhutilOpaqueEnvelope($key),
+ $code);
+
+ if ($okay) {
+ $config = $this->newConfigForUser($user)
+ ->setFactorName(pht('Mobile App (TOTP)'))
+ ->setFactorSecret($key);
+
+ return $config;
+ } else {
+ if (!strlen($code)) {
+ $e_code = pht('Required');
+ } else {
+ $e_code = pht('Invalid');
+ }
+ }
+ }
+
+ $form->addHiddenInput('totp', true);
+ $form->addHiddenInput('totpkey', $key);
+
+ $form->appendRemarkupInstructions(
+ pht(
+ 'First, download an authenticator application on your phone. Two '.
+ 'applications which work well are **Authy** and **Google '.
+ 'Authenticator**, but any other TOTP application should also work.'));
+
+ $form->appendInstructions(
+ pht(
+ 'Launch the application on your phone, and add a new entry for '.
+ 'this Phabricator install. When prompted, enter the key shown '.
+ 'below into the application.'));
+
+ $form->appendChild(
+ id(new AphrontFormStaticControl())
+ ->setLabel(pht('Key'))
+ ->setValue(phutil_tag('strong', array(), $key)));
+
+ $form->appendInstructions(
+ pht(
+ '(If given an option, select that this key is "Time Based", not '.
+ '"Counter Based".)'));
+
+ $form->appendInstructions(
+ pht(
+ 'After entering the key, the application should display a numeric '.
+ 'code. Enter that code below to confirm that you have configured '.
+ 'the authenticator correctly:'));
+
+ $form->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel(pht('TOTP Code'))
+ ->setName('totpcode')
+ ->setValue($code)
+ ->setError($e_code));
+
+ }
+
+ public static function generateNewTOTPKey() {
+ return strtoupper(Filesystem::readRandomCharacters(16));
+ }
+
+ public static function verifyTOTPCode(
+ PhabricatorUser $user,
+ PhutilOpaqueEnvelope $key,
+ $code) {
+
+ // TODO: This should use rate limiting to prevent multiple attempts in a
+ // short period of time.
+
+ $now = (int)(time() / 30);
+
+ // Allow the user to enter a code a few minutes away on either side, in
+ // case the server or client has some clock skew.
+ for ($offset = -2; $offset <= 2; $offset++) {
+ $real = self::getTOTPCode($key, $now + $offset);
+ if ($real === $code) {
+ return true;
+ }
+ }
+
+ // TODO: After validating a code, this should mark it as used and prevent
+ // it from being reused.
+
+ return false;
+ }
+
+
+ public static function base32Decode($buf) {
+ $buf = strtoupper($buf);
+
+ $map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
+ $map = str_split($map);
+ $map = array_flip($map);
+
+ $out = '';
+ $len = strlen($buf);
+ $acc = 0;
+ $bits = 0;
+ for ($ii = 0; $ii < $len; $ii++) {
+ $chr = $buf[$ii];
+ $val = $map[$chr];
+
+ $acc = $acc << 5;
+ $acc = $acc + $val;
+
+ $bits += 5;
+ if ($bits >= 8) {
+ $bits = $bits - 8;
+ $out .= chr(($acc & (0xFF << $bits)) >> $bits);
+ }
+ }
+
+ return $out;
+ }
+
+ public static function getTOTPCode(PhutilOpaqueEnvelope $key, $timestamp) {
+ $binary_timestamp = pack('N*', 0).pack('N*', $timestamp);
+ $binary_key = self::base32Decode($key->openEnvelope());
+
+ $hash = hash_hmac('sha1', $binary_timestamp, $binary_key, true);
+
+ // See RFC 4226.
+
+ $offset = ord($hash[19]) & 0x0F;
+
+ $code = ((ord($hash[$offset + 0]) & 0x7F) << 24) |
+ ((ord($hash[$offset + 1]) & 0xFF) << 16) |
+ ((ord($hash[$offset + 2]) & 0xFF) << 8) |
+ ((ord($hash[$offset + 3]) ) );
+
+ $code = ($code % 1000000);
+ $code = str_pad($code, 6, '0', STR_PAD_LEFT);
+
+ return $code;
+ }
+
+}
diff --git a/src/applications/auth/factor/__tests__/PhabricatorAuthFactorTOTPTestCase.php b/src/applications/auth/factor/__tests__/PhabricatorAuthFactorTOTPTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/factor/__tests__/PhabricatorAuthFactorTOTPTestCase.php
@@ -0,0 +1,44 @@
+<?php
+
+final class PhabricatorAuthFactorTOTPTestCase extends PhabricatorTestCase {
+
+ public function testTOTPCodeGeneration() {
+ $tests = array(
+ array(
+ 'AAAABBBBCCCCDDDD',
+ 46620383,
+ '724492',
+ ),
+ array(
+ 'AAAABBBBCCCCDDDD',
+ 46620390,
+ '935803',
+ ),
+ array(
+ 'Z3RFWEFJN233R23P',
+ 46620398,
+ '273030',
+ ),
+
+ // This is testing the case where the code has leading zeroes.
+ array(
+ 'Z3RFWEFJN233R23W',
+ 46620399,
+ '072346',
+ ),
+ );
+
+ foreach ($tests as $test) {
+ list($key, $time, $code) = $test;
+ $this->assertEqual(
+ $code,
+ PhabricatorAuthFactorTOTP::getTOTPCode(
+ new PhutilOpaqueEnvelope($key),
+ $time));
+ }
+
+ }
+
+
+
+}
diff --git a/src/applications/auth/phid/PhabricatorAuthPHIDTypeAuthFactor.php b/src/applications/auth/phid/PhabricatorAuthPHIDTypeAuthFactor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/phid/PhabricatorAuthPHIDTypeAuthFactor.php
@@ -0,0 +1,39 @@
+<?php
+
+final class PhabricatorAuthPHIDTypeAuthFactor extends PhabricatorPHIDType {
+
+ const TYPECONST = 'AFTR';
+
+ public function getTypeConstant() {
+ return self::TYPECONST;
+ }
+
+ public function getTypeName() {
+ return pht('Auth Factor');
+ }
+
+ public function newObject() {
+ return new PhabricatorAuthFactorConfig();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ // TODO: Maybe we need this eventually?
+ throw new Exception(pht('Not Supported'));
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $factor = $objects[$phid];
+
+ $handle->setName($factor->getFactorName());
+ }
+ }
+
+}
diff --git a/src/applications/auth/storage/PhabricatorAuthFactorConfig.php b/src/applications/auth/storage/PhabricatorAuthFactorConfig.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/storage/PhabricatorAuthFactorConfig.php
@@ -0,0 +1,29 @@
+<?php
+
+final class PhabricatorAuthFactorConfig extends PhabricatorAuthDAO {
+
+ protected $userPHID;
+ protected $factorKey;
+ protected $factorName;
+ protected $factorSecret;
+ protected $properties = array();
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_SERIALIZATION => array(
+ 'properties' => self::SERIALIZATION_JSON,
+ ),
+ self::CONFIG_AUX_PHID => true,
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ PhabricatorAuthPHIDTypeAuthFactor::TYPECONST);
+ }
+
+ public function getImplementation() {
+ return idx(PhabricatorAuthFactor::getAllFactors(), $this->getFactorKey());
+ }
+
+}
diff --git a/src/applications/people/storage/PhabricatorUserLog.php b/src/applications/people/storage/PhabricatorUserLog.php
--- a/src/applications/people/storage/PhabricatorUserLog.php
+++ b/src/applications/people/storage/PhabricatorUserLog.php
@@ -30,6 +30,9 @@
const ACTION_ENTER_HISEC = 'hisec-enter';
const ACTION_EXIT_HISEC = 'hisec-exit';
+ const ACTION_MULTI_ADD = 'multi-add';
+ const ACTION_MULTI_REMOVE = 'multi-remove';
+
protected $actorPHID;
protected $userPHID;
protected $action;
@@ -63,6 +66,8 @@
self::ACTION_CHANGE_USERNAME => pht('Change Username'),
self::ACTION_ENTER_HISEC => pht('Hisec: Enter'),
self::ACTION_EXIT_HISEC => pht('Hisec: Exit'),
+ self::ACTION_MULTI_ADD => pht('Multi-Factor: Add Factor'),
+ self::ACTION_MULTI_REMOVE => pht('Multi-Factor: Remove Factor'),
);
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelActivity.php b/src/applications/settings/panel/PhabricatorSettingsPanelActivity.php
--- a/src/applications/settings/panel/PhabricatorSettingsPanelActivity.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelActivity.php
@@ -3,6 +3,10 @@
final class PhabricatorSettingsPanelActivity
extends PhabricatorSettingsPanel {
+ public function isEditableByAdministrators() {
+ return true;
+ }
+
public function getPanelKey() {
return 'activity';
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelMultiFactor.php b/src/applications/settings/panel/PhabricatorSettingsPanelMultiFactor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelMultiFactor.php
@@ -0,0 +1,309 @@
+<?php
+
+final class PhabricatorSettingsPanelMultiFactor
+ extends PhabricatorSettingsPanel {
+
+ public function getPanelKey() {
+ return 'multifactor';
+ }
+
+ public function getPanelName() {
+ return pht('Multi-Factor Auth');
+ }
+
+ public function getPanelGroup() {
+ return pht('Authentication');
+ }
+
+ public function isEnabled() {
+ // TODO: Enable this panel once more pieces work correctly.
+ return false;
+ }
+
+ public function processRequest(AphrontRequest $request) {
+ if ($request->getExists('new')) {
+ 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 PhabricatorAuthFactorConfig())->loadAllWhere(
+ 'userPHID = %s',
+ $user->getPHID());
+
+ $rows = array();
+ $rowc = array();
+
+ $highlight_id = $request->getInt('id');
+ foreach ($factors as $factor) {
+
+ $impl = $factor->getImplementation();
+ if ($impl) {
+ $type = $impl->getFactorName();
+ } else {
+ $type = $factor->getFactorKey();
+ }
+
+ 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()),
+ $type,
+ phabricator_datetime($factor->getDateCreated(), $viewer),
+ javelin_tag(
+ 'a',
+ array(
+ 'href' => $this->getPanelURI('?delete='.$factor->getID()),
+ 'sigil' => 'workflow',
+ 'class' => 'small grey button',
+ ),
+ 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,
+ ));
+
+ $panel = new PHUIObjectBoxView();
+ $header = new PHUIHeaderView();
+
+ $create_icon = id(new PHUIIconView())
+ ->setSpriteSheet(PHUIIconView::SPRITE_ICONS)
+ ->setSpriteIcon('new');
+ $create_button = id(new PHUIButtonView())
+ ->setText(pht('Add Authentication Factor'))
+ ->setHref($this->getPanelURI('?new=true'))
+ ->setTag('a')
+ ->setWorkflow(true)
+ ->setIcon($create_icon);
+
+ $header->setHeader(pht('Authentication Factors'));
+ $header->addActionLink($create_button);
+
+ $panel->setHeader($header);
+ $panel->appendChild($table);
+
+ return $panel;
+ }
+
+ private function processNew(AphrontRequest $request) {
+ $viewer = $request->getUser();
+ $user = $this->getUser();
+
+ $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
+ $viewer,
+ $request,
+ $this->getPanelURI());
+
+ $factors = PhabricatorAuthFactor::getAllFactors();
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer);
+
+ $type = $request->getStr('type');
+ if (empty($factors[$type]) || !$request->isFormPost()) {
+ $factor = null;
+ } else {
+ $factor = $factors[$type];
+ }
+
+ $dialog = id(new AphrontDialogView())
+ ->setUser($viewer)
+ ->addHiddenInput('new', true);
+
+ if ($factor === null) {
+ $choice_control = id(new AphrontFormRadioButtonControl())
+ ->setName('type')
+ ->setValue(key($factors));
+
+ foreach ($factors as $available_factor) {
+ $choice_control->addButton(
+ $available_factor->getFactorKey(),
+ $available_factor->getFactorName(),
+ $available_factor->getFactorDescription());
+ }
+
+ $dialog->appendParagraph(
+ pht(
+ 'Adding an additional authentication factor increases the security '.
+ 'of your account.'));
+
+ $form
+ ->appendChild($choice_control);
+ } else {
+ $dialog->addHiddenInput('type', $type);
+
+ $config = $factor->processAddFactorForm(
+ $form,
+ $request,
+ $user);
+
+ if ($config) {
+ $config->save();
+
+ $log = PhabricatorUserLog::initializeNewLog(
+ $viewer,
+ $user->getPHID(),
+ PhabricatorUserLog::ACTION_MULTI_ADD);
+ $log->save();
+
+ return id(new AphrontRedirectResponse())
+ ->setURI($this->getPanelURI('?id='.$config->getID()));
+ }
+ }
+
+ $dialog
+ ->setWidth(AphrontDialogView::WIDTH_FORM)
+ ->setTitle(pht('Add Authentication Factor'))
+ ->appendChild($form->buildLayoutView())
+ ->addSubmitButton(pht('Continue'))
+ ->addCancelButton($this->getPanelURI());
+
+ return id(new AphrontDialogResponse())
+ ->setDialog($dialog);
+ }
+
+ 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();
+
+ 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();
+
+ 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);
+ }
+
+
+}

File Metadata

Mime Type
text/plain
Expires
Sun, May 12, 6:38 AM (3 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6290830
Default Alt Text
D8875.diff (25 KB)

Event Timeline