Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15413235
D8875.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
25 KB
Referenced Files
None
Subscribers
None
D8875.id.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Thu, Mar 20, 5:35 PM (1 d, 7 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7223397
Default Alt Text
D8875.id.diff (25 KB)
Attached To
Mode
D8875: Add multi-factor auth and TOTP support
Attached
Detach File
Event Timeline
Log In to Comment