src/eric7/WebBrowser/WebAuth/Fido2ManagementDialog.py

branch
eric7
changeset 10856
b19cefceca15
parent 10854
30c45bd597e6
child 10857
abcb288e7e17
equal deleted inserted replaced
10855:9082eb8f6571 10856:b19cefceca15
5 """ 5 """
6 Module implementing a dialog to manage FIDO2 security keys. 6 Module implementing a dialog to manage FIDO2 security keys.
7 """ 7 """
8 8
9 from PyQt6.QtCore import Qt, QTimer, pyqtSlot 9 from PyQt6.QtCore import Qt, QTimer, pyqtSlot
10 from PyQt6.QtWidgets import QDialog, QTreeWidgetItem 10 from PyQt6.QtWidgets import (
11 QDialog,
12 QDialogButtonBox,
13 QMenu,
14 QToolButton,
15 QTreeWidgetItem,
16 )
11 17
12 from eric7.EricGui import EricPixmapCache 18 from eric7.EricGui import EricPixmapCache
13 from eric7.EricGui.EricOverrideCursor import EricOverrideCursor 19 from eric7.EricGui.EricOverrideCursor import EricOverrideCursor
14 from eric7.EricWidgets import EricMessageBox 20 from eric7.EricWidgets import EricMessageBox
15 21
29 RelyingPartyColumn = 0 35 RelyingPartyColumn = 0
30 CredentialIdColumn = 1 36 CredentialIdColumn = 1
31 DisplayNameColumn = 2 37 DisplayNameColumn = 2
32 UserNameColumn = 3 38 UserNameColumn = 3
33 39
34 def __init__(self, parent=None): 40 def __init__(self, standalone=False, parent=None):
35 """ 41 """
36 Constructor 42 Constructor
37 43
44 @param standalone flag indicating the standalone management application
45 (defaults to False)
46 @type bool (optional)
38 @param parent reference to the parent widget (defaults to None) 47 @param parent reference to the parent widget (defaults to None)
39 @type QWidget (optional) 48 @type QWidget (optional)
40 """ 49 """
41 super().__init__(parent) 50 super().__init__(parent)
42 self.setupUi(self) 51 self.setupUi(self)
43 52
44 self.reloadButton.setIcon(EricPixmapCache.getIcon("reload")) 53 self.reloadButton.setIcon(EricPixmapCache.getIcon("reload"))
45 self.lockButton.setIcon(EricPixmapCache.getIcon("locked")) 54 self.lockButton.setIcon(EricPixmapCache.getIcon("locked"))
46 55
56 self.menuButton.setObjectName("fido2_supermenu_button")
57 self.menuButton.setIcon(EricPixmapCache.getIcon("superMenu"))
58 self.menuButton.setToolTip(self.tr("Security Key Management Menu"))
59 self.menuButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
60 self.menuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
61 self.menuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
62 self.menuButton.setAutoRaise(True)
63 self.menuButton.setShowMenuInside(True)
64
65 self.__initManagementMenu()
66
67 if standalone:
68 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setText(
69 self.tr("Quit")
70 )
71
47 self.reloadButton.clicked.connect(self.__populateDeviceSelector) 72 self.reloadButton.clicked.connect(self.__populateDeviceSelector)
48 73
49 self.__manager = Fido2Management(parent=self) 74 self.__manager = Fido2Management(parent=self)
50 ##self.__manager.deviceConnected.connect(self.__deviceConnected)
51 ##self.__manager.deviceDisconnected.connect(self.__deviceDisconnected)
52 75
53 QTimer.singleShot(0, self.__populateDeviceSelector) 76 QTimer.singleShot(0, self.__populateDeviceSelector)
77
78 def __initManagementMenu(self):
79 """
80 Private method to initialize the security key management menu with
81 actions not needed so much.
82 """
83 self.__mgmtMenu = QMenu()
84 self.__mgmtMenu.addAction(self.tr("Show Info"), self.__showSecurityKeyInfo)
85 self.__mgmtMenu.addSeparator()
86 self.__mgmtMenu.addAction(
87 self.tr("Reset Security Key"), self.__resetSecurityKey
88 )
89 # TODO: potentially add these 'config' actions
90 # - Force PIN Change
91 # - Set Minimum PIN Length
92 # - Toggle 'Always Require UV'
93
94 self.__mgmtMenu.aboutToShow.connect(self.__aboutToShowManagementMenu)
95
96 self.menuButton.setMenu(self.__mgmtMenu)
97
98 @pyqtSlot()
99 def __aboutToShowManagementMenu(self):
100 """
101 Private slot to prepare the security key management menu before it is shown.
102 """
103 # TODO: not implemented yet
104 pass
54 105
55 ############################################################################ 106 ############################################################################
56 ## methods related to device handling 107 ## methods related to device handling
57 ############################################################################ 108 ############################################################################
58 109
102 153
103 securityKey = self.securityKeysComboBox.itemData(index) 154 securityKey = self.securityKeysComboBox.itemData(index)
104 155
105 self.lockButton.setEnabled(securityKey is not None) 156 self.lockButton.setEnabled(securityKey is not None)
106 self.pinButton.setEnabled(securityKey is not None) 157 self.pinButton.setEnabled(securityKey is not None)
107 self.showInfoButton.setEnabled(securityKey is not None) 158 self.menuButton.setEnabled(securityKey is not None)
108 self.resetButton.setEnabled(securityKey is not None)
109 self.loadPasskeysButton.setEnabled(securityKey is not None) 159 self.loadPasskeysButton.setEnabled(securityKey is not None)
110 160
111 if securityKey is not None: 161 if securityKey is not None:
112 self.__manager.connectToDevice(securityKey) 162 self.__manager.connectToDevice(securityKey)
113 hasPin = self.__manager.hasPin() 163 hasPin = self.__manager.hasPin()
164 # lock the selected security key 214 # lock the selected security key
165 self.lockButton.setIcon(EricPixmapCache.getIcon("locked")) 215 self.lockButton.setIcon(EricPixmapCache.getIcon("locked"))
166 self.__manager.lockDevice() 216 self.__manager.lockDevice()
167 217
168 @pyqtSlot() 218 @pyqtSlot()
169 def on_showInfoButton_clicked(self): 219 def __showSecurityKeyInfo(self):
170 """ 220 """
171 Slot documentation goes here. 221 Private slot to show some info about the selected security key.
172 """ 222 """
173 # TODO: not implemented yet 223 from .Fido2InfoDialog import Fido2InfoDialog
174 pass 224
225 securityKey = self.securityKeysComboBox.currentData()
226 dlg = Fido2InfoDialog(
227 header=securityKey.product_name, manager=self.__manager, parent=self
228 )
229 dlg.exec()
230
231 @pyqtSlot()
232 def __resetSecurityKey(self):
233 """
234 Private slot to reset the selected security key.
235 """
236 title = self.tr("Reset Security Key")
237
238 yes = EricMessageBox.yesNo(
239 parent=self,
240 title=title,
241 text=self.tr(
242 "<p>Shall the selected security key really be reset?</p><p><b>WARNING"
243 ":</b> This will delete all passkeys and restore factory settings.</p>"
244 ),
245 )
246 if yes:
247 if len(self.__manager.getDevices()) != 1:
248 EricMessageBox.critical(
249 self,
250 title=title,
251 text=self.tr(
252 "Only one security key can be connected to perform a reset."
253 " Remove all other security keys and try again."
254 ),
255 )
256 return
257
258 EricMessageBox.information(
259 self,
260 title=title,
261 text=self.tr(
262 "Confirm this dialog then remove and re-insert the security key."
263 " Confirm the reset by touching it."
264 ),
265 )
266
267 ok, msg = self.__manager.resetDevice()
268 if ok:
269 EricMessageBox.information(self, title, msg)
270 else:
271 EricMessageBox.warning(self, title, msg)
272
273 self.__populateDeviceSelector()
175 274
176 ############################################################################ 275 ############################################################################
177 ## methods related to PIN handling 276 ## methods related to PIN handling
178 ############################################################################ 277 ############################################################################
179 278
239 retries = self.__manager.getPinRetries()[0] 338 retries = self.__manager.getPinRetries()[0]
240 title = self.tr("PIN required") if feature is None else feature 339 title = self.tr("PIN required") if feature is None else feature
241 dlg = Fido2PinDialog( 340 dlg = Fido2PinDialog(
242 mode=Fido2PinDialogMode.GET, 341 mode=Fido2PinDialogMode.GET,
243 title=title, 342 title=title,
244 message=self.tr( 343 message=self.tr("Enter the PIN to unlock the security key."),
245 "Enter the PIN to unlock the security key (%n attempt(s)"
246 " remaining.",
247 "",
248 retries,
249 ),
250 minLength=self.__manager.getMinimumPinLength(), 344 minLength=self.__manager.getMinimumPinLength(),
345 retries=retries,
251 parent=self, 346 parent=self,
252 ) 347 )
253 if dlg.exec() == QDialog.DialogCode.Accepted: 348 if dlg.exec() == QDialog.DialogCode.Accepted:
254 return dlg.getPins()[0] 349 return dlg.getPins()[0]
255 else: 350 else:
260 @pyqtSlot() 355 @pyqtSlot()
261 def __setPin(self): 356 def __setPin(self):
262 """ 357 """
263 Private slot to set a PIN for the selected security key. 358 Private slot to set a PIN for the selected security key.
264 """ 359 """
265 # TODO: not implemented yet 360 retries = self.__manager.getPinRetries()[0]
266 pass 361 title = self.tr("Set PIN")
362
363 dlg = Fido2PinDialog(
364 mode=Fido2PinDialogMode.SET,
365 title=title,
366 message=self.tr("Enter the PIN for the security key."),
367 minLength=self.__manager.getMinimumPinLength(),
368 retries=retries,
369 parent=self,
370 )
371 if dlg.exec() == QDialog.DialogCode.Accepted:
372 newPin = dlg.getPins()[1]
373 ok, msg = self.__manager.setPin(newPin)
374 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)
381 else:
382 EricMessageBox.warning(self, title, msg)
267 383
268 @pyqtSlot() 384 @pyqtSlot()
269 def __changePin(self): 385 def __changePin(self):
270 """ 386 """
271 Private slot to set a PIN for the selected security key. 387 Private slot to change the PIN of the selected security key.
272 """ 388 """
273 # TODO: not implemented yet 389 retries = self.__manager.getPinRetries()[0]
274 pass 390 title = self.tr("Change PIN")
391
392 dlg = Fido2PinDialog(
393 mode=Fido2PinDialogMode.CHANGE,
394 title=title,
395 message=self.tr("Enter the current and new PINs."),
396 minLength=self.__manager.getMinimumPinLength(),
397 retries=retries,
398 parent=self,
399 )
400 if dlg.exec() == QDialog.DialogCode.Accepted:
401 oldPin, newPin = dlg.getPins()
402 ok, msg = self.__manager.changePin(oldPin, newPin)
403 if ok:
404 self.lockButton.setChecked(False)
405 EricMessageBox.information(self, title, msg)
406 else:
407 EricMessageBox.warning(self, title, msg)
275 408
276 @pyqtSlot() 409 @pyqtSlot()
277 def on_pinButton_clicked(self): 410 def on_pinButton_clicked(self):
278 """ 411 """
279 Private slot to set or change the PIN for the selected security key. 412 Private slot to set or change the PIN for the selected security key.
280 """ 413 """
281 # TODO: not implemented yet
282 if self.__manager.hasPin(): 414 if self.__manager.hasPin():
283 self.__changePin() 415 self.__changePin()
284 else: 416 else:
285 self.__setPin() 417 self.__setPin()
286 418
343 ) 475 )
344 476
345 @pyqtSlot() 477 @pyqtSlot()
346 def on_loadPasskeysButton_clicked(self): 478 def on_loadPasskeysButton_clicked(self):
347 """ 479 """
348 Slot documentation goes here. 480 Private slot to (re-)populate the passkeys list.
349 """ 481 """
350 self.__populatePasskeysList() 482 self.__populatePasskeysList()
351 483
352 @pyqtSlot() 484 @pyqtSlot()
353 def on_passkeysList_itemSelectionChanged(self): 485 def on_passkeysList_itemSelectionChanged(self):
354 """ 486 """
355 Slot documentation goes here. 487 Private slot handling the selection of a passkey.
356 """ 488 """
357 enableButtons = ( 489 enableButtons = (
358 len(self.passkeysList.selectedItems()) == 1 490 len(self.passkeysList.selectedItems()) == 1
359 and self.passkeysList.selectedItems()[0].parent() is not None 491 and self.passkeysList.selectedItems()[0].parent() is not None
360 ) 492 )

eric ide

mercurial