src/eric7/WebBrowser/WebAuth/Fido2ManagementDialog.py

branch
eric7
changeset 10856
b19cefceca15
parent 10854
30c45bd597e6
child 10857
abcb288e7e17
--- a/src/eric7/WebBrowser/WebAuth/Fido2ManagementDialog.py	Sat Jul 20 11:14:51 2024 +0200
+++ b/src/eric7/WebBrowser/WebAuth/Fido2ManagementDialog.py	Mon Jul 22 10:15:41 2024 +0200
@@ -7,7 +7,13 @@
 """
 
 from PyQt6.QtCore import Qt, QTimer, pyqtSlot
-from PyQt6.QtWidgets import QDialog, QTreeWidgetItem
+from PyQt6.QtWidgets import (
+    QDialog,
+    QDialogButtonBox,
+    QMenu,
+    QToolButton,
+    QTreeWidgetItem,
+)
 
 from eric7.EricGui import EricPixmapCache
 from eric7.EricGui.EricOverrideCursor import EricOverrideCursor
@@ -31,10 +37,13 @@
     DisplayNameColumn = 2
     UserNameColumn = 3
 
-    def __init__(self, parent=None):
+    def __init__(self, standalone=False, parent=None):
         """
         Constructor
 
+        @param standalone flag indicating the standalone management application
+            (defaults to False)
+        @type bool (optional)
         @param parent reference to the parent widget (defaults to None)
         @type QWidget (optional)
         """
@@ -44,14 +53,56 @@
         self.reloadButton.setIcon(EricPixmapCache.getIcon("reload"))
         self.lockButton.setIcon(EricPixmapCache.getIcon("locked"))
 
+        self.menuButton.setObjectName("fido2_supermenu_button")
+        self.menuButton.setIcon(EricPixmapCache.getIcon("superMenu"))
+        self.menuButton.setToolTip(self.tr("Security Key Management Menu"))
+        self.menuButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
+        self.menuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
+        self.menuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
+        self.menuButton.setAutoRaise(True)
+        self.menuButton.setShowMenuInside(True)
+
+        self.__initManagementMenu()
+
+        if standalone:
+            self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setText(
+                self.tr("Quit")
+            )
+
         self.reloadButton.clicked.connect(self.__populateDeviceSelector)
 
         self.__manager = Fido2Management(parent=self)
-        ##self.__manager.deviceConnected.connect(self.__deviceConnected)
-        ##self.__manager.deviceDisconnected.connect(self.__deviceDisconnected)
 
         QTimer.singleShot(0, self.__populateDeviceSelector)
 
+    def __initManagementMenu(self):
+        """
+        Private method to initialize the security key management menu with
+        actions not needed so much.
+        """
+        self.__mgmtMenu = QMenu()
+        self.__mgmtMenu.addAction(self.tr("Show Info"), self.__showSecurityKeyInfo)
+        self.__mgmtMenu.addSeparator()
+        self.__mgmtMenu.addAction(
+            self.tr("Reset Security Key"), self.__resetSecurityKey
+        )
+        # TODO: potentially add these 'config' actions
+        #       - Force PIN Change
+        #       - Set Minimum PIN Length
+        #       - Toggle 'Always Require UV'
+
+        self.__mgmtMenu.aboutToShow.connect(self.__aboutToShowManagementMenu)
+
+        self.menuButton.setMenu(self.__mgmtMenu)
+
+    @pyqtSlot()
+    def __aboutToShowManagementMenu(self):
+        """
+        Private slot to prepare the security key management menu before it is shown.
+        """
+        # TODO: not implemented yet
+        pass
+
     ############################################################################
     ## methods related to device handling
     ############################################################################
@@ -104,8 +155,7 @@
 
         self.lockButton.setEnabled(securityKey is not None)
         self.pinButton.setEnabled(securityKey is not None)
-        self.showInfoButton.setEnabled(securityKey is not None)
-        self.resetButton.setEnabled(securityKey is not None)
+        self.menuButton.setEnabled(securityKey is not None)
         self.loadPasskeysButton.setEnabled(securityKey is not None)
 
         if securityKey is not None:
@@ -166,12 +216,61 @@
             self.__manager.lockDevice()
 
     @pyqtSlot()
-    def on_showInfoButton_clicked(self):
+    def __showSecurityKeyInfo(self):
+        """
+        Private slot to show some info about the selected security key.
+        """
+        from .Fido2InfoDialog import Fido2InfoDialog
+
+        securityKey = self.securityKeysComboBox.currentData()
+        dlg = Fido2InfoDialog(
+            header=securityKey.product_name, manager=self.__manager, parent=self
+        )
+        dlg.exec()
+
+    @pyqtSlot()
+    def __resetSecurityKey(self):
+        """
+        Private slot to reset the selected security key.
         """
-        Slot documentation goes here.
-        """
-        # TODO: not implemented yet
-        pass
+        title = self.tr("Reset Security Key")
+
+        yes = EricMessageBox.yesNo(
+            parent=self,
+            title=title,
+            text=self.tr(
+                "<p>Shall the selected security key really be reset?</p><p><b>WARNING"
+                ":</b> This will delete all passkeys and restore factory settings.</p>"
+            ),
+        )
+        if yes:
+            if len(self.__manager.getDevices()) != 1:
+                EricMessageBox.critical(
+                    self,
+                    title=title,
+                    text=self.tr(
+                        "Only one security key can be connected to perform a reset."
+                        " Remove all other security keys and try again."
+                    ),
+                )
+                return
+
+            EricMessageBox.information(
+                self,
+                title=title,
+                text=self.tr(
+                    "Confirm this dialog then remove and re-insert the security key."
+                    " Confirm the reset by touching it."
+                ),
+            )
+
+            ok, msg = self.__manager.resetDevice()
+            if ok:
+                EricMessageBox.information(self, title, msg)
+            else:
+                EricMessageBox.warning(self, title, msg)
+
+            self.__populateDeviceSelector()
 
     ############################################################################
     ## methods related to PIN handling
@@ -241,13 +340,9 @@
                 dlg = Fido2PinDialog(
                     mode=Fido2PinDialogMode.GET,
                     title=title,
-                    message=self.tr(
-                        "Enter the PIN to unlock the security key (%n attempt(s)"
-                        " remaining.",
-                        "",
-                        retries,
-                    ),
+                    message=self.tr("Enter the PIN to unlock the security key."),
                     minLength=self.__manager.getMinimumPinLength(),
+                    retries=retries,
                     parent=self,
                 )
                 if dlg.exec() == QDialog.DialogCode.Accepted:
@@ -262,23 +357,60 @@
         """
         Private slot to set a PIN for the selected security key.
         """
-        # TODO: not implemented yet
-        pass
+        retries = self.__manager.getPinRetries()[0]
+        title = self.tr("Set PIN")
+
+        dlg = Fido2PinDialog(
+            mode=Fido2PinDialogMode.SET,
+            title=title,
+            message=self.tr("Enter the PIN for the security key."),
+            minLength=self.__manager.getMinimumPinLength(),
+            retries=retries,
+            parent=self,
+        )
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            newPin = dlg.getPins()[1]
+            ok, msg = self.__manager.setPin(newPin)
+            if ok:
+                self.lockButton.setEnabled(True)
+                self.lockButton.setChecked(False)
+                self.pinButton.setText(self.tr("Change PIN"))
+                self.loadPasskeysButton.setEnabled(True)
+                self.__manager.reconnectToDevice()
+                EricMessageBox.information(self, title, msg)
+            else:
+                EricMessageBox.warning(self, title, msg)
 
     @pyqtSlot()
     def __changePin(self):
         """
-        Private slot to set a PIN for the selected security key.
+        Private slot to change the PIN of the selected security key.
         """
-        # TODO: not implemented yet
-        pass
+        retries = self.__manager.getPinRetries()[0]
+        title = self.tr("Change PIN")
+
+        dlg = Fido2PinDialog(
+            mode=Fido2PinDialogMode.CHANGE,
+            title=title,
+            message=self.tr("Enter the current and new PINs."),
+            minLength=self.__manager.getMinimumPinLength(),
+            retries=retries,
+            parent=self,
+        )
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            oldPin, newPin = dlg.getPins()
+            ok, msg = self.__manager.changePin(oldPin, newPin)
+            if ok:
+                self.lockButton.setChecked(False)
+                EricMessageBox.information(self, title, msg)
+            else:
+                EricMessageBox.warning(self, title, msg)
 
     @pyqtSlot()
     def on_pinButton_clicked(self):
         """
         Private slot to set or change the PIN for the selected security key.
         """
-        # TODO: not implemented yet
         if self.__manager.hasPin():
             self.__changePin()
         else:
@@ -345,14 +477,14 @@
     @pyqtSlot()
     def on_loadPasskeysButton_clicked(self):
         """
-        Slot documentation goes here.
+        Private slot to (re-)populate the passkeys list.
         """
         self.__populatePasskeysList()
 
     @pyqtSlot()
     def on_passkeysList_itemSelectionChanged(self):
         """
-        Slot documentation goes here.
+        Private slot handling the selection of a passkey.
         """
         enableButtons = (
             len(self.passkeysList.selectedItems()) == 1

eric ide

mercurial