diff -r b19cefceca15 -r abcb288e7e17 src/eric7/WebBrowser/WebAuth/Fido2ManagementDialog.py --- a/src/eric7/WebBrowser/WebAuth/Fido2ManagementDialog.py Mon Jul 22 10:15:41 2024 +0200 +++ b/src/eric7/WebBrowser/WebAuth/Fido2ManagementDialog.py Mon Jul 22 15:24:27 2024 +0200 @@ -10,6 +10,7 @@ from PyQt6.QtWidgets import ( QDialog, QDialogButtonBox, + QInputDialog, QMenu, QToolButton, QTreeWidgetItem, @@ -72,6 +73,8 @@ 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) @@ -86,10 +89,16 @@ 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.addSeparator() + self.__forcePinChangeAct = self.__mgmtMenu.addAction( + self.tr("Force PIN Change"), self.__forcePinChange + ) + self.__minPinLengthAct = self.__mgmtMenu.addAction( + self.tr("Set Minimum PIN Length"), self.__setMinimumPinLength + ) + self.__toggleAlwaysUvAct = self.__mgmtMenu.addAction( + self.tr("Toggle 'Always Require User Verification'"), self.__toggleAlwaysUv + ) self.__mgmtMenu.aboutToShow.connect(self.__aboutToShowManagementMenu) @@ -100,8 +109,18 @@ """ Private slot to prepare the security key management menu before it is shown. """ - # TODO: not implemented yet - pass + self.__forcePinChangeAct.setEnabled( + self.__manager.forcePinChangeSupported() + and not self.__manager.pinChangeRequired() + ) + self.__minPinLengthAct.setEnabled( + self.__manager.canSetMinimumPinLength() + and not self.__manager.pinChangeRequired() + ) + self.__toggleAlwaysUvAct.setEnabled( + self.__manager.canToggleAlwaysUv() + and not self.__manager.pinChangeRequired() + ) ############################################################################ ## methods related to device handling @@ -114,7 +133,6 @@ """ self.__manager.disconnectFromDevice() self.securityKeysComboBox.clear() - self.reloadButton.setEnabled(False) securityKeys = self.__manager.getDevices() @@ -128,8 +146,6 @@ securityKey, ) - self.reloadButton.setEnabled(True) - if len(securityKeys) == 0: EricMessageBox.information( self, @@ -148,39 +164,60 @@ @param index index of the selected security key @type int """ - self.lockButton.setChecked(False) self.__manager.disconnectFromDevice() securityKey = self.securityKeysComboBox.itemData(index) - - self.lockButton.setEnabled(securityKey is not None) - self.pinButton.setEnabled(securityKey is not None) - self.menuButton.setEnabled(securityKey is not None) - self.loadPasskeysButton.setEnabled(securityKey is not None) - if securityKey is not None: self.__manager.connectToDevice(securityKey) - hasPin = self.__manager.hasPin() - forcedPinChange = self.__manager.forcedPinChange() - if hasPin is True: - self.pinButton.setText(self.tr("Change PIN")) - elif hasPin is False: - self.pinButton.setText(self.tr("Set PIN")) - else: - self.pinButton.setEnabled(False) - if forcedPinChange or hasPin is False: - self.lockButton.setEnabled(False) - self.loadPasskeysButton.setEnabled(False) - msg = ( - self.tr("A PIN change is required.") - if forcedPinChange - else self.tr("You must set a PIN first.") - ) - EricMessageBox.information( - self, - self.tr("FIDO2 Security Key Management"), - msg, - ) + + @pyqtSlot() + def __deviceConnected(self): + """ + Private slot handling the device connected signal. + """ + self.lockButton.setEnabled(True) + self.pinButton.setEnabled(True) + self.menuButton.setEnabled(True) + self.loadPasskeysButton.setEnabled(True) + + hasPin = self.__manager.hasPin() + forcedPinChange = self.__manager.pinChangeRequired() + if hasPin is True: + self.pinButton.setText(self.tr("Change PIN")) + elif hasPin is False: + self.pinButton.setText(self.tr("Set PIN")) + else: + self.pinButton.setEnabled(False) + if forcedPinChange or hasPin is False: + self.lockButton.setEnabled(False) + self.loadPasskeysButton.setEnabled(False) + msg = ( + self.tr("A PIN change is required.") + if forcedPinChange + else self.tr("You must set a PIN first.") + ) + EricMessageBox.information( + self, + self.tr("FIDO2 Security Key Management"), + msg, + ) + + self.passkeysList.clear() + self.on_passkeysList_itemSelectionChanged() + + @pyqtSlot() + def __deviceDisconnected(self): + """ + Private slot handling the device disconnected signal. + """ + self.lockButton.setChecked(False) + self.passkeysList.clear() + self.on_passkeysList_itemSelectionChanged() + + self.lockButton.setEnabled(False) + self.pinButton.setEnabled(False) + self.menuButton.setEnabled(False) + self.loadPasskeysButton.setEnabled(False) self.passkeysList.clear() self.on_passkeysList_itemSelectionChanged() @@ -296,7 +333,7 @@ ) elif not hasPin: msg = self.tr("{0} requires having a PIN. Set a PIN first.").format(feature) - elif self.__manager.forcedPinChange(): + elif self.__manager.pinChangeRequired(): msg = self.tr("The security key is locked. Change the PIN first.") elif powerCycle: msg = self.tr( @@ -372,11 +409,6 @@ 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) @@ -401,7 +433,6 @@ 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) @@ -581,6 +612,89 @@ del rpItem ############################################################################ + ## methods related to device configuration + ############################################################################ + + @pyqtSlot() + def __forcePinChange(self): + """ + Private slot to force a PIN change before the next use. + """ + pin = self.__getRequiredPin(feature=self.tr("Force PIN Change")) + try: + self.__manager.forcePinChange(pin=pin) + except (Fido2DeviceError, Fido2PinError) as err: + self.__handleError( + error=err, + title=self.tr("Force PIN Change"), + message=self.tr("The 'Force PIN Change' flag could not be set."), + ) + + @pyqtSlot() + def __setMinimumPinLength(self): + """ + Private slot to set the minimum PIN length. + """ + currMinLength = self.__manager.getMinimumPinLength() + + minPinLength, ok = QInputDialog.getInt( + self, + self.tr("Set Minimum PIN Length"), + self.tr("Enter the minimum PIN length (between {0} and 63):").format( + currMinLength + ), + 0, + currMinLength, + 63, + 1, + ) + if ok and minPinLength != currMinLength: + pin = self.__getRequiredPin(feature=self.tr("Set Minimum PIN Length")) + try: + self.__manager.setMinimumPinLength(pin=pin, minLength=minPinLength) + EricMessageBox.information( + self, + self.tr("Set Minimum PIN Length"), + self.tr("The minimum PIN length was set to be {0}.").format( + minPinLength + ), + ) + except (Fido2DeviceError, Fido2PinError) as err: + self.__handleError( + error=err, + title=self.tr("Set Minimum PIN Length"), + message=self.tr("The minimum PIN length could not be set."), + ) + + @pyqtSlot() + def __toggleAlwaysUv(self): + """ + Private slot to toggle the state of the 'Always Require User Verification' + flag. + """ + pin = self.__getRequiredPin( + feature=self.tr("Toggle 'Always Require User Verification'") + ) + try: + self.__manager.toggleAlwaysUv(pin=pin) + EricMessageBox.information( + self, + self.tr("Always Require User Verification"), + self.tr("Always Require User Verification is now enabled.") + if self.__manager.getAlwaysUv() + else self.tr("Always Require User Verification is now disabled."), + ) + + except (Fido2DeviceError, Fido2PinError) as err: + self.__handleError( + error=err, + title=self.tr("Toggle 'Always Require User Verification'"), + message=self.tr( + "The 'Always Require User Verification' flag could not be toggled." + ), + ) + + ############################################################################ ## utility methods ############################################################################