Add barebones support for deleting TOTP devices
authorTim Düsterhus <duesterhus@woltlab.com>
Fri, 6 Nov 2020 15:42:54 +0000 (16:42 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Mon, 16 Nov 2020 16:29:04 +0000 (17:29 +0100)
wcfsetup/install/files/lib/system/user/multifactor/TotpMultifactorMethod.class.php
wcfsetup/install/files/lib/system/user/multifactor/totp/Totp.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index d8c692cfb3e33bc0b3558afdd9c4133231bee042..d35025166272069ab46cc5a7215bdc518c584736 100644 (file)
@@ -3,6 +3,8 @@ namespace wcf\system\user\multifactor;
 use ParagonIE\ConstantTime\Hex;
 use wcf\system\form\builder\button\FormButton;
 use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\field\ButtonFormField;
+use wcf\system\form\builder\field\dependency\IsNotClickedFormFieldDependency;
 use wcf\system\form\builder\field\HiddenFormField;
 use wcf\system\form\builder\field\IFormField;
 use wcf\system\form\builder\field\RadioButtonFormField;
@@ -75,6 +77,34 @@ class TotpMultifactorMethod implements IMultifactorMethod {
                                        ->addClass('buttonPrimary'),
                        ]);
                $form->appendChild($newDeviceContainer);
+               
+               // Note: The order of the two parts of the form is important. Pressing submit within an input
+               // will implicitly press the first submit button. If this container comes first the submit
+               // button will be a delete button.
+               if ($setupId) {
+                       $sql = "SELECT  deviceID, deviceName, createTime, useTime
+                               FROM    wcf".WCF_N."_user_multifactor_totp
+                               WHERE   setupID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([$setupId]);
+                       $devicesContainer = FormContainer::create('devices')
+                               ->label('wcf.user.security.multifactor.totp.devices');
+                       while ($row = $statement->fetchArray()) {
+                               $devicesContainer->appendChildren([
+                                       $button = ButtonFormField::create('delete_'.$row['deviceID'])
+                                               ->buttonLabel($row['deviceName'])
+                                               ->objectProperty('delete')
+                                               ->value($row['deviceID']),
+                               ]);
+                               
+                               $newDeviceContainer->addDependency(
+                                       IsNotClickedFormFieldDependency::create('delete_'.$row['deviceID'])
+                                               ->field($button)
+                               );
+                       }
+                       
+                       $form->appendChild($devicesContainer);
+               }
        }
        
        /**
@@ -82,17 +112,48 @@ class TotpMultifactorMethod implements IMultifactorMethod {
         */
        public function processManagementForm(IFormDocument $form, int $setupId): void {
                $formData = $form->getData();
-
-               $sql = "INSERT INTO wcf".WCF_N."_user_multifactor_totp (setupID, deviceID, deviceName, secret, minCounter, createTime) VALUES (?, ?, ?, ?, ?, ?)";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([
-                       $setupId,
-                       Hex::encode(\random_bytes(16)),
-                       $formData['data']['deviceName'],
-                       $formData['data']['secret'],
-                       $formData['data']['code']['minCounter'],
-                       TIME_NOW,
-               ]);
+               
+               assert(
+                       (!empty($formData['data']) && empty($formData['delete'])) ||
+                       (empty($formData['data']) && !empty($formData['delete']))
+               );
+               
+               if (!empty($formData['delete'])) {
+                       $sql = "DELETE FROM     wcf".WCF_N."_user_multifactor_totp
+                               WHERE           setupID = ?
+                                       AND     deviceID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([
+                               $setupId,
+                               $formData['delete'],
+                       ]);
+                       
+                       $sql = "SELECT  COUNT(*)
+                               FROM    wcf".WCF_N."_user_multifactor_totp
+                               WHERE   setupID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([
+                               $setupId,
+                       ]);
+                       
+                       if (!$statement->fetchSingleColumn()) {
+                               throw new \LogicException('Unreachable');
+                       }
+               }
+               else {
+                       $sql = "INSERT INTO     wcf".WCF_N."_user_multifactor_totp
+                                               (setupID, deviceID, deviceName, secret, minCounter, createTime)
+                               VALUES          (?, ?, ?, ?, ?, ?)";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute([
+                               $setupId,
+                               Hex::encode(\random_bytes(16)),
+                               $formData['data']['deviceName'],
+                               $formData['data']['secret'],
+                               $formData['data']['code']['minCounter'],
+                               TIME_NOW,
+                       ]);
+               }
        }
        
        /**
index c884cd494e98dd4dcb6e7cfb4f1c512425a874da..f512549bf56fee30144f8a294f0fae0332beb231 100644 (file)
@@ -7,7 +7,7 @@ namespace wcf\system\user\multifactor\totp;
  * @author     Tim Duesterhus
  * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\System\User\Multifactor
+ * @package    WoltLabSuite\System\User\Multifactor\Totp
  * @since      5.4
  */
 final class Totp {
index 433815cee890488ce55fcd4a9711b7b6ea08186d..79e9e3773d7b4a310782745e180dcafed70649eb 100644 (file)
@@ -4855,6 +4855,7 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.security.multifactor.setup"><![CDATA[Einrichten]]></item>
                <item name="wcf.user.security.multifactor.active"><![CDATA[Aktiv]]></item>
                <item name="wcf.user.security.multifactor.backup.generateCodes"><![CDATA[Codes generieren]]></item>
+               <item name="wcf.user.security.multifactor.totp.devices"><![CDATA[Eingerichtete Geräte]]></item>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophäen]]></item>
index 8cd607cfc4872d5905c4d2a3eb5401455f90e582..88463d036a303624f92e33ec13206799e097525f 100644 (file)
@@ -4852,6 +4852,7 @@ Open the link below to access the user profile:
                <item name="wcf.user.security.multifactor.setup"><![CDATA[Set Up]]></item>
                <item name="wcf.user.security.multifactor.active"><![CDATA[Active]]></item>
                <item name="wcf.user.security.multifactor.backup.generateCodes"><![CDATA[Generate Codes]]></item>
+               <item name="wcf.user.security.multifactor.totp.devices"><![CDATA[Set Up Devices]]></item>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophies]]></item>