src/eric7/WebBrowser/WebAuth/Fido2ManagementDialog.py

branch
eric7
changeset 10857
abcb288e7e17
parent 10856
b19cefceca15
child 10859
399d19fc7eb5
equal deleted inserted replaced
10856:b19cefceca15 10857:abcb288e7e17
8 8
9 from PyQt6.QtCore import Qt, QTimer, pyqtSlot 9 from PyQt6.QtCore import Qt, QTimer, pyqtSlot
10 from PyQt6.QtWidgets import ( 10 from PyQt6.QtWidgets import (
11 QDialog, 11 QDialog,
12 QDialogButtonBox, 12 QDialogButtonBox,
13 QInputDialog,
13 QMenu, 14 QMenu,
14 QToolButton, 15 QToolButton,
15 QTreeWidgetItem, 16 QTreeWidgetItem,
16 ) 17 )
17 18
70 ) 71 )
71 72
72 self.reloadButton.clicked.connect(self.__populateDeviceSelector) 73 self.reloadButton.clicked.connect(self.__populateDeviceSelector)
73 74
74 self.__manager = Fido2Management(parent=self) 75 self.__manager = Fido2Management(parent=self)
76 self.__manager.deviceConnected.connect(self.__deviceConnected)
77 self.__manager.deviceDisconnected.connect(self.__deviceDisconnected)
75 78
76 QTimer.singleShot(0, self.__populateDeviceSelector) 79 QTimer.singleShot(0, self.__populateDeviceSelector)
77 80
78 def __initManagementMenu(self): 81 def __initManagementMenu(self):
79 """ 82 """
84 self.__mgmtMenu.addAction(self.tr("Show Info"), self.__showSecurityKeyInfo) 87 self.__mgmtMenu.addAction(self.tr("Show Info"), self.__showSecurityKeyInfo)
85 self.__mgmtMenu.addSeparator() 88 self.__mgmtMenu.addSeparator()
86 self.__mgmtMenu.addAction( 89 self.__mgmtMenu.addAction(
87 self.tr("Reset Security Key"), self.__resetSecurityKey 90 self.tr("Reset Security Key"), self.__resetSecurityKey
88 ) 91 )
89 # TODO: potentially add these 'config' actions 92 self.__mgmtMenu.addSeparator()
90 # - Force PIN Change 93 self.__forcePinChangeAct = self.__mgmtMenu.addAction(
91 # - Set Minimum PIN Length 94 self.tr("Force PIN Change"), self.__forcePinChange
92 # - Toggle 'Always Require UV' 95 )
96 self.__minPinLengthAct = self.__mgmtMenu.addAction(
97 self.tr("Set Minimum PIN Length"), self.__setMinimumPinLength
98 )
99 self.__toggleAlwaysUvAct = self.__mgmtMenu.addAction(
100 self.tr("Toggle 'Always Require User Verification'"), self.__toggleAlwaysUv
101 )
93 102
94 self.__mgmtMenu.aboutToShow.connect(self.__aboutToShowManagementMenu) 103 self.__mgmtMenu.aboutToShow.connect(self.__aboutToShowManagementMenu)
95 104
96 self.menuButton.setMenu(self.__mgmtMenu) 105 self.menuButton.setMenu(self.__mgmtMenu)
97 106
98 @pyqtSlot() 107 @pyqtSlot()
99 def __aboutToShowManagementMenu(self): 108 def __aboutToShowManagementMenu(self):
100 """ 109 """
101 Private slot to prepare the security key management menu before it is shown. 110 Private slot to prepare the security key management menu before it is shown.
102 """ 111 """
103 # TODO: not implemented yet 112 self.__forcePinChangeAct.setEnabled(
104 pass 113 self.__manager.forcePinChangeSupported()
114 and not self.__manager.pinChangeRequired()
115 )
116 self.__minPinLengthAct.setEnabled(
117 self.__manager.canSetMinimumPinLength()
118 and not self.__manager.pinChangeRequired()
119 )
120 self.__toggleAlwaysUvAct.setEnabled(
121 self.__manager.canToggleAlwaysUv()
122 and not self.__manager.pinChangeRequired()
123 )
105 124
106 ############################################################################ 125 ############################################################################
107 ## methods related to device handling 126 ## methods related to device handling
108 ############################################################################ 127 ############################################################################
109 128
112 """ 131 """
113 Private slot to populate the device selector combo box. 132 Private slot to populate the device selector combo box.
114 """ 133 """
115 self.__manager.disconnectFromDevice() 134 self.__manager.disconnectFromDevice()
116 self.securityKeysComboBox.clear() 135 self.securityKeysComboBox.clear()
117 self.reloadButton.setEnabled(False)
118 136
119 securityKeys = self.__manager.getDevices() 137 securityKeys = self.__manager.getDevices()
120 138
121 if len(securityKeys) != 1: 139 if len(securityKeys) != 1:
122 self.securityKeysComboBox.addItem("") 140 self.securityKeysComboBox.addItem("")
126 securityKey.product_name, securityKey.descriptor.path 144 securityKey.product_name, securityKey.descriptor.path
127 ), 145 ),
128 securityKey, 146 securityKey,
129 ) 147 )
130 148
131 self.reloadButton.setEnabled(True)
132
133 if len(securityKeys) == 0: 149 if len(securityKeys) == 0:
134 EricMessageBox.information( 150 EricMessageBox.information(
135 self, 151 self,
136 self.tr("FIDO2 Security Key Management"), 152 self.tr("FIDO2 Security Key Management"),
137 self.tr( 153 self.tr(
146 Private slot handling the selection of security key. 162 Private slot handling the selection of security key.
147 163
148 @param index index of the selected security key 164 @param index index of the selected security key
149 @type int 165 @type int
150 """ 166 """
151 self.lockButton.setChecked(False)
152 self.__manager.disconnectFromDevice() 167 self.__manager.disconnectFromDevice()
153 168
154 securityKey = self.securityKeysComboBox.itemData(index) 169 securityKey = self.securityKeysComboBox.itemData(index)
155
156 self.lockButton.setEnabled(securityKey is not None)
157 self.pinButton.setEnabled(securityKey is not None)
158 self.menuButton.setEnabled(securityKey is not None)
159 self.loadPasskeysButton.setEnabled(securityKey is not None)
160
161 if securityKey is not None: 170 if securityKey is not None:
162 self.__manager.connectToDevice(securityKey) 171 self.__manager.connectToDevice(securityKey)
163 hasPin = self.__manager.hasPin() 172
164 forcedPinChange = self.__manager.forcedPinChange() 173 @pyqtSlot()
165 if hasPin is True: 174 def __deviceConnected(self):
166 self.pinButton.setText(self.tr("Change PIN")) 175 """
167 elif hasPin is False: 176 Private slot handling the device connected signal.
168 self.pinButton.setText(self.tr("Set PIN")) 177 """
169 else: 178 self.lockButton.setEnabled(True)
170 self.pinButton.setEnabled(False) 179 self.pinButton.setEnabled(True)
171 if forcedPinChange or hasPin is False: 180 self.menuButton.setEnabled(True)
172 self.lockButton.setEnabled(False) 181 self.loadPasskeysButton.setEnabled(True)
173 self.loadPasskeysButton.setEnabled(False) 182
174 msg = ( 183 hasPin = self.__manager.hasPin()
175 self.tr("A PIN change is required.") 184 forcedPinChange = self.__manager.pinChangeRequired()
176 if forcedPinChange 185 if hasPin is True:
177 else self.tr("You must set a PIN first.") 186 self.pinButton.setText(self.tr("Change PIN"))
178 ) 187 elif hasPin is False:
179 EricMessageBox.information( 188 self.pinButton.setText(self.tr("Set PIN"))
180 self, 189 else:
181 self.tr("FIDO2 Security Key Management"), 190 self.pinButton.setEnabled(False)
182 msg, 191 if forcedPinChange or hasPin is False:
183 ) 192 self.lockButton.setEnabled(False)
193 self.loadPasskeysButton.setEnabled(False)
194 msg = (
195 self.tr("A PIN change is required.")
196 if forcedPinChange
197 else self.tr("You must set a PIN first.")
198 )
199 EricMessageBox.information(
200 self,
201 self.tr("FIDO2 Security Key Management"),
202 msg,
203 )
204
205 self.passkeysList.clear()
206 self.on_passkeysList_itemSelectionChanged()
207
208 @pyqtSlot()
209 def __deviceDisconnected(self):
210 """
211 Private slot handling the device disconnected signal.
212 """
213 self.lockButton.setChecked(False)
214 self.passkeysList.clear()
215 self.on_passkeysList_itemSelectionChanged()
216
217 self.lockButton.setEnabled(False)
218 self.pinButton.setEnabled(False)
219 self.menuButton.setEnabled(False)
220 self.loadPasskeysButton.setEnabled(False)
184 221
185 self.passkeysList.clear() 222 self.passkeysList.clear()
186 self.on_passkeysList_itemSelectionChanged() 223 self.on_passkeysList_itemSelectionChanged()
187 224
188 @pyqtSlot(bool) 225 @pyqtSlot(bool)
294 msg = self.tr("{0} is not supported by the selected security key.").format( 331 msg = self.tr("{0} is not supported by the selected security key.").format(
295 feature 332 feature
296 ) 333 )
297 elif not hasPin: 334 elif not hasPin:
298 msg = self.tr("{0} requires having a PIN. Set a PIN first.").format(feature) 335 msg = self.tr("{0} requires having a PIN. Set a PIN first.").format(feature)
299 elif self.__manager.forcedPinChange(): 336 elif self.__manager.pinChangeRequired():
300 msg = self.tr("The security key is locked. Change the PIN first.") 337 msg = self.tr("The security key is locked. Change the PIN first.")
301 elif powerCycle: 338 elif powerCycle:
302 msg = self.tr( 339 msg = self.tr(
303 "The security key is locked because the wrong PIN was entered " 340 "The security key is locked because the wrong PIN was entered "
304 "too many times. To unlock it, remove and reinsert it." 341 "too many times. To unlock it, remove and reinsert it."
370 ) 407 )
371 if dlg.exec() == QDialog.DialogCode.Accepted: 408 if dlg.exec() == QDialog.DialogCode.Accepted:
372 newPin = dlg.getPins()[1] 409 newPin = dlg.getPins()[1]
373 ok, msg = self.__manager.setPin(newPin) 410 ok, msg = self.__manager.setPin(newPin)
374 if ok: 411 if ok:
375 self.lockButton.setEnabled(True)
376 self.lockButton.setChecked(False)
377 self.pinButton.setText(self.tr("Change PIN"))
378 self.loadPasskeysButton.setEnabled(True)
379 self.__manager.reconnectToDevice()
380 EricMessageBox.information(self, title, msg) 412 EricMessageBox.information(self, title, msg)
381 else: 413 else:
382 EricMessageBox.warning(self, title, msg) 414 EricMessageBox.warning(self, title, msg)
383 415
384 @pyqtSlot() 416 @pyqtSlot()
399 ) 431 )
400 if dlg.exec() == QDialog.DialogCode.Accepted: 432 if dlg.exec() == QDialog.DialogCode.Accepted:
401 oldPin, newPin = dlg.getPins() 433 oldPin, newPin = dlg.getPins()
402 ok, msg = self.__manager.changePin(oldPin, newPin) 434 ok, msg = self.__manager.changePin(oldPin, newPin)
403 if ok: 435 if ok:
404 self.lockButton.setChecked(False)
405 EricMessageBox.information(self, title, msg) 436 EricMessageBox.information(self, title, msg)
406 else: 437 else:
407 EricMessageBox.warning(self, title, msg) 438 EricMessageBox.warning(self, title, msg)
408 439
409 @pyqtSlot() 440 @pyqtSlot()
579 index = self.passkeysList.indexOfTopLevelItem(rpItem) 610 index = self.passkeysList.indexOfTopLevelItem(rpItem)
580 self.passkeysList.takeTopLevelItem(index) 611 self.passkeysList.takeTopLevelItem(index)
581 del rpItem 612 del rpItem
582 613
583 ############################################################################ 614 ############################################################################
615 ## methods related to device configuration
616 ############################################################################
617
618 @pyqtSlot()
619 def __forcePinChange(self):
620 """
621 Private slot to force a PIN change before the next use.
622 """
623 pin = self.__getRequiredPin(feature=self.tr("Force PIN Change"))
624 try:
625 self.__manager.forcePinChange(pin=pin)
626 except (Fido2DeviceError, Fido2PinError) as err:
627 self.__handleError(
628 error=err,
629 title=self.tr("Force PIN Change"),
630 message=self.tr("The 'Force PIN Change' flag could not be set."),
631 )
632
633 @pyqtSlot()
634 def __setMinimumPinLength(self):
635 """
636 Private slot to set the minimum PIN length.
637 """
638 currMinLength = self.__manager.getMinimumPinLength()
639
640 minPinLength, ok = QInputDialog.getInt(
641 self,
642 self.tr("Set Minimum PIN Length"),
643 self.tr("Enter the minimum PIN length (between {0} and 63):").format(
644 currMinLength
645 ),
646 0,
647 currMinLength,
648 63,
649 1,
650 )
651 if ok and minPinLength != currMinLength:
652 pin = self.__getRequiredPin(feature=self.tr("Set Minimum PIN Length"))
653 try:
654 self.__manager.setMinimumPinLength(pin=pin, minLength=minPinLength)
655 EricMessageBox.information(
656 self,
657 self.tr("Set Minimum PIN Length"),
658 self.tr("The minimum PIN length was set to be {0}.").format(
659 minPinLength
660 ),
661 )
662 except (Fido2DeviceError, Fido2PinError) as err:
663 self.__handleError(
664 error=err,
665 title=self.tr("Set Minimum PIN Length"),
666 message=self.tr("The minimum PIN length could not be set."),
667 )
668
669 @pyqtSlot()
670 def __toggleAlwaysUv(self):
671 """
672 Private slot to toggle the state of the 'Always Require User Verification'
673 flag.
674 """
675 pin = self.__getRequiredPin(
676 feature=self.tr("Toggle 'Always Require User Verification'")
677 )
678 try:
679 self.__manager.toggleAlwaysUv(pin=pin)
680 EricMessageBox.information(
681 self,
682 self.tr("Always Require User Verification"),
683 self.tr("Always Require User Verification is now enabled.")
684 if self.__manager.getAlwaysUv()
685 else self.tr("Always Require User Verification is now disabled."),
686 )
687
688 except (Fido2DeviceError, Fido2PinError) as err:
689 self.__handleError(
690 error=err,
691 title=self.tr("Toggle 'Always Require User Verification'"),
692 message=self.tr(
693 "The 'Always Require User Verification' flag could not be toggled."
694 ),
695 )
696
697 ############################################################################
584 ## utility methods 698 ## utility methods
585 ############################################################################ 699 ############################################################################
586 700
587 def __handleError(self, error, title, message): 701 def __handleError(self, error, title, message):
588 """ 702 """

eric ide

mercurial